123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329 |
- From 66ea263246ca6dd9f63dce8fb22157fa83693300 Mon Sep 17 00:00:00 2001
- From: popcornmix <popcornmix@gmail.com>
- Date: Mon, 4 Nov 2013 18:56:10 +0000
- Subject: [PATCH 019/114] Add Chris Boot's i2c and spi drivers.
- i2c-bcm2708: fixed baudrate
- Fixed issue where the wrong CDIV value was set for baudrates below 3815 Hz (for 250MHz bus clock).
- In that case the computed CDIV value was more than 0xffff. However the CDIV register width is only 16 bits.
- This resulted in incorrect setting of CDIV and higher baudrate than intended.
- Example: 3500Hz -> CDIV=0x11704 -> CDIV(16bit)=0x1704 -> 42430Hz
- After correction: 3500Hz -> CDIV=0x11704 -> CDIV(16bit)=0xffff -> 3815Hz
- The correct baudrate is shown in the log after the cdiv > 0xffff correction.
- ---
- arch/arm/configs/bcmrpi_defconfig | 7 +
- arch/arm/mach-bcm2708/Kconfig | 7 +
- arch/arm/mach-bcm2708/bcm2708.c | 104 ++++++-
- drivers/i2c/busses/Kconfig | 19 ++
- drivers/i2c/busses/Makefile | 1 +
- drivers/i2c/busses/i2c-bcm2708.c | 420 +++++++++++++++++++++++++
- drivers/spi/Kconfig | 8 +
- drivers/spi/Makefile | 1 +
- drivers/spi/spi-bcm2708.c | 626 ++++++++++++++++++++++++++++++++++++++
- 9 files changed, 1191 insertions(+), 2 deletions(-)
- create mode 100644 drivers/i2c/busses/i2c-bcm2708.c
- create mode 100644 drivers/spi/spi-bcm2708.c
- --- a/arch/arm/configs/bcmrpi_defconfig
- +++ b/arch/arm/configs/bcmrpi_defconfig
- @@ -195,6 +195,13 @@ CONFIG_SERIAL_AMBA_PL011=y
- CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
- # CONFIG_HW_RANDOM is not set
- CONFIG_RAW_DRIVER=y
- +CONFIG_I2C=y
- +CONFIG_I2C_CHARDEV=m
- +CONFIG_I2C_BCM2708=m
- +CONFIG_SPI=y
- +CONFIG_SPI_BCM2708=m
- +CONFIG_SPI_SPIDEV=m
- +CONFIG_GPIO_SYSFS=y
- # CONFIG_HWMON is not set
- CONFIG_WATCHDOG=y
- CONFIG_FB=y
- --- a/arch/arm/mach-bcm2708/Kconfig
- +++ b/arch/arm/mach-bcm2708/Kconfig
- @@ -31,4 +31,11 @@ config BCM2708_NOL2CACHE
- help
- Do not allow ARM to use GPU's L2 cache. Requires disable_l2cache in config.txt.
-
- +config BCM2708_SPIDEV
- + bool "Bind spidev to SPI0 master"
- + depends on MACH_BCM2708
- + depends on SPI
- + default y
- + help
- + Binds spidev driver to the SPI0 master
- endmenu
- --- a/arch/arm/mach-bcm2708/bcm2708.c
- +++ b/arch/arm/mach-bcm2708/bcm2708.c
- @@ -31,6 +31,7 @@
- #include <linux/cnt32_to_63.h>
- #include <linux/io.h>
- #include <linux/module.h>
- +#include <linux/spi/spi.h>
-
- #include <linux/version.h>
- #include <linux/clkdev.h>
- @@ -205,7 +206,6 @@ static struct clk osc_clk = {
-
- /* warning - the USB needs a clock > 34MHz */
-
- -#ifdef CONFIG_MMC_BCM2708
- static struct clk sdhost_clk = {
- #ifdef CONFIG_ARCH_BCM2708_CHIPIT
- .rate = 4000000, /* 4MHz */
- @@ -213,7 +213,6 @@ static struct clk sdhost_clk = {
- .rate = 250000000, /* 250MHz */
- #endif
- };
- -#endif
-
- static struct clk_lookup lookups[] = {
- { /* UART0 */
- @@ -223,6 +222,15 @@ static struct clk_lookup lookups[] = {
- { /* USB */
- .dev_id = "bcm2708_usb",
- .clk = &osc_clk,
- + }, { /* SPI */
- + .dev_id = "bcm2708_spi.0",
- + .clk = &sdhost_clk,
- + }, { /* BSC0 */
- + .dev_id = "bcm2708_i2c.0",
- + .clk = &sdhost_clk,
- + }, { /* BSC1 */
- + .dev_id = "bcm2708_i2c.1",
- + .clk = &sdhost_clk,
- }
- };
-
- @@ -455,6 +463,89 @@ static struct platform_device bcm2708_al
- },
- };
-
- +static struct resource bcm2708_spi_resources[] = {
- + {
- + .start = SPI0_BASE,
- + .end = SPI0_BASE + SZ_256 - 1,
- + .flags = IORESOURCE_MEM,
- + }, {
- + .start = IRQ_SPI,
- + .end = IRQ_SPI,
- + .flags = IORESOURCE_IRQ,
- + }
- +};
- +
- +
- +static u64 bcm2708_spi_dmamask = DMA_BIT_MASK(DMA_MASK_BITS_COMMON);
- +static struct platform_device bcm2708_spi_device = {
- + .name = "bcm2708_spi",
- + .id = 0,
- + .num_resources = ARRAY_SIZE(bcm2708_spi_resources),
- + .resource = bcm2708_spi_resources,
- + .dev = {
- + .dma_mask = &bcm2708_spi_dmamask,
- + .coherent_dma_mask = DMA_BIT_MASK(DMA_MASK_BITS_COMMON)},
- +};
- +
- +#ifdef CONFIG_BCM2708_SPIDEV
- +static struct spi_board_info bcm2708_spi_devices[] = {
- +#ifdef CONFIG_SPI_SPIDEV
- + {
- + .modalias = "spidev",
- + .max_speed_hz = 500000,
- + .bus_num = 0,
- + .chip_select = 0,
- + .mode = SPI_MODE_0,
- + }, {
- + .modalias = "spidev",
- + .max_speed_hz = 500000,
- + .bus_num = 0,
- + .chip_select = 1,
- + .mode = SPI_MODE_0,
- + }
- +#endif
- +};
- +#endif
- +
- +static struct resource bcm2708_bsc0_resources[] = {
- + {
- + .start = BSC0_BASE,
- + .end = BSC0_BASE + SZ_256 - 1,
- + .flags = IORESOURCE_MEM,
- + }, {
- + .start = INTERRUPT_I2C,
- + .end = INTERRUPT_I2C,
- + .flags = IORESOURCE_IRQ,
- + }
- +};
- +
- +static struct platform_device bcm2708_bsc0_device = {
- + .name = "bcm2708_i2c",
- + .id = 0,
- + .num_resources = ARRAY_SIZE(bcm2708_bsc0_resources),
- + .resource = bcm2708_bsc0_resources,
- +};
- +
- +
- +static struct resource bcm2708_bsc1_resources[] = {
- + {
- + .start = BSC1_BASE,
- + .end = BSC1_BASE + SZ_256 - 1,
- + .flags = IORESOURCE_MEM,
- + }, {
- + .start = INTERRUPT_I2C,
- + .end = INTERRUPT_I2C,
- + .flags = IORESOURCE_IRQ,
- + }
- +};
- +
- +static struct platform_device bcm2708_bsc1_device = {
- + .name = "bcm2708_i2c",
- + .id = 1,
- + .num_resources = ARRAY_SIZE(bcm2708_bsc1_resources),
- + .resource = bcm2708_bsc1_resources,
- +};
- +
- static struct platform_device bcm2835_hwmon_device = {
- .name = "bcm2835_hwmon",
- };
- @@ -571,6 +662,10 @@ void __init bcm2708_init(void)
- for (i = 0; i < ARRAY_SIZE(bcm2708_alsa_devices); i++)
- bcm_register_device(&bcm2708_alsa_devices[i]);
-
- + bcm_register_device(&bcm2708_spi_device);
- + bcm_register_device(&bcm2708_bsc0_device);
- + bcm_register_device(&bcm2708_bsc1_device);
- +
- bcm_register_device(&bcm2835_hwmon_device);
- bcm_register_device(&bcm2835_thermal_device);
-
- @@ -580,6 +675,11 @@ void __init bcm2708_init(void)
- }
- system_rev = boardrev;
- system_serial_low = serial;
- +
- +#ifdef CONFIG_BCM2708_SPIDEV
- + spi_register_board_info(bcm2708_spi_devices,
- + ARRAY_SIZE(bcm2708_spi_devices));
- +#endif
- }
-
- static void timer_set_mode(enum clock_event_mode mode,
- --- a/drivers/i2c/busses/Kconfig
- +++ b/drivers/i2c/busses/Kconfig
- @@ -372,6 +372,25 @@ config I2C_BCM2835
- This support is also available as a module. If so, the module
- will be called i2c-bcm2835.
-
- +config I2C_BCM2708
- + tristate "BCM2708 BSC"
- + depends on MACH_BCM2708
- + help
- + Enabling this option will add BSC (Broadcom Serial Controller)
- + support for the BCM2708. BSC is a Broadcom proprietary bus compatible
- + with I2C/TWI/SMBus.
- +
- +config I2C_BCM2708_BAUDRATE
- + prompt "BCM2708 I2C baudrate"
- + depends on I2C_BCM2708
- + int
- + default 100000
- + help
- + Set the I2C baudrate. This will alter the default value. A
- + different baudrate can be set by using a module parameter as well. If
- + no parameter is provided when loading, this is the value that will be
- + used.
- +
- config I2C_BCM_KONA
- tristate "BCM Kona I2C adapter"
- depends on ARCH_BCM_MOBILE
- --- a/drivers/i2c/busses/Makefile
- +++ b/drivers/i2c/busses/Makefile
- @@ -33,6 +33,7 @@ obj-$(CONFIG_I2C_AT91) += i2c-at91.o
- obj-$(CONFIG_I2C_AU1550) += i2c-au1550.o
- obj-$(CONFIG_I2C_AXXIA) += i2c-axxia.o
- obj-$(CONFIG_I2C_BCM2835) += i2c-bcm2835.o
- +obj-$(CONFIG_I2C_BCM2708) += i2c-bcm2708.o
- obj-$(CONFIG_I2C_BLACKFIN_TWI) += i2c-bfin-twi.o
- obj-$(CONFIG_I2C_CADENCE) += i2c-cadence.o
- obj-$(CONFIG_I2C_CBUS_GPIO) += i2c-cbus-gpio.o
- --- /dev/null
- +++ b/drivers/i2c/busses/i2c-bcm2708.c
- @@ -0,0 +1,420 @@
- +/*
- + * Driver for Broadcom BCM2708 BSC Controllers
- + *
- + * Copyright (C) 2012 Chris Boot & Frank Buss
- + *
- + * This driver is inspired by:
- + * i2c-ocores.c, by Peter Korsgaard <jacmet@sunsite.dk>
- + *
- + * 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 <linux/kernel.h>
- +#include <linux/module.h>
- +#include <linux/spinlock.h>
- +#include <linux/clk.h>
- +#include <linux/err.h>
- +#include <linux/platform_device.h>
- +#include <linux/io.h>
- +#include <linux/slab.h>
- +#include <linux/i2c.h>
- +#include <linux/interrupt.h>
- +#include <linux/sched.h>
- +#include <linux/wait.h>
- +
- +/* BSC register offsets */
- +#define BSC_C 0x00
- +#define BSC_S 0x04
- +#define BSC_DLEN 0x08
- +#define BSC_A 0x0c
- +#define BSC_FIFO 0x10
- +#define BSC_DIV 0x14
- +#define BSC_DEL 0x18
- +#define BSC_CLKT 0x1c
- +
- +/* Bitfields in BSC_C */
- +#define BSC_C_I2CEN 0x00008000
- +#define BSC_C_INTR 0x00000400
- +#define BSC_C_INTT 0x00000200
- +#define BSC_C_INTD 0x00000100
- +#define BSC_C_ST 0x00000080
- +#define BSC_C_CLEAR_1 0x00000020
- +#define BSC_C_CLEAR_2 0x00000010
- +#define BSC_C_READ 0x00000001
- +
- +/* Bitfields in BSC_S */
- +#define BSC_S_CLKT 0x00000200
- +#define BSC_S_ERR 0x00000100
- +#define BSC_S_RXF 0x00000080
- +#define BSC_S_TXE 0x00000040
- +#define BSC_S_RXD 0x00000020
- +#define BSC_S_TXD 0x00000010
- +#define BSC_S_RXR 0x00000008
- +#define BSC_S_TXW 0x00000004
- +#define BSC_S_DONE 0x00000002
- +#define BSC_S_TA 0x00000001
- +
- +#define I2C_TIMEOUT_MS 150
- +
- +#define DRV_NAME "bcm2708_i2c"
- +
- +static unsigned int baudrate = CONFIG_I2C_BCM2708_BAUDRATE;
- +module_param(baudrate, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
- +MODULE_PARM_DESC(baudrate, "The I2C baudrate");
- +
- +
- +struct bcm2708_i2c {
- + struct i2c_adapter adapter;
- +
- + spinlock_t lock;
- + void __iomem *base;
- + int irq;
- + struct clk *clk;
- +
- + struct completion done;
- +
- + struct i2c_msg *msg;
- + int pos;
- + int nmsgs;
- + bool error;
- +};
- +
- +/*
- + * This function sets the ALT mode on the I2C pins so that we can use them with
- + * the BSC hardware.
- + *
- + * FIXME: This is a hack. Use pinmux / pinctrl.
- + */
- +static void bcm2708_i2c_init_pinmode(int id)
- +{
- +#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
- +#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
- +
- + int pin;
- + u32 *gpio = ioremap(GPIO_BASE, SZ_16K);
- +
- + BUG_ON(id != 0 && id != 1);
- + /* BSC0 is on GPIO 0 & 1, BSC1 is on GPIO 2 & 3 */
- + for (pin = id*2+0; pin <= id*2+1; pin++) {
- +printk("bcm2708_i2c_init_pinmode(%d,%d)\n", id, pin);
- + INP_GPIO(pin); /* set mode to GPIO input first */
- + SET_GPIO_ALT(pin, 0); /* set mode to ALT 0 */
- + }
- +
- + iounmap(gpio);
- +
- +#undef INP_GPIO
- +#undef SET_GPIO_ALT
- +}
- +
- +static inline u32 bcm2708_rd(struct bcm2708_i2c *bi, unsigned reg)
- +{
- + return readl(bi->base + reg);
- +}
- +
- +static inline void bcm2708_wr(struct bcm2708_i2c *bi, unsigned reg, u32 val)
- +{
- + writel(val, bi->base + reg);
- +}
- +
- +static inline void bcm2708_bsc_reset(struct bcm2708_i2c *bi)
- +{
- + bcm2708_wr(bi, BSC_C, 0);
- + bcm2708_wr(bi, BSC_S, BSC_S_CLKT | BSC_S_ERR | BSC_S_DONE);
- +}
- +
- +static inline void bcm2708_bsc_fifo_drain(struct bcm2708_i2c *bi)
- +{
- + while ((bcm2708_rd(bi, BSC_S) & BSC_S_RXD) && (bi->pos < bi->msg->len))
- + bi->msg->buf[bi->pos++] = bcm2708_rd(bi, BSC_FIFO);
- +}
- +
- +static inline void bcm2708_bsc_fifo_fill(struct bcm2708_i2c *bi)
- +{
- + while ((bcm2708_rd(bi, BSC_S) & BSC_S_TXD) && (bi->pos < bi->msg->len))
- + bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]);
- +}
- +
- +static inline void bcm2708_bsc_setup(struct bcm2708_i2c *bi)
- +{
- + unsigned long bus_hz;
- + u32 cdiv;
- + u32 c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_ST | BSC_C_CLEAR_1;
- +
- + bus_hz = clk_get_rate(bi->clk);
- + cdiv = bus_hz / baudrate;
- + if (cdiv > 0xffff)
- + cdiv = 0xffff;
- +
- + if (bi->msg->flags & I2C_M_RD)
- + c |= BSC_C_INTR | BSC_C_READ;
- + else
- + c |= BSC_C_INTT;
- +
- + bcm2708_wr(bi, BSC_DIV, cdiv);
- + bcm2708_wr(bi, BSC_A, bi->msg->addr);
- + bcm2708_wr(bi, BSC_DLEN, bi->msg->len);
- + bcm2708_wr(bi, BSC_C, c);
- +}
- +
- +static irqreturn_t bcm2708_i2c_interrupt(int irq, void *dev_id)
- +{
- + struct bcm2708_i2c *bi = dev_id;
- + bool handled = true;
- + u32 s;
- +
- + spin_lock(&bi->lock);
- +
- + /* we may see camera interrupts on the "other" I2C channel
- + Just return if we've not sent anything */
- + if (!bi->nmsgs || !bi->msg )
- + goto early_exit;
- +
- + s = bcm2708_rd(bi, BSC_S);
- +
- + if (s & (BSC_S_CLKT | BSC_S_ERR)) {
- + bcm2708_bsc_reset(bi);
- + bi->error = true;
- +
- + /* wake up our bh */
- + complete(&bi->done);
- + } else if (s & BSC_S_DONE) {
- + bi->nmsgs--;
- +
- + if (bi->msg->flags & I2C_M_RD)
- + bcm2708_bsc_fifo_drain(bi);
- +
- + bcm2708_bsc_reset(bi);
- +
- + if (bi->nmsgs) {
- + /* advance to next message */
- + bi->msg++;
- + bi->pos = 0;
- + bcm2708_bsc_setup(bi);
- + } else {
- + /* wake up our bh */
- + complete(&bi->done);
- + }
- + } else if (s & BSC_S_TXW) {
- + bcm2708_bsc_fifo_fill(bi);
- + } else if (s & BSC_S_RXR) {
- + bcm2708_bsc_fifo_drain(bi);
- + } else {
- + handled = false;
- + }
- +
- +early_exit:
- + spin_unlock(&bi->lock);
- +
- + return handled ? IRQ_HANDLED : IRQ_NONE;
- +}
- +
- +static int bcm2708_i2c_master_xfer(struct i2c_adapter *adap,
- + struct i2c_msg *msgs, int num)
- +{
- + struct bcm2708_i2c *bi = adap->algo_data;
- + unsigned long flags;
- + int ret;
- +
- + spin_lock_irqsave(&bi->lock, flags);
- +
- + reinit_completion(&bi->done);
- + bi->msg = msgs;
- + bi->pos = 0;
- + bi->nmsgs = num;
- + bi->error = false;
- +
- + bcm2708_bsc_setup(bi);
- +
- + /* unlockig _after_ the setup to avoid races with the interrupt routine */
- + spin_unlock_irqrestore(&bi->lock, flags);
- +
- + ret = wait_for_completion_timeout(&bi->done,
- + msecs_to_jiffies(I2C_TIMEOUT_MS));
- + if (ret == 0) {
- + dev_err(&adap->dev, "transfer timed out\n");
- + spin_lock_irqsave(&bi->lock, flags);
- + bcm2708_bsc_reset(bi);
- + spin_unlock_irqrestore(&bi->lock, flags);
- + return -ETIMEDOUT;
- + }
- +
- + return bi->error ? -EIO : num;
- +}
- +
- +static u32 bcm2708_i2c_functionality(struct i2c_adapter *adap)
- +{
- + return I2C_FUNC_I2C | /*I2C_FUNC_10BIT_ADDR |*/ I2C_FUNC_SMBUS_EMUL;
- +}
- +
- +static struct i2c_algorithm bcm2708_i2c_algorithm = {
- + .master_xfer = bcm2708_i2c_master_xfer,
- + .functionality = bcm2708_i2c_functionality,
- +};
- +
- +static int bcm2708_i2c_probe(struct platform_device *pdev)
- +{
- + struct resource *regs;
- + int irq, err = -ENOMEM;
- + struct clk *clk;
- + struct bcm2708_i2c *bi;
- + struct i2c_adapter *adap;
- + unsigned long bus_hz;
- + u32 cdiv;
- +
- + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- + if (!regs) {
- + dev_err(&pdev->dev, "could not get IO memory\n");
- + return -ENXIO;
- + }
- +
- + irq = platform_get_irq(pdev, 0);
- + if (irq < 0) {
- + dev_err(&pdev->dev, "could not get IRQ\n");
- + return irq;
- + }
- +
- + clk = clk_get(&pdev->dev, NULL);
- + if (IS_ERR(clk)) {
- + dev_err(&pdev->dev, "could not find clk: %ld\n", PTR_ERR(clk));
- + return PTR_ERR(clk);
- + }
- +
- + bcm2708_i2c_init_pinmode(pdev->id);
- +
- + bi = kzalloc(sizeof(*bi), GFP_KERNEL);
- + if (!bi)
- + goto out_clk_put;
- +
- + platform_set_drvdata(pdev, bi);
- +
- + adap = &bi->adapter;
- + adap->class = I2C_CLASS_HWMON | I2C_CLASS_DDC;
- + adap->algo = &bcm2708_i2c_algorithm;
- + adap->algo_data = bi;
- + adap->dev.parent = &pdev->dev;
- + adap->nr = pdev->id;
- + strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
- +
- + switch (pdev->id) {
- + case 0:
- + adap->class = I2C_CLASS_HWMON;
- + break;
- + case 1:
- + adap->class = I2C_CLASS_DDC;
- + break;
- + default:
- + dev_err(&pdev->dev, "can only bind to BSC 0 or 1\n");
- + err = -ENXIO;
- + goto out_free_bi;
- + }
- +
- + spin_lock_init(&bi->lock);
- + init_completion(&bi->done);
- +
- + bi->base = ioremap(regs->start, resource_size(regs));
- + if (!bi->base) {
- + dev_err(&pdev->dev, "could not remap memory\n");
- + goto out_free_bi;
- + }
- +
- + bi->irq = irq;
- + bi->clk = clk;
- +
- + err = request_irq(irq, bcm2708_i2c_interrupt, IRQF_SHARED,
- + dev_name(&pdev->dev), bi);
- + if (err) {
- + dev_err(&pdev->dev, "could not request IRQ: %d\n", err);
- + goto out_iounmap;
- + }
- +
- + bcm2708_bsc_reset(bi);
- +
- + err = i2c_add_numbered_adapter(adap);
- + if (err < 0) {
- + dev_err(&pdev->dev, "could not add I2C adapter: %d\n", err);
- + goto out_free_irq;
- + }
- +
- + bus_hz = clk_get_rate(bi->clk);
- + cdiv = bus_hz / baudrate;
- + if (cdiv > 0xffff) {
- + cdiv = 0xffff;
- + baudrate = bus_hz / cdiv;
- + }
- +
- + dev_info(&pdev->dev, "BSC%d Controller at 0x%08lx (irq %d) (baudrate %d)\n",
- + pdev->id, (unsigned long)regs->start, irq, baudrate);
- +
- + return 0;
- +
- +out_free_irq:
- + free_irq(bi->irq, bi);
- +out_iounmap:
- + iounmap(bi->base);
- +out_free_bi:
- + kfree(bi);
- +out_clk_put:
- + clk_put(clk);
- + return err;
- +}
- +
- +static int bcm2708_i2c_remove(struct platform_device *pdev)
- +{
- + struct bcm2708_i2c *bi = platform_get_drvdata(pdev);
- +
- + platform_set_drvdata(pdev, NULL);
- +
- + i2c_del_adapter(&bi->adapter);
- + free_irq(bi->irq, bi);
- + iounmap(bi->base);
- + clk_disable(bi->clk);
- + clk_put(bi->clk);
- + kfree(bi);
- +
- + return 0;
- +}
- +
- +static struct platform_driver bcm2708_i2c_driver = {
- + .driver = {
- + .name = DRV_NAME,
- + .owner = THIS_MODULE,
- + },
- + .probe = bcm2708_i2c_probe,
- + .remove = bcm2708_i2c_remove,
- +};
- +
- +// module_platform_driver(bcm2708_i2c_driver);
- +
- +
- +static int __init bcm2708_i2c_init(void)
- +{
- + return platform_driver_register(&bcm2708_i2c_driver);
- +}
- +
- +static void __exit bcm2708_i2c_exit(void)
- +{
- + platform_driver_unregister(&bcm2708_i2c_driver);
- +}
- +
- +module_init(bcm2708_i2c_init);
- +module_exit(bcm2708_i2c_exit);
- +
- +
- +
- +MODULE_DESCRIPTION("BSC controller driver for Broadcom BCM2708");
- +MODULE_AUTHOR("Chris Boot <bootc@bootc.net>");
- +MODULE_LICENSE("GPL v2");
- +MODULE_ALIAS("platform:" DRV_NAME);
- --- a/drivers/spi/Kconfig
- +++ b/drivers/spi/Kconfig
- @@ -86,6 +86,14 @@ config SPI_BCM2835
- is for the regular SPI controller. Slave mode operation is not also
- not supported.
-
- +config SPI_BCM2708
- + tristate "BCM2708 SPI controller driver (SPI0)"
- + depends on MACH_BCM2708
- + help
- + This selects a driver for the Broadcom BCM2708 SPI master (SPI0). This
- + driver is not compatible with the "Universal SPI Master" or the SPI slave
- + device.
- +
- config SPI_BFIN5XX
- tristate "SPI controller driver for ADI Blackfin5xx"
- depends on BLACKFIN && !BF60x
- --- a/drivers/spi/Makefile
- +++ b/drivers/spi/Makefile
- @@ -20,6 +20,7 @@ obj-$(CONFIG_SPI_BCM63XX) += spi-bcm63x
- obj-$(CONFIG_SPI_BCM63XX_HSSPI) += spi-bcm63xx-hsspi.o
- obj-$(CONFIG_SPI_BFIN5XX) += spi-bfin5xx.o
- obj-$(CONFIG_SPI_ADI_V3) += spi-adi-v3.o
- +obj-$(CONFIG_SPI_BCM2708) += spi-bcm2708.o
- obj-$(CONFIG_SPI_BFIN_SPORT) += spi-bfin-sport.o
- obj-$(CONFIG_SPI_BITBANG) += spi-bitbang.o
- obj-$(CONFIG_SPI_BUTTERFLY) += spi-butterfly.o
- --- /dev/null
- +++ b/drivers/spi/spi-bcm2708.c
- @@ -0,0 +1,626 @@
- +/*
- + * Driver for Broadcom BCM2708 SPI Controllers
- + *
- + * Copyright (C) 2012 Chris Boot
- + *
- + * This driver is inspired by:
- + * spi-ath79.c, Copyright (C) 2009-2011 Gabor Juhos <juhosg@openwrt.org>
- + * spi-atmel.c, Copyright (C) 2006 Atmel Corporation
- + *
- + * 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 <linux/kernel.h>
- +#include <linux/module.h>
- +#include <linux/spinlock.h>
- +#include <linux/clk.h>
- +#include <linux/err.h>
- +#include <linux/platform_device.h>
- +#include <linux/io.h>
- +#include <linux/spi/spi.h>
- +#include <linux/interrupt.h>
- +#include <linux/delay.h>
- +#include <linux/log2.h>
- +#include <linux/sched.h>
- +#include <linux/wait.h>
- +
- +/* SPI register offsets */
- +#define SPI_CS 0x00
- +#define SPI_FIFO 0x04
- +#define SPI_CLK 0x08
- +#define SPI_DLEN 0x0c
- +#define SPI_LTOH 0x10
- +#define SPI_DC 0x14
- +
- +/* Bitfields in CS */
- +#define SPI_CS_LEN_LONG 0x02000000
- +#define SPI_CS_DMA_LEN 0x01000000
- +#define SPI_CS_CSPOL2 0x00800000
- +#define SPI_CS_CSPOL1 0x00400000
- +#define SPI_CS_CSPOL0 0x00200000
- +#define SPI_CS_RXF 0x00100000
- +#define SPI_CS_RXR 0x00080000
- +#define SPI_CS_TXD 0x00040000
- +#define SPI_CS_RXD 0x00020000
- +#define SPI_CS_DONE 0x00010000
- +#define SPI_CS_LEN 0x00002000
- +#define SPI_CS_REN 0x00001000
- +#define SPI_CS_ADCS 0x00000800
- +#define SPI_CS_INTR 0x00000400
- +#define SPI_CS_INTD 0x00000200
- +#define SPI_CS_DMAEN 0x00000100
- +#define SPI_CS_TA 0x00000080
- +#define SPI_CS_CSPOL 0x00000040
- +#define SPI_CS_CLEAR_RX 0x00000020
- +#define SPI_CS_CLEAR_TX 0x00000010
- +#define SPI_CS_CPOL 0x00000008
- +#define SPI_CS_CPHA 0x00000004
- +#define SPI_CS_CS_10 0x00000002
- +#define SPI_CS_CS_01 0x00000001
- +
- +#define SPI_TIMEOUT_MS 150
- +
- +#define DRV_NAME "bcm2708_spi"
- +
- +struct bcm2708_spi {
- + spinlock_t lock;
- + void __iomem *base;
- + int irq;
- + struct clk *clk;
- + bool stopping;
- +
- + struct list_head queue;
- + struct workqueue_struct *workq;
- + struct work_struct work;
- + struct completion done;
- +
- + const u8 *tx_buf;
- + u8 *rx_buf;
- + int len;
- +};
- +
- +struct bcm2708_spi_state {
- + u32 cs;
- + u16 cdiv;
- +};
- +
- +/*
- + * This function sets the ALT mode on the SPI pins so that we can use them with
- + * the SPI hardware.
- + *
- + * FIXME: This is a hack. Use pinmux / pinctrl.
- + */
- +static void bcm2708_init_pinmode(void)
- +{
- +#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
- +#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
- +
- + int pin;
- + u32 *gpio = ioremap(GPIO_BASE, SZ_16K);
- +
- + /* SPI is on GPIO 7..11 */
- + for (pin = 7; pin <= 11; pin++) {
- + INP_GPIO(pin); /* set mode to GPIO input first */
- + SET_GPIO_ALT(pin, 0); /* set mode to ALT 0 */
- + }
- +
- + iounmap(gpio);
- +
- +#undef INP_GPIO
- +#undef SET_GPIO_ALT
- +}
- +
- +static inline u32 bcm2708_rd(struct bcm2708_spi *bs, unsigned reg)
- +{
- + return readl(bs->base + reg);
- +}
- +
- +static inline void bcm2708_wr(struct bcm2708_spi *bs, unsigned reg, u32 val)
- +{
- + writel(val, bs->base + reg);
- +}
- +
- +static inline void bcm2708_rd_fifo(struct bcm2708_spi *bs, int len)
- +{
- + u8 byte;
- +
- + while (len--) {
- + byte = bcm2708_rd(bs, SPI_FIFO);
- + if (bs->rx_buf)
- + *bs->rx_buf++ = byte;
- + }
- +}
- +
- +static inline void bcm2708_wr_fifo(struct bcm2708_spi *bs, int len)
- +{
- + u8 byte;
- + u16 val;
- +
- + if (len > bs->len)
- + len = bs->len;
- +
- + if (unlikely(bcm2708_rd(bs, SPI_CS) & SPI_CS_LEN)) {
- + /* LoSSI mode */
- + if (unlikely(len % 2)) {
- + printk(KERN_ERR"bcm2708_wr_fifo: length must be even, skipping.\n");
- + bs->len = 0;
- + return;
- + }
- + while (len) {
- + if (bs->tx_buf) {
- + val = *(const u16 *)bs->tx_buf;
- + bs->tx_buf += 2;
- + } else
- + val = 0;
- + bcm2708_wr(bs, SPI_FIFO, val);
- + bs->len -= 2;
- + len -= 2;
- + }
- + return;
- + }
- +
- + while (len--) {
- + byte = bs->tx_buf ? *bs->tx_buf++ : 0;
- + bcm2708_wr(bs, SPI_FIFO, byte);
- + bs->len--;
- + }
- +}
- +
- +static irqreturn_t bcm2708_spi_interrupt(int irq, void *dev_id)
- +{
- + struct spi_master *master = dev_id;
- + struct bcm2708_spi *bs = spi_master_get_devdata(master);
- + u32 cs;
- +
- + spin_lock(&bs->lock);
- +
- + cs = bcm2708_rd(bs, SPI_CS);
- +
- + if (cs & SPI_CS_DONE) {
- + if (bs->len) { /* first interrupt in a transfer */
- + /* fill the TX fifo with up to 16 bytes */
- + bcm2708_wr_fifo(bs, 16);
- + } else { /* transfer complete */
- + /* disable interrupts */
- + cs &= ~(SPI_CS_INTR | SPI_CS_INTD);
- + bcm2708_wr(bs, SPI_CS, cs);
- +
- + /* drain RX FIFO */
- + while (cs & SPI_CS_RXD) {
- + bcm2708_rd_fifo(bs, 1);
- + cs = bcm2708_rd(bs, SPI_CS);
- + }
- +
- + /* wake up our bh */
- + complete(&bs->done);
- + }
- + } else if (cs & SPI_CS_RXR) {
- + /* read 12 bytes of data */
- + bcm2708_rd_fifo(bs, 12);
- +
- + /* write up to 12 bytes */
- + bcm2708_wr_fifo(bs, 12);
- + }
- +
- + spin_unlock(&bs->lock);
- +
- + return IRQ_HANDLED;
- +}
- +
- +static int bcm2708_setup_state(struct spi_master *master,
- + struct device *dev, struct bcm2708_spi_state *state,
- + u32 hz, u8 csel, u8 mode, u8 bpw)
- +{
- + struct bcm2708_spi *bs = spi_master_get_devdata(master);
- + int cdiv;
- + unsigned long bus_hz;
- + u32 cs = 0;
- +
- + bus_hz = clk_get_rate(bs->clk);
- +
- + if (hz >= bus_hz) {
- + cdiv = 2; /* bus_hz / 2 is as fast as we can go */
- + } else if (hz) {
- + cdiv = DIV_ROUND_UP(bus_hz, hz);
- +
- + /* CDIV must be a power of 2, so round up */
- + cdiv = roundup_pow_of_two(cdiv);
- +
- + if (cdiv > 65536) {
- + dev_dbg(dev,
- + "setup: %d Hz too slow, cdiv %u; min %ld Hz\n",
- + hz, cdiv, bus_hz / 65536);
- + return -EINVAL;
- + } else if (cdiv == 65536) {
- + cdiv = 0;
- + } else if (cdiv == 1) {
- + cdiv = 2; /* 1 gets rounded down to 0; == 65536 */
- + }
- + } else {
- + cdiv = 0;
- + }
- +
- + switch (bpw) {
- + case 8:
- + break;
- + case 9:
- + /* Reading in LoSSI mode is a special case. See 'BCM2835 ARM Peripherals' datasheet */
- + cs |= SPI_CS_LEN;
- + break;
- + default:
- + dev_dbg(dev, "setup: invalid bits_per_word %u (must be 8 or 9)\n",
- + bpw);
- + return -EINVAL;
- + }
- +
- + if (mode & SPI_CPOL)
- + cs |= SPI_CS_CPOL;
- + if (mode & SPI_CPHA)
- + cs |= SPI_CS_CPHA;
- +
- + if (!(mode & SPI_NO_CS)) {
- + if (mode & SPI_CS_HIGH) {
- + cs |= SPI_CS_CSPOL;
- + cs |= SPI_CS_CSPOL0 << csel;
- + }
- +
- + cs |= csel;
- + } else {
- + cs |= SPI_CS_CS_10 | SPI_CS_CS_01;
- + }
- +
- + if (state) {
- + state->cs = cs;
- + state->cdiv = cdiv;
- + dev_dbg(dev, "setup: want %d Hz; "
- + "bus_hz=%lu / cdiv=%u == %lu Hz; "
- + "mode %u: cs 0x%08X\n",
- + hz, bus_hz, cdiv, bus_hz/cdiv, mode, cs);
- + }
- +
- + return 0;
- +}
- +
- +static int bcm2708_process_transfer(struct bcm2708_spi *bs,
- + struct spi_message *msg, struct spi_transfer *xfer)
- +{
- + struct spi_device *spi = msg->spi;
- + struct bcm2708_spi_state state, *stp;
- + int ret;
- + u32 cs;
- +
- + if (bs->stopping)
- + return -ESHUTDOWN;
- +
- + if (xfer->bits_per_word || xfer->speed_hz) {
- + ret = bcm2708_setup_state(spi->master, &spi->dev, &state,
- + xfer->speed_hz ? xfer->speed_hz : spi->max_speed_hz,
- + spi->chip_select, spi->mode,
- + xfer->bits_per_word ? xfer->bits_per_word :
- + spi->bits_per_word);
- + if (ret)
- + return ret;
- +
- + stp = &state;
- + } else {
- + stp = spi->controller_state;
- + }
- +
- + reinit_completion(&bs->done);
- + bs->tx_buf = xfer->tx_buf;
- + bs->rx_buf = xfer->rx_buf;
- + bs->len = xfer->len;
- +
- + cs = stp->cs | SPI_CS_INTR | SPI_CS_INTD | SPI_CS_TA;
- +
- + bcm2708_wr(bs, SPI_CLK, stp->cdiv);
- + bcm2708_wr(bs, SPI_CS, cs);
- +
- + ret = wait_for_completion_timeout(&bs->done,
- + msecs_to_jiffies(SPI_TIMEOUT_MS));
- + if (ret == 0) {
- + dev_err(&spi->dev, "transfer timed out\n");
- + return -ETIMEDOUT;
- + }
- +
- + if (xfer->delay_usecs)
- + udelay(xfer->delay_usecs);
- +
- + if (list_is_last(&xfer->transfer_list, &msg->transfers) ||
- + xfer->cs_change) {
- + /* clear TA and interrupt flags */
- + bcm2708_wr(bs, SPI_CS, stp->cs);
- + }
- +
- + msg->actual_length += (xfer->len - bs->len);
- +
- + return 0;
- +}
- +
- +static void bcm2708_work(struct work_struct *work)
- +{
- + struct bcm2708_spi *bs = container_of(work, struct bcm2708_spi, work);
- + unsigned long flags;
- + struct spi_message *msg;
- + struct spi_transfer *xfer;
- + int status = 0;
- +
- + spin_lock_irqsave(&bs->lock, flags);
- + while (!list_empty(&bs->queue)) {
- + msg = list_first_entry(&bs->queue, struct spi_message, queue);
- + list_del_init(&msg->queue);
- + spin_unlock_irqrestore(&bs->lock, flags);
- +
- + list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- + status = bcm2708_process_transfer(bs, msg, xfer);
- + if (status)
- + break;
- + }
- +
- + msg->status = status;
- + msg->complete(msg->context);
- +
- + spin_lock_irqsave(&bs->lock, flags);
- + }
- + spin_unlock_irqrestore(&bs->lock, flags);
- +}
- +
- +static int bcm2708_spi_setup(struct spi_device *spi)
- +{
- + struct bcm2708_spi *bs = spi_master_get_devdata(spi->master);
- + struct bcm2708_spi_state *state;
- + int ret;
- +
- + if (bs->stopping)
- + return -ESHUTDOWN;
- +
- + if (!(spi->mode & SPI_NO_CS) &&
- + (spi->chip_select > spi->master->num_chipselect)) {
- + dev_dbg(&spi->dev,
- + "setup: invalid chipselect %u (%u defined)\n",
- + spi->chip_select, spi->master->num_chipselect);
- + return -EINVAL;
- + }
- +
- + state = spi->controller_state;
- + if (!state) {
- + state = kzalloc(sizeof(*state), GFP_KERNEL);
- + if (!state)
- + return -ENOMEM;
- +
- + spi->controller_state = state;
- + }
- +
- + ret = bcm2708_setup_state(spi->master, &spi->dev, state,
- + spi->max_speed_hz, spi->chip_select, spi->mode,
- + spi->bits_per_word);
- + if (ret < 0) {
- + kfree(state);
- + spi->controller_state = NULL;
- + return ret;
- + }
- +
- + dev_dbg(&spi->dev,
- + "setup: cd %d: %d Hz, bpw %u, mode 0x%x -> CS=%08x CDIV=%04x\n",
- + spi->chip_select, spi->max_speed_hz, spi->bits_per_word,
- + spi->mode, state->cs, state->cdiv);
- +
- + return 0;
- +}
- +
- +static int bcm2708_spi_transfer(struct spi_device *spi, struct spi_message *msg)
- +{
- + struct bcm2708_spi *bs = spi_master_get_devdata(spi->master);
- + struct spi_transfer *xfer;
- + int ret;
- + unsigned long flags;
- +
- + if (unlikely(list_empty(&msg->transfers)))
- + return -EINVAL;
- +
- + if (bs->stopping)
- + return -ESHUTDOWN;
- +
- + list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- + if (!(xfer->tx_buf || xfer->rx_buf) && xfer->len) {
- + dev_dbg(&spi->dev, "missing rx or tx buf\n");
- + return -EINVAL;
- + }
- +
- + if (!xfer->bits_per_word || xfer->speed_hz)
- + continue;
- +
- + ret = bcm2708_setup_state(spi->master, &spi->dev, NULL,
- + xfer->speed_hz ? xfer->speed_hz : spi->max_speed_hz,
- + spi->chip_select, spi->mode,
- + xfer->bits_per_word ? xfer->bits_per_word :
- + spi->bits_per_word);
- + if (ret)
- + return ret;
- + }
- +
- + msg->status = -EINPROGRESS;
- + msg->actual_length = 0;
- +
- + spin_lock_irqsave(&bs->lock, flags);
- + list_add_tail(&msg->queue, &bs->queue);
- + queue_work(bs->workq, &bs->work);
- + spin_unlock_irqrestore(&bs->lock, flags);
- +
- + return 0;
- +}
- +
- +static void bcm2708_spi_cleanup(struct spi_device *spi)
- +{
- + if (spi->controller_state) {
- + kfree(spi->controller_state);
- + spi->controller_state = NULL;
- + }
- +}
- +
- +static int bcm2708_spi_probe(struct platform_device *pdev)
- +{
- + struct resource *regs;
- + int irq, err = -ENOMEM;
- + struct clk *clk;
- + struct spi_master *master;
- + struct bcm2708_spi *bs;
- +
- + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- + if (!regs) {
- + dev_err(&pdev->dev, "could not get IO memory\n");
- + return -ENXIO;
- + }
- +
- + irq = platform_get_irq(pdev, 0);
- + if (irq < 0) {
- + dev_err(&pdev->dev, "could not get IRQ\n");
- + return irq;
- + }
- +
- + clk = clk_get(&pdev->dev, NULL);
- + if (IS_ERR(clk)) {
- + dev_err(&pdev->dev, "could not find clk: %ld\n", PTR_ERR(clk));
- + return PTR_ERR(clk);
- + }
- +
- + bcm2708_init_pinmode();
- +
- + master = spi_alloc_master(&pdev->dev, sizeof(*bs));
- + if (!master) {
- + dev_err(&pdev->dev, "spi_alloc_master() failed\n");
- + goto out_clk_put;
- + }
- +
- + /* the spi->mode bits understood by this driver: */
- + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_NO_CS;
- +
- + master->bus_num = pdev->id;
- + master->num_chipselect = 3;
- + master->setup = bcm2708_spi_setup;
- + master->transfer = bcm2708_spi_transfer;
- + master->cleanup = bcm2708_spi_cleanup;
- + platform_set_drvdata(pdev, master);
- +
- + bs = spi_master_get_devdata(master);
- +
- + spin_lock_init(&bs->lock);
- + INIT_LIST_HEAD(&bs->queue);
- + init_completion(&bs->done);
- + INIT_WORK(&bs->work, bcm2708_work);
- +
- + bs->base = ioremap(regs->start, resource_size(regs));
- + if (!bs->base) {
- + dev_err(&pdev->dev, "could not remap memory\n");
- + goto out_master_put;
- + }
- +
- + bs->workq = create_singlethread_workqueue(dev_name(&pdev->dev));
- + if (!bs->workq) {
- + dev_err(&pdev->dev, "could not create workqueue\n");
- + goto out_iounmap;
- + }
- +
- + bs->irq = irq;
- + bs->clk = clk;
- + bs->stopping = false;
- +
- + err = request_irq(irq, bcm2708_spi_interrupt, 0, dev_name(&pdev->dev),
- + master);
- + if (err) {
- + dev_err(&pdev->dev, "could not request IRQ: %d\n", err);
- + goto out_workqueue;
- + }
- +
- + /* initialise the hardware */
- + clk_enable(clk);
- + bcm2708_wr(bs, SPI_CS, SPI_CS_REN | SPI_CS_CLEAR_RX | SPI_CS_CLEAR_TX);
- +
- + err = spi_register_master(master);
- + if (err) {
- + dev_err(&pdev->dev, "could not register SPI master: %d\n", err);
- + goto out_free_irq;
- + }
- +
- + dev_info(&pdev->dev, "SPI Controller at 0x%08lx (irq %d)\n",
- + (unsigned long)regs->start, irq);
- +
- + return 0;
- +
- +out_free_irq:
- + free_irq(bs->irq, master);
- +out_workqueue:
- + destroy_workqueue(bs->workq);
- +out_iounmap:
- + iounmap(bs->base);
- +out_master_put:
- + spi_master_put(master);
- +out_clk_put:
- + clk_put(clk);
- + return err;
- +}
- +
- +static int bcm2708_spi_remove(struct platform_device *pdev)
- +{
- + struct spi_master *master = platform_get_drvdata(pdev);
- + struct bcm2708_spi *bs = spi_master_get_devdata(master);
- +
- + /* reset the hardware and block queue progress */
- + spin_lock_irq(&bs->lock);
- + bs->stopping = true;
- + bcm2708_wr(bs, SPI_CS, SPI_CS_CLEAR_RX | SPI_CS_CLEAR_TX);
- + spin_unlock_irq(&bs->lock);
- +
- + flush_work(&bs->work);
- +
- + clk_disable(bs->clk);
- + clk_put(bs->clk);
- + free_irq(bs->irq, master);
- + iounmap(bs->base);
- +
- + spi_unregister_master(master);
- +
- + return 0;
- +}
- +
- +static struct platform_driver bcm2708_spi_driver = {
- + .driver = {
- + .name = DRV_NAME,
- + .owner = THIS_MODULE,
- + },
- + .probe = bcm2708_spi_probe,
- + .remove = bcm2708_spi_remove,
- +};
- +
- +
- +static int __init bcm2708_spi_init(void)
- +{
- + return platform_driver_probe(&bcm2708_spi_driver, bcm2708_spi_probe);
- +}
- +module_init(bcm2708_spi_init);
- +
- +static void __exit bcm2708_spi_exit(void)
- +{
- + platform_driver_unregister(&bcm2708_spi_driver);
- +}
- +module_exit(bcm2708_spi_exit);
- +
- +
- +//module_platform_driver(bcm2708_spi_driver);
- +
- +MODULE_DESCRIPTION("SPI controller driver for Broadcom BCM2708");
- +MODULE_AUTHOR("Chris Boot <bootc@bootc.net>");
- +MODULE_LICENSE("GPL v2");
- +MODULE_ALIAS("platform:" DRV_NAME);
|