From patchwork Wed Aug 1 09:33:30 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rajeshwari Shinde X-Patchwork-Id: 10436 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 3A1C524048 for ; Wed, 1 Aug 2012 09:27:26 +0000 (UTC) Received: from mail-gh0-f180.google.com (mail-gh0-f180.google.com [209.85.160.180]) by fiordland.canonical.com (Postfix) with ESMTP id E28FAA18EDD for ; Wed, 1 Aug 2012 09:27:25 +0000 (UTC) Received: by mail-gh0-f180.google.com with SMTP id z12so6902136ghb.11 for ; Wed, 01 Aug 2012 02:27:25 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-forwarded-to:x-forwarded-for:delivered-to:received-spf:x-auditid :from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references :x-brightmail-tracker:x-tm-as-mml:x-gm-message-state; bh=+Ytjb4ulQXr/A7h+J30ZDcCiUyEh+OISDPOb5GtWd4I=; b=UI6t9IOPRkXYPLCzPIyzYhmyURkFi9MEWOG5Ok+RSQNCbx+20b7Mpq777mCexHs8l7 enpdJyALMCwrqnvldPssmz+CbMNoFaiivSg0phnMmH0yRZzUlW6BDc4ZvTWHdY5jLCg7 rvrrXe1LQYNxCSofDUR8A8iMTjZOIRaFmOq9GE+Lr3+GyIRKHUb5ShX9Fb+nFKSNxVWi ZTVH0AAnORbVxwZqqShQ4Qw8tURBoxg1xfrKCxJYg5b0FyUoPSseXbwM6YQS1SZI71i8 PiWuB03+SvSxcyx6/4hyX5wE9NmSetNrVSaS8QGSl3cQ9sqUWHsvg9KCs3CtV+ytqTTY sefA== Received: by 10.50.163.5 with SMTP id ye5mr3370789igb.51.1343813245126; Wed, 01 Aug 2012 02:27:25 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.50.87.40 with SMTP id u8csp187712igz; Wed, 1 Aug 2012 02:27:24 -0700 (PDT) Received: by 10.68.217.37 with SMTP id ov5mr50460505pbc.12.1343813244053; Wed, 01 Aug 2012 02:27:24 -0700 (PDT) Received: from mailout3.samsung.com (mailout3.samsung.com. [203.254.224.33]) by mx.google.com with ESMTP id ur10si5130537pbc.338.2012.08.01.02.27.23; Wed, 01 Aug 2012 02:27:24 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of rajeshwari.s@samsung.com designates 203.254.224.33 as permitted sender) client-ip=203.254.224.33; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of rajeshwari.s@samsung.com designates 203.254.224.33 as permitted sender) smtp.mail=rajeshwari.s@samsung.com Received: from epcpsbgm1.samsung.com (mailout3.samsung.com [203.254.224.33]) by mailout3.samsung.com (Oracle Communications Messaging Server 7u4-24.01(7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTP id <0M82003GGKWD1X40@mailout3.samsung.com>; Wed, 01 Aug 2012 18:27:22 +0900 (KST) X-AuditID: cbfee61a-b7f616d000004b7e-37-5018f67aa2d0 Received: from epmmp2 ( [203.254.227.17]) by epcpsbgm1.samsung.com (EPCPMTA) with SMTP id F2.C5.19326.A76F8105; Wed, 01 Aug 2012 18:27:22 +0900 (KST) Received: from rajeshwari-linux.sisodomain.com ([107.108.215.115]) by mmp2.samsung.com (Oracle Communications Messaging Server 7u4-24.01(7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTPA id <0M820029AKX8FS00@mmp2.samsung.com>; Wed, 01 Aug 2012 18:27:22 +0900 (KST) From: Rajeshwari Shinde To: u-boot@lists.denx.de Cc: patches@linaro.org, alim.akhtar@samsung.com, sjg@chromium.org, mk7.kang@samsung.com, chander.kashyap@linaro.org Subject: [PATCH 5/7 V3] SPI: Add SPI Driver for EXYNOS. Date: Wed, 01 Aug 2012 15:03:30 +0530 Message-id: <1343813612-1587-6-git-send-email-rajeshwari.s@samsung.com> X-Mailer: git-send-email 1.7.4.4 In-reply-to: <1343813612-1587-1-git-send-email-rajeshwari.s@samsung.com> References: <1343813612-1587-1-git-send-email-rajeshwari.s@samsung.com> X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFnrKJMWRmVeSWpSXmKPExsVy+t9jQd2qbxIBBpO79Swerr/JYjHl8BcW ByaPO9f2sAUwRnHZpKTmZJalFunbJXBlTFx7jqlgcX7FnfOvWRsYV0R2MXJySAiYSFxpusEM YYtJXLi3nq2LkYtDSGA6o8SCtpXsEM5EJoll15qYQKrYBIwktp6cxghiiwhISPzqvwpkc3Aw C5RKTJmYBxIWFjCXmDvzHTtImEVAVaJ9cjFImFfAXWLL6252iF0KEsemfmUFKeEU8JD4vpMN JCwEVHLr7S7WCYy8CxgZVjGKphYkFxQnpeca6hUn5haX5qXrJefnbmIEe/6Z1A7GlQ0WhxgF OBiVeHgnmkgECLEmlhVX5h5ilOBgVhLhLbgJFOJNSaysSi3Kjy8qzUktPsQozcGiJM5r7P3V X0ggPbEkNTs1tSC1CCbLxMEp1cAoVZ3Bd4G7qSi7wSh/4RfN+4sM5iS+5nn6r27Zf85tS5Rs GVVcC7jMl/aIXm3dpGkeVztTafYKroefvz2yzJLae2d1/we3e+tbxRgZb//jyfxnbHPoj5j4 q8OKl1fNFljDuSHEo8NvhSt31LW/MzqY7j1Y+WZ1+mJzr9S4tLfZ5/bpPDy/68BeJZbijERD Leai4kQAXBK5J/gBAAA= X-TM-AS-MML: No X-Gm-Message-State: ALoCoQnCwRRLj3fkdbrR79RX2UuSjdbyJ7/qAj2pXoc96dRrLyhwhfKIijANXkhLx+uB7We/l+X8 This patch adds SPI driver for EXYNOS. Signed-off-by: Simon Glass Signed-off-by: Padmavathi Venna Signed-off-by: Gabe Black Signed-off-by: Rajeshwari Shinde --- Changes in V2: - None. Changes in V3: - Removed SPI_SLAVE flag. arch/arm/include/asm/arch-exynos/spi.h | 78 +++++++ drivers/spi/Makefile | 1 + drivers/spi/exynos_spi.c | 368 ++++++++++++++++++++++++++++++++ 3 files changed, 447 insertions(+), 0 deletions(-) create mode 100644 arch/arm/include/asm/arch-exynos/spi.h create mode 100644 drivers/spi/exynos_spi.c diff --git a/arch/arm/include/asm/arch-exynos/spi.h b/arch/arm/include/asm/arch-exynos/spi.h new file mode 100644 index 0000000..7cab1e9 --- /dev/null +++ b/arch/arm/include/asm/arch-exynos/spi.h @@ -0,0 +1,78 @@ +/* + * (C) Copyright 2012 SAMSUNG Electronics + * Padmavathi Venna + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ASM_ARCH_EXYNOS_COMMON_SPI_H_ +#define __ASM_ARCH_EXYNOS_COMMON_SPI_H_ + +#ifndef __ASSEMBLY__ + +/* SPI peripheral register map; padded to 64KB */ +struct exynos_spi { + unsigned int ch_cfg; /* 0x00 */ + unsigned char reserved0[4]; + unsigned int mode_cfg; /* 0x08 */ + unsigned int cs_reg; /* 0x0c */ + unsigned char reserved1[4]; + unsigned int spi_sts; /* 0x14 */ + unsigned int tx_data; /* 0x18 */ + unsigned int rx_data; /* 0x1c */ + unsigned int pkt_cnt; /* 0x20 */ + unsigned char reserved2[4]; + unsigned char reserved3[4]; + unsigned int fb_clk; /* 0x2c */ + unsigned char padding[0xffd0]; +}; + +#define EXYNOS_SPI_MAX_FREQ 50000000 + +#define SPI_TIMEOUT_MS 10 + +/* SPI_CHCFG */ +#define SPI_CH_HS_EN (1 << 6) +#define SPI_CH_RST (1 << 5) +#define SPI_SLAVE_MODE (1 << 4) +#define SPI_CH_CPOL_L (1 << 3) +#define SPI_CH_CPHA_B (1 << 2) +#define SPI_RX_CH_ON (1 << 1) +#define SPI_TX_CH_ON (1 << 0) + +/* SPI_MODECFG */ +#define SPI_MODE_CH_WIDTH_WORD (0x2 << 29) +#define SPI_MODE_BUS_WIDTH_WORD (0x2 << 17) + +/* SPI_CSREG */ +#define SPI_SLAVE_SIG_INACT (1 << 0) + +/* SPI_STS */ +#define SPI_ST_TX_DONE (1 << 25) +#define SPI_FIFO_LVL_MASK 0x1ff +#define SPI_TX_LVL_OFFSET 6 +#define SPI_RX_LVL_OFFSET 15 + +/* Feedback Delay */ +#define SPI_CLK_BYPASS (0 << 0) +#define SPI_FB_DELAY_90 (1 << 0) +#define SPI_FB_DELAY_180 (2 << 0) +#define SPI_FB_DELAY_270 (3 << 0) + +/* Packet Count */ +#define SPI_PACKET_CNT_EN (1 << 16) + +#endif /* __ASSEMBLY__ */ +#endif diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index c20f1f2..f15adf0 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -33,6 +33,7 @@ COBJS-$(CONFIG_ATMEL_SPI) += atmel_spi.o COBJS-$(CONFIG_BFIN_SPI) += bfin_spi.o COBJS-$(CONFIG_CF_SPI) += cf_spi.o COBJS-$(CONFIG_DAVINCI_SPI) += davinci_spi.o +COBJS-$(CONFIG_EXYNOS_SPI) += exynos_spi.o COBJS-$(CONFIG_KIRKWOOD_SPI) += kirkwood_spi.o COBJS-$(CONFIG_MPC52XX_SPI) += mpc52xx_spi.o COBJS-$(CONFIG_MPC8XXX_SPI) += mpc8xxx_spi.o diff --git a/drivers/spi/exynos_spi.c b/drivers/spi/exynos_spi.c new file mode 100644 index 0000000..a633f95 --- /dev/null +++ b/drivers/spi/exynos_spi.c @@ -0,0 +1,368 @@ +/* + * (C) Copyright 2012 SAMSUNG Electronics + * Padmavathi Venna + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +/* Information about each SPI controller */ +struct spi_bus { + enum periph_id periph_id; + s32 frequency; /* Default clock frequency, -1 for none */ + struct exynos_spi *regs; + int inited; /* 1 if this bus is ready for use */ +}; + +/* A list of spi buses that we know about */ +static struct spi_bus spi_bus[EXYNOS5_SPI_NUM_CONTROLLERS]; +static unsigned int bus_count; + +struct exynos_spi_slave { + struct spi_slave slave; + struct exynos_spi *regs; + unsigned int freq; /* Default frequency */ + unsigned int mode; + enum periph_id periph_id; /* Peripheral ID for this device */ + unsigned int fifo_size; +}; + +static struct spi_bus *spi_get_bus(unsigned dev_index) +{ + if (dev_index < bus_count) + return &spi_bus[dev_index]; + debug("%s: invalid bus %d", __func__, dev_index); + + return NULL; +} + +static inline struct exynos_spi_slave *to_exynos_spi(struct spi_slave *slave) +{ + return container_of(slave, struct exynos_spi_slave, slave); +} + +/** + * Setup the driver private data + * + * @param bus ID of the bus that the slave is attached to + * @param cs ID of the chip select connected to the slave + * @param max_hz Required spi frequency + * @param mode Required spi mode (clk polarity, clk phase and + * master or slave) + * @return new device or NULL + */ +struct spi_slave *spi_setup_slave(unsigned int busnum, unsigned int cs, + unsigned int max_hz, unsigned int mode) +{ + struct exynos_spi_slave *spi_slave; + struct spi_bus *bus; + + if (!spi_cs_is_valid(busnum, cs)) { + debug("%s: Invalid bus/chip select %d, %d\n", __func__, + busnum, cs); + return NULL; + } + + spi_slave = malloc(sizeof(*spi_slave)); + if (!spi_slave) { + debug("%s: Could not allocate spi_slave\n", __func__); + return NULL; + } + + bus = &spi_bus[busnum]; + spi_slave->slave.bus = busnum; + spi_slave->slave.cs = cs; + spi_slave->regs = bus->regs; + spi_slave->mode = mode; + spi_slave->periph_id = bus->periph_id; + if (bus->periph_id == PERIPH_ID_SPI0) + spi_slave->fifo_size = 256; + else + spi_slave->fifo_size = 64; + + spi_slave->freq = bus->frequency; + if (max_hz) + spi_slave->freq = min(max_hz, spi_slave->freq); + + return &spi_slave->slave; +} + +/** + * Free spi controller + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + */ +void spi_free_slave(struct spi_slave *slave) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + + free(spi_slave); +} + +/** + * Flush spi tx, rx fifos and reset the SPI controller + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + */ +void spi_flush_fifo(struct spi_slave *slave) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + struct exynos_spi *regs = spi_slave->regs; + + clrsetbits_le32(®s->ch_cfg, SPI_CH_HS_EN, SPI_CH_RST); + clrbits_le32(®s->ch_cfg, SPI_CH_RST); + setbits_le32(®s->ch_cfg, SPI_TX_CH_ON | SPI_RX_CH_ON); +} + +/** + * Initialize the spi base registers, set the required clock frequency and + * initialize the gpios + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + * @return zero on success else a negative value + */ +int spi_claim_bus(struct spi_slave *slave) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + struct exynos_spi *regs = spi_slave->regs; + u32 reg = 0; + int ret; + + ret = spi_set_clock_rate(spi_slave->periph_id, + spi_slave->freq); + if (ret < 0) { + debug("%s: Failed to setup spi clock\n", __func__); + return ret; + } + + exynos_pinmux_config(spi_slave->periph_id, PINMUX_FLAG_NONE); + + spi_flush_fifo(slave); + + reg = readl(®s->ch_cfg); + reg &= ~(SPI_CH_CPHA_B | SPI_CH_CPOL_L); + + if (spi_slave->mode & SPI_CPHA) + reg |= SPI_CH_CPHA_B; + + if (spi_slave->mode & SPI_CPOL) + reg |= SPI_CH_CPOL_L; + + writel(reg, ®s->ch_cfg); + writel(SPI_FB_DELAY_180, ®s->fb_clk); + + return 0; +} + +/** + * Reset the spi H/W and flush the tx and rx fifos + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + */ +void spi_release_bus(struct spi_slave *slave) +{ + spi_flush_fifo(slave); +} + +static void spi_get_fifo_levels(struct exynos_spi *regs, + int *rx_lvl, int *tx_lvl) +{ + uint32_t spi_sts = readl(®s->spi_sts); + + *rx_lvl = (spi_sts >> SPI_RX_LVL_OFFSET) & SPI_FIFO_LVL_MASK; + *tx_lvl = (spi_sts >> SPI_TX_LVL_OFFSET) & SPI_FIFO_LVL_MASK; +} + +/** + * If there's something to transfer, do a software reset and set a + * transaction size. + * + * @param regs SPI peripheral registers + * @param count Number of bytes to transfer + */ +static void spi_request_bytes(struct exynos_spi *regs, int count) +{ + assert(count && count < (1 << 16)); + setbits_le32(®s->ch_cfg, SPI_CH_RST); + clrbits_le32(®s->ch_cfg, SPI_CH_RST); + writel(count | SPI_PACKET_CNT_EN, ®s->pkt_cnt); +} + +static void spi_rx_tx(struct exynos_spi_slave *spi_slave, int todo, + void **dinp, void const **doutp) +{ + struct exynos_spi *regs = spi_slave->regs; + uchar *rxp = *dinp; + const uchar *txp = *doutp; + int rx_lvl, tx_lvl; + uint out_bytes, in_bytes; + + out_bytes = in_bytes = todo; + + /* + * If there's something to send, do a software reset and set a + * transaction size. + */ + spi_request_bytes(regs, todo); + + /* + * Bytes are transmitted/received in pairs. Wait to receive all the + * data because then transmission will be done as well. + */ + while (in_bytes) { + int temp; + + /* Keep the fifos full/empty. */ + spi_get_fifo_levels(regs, &rx_lvl, &tx_lvl); + if (tx_lvl < spi_slave->fifo_size && out_bytes) { + temp = txp ? *txp++ : 0xff; + writel(temp, ®s->tx_data); + out_bytes--; + } + if (rx_lvl > 0 && in_bytes) { + temp = readl(®s->rx_data); + if (rxp) + *rxp++ = temp; + in_bytes--; + } + } + *dinp = rxp; + *doutp = txp; +} + +/** + * Transfer and receive data + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + * @param bitlen No of bits to tranfer or receive + * @param dout Pointer to transfer buffer + * @param din Pointer to receive buffer + * @param flags Flags for transfer begin and end + * @return zero on success else a negative value + */ +int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, + void *din, unsigned long flags) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + int upto, todo; + int bytelen; + + /* spi core configured to do 8 bit transfers */ + if (bitlen % 8) { + debug("Non byte aligned SPI transfer.\n"); + return -1; + } + + /* Start the transaction, if necessary. */ + if ((flags & SPI_XFER_BEGIN)) + spi_cs_activate(slave); + + /* Exynos SPI limits each transfer to 65535 bytes */ + bytelen = bitlen / 8; + for (upto = 0; upto < bytelen; upto += todo) { + todo = min(bytelen - upto, (1 << 16) - 1); + spi_rx_tx(spi_slave, todo, &din, &dout); + } + + /* Stop the transaction, if necessary. */ + if ((flags & SPI_XFER_END)) + spi_cs_deactivate(slave); + + return 0; +} + +/** + * Validates the bus and chip select numbers + * + * @param bus ID of the bus that the slave is attached to + * @param cs ID of the chip select connected to the slave + * @return one on success else zero + */ +int spi_cs_is_valid(unsigned int bus, unsigned int cs) +{ + return spi_get_bus(bus) && cs == 0; +} + +/** + * Activate the CS by driving it LOW + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + */ +void spi_cs_activate(struct spi_slave *slave) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + + clrbits_le32(&spi_slave->regs->cs_reg, SPI_SLAVE_SIG_INACT); + debug("Activate CS, bus %d\n", spi_slave->slave.bus); +} + +/** + * Deactivate the CS by driving it HIGH + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + */ +void spi_cs_deactivate(struct spi_slave *slave) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + + setbits_le32(&spi_slave->regs->cs_reg, SPI_SLAVE_SIG_INACT); + debug("Deactivate CS, bus %d\n", spi_slave->slave.bus); +} + +static inline struct exynos_spi *get_spi_base(int dev_index) +{ + if (dev_index < 3) + return (struct exynos_spi *)samsung_get_base_spi() + dev_index; + else + return (struct exynos_spi *)samsung_get_base_spi_isp() + + (dev_index - 3); +} + +/* Sadly there is no error return from this function */ +void spi_init(void) +{ + int i; + struct spi_bus *bus; + for (i = 0; i < EXYNOS5_SPI_NUM_CONTROLLERS; i++) { + bus = &spi_bus[i]; + bus->regs = get_spi_base(i); + bus->periph_id = PERIPH_ID_SPI0 + i; + if (i == 1) + bus->frequency = 2500000; + else + bus->frequency = 500000; + bus->inited = 1; + bus_count++; + } +}