From mboxrd@z Thu Jan 1 00:00:00 1970 From: Valentin Longchamp Subject: Re: [RESEND][PATCH] SPI: Add SPI driver for most known i.MX SoCs Date: Wed, 13 May 2009 10:50:19 +0200 Message-ID: <4A0A89CB.6070605@epfl.ch> References: <20090512123151.GG29278@pengutronix.de> Mime-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org, linux-arm-kernel-xIg/pKzrS19vn6HldHNs0ANdhmdF6hFW@public.gmane.org, Wolfram Sang To: Sascha Hauer Return-path: In-Reply-To: <20090512123151.GG29278-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: spi-devel-general-bounces-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org List-Id: linux-spi.vger.kernel.org Sascha Hauer wrote: > Hi all, > > I got no comments the last time I sent this, here is an updated version > which now also works on i.MX35. Any input on this? I would gladly test it on mx31moboard. What MX13783 driver did you use to test it with ? Val > > Sascha > > > v2: Updated and tested on i.MX35 > > While there already is a SPI driver for i.MX1 in the tree there are > good reasons to replace it: > > - The inkernel driver implements a full blown SPI driver, but > the hardware can be fully supported using a bitbang driver. > This greatly reduces the size and complexity of the driver. > - The inkernel driver only works on i.MX1 SoCs. Unfortunately > Freescale decided to randomly mix the register bits for each > new SoC, This is quite hard to handle with the current driver > since it has register accesses in many places. > - The DMA API of the durrent driver is broken for arch-mx1 (as opposed > to arch-imx) and nobody cared to fix it yet. > > This driver has been tested on i.MX1/i.MX27/i.MX35 with an AT25 type > EEPROM and on i.MX27/i.MX31 with a Freescale MC13783 PMIC. > > Signed-off-by: Sascha Hauer > --- > arch/arm/plat-mxc/include/mach/spi.h | 27 ++ > drivers/spi/Kconfig | 7 +- > drivers/spi/Makefile | 2 +- > drivers/spi/mxc_spi.c | 685 ++++++++++++++++++++++++++++++++++ > 4 files changed, 717 insertions(+), 4 deletions(-) > create mode 100644 arch/arm/plat-mxc/include/mach/spi.h > create mode 100644 drivers/spi/mxc_spi.c > > diff --git a/arch/arm/plat-mxc/include/mach/spi.h b/arch/arm/plat-mxc/include/mach/spi.h > new file mode 100644 > index 0000000..08be445 > --- /dev/null > +++ b/arch/arm/plat-mxc/include/mach/spi.h > @@ -0,0 +1,27 @@ > + > +#ifndef __MACH_SPI_H_ > +#define __MACH_SPI_H_ > + > +/* > + * struct spi_imx_master - device.platform_data for SPI controller devices. > + * @chipselect: Array of chipselects for this master. Numbers >= 0 mean gpio > + * pins, numbers < 0 mean internal CSPI chipselects according > + * to MXC_SPI_CS(). Normally you want to use gpio based chip > + * selects as the CSPI module tries to be intelligent about > + * when to assert the chipselect: The CSPI module deasserts the > + * chipselect once it runs out of input data. The other problem > + * is that it is not possible to mix between high active and low > + * active chipselects on one single bus using the internal > + * chipselects. Unfortunately Freescale decided to put some > + * chipselects on dedicated pins which are not usable as gpios, > + * so we have to support the internal chipselects. > + * @num_chipselect: ARRAY_SIZE(chipselect) > + */ > +struct spi_imx_master { > + int *chipselect; > + int num_chipselect; > +}; > + > +#define MXC_SPI_CS(no) ((no) - 32) > + > +#endif /* __MACH_SPI_H_*/ > diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig > index 7c61251..6bb8e12 100644 > --- a/drivers/spi/Kconfig > +++ b/drivers/spi/Kconfig > @@ -117,10 +117,11 @@ config SPI_GPIO > speed with a custom version of this driver; see the source code. > > config SPI_IMX > - tristate "Freescale iMX SPI controller" > - depends on ARCH_MX1 && EXPERIMENTAL > + tristate "Freescale iMX SPI controllers" > + depends on ARCH_MXC > + select SPI_BITBANG > help > - This enables using the Freescale iMX SPI controller in master > + This enables using the Freescale iMX SPI controllers in master > mode. > > config SPI_LM70_LLP > diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile > index 5d04519..73b508b 100644 > --- a/drivers/spi/Makefile > +++ b/drivers/spi/Makefile > @@ -17,7 +17,7 @@ obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o > obj-$(CONFIG_SPI_AU1550) += au1550_spi.o > obj-$(CONFIG_SPI_BUTTERFLY) += spi_butterfly.o > obj-$(CONFIG_SPI_GPIO) += spi_gpio.o > -obj-$(CONFIG_SPI_IMX) += spi_imx.o > +obj-$(CONFIG_SPI_IMX) += mxc_spi.o > obj-$(CONFIG_SPI_LM70_LLP) += spi_lm70llp.o > obj-$(CONFIG_SPI_PXA2XX) += pxa2xx_spi.o > obj-$(CONFIG_SPI_OMAP_UWIRE) += omap_uwire.o > diff --git a/drivers/spi/mxc_spi.c b/drivers/spi/mxc_spi.c > new file mode 100644 > index 0000000..b144723 > --- /dev/null > +++ b/drivers/spi/mxc_spi.c > @@ -0,0 +1,685 @@ > +/* > + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. > + * Copyright (C) 2008 Juergen Beisert > + * > + * 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 > + * 51 Franklin Street, Fifth Floor > + * Boston, MA 02110-1301, USA. > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > + > +#define DRIVER_NAME "spi_imx" > + > +#define MXC_CSPIRXDATA 0x00 > +#define MXC_CSPITXDATA 0x04 > +#define MXC_CSPICTRL 0x08 > +#define MXC_CSPIINT 0x0c > +#define MXC_RESET 0x1c > + > +/* generic defines to abstract from the different register layouts */ > +#define MXC_INT_RR (1 << 0) /* Receive data ready interrupt */ > +#define MXC_INT_TE (1 << 1) /* Transmit FIFO empty interrupt */ > + > +struct mxc_spi_config { > + unsigned int speed_hz; > + unsigned int bpw; > + unsigned int mode; > + int cs; > +}; > + > +struct mxc_spi_data { > + struct spi_bitbang bitbang; > + > + struct completion xfer_done; > + void *base; > + int irq; > + struct clk *clk; > + unsigned long spi_clk; > + int *chipselect; > + > + unsigned int count; > + void (*tx)(struct mxc_spi_data *); > + void (*rx)(struct mxc_spi_data *); > + void *rx_buf; > + const void *tx_buf; > + unsigned int txfifo; /* number of words pushed in tx FIFO */ > + > + /* SoC specific functions */ > + void (*intctrl)(struct mxc_spi_data *, int); > + int (*config)(struct mxc_spi_data *, struct mxc_spi_config *); > + void (*trigger)(struct mxc_spi_data *); > + int (*rx_available)(struct mxc_spi_data *); > +}; > + > +#define MXC_SPI_BUF_RX(type) \ > +static void mxc_spi_buf_rx_##type(struct mxc_spi_data *mxc_spi) \ > +{ \ > + unsigned int val = readl(mxc_spi->base + MXC_CSPIRXDATA); \ > + \ > + if (mxc_spi->rx_buf) { \ > + *(type *)mxc_spi->rx_buf = val; \ > + mxc_spi->rx_buf += sizeof(type); \ > + } \ > +} > + > +#define MXC_SPI_BUF_TX(type) \ > +static void mxc_spi_buf_tx_##type(struct mxc_spi_data *mxc_spi) \ > +{ \ > + type val = 0; \ > + \ > + if (mxc_spi->tx_buf) { \ > + val = *(type *)mxc_spi->tx_buf; \ > + mxc_spi->tx_buf += sizeof(type); \ > + } \ > + \ > + mxc_spi->count -= sizeof(type); \ > + \ > + writel(val, mxc_spi->base + MXC_CSPITXDATA); \ > +} > + > +MXC_SPI_BUF_RX(u8) > +MXC_SPI_BUF_TX(u8) > +MXC_SPI_BUF_RX(u16) > +MXC_SPI_BUF_TX(u16) > +MXC_SPI_BUF_RX(u32) > +MXC_SPI_BUF_TX(u32) > + > +/* First entry is reserved, second entry is valid only if SDHC_SPIEN is set > + * (which is currently not the case in this driver) > + */ > +static int mxc_clkdivs[] = {0, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, > + 256, 384, 512, 768, 1024}; > + > +/* MX21, MX27 */ > +static unsigned int mxc_spi_clkdiv_1(unsigned int fin, > + unsigned int fspi) > +{ > + int i, max; > + > + if (cpu_is_mx21()) > + max = 18; > + else > + max = 16; > + > + for (i = 2; i < max; i++) > + if (fspi * mxc_clkdivs[i] >= fin) > + return i; > + > + return max; > +} > + > +/* MX1, MX31, MX35 */ > +static unsigned int mxc_spi_clkdiv_2(unsigned int fin, > + unsigned int fspi) > +{ > + int i, div = 4; > + > + for (i = 0; i < 7; i++) { > + if (fspi * div >= fin) > + return i; > + div <<= 1; > + } > + > + return 7; > +} > + > +#define MX31_INTREG_TEEN (1 << 0) > +#define MX31_INTREG_RREN (1 << 3) > + > +#define MX31_CSPICTRL_ENABLE (1 << 0) > +#define MX31_CSPICTRL_MASTER (1 << 1) > +#define MX31_CSPICTRL_XCH (1 << 2) > +#define MX31_CSPICTRL_POL (1 << 4) > +#define MX31_CSPICTRL_PHA (1 << 5) > +#define MX31_CSPICTRL_SSCTL (1 << 6) > +#define MX31_CSPICTRL_SSPOL (1 << 7) > +#define MX31_CSPICTRL_BC_SHIFT 8 > +#define MX35_CSPICTRL_BL_SHIFT 20 > +#define MX31_CSPICTRL_CS_SHIFT 24 > +#define MX35_CSPICTRL_CS_SHIFT 12 > +#define MX31_CSPICTRL_DR_SHIFT 16 > + > +#define MX31_CSPISTATUS 0x14 > +#define MX31_STATUS_RR (1 << 3) > + > +/* These functions also work for the i.MX35, but be aware that > + * the i.MX35 has a slightly different register layout for bits > + * we do not use here. > + */ > +static void mx31_intctrl(struct mxc_spi_data *mxc_spi, int enable) > +{ > + unsigned int val = 0; > + > + if (enable & MXC_INT_TE) > + val |= MX31_INTREG_TEEN; > + if (enable & MXC_INT_RR) > + val |= MX31_INTREG_RREN; > + > + writel(val, mxc_spi->base + MXC_CSPIINT); > +} > + > +static void mx31_trigger(struct mxc_spi_data *mxc_spi) > +{ > + unsigned int reg; > + > + reg = readl(mxc_spi->base + MXC_CSPICTRL); > + reg |= MX31_CSPICTRL_XCH; > + writel(reg, mxc_spi->base + MXC_CSPICTRL); > +} > + > +static int mx31_config(struct mxc_spi_data *mxc_spi, > + struct mxc_spi_config *config) > +{ > + unsigned int reg = MX31_CSPICTRL_ENABLE | MX31_CSPICTRL_MASTER; > + > + reg |= mxc_spi_clkdiv_2(mxc_spi->spi_clk, config->speed_hz) << > + MX31_CSPICTRL_DR_SHIFT; > + > + if (cpu_is_mx31()) > + reg |= (config->bpw - 1) << MX31_CSPICTRL_BC_SHIFT; > + else if (cpu_is_mx35()) { > + reg |= (config->bpw - 1) << MX35_CSPICTRL_BL_SHIFT; > + reg |= MX31_CSPICTRL_SSCTL; > + } > + > + if (config->mode & SPI_CPHA) > + reg |= MX31_CSPICTRL_PHA; > + if (config->mode & SPI_CPOL) > + reg |= MX31_CSPICTRL_POL; > + if (config->mode & SPI_CS_HIGH) > + reg |= MX31_CSPICTRL_SSPOL; > + if (config->cs < 0) { > + if (cpu_is_mx31()) > + reg |= (config->cs + 32) << MX31_CSPICTRL_CS_SHIFT; > + else if (cpu_is_mx35()) > + reg |= (config->cs + 32) << MX35_CSPICTRL_CS_SHIFT; > + } > + > + writel(reg, mxc_spi->base + MXC_CSPICTRL); > + > + return 0; > +} > + > +static int mx31_rx_available(struct mxc_spi_data *mxc_spi) > +{ > + return readl(mxc_spi->base + MX31_CSPISTATUS) & MX31_STATUS_RR; > +} > + > +#define MX27_INTREG_RR (1 << 4) > +#define MX27_INTREG_TEEN (1 << 9) > +#define MX27_INTREG_RREN (1 << 13) > + > +#define MX27_CSPICTRL_POL (1 << 5) > +#define MX27_CSPICTRL_PHA (1 << 6) > +#define MX27_CSPICTRL_SSPOL (1 << 8) > +#define MX27_CSPICTRL_XCH (1 << 9) > +#define MX27_CSPICTRL_ENABLE (1 << 10) > +#define MX27_CSPICTRL_MASTER (1 << 11) > +#define MX27_CSPICTRL_DR_SHIFT 14 > +#define MX27_CSPICTRL_CS_SHIFT 19 > + > +static void mx27_intctrl(struct mxc_spi_data *mxc_spi, int enable) > +{ > + unsigned int val = 0; > + > + if (enable & MXC_INT_TE) > + val |= MX27_INTREG_TEEN; > + if (enable & MXC_INT_RR) > + val |= MX27_INTREG_RREN; > + > + writel(val, mxc_spi->base + MXC_CSPIINT); > +} > + > +static void mx27_trigger(struct mxc_spi_data *mxc_spi) > +{ > + unsigned int reg; > + > + reg = readl(mxc_spi->base + MXC_CSPICTRL); > + reg |= MX27_CSPICTRL_XCH; > + writel(reg, mxc_spi->base + MXC_CSPICTRL); > +} > + > +static int mx27_config(struct mxc_spi_data *mxc_spi, > + struct mxc_spi_config *config) > +{ > + unsigned int reg = MX27_CSPICTRL_ENABLE | MX27_CSPICTRL_MASTER; > + > + reg |= mxc_spi_clkdiv_1(mxc_spi->spi_clk, config->speed_hz) << > + MX27_CSPICTRL_DR_SHIFT; > + reg |= config->bpw - 1; > + > + if (config->mode & SPI_CPHA) > + reg |= MX27_CSPICTRL_PHA; > + if (config->mode & SPI_CPOL) > + reg |= MX27_CSPICTRL_POL; > + if (config->mode & SPI_CS_HIGH) > + reg |= MX27_CSPICTRL_SSPOL; > + if (config->cs < 0) > + reg |= (config->cs + 32) << MX27_CSPICTRL_CS_SHIFT; > + > + writel(reg, mxc_spi->base + MXC_CSPICTRL); > + > + return 0; > +} > + > +static int mx27_rx_available(struct mxc_spi_data *mxc_spi) > +{ > + return readl(mxc_spi->base + MXC_CSPIINT) & MX27_INTREG_RR; > +} > + > +#define MX1_INTREG_RR (1 << 3) > +#define MX1_INTREG_TEEN (1 << 8) > +#define MX1_INTREG_RREN (1 << 11) > + > +#define MX1_CSPICTRL_POL (1 << 4) > +#define MX1_CSPICTRL_PHA (1 << 5) > +#define MX1_CSPICTRL_XCH (1 << 8) > +#define MX1_CSPICTRL_ENABLE (1 << 9) > +#define MX1_CSPICTRL_MASTER (1 << 10) > +#define MX1_CSPICTRL_DR_SHIFT 13 > + > +static void mx1_intctrl(struct mxc_spi_data *mxc_spi, int enable) > +{ > + unsigned int val = 0; > + > + if (enable & MXC_INT_TE) > + val |= MX1_INTREG_TEEN; > + if (enable & MXC_INT_RR) > + val |= MX1_INTREG_RREN; > + > + writel(val, mxc_spi->base + MXC_CSPIINT); > +} > + > +static void mx1_trigger(struct mxc_spi_data *mxc_spi) > +{ > + unsigned int reg; > + > + reg = readl(mxc_spi->base + MXC_CSPICTRL); > + reg |= MX1_CSPICTRL_XCH; > + writel(reg, mxc_spi->base + MXC_CSPICTRL); > +} > + > +static int mx1_config(struct mxc_spi_data *mxc_spi, > + struct mxc_spi_config *config) > +{ > + unsigned int reg = MX1_CSPICTRL_ENABLE | MX1_CSPICTRL_MASTER; > + > + reg |= mxc_spi_clkdiv_2(mxc_spi->spi_clk, config->speed_hz) << > + MX1_CSPICTRL_DR_SHIFT; > + reg |= config->bpw - 1; > + > + if (config->mode & SPI_CPHA) > + reg |= MX1_CSPICTRL_PHA; > + if (config->mode & SPI_CPOL) > + reg |= MX1_CSPICTRL_POL; > + > + writel(reg, mxc_spi->base + MXC_CSPICTRL); > + > + return 0; > +} > + > +static int mx1_rx_available(struct mxc_spi_data *mxc_spi) > +{ > + return readl(mxc_spi->base + MXC_CSPIINT) & MX1_INTREG_RR; > +} > + > +static void mxc_spi_chipselect(struct spi_device *spi, int is_active) > +{ > + struct mxc_spi_data *mxc_spi = spi_master_get_devdata(spi->master); > + unsigned int cs = 0; > + int gpio = mxc_spi->chipselect[spi->chip_select]; > + struct mxc_spi_config config; > + > + if (spi->mode & SPI_CS_HIGH) > + cs = 1; > + > + if (is_active == BITBANG_CS_INACTIVE) { > + if (gpio >= 0) > + gpio_set_value(gpio, !cs); > + return; > + } > + > + config.bpw = spi->bits_per_word; > + config.speed_hz = spi->max_speed_hz; > + config.mode = spi->mode; > + config.cs = mxc_spi->chipselect[spi->chip_select]; > + > + mxc_spi->config(mxc_spi, &config); > + > + /* Initialize the functions for transfer */ > + if (config.bpw <= 8) { > + mxc_spi->rx = mxc_spi_buf_rx_u8; > + mxc_spi->tx = mxc_spi_buf_tx_u8; > + } else if (config.bpw <= 16) { > + mxc_spi->rx = mxc_spi_buf_rx_u16; > + mxc_spi->tx = mxc_spi_buf_tx_u16; > + } else if (config.bpw <= 32) { > + mxc_spi->rx = mxc_spi_buf_rx_u32; > + mxc_spi->tx = mxc_spi_buf_tx_u32; > + } else > + BUG(); > + > + if (gpio >= 0) > + gpio_set_value(gpio, cs); > + > + return; > +} > + > +static void mxc_spi_push(struct mxc_spi_data *mxc_spi) > +{ > + while (mxc_spi->txfifo < 8) { > + if (!mxc_spi->count) > + break; > + mxc_spi->tx(mxc_spi); > + mxc_spi->txfifo++; > + } > + > + mxc_spi->trigger(mxc_spi); > +} > + > +static irqreturn_t mxc_spi_isr(int irq, void *dev_id) > +{ > + struct mxc_spi_data *mxc_spi = dev_id; > + > + while (mxc_spi->rx_available(mxc_spi)) { > + mxc_spi->rx(mxc_spi); > + mxc_spi->txfifo--; > + } > + > + if (mxc_spi->count) { > + mxc_spi_push(mxc_spi); > + return IRQ_HANDLED; > + } > + > + if (mxc_spi->txfifo) { > + /* No data left to push, but still waiting for rx data, > + * enable receive data available interrupt. > + */ > + mxc_spi->intctrl(mxc_spi, MXC_INT_RR); > + return IRQ_HANDLED; > + } > + > + mxc_spi->intctrl(mxc_spi, 0); > + complete(&mxc_spi->xfer_done); > + > + return IRQ_HANDLED; > +} > + > +static int mxc_spi_setupxfer(struct spi_device *spi, > + struct spi_transfer *t) > +{ > + struct mxc_spi_data *mxc_spi = spi_master_get_devdata(spi->master); > + struct mxc_spi_config config; > + > + config.bpw = t ? t->bits_per_word : spi->bits_per_word; > + config.speed_hz = t ? t->speed_hz : spi->max_speed_hz; > + config.mode = spi->mode; > + > + mxc_spi->config(mxc_spi, &config); > + > + return 0; > +} > + > +static int mxc_spi_transfer(struct spi_device *spi, > + struct spi_transfer *transfer) > +{ > + struct mxc_spi_data *mxc_spi = spi_master_get_devdata(spi->master); > + > + mxc_spi->tx_buf = transfer->tx_buf; > + mxc_spi->rx_buf = transfer->rx_buf; > + mxc_spi->count = transfer->len; > + mxc_spi->txfifo = 0; > + > + init_completion(&mxc_spi->xfer_done); > + > + mxc_spi_push(mxc_spi); > + > + mxc_spi->intctrl(mxc_spi, MXC_INT_TE); > + > + wait_for_completion(&mxc_spi->xfer_done); > + > + return transfer->len; > +} > + > +static int mxc_spi_setup(struct spi_device *spi) > +{ > + if (!spi->bits_per_word) > + spi->bits_per_word = 8; > + > + pr_debug("%s: mode %d, %u bpw, %d hz\n", __func__, > + spi->mode, spi->bits_per_word, spi->max_speed_hz); > + > + mxc_spi_chipselect(spi, BITBANG_CS_INACTIVE); > + > + return 0; > +} > + > +static void mxc_spi_cleanup(struct spi_device *spi) > +{ > +} > + > +static int __init mxc_spi_probe(struct platform_device *pdev) > +{ > + struct spi_imx_master *mxc_platform_info; > + struct spi_master *master; > + struct mxc_spi_data *mxc_spi; > + struct resource *res; > + int i, ret; > + > + mxc_platform_info = (struct spi_imx_master *)pdev->dev.platform_data; > + if (!mxc_platform_info) { > + dev_err(&pdev->dev, "can't get the platform data\n"); > + return -EINVAL; > + } > + > + master = spi_alloc_master(&pdev->dev, sizeof(struct mxc_spi_data)); > + if (!master) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, master); > + > + master->bus_num = pdev->id; > + master->num_chipselect = mxc_platform_info->num_chipselect; > + > + mxc_spi = spi_master_get_devdata(master); > + mxc_spi->bitbang.master = spi_master_get(master); > + mxc_spi->chipselect = mxc_platform_info->chipselect; > + > + for (i = 0; i < master->num_chipselect; i++) { > + if (mxc_spi->chipselect[i] < 0) > + continue; > + ret = gpio_request(mxc_spi->chipselect[i], DRIVER_NAME); > + if (ret) { > + i--; > + while (i > 0) > + if (mxc_spi->chipselect[i] >= 0) > + gpio_free(mxc_spi->chipselect[i--]); > + dev_err(&pdev->dev, "can't get cs gpios"); > + goto out_master_put; > + } > + gpio_direction_output(mxc_spi->chipselect[i], 1); > + } > + > + mxc_spi->bitbang.chipselect = mxc_spi_chipselect; > + mxc_spi->bitbang.setup_transfer = mxc_spi_setupxfer; > + mxc_spi->bitbang.txrx_bufs = mxc_spi_transfer; > + mxc_spi->bitbang.master->setup = mxc_spi_setup; > + mxc_spi->bitbang.master->cleanup = mxc_spi_cleanup; > + > + init_completion(&mxc_spi->xfer_done); > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) { > + dev_err(&pdev->dev, "can't get platform resource\n"); > + ret = -ENOMEM; > + goto out_gpio_free; > + } > + > + if (!request_mem_region(res->start, resource_size(res), pdev->name)) { > + dev_err(&pdev->dev, "request_mem_region failed\n"); > + ret = -EBUSY; > + goto out_gpio_free; > + } > + > + mxc_spi->base = ioremap(res->start, resource_size(res)); > + if (!mxc_spi->base) { > + ret = -EINVAL; > + goto out_release_mem; > + } > + > + mxc_spi->irq = platform_get_irq(pdev, 0); > + if (!mxc_spi->irq) { > + ret = -EINVAL; > + goto out_iounmap; > + } > + > + ret = request_irq(mxc_spi->irq, mxc_spi_isr, 0, DRIVER_NAME, mxc_spi); > + if (ret) { > + dev_err(&pdev->dev, "can't get irq%d: %d\n", mxc_spi->irq, ret); > + goto out_iounmap; > + } > + > + if (cpu_is_mx31() || cpu_is_mx35()) { > + mxc_spi->intctrl = mx31_intctrl; > + mxc_spi->config = mx31_config; > + mxc_spi->trigger = mx31_trigger; > + mxc_spi->rx_available = mx31_rx_available; > + } else if (cpu_is_mx27() || cpu_is_mx21()) { > + mxc_spi->intctrl = mx27_intctrl; > + mxc_spi->config = mx27_config; > + mxc_spi->trigger = mx27_trigger; > + mxc_spi->rx_available = mx27_rx_available; > + } else if (cpu_is_mx1()) { > + mxc_spi->intctrl = mx1_intctrl; > + mxc_spi->config = mx1_config; > + mxc_spi->trigger = mx1_trigger; > + mxc_spi->rx_available = mx1_rx_available; > + } else > + BUG(); > + > + mxc_spi->clk = clk_get(&pdev->dev, NULL); > + if (IS_ERR(mxc_spi->clk)) { > + dev_err(&pdev->dev, "unable to get clock\n"); > + ret = PTR_ERR(mxc_spi->clk); > + goto out_free_irq; > + } > + > + clk_enable(mxc_spi->clk); > + mxc_spi->spi_clk = clk_get_rate(mxc_spi->clk); > + > + if (!cpu_is_mx31() || !cpu_is_mx35()) > + writel(1, mxc_spi->base + MXC_RESET); > + > + mxc_spi->intctrl(mxc_spi, 0); > + > + ret = spi_bitbang_start(&mxc_spi->bitbang); > + if (ret) { > + dev_err(&pdev->dev, "bitbang start failed with %d\n", ret); > + goto out_clk_put; > + } > + > + dev_info(&pdev->dev, "probed\n"); > + > + return ret; > + > +out_clk_put: > + clk_disable(mxc_spi->clk); > + clk_put(mxc_spi->clk); > +out_free_irq: > + free_irq(mxc_spi->irq, mxc_spi); > +out_iounmap: > + iounmap(mxc_spi->base); > +out_release_mem: > + release_mem_region(res->start, resource_size(res)); > +out_gpio_free: > + for (i = 0; i < master->num_chipselect; i++) > + if (mxc_spi->chipselect[i] >= 0) > + gpio_free(mxc_spi->chipselect[i]); > +out_master_put: > + spi_master_put(master); > + kfree(master); > + platform_set_drvdata(pdev, NULL); > + return ret; > +} > + > +static int __exit mxc_spi_remove(struct platform_device *pdev) > +{ > + struct spi_master *master = platform_get_drvdata(pdev); > + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + struct mxc_spi_data *mxc_spi = spi_master_get_devdata(master); > + int i; > + > + spi_bitbang_stop(&mxc_spi->bitbang); > + > + writel(0, mxc_spi->base + MXC_CSPICTRL); > + clk_disable(mxc_spi->clk); > + clk_put(mxc_spi->clk); > + free_irq(mxc_spi->irq, mxc_spi); > + iounmap(mxc_spi->base); > + > + for (i = 0; i < master->num_chipselect; i++) > + if (mxc_spi->chipselect[i] >= 0) > + gpio_free(mxc_spi->chipselect[i]); > + > + spi_master_put(master); > + > + release_mem_region(res->start, resource_size(res)); > + > + platform_set_drvdata(pdev, NULL); > + > + return 0; > +} > + > +static struct platform_driver mxc_spi_driver = { > + .driver = { > + .name = DRIVER_NAME, > + .owner = THIS_MODULE, > + }, > + .probe = mxc_spi_probe, > + .remove = __exit_p(mxc_spi_remove), > +}; > + > +static int __init mxc_spi_init(void) > +{ > + return platform_driver_register(&mxc_spi_driver); > +} > + > +static void __exit mxc_spi_exit(void) > +{ > + platform_driver_unregister(&mxc_spi_driver); > +} > + > +module_init(mxc_spi_init); > +module_exit(mxc_spi_exit); > + > +MODULE_DESCRIPTION("SPI Master Controller driver"); > +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); > +MODULE_LICENSE("GPL"); > -- > 1.6.2.1 > > -- > Pengutronix e.K. | | > Industrial Linux Solutions | http://www.pengutronix.de/ | > Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | > Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 | > > ------------------------------------------------------------------- > List admin: http://lists.arm.linux.org.uk/mailman/listinfo/linux-arm-kernel > FAQ: http://www.arm.linux.org.uk/mailinglists/faq.php > Etiquette: http://www.arm.linux.org.uk/mailinglists/etiquette.php > -- Valentin Longchamp, PhD Student, EPFL-STI-LSRO1 valentin.longchamp-p8DiymsW2f8@public.gmane.org, Phone: +41216937827 http://people.epfl.ch/valentin.longchamp MEA3485, Station 9, CH-1015 Lausanne ------------------------------------------------------------------------------ The NEW KODAK i700 Series Scanners deliver under ANY circumstances! Your production scanning environment may not be a perfect world - but thanks to Kodak, there's a perfect scanner to get the job done! With the NEW KODAK i700 Series Scanner you'll get full speed at 300 dpi even with all image processing features enabled. http://p.sf.net/sfu/kodak-com