From 007d9f94b01c399553cff4143ae3d696b8189b57 Mon Sep 17 00:00:00 2001 From: Matthieu Crapet Date: Sun, 4 Jan 2009 01:51:24 +0100 Subject: [PATCH] EP93xx SPI driver MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Signed-off-by: Petr Å tetiar --- arch/arm/mach-ep93xx/clock.c | 6 +- arch/arm/mach-ep93xx/core.c | 29 ++ arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h | 1 + arch/arm/mach-ep93xx/include/mach/spi.h | 18 + arch/arm/mach-ep93xx/ts72xx.c | 37 ++- drivers/spi/Kconfig | 13 + drivers/spi/Makefile | 2 + drivers/spi/spi_ep93xx.c | 496 +++++++++++++++++++++++ drivers/spi/spi_ep93xx.h | 61 +++ drivers/spi/tmp124.c | 158 +++++++ 10 files changed, 819 insertions(+), 2 deletions(-) create mode 100644 arch/arm/mach-ep93xx/include/mach/spi.h create mode 100644 drivers/spi/spi_ep93xx.c create mode 100644 drivers/spi/spi_ep93xx.h create mode 100644 drivers/spi/tmp124.c diff --git a/arch/arm/mach-ep93xx/clock.c b/arch/arm/mach-ep93xx/clock.c index 6062e47..2b47048 100644 --- a/arch/arm/mach-ep93xx/clock.c +++ b/arch/arm/mach-ep93xx/clock.c @@ -51,7 +51,10 @@ static struct clk clk_usb_host = { .enable_reg = EP93XX_SYSCON_CLOCK_CONTROL, .enable_mask = EP93XX_SYSCON_CLOCK_USH_EN, }; - +static struct clk clk_ssp = { + .name = "sspclk", + .rate = 7400000, +}; static struct clk *clocks[] = { &clk_uart, @@ -61,6 +64,7 @@ static struct clk *clocks[] = { &clk_p, &clk_pll2, &clk_usb_host, + &clk_ssp, }; struct clk *clk_get(struct device *dev, const char *id) diff --git a/arch/arm/mach-ep93xx/core.c b/arch/arm/mach-ep93xx/core.c index f689531..00c2316 100644 --- a/arch/arm/mach-ep93xx/core.c +++ b/arch/arm/mach-ep93xx/core.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -484,6 +485,33 @@ static struct platform_device ep93xx_ohci_device = { }; +static struct resource ep93xx_ssp_resources[] = { + { + .start = EP93XX_SPI_BASE, // virtual addresses + .end = EP93XX_SPI_BASE + 0x14, + .flags = IORESOURCE_MEM, + }, { + .start = IRQ_EP93XX_SSP, // overrun in receive fifo + .end = IRQ_EP93XX_SSP, + .flags = IORESOURCE_IRQ, + } +}; + +static struct ep93xx_spi_data ep93xx_ssp_data = { + .chip_select_num = 4, +}; + +static struct platform_device ep93xx_ssp_device = { + .name = "ep93xx-spi", + .id = 1, + .resource = ep93xx_ssp_resources, + .num_resources = ARRAY_SIZE(ep93xx_ssp_resources), + .dev = { + .platform_data = &ep93xx_ssp_data, + } +}; + + static const struct gpio_led ep93xx_led_pins[] = { { .name = "green", @@ -563,4 +591,5 @@ void __init ep93xx_init_devices(void) platform_device_register(&ep93xx_gpio_leds); platform_device_register(&ep93xx_rtc_device); platform_device_register(&ep93xx_ohci_device); + platform_device_register(&ep93xx_ssp_device); } diff --git a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h index e26b41b..be0b9d4 100644 --- a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h +++ b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h @@ -97,6 +97,7 @@ #define EP93XX_AAC_BASE (EP93XX_APB_VIRT_BASE + 0x00080000) #define EP93XX_SPI_BASE (EP93XX_APB_VIRT_BASE + 0x000a0000) +#define EP93XX_SPI_PHYS_BASE (EP93XX_APB_PHYS_BASE + 0x000a0000) #define EP93XX_IRDA_BASE (EP93XX_APB_VIRT_BASE + 0x000b0000) diff --git a/arch/arm/mach-ep93xx/include/mach/spi.h b/arch/arm/mach-ep93xx/include/mach/spi.h new file mode 100644 index 0000000..0e07fc9 --- /dev/null +++ b/arch/arm/mach-ep93xx/include/mach/spi.h @@ -0,0 +1,18 @@ +/* + * arch/arm/mach-ep93xx/include/mach/spi.h + */ + +struct ep93xx_spi_data { + u16 chip_select_num; +}; + + +/* spi_board_info.controller_data for SPI slave devices */ +struct ep93xx_spi_chip { + void (*cs_control)(u32 command); +}; + +/* Chip-select state */ +#define SPI_CS_ASSERT 0x1 +#define SPI_CS_DEASSERT 0x2 +#define SPI_CS_INIT 0x4 diff --git a/arch/arm/mach-ep93xx/ts72xx.c b/arch/arm/mach-ep93xx/ts72xx.c index 9835b05..2141e73 100644 --- a/arch/arm/mach-ep93xx/ts72xx.c +++ b/arch/arm/mach-ep93xx/ts72xx.c @@ -19,8 +19,11 @@ #include #include #include +#include #include #include +#include +#include #include #include #include @@ -233,6 +236,34 @@ static struct platform_device ts72xx_max197_device = { .resource = ts72xx_max197_resources, }; +#if defined(CONFIG_SPI_MASTER) +void tmp124_spi_cs(u32 command) // FGPIO[2] +{ + if (command & SPI_CS_ASSERT) { + gpio_set_value(EP93XX_GPIO_LINE_MCCD2, 0); + } else if (command & SPI_CS_DEASSERT) { + gpio_set_value(EP93XX_GPIO_LINE_MCCD2, 1); + } else if (command & SPI_CS_INIT) { + gpio_direction_output(EP93XX_GPIO_LINE_MCCD2, 1); + } +} + +static struct ep93xx_spi_chip tmp124_hw = { + .cs_control = tmp124_spi_cs, +}; + +static struct spi_board_info ts72xx_spi_bus[] __initdata = { + { + /* TMP124 */ + .modalias = "tmp124", + .controller_data = &tmp124_hw, + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 2 * 1000 * 1000, + } +}; +#endif + static void __init ts72xx_init_machine(void) { ep93xx_init_devices(); @@ -251,7 +282,11 @@ static void __init ts72xx_init_machine(void) if (is_max197_installed()) { platform_device_register(&ts72xx_max197_device); - } + } + + #if defined(CONFIG_SPI_MASTER) + spi_register_board_info(ts72xx_spi_bus, ARRAY_SIZE(ts72xx_spi_bus)); + #endif } MACHINE_START(TS72XX, "Technologic Systems TS-72xx SBC") diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index b9d0efb..51b55b5 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -204,6 +204,12 @@ config SPI_XILINX See the "OPB Serial Peripheral Interface (SPI) (v1.00e)" Product Specification document (DS464) for hardware details. +config SPI_EP93XX + tristate "EP93XX SPI controller" + depends on SPI_MASTER + help + Simple SPI driver for EP93xx. + # # Add new SPI master controllers in alphabetical order above this line # @@ -243,6 +249,13 @@ config SPI_TLE62X0 sysfs interface, with each line presented as a kind of GPIO exposing both switch control and diagnostic feedback. +config SPI_TMP124 + tristate "Texas Instruments TMP1224, TMP124" + depends on SPI_MASTER && SYSFS + help + SPI driver for TMP12X temperature sensor chips. + This provides a sysfs entry for temperature reading (2°C accurate). + # # Add new SPI protocol masters in alphabetical order above this line # diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index ccf18de..1effdf3 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s3c24xx_gpio.o obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o obj-$(CONFIG_SPI_TXX9) += spi_txx9.o obj-$(CONFIG_SPI_XILINX) += xilinx_spi.o +obj-$(CONFIG_SPI_EP93XX) += spi_ep93xx.o obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o # ... add above this line ... @@ -35,6 +36,7 @@ obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o obj-$(CONFIG_SPI_AT25) += at25.o obj-$(CONFIG_SPI_SPIDEV) += spidev.o obj-$(CONFIG_SPI_TLE62X0) += tle62x0.o +obj-$(CONFIG_SPI_TMP124) += tmp124.o # ... add above this line ... # SPI slave controller drivers (upstream link) diff --git a/drivers/spi/spi_ep93xx.c b/drivers/spi/spi_ep93xx.c new file mode 100644 index 0000000..0f51b00 --- /dev/null +++ b/drivers/spi/spi_ep93xx.c @@ -0,0 +1,496 @@ +/* + * EP93xx SPI driver + * + * (c) Copyright 2008 Matthieu Crapet + * Based on pxa2xx_spi.c by Stephen Street / StreetFire Sound Labs + * + * 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. + * + * Notes: + * - Uses SSP IP of processor + * - Restricted to SPI master mode + * - No DMA transfer + * - No power management support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "spi_ep93xx.h" + + +struct ep93xx_spi { + struct spi_master *master; /* SPI framework hookup */ + void __iomem *ioaddr; /* Virtual base address to SSP registers */ + u32 freq_max; + u32 freq_min; + struct clk *clk; + + struct workqueue_struct *workqueue; + struct work_struct work; + spinlock_t lock; + struct list_head queue; + + struct ep93xx_spi_chip *cs_chip; /* Chip Select function */ +}; + +static inline u16 read_reg(void *base, off_t offset) +{ + return __raw_readw(base + offset); +} + +static inline void write_reg(u16 v, void *base, off_t offset) +{ + __raw_writew(v, base + offset); +} + +/* + * compute SCR and CPSDVR bits to setup spi clock based on main input clock rate + * that was specified in platform data structure + * according to datasheet: + * tempclk = sspclk / cpsdvr + * spiclk = tempclk / (scr + 1) + * SCR valid range is 0..255 + * CPSDVR valid range is 2..254 + */ +static int spi_speed_set(struct ep93xx_spi *drv_data, unsigned speed_hz) +{ + unsigned long mainclk_hz = clk_get_rate(drv_data->clk); + u32 cpsdvr, scr; + u16 ssp_cr0; + + for (cpsdvr = 2; cpsdvr <= 254; cpsdvr+=2) { + scr = DIV_ROUND_UP(mainclk_hz / speed_hz, cpsdvr); + /* now we have SCR+1 in scr, so count with that */ + if (scr == 0) { /* speed_hz too big */ + return -EINVAL; + } + if (scr <= (255 + 1)) + break; /* we have valid scr and cpsdvr */ + } + if (cpsdvr > 254) { + /* speed_hz is too small, set to minimum speed */ + scr = cpsdvr + 1; + cpsdvr--; + } + scr--; + write_reg(cpsdvr, drv_data->ioaddr, SSPCPSR); + ssp_cr0 = read_reg(drv_data->ioaddr, SSPCR0); + ssp_cr0 &= ~(SSP_CONTROL_SCR(0xff)); + write_reg((ssp_cr0 | SSP_CONTROL_SCR(scr)), drv_data->ioaddr, SSPCR0); + + return 0; +} + +static irqreturn_t ssp_int(int irq, void *dev_id) +{ + struct ep93xx_spi *drv_data = dev_id; + write_reg(SSP_SSPIxx_RORIS, drv_data->ioaddr, SSPIxR); /* clear it */ + + printk(KERN_WARNING "SSP overrun\n"); + return IRQ_HANDLED; +} + +static int transfer_one_work(struct ep93xx_spi *drv_data, struct spi_message *msg) +{ + struct spi_device *spi = msg->spi; + struct spi_transfer *xfer; + int i; + u8 *p; + + drv_data->cs_chip->cs_control(SPI_CS_ASSERT); + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + if (!(xfer->tx_buf || xfer->rx_buf)) { + dev_dbg(&spi->dev, "missing rx or tx buf\n"); + drv_data->cs_chip->cs_control(SPI_CS_DEASSERT); + return -EINVAL; + } + + if (xfer->bits_per_word) { + u16 v = read_reg(drv_data->ioaddr, SSPCR0); + v = v & SSP_CONTROL_DSS_MASK; + v = v | ((xfer->bits_per_word - 1) & SSP_CONTROL_DSS_MASK); + write_reg(v, drv_data->ioaddr, SSPCR0); + } + + if (xfer->speed_hz) { + if (spi_speed_set(drv_data,xfer->speed_hz) != 0){ + dev_err(&spi->dev, "xfer speed hz invalid\n"); + return -EINVAL; + } + } + + if (xfer->tx_buf) { + p = (u8 *)xfer->tx_buf; + + if ((spi->bits_per_word == 16 && xfer->bits_per_word == 0) || + (xfer->bits_per_word == 16)) { + for (i = 0; i < xfer->len; i+=2) + write_reg((p[i] << 8) + p[i+1], drv_data->ioaddr, SSPDR); + } else { + for (i = 0; i < xfer->len; i++) + write_reg(p[i], drv_data->ioaddr, SSPDR); + } + } + + if (xfer->rx_buf) { + u16 v; + p = xfer->rx_buf; + + if ((spi->bits_per_word == 16 && xfer->bits_per_word == 0) || + (xfer->bits_per_word == 16)) { + for (i = 0; i < xfer->len; i+=2) { + v = read_reg(drv_data->ioaddr, SSPDR); + p[i] = v >> 8; + p[i+1] = v & 0xFF; + } + } else { + for (i = 0; i < xfer->len; i++) + p[i] = read_reg(drv_data->ioaddr, SSPDR); + } + } + + /* restore device bits_per_word */ + if (xfer->bits_per_word) { + u16 v = read_reg(drv_data->ioaddr, SSPCR0); + v = v & SSP_CONTROL_DSS_MASK; + v |= spi->bits_per_word - 1; + write_reg(v, drv_data->ioaddr, SSPCR0); + } + + /* restore device speed_hz */ + if (xfer->speed_hz) { + if (spi_speed_set(drv_data,spi->max_speed_hz) != 0) + return -EINVAL; + } + + dev_dbg(&spi->dev, "transfer: len=%u, tx_buf=%p, rx_buf=%p\n", xfer->len, xfer->tx_buf, xfer->rx_buf); + } + + if (xfer->delay_usecs) + udelay(xfer->delay_usecs); + drv_data->cs_chip->cs_control(SPI_CS_DEASSERT); + + msg->actual_length = 0; + msg->status = 0; + + if (msg->complete) + msg->complete(msg->context); + + return 0; +} + + +static void ssp_work(struct work_struct *work) +{ + struct ep93xx_spi *drv_data = container_of(work, struct ep93xx_spi, work); + unsigned long flags; + + spin_lock_irqsave(&drv_data->lock, flags); + while (!list_empty(&drv_data->queue)) { + struct spi_message *m; + + m = container_of(drv_data->queue.next, struct spi_message, queue); + list_del_init(&m->queue); + spin_unlock_irqrestore(&drv_data->lock, flags); + + transfer_one_work(drv_data, m); + + spin_lock_irqsave(&drv_data->lock, flags); + } + spin_unlock_irqrestore(&drv_data->lock, flags); +} + + +static int ssp_transfer(struct spi_device *spi, struct spi_message *m) +{ + struct spi_master *master = spi->master; + struct ep93xx_spi *drv_data = spi_master_get_devdata(master); + struct spi_transfer *t; + unsigned long flags; + + m->actual_length = 0; + + /* check each transfer's parameters */ + list_for_each_entry (t, &m->transfers, transfer_list) { + u32 speed_hz = t->speed_hz ? t->speed_hz : spi->max_speed_hz; + u8 bits_per_word = t->bits_per_word ? t->bits_per_word : spi->bits_per_word; + + if (!t->tx_buf && !t->rx_buf && t->len) + return -EINVAL; + if (bits_per_word < 4 || bits_per_word > 16) + return -EINVAL; + /*if (t->len & ((bits_per_word >> 3) - 1)) + return -EINVAL;*/ + if (speed_hz < drv_data->freq_min || speed_hz > drv_data->freq_max) + return -EINVAL; + } + + spin_lock_irqsave(&drv_data->lock, flags); + list_add_tail(&m->queue, &drv_data->queue); + queue_work(drv_data->workqueue, &drv_data->work); + spin_unlock_irqrestore(&drv_data->lock, flags); + + return 0; + +} + +/* the spi->mode bits understood by this driver: */ +#define MODEBITS (SPI_CPOL | SPI_CPHA) + +static int ssp_setup(struct spi_device *spi) +{ + struct ep93xx_spi *drv_data = spi_master_get_devdata(spi->master); + struct ep93xx_spi_chip *chip_info; + u16 v; + + /* Get controller data */ + chip_info = spi->controller_data; + if (!chip_info) { + dev_err(&spi->dev, "setup: controller data required\n"); + return -EINVAL; + } + drv_data->cs_chip = chip_info; + drv_data->cs_chip->cs_control(SPI_CS_INIT); + + if (!spi->bits_per_word) { + spi->bits_per_word = 8; + } + + if (spi->bits_per_word < 4 || spi->bits_per_word > 16) { + dev_dbg(&spi->dev, "setup: unsupported %d bit words\n", + spi->bits_per_word); + return -EINVAL; + } + + if (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; + } + + if (spi->mode & ~MODEBITS) { + dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n", + spi->mode & ~MODEBITS); + return -EINVAL; + } + + v = read_reg(drv_data->ioaddr, SSPCR0); + + if (spi->mode & SPI_CPOL) + v |= SSP_CONTROL_SPO; + else + v &= ~SSP_CONTROL_SPO; + + if (spi->mode & SPI_CPHA) + v |= SSP_CONTROL_SPH; + else + v &= ~SSP_CONTROL_SPH; + + v = v & SSP_CONTROL_DSS_MASK; + v |= spi->bits_per_word - 1; + + write_reg(v, drv_data->ioaddr, SSPCR0); + + if(!spi->max_speed_hz){ + spi->max_speed_hz = drv_data->freq_min; + }else if(spi->max_speed_hz > drv_data->freq_max || + spi->max_speed_hz < drv_data->freq_min){ + return -EINVAL; + } + + if (spi_speed_set(drv_data,spi->max_speed_hz) != 0){ + dev_dbg(&spi->dev, "setup: unsupported speed %u\n", spi->max_speed_hz); + return -EINVAL; + } + + return 0; +} + + +static void ssp_cleanup(struct spi_device *spi) +{ + struct ep93xx_spi *drv_data = spi_master_get_devdata(spi->master); + drv_data->cs_chip = NULL; +} + + +static int __init spi_ep93xx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_master *master; + struct ep93xx_spi_data *spi_data = pdev->dev.platform_data; + struct ep93xx_spi *drv_data = 0; + struct resource *memory_resource; + int irq, status = 0, min_div = 2, max_div = 254*(255+1); + + + /* Check I2SonSSP bit (ssp pins and i2s pins are multiplexed) */ + if (readl(EP93XX_SYSCON_DEVICE_CONFIG) & 0x80) + return -ENODEV; + + /* Allocate master with space for drv_data */ + master = spi_alloc_master(dev, sizeof(struct ep93xx_spi)); + if (!master) { + dev_err(&pdev->dev, "can not alloc spi_master\n"); + return -ENOMEM; + } + + /* Setup register addresses */ + memory_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!memory_resource) { + dev_err(&pdev->dev, "memory resources not defined\n"); + status = -ENODEV; + goto out_error_master_alloc; + } + + drv_data = spi_master_get_devdata(master); + drv_data->master = master; + drv_data->ioaddr = (unsigned long *)memory_resource->start; + drv_data->clk = clk_get(&pdev->dev, "sspclk"); + drv_data->freq_max = clk_get_rate(drv_data->clk) / min_div; + drv_data->freq_min = clk_get_rate(drv_data->clk) / max_div + 1; + + INIT_WORK(&drv_data->work, ssp_work); + spin_lock_init(&drv_data->lock); + INIT_LIST_HEAD(&drv_data->queue); + + drv_data->workqueue = create_singlethread_workqueue(master->dev.parent->bus_id); + if (!drv_data->workqueue){ + status = -EBUSY; + goto out_error_master_alloc; + } + + master->bus_num = pdev->id; + master->num_chipselect = spi_data->chip_select_num; + master->cleanup = ssp_cleanup; + master->setup = ssp_setup; + master->transfer = ssp_transfer; + + /* Attach to IRQ */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "irq resource not defined\n"); + status = -ENODEV; + goto out_error_master_alloc; + } + + status = request_irq(irq, ssp_int, 0, dev->bus_id, drv_data); + if (status < 0) { + dev_err(&pdev->dev, "can not get IRQ\n"); + goto out_error_master_alloc; + } + + /* Load default SSP configuration */ + write_reg(SSP_CONTROL_SSE, drv_data->ioaddr, SSPCR1); + write_reg(SPI_DEFAULT0, drv_data->ioaddr, SSPCR0); + write_reg(SPI_DEFAULT_DIVISOR, drv_data->ioaddr, SSPCPSR); + write_reg(0x00, drv_data->ioaddr, SSPCR1); + + /* Register with the SPI framework */ + platform_set_drvdata(pdev, drv_data); + status = spi_register_master(master); + if (status != 0) { + dev_err(&pdev->dev, "problem registering spi master\n"); + goto out_error_irq_alloc; + } + + write_reg(SPI_DEFAULT1, drv_data->ioaddr, SSPCR1); + return status; + +out_error_irq_alloc: + free_irq(irq, drv_data); + +out_error_master_alloc: + spi_master_put(master); + return status; +} + +static int spi_ep93xx_remove(struct platform_device *pdev) +{ + struct ep93xx_spi *drv_data = platform_get_drvdata(pdev); + int irq; + + if (!drv_data) + return 0; + + /* Disable SSP (clear SSE bit) */ + write_reg(0x00, drv_data->ioaddr, SSPCR1); + + /* Release IRQ */ + irq = platform_get_irq(pdev, 0); + + if (irq >= 0) + free_irq(irq, drv_data); + + /* Disconnect from the SPI framework */ + spi_unregister_master(drv_data->master); + + /* Remove the workqueue */ + destroy_workqueue(drv_data->workqueue); + + /* Prevent double remove */ + platform_set_drvdata(pdev, NULL); + + spi_master_put(drv_data->master); + + return 0; +} + +static void spi_ep93xx_shutdown(struct platform_device *pdev) +{ + int status = 0; + + if ((status = spi_ep93xx_remove(pdev)) != 0) + dev_err(&pdev->dev, "shutdown failed with %d\n", status); +} + +static struct platform_driver ep93xx_spi_platform_driver = { + .driver = { + .name = "ep93xx-spi", + .bus = &platform_bus_type, + .owner = THIS_MODULE, + }, + .remove = __exit_p(spi_ep93xx_remove), + .shutdown = spi_ep93xx_shutdown, +}; + +static int __init spi_ep93xx_init(void) +{ + return platform_driver_probe(&ep93xx_spi_platform_driver, spi_ep93xx_probe); +} + +static void __exit spi_ep93xx_exit(void) +{ + platform_driver_unregister(&ep93xx_spi_platform_driver); +} + +module_init(spi_ep93xx_init); +module_exit(spi_ep93xx_exit); + +MODULE_AUTHOR("Matthieu Crapet "); +MODULE_DESCRIPTION("EP93xx SPI Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.21"); diff --git a/drivers/spi/spi_ep93xx.h b/drivers/spi/spi_ep93xx.h new file mode 100644 index 0000000..6fad735 --- /dev/null +++ b/drivers/spi/spi_ep93xx.h @@ -0,0 +1,61 @@ +/* + * EP93xx SPI (simple) include + * + * (c) Copyright 2008 Matthieu Crapet + * Based on pxa2xx_spi.c by Stephen Street / StreetFire Sound Labs + * + * 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. + * + */ + +/* SSP Registers */ +#define SSPCR0 0x00 /* Control register 0 */ +#define SSPCR1 0x04 /* Control register 1 */ +#define SSPDR 0x08 /* Receice FIFO data register (16-bit read) */ + /* Transmit FIFO data register (16-bit write) */ +#define SSPSR 0x0C /* Status register */ +#define SSPCPSR 0x10 /* Clock prescale register (from 2 to 254, even number) */ +#define SSPIxR 0x14 /* Interrupt identification register (read) */ + /* Interrupt clear register (write) */ + +/* SSP control registers bit fields & masks */ +#define SSP_CONTROL_SCR(x) (((x) & 0xFF) << 8) /* Serial clock rate = SCLKOUT / CPSDVR / (1+SCR) */ +#define SSP_CONTROL_SPH (1 << 7) /* SCLKOUT phase (for SPI only) */ +#define SSP_CONTROL_SPO (1 << 6) /* SCLKOUT polarity (for SPI only) */ +#define SSP_CONTROL_FRF(x) (((x) & 3) << 4) /* Frame format (0=SPI) */ +#define SSP_CONTROL_DSS_4BIT_DATA 3 +#define SSP_CONTROL_DSS_8BIT_DATA 7 +#define SSP_CONTROL_DSS_15BIT_DATA 14 +#define SSP_CONTROL_DSS_16BIT_DATA 15 +#define SSP_CONTROL_DSS_MASK 0xF +#define SSP_CONTROL_MS (1 << 5) /* 0=master, 1=slave (can be modified when SSE=0) */ +#define SSP_CONTROL_SSE (1 << 4) /* SSP operation enable (=1), disable (=0) */ +#define SSP_CONTROL_LBM (1 << 3) /* Loop back mode */ +#define SSP_CONTROL_RORIE (1 << 2) /* Interrupt enable : overrun condition */ +#define SSP_CONTROL_TIE (1 << 1) /* Interrupt enable : transmit fifo */ +#define SSP_CONTROL_RIE (1 << 0) /* Interrupt enable : receive fifo */ + +/* SSP status register (read only) */ +#define SSP_STATUS_BUSY (1 << 4) /* Busy flag (0: SSP is idle) */ +#define SSP_STATUS_RFF (1 << 3) /* Receive fifo full ? (1=full) */ +#define SSP_STATUS_RNE (1 << 2) /* Receive fifo not empty ? (0=empty) */ +#define SSP_STATUS_TNF (1 << 1) /* Transmit fifo not full ? (0=full) */ +#define SSP_STATUS_TFE (1 << 0) /* Transmit fifo empty ? (1=empty) */ + +/* SSP SSPIIR/SSPICR register (write 1 to clear interrupt) */ +#define SSP_SSPIxx_RORIS (1 << 2) /* Receive fifo overrun interrupt status */ +#define SSP_SSPIxx_TIS (1 << 1) /* Transmit fifo service request interrupt status */ +#define SSP_SSPIxx_RIS (1 << 0) /* Receive fifo service request interrupt status */ + +/* Default configuration values */ +#define SPI_DEFAULT0 (SSP_CONTROL_DSS_16BIT_DATA | SSP_CONTROL_FRF(0) | SSP_CONTROL_SCR(0)) +#define SPI_DEFAULT1 (SSP_CONTROL_SSE | SSP_CONTROL_RORIE) +#define SPI_DEFAULT_DIVISOR 254 diff --git a/drivers/spi/tmp124.c b/drivers/spi/tmp124.c new file mode 100644 index 0000000..7f7deaf --- /dev/null +++ b/drivers/spi/tmp124.c @@ -0,0 +1,158 @@ +/* + * TMP124 SPI protocol driver + * + * (c) Copyright 2008 Matthieu Crapet + * Based on tle62x0.c by Ben Dooks, + * + * 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. + * + * Note: The chip uses a '3-wire SPI' (miso and mosi are the same pin). + */ + +#include +#include +#include + +struct tmp124_state { + struct spi_device *bus; + u8 tx_buff[2]; + u8 rx_buff[2]; +}; + + +static inline int tmp124_write_then_read(struct tmp124_state *st) +{ + struct spi_message msg; + struct spi_transfer xfer[2] = { + { + .tx_buf = st->tx_buff, + .rx_buf = NULL, + .len = 2, + .delay_usecs = 1000, + }, { + .tx_buf = NULL, + .rx_buf = st->rx_buff, + .len = 2, + } + }; + + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); + spi_message_add_tail(&xfer[1], &msg); + + return spi_sync(st->bus, &msg); +} + + +static ssize_t tmp124_temperature_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tmp124_state *st = dev_get_drvdata(dev); + int ret; + + st->tx_buff[0] = 0x80; + st->tx_buff[1] = 0x00; + + ret = tmp124_write_then_read(st); + if (ret < 0) { + dev_err(&st->bus->dev, "tmp124_write_then_read\n"); + ret = 0; + } else { + signed short v = (st->rx_buff[0] << 8) + st->rx_buff[1]; + signed long val; + + val = v >> 3; + + /* 2 digit precision (0.0625*100) */ + val = (val * 50) / 8; + ret = snprintf(buf, PAGE_SIZE, "%ld.%02d\n", val/100, abs(val%100)); + } + return ret; +} + + +static DEVICE_ATTR(temperature, S_IRUGO, tmp124_temperature_show, NULL); + + +static int __devinit tmp124_probe(struct spi_device *spi) +{ + struct tmp124_state *st; + int ret; + + st = kzalloc(sizeof(struct tmp124_state), GFP_KERNEL); + if (st == NULL) { + dev_err(&spi->dev, "no memory for device state\n"); + return -ENOMEM; + } + + /* required config */ + spi->bits_per_word = 16; + + st->bus = spi; + + ret = spi_setup(spi); + if (ret) { + dev_err(&spi->dev, "setup device\n"); + goto err; + } + + ret = device_create_file(&spi->dev, &dev_attr_temperature); + if (ret) { + dev_err(&spi->dev, "cannot create temperature attribute\n"); + goto err; + } + + spi_set_drvdata(spi, st); + return 0; + +err: + kfree(st); + return ret; +} + + +static int __devexit tmp124_remove(struct spi_device *spi) +{ + struct tmp124_state *st = spi_get_drvdata(spi); + + device_remove_file(&spi->dev, &dev_attr_temperature); + kfree(st); + + return 0; +} + + +static struct spi_driver tmp124_driver = { + .driver = { + .name = "tmp124", + .owner = THIS_MODULE, + }, + .probe = tmp124_probe, + .remove = __devexit_p(tmp124_remove), +}; + +static __init int tmp124_init(void) +{ + return spi_register_driver(&tmp124_driver); +} + +static __exit void tmp124_exit(void) +{ + spi_unregister_driver(&tmp124_driver); +} + +module_init(tmp124_init); +module_exit(tmp124_exit); + +MODULE_AUTHOR("Matthieu Crapet "); +MODULE_DESCRIPTION("TMP124 SPI Protocol Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); -- 1.6.0.4