Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
231
drivers/spi/Kconfig
Normal file
231
drivers/spi/Kconfig
Normal file
@@ -0,0 +1,231 @@
|
||||
#
|
||||
# SPI driver configuration
|
||||
#
|
||||
# NOTE: the reason this doesn't show SPI slave support is mostly that
|
||||
# nobody's needed a slave side API yet. The master-role API is not
|
||||
# fully appropriate there, so it'd need some thought to do well.
|
||||
#
|
||||
menu "SPI support"
|
||||
|
||||
config SPI
|
||||
bool "SPI support"
|
||||
help
|
||||
The "Serial Peripheral Interface" is a low level synchronous
|
||||
protocol. Chips that support SPI can have data transfer rates
|
||||
up to several tens of Mbit/sec. Chips are addressed with a
|
||||
controller and a chipselect. Most SPI slaves don't support
|
||||
dynamic device discovery; some are even write-only or read-only.
|
||||
|
||||
SPI is widely used by microcontrollers to talk with sensors,
|
||||
eeprom and flash memory, codecs and various other controller
|
||||
chips, analog to digital (and d-to-a) converters, and more.
|
||||
MMC and SD cards can be accessed using SPI protocol; and for
|
||||
DataFlash cards used in MMC sockets, SPI must always be used.
|
||||
|
||||
SPI is one of a family of similar protocols using a four wire
|
||||
interface (select, clock, data in, data out) including Microwire
|
||||
(half duplex), SSP, SSI, and PSP. This driver framework should
|
||||
work with most such devices and controllers.
|
||||
|
||||
config SPI_DEBUG
|
||||
boolean "Debug support for SPI drivers"
|
||||
depends on SPI && DEBUG_KERNEL
|
||||
help
|
||||
Say "yes" to enable debug messaging (like dev_dbg and pr_debug),
|
||||
sysfs, and debugfs support in SPI controller and protocol drivers.
|
||||
|
||||
#
|
||||
# MASTER side ... talking to discrete SPI slave chips including microcontrollers
|
||||
#
|
||||
|
||||
config SPI_MASTER
|
||||
# boolean "SPI Master Support"
|
||||
boolean
|
||||
default SPI
|
||||
help
|
||||
If your system has an master-capable SPI controller (which
|
||||
provides the clock and chipselect), you can enable that
|
||||
controller and the protocol drivers for the SPI slave chips
|
||||
that are connected.
|
||||
|
||||
comment "SPI Master Controller Drivers"
|
||||
depends on SPI_MASTER
|
||||
|
||||
config SPI_ATMEL
|
||||
tristate "Atmel SPI Controller"
|
||||
depends on (ARCH_AT91 || AVR32) && SPI_MASTER
|
||||
help
|
||||
This selects a driver for the Atmel SPI Controller, present on
|
||||
many AT32 (AVR32) and AT91 (ARM) chips.
|
||||
|
||||
config SPI_BITBANG
|
||||
tristate "Bitbanging SPI master"
|
||||
depends on SPI_MASTER && EXPERIMENTAL
|
||||
help
|
||||
With a few GPIO pins, your system can bitbang the SPI protocol.
|
||||
Select this to get SPI support through I/O pins (GPIO, parallel
|
||||
port, etc). Or, some systems' SPI master controller drivers use
|
||||
this code to manage the per-word or per-transfer accesses to the
|
||||
hardware shift registers.
|
||||
|
||||
This is library code, and is automatically selected by drivers that
|
||||
need it. You only need to select this explicitly to support driver
|
||||
modules that aren't part of this kernel tree.
|
||||
|
||||
config SPI_BUTTERFLY
|
||||
tristate "Parallel port adapter for AVR Butterfly (DEVELOPMENT)"
|
||||
depends on SPI_MASTER && PARPORT && EXPERIMENTAL
|
||||
select SPI_BITBANG
|
||||
help
|
||||
This uses a custom parallel port cable to connect to an AVR
|
||||
Butterfly <http://www.atmel.com/products/avr/butterfly>, an
|
||||
inexpensive battery powered microcontroller evaluation board.
|
||||
This same cable can be used to flash new firmware.
|
||||
|
||||
config SPI_IMX
|
||||
tristate "Freescale iMX SPI controller"
|
||||
depends on SPI_MASTER && ARCH_IMX && EXPERIMENTAL
|
||||
help
|
||||
This enables using the Freescale iMX SPI controller in master
|
||||
mode.
|
||||
|
||||
config SPI_MPC83xx
|
||||
tristate "Freescale MPC83xx SPI controller"
|
||||
depends on SPI_MASTER && PPC_83xx && EXPERIMENTAL
|
||||
select SPI_BITBANG
|
||||
help
|
||||
This enables using the Freescale MPC83xx SPI controller in master
|
||||
mode.
|
||||
|
||||
Note, this driver uniquely supports the SPI controller on the MPC83xx
|
||||
family of PowerPC processors. The MPC83xx uses a simple set of shift
|
||||
registers for data (opposed to the CPM based descriptor model).
|
||||
|
||||
config SPI_OMAP_UWIRE
|
||||
tristate "OMAP1 MicroWire"
|
||||
depends on SPI_MASTER && ARCH_OMAP1
|
||||
select SPI_BITBANG
|
||||
help
|
||||
This hooks up to the MicroWire controller on OMAP1 chips.
|
||||
|
||||
|
||||
config SPI_PXA2XX
|
||||
tristate "PXA2xx SSP SPI master"
|
||||
depends on SPI_MASTER && ARCH_PXA && EXPERIMENTAL
|
||||
help
|
||||
This enables using a PXA2xx SSP port as a SPI master controller.
|
||||
The driver can be configured to use any SSP port and additional
|
||||
documentation can be found a Documentation/spi/pxa2xx.
|
||||
|
||||
config SPI_S3C24XX
|
||||
tristate "Samsung S3C24XX series SPI"
|
||||
depends on SPI_MASTER && ARCH_S3C2410 && EXPERIMENTAL
|
||||
help
|
||||
SPI driver for Samsung S3C24XX series ARM SoCs
|
||||
|
||||
config SPI_S3C24XX_GPIO
|
||||
tristate "Samsung S3C24XX series SPI by GPIO"
|
||||
depends on SPI_MASTER && ARCH_S3C2410 && SPI_BITBANG && EXPERIMENTAL
|
||||
help
|
||||
SPI driver for Samsung S3C24XX series ARM SoCs using
|
||||
GPIO lines to provide the SPI bus. This can be used where
|
||||
the inbuilt hardware cannot provide the transfer mode, or
|
||||
where the board is using non hardware connected pins.
|
||||
|
||||
config HS_SPI_S3C2443
|
||||
tristate "S3C2443 High Speed SPI Driver"
|
||||
depends on SPI && MACH_SMDK2443
|
||||
help
|
||||
Say Y here to include support for SPI controller in the
|
||||
Samsung S3C2443 based System-on-Chip devices.
|
||||
|
||||
config HS_SPI_S3C2450
|
||||
tristate "S3C2450 High Speed SPI Driver"
|
||||
depends on SPI && MACH_SMDK2450
|
||||
help
|
||||
Say Y here to include support for SPI controller in the
|
||||
Samsung S3C2450 based System-on-Chip devices.
|
||||
|
||||
config HS_SPI_S3C2416
|
||||
tristate "S3C2416 High Speed SPI Driver"
|
||||
depends on SPI && MACH_SMDK2416
|
||||
help
|
||||
Say Y here to include support for SPI controller in the
|
||||
Samsung S3C2416 based System-on-Chip devices.
|
||||
|
||||
config HS_SPI_S3C6400
|
||||
tristate "S3C6400 High Speed SPI Driver"
|
||||
depends on SPI && MACH_SMDK6400
|
||||
help
|
||||
Say Y here to include support for SPI controller in the
|
||||
Samsung S3C6400 based System-on-Chip devices.
|
||||
|
||||
config HS_SPI_S3C6410
|
||||
tristate "S3C6410 High Speed SPI Driver"
|
||||
depends on SPI && (MACH_SMDK6410 )
|
||||
help
|
||||
Say Y here to include support for SPI controller in the
|
||||
Samsung S3C6410 based System-on-Chip devices.
|
||||
|
||||
choice
|
||||
prompt "High Speed SPI Clock Implementations"
|
||||
depends on HS_SPI_S3C6400 || HS_SPI_S3C2443 || HS_SPI_S3C2450 || HS_SPI_S3C6410 || HS_SPI_S3C2416
|
||||
|
||||
config SPICLK_PCLK
|
||||
bool "PCLK"
|
||||
depends on HS_SPI_S3C6400 || HS_SPI_S3C2443 || HS_SPI_S3C2450 || HS_SPI_S3C6410 || HS_SPI_S3C2416
|
||||
help
|
||||
Say Y here to include support for pclk source.
|
||||
|
||||
config SPICLK_EPLL
|
||||
bool "EPLL"
|
||||
depends on HS_SPI_S3C6400 || HS_SPI_S3C2443 || HS_SPI_S3C2450 || HS_SPI_S3C6410 || HS_SPI_S3C2416
|
||||
help
|
||||
Say Y here to include support for EPLL source.
|
||||
|
||||
config SPICLK_USBCLK
|
||||
bool "USBCLK"
|
||||
depends on HS_SPI_S3C6400 || HS_SPI_S3C6410
|
||||
help
|
||||
Say Y here to include support for USB clock source.
|
||||
|
||||
endchoice
|
||||
|
||||
config WORD_TRANSIZE
|
||||
bool "Tansfer size as Word"
|
||||
depends on HS_SPI_S3C6400 || HS_SPI_S3C6410
|
||||
help
|
||||
Say Y here to include support for word transfer size.
|
||||
|
||||
|
||||
#
|
||||
# Add new SPI master controllers in alphabetical order above this line
|
||||
#
|
||||
|
||||
#
|
||||
# There are lots of SPI device types, with sensors and memory
|
||||
# being probably the most widely used ones.
|
||||
#
|
||||
comment "SPI Protocol Masters"
|
||||
depends on SPI_MASTER
|
||||
|
||||
config SPI_AT25
|
||||
tristate "SPI EEPROMs from most vendors"
|
||||
depends on SPI_MASTER && SYSFS
|
||||
help
|
||||
Enable this driver to get read/write support to most SPI EEPROMs,
|
||||
after you configure the board init code to know about each eeprom
|
||||
on your target board.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called at25.
|
||||
|
||||
#
|
||||
# Add new SPI protocol masters in alphabetical order above this line
|
||||
#
|
||||
|
||||
|
||||
# (slave support would go here)
|
||||
|
||||
endmenu # "SPI support"
|
||||
|
||||
61
drivers/spi/Makefile
Normal file
61
drivers/spi/Makefile
Normal file
@@ -0,0 +1,61 @@
|
||||
#
|
||||
# Makefile for kernel SPI drivers.
|
||||
#
|
||||
|
||||
ifeq ($(CONFIG_SPI_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
||||
# small core, mostly translating board-specific
|
||||
# config declarations into driver model code
|
||||
obj-$(CONFIG_SPI_MASTER) += spi.o
|
||||
|
||||
# SPI master controller drivers (bus)
|
||||
obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o
|
||||
obj-$(CONFIG_SPI_ATMEL) += atmel_spi.o
|
||||
obj-$(CONFIG_SPI_BUTTERFLY) += spi_butterfly.o
|
||||
obj-$(CONFIG_SPI_IMX) += spi_imx.o
|
||||
obj-$(CONFIG_SPI_PXA2XX) += pxa2xx_spi.o
|
||||
obj-$(CONFIG_SPI_OMAP_UWIRE) += omap_uwire.o
|
||||
obj-$(CONFIG_SPI_MPC83xx) += spi_mpc83xx.o
|
||||
# ... add above this line ...
|
||||
|
||||
# SPI protocol drivers (device/link on bus)
|
||||
obj-$(CONFIG_SPI_AT25) += at25.o
|
||||
# ... add above this line ...
|
||||
|
||||
# SPI slave controller drivers (upstream link)
|
||||
# ... add above this line ...
|
||||
|
||||
# SPI slave drivers (protocol for that link)
|
||||
# ... add above this line ...
|
||||
|
||||
ifeq ($(CONFIG_MACH_SMDK2443),y)
|
||||
obj-$(CONFIG_SPI) += spi-dev.o hspi-s3c2443.o
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_MACH_SMDK2450),y)
|
||||
obj-$(CONFIG_SPI) += spi-dev.o hspi-s3c2443.o
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_MACH_SMDK2416),y)
|
||||
#obj-$(CONFIG_SPI) += spi-dev.o hspi-s3c2443.o
|
||||
obj-$(CONFIG_HS_SPI_S3C2416) += spi-dev.o hspi-s3c2443.o
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_MACH_ASUS2416X),y)
|
||||
obj-$(CONFIG_SPI) += spi-dev.o hspi-s3c2443.o
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_MACH_SMDK6400),y)
|
||||
obj-$(CONFIG_SPI) += spi-dev.o hspi-s3c6400.o
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_MACH_SMDK6410),y)
|
||||
obj-$(CONFIG_SPI) += spi-dev.o hspi-s3c6400.o
|
||||
endif
|
||||
|
||||
# SPI master controller drivers (bus)
|
||||
obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s3c24xx_gpio.o
|
||||
obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o
|
||||
# ... add above this line ...
|
||||
381
drivers/spi/at25.c
Normal file
381
drivers/spi/at25.c
Normal file
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* at25.c -- support most SPI EEPROMs, such as Atmel AT25 models
|
||||
*
|
||||
* Copyright (C) 2006 David Brownell
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/eeprom.h>
|
||||
|
||||
|
||||
struct at25_data {
|
||||
struct spi_device *spi;
|
||||
struct mutex lock;
|
||||
struct spi_eeprom chip;
|
||||
struct bin_attribute bin;
|
||||
unsigned addrlen;
|
||||
};
|
||||
|
||||
#define AT25_WREN 0x06 /* latch the write enable */
|
||||
#define AT25_WRDI 0x04 /* reset the write enable */
|
||||
#define AT25_RDSR 0x05 /* read status register */
|
||||
#define AT25_WRSR 0x01 /* write status register */
|
||||
#define AT25_READ 0x03 /* read byte(s) */
|
||||
#define AT25_WRITE 0x02 /* write byte(s)/sector */
|
||||
|
||||
#define AT25_SR_nRDY 0x01 /* nRDY = write-in-progress */
|
||||
#define AT25_SR_WEN 0x02 /* write enable (latched) */
|
||||
#define AT25_SR_BP0 0x04 /* BP for software writeprotect */
|
||||
#define AT25_SR_BP1 0x08
|
||||
#define AT25_SR_WPEN 0x80 /* writeprotect enable */
|
||||
|
||||
|
||||
#define EE_MAXADDRLEN 3 /* 24 bit addresses, up to 2 MBytes */
|
||||
|
||||
/* Specs often allow 5 msec for a page write, sometimes 20 msec;
|
||||
* it's important to recover from write timeouts.
|
||||
*/
|
||||
#define EE_TIMEOUT 25
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define io_limit PAGE_SIZE /* bytes */
|
||||
|
||||
static ssize_t
|
||||
at25_ee_read(
|
||||
struct at25_data *at25,
|
||||
char *buf,
|
||||
unsigned offset,
|
||||
size_t count
|
||||
)
|
||||
{
|
||||
u8 command[EE_MAXADDRLEN + 1];
|
||||
u8 *cp;
|
||||
ssize_t status;
|
||||
struct spi_transfer t[2];
|
||||
struct spi_message m;
|
||||
|
||||
cp = command;
|
||||
*cp++ = AT25_READ;
|
||||
|
||||
/* 8/16/24-bit address is written MSB first */
|
||||
switch (at25->addrlen) {
|
||||
default: /* case 3 */
|
||||
*cp++ = offset >> 16;
|
||||
case 2:
|
||||
*cp++ = offset >> 8;
|
||||
case 1:
|
||||
case 0: /* can't happen: for better codegen */
|
||||
*cp++ = offset >> 0;
|
||||
}
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(t, 0, sizeof t);
|
||||
|
||||
t[0].tx_buf = command;
|
||||
t[0].len = at25->addrlen + 1;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].len = count;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
mutex_lock(&at25->lock);
|
||||
|
||||
/* Read it all at once.
|
||||
*
|
||||
* REVISIT that's potentially a problem with large chips, if
|
||||
* other devices on the bus need to be accessed regularly or
|
||||
* this chip is clocked very slowly
|
||||
*/
|
||||
status = spi_sync(at25->spi, &m);
|
||||
dev_dbg(&at25->spi->dev,
|
||||
"read %Zd bytes at %d --> %d\n",
|
||||
count, offset, (int) status);
|
||||
|
||||
mutex_unlock(&at25->lock);
|
||||
return status ? status : count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
at25_bin_read(struct kobject *kobj, char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev;
|
||||
struct at25_data *at25;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
at25 = dev_get_drvdata(dev);
|
||||
|
||||
if (unlikely(off >= at25->bin.size))
|
||||
return 0;
|
||||
if ((off + count) > at25->bin.size)
|
||||
count = at25->bin.size - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
return at25_ee_read(at25, buf, off, count);
|
||||
}
|
||||
|
||||
|
||||
static ssize_t
|
||||
at25_ee_write(struct at25_data *at25, char *buf, loff_t off, size_t count)
|
||||
{
|
||||
ssize_t status = 0;
|
||||
unsigned written = 0;
|
||||
unsigned buf_size;
|
||||
u8 *bounce;
|
||||
|
||||
/* Temp buffer starts with command and address */
|
||||
buf_size = at25->chip.page_size;
|
||||
if (buf_size > io_limit)
|
||||
buf_size = io_limit;
|
||||
bounce = kmalloc(buf_size + at25->addrlen + 1, GFP_KERNEL);
|
||||
if (!bounce)
|
||||
return -ENOMEM;
|
||||
|
||||
/* For write, rollover is within the page ... so we write at
|
||||
* most one page, then manually roll over to the next page.
|
||||
*/
|
||||
bounce[0] = AT25_WRITE;
|
||||
mutex_lock(&at25->lock);
|
||||
do {
|
||||
unsigned long timeout, retries;
|
||||
unsigned segment;
|
||||
unsigned offset = (unsigned) off;
|
||||
u8 *cp = bounce + 1;
|
||||
|
||||
*cp = AT25_WREN;
|
||||
status = spi_write(at25->spi, cp, 1);
|
||||
if (status < 0) {
|
||||
dev_dbg(&at25->spi->dev, "WREN --> %d\n",
|
||||
(int) status);
|
||||
break;
|
||||
}
|
||||
|
||||
/* 8/16/24-bit address is written MSB first */
|
||||
switch (at25->addrlen) {
|
||||
default: /* case 3 */
|
||||
*cp++ = offset >> 16;
|
||||
case 2:
|
||||
*cp++ = offset >> 8;
|
||||
case 1:
|
||||
case 0: /* can't happen: for better codegen */
|
||||
*cp++ = offset >> 0;
|
||||
}
|
||||
|
||||
/* Write as much of a page as we can */
|
||||
segment = buf_size - (offset % buf_size);
|
||||
if (segment > count)
|
||||
segment = count;
|
||||
memcpy(cp, buf, segment);
|
||||
status = spi_write(at25->spi, bounce,
|
||||
segment + at25->addrlen + 1);
|
||||
dev_dbg(&at25->spi->dev,
|
||||
"write %u bytes at %u --> %d\n",
|
||||
segment, offset, (int) status);
|
||||
if (status < 0)
|
||||
break;
|
||||
|
||||
/* REVISIT this should detect (or prevent) failed writes
|
||||
* to readonly sections of the EEPROM...
|
||||
*/
|
||||
|
||||
/* Wait for non-busy status */
|
||||
timeout = jiffies + msecs_to_jiffies(EE_TIMEOUT);
|
||||
retries = 0;
|
||||
do {
|
||||
int sr;
|
||||
|
||||
sr = spi_w8r8(at25->spi, AT25_RDSR);
|
||||
if (sr < 0 || (sr & AT25_SR_nRDY)) {
|
||||
dev_dbg(&at25->spi->dev,
|
||||
"rdsr --> %d (%02x)\n", sr, sr);
|
||||
/* at HZ=100, this is sloooow */
|
||||
msleep(1);
|
||||
continue;
|
||||
}
|
||||
if (!(sr & AT25_SR_nRDY))
|
||||
break;
|
||||
} while (retries++ < 3 || time_before_eq(jiffies, timeout));
|
||||
|
||||
if (time_after(jiffies, timeout)) {
|
||||
dev_err(&at25->spi->dev,
|
||||
"write %d bytes offset %d, "
|
||||
"timeout after %u msecs\n",
|
||||
segment, offset,
|
||||
jiffies_to_msecs(jiffies -
|
||||
(timeout - EE_TIMEOUT)));
|
||||
status = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
|
||||
off += segment;
|
||||
buf += segment;
|
||||
count -= segment;
|
||||
written += segment;
|
||||
|
||||
} while (count > 0);
|
||||
|
||||
mutex_unlock(&at25->lock);
|
||||
|
||||
kfree(bounce);
|
||||
return written ? written : status;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
at25_bin_write(struct kobject *kobj, char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev;
|
||||
struct at25_data *at25;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
at25 = dev_get_drvdata(dev);
|
||||
|
||||
if (unlikely(off >= at25->bin.size))
|
||||
return -EFBIG;
|
||||
if ((off + count) > at25->bin.size)
|
||||
count = at25->bin.size - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
return at25_ee_write(at25, buf, off, count);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int at25_probe(struct spi_device *spi)
|
||||
{
|
||||
struct at25_data *at25 = NULL;
|
||||
const struct spi_eeprom *chip;
|
||||
int err;
|
||||
int sr;
|
||||
int addrlen;
|
||||
|
||||
/* Chip description */
|
||||
chip = spi->dev.platform_data;
|
||||
if (!chip) {
|
||||
dev_dbg(&spi->dev, "no chip description\n");
|
||||
err = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* For now we only support 8/16/24 bit addressing */
|
||||
if (chip->flags & EE_ADDR1)
|
||||
addrlen = 1;
|
||||
else if (chip->flags & EE_ADDR2)
|
||||
addrlen = 2;
|
||||
else if (chip->flags & EE_ADDR3)
|
||||
addrlen = 3;
|
||||
else {
|
||||
dev_dbg(&spi->dev, "unsupported address type\n");
|
||||
err = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Ping the chip ... the status register is pretty portable,
|
||||
* unlike probing manufacturer IDs. We do expect that system
|
||||
* firmware didn't write it in the past few milliseconds!
|
||||
*/
|
||||
sr = spi_w8r8(spi, AT25_RDSR);
|
||||
if (sr < 0 || sr & AT25_SR_nRDY) {
|
||||
dev_dbg(&spi->dev, "rdsr --> %d (%02x)\n", sr, sr);
|
||||
err = -ENXIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(at25 = kzalloc(sizeof *at25, GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mutex_init(&at25->lock);
|
||||
at25->chip = *chip;
|
||||
at25->spi = spi_dev_get(spi);
|
||||
dev_set_drvdata(&spi->dev, at25);
|
||||
at25->addrlen = addrlen;
|
||||
|
||||
/* Export the EEPROM bytes through sysfs, since that's convenient.
|
||||
* Default to root-only access to the data; EEPROMs often hold data
|
||||
* that's sensitive for read and/or write, like ethernet addresses,
|
||||
* security codes, board-specific manufacturing calibrations, etc.
|
||||
*/
|
||||
at25->bin.attr.name = "eeprom";
|
||||
at25->bin.attr.mode = S_IRUSR;
|
||||
at25->bin.attr.owner = THIS_MODULE;
|
||||
at25->bin.read = at25_bin_read;
|
||||
|
||||
at25->bin.size = at25->chip.byte_len;
|
||||
if (!(chip->flags & EE_READONLY)) {
|
||||
at25->bin.write = at25_bin_write;
|
||||
at25->bin.attr.mode |= S_IWUSR;
|
||||
}
|
||||
|
||||
err = sysfs_create_bin_file(&spi->dev.kobj, &at25->bin);
|
||||
if (err)
|
||||
goto fail;
|
||||
|
||||
dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n",
|
||||
(at25->bin.size < 1024)
|
||||
? at25->bin.size
|
||||
: (at25->bin.size / 1024),
|
||||
(at25->bin.size < 1024) ? "Byte" : "KByte",
|
||||
at25->chip.name,
|
||||
(chip->flags & EE_READONLY) ? " (readonly)" : "",
|
||||
at25->chip.page_size);
|
||||
return 0;
|
||||
fail:
|
||||
dev_dbg(&spi->dev, "probe err %d\n", err);
|
||||
kfree(at25);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit at25_remove(struct spi_device *spi)
|
||||
{
|
||||
struct at25_data *at25;
|
||||
|
||||
at25 = dev_get_drvdata(&spi->dev);
|
||||
sysfs_remove_bin_file(&spi->dev.kobj, &at25->bin);
|
||||
kfree(at25);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static struct spi_driver at25_driver = {
|
||||
.driver = {
|
||||
.name = "at25",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = at25_probe,
|
||||
.remove = __devexit_p(at25_remove),
|
||||
};
|
||||
|
||||
static int __init at25_init(void)
|
||||
{
|
||||
return spi_register_driver(&at25_driver);
|
||||
}
|
||||
module_init(at25_init);
|
||||
|
||||
static void __exit at25_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&at25_driver);
|
||||
}
|
||||
module_exit(at25_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Driver for most SPI EEPROMs");
|
||||
MODULE_AUTHOR("David Brownell");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
682
drivers/spi/atmel_spi.c
Normal file
682
drivers/spi/atmel_spi.c
Normal file
@@ -0,0 +1,682 @@
|
||||
/*
|
||||
* Driver for Atmel AT32 and AT91 SPI Controllers
|
||||
*
|
||||
* 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 version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/board.h>
|
||||
#include <asm/arch/gpio.h>
|
||||
|
||||
#ifdef CONFIG_ARCH_AT91
|
||||
#include <asm/arch/cpu.h>
|
||||
#endif
|
||||
|
||||
#include "atmel_spi.h"
|
||||
|
||||
/*
|
||||
* The core SPI transfer engine just talks to a register bank to set up
|
||||
* DMA transfers; transfer queue progress is driven by IRQs. The clock
|
||||
* framework provides the base clock, subdivided for each spi_device.
|
||||
*
|
||||
* Newer controllers, marked with "new_1" flag, have:
|
||||
* - CR.LASTXFER
|
||||
* - SPI_MR.DIV32 may become FDIV or must-be-zero (here: always zero)
|
||||
* - SPI_SR.TXEMPTY, SPI_SR.NSSR (and corresponding irqs)
|
||||
* - SPI_CSRx.CSAAT
|
||||
* - SPI_CSRx.SBCR allows faster clocking
|
||||
*/
|
||||
struct atmel_spi {
|
||||
spinlock_t lock;
|
||||
|
||||
void __iomem *regs;
|
||||
int irq;
|
||||
struct clk *clk;
|
||||
struct platform_device *pdev;
|
||||
unsigned new_1:1;
|
||||
|
||||
u8 stopping;
|
||||
struct list_head queue;
|
||||
struct spi_transfer *current_transfer;
|
||||
unsigned long remaining_bytes;
|
||||
|
||||
void *buffer;
|
||||
dma_addr_t buffer_dma;
|
||||
};
|
||||
|
||||
#define BUFFER_SIZE PAGE_SIZE
|
||||
#define INVALID_DMA_ADDRESS 0xffffffff
|
||||
|
||||
/*
|
||||
* Earlier SPI controllers (e.g. on at91rm9200) have a design bug whereby
|
||||
* they assume that spi slave device state will not change on deselect, so
|
||||
* that automagic deselection is OK. Not so! Workaround uses nCSx pins
|
||||
* as GPIOs; or newer controllers have CSAAT and friends.
|
||||
*
|
||||
* Since the CSAAT functionality is a bit weird on newer controllers
|
||||
* as well, we use GPIO to control nCSx pins on all controllers.
|
||||
*/
|
||||
|
||||
static inline void cs_activate(struct spi_device *spi)
|
||||
{
|
||||
unsigned gpio = (unsigned) spi->controller_data;
|
||||
unsigned active = spi->mode & SPI_CS_HIGH;
|
||||
|
||||
dev_dbg(&spi->dev, "activate %u%s\n", gpio, active ? " (high)" : "");
|
||||
gpio_set_value(gpio, active);
|
||||
}
|
||||
|
||||
static inline void cs_deactivate(struct spi_device *spi)
|
||||
{
|
||||
unsigned gpio = (unsigned) spi->controller_data;
|
||||
unsigned active = spi->mode & SPI_CS_HIGH;
|
||||
|
||||
dev_dbg(&spi->dev, "DEactivate %u%s\n", gpio, active ? " (low)" : "");
|
||||
gpio_set_value(gpio, !active);
|
||||
}
|
||||
|
||||
/*
|
||||
* Submit next transfer for DMA.
|
||||
* lock is held, spi irq is blocked
|
||||
*/
|
||||
static void atmel_spi_next_xfer(struct spi_master *master,
|
||||
struct spi_message *msg)
|
||||
{
|
||||
struct atmel_spi *as = spi_master_get_devdata(master);
|
||||
struct spi_transfer *xfer;
|
||||
u32 len;
|
||||
dma_addr_t tx_dma, rx_dma;
|
||||
|
||||
xfer = as->current_transfer;
|
||||
if (!xfer || as->remaining_bytes == 0) {
|
||||
if (xfer)
|
||||
xfer = list_entry(xfer->transfer_list.next,
|
||||
struct spi_transfer, transfer_list);
|
||||
else
|
||||
xfer = list_entry(msg->transfers.next,
|
||||
struct spi_transfer, transfer_list);
|
||||
as->remaining_bytes = xfer->len;
|
||||
as->current_transfer = xfer;
|
||||
}
|
||||
|
||||
len = as->remaining_bytes;
|
||||
|
||||
tx_dma = xfer->tx_dma;
|
||||
rx_dma = xfer->rx_dma;
|
||||
|
||||
/* use scratch buffer only when rx or tx data is unspecified */
|
||||
if (rx_dma == INVALID_DMA_ADDRESS) {
|
||||
rx_dma = as->buffer_dma;
|
||||
if (len > BUFFER_SIZE)
|
||||
len = BUFFER_SIZE;
|
||||
}
|
||||
if (tx_dma == INVALID_DMA_ADDRESS) {
|
||||
tx_dma = as->buffer_dma;
|
||||
if (len > BUFFER_SIZE)
|
||||
len = BUFFER_SIZE;
|
||||
memset(as->buffer, 0, len);
|
||||
dma_sync_single_for_device(&as->pdev->dev,
|
||||
as->buffer_dma, len, DMA_TO_DEVICE);
|
||||
}
|
||||
|
||||
spi_writel(as, RPR, rx_dma);
|
||||
spi_writel(as, TPR, tx_dma);
|
||||
|
||||
as->remaining_bytes -= len;
|
||||
if (msg->spi->bits_per_word > 8)
|
||||
len >>= 1;
|
||||
|
||||
/* REVISIT: when xfer->delay_usecs == 0, the PDC "next transfer"
|
||||
* mechanism might help avoid the IRQ latency between transfers
|
||||
*
|
||||
* We're also waiting for ENDRX before we start the next
|
||||
* transfer because we need to handle some difficult timing
|
||||
* issues otherwise. If we wait for ENDTX in one transfer and
|
||||
* then starts waiting for ENDRX in the next, it's difficult
|
||||
* to tell the difference between the ENDRX interrupt we're
|
||||
* actually waiting for and the ENDRX interrupt of the
|
||||
* previous transfer.
|
||||
*
|
||||
* It should be doable, though. Just not now...
|
||||
*/
|
||||
spi_writel(as, TNCR, 0);
|
||||
spi_writel(as, RNCR, 0);
|
||||
spi_writel(as, IER, SPI_BIT(ENDRX) | SPI_BIT(OVRES));
|
||||
|
||||
dev_dbg(&msg->spi->dev,
|
||||
" start xfer %p: len %u tx %p/%08x rx %p/%08x imr %03x\n",
|
||||
xfer, xfer->len, xfer->tx_buf, xfer->tx_dma,
|
||||
xfer->rx_buf, xfer->rx_dma, spi_readl(as, IMR));
|
||||
|
||||
spi_writel(as, TCR, len);
|
||||
spi_writel(as, RCR, len);
|
||||
spi_writel(as, PTCR, SPI_BIT(TXTEN) | SPI_BIT(RXTEN));
|
||||
}
|
||||
|
||||
static void atmel_spi_next_message(struct spi_master *master)
|
||||
{
|
||||
struct atmel_spi *as = spi_master_get_devdata(master);
|
||||
struct spi_message *msg;
|
||||
u32 mr;
|
||||
|
||||
BUG_ON(as->current_transfer);
|
||||
|
||||
msg = list_entry(as->queue.next, struct spi_message, queue);
|
||||
|
||||
/* Select the chip */
|
||||
mr = spi_readl(as, MR);
|
||||
mr = SPI_BFINS(PCS, ~(1 << msg->spi->chip_select), mr);
|
||||
spi_writel(as, MR, mr);
|
||||
cs_activate(msg->spi);
|
||||
|
||||
atmel_spi_next_xfer(master, msg);
|
||||
}
|
||||
|
||||
static void
|
||||
atmel_spi_dma_map_xfer(struct atmel_spi *as, struct spi_transfer *xfer)
|
||||
{
|
||||
xfer->tx_dma = xfer->rx_dma = INVALID_DMA_ADDRESS;
|
||||
if (xfer->tx_buf)
|
||||
xfer->tx_dma = dma_map_single(&as->pdev->dev,
|
||||
(void *) xfer->tx_buf, xfer->len,
|
||||
DMA_TO_DEVICE);
|
||||
if (xfer->rx_buf)
|
||||
xfer->rx_dma = dma_map_single(&as->pdev->dev,
|
||||
xfer->rx_buf, xfer->len,
|
||||
DMA_FROM_DEVICE);
|
||||
}
|
||||
|
||||
static void atmel_spi_dma_unmap_xfer(struct spi_master *master,
|
||||
struct spi_transfer *xfer)
|
||||
{
|
||||
if (xfer->tx_dma != INVALID_DMA_ADDRESS)
|
||||
dma_unmap_single(master->cdev.dev, xfer->tx_dma,
|
||||
xfer->len, DMA_TO_DEVICE);
|
||||
if (xfer->rx_dma != INVALID_DMA_ADDRESS)
|
||||
dma_unmap_single(master->cdev.dev, xfer->rx_dma,
|
||||
xfer->len, DMA_FROM_DEVICE);
|
||||
}
|
||||
|
||||
static void
|
||||
atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as,
|
||||
struct spi_message *msg, int status)
|
||||
{
|
||||
cs_deactivate(msg->spi);
|
||||
list_del(&msg->queue);
|
||||
msg->status = status;
|
||||
|
||||
dev_dbg(master->cdev.dev,
|
||||
"xfer complete: %u bytes transferred\n",
|
||||
msg->actual_length);
|
||||
|
||||
spin_unlock(&as->lock);
|
||||
msg->complete(msg->context);
|
||||
spin_lock(&as->lock);
|
||||
|
||||
as->current_transfer = NULL;
|
||||
|
||||
/* continue if needed */
|
||||
if (list_empty(&as->queue) || as->stopping)
|
||||
spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
|
||||
else
|
||||
atmel_spi_next_message(master);
|
||||
}
|
||||
|
||||
static irqreturn_t
|
||||
atmel_spi_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct spi_master *master = dev_id;
|
||||
struct atmel_spi *as = spi_master_get_devdata(master);
|
||||
struct spi_message *msg;
|
||||
struct spi_transfer *xfer;
|
||||
u32 status, pending, imr;
|
||||
int ret = IRQ_NONE;
|
||||
|
||||
spin_lock(&as->lock);
|
||||
|
||||
xfer = as->current_transfer;
|
||||
msg = list_entry(as->queue.next, struct spi_message, queue);
|
||||
|
||||
imr = spi_readl(as, IMR);
|
||||
status = spi_readl(as, SR);
|
||||
pending = status & imr;
|
||||
|
||||
if (pending & SPI_BIT(OVRES)) {
|
||||
int timeout;
|
||||
|
||||
ret = IRQ_HANDLED;
|
||||
|
||||
spi_writel(as, IDR, (SPI_BIT(ENDTX) | SPI_BIT(ENDRX)
|
||||
| SPI_BIT(OVRES)));
|
||||
|
||||
/*
|
||||
* When we get an overrun, we disregard the current
|
||||
* transfer. Data will not be copied back from any
|
||||
* bounce buffer and msg->actual_len will not be
|
||||
* updated with the last xfer.
|
||||
*
|
||||
* We will also not process any remaning transfers in
|
||||
* the message.
|
||||
*
|
||||
* First, stop the transfer and unmap the DMA buffers.
|
||||
*/
|
||||
spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
|
||||
if (!msg->is_dma_mapped)
|
||||
atmel_spi_dma_unmap_xfer(master, xfer);
|
||||
|
||||
/* REVISIT: udelay in irq is unfriendly */
|
||||
if (xfer->delay_usecs)
|
||||
udelay(xfer->delay_usecs);
|
||||
|
||||
dev_warn(master->cdev.dev, "fifo overrun (%u/%u remaining)\n",
|
||||
spi_readl(as, TCR), spi_readl(as, RCR));
|
||||
|
||||
/*
|
||||
* Clean up DMA registers and make sure the data
|
||||
* registers are empty.
|
||||
*/
|
||||
spi_writel(as, RNCR, 0);
|
||||
spi_writel(as, TNCR, 0);
|
||||
spi_writel(as, RCR, 0);
|
||||
spi_writel(as, TCR, 0);
|
||||
for (timeout = 1000; timeout; timeout--)
|
||||
if (spi_readl(as, SR) & SPI_BIT(TXEMPTY))
|
||||
break;
|
||||
if (!timeout)
|
||||
dev_warn(master->cdev.dev,
|
||||
"timeout waiting for TXEMPTY");
|
||||
while (spi_readl(as, SR) & SPI_BIT(RDRF))
|
||||
spi_readl(as, RDR);
|
||||
|
||||
/* Clear any overrun happening while cleaning up */
|
||||
spi_readl(as, SR);
|
||||
|
||||
atmel_spi_msg_done(master, as, msg, -EIO);
|
||||
} else if (pending & SPI_BIT(ENDRX)) {
|
||||
ret = IRQ_HANDLED;
|
||||
|
||||
spi_writel(as, IDR, pending);
|
||||
|
||||
if (as->remaining_bytes == 0) {
|
||||
msg->actual_length += xfer->len;
|
||||
|
||||
if (!msg->is_dma_mapped)
|
||||
atmel_spi_dma_unmap_xfer(master, xfer);
|
||||
|
||||
/* REVISIT: udelay in irq is unfriendly */
|
||||
if (xfer->delay_usecs)
|
||||
udelay(xfer->delay_usecs);
|
||||
|
||||
if (msg->transfers.prev == &xfer->transfer_list) {
|
||||
/* report completed message */
|
||||
atmel_spi_msg_done(master, as, msg, 0);
|
||||
} else {
|
||||
if (xfer->cs_change) {
|
||||
cs_deactivate(msg->spi);
|
||||
udelay(1);
|
||||
cs_activate(msg->spi);
|
||||
}
|
||||
|
||||
/*
|
||||
* Not done yet. Submit the next transfer.
|
||||
*
|
||||
* FIXME handle protocol options for xfer
|
||||
*/
|
||||
atmel_spi_next_xfer(master, msg);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Keep going, we still have data to send in
|
||||
* the current transfer.
|
||||
*/
|
||||
atmel_spi_next_xfer(master, msg);
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock(&as->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)
|
||||
|
||||
static int atmel_spi_setup(struct spi_device *spi)
|
||||
{
|
||||
struct atmel_spi *as;
|
||||
u32 scbr, csr;
|
||||
unsigned int bits = spi->bits_per_word;
|
||||
unsigned long bus_hz, sck_hz;
|
||||
unsigned int npcs_pin;
|
||||
int ret;
|
||||
|
||||
as = spi_master_get_devdata(spi->master);
|
||||
|
||||
if (as->stopping)
|
||||
return -ESHUTDOWN;
|
||||
|
||||
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 (bits == 0)
|
||||
bits = 8;
|
||||
if (bits < 8 || bits > 16) {
|
||||
dev_dbg(&spi->dev,
|
||||
"setup: invalid bits_per_word %u (8 to 16)\n",
|
||||
bits);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (spi->mode & ~MODEBITS) {
|
||||
dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n",
|
||||
spi->mode & ~MODEBITS);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* speed zero convention is used by some upper layers */
|
||||
bus_hz = clk_get_rate(as->clk);
|
||||
if (spi->max_speed_hz) {
|
||||
/* assume div32/fdiv/mbz == 0 */
|
||||
if (!as->new_1)
|
||||
bus_hz /= 2;
|
||||
scbr = ((bus_hz + spi->max_speed_hz - 1)
|
||||
/ spi->max_speed_hz);
|
||||
if (scbr >= (1 << SPI_SCBR_SIZE)) {
|
||||
dev_dbg(&spi->dev, "setup: %d Hz too slow, scbr %u\n",
|
||||
spi->max_speed_hz, scbr);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else
|
||||
scbr = 0xff;
|
||||
sck_hz = bus_hz / scbr;
|
||||
|
||||
csr = SPI_BF(SCBR, scbr) | SPI_BF(BITS, bits - 8);
|
||||
if (spi->mode & SPI_CPOL)
|
||||
csr |= SPI_BIT(CPOL);
|
||||
if (!(spi->mode & SPI_CPHA))
|
||||
csr |= SPI_BIT(NCPHA);
|
||||
|
||||
/* TODO: DLYBS and DLYBCT */
|
||||
csr |= SPI_BF(DLYBS, 10);
|
||||
csr |= SPI_BF(DLYBCT, 10);
|
||||
|
||||
/* chipselect must have been muxed as GPIO (e.g. in board setup) */
|
||||
npcs_pin = (unsigned int)spi->controller_data;
|
||||
if (!spi->controller_state) {
|
||||
ret = gpio_request(npcs_pin, "spi_npcs");
|
||||
if (ret)
|
||||
return ret;
|
||||
spi->controller_state = (void *)npcs_pin;
|
||||
gpio_direction_output(npcs_pin, !(spi->mode & SPI_CS_HIGH));
|
||||
}
|
||||
|
||||
dev_dbg(&spi->dev,
|
||||
"setup: %lu Hz bpw %u mode 0x%x -> csr%d %08x\n",
|
||||
sck_hz, bits, spi->mode, spi->chip_select, csr);
|
||||
|
||||
spi_writel(as, CSR0 + 4 * spi->chip_select, csr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)
|
||||
{
|
||||
struct atmel_spi *as;
|
||||
struct spi_transfer *xfer;
|
||||
unsigned long flags;
|
||||
struct device *controller = spi->master->cdev.dev;
|
||||
|
||||
as = spi_master_get_devdata(spi->master);
|
||||
|
||||
dev_dbg(controller, "new message %p submitted for %s\n",
|
||||
msg, spi->dev.bus_id);
|
||||
|
||||
if (unlikely(list_empty(&msg->transfers)
|
||||
|| !spi->max_speed_hz))
|
||||
return -EINVAL;
|
||||
|
||||
if (as->stopping)
|
||||
return -ESHUTDOWN;
|
||||
|
||||
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");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* FIXME implement these protocol options!! */
|
||||
if (xfer->bits_per_word || xfer->speed_hz) {
|
||||
dev_dbg(&spi->dev, "no protocol options yet\n");
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
}
|
||||
|
||||
/* scrub dcache "early" */
|
||||
if (!msg->is_dma_mapped) {
|
||||
list_for_each_entry(xfer, &msg->transfers, transfer_list)
|
||||
atmel_spi_dma_map_xfer(as, xfer);
|
||||
}
|
||||
|
||||
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
|
||||
dev_dbg(controller,
|
||||
" xfer %p: len %u tx %p/%08x rx %p/%08x\n",
|
||||
xfer, xfer->len,
|
||||
xfer->tx_buf, xfer->tx_dma,
|
||||
xfer->rx_buf, xfer->rx_dma);
|
||||
}
|
||||
|
||||
msg->status = -EINPROGRESS;
|
||||
msg->actual_length = 0;
|
||||
|
||||
spin_lock_irqsave(&as->lock, flags);
|
||||
list_add_tail(&msg->queue, &as->queue);
|
||||
if (!as->current_transfer)
|
||||
atmel_spi_next_message(spi->master);
|
||||
spin_unlock_irqrestore(&as->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void atmel_spi_cleanup(struct spi_device *spi)
|
||||
{
|
||||
if (spi->controller_state)
|
||||
gpio_free((unsigned int)spi->controller_data);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __init atmel_spi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *regs;
|
||||
int irq;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
struct spi_master *master;
|
||||
struct atmel_spi *as;
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!regs)
|
||||
return -ENXIO;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
clk = clk_get(&pdev->dev, "spi_clk");
|
||||
if (IS_ERR(clk))
|
||||
return PTR_ERR(clk);
|
||||
|
||||
/* setup spi core then atmel-specific driver state */
|
||||
ret = -ENOMEM;
|
||||
master = spi_alloc_master(&pdev->dev, sizeof *as);
|
||||
if (!master)
|
||||
goto out_free;
|
||||
|
||||
master->bus_num = pdev->id;
|
||||
master->num_chipselect = 4;
|
||||
master->setup = atmel_spi_setup;
|
||||
master->transfer = atmel_spi_transfer;
|
||||
master->cleanup = atmel_spi_cleanup;
|
||||
platform_set_drvdata(pdev, master);
|
||||
|
||||
as = spi_master_get_devdata(master);
|
||||
|
||||
as->buffer = dma_alloc_coherent(&pdev->dev, BUFFER_SIZE,
|
||||
&as->buffer_dma, GFP_KERNEL);
|
||||
if (!as->buffer)
|
||||
goto out_free;
|
||||
|
||||
spin_lock_init(&as->lock);
|
||||
INIT_LIST_HEAD(&as->queue);
|
||||
as->pdev = pdev;
|
||||
as->regs = ioremap(regs->start, (regs->end - regs->start) + 1);
|
||||
if (!as->regs)
|
||||
goto out_free_buffer;
|
||||
as->irq = irq;
|
||||
as->clk = clk;
|
||||
#ifdef CONFIG_ARCH_AT91
|
||||
if (!cpu_is_at91rm9200())
|
||||
as->new_1 = 1;
|
||||
#endif
|
||||
|
||||
ret = request_irq(irq, atmel_spi_interrupt, 0,
|
||||
pdev->dev.bus_id, master);
|
||||
if (ret)
|
||||
goto out_unmap_regs;
|
||||
|
||||
/* Initialize the hardware */
|
||||
clk_enable(clk);
|
||||
spi_writel(as, CR, SPI_BIT(SWRST));
|
||||
spi_writel(as, MR, SPI_BIT(MSTR) | SPI_BIT(MODFDIS));
|
||||
spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
|
||||
spi_writel(as, CR, SPI_BIT(SPIEN));
|
||||
|
||||
/* go! */
|
||||
dev_info(&pdev->dev, "Atmel SPI Controller at 0x%08lx (irq %d)\n",
|
||||
(unsigned long)regs->start, irq);
|
||||
|
||||
ret = spi_register_master(master);
|
||||
if (ret)
|
||||
goto out_reset_hw;
|
||||
|
||||
return 0;
|
||||
|
||||
out_reset_hw:
|
||||
spi_writel(as, CR, SPI_BIT(SWRST));
|
||||
clk_disable(clk);
|
||||
free_irq(irq, master);
|
||||
out_unmap_regs:
|
||||
iounmap(as->regs);
|
||||
out_free_buffer:
|
||||
dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer,
|
||||
as->buffer_dma);
|
||||
out_free:
|
||||
clk_put(clk);
|
||||
spi_master_put(master);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __exit atmel_spi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_master *master = platform_get_drvdata(pdev);
|
||||
struct atmel_spi *as = spi_master_get_devdata(master);
|
||||
struct spi_message *msg;
|
||||
|
||||
/* reset the hardware and block queue progress */
|
||||
spin_lock_irq(&as->lock);
|
||||
as->stopping = 1;
|
||||
spi_writel(as, CR, SPI_BIT(SWRST));
|
||||
spi_readl(as, SR);
|
||||
spin_unlock_irq(&as->lock);
|
||||
|
||||
/* Terminate remaining queued transfers */
|
||||
list_for_each_entry(msg, &as->queue, queue) {
|
||||
/* REVISIT unmapping the dma is a NOP on ARM and AVR32
|
||||
* but we shouldn't depend on that...
|
||||
*/
|
||||
msg->status = -ESHUTDOWN;
|
||||
msg->complete(msg->context);
|
||||
}
|
||||
|
||||
dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer,
|
||||
as->buffer_dma);
|
||||
|
||||
clk_disable(as->clk);
|
||||
clk_put(as->clk);
|
||||
free_irq(as->irq, master);
|
||||
iounmap(as->regs);
|
||||
|
||||
spi_unregister_master(master);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int atmel_spi_suspend(struct platform_device *pdev, pm_message_t mesg)
|
||||
{
|
||||
struct spi_master *master = platform_get_drvdata(pdev);
|
||||
struct atmel_spi *as = spi_master_get_devdata(master);
|
||||
|
||||
clk_disable(as->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_spi_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_master *master = platform_get_drvdata(pdev);
|
||||
struct atmel_spi *as = spi_master_get_devdata(master);
|
||||
|
||||
clk_enable(as->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define atmel_spi_suspend NULL
|
||||
#define atmel_spi_resume NULL
|
||||
#endif
|
||||
|
||||
|
||||
static struct platform_driver atmel_spi_driver = {
|
||||
.driver = {
|
||||
.name = "atmel_spi",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.suspend = atmel_spi_suspend,
|
||||
.resume = atmel_spi_resume,
|
||||
.remove = __exit_p(atmel_spi_remove),
|
||||
};
|
||||
|
||||
static int __init atmel_spi_init(void)
|
||||
{
|
||||
return platform_driver_probe(&atmel_spi_driver, atmel_spi_probe);
|
||||
}
|
||||
module_init(atmel_spi_init);
|
||||
|
||||
static void __exit atmel_spi_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&atmel_spi_driver);
|
||||
}
|
||||
module_exit(atmel_spi_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Atmel AT32/AT91 SPI Controller driver");
|
||||
MODULE_AUTHOR("Haavard Skinnemoen <hskinnemoen@atmel.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
167
drivers/spi/atmel_spi.h
Normal file
167
drivers/spi/atmel_spi.h
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Register definitions for Atmel Serial Peripheral Interface (SPI)
|
||||
*
|
||||
* 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 version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef __ATMEL_SPI_H__
|
||||
#define __ATMEL_SPI_H__
|
||||
|
||||
/* SPI register offsets */
|
||||
#define SPI_CR 0x0000
|
||||
#define SPI_MR 0x0004
|
||||
#define SPI_RDR 0x0008
|
||||
#define SPI_TDR 0x000c
|
||||
#define SPI_SR 0x0010
|
||||
#define SPI_IER 0x0014
|
||||
#define SPI_IDR 0x0018
|
||||
#define SPI_IMR 0x001c
|
||||
#define SPI_CSR0 0x0030
|
||||
#define SPI_CSR1 0x0034
|
||||
#define SPI_CSR2 0x0038
|
||||
#define SPI_CSR3 0x003c
|
||||
#define SPI_RPR 0x0100
|
||||
#define SPI_RCR 0x0104
|
||||
#define SPI_TPR 0x0108
|
||||
#define SPI_TCR 0x010c
|
||||
#define SPI_RNPR 0x0110
|
||||
#define SPI_RNCR 0x0114
|
||||
#define SPI_TNPR 0x0118
|
||||
#define SPI_TNCR 0x011c
|
||||
#define SPI_PTCR 0x0120
|
||||
#define SPI_PTSR 0x0124
|
||||
|
||||
/* Bitfields in CR */
|
||||
#define SPI_SPIEN_OFFSET 0
|
||||
#define SPI_SPIEN_SIZE 1
|
||||
#define SPI_SPIDIS_OFFSET 1
|
||||
#define SPI_SPIDIS_SIZE 1
|
||||
#define SPI_SWRST_OFFSET 7
|
||||
#define SPI_SWRST_SIZE 1
|
||||
#define SPI_LASTXFER_OFFSET 24
|
||||
#define SPI_LASTXFER_SIZE 1
|
||||
|
||||
/* Bitfields in MR */
|
||||
#define SPI_MSTR_OFFSET 0
|
||||
#define SPI_MSTR_SIZE 1
|
||||
#define SPI_PS_OFFSET 1
|
||||
#define SPI_PS_SIZE 1
|
||||
#define SPI_PCSDEC_OFFSET 2
|
||||
#define SPI_PCSDEC_SIZE 1
|
||||
#define SPI_FDIV_OFFSET 3
|
||||
#define SPI_FDIV_SIZE 1
|
||||
#define SPI_MODFDIS_OFFSET 4
|
||||
#define SPI_MODFDIS_SIZE 1
|
||||
#define SPI_LLB_OFFSET 7
|
||||
#define SPI_LLB_SIZE 1
|
||||
#define SPI_PCS_OFFSET 16
|
||||
#define SPI_PCS_SIZE 4
|
||||
#define SPI_DLYBCS_OFFSET 24
|
||||
#define SPI_DLYBCS_SIZE 8
|
||||
|
||||
/* Bitfields in RDR */
|
||||
#define SPI_RD_OFFSET 0
|
||||
#define SPI_RD_SIZE 16
|
||||
|
||||
/* Bitfields in TDR */
|
||||
#define SPI_TD_OFFSET 0
|
||||
#define SPI_TD_SIZE 16
|
||||
|
||||
/* Bitfields in SR */
|
||||
#define SPI_RDRF_OFFSET 0
|
||||
#define SPI_RDRF_SIZE 1
|
||||
#define SPI_TDRE_OFFSET 1
|
||||
#define SPI_TDRE_SIZE 1
|
||||
#define SPI_MODF_OFFSET 2
|
||||
#define SPI_MODF_SIZE 1
|
||||
#define SPI_OVRES_OFFSET 3
|
||||
#define SPI_OVRES_SIZE 1
|
||||
#define SPI_ENDRX_OFFSET 4
|
||||
#define SPI_ENDRX_SIZE 1
|
||||
#define SPI_ENDTX_OFFSET 5
|
||||
#define SPI_ENDTX_SIZE 1
|
||||
#define SPI_RXBUFF_OFFSET 6
|
||||
#define SPI_RXBUFF_SIZE 1
|
||||
#define SPI_TXBUFE_OFFSET 7
|
||||
#define SPI_TXBUFE_SIZE 1
|
||||
#define SPI_NSSR_OFFSET 8
|
||||
#define SPI_NSSR_SIZE 1
|
||||
#define SPI_TXEMPTY_OFFSET 9
|
||||
#define SPI_TXEMPTY_SIZE 1
|
||||
#define SPI_SPIENS_OFFSET 16
|
||||
#define SPI_SPIENS_SIZE 1
|
||||
|
||||
/* Bitfields in CSR0 */
|
||||
#define SPI_CPOL_OFFSET 0
|
||||
#define SPI_CPOL_SIZE 1
|
||||
#define SPI_NCPHA_OFFSET 1
|
||||
#define SPI_NCPHA_SIZE 1
|
||||
#define SPI_CSAAT_OFFSET 3
|
||||
#define SPI_CSAAT_SIZE 1
|
||||
#define SPI_BITS_OFFSET 4
|
||||
#define SPI_BITS_SIZE 4
|
||||
#define SPI_SCBR_OFFSET 8
|
||||
#define SPI_SCBR_SIZE 8
|
||||
#define SPI_DLYBS_OFFSET 16
|
||||
#define SPI_DLYBS_SIZE 8
|
||||
#define SPI_DLYBCT_OFFSET 24
|
||||
#define SPI_DLYBCT_SIZE 8
|
||||
|
||||
/* Bitfields in RCR */
|
||||
#define SPI_RXCTR_OFFSET 0
|
||||
#define SPI_RXCTR_SIZE 16
|
||||
|
||||
/* Bitfields in TCR */
|
||||
#define SPI_TXCTR_OFFSET 0
|
||||
#define SPI_TXCTR_SIZE 16
|
||||
|
||||
/* Bitfields in RNCR */
|
||||
#define SPI_RXNCR_OFFSET 0
|
||||
#define SPI_RXNCR_SIZE 16
|
||||
|
||||
/* Bitfields in TNCR */
|
||||
#define SPI_TXNCR_OFFSET 0
|
||||
#define SPI_TXNCR_SIZE 16
|
||||
|
||||
/* Bitfields in PTCR */
|
||||
#define SPI_RXTEN_OFFSET 0
|
||||
#define SPI_RXTEN_SIZE 1
|
||||
#define SPI_RXTDIS_OFFSET 1
|
||||
#define SPI_RXTDIS_SIZE 1
|
||||
#define SPI_TXTEN_OFFSET 8
|
||||
#define SPI_TXTEN_SIZE 1
|
||||
#define SPI_TXTDIS_OFFSET 9
|
||||
#define SPI_TXTDIS_SIZE 1
|
||||
|
||||
/* Constants for BITS */
|
||||
#define SPI_BITS_8_BPT 0
|
||||
#define SPI_BITS_9_BPT 1
|
||||
#define SPI_BITS_10_BPT 2
|
||||
#define SPI_BITS_11_BPT 3
|
||||
#define SPI_BITS_12_BPT 4
|
||||
#define SPI_BITS_13_BPT 5
|
||||
#define SPI_BITS_14_BPT 6
|
||||
#define SPI_BITS_15_BPT 7
|
||||
#define SPI_BITS_16_BPT 8
|
||||
|
||||
/* Bit manipulation macros */
|
||||
#define SPI_BIT(name) \
|
||||
(1 << SPI_##name##_OFFSET)
|
||||
#define SPI_BF(name,value) \
|
||||
(((value) & ((1 << SPI_##name##_SIZE) - 1)) << SPI_##name##_OFFSET)
|
||||
#define SPI_BFEXT(name,value) \
|
||||
(((value) >> SPI_##name##_OFFSET) & ((1 << SPI_##name##_SIZE) - 1))
|
||||
#define SPI_BFINS(name,value,old) \
|
||||
( ((old) & ~(((1 << SPI_##name##_SIZE) - 1) << SPI_##name##_OFFSET)) \
|
||||
| SPI_BF(name,value))
|
||||
|
||||
/* Register access macros */
|
||||
#define spi_readl(port,reg) \
|
||||
__raw_readl((port)->regs + SPI_##reg)
|
||||
#define spi_writel(port,reg,value) \
|
||||
__raw_writel((value), (port)->regs + SPI_##reg)
|
||||
|
||||
#endif /* __ATMEL_SPI_H__ */
|
||||
1313
drivers/spi/hspi-s3c2443.c
Normal file
1313
drivers/spi/hspi-s3c2443.c
Normal file
File diff suppressed because it is too large
Load Diff
113
drivers/spi/hspi-s3c2443.h
Normal file
113
drivers/spi/hspi-s3c2443.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* */
|
||||
/* spi-s3c6400.h - definitions of s3c6400 specific spi interface */
|
||||
/* */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Copyright (C) 2006 Samsung Electronics Co. ltd.
|
||||
|
||||
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., 675 Mass Ave, Cambridge, MA 02139, USA. */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
#ifndef _S3C2443_SPI_H
|
||||
#define _S3C2443_SPI_H
|
||||
|
||||
#include <asm/dma.h>
|
||||
#include <asm/arch/dma.h>
|
||||
|
||||
#define S3C_DCON_HANDSHAKE (1<<31)
|
||||
#define S3C_DCON_SYNC_PCLK (0<<30)
|
||||
|
||||
//#define SPI_CHANNEL 1
|
||||
|
||||
#if(SPI_CHANNEL==0)
|
||||
/* SPI CHANNEL 0 */
|
||||
#define S3C_SPI_TX_DATA_REG 0x52000018 //SPI TX data
|
||||
#define S3C_SPI_RX_DATA_REG 0x5200001C //SPI RX data
|
||||
#else
|
||||
/* SPI CHANNEL 1 */
|
||||
#define S3C_SPI_TX_DATA_REG 0x59000018 //SPI TX data
|
||||
#define S3C_SPI_RX_DATA_REG 0x5900001C //SPI RX data
|
||||
#endif
|
||||
|
||||
/* DMA channel to be used for the SPI interface. */
|
||||
#define S3C_SPI_DMA 0
|
||||
|
||||
/* DMA transfer unit (byte). */
|
||||
#define S3C_DMA_XFER_BYTE 1
|
||||
#define S3C_DMA_XFER_WORD 4
|
||||
|
||||
/* DMA configuration setup byte. */
|
||||
#define S3C_DCON_SPI1 (S3C_DCON_HANDSHAKE | S3C_DCON_SYNC_PCLK)
|
||||
|
||||
/* DMA hardware configuration mode (DISRCC register). */
|
||||
#define S3C_SPI1_DMA_HWCFG 3
|
||||
#define S3C_SPI_DMA_HWCFG 3
|
||||
|
||||
#define DMA_BUFFER_SIZE 1500
|
||||
|
||||
/* spi controller state */
|
||||
int req_dma_flag = 1;
|
||||
enum s3c_spi_state {
|
||||
STATE_IDLE,
|
||||
STATE_XFER_TX,
|
||||
STATE_XFER_RX,
|
||||
STATE_STOP
|
||||
};
|
||||
|
||||
/* vivek, 2009-05-14 11:01 Notes: define clients for dma read and write channels*/
|
||||
//static struct s3c2410_dma_client s3c2443spi_dma_client = {
|
||||
// .name = "s3c2443-spi-dma",
|
||||
//};
|
||||
static struct s3c2410_dma_client s3c2443spi_dma_clientw = {
|
||||
.name = "s3c2443-spi-dmaw",
|
||||
};
|
||||
static struct s3c2410_dma_client s3c2443spi_dma_clientr = {
|
||||
.name = "s3c2443-spi-dmar",
|
||||
};
|
||||
|
||||
|
||||
struct s3c_spi {
|
||||
spinlock_t lock;
|
||||
struct semaphore sem;
|
||||
int nr;
|
||||
int dma;
|
||||
/* vivek, 2009-05-14 10:45 Notes: define dmar and dmaw for both write and read channels*/
|
||||
int dmar;
|
||||
int dmaw;
|
||||
|
||||
u_int subchannel;/* user fragment index */
|
||||
dma_addr_t dmabuf_addr;
|
||||
|
||||
struct spi_msg *msg;
|
||||
unsigned int msg_num;
|
||||
unsigned int msg_idx;
|
||||
unsigned int msg_ptr;
|
||||
unsigned int msg_rd_ptr;
|
||||
|
||||
enum s3c_spi_state state;
|
||||
|
||||
void __iomem *regs;
|
||||
struct clk *clk;
|
||||
struct device *dev;
|
||||
struct resource *irq;
|
||||
struct resource *ioarea;
|
||||
struct spi_dev spidev;
|
||||
|
||||
/* GeorgeKuo: */
|
||||
struct completion comp;
|
||||
};
|
||||
|
||||
|
||||
#endif /* _S3C6400_SPI_H */
|
||||
784
drivers/spi/hspi-s3c6400.c
Normal file
784
drivers/spi/hspi-s3c6400.c
Normal file
@@ -0,0 +1,784 @@
|
||||
/* spi-s3c6400.c
|
||||
*
|
||||
* Copyright (C) 2006 Samsung Electronics Co. Ltd.
|
||||
*
|
||||
* S3C6400 SPI Controller
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <asm/hardware.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/regs-s3c6400-clock.h>
|
||||
#include <asm/arch/regs-gpio.h>
|
||||
#include <asm/arch/regs-spi.h>
|
||||
#include <asm/dma.h>
|
||||
|
||||
#include "spi-dev.h"
|
||||
#include "hspi-s3c6400.h"
|
||||
|
||||
//#define S3C_SPI_DEBUG
|
||||
|
||||
#ifdef S3C_SPI_DEBUG
|
||||
#define DBG(x...) printk(x)
|
||||
#define DEBUG printk("%s :: %d\n",__FUNCTION__,__LINE__)
|
||||
void print_reg(struct s3c_spi *spi)
|
||||
{
|
||||
printk("CH_CFG = 0x%08x\n",readl(spi->regs + S3C_CH_CFG));
|
||||
printk("CLK_CFG = 0x%08x\n",readl(spi->regs + S3C_CLK_CFG));
|
||||
printk("MODE_CFG = 0x%08x\n",readl(spi->regs + S3C_MODE_CFG));
|
||||
printk("SLAVE_CFG = 0x%08x\n",readl(spi->regs + S3C_SLAVE_SEL));
|
||||
printk("INT_EN = 0x%08x\n",readl(spi->regs + S3C_SPI_INT_EN));
|
||||
printk("SPI_STATUS = 0x%08x\n",readl(spi->regs + S3C_SPI_STATUS));
|
||||
printk("PACKET_CNT = 0x%08x\n",readl(spi->regs + S3C_PACKET_CNT));
|
||||
printk("PEND_CLR = 0x%08x\n",readl(spi->regs + S3C_PENDING_CLR));
|
||||
printk("SWAP_CFG = 0x%08x\n",readl(spi->regs + S3C_SWAP_CFG));
|
||||
printk("FB_CLK = 0x%08x\n",readl(spi->regs + S3C_FB_CLK));
|
||||
}
|
||||
#else
|
||||
#define DEBUG
|
||||
#define DBG(x...) do { } while (0)
|
||||
void print_reg(struct s3c_spi *spi)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static void s3c_spi_free(struct s3c_spi *spi)
|
||||
{
|
||||
if (spi->clk != NULL && !IS_ERR(spi->clk)) {
|
||||
clk_disable(spi->clk);
|
||||
clk_put(spi->clk);
|
||||
spi->clk = NULL;
|
||||
}
|
||||
|
||||
if (spi->regs != NULL) {
|
||||
iounmap(spi->regs);
|
||||
spi->regs = NULL;
|
||||
}
|
||||
|
||||
if (spi->ioarea != NULL) {
|
||||
release_resource(spi->ioarea);
|
||||
kfree(spi->ioarea);
|
||||
spi->ioarea = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int s3c_spi_hw_init(struct s3c_spi *spi)
|
||||
{
|
||||
#ifdef CONFIG_SPICLK_PCLK
|
||||
clk_enable(spi->clk);
|
||||
#elif defined (CONFIG_SPICLK_EPLL)
|
||||
writel((readl(S3C_PCLK_GATE)|S3C_CLKCON_PCLK_SPI0|S3C_CLKCON_PCLK_SPI1),S3C_PCLK_GATE);
|
||||
writel((readl(S3C_SCLK_GATE)|S3C_CLKCON_SCLK_SPI0|S3C_CLKCON_SCLK_SPI1),S3C_SCLK_GATE);
|
||||
|
||||
writel(readl(S3C_CLK_SRC)|S3C_CLKSRC_MPLL_CLKSEL, S3C_CLK_SRC);
|
||||
|
||||
/* Set SPi Clock to MOUT(266Mhz)*/
|
||||
if(SPI_CHANNEL == 0)
|
||||
writel((readl(S3C_CLK_SRC)&~(0x3<<14))|(1<<14), S3C_CLK_SRC);
|
||||
else /* SPI_CHANNEL = 1 */
|
||||
writel((readl(S3C_CLK_SRC)&~(0x3<<16))|(1<<16), S3C_CLK_SRC);
|
||||
|
||||
/* CLK_DIV2 setting */
|
||||
/* SPI Input Clock(88.87Mhz) = 266.66Mhz / (2 + 1)*/
|
||||
writel(((readl(S3C_CLK_DIV2) & ~(0xff << 0)) | 2) , S3C_CLK_DIV2);
|
||||
|
||||
#elif defined (CONFIG_SPICLK_USBCLK)
|
||||
writel((readl(S3C_PCLK_GATE)| S3C_CLKCON_PCLK_SPI0|S3C_CLKCON_PCLK_SPI1),S3C_PCLK_GATE);
|
||||
writel((readl(S3C_SCLK_GATE)|S3C_CLKCON_SCLK_SPI0_48|S3C_CLKCON_SCLK_SPI1_48),S3C_SCLK_GATE);
|
||||
#else
|
||||
#error you must define correct confige file.
|
||||
#endif
|
||||
|
||||
/* initialize the gpio */
|
||||
if(SPI_CHANNEL == 0) {
|
||||
|
||||
s3c_gpio_cfgpin(S3C_GPC0, S3C_GPC0_SPI_MISO0);
|
||||
s3c_gpio_cfgpin(S3C_GPC1, S3C_GPC1_SPI_CLK0);
|
||||
s3c_gpio_cfgpin(S3C_GPC2, S3C_GPC2_SPI_MOSI0);
|
||||
s3c_gpio_cfgpin(S3C_GPC3, S3C_GPC3_SPI_CS0);
|
||||
|
||||
s3c_gpio_pullup(S3C_GPC0,1);
|
||||
s3c_gpio_pullup(S3C_GPC1,1);
|
||||
s3c_gpio_pullup(S3C_GPC2,1);
|
||||
s3c_gpio_pullup(S3C_GPC3,1);
|
||||
|
||||
} else {
|
||||
|
||||
s3c_gpio_cfgpin(S3C_GPC4, S3C_GPC4_SPI_MISO1);
|
||||
s3c_gpio_cfgpin(S3C_GPC5, S3C_GPC5_SPI_CLK1);
|
||||
s3c_gpio_cfgpin(S3C_GPC6, S3C_GPC6_SPI_MOSI1);
|
||||
s3c_gpio_cfgpin(S3C_GPC7, S3C_GPC7_SPI_CS1);
|
||||
|
||||
s3c_gpio_pullup(S3C_GPC4,1);
|
||||
s3c_gpio_pullup(S3C_GPC5,1);
|
||||
s3c_gpio_pullup(S3C_GPC6,1);
|
||||
s3c_gpio_pullup(S3C_GPC7,1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_spi_dma_init(struct s3c_spi *spi, int mode)
|
||||
{
|
||||
// TX
|
||||
if (mode == 0) {
|
||||
if(SPI_CHANNEL == 0)
|
||||
s3c2410_dma_devconfig(spi->subchannel, S3C2410_DMASRC_MEM, 0, S3C_SPI_TX_DATA_REG);
|
||||
else // SPI_CHANNEL = 1
|
||||
s3c2410_dma_devconfig(spi->subchannel, S3C2410_DMASRC_MEM, 0, S3C_SPI_TX_DATA_REG);
|
||||
|
||||
}
|
||||
|
||||
// RX
|
||||
if (mode == 1) {
|
||||
if(SPI_CHANNEL == 0)
|
||||
s3c2410_dma_devconfig(spi->subchannel, S3C2410_DMASRC_HW, 0, S3C_SPI_RX_DATA_REG);
|
||||
else // SPI_CHANNEL = 1
|
||||
s3c2410_dma_devconfig(spi->subchannel, S3C2410_DMASRC_HW, 0, S3C_SPI_RX_DATA_REG);
|
||||
|
||||
}
|
||||
|
||||
#ifdef CONFIG_WORD_TRANSIZE
|
||||
s3c2410_dma_config(spi->subchannel, S3C_DMA_XFER_WORD, S3C_DMA_XFER_WORD);
|
||||
#else
|
||||
s3c2410_dma_config(spi->subchannel, S3C_DMA_XFER_BYTE, S3C_DMA_XFER_BYTE);
|
||||
#endif
|
||||
s3c2410_dma_setflags(spi->subchannel, S3C2410_DMAF_AUTOSTART);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void s3c_spi_write_fifo(struct s3c_spi *spi)
|
||||
{
|
||||
u32 wdata = 0;
|
||||
|
||||
if (spi->msg->wbuf){
|
||||
wdata = spi->msg->wbuf[spi->msg_ptr++];
|
||||
} else {
|
||||
spi->msg_ptr++;
|
||||
wdata = 0xff;
|
||||
}
|
||||
|
||||
DBG("wdata = %x\n",wdata);
|
||||
writel(wdata, spi->regs + S3C_SPI_TX_DATA);
|
||||
}
|
||||
|
||||
/* s3c_spi_master_complete
|
||||
*
|
||||
* complete the message and wake up the caller, using the given return code,
|
||||
* or zero to mean ok.
|
||||
*/
|
||||
static inline void s3c_spi_master_complete(struct s3c_spi *spi, int ret)
|
||||
{
|
||||
spi->msg_ptr = 0;
|
||||
spi->msg_rd_ptr = 0;
|
||||
spi->msg->flags = 0;
|
||||
spi->msg = NULL;
|
||||
spi->msg_idx ++;
|
||||
spi->msg_num = 0;
|
||||
|
||||
writel(0xff, spi->regs + S3C_PENDING_CLR);
|
||||
|
||||
if (ret)
|
||||
spi->msg_idx = ret;
|
||||
}
|
||||
|
||||
static int s3c_spi_done(struct s3c_spi *spi)
|
||||
{
|
||||
u32 spi_clkcfg;
|
||||
|
||||
/* initialize the gpio */
|
||||
if(SPI_CHANNEL == 0) {
|
||||
s3c_gpio_cfgpin(S3C_GPC0, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC1, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC2, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC3, 0);
|
||||
|
||||
} else {
|
||||
s3c_gpio_cfgpin(S3C_GPC4, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC5, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC6, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC7, 0);
|
||||
}
|
||||
spi_clkcfg = readl( spi->regs + S3C_CLK_CFG);
|
||||
spi_clkcfg &= SPI_ENCLK_DISABLE;
|
||||
writel( spi_clkcfg , spi->regs + S3C_CLK_CFG);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static inline void s3c_spi_stop(struct s3c_spi *spi, int ret)
|
||||
{
|
||||
writel(0x0, spi->regs + S3C_SPI_INT_EN);
|
||||
writel(0x1f, spi->regs + S3C_PENDING_CLR);
|
||||
writel(0x0, spi->regs + S3C_CH_CFG);
|
||||
|
||||
s3c_spi_done(spi);
|
||||
spi->state = STATE_IDLE;
|
||||
s3c_spi_master_complete(spi, ret);
|
||||
print_reg(spi);
|
||||
up(&spi->sem);
|
||||
}
|
||||
|
||||
void s3c_spi_dma_cb(struct s3c2410_dma_chan *dma_ch, void *buf_id,
|
||||
int size, enum s3c2410_dma_buffresult result)
|
||||
{
|
||||
struct s3c_spi *spi = (struct s3c_spi *)buf_id;
|
||||
unsigned long status = 0;
|
||||
|
||||
status = readl(spi->regs + S3C_SPI_STATUS);
|
||||
|
||||
pr_debug("DMA call back\n");
|
||||
|
||||
if (spi->msg->wbuf)
|
||||
while (!(readl(spi->regs +S3C_SPI_STATUS) & SPI_STUS_TX_DONE)) {}
|
||||
|
||||
//s3c_spi_stop(spi, status);
|
||||
s3c_spi_stop(spi, 0);
|
||||
}
|
||||
|
||||
/* s3c_spi_message_start
|
||||
*
|
||||
* configure the spi controler and transmit start of a message onto the bus
|
||||
*/
|
||||
static void s3c_spi_message_start(struct s3c_spi *spi)
|
||||
{
|
||||
struct spi_msg *msg = spi->msg;
|
||||
|
||||
u32 spi_chcfg = 0, spi_slavecfg, spi_inten= 0, spi_packet=0;
|
||||
|
||||
// u8 prescaler = 0; // 44.435 Mhz
|
||||
u8 prescaler = 1; // 22.2175 Mhz
|
||||
// u8 prescaler = 2; // 14.81 Mhz
|
||||
// u8 prescaler = 3; // 11.10875 Mhz
|
||||
// u8 prescaler = 4; // 8.887Mhz
|
||||
|
||||
u32 spi_clkcfg = 0, spi_modecfg = 0 ;
|
||||
|
||||
/* initialise the spi controller */
|
||||
s3c_spi_hw_init(spi);
|
||||
|
||||
/* 1. Set transfer type (CPOL & CPHA set) */
|
||||
spi_chcfg = SPI_CH_RISING | SPI_CH_FORMAT_A;
|
||||
|
||||
if (spi->msg->flags & SPI_M_MODE_MASTER) {
|
||||
spi_chcfg |= SPI_CH_MASTER;
|
||||
} else if(spi->msg->flags & SPI_M_MODE_SLAVE){
|
||||
spi_chcfg |= SPI_CH_SLAVE;
|
||||
}
|
||||
|
||||
writel( spi_chcfg , spi->regs + S3C_CH_CFG);
|
||||
|
||||
/* 2. Set clock configuration register */
|
||||
spi_clkcfg = SPI_ENCLK_ENABLE;
|
||||
|
||||
#if defined CONFIG_SPICLK_PCLK
|
||||
spi_clkcfg |= SPI_CLKSEL_PCLK;
|
||||
#elif defined CONFIG_SPICLK_EPLL
|
||||
spi_clkcfg |= SPI_CLKSEL_ECLK;
|
||||
#elif defined CONFIG_SPICLK_USBCLK
|
||||
spi_clkcfg |= SPI_CLKSEL_USBCLK;
|
||||
#else
|
||||
#error you must define correct confige file.
|
||||
#endif
|
||||
writel( spi_clkcfg , spi->regs + S3C_CLK_CFG);
|
||||
|
||||
spi_clkcfg = readl( spi->regs + S3C_CLK_CFG);
|
||||
|
||||
/* SPI clockout = clock source / (2 * (prescaler +1)) */
|
||||
spi_clkcfg |= prescaler;
|
||||
writel( spi_clkcfg , spi->regs + S3C_CLK_CFG);
|
||||
|
||||
/* 3. Set SPI MODE configuration register */
|
||||
#ifdef CONFIG_WORD_TRANSIZE
|
||||
spi_modecfg = SPI_MODE_CH_TSZ_WORD| SPI_MODE_BUS_TSZ_WORD;
|
||||
#else
|
||||
spi_modecfg = SPI_MODE_CH_TSZ_BYTE| SPI_MODE_BUS_TSZ_BYTE;
|
||||
#endif
|
||||
spi_modecfg |= SPI_MODE_TXDMA_OFF| SPI_MODE_SINGLE| SPI_MODE_RXDMA_OFF;
|
||||
|
||||
if (msg->flags & SPI_M_DMA_MODE) {
|
||||
spi_modecfg |= SPI_MODE_TXDMA_ON| SPI_MODE_RXDMA_ON;
|
||||
}
|
||||
|
||||
if (msg->wbuf)
|
||||
spi_modecfg |= ( 0x3f << 5); /* Tx FIFO trigger level in INT mode */
|
||||
if (msg->rbuf)
|
||||
spi_modecfg |= ( 0x3f << 11); /* Rx FIFO trigger level in INT mode */
|
||||
|
||||
spi_modecfg |= ( 0x3ff << 19);
|
||||
writel(spi_modecfg, spi->regs + S3C_MODE_CFG);
|
||||
|
||||
/* 4. Set SPI INT_EN register */
|
||||
|
||||
if (msg->wbuf)
|
||||
spi_inten = SPI_INT_TX_FIFORDY_EN|SPI_INT_TX_UNDERRUN_EN|SPI_INT_TX_OVERRUN_EN;
|
||||
if (msg->rbuf){
|
||||
spi_inten = SPI_INT_RX_FIFORDY_EN|SPI_INT_RX_UNDERRUN_EN|SPI_INT_RX_OVERRUN_EN|SPI_INT_TRAILING_EN ;
|
||||
}
|
||||
writel(spi_inten, spi->regs + S3C_SPI_INT_EN);
|
||||
|
||||
writel(0x1f, spi->regs + S3C_PENDING_CLR);
|
||||
|
||||
/* 5. Set Packet Count configuration register */
|
||||
spi_packet = SPI_PACKET_CNT_EN;
|
||||
spi_packet |= 0xffff;
|
||||
writel(spi_packet, spi->regs + S3C_PACKET_CNT);
|
||||
|
||||
/* 6. Set Tx or Rx Channel on */
|
||||
spi_chcfg = readl(spi->regs + S3C_CH_CFG);
|
||||
spi_chcfg |= SPI_CH_TXCH_OFF | SPI_CH_RXCH_OFF;
|
||||
|
||||
if (msg->wbuf)
|
||||
spi_chcfg |= SPI_CH_TXCH_ON;
|
||||
if (msg->rbuf)
|
||||
spi_chcfg |= SPI_CH_RXCH_ON;
|
||||
|
||||
writel(spi_chcfg, spi->regs + S3C_CH_CFG);
|
||||
|
||||
if (msg->flags & SPI_M_DMA_MODE) {
|
||||
spi->dma = S3C_SPI_DMA;
|
||||
|
||||
if (msg->wbuf)
|
||||
spi->subchannel = DMACH_SPI0_OUT;
|
||||
if (msg->rbuf)
|
||||
spi->subchannel = DMACH_SPI0_IN;
|
||||
|
||||
if (s3c2410_dma_request(spi->subchannel, &s3c6400spi_dma_client, NULL)) {
|
||||
printk(KERN_WARNING "unable to get DMA channel.\n" );
|
||||
}
|
||||
|
||||
s3c2410_dma_set_buffdone_fn(spi->subchannel, s3c_spi_dma_cb);
|
||||
s3c2410_dma_set_opfn(spi->subchannel, NULL);
|
||||
|
||||
|
||||
if (msg->wbuf)
|
||||
s3c_spi_dma_init(spi, 0);
|
||||
if (msg->rbuf)
|
||||
s3c_spi_dma_init(spi, 1);
|
||||
|
||||
s3c2410_dma_enqueue(spi->subchannel, (void *) spi, spi->dmabuf_addr, spi->msg->len);
|
||||
}
|
||||
|
||||
/* 7. Set nSS low to start Tx or Rx operation */
|
||||
spi_slavecfg = readl(spi->regs + S3C_SLAVE_SEL);
|
||||
spi_slavecfg &= SPI_SLAVE_SIG_ACT;
|
||||
spi_slavecfg |= (0x3f << 4);
|
||||
writel(spi_slavecfg, spi->regs + S3C_SLAVE_SEL);
|
||||
|
||||
print_reg(spi);
|
||||
}
|
||||
|
||||
/* is_msgend
|
||||
*
|
||||
* returns TRUE if we reached the end of the current message
|
||||
*/
|
||||
|
||||
static inline int tx_msgend(struct s3c_spi *spi)
|
||||
{
|
||||
return spi->msg_ptr >= spi->msg->len;
|
||||
}
|
||||
|
||||
static inline int rx_msgend(struct s3c_spi *spi)
|
||||
{
|
||||
return spi->msg_rd_ptr >= spi->msg->len;
|
||||
}
|
||||
|
||||
/* spi_s3c_irq_nextbyte
|
||||
*
|
||||
* process an interrupt and work out what to do
|
||||
*/
|
||||
static void spi_s3c_irq_nextbyte(struct s3c_spi *spi, unsigned long spsta)
|
||||
{
|
||||
DBG("spi->state = %d \n",spi->state);
|
||||
switch (spi->state) {
|
||||
case STATE_IDLE:
|
||||
DBG("%s: called in STATE_IDLE\n", __FUNCTION__);
|
||||
break;
|
||||
|
||||
case STATE_STOP:
|
||||
udelay(200);
|
||||
s3c_spi_stop(spi, 0);
|
||||
DBG("%s: called in STATE_STOP\n", __FUNCTION__);
|
||||
break;
|
||||
|
||||
case STATE_XFER_TX:
|
||||
print_reg(spi);
|
||||
DBG("msg_ptr = 0x%x, len = 0x%x \n", spi->msg_ptr ,spi->msg->len);
|
||||
while(!(tx_msgend(spi)))
|
||||
s3c_spi_write_fifo(spi);
|
||||
print_reg(spi);
|
||||
spi->state = STATE_STOP;
|
||||
break;
|
||||
case STATE_XFER_RX:
|
||||
print_reg(spi);
|
||||
DBG("msg_rd_ptr = 0x%x, len = 0x%x \n", spi->msg_rd_ptr ,spi->msg->len);
|
||||
while(!(rx_msgend(spi))){
|
||||
spi->msg->rbuf[spi->msg_rd_ptr++] = readl(spi->regs + S3C_SPI_RX_DATA);
|
||||
DBG("msg_rd_ptr = 0x%x, len = 0x%x \n", spi->msg_rd_ptr ,spi->msg->len);
|
||||
DBG("msg_rbuf = 0x%x\n", spi->msg->rbuf[spi->msg_rd_ptr - 1]);
|
||||
}
|
||||
DBG("msg_rd_ptr = 0x%x, len = 0x%x \n", spi->msg_rd_ptr ,spi->msg->len);
|
||||
print_reg(spi);
|
||||
s3c_spi_stop(spi, 0);
|
||||
break;
|
||||
default:
|
||||
dev_err(spi->dev, "%s: called with Invalid option\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* s3c_spi_irq
|
||||
*
|
||||
* top level IRQ servicing routine
|
||||
*/
|
||||
static irqreturn_t s3c_spi_irq(int irqno, void *dev_id)
|
||||
{
|
||||
struct s3c_spi *spi = dev_id;
|
||||
unsigned long spi_sts;
|
||||
|
||||
spi_sts = readl(spi->regs + S3C_SPI_STATUS);
|
||||
if (spi_sts & SPI_STUS_RX_OVERRUN_ERR) {
|
||||
printk("hspi : Rx overrun error detected\n");
|
||||
}
|
||||
|
||||
if (spi_sts & SPI_STUS_RX_UNDERRUN_ERR) {
|
||||
printk("hspi : Rx underrun error detected\n");
|
||||
}
|
||||
|
||||
if (spi_sts & SPI_STUS_TX_OVERRUN_ERR) {
|
||||
printk("hspi : Tx overrun error detected\n");
|
||||
}
|
||||
|
||||
if (spi_sts & SPI_STUS_TX_UNDERRUN_ERR) {
|
||||
printk("hspi : Tx underrun error detected\n");
|
||||
}
|
||||
|
||||
/* pretty much this leaves us with the fact that we've
|
||||
* transmitted or received whatever byte we last sent */
|
||||
spi_s3c_irq_nextbyte(spi, spi_sts);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int s3c_spi_doxfer(struct s3c_spi *spi, struct spi_msg msgs[], int num)
|
||||
{
|
||||
int ret;
|
||||
|
||||
spin_lock_irq(&spi->lock);
|
||||
|
||||
spi->msg = msgs;
|
||||
spi->msg_num = num;
|
||||
spi->msg_ptr = 0;
|
||||
spi->msg_rd_ptr = 0;
|
||||
spi->msg_idx = 0;
|
||||
|
||||
if (spi->msg->flags & SPI_M_DMA_MODE) {
|
||||
spi->dmabuf_addr = spi->spidev.dmabuf;
|
||||
pr_debug("spi->dmabuf_addr = 0x%x\n",spi->dmabuf_addr);
|
||||
}
|
||||
|
||||
if (spi->msg->wbuf) {
|
||||
spi->state = STATE_XFER_TX;
|
||||
} else if (spi->msg->rbuf) {
|
||||
spi->state = STATE_XFER_RX;
|
||||
} else {
|
||||
dev_err(spi->dev,"Unknown functionality \n");
|
||||
return -ESRCH;
|
||||
}
|
||||
|
||||
s3c_spi_message_start(spi);
|
||||
|
||||
if (down_interruptible(&spi->sem))
|
||||
return -EINTR;
|
||||
|
||||
spin_unlock_irq(&spi->lock);
|
||||
|
||||
ret = spi->msg_idx;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* s3c_spi_xfer
|
||||
*
|
||||
* first port of call from the spi bus code when an message needs
|
||||
* transfering across the spi bus.
|
||||
*/
|
||||
static int s3c_spi_xfer(struct spi_dev *spi_dev,
|
||||
struct spi_msg msgs[], int num)
|
||||
{
|
||||
struct s3c_spi *spi = (struct s3c_spi *)spi_dev->algo_data;
|
||||
int retry;
|
||||
int ret;
|
||||
|
||||
for (retry = 0; retry < spi_dev->retries; retry++) {
|
||||
ret = s3c_spi_doxfer(spi, msgs, num);
|
||||
|
||||
print_reg(spi);
|
||||
if (ret != -EAGAIN)
|
||||
return ret;
|
||||
printk("Retrying transmission (%d)\n", retry);
|
||||
|
||||
udelay(100);
|
||||
}
|
||||
|
||||
return -EREMOTEIO;
|
||||
}
|
||||
|
||||
static int s3c_spi_close(struct spi_dev *spi_dev)
|
||||
{
|
||||
struct s3c_spi *spi = (struct s3c_spi *)spi_dev->algo_data;
|
||||
u32 spi_clkcfg;
|
||||
|
||||
s3c2410_dma_free(spi->subchannel, &s3c6400spi_dma_client);
|
||||
|
||||
if(SPI_CHANNEL == 0) {
|
||||
|
||||
s3c_gpio_cfgpin(S3C_GPC0, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC1, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC2, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC3, 0);
|
||||
|
||||
} else {
|
||||
|
||||
s3c_gpio_cfgpin(S3C_GPC4, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC5, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC6, 0);
|
||||
s3c_gpio_cfgpin(S3C_GPC7, 0);
|
||||
}
|
||||
spi_clkcfg = readl( spi->regs + S3C_CLK_CFG);
|
||||
spi_clkcfg &= SPI_ENCLK_DISABLE;
|
||||
writel( spi_clkcfg , spi->regs + S3C_CLK_CFG);
|
||||
|
||||
/* Buffer Clear after finish xfer */
|
||||
writel( 0x20, spi->regs + S3C_CH_CFG);
|
||||
writel( 0x0, spi->regs + S3C_CH_CFG);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* spi bus registration info */
|
||||
static struct spi_algorithm s3c_spi_algorithm = {
|
||||
.name = "S3C6400-spi-algorithm",
|
||||
.master_xfer = s3c_spi_xfer,
|
||||
.close = s3c_spi_close,
|
||||
};
|
||||
|
||||
static struct s3c_spi s3c_spi[2] = {
|
||||
[0] = {
|
||||
.lock = SPIN_LOCK_UNLOCKED,
|
||||
.spidev = {
|
||||
.algo = &s3c_spi_algorithm,
|
||||
.retries = 2,
|
||||
.timeout = 5,
|
||||
}
|
||||
},
|
||||
[1] = {
|
||||
.lock = SPIN_LOCK_UNLOCKED,
|
||||
.spidev = {
|
||||
.algo = &s3c_spi_algorithm,
|
||||
.retries = 2,
|
||||
.timeout = 5,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/* s3c_spi_probe
|
||||
*
|
||||
* called by the bus driver when a suitable device is found
|
||||
*/
|
||||
|
||||
static int s3c_spi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct s3c_spi *spi = &s3c_spi[pdev->id];
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
/* find the clock and enable it */
|
||||
sema_init(&spi->sem, 0);
|
||||
spi->nr = pdev->id;
|
||||
spi->dev = &pdev->dev;
|
||||
|
||||
spi->clk = clk_get(&pdev->dev, "spi");
|
||||
|
||||
if (IS_ERR(spi->clk)) {
|
||||
dev_err(&pdev->dev, "cannot get clock\n");
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* map the registers */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
if (res == NULL) {
|
||||
dev_err(&pdev->dev, "cannot find IO resource\n");
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
spi->ioarea = request_mem_region(res->start, (res->end - res->start) + 1, pdev->name);
|
||||
|
||||
if (spi->ioarea == NULL) {
|
||||
dev_err(&pdev->dev, "cannot request IO\n");
|
||||
ret = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
spi->regs = ioremap(res->start, (res->end - res->start) + 1);
|
||||
|
||||
if (spi->regs == NULL) {
|
||||
dev_err(&pdev->dev, "cannot map IO\n");
|
||||
ret = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
printk("hspi registers %p (%p, %p)\n", spi->regs, spi->ioarea, res);
|
||||
|
||||
/* setup info block for the spi core */
|
||||
spi->spidev.algo_data = spi;
|
||||
spi->spidev.dev.parent = &pdev->dev;
|
||||
spi->spidev.minor = spi->nr;
|
||||
init_MUTEX(&spi->spidev.bus_lock);
|
||||
|
||||
/* find the IRQ for this unit (note, this relies on the init call to
|
||||
* ensure no current IRQs pending
|
||||
*/
|
||||
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
|
||||
if (res == NULL) {
|
||||
printk("hspi cannot find IRQ\n");
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = request_irq(res->start, s3c_spi_irq, SA_INTERRUPT,
|
||||
pdev->name, spi);
|
||||
|
||||
if (ret != 0) {
|
||||
printk("hspi cannot claim IRQ\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
printk("hspi irq resource %p (%d)\n", res, res->start);
|
||||
|
||||
ret = spi_attach_spidev(&spi->spidev);
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to add adapter to spi core\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&pdev->dev, spi);
|
||||
|
||||
dev_info(&pdev->dev, "%s: S3C SPI adapter\n", spi->dev->bus_id);
|
||||
|
||||
printk("%s: S3C SPI adapter\n", spi->dev->bus_id);
|
||||
|
||||
out:
|
||||
if (ret < 0)
|
||||
s3c_spi_free(spi);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* s3c_spi_remove
|
||||
*
|
||||
* called when device is removed from the bus
|
||||
*/
|
||||
static int s3c_spi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct s3c_spi *spi = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
if (spi != NULL) {
|
||||
spi_detach_spidev(&spi->spidev);
|
||||
s3c_spi_free(spi);
|
||||
dev_set_drvdata(&pdev->dev, NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int s3c_spi_suspend(struct platform_device *pdev, pm_message_t msg)
|
||||
{
|
||||
struct s3c_spi *hw = platform_get_drvdata(pdev);
|
||||
clk_disable(hw->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_spi_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct s3c_spi *hw = platform_get_drvdata(pdev);
|
||||
clk_enable(hw->clk);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define s3c_spi_suspend NULL
|
||||
#define s3c_spi_resume NULL
|
||||
#endif
|
||||
|
||||
/* device driver for platform bus bits */
|
||||
static struct platform_driver s3c_spi_driver = {
|
||||
.probe = s3c_spi_probe,
|
||||
.remove = s3c_spi_remove,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = s3c_spi_suspend,
|
||||
.resume = s3c_spi_resume,
|
||||
#endif
|
||||
.driver = {
|
||||
.name = "s3c2410-spi",
|
||||
.owner = THIS_MODULE,
|
||||
.bus = &platform_bus_type,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init s3c_spi_driver_init(void)
|
||||
{
|
||||
printk(KERN_INFO "S3C6400 SPI Driver \n");
|
||||
|
||||
return platform_driver_register(&s3c_spi_driver);
|
||||
}
|
||||
|
||||
static void __exit s3c_spi_driver_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&s3c_spi_driver);
|
||||
}
|
||||
|
||||
module_init(s3c_spi_driver_init);
|
||||
module_exit(s3c_spi_driver_exit);
|
||||
|
||||
MODULE_DESCRIPTION("S3C6400 SPI Bus driver");
|
||||
MODULE_AUTHOR("Ryu Euiyoul<steven.ryu@samsung.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
95
drivers/spi/hspi-s3c6400.h
Normal file
95
drivers/spi/hspi-s3c6400.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* */
|
||||
/* spi-s3c6400.h - definitions of s3c6400 specific spi interface */
|
||||
/* */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Copyright (C) 2006 Samsung Electronics Co. ltd.
|
||||
|
||||
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., 675 Mass Ave, Cambridge, MA 02139, USA. */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
#ifndef _S3C6400_SPI_H
|
||||
#define _S3C6400_SPI_H
|
||||
|
||||
#include <asm/dma.h>
|
||||
#include <asm/arch/dma.h>
|
||||
|
||||
//#define SPI_CHANNEL 1
|
||||
|
||||
#if(SPI_CHANNEL==0)
|
||||
/* SPI CHANNEL 0 */
|
||||
#define S3C_SPI_TX_DATA_REG 0x7F00B018 //SPI TX data
|
||||
#define S3C_SPI_RX_DATA_REG 0x7F00B01C //SPI RX data
|
||||
#else
|
||||
/* SPI CHANNEL 1 */
|
||||
#define S3C_SPI_TX_DATA_REG 0x7F00C018 //SPI TX data
|
||||
#define S3C_SPI_RX_DATA_REG 0x7F00C01C //SPI RX data
|
||||
#endif
|
||||
|
||||
/* DMA channel to be used for the SPI interface. */
|
||||
#define S3C_SPI_DMA 0
|
||||
|
||||
/* DMA transfer unit (byte). */
|
||||
#define S3C_DMA_XFER_BYTE 1
|
||||
#define S3C_DMA_XFER_WORD 4
|
||||
|
||||
/* DMA configuration setup byte. */
|
||||
#define S3C_DCON_SPI1 (S3C_DCON_HANDSHAKE | S3C_DCON_SYNC_PCLK)
|
||||
|
||||
/* DMA hardware configuration mode (DISRCC register). */
|
||||
#define S3C_SPI1_DMA_HWCFG 3
|
||||
#define S3C_SPI_DMA_HWCFG 3
|
||||
|
||||
#define DMA_BUFFER_SIZE 1500
|
||||
|
||||
/* spi controller state */
|
||||
int req_dma_flag = 1;
|
||||
enum s3c_spi_state {
|
||||
STATE_IDLE,
|
||||
STATE_XFER_TX,
|
||||
STATE_XFER_RX,
|
||||
STATE_STOP
|
||||
};
|
||||
|
||||
static struct s3c2410_dma_client s3c6400spi_dma_client = {
|
||||
.name = "s3c6400-spi-dma",
|
||||
};
|
||||
|
||||
struct s3c_spi {
|
||||
spinlock_t lock;
|
||||
struct semaphore sem;
|
||||
int nr;
|
||||
int dma;
|
||||
u_int subchannel;/* user fragment index */
|
||||
dma_addr_t dmabuf_addr;
|
||||
|
||||
struct spi_msg *msg;
|
||||
unsigned int msg_num;
|
||||
unsigned int msg_idx;
|
||||
unsigned int msg_ptr;
|
||||
unsigned int msg_rd_ptr;
|
||||
|
||||
enum s3c_spi_state state;
|
||||
|
||||
void __iomem *regs;
|
||||
struct clk *clk;
|
||||
struct device *dev;
|
||||
struct resource *irq;
|
||||
struct resource *ioarea;
|
||||
struct spi_dev spidev;
|
||||
};
|
||||
|
||||
|
||||
#endif /* _S3C6400_SPI_H */
|
||||
572
drivers/spi/omap_uwire.c
Normal file
572
drivers/spi/omap_uwire.c
Normal file
@@ -0,0 +1,572 @@
|
||||
/*
|
||||
* omap_uwire.c -- MicroWire interface driver for OMAP
|
||||
*
|
||||
* Copyright 2003 MontaVista Software Inc. <source@mvista.com>
|
||||
*
|
||||
* Ported to 2.6 OMAP uwire interface.
|
||||
* Copyright (C) 2004 Texas Instruments.
|
||||
*
|
||||
* Generalization patches by Juha Yrjola <juha.yrjola@nokia.com>
|
||||
*
|
||||
* Copyright (C) 2005 David Brownell (ported to 2.6 SPI interface)
|
||||
* Copyright (C) 2006 Nokia
|
||||
*
|
||||
* Many updates by Imre Deak <imre.deak@nokia.com>
|
||||
*
|
||||
* 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 SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* 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.,
|
||||
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi_bitbang.h>
|
||||
|
||||
#include <asm/system.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/hardware.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include <asm/arch/mux.h>
|
||||
#include <asm/arch/omap730.h> /* OMAP730_IO_CONF registers */
|
||||
|
||||
|
||||
/* FIXME address is now a platform device resource,
|
||||
* and irqs should show there too...
|
||||
*/
|
||||
#define UWIRE_BASE_PHYS 0xFFFB3000
|
||||
#define UWIRE_BASE ((void *__iomem)IO_ADDRESS(UWIRE_BASE_PHYS))
|
||||
|
||||
/* uWire Registers: */
|
||||
#define UWIRE_IO_SIZE 0x20
|
||||
#define UWIRE_TDR 0x00
|
||||
#define UWIRE_RDR 0x00
|
||||
#define UWIRE_CSR 0x01
|
||||
#define UWIRE_SR1 0x02
|
||||
#define UWIRE_SR2 0x03
|
||||
#define UWIRE_SR3 0x04
|
||||
#define UWIRE_SR4 0x05
|
||||
#define UWIRE_SR5 0x06
|
||||
|
||||
/* CSR bits */
|
||||
#define RDRB (1 << 15)
|
||||
#define CSRB (1 << 14)
|
||||
#define START (1 << 13)
|
||||
#define CS_CMD (1 << 12)
|
||||
|
||||
/* SR1 or SR2 bits */
|
||||
#define UWIRE_READ_FALLING_EDGE 0x0001
|
||||
#define UWIRE_READ_RISING_EDGE 0x0000
|
||||
#define UWIRE_WRITE_FALLING_EDGE 0x0000
|
||||
#define UWIRE_WRITE_RISING_EDGE 0x0002
|
||||
#define UWIRE_CS_ACTIVE_LOW 0x0000
|
||||
#define UWIRE_CS_ACTIVE_HIGH 0x0004
|
||||
#define UWIRE_FREQ_DIV_2 0x0000
|
||||
#define UWIRE_FREQ_DIV_4 0x0008
|
||||
#define UWIRE_FREQ_DIV_8 0x0010
|
||||
#define UWIRE_CHK_READY 0x0020
|
||||
#define UWIRE_CLK_INVERTED 0x0040
|
||||
|
||||
|
||||
struct uwire_spi {
|
||||
struct spi_bitbang bitbang;
|
||||
struct clk *ck;
|
||||
};
|
||||
|
||||
struct uwire_state {
|
||||
unsigned bits_per_word;
|
||||
unsigned div1_idx;
|
||||
};
|
||||
|
||||
/* REVISIT compile time constant for idx_shift? */
|
||||
static unsigned int uwire_idx_shift;
|
||||
|
||||
static inline void uwire_write_reg(int idx, u16 val)
|
||||
{
|
||||
__raw_writew(val, UWIRE_BASE + (idx << uwire_idx_shift));
|
||||
}
|
||||
|
||||
static inline u16 uwire_read_reg(int idx)
|
||||
{
|
||||
return __raw_readw(UWIRE_BASE + (idx << uwire_idx_shift));
|
||||
}
|
||||
|
||||
static inline void omap_uwire_configure_mode(u8 cs, unsigned long flags)
|
||||
{
|
||||
u16 w, val = 0;
|
||||
int shift, reg;
|
||||
|
||||
if (flags & UWIRE_CLK_INVERTED)
|
||||
val ^= 0x03;
|
||||
val = flags & 0x3f;
|
||||
if (cs & 1)
|
||||
shift = 6;
|
||||
else
|
||||
shift = 0;
|
||||
if (cs <= 1)
|
||||
reg = UWIRE_SR1;
|
||||
else
|
||||
reg = UWIRE_SR2;
|
||||
|
||||
w = uwire_read_reg(reg);
|
||||
w &= ~(0x3f << shift);
|
||||
w |= val << shift;
|
||||
uwire_write_reg(reg, w);
|
||||
}
|
||||
|
||||
static int wait_uwire_csr_flag(u16 mask, u16 val, int might_not_catch)
|
||||
{
|
||||
u16 w;
|
||||
int c = 0;
|
||||
unsigned long max_jiffies = jiffies + HZ;
|
||||
|
||||
for (;;) {
|
||||
w = uwire_read_reg(UWIRE_CSR);
|
||||
if ((w & mask) == val)
|
||||
break;
|
||||
if (time_after(jiffies, max_jiffies)) {
|
||||
printk(KERN_ERR "%s: timeout. reg=%#06x "
|
||||
"mask=%#06x val=%#06x\n",
|
||||
__FUNCTION__, w, mask, val);
|
||||
return -1;
|
||||
}
|
||||
c++;
|
||||
if (might_not_catch && c > 64)
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void uwire_set_clk1_div(int div1_idx)
|
||||
{
|
||||
u16 w;
|
||||
|
||||
w = uwire_read_reg(UWIRE_SR3);
|
||||
w &= ~(0x03 << 1);
|
||||
w |= div1_idx << 1;
|
||||
uwire_write_reg(UWIRE_SR3, w);
|
||||
}
|
||||
|
||||
static void uwire_chipselect(struct spi_device *spi, int value)
|
||||
{
|
||||
struct uwire_state *ust = spi->controller_state;
|
||||
u16 w;
|
||||
int old_cs;
|
||||
|
||||
|
||||
BUG_ON(wait_uwire_csr_flag(CSRB, 0, 0));
|
||||
|
||||
w = uwire_read_reg(UWIRE_CSR);
|
||||
old_cs = (w >> 10) & 0x03;
|
||||
if (value == BITBANG_CS_INACTIVE || old_cs != spi->chip_select) {
|
||||
/* Deselect this CS, or the previous CS */
|
||||
w &= ~CS_CMD;
|
||||
uwire_write_reg(UWIRE_CSR, w);
|
||||
}
|
||||
/* activate specfied chipselect */
|
||||
if (value == BITBANG_CS_ACTIVE) {
|
||||
uwire_set_clk1_div(ust->div1_idx);
|
||||
/* invert clock? */
|
||||
if (spi->mode & SPI_CPOL)
|
||||
uwire_write_reg(UWIRE_SR4, 1);
|
||||
else
|
||||
uwire_write_reg(UWIRE_SR4, 0);
|
||||
|
||||
w = spi->chip_select << 10;
|
||||
w |= CS_CMD;
|
||||
uwire_write_reg(UWIRE_CSR, w);
|
||||
}
|
||||
}
|
||||
|
||||
static int uwire_txrx(struct spi_device *spi, struct spi_transfer *t)
|
||||
{
|
||||
struct uwire_state *ust = spi->controller_state;
|
||||
unsigned len = t->len;
|
||||
unsigned bits = ust->bits_per_word;
|
||||
unsigned bytes;
|
||||
u16 val, w;
|
||||
int status = 0;;
|
||||
|
||||
if (!t->tx_buf && !t->rx_buf)
|
||||
return 0;
|
||||
|
||||
/* Microwire doesn't read and write concurrently */
|
||||
if (t->tx_buf && t->rx_buf)
|
||||
return -EPERM;
|
||||
|
||||
w = spi->chip_select << 10;
|
||||
w |= CS_CMD;
|
||||
|
||||
if (t->tx_buf) {
|
||||
const u8 *buf = t->tx_buf;
|
||||
|
||||
/* NOTE: DMA could be used for TX transfers */
|
||||
|
||||
/* write one or two bytes at a time */
|
||||
while (len >= 1) {
|
||||
/* tx bit 15 is first sent; we byteswap multibyte words
|
||||
* (msb-first) on the way out from memory.
|
||||
*/
|
||||
val = *buf++;
|
||||
if (bits > 8) {
|
||||
bytes = 2;
|
||||
val |= *buf++ << 8;
|
||||
} else
|
||||
bytes = 1;
|
||||
val <<= 16 - bits;
|
||||
|
||||
#ifdef VERBOSE
|
||||
pr_debug("%s: write-%d =%04x\n",
|
||||
spi->dev.bus_id, bits, val);
|
||||
#endif
|
||||
if (wait_uwire_csr_flag(CSRB, 0, 0))
|
||||
goto eio;
|
||||
|
||||
uwire_write_reg(UWIRE_TDR, val);
|
||||
|
||||
/* start write */
|
||||
val = START | w | (bits << 5);
|
||||
|
||||
uwire_write_reg(UWIRE_CSR, val);
|
||||
len -= bytes;
|
||||
|
||||
/* Wait till write actually starts.
|
||||
* This is needed with MPU clock 60+ MHz.
|
||||
* REVISIT: we may not have time to catch it...
|
||||
*/
|
||||
if (wait_uwire_csr_flag(CSRB, CSRB, 1))
|
||||
goto eio;
|
||||
|
||||
status += bytes;
|
||||
}
|
||||
|
||||
/* REVISIT: save this for later to get more i/o overlap */
|
||||
if (wait_uwire_csr_flag(CSRB, 0, 0))
|
||||
goto eio;
|
||||
|
||||
} else if (t->rx_buf) {
|
||||
u8 *buf = t->rx_buf;
|
||||
|
||||
/* read one or two bytes at a time */
|
||||
while (len) {
|
||||
if (bits > 8) {
|
||||
bytes = 2;
|
||||
} else
|
||||
bytes = 1;
|
||||
|
||||
/* start read */
|
||||
val = START | w | (bits << 0);
|
||||
uwire_write_reg(UWIRE_CSR, val);
|
||||
len -= bytes;
|
||||
|
||||
/* Wait till read actually starts */
|
||||
(void) wait_uwire_csr_flag(CSRB, CSRB, 1);
|
||||
|
||||
if (wait_uwire_csr_flag(RDRB | CSRB,
|
||||
RDRB, 0))
|
||||
goto eio;
|
||||
|
||||
/* rx bit 0 is last received; multibyte words will
|
||||
* be properly byteswapped on the way to memory.
|
||||
*/
|
||||
val = uwire_read_reg(UWIRE_RDR);
|
||||
val &= (1 << bits) - 1;
|
||||
*buf++ = (u8) val;
|
||||
if (bytes == 2)
|
||||
*buf++ = val >> 8;
|
||||
status += bytes;
|
||||
#ifdef VERBOSE
|
||||
pr_debug("%s: read-%d =%04x\n",
|
||||
spi->dev.bus_id, bits, val);
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
return status;
|
||||
eio:
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int uwire_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
|
||||
{
|
||||
struct uwire_state *ust = spi->controller_state;
|
||||
struct uwire_spi *uwire;
|
||||
unsigned flags = 0;
|
||||
unsigned bits;
|
||||
unsigned hz;
|
||||
unsigned long rate;
|
||||
int div1_idx;
|
||||
int div1;
|
||||
int div2;
|
||||
int status;
|
||||
|
||||
uwire = spi_master_get_devdata(spi->master);
|
||||
|
||||
if (spi->chip_select > 3) {
|
||||
pr_debug("%s: cs%d?\n", spi->dev.bus_id, spi->chip_select);
|
||||
status = -ENODEV;
|
||||
goto done;
|
||||
}
|
||||
|
||||
bits = spi->bits_per_word;
|
||||
if (t != NULL && t->bits_per_word)
|
||||
bits = t->bits_per_word;
|
||||
if (!bits)
|
||||
bits = 8;
|
||||
|
||||
if (bits > 16) {
|
||||
pr_debug("%s: wordsize %d?\n", spi->dev.bus_id, bits);
|
||||
status = -ENODEV;
|
||||
goto done;
|
||||
}
|
||||
ust->bits_per_word = bits;
|
||||
|
||||
/* mode 0..3, clock inverted separately;
|
||||
* standard nCS signaling;
|
||||
* don't treat DI=high as "not ready"
|
||||
*/
|
||||
if (spi->mode & SPI_CS_HIGH)
|
||||
flags |= UWIRE_CS_ACTIVE_HIGH;
|
||||
|
||||
if (spi->mode & SPI_CPOL)
|
||||
flags |= UWIRE_CLK_INVERTED;
|
||||
|
||||
switch (spi->mode & (SPI_CPOL | SPI_CPHA)) {
|
||||
case SPI_MODE_0:
|
||||
case SPI_MODE_3:
|
||||
flags |= UWIRE_WRITE_RISING_EDGE | UWIRE_READ_FALLING_EDGE;
|
||||
break;
|
||||
case SPI_MODE_1:
|
||||
case SPI_MODE_2:
|
||||
flags |= UWIRE_WRITE_FALLING_EDGE | UWIRE_READ_RISING_EDGE;
|
||||
break;
|
||||
}
|
||||
|
||||
/* assume it's already enabled */
|
||||
rate = clk_get_rate(uwire->ck);
|
||||
|
||||
hz = spi->max_speed_hz;
|
||||
if (t != NULL && t->speed_hz)
|
||||
hz = t->speed_hz;
|
||||
|
||||
if (!hz) {
|
||||
pr_debug("%s: zero speed?\n", spi->dev.bus_id);
|
||||
status = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* F_INT = mpu_xor_clk / DIV1 */
|
||||
for (div1_idx = 0; div1_idx < 4; div1_idx++) {
|
||||
switch (div1_idx) {
|
||||
case 0:
|
||||
div1 = 2;
|
||||
break;
|
||||
case 1:
|
||||
div1 = 4;
|
||||
break;
|
||||
case 2:
|
||||
div1 = 7;
|
||||
break;
|
||||
default:
|
||||
case 3:
|
||||
div1 = 10;
|
||||
break;
|
||||
}
|
||||
div2 = (rate / div1 + hz - 1) / hz;
|
||||
if (div2 <= 8)
|
||||
break;
|
||||
}
|
||||
if (div1_idx == 4) {
|
||||
pr_debug("%s: lowest clock %ld, need %d\n",
|
||||
spi->dev.bus_id, rate / 10 / 8, hz);
|
||||
status = -EDOM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* we have to cache this and reset in uwire_chipselect as this is a
|
||||
* global parameter and another uwire device can change it under
|
||||
* us */
|
||||
ust->div1_idx = div1_idx;
|
||||
uwire_set_clk1_div(div1_idx);
|
||||
|
||||
rate /= div1;
|
||||
|
||||
switch (div2) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
flags |= UWIRE_FREQ_DIV_2;
|
||||
rate /= 2;
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
flags |= UWIRE_FREQ_DIV_4;
|
||||
rate /= 4;
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
flags |= UWIRE_FREQ_DIV_8;
|
||||
rate /= 8;
|
||||
break;
|
||||
}
|
||||
omap_uwire_configure_mode(spi->chip_select, flags);
|
||||
pr_debug("%s: uwire flags %02x, armxor %lu KHz, SCK %lu KHz\n",
|
||||
__FUNCTION__, flags,
|
||||
clk_get_rate(uwire->ck) / 1000,
|
||||
rate / 1000);
|
||||
status = 0;
|
||||
done:
|
||||
return status;
|
||||
}
|
||||
|
||||
static int uwire_setup(struct spi_device *spi)
|
||||
{
|
||||
struct uwire_state *ust = spi->controller_state;
|
||||
|
||||
if (ust == NULL) {
|
||||
ust = kzalloc(sizeof(*ust), GFP_KERNEL);
|
||||
if (ust == NULL)
|
||||
return -ENOMEM;
|
||||
spi->controller_state = ust;
|
||||
}
|
||||
|
||||
return uwire_setup_transfer(spi, NULL);
|
||||
}
|
||||
|
||||
static void uwire_cleanup(struct spi_device *spi)
|
||||
{
|
||||
kfree(spi->controller_state);
|
||||
}
|
||||
|
||||
static void uwire_off(struct uwire_spi *uwire)
|
||||
{
|
||||
uwire_write_reg(UWIRE_SR3, 0);
|
||||
clk_disable(uwire->ck);
|
||||
clk_put(uwire->ck);
|
||||
spi_master_put(uwire->bitbang.master);
|
||||
}
|
||||
|
||||
static int uwire_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_master *master;
|
||||
struct uwire_spi *uwire;
|
||||
int status;
|
||||
|
||||
master = spi_alloc_master(&pdev->dev, sizeof *uwire);
|
||||
if (!master)
|
||||
return -ENODEV;
|
||||
|
||||
uwire = spi_master_get_devdata(master);
|
||||
dev_set_drvdata(&pdev->dev, uwire);
|
||||
|
||||
uwire->ck = clk_get(&pdev->dev, "armxor_ck");
|
||||
if (!uwire->ck || IS_ERR(uwire->ck)) {
|
||||
dev_dbg(&pdev->dev, "no mpu_xor_clk ?\n");
|
||||
spi_master_put(master);
|
||||
return -ENODEV;
|
||||
}
|
||||
clk_enable(uwire->ck);
|
||||
|
||||
if (cpu_is_omap730())
|
||||
uwire_idx_shift = 1;
|
||||
else
|
||||
uwire_idx_shift = 2;
|
||||
|
||||
uwire_write_reg(UWIRE_SR3, 1);
|
||||
|
||||
master->bus_num = 2; /* "official" */
|
||||
master->num_chipselect = 4;
|
||||
master->setup = uwire_setup;
|
||||
master->cleanup = uwire_cleanup;
|
||||
|
||||
uwire->bitbang.master = master;
|
||||
uwire->bitbang.chipselect = uwire_chipselect;
|
||||
uwire->bitbang.setup_transfer = uwire_setup_transfer;
|
||||
uwire->bitbang.txrx_bufs = uwire_txrx;
|
||||
|
||||
status = spi_bitbang_start(&uwire->bitbang);
|
||||
if (status < 0)
|
||||
uwire_off(uwire);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int uwire_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct uwire_spi *uwire = dev_get_drvdata(&pdev->dev);
|
||||
int status;
|
||||
|
||||
// FIXME remove all child devices, somewhere ...
|
||||
|
||||
status = spi_bitbang_stop(&uwire->bitbang);
|
||||
uwire_off(uwire);
|
||||
return status;
|
||||
}
|
||||
|
||||
static struct platform_driver uwire_driver = {
|
||||
.driver = {
|
||||
.name = "omap_uwire",
|
||||
.bus = &platform_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = uwire_probe,
|
||||
.remove = uwire_remove,
|
||||
// suspend ... unuse ck
|
||||
// resume ... use ck
|
||||
};
|
||||
|
||||
static int __init omap_uwire_init(void)
|
||||
{
|
||||
/* FIXME move these into the relevant board init code. also, include
|
||||
* H3 support; it uses tsc2101 like H2 (on a different chipselect).
|
||||
*/
|
||||
|
||||
if (machine_is_omap_h2()) {
|
||||
/* defaults: W21 SDO, U18 SDI, V19 SCL */
|
||||
omap_cfg_reg(N14_1610_UWIRE_CS0);
|
||||
omap_cfg_reg(N15_1610_UWIRE_CS1);
|
||||
}
|
||||
if (machine_is_omap_perseus2()) {
|
||||
/* configure pins: MPU_UW_nSCS1, MPU_UW_SDO, MPU_UW_SCLK */
|
||||
int val = omap_readl(OMAP730_IO_CONF_9) & ~0x00EEE000;
|
||||
omap_writel(val | 0x00AAA000, OMAP730_IO_CONF_9);
|
||||
}
|
||||
|
||||
return platform_driver_register(&uwire_driver);
|
||||
}
|
||||
|
||||
static void __exit omap_uwire_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&uwire_driver);
|
||||
}
|
||||
|
||||
subsys_initcall(omap_uwire_init);
|
||||
module_exit(omap_uwire_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
1635
drivers/spi/pxa2xx_spi.c
Normal file
1635
drivers/spi/pxa2xx_spi.c
Normal file
File diff suppressed because it is too large
Load Diff
390
drivers/spi/spi-dev.c
Normal file
390
drivers/spi/spi-dev.c
Normal file
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* spi-dev.c - spi-bus driver, char device interface
|
||||
*
|
||||
* Copyright (C) 2006 Samsung Electronics Co. Ltd.
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <asm/dma.h>
|
||||
#include <asm/arch/dma.h>
|
||||
//#include <linux/config.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/smp_lock.h>
|
||||
//#include <linux/devfs_fs_kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include "spi-dev.h"
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#undef debug
|
||||
//#define debug
|
||||
#ifdef debug
|
||||
#define CRDEBUG printk("%s :: %d\n",__FUNCTION__,__LINE__)
|
||||
#else
|
||||
#define CRDEBUG
|
||||
#endif
|
||||
|
||||
//#define pr_debuf printk
|
||||
#define BUFFER_SIZE 65536 //8192 for perf measurement
|
||||
#define SPI_MINORS 2
|
||||
static struct spi_dev *spi_dev_array[SPI_MINORS];
|
||||
static spinlock_t (spi_dev_array_lock);
|
||||
|
||||
static struct spi_dev *attach_to_spi_dev_array(struct spi_dev *spi_dev)
|
||||
{
|
||||
CRDEBUG;
|
||||
spin_lock(&spi_dev_array_lock);
|
||||
|
||||
if (spi_dev_array[spi_dev->minor]) {
|
||||
spin_unlock(&spi_dev_array_lock);
|
||||
dev_err(&spi_dev->dev, "spi-dev already has a device assigned to this adapter\n");
|
||||
goto error;
|
||||
}
|
||||
spi_dev_array[spi_dev->minor] = spi_dev;
|
||||
spin_unlock(&spi_dev_array_lock);
|
||||
return spi_dev;
|
||||
error:
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
static void return_spi_dev(struct spi_dev *spi_dev)
|
||||
{
|
||||
CRDEBUG;
|
||||
spin_lock(&spi_dev_array_lock);
|
||||
spi_dev_array[spi_dev->minor] = NULL;
|
||||
spin_unlock(&spi_dev_array_lock);
|
||||
}
|
||||
|
||||
int spi_attach_spidev(struct spi_dev *spidev)
|
||||
{
|
||||
struct spi_dev *spi_dev;
|
||||
|
||||
printk(KERN_INFO "spi_attach_spidev flags:0x%x\n", spidev->flags);
|
||||
CRDEBUG;
|
||||
spi_dev = attach_to_spi_dev_array(spidev);
|
||||
if (IS_ERR(spi_dev))
|
||||
return PTR_ERR(spi_dev);
|
||||
|
||||
// devfs_mk_cdev(MKDEV(SPI_MAJOR, spi_dev->minor),
|
||||
// S_IFCHR|S_IRUSR|S_IWUSR, "spi/%d", spi_dev->minor);
|
||||
dev_dbg(&spi_dev->dev, "Registered as minor %d\n", spi_dev->minor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spi_detach_spidev(struct spi_dev *spi_dev)
|
||||
{
|
||||
CRDEBUG;
|
||||
// devfs_remove("spi/%d", spi_dev->minor);
|
||||
return_spi_dev(spi_dev);
|
||||
|
||||
dev_dbg(&spi_dev->dev, "Adapter unregistered\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct spi_dev *spi_dev_get_by_minor(unsigned index)
|
||||
{
|
||||
struct spi_dev *spi_dev;
|
||||
|
||||
CRDEBUG;
|
||||
spin_lock(&spi_dev_array_lock);
|
||||
spi_dev = spi_dev_array[index];
|
||||
spin_unlock(&spi_dev_array_lock);
|
||||
return spi_dev;
|
||||
}
|
||||
|
||||
int spi_master_recv(struct spi_dev *spi_dev, char *rbuf ,int count)
|
||||
{
|
||||
struct spi_msg msg;
|
||||
int ret;
|
||||
|
||||
CRDEBUG;
|
||||
if (spi_dev->algo->master_xfer) {
|
||||
msg.flags = spi_dev->flags;
|
||||
msg.len = count;
|
||||
msg.wbuf = NULL;
|
||||
msg.rbuf = rbuf;
|
||||
/*
|
||||
dev_dbg(&spi_dev->dev, "master_recv: reading %d bytes.\n",
|
||||
count);
|
||||
*/
|
||||
down(&spi_dev->bus_lock);
|
||||
ret = spi_dev->algo->master_xfer(spi_dev, &msg, 1);
|
||||
up(&spi_dev->bus_lock);
|
||||
/*
|
||||
dev_dbg(&spi_dev->dev, "master_recv: return:%d (count:%d)\n",
|
||||
ret, count);
|
||||
*/
|
||||
/* if everything went ok (i.e. 1 msg transmitted), return #bytes
|
||||
* transmitted, else error code.
|
||||
*/
|
||||
return (ret == 1 )? count : ret;
|
||||
} else {
|
||||
dev_err(&spi_dev->dev, "SPI level transfers not supported\n");
|
||||
return -ENOSYS;
|
||||
}
|
||||
}
|
||||
|
||||
int spi_master_send(struct spi_dev *spi_dev, char *wbuf, int count)
|
||||
{
|
||||
int ret;
|
||||
struct spi_msg msg;
|
||||
|
||||
CRDEBUG;
|
||||
if (spi_dev->algo->master_xfer) {
|
||||
msg.flags = spi_dev->flags;
|
||||
msg.len = count;
|
||||
msg.wbuf = wbuf;
|
||||
msg.rbuf = NULL;
|
||||
|
||||
/*
|
||||
dev_dbg(&spi_dev->dev, "master_send: writing %d bytes.\n",
|
||||
count);
|
||||
*/
|
||||
down(&spi_dev->bus_lock);
|
||||
ret = spi_dev->algo->master_xfer(spi_dev, &msg, 1);
|
||||
up(&spi_dev->bus_lock);
|
||||
|
||||
/* if everything went ok (i.e. 1 msg transmitted), return #bytes
|
||||
* transmitted, else error code.
|
||||
*/
|
||||
return (ret == 1 ) ? count : ret;
|
||||
} else {
|
||||
dev_err(&spi_dev->dev, "SPI level transfers not supported\n");
|
||||
return -ENOSYS;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t spidev_read (struct file *file, char __user *buf, size_t count,
|
||||
loff_t *offset)
|
||||
{
|
||||
char *tmp;
|
||||
int ret;
|
||||
struct spi_dev *spi_dev = (struct spi_dev *)file->private_data;
|
||||
#ifdef CONFIG_WORD_TRANSIZE
|
||||
count = count * 4;
|
||||
#endif
|
||||
CRDEBUG;
|
||||
if (count > BUFFER_SIZE)
|
||||
count = BUFFER_SIZE;
|
||||
|
||||
if(spi_dev->flags & SPI_M_DMA_MODE){
|
||||
tmp = dma_alloc_coherent(NULL, BUFFER_SIZE,
|
||||
&spi_dev->dmabuf, GFP_KERNEL | GFP_DMA);
|
||||
}else{
|
||||
tmp = kmalloc(count,GFP_KERNEL);
|
||||
}
|
||||
if (tmp==NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
pr_debug("%s: tmp=0x%x dmabuf=0x%x\n",
|
||||
__FUNCTION__,*tmp,spi_dev->dmabuf);
|
||||
pr_debug("spi-dev: spi-%d reading %zd bytes.\n",
|
||||
iminor(file->f_dentry->d_inode), count);
|
||||
|
||||
ret = spi_master_recv(spi_dev,tmp,count);
|
||||
if (ret >= 0)
|
||||
ret = copy_to_user(buf,tmp,count)?-EFAULT:ret;
|
||||
if(spi_dev->flags & SPI_M_DMA_MODE){
|
||||
dma_free_coherent(NULL,BUFFER_SIZE,tmp,spi_dev->dmabuf);
|
||||
}else{
|
||||
kfree(tmp);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t spidev_write (struct file *file, const char __user *buf, size_t count,
|
||||
loff_t *offset)
|
||||
{
|
||||
int ret;
|
||||
char *tmp;
|
||||
struct spi_dev *spi_dev = (struct spi_dev *)file->private_data;
|
||||
#ifdef CONFIG_WORD_TRANSIZE
|
||||
count = count * 4;
|
||||
#endif
|
||||
if (count > BUFFER_SIZE)
|
||||
count = BUFFER_SIZE;
|
||||
|
||||
if(spi_dev->flags & SPI_M_DMA_MODE){
|
||||
tmp = dma_alloc_coherent(NULL, BUFFER_SIZE,
|
||||
&spi_dev->dmabuf, GFP_KERNEL | GFP_DMA);
|
||||
}
|
||||
else{
|
||||
tmp = kmalloc(count,GFP_KERNEL);
|
||||
}
|
||||
|
||||
if (tmp==NULL)
|
||||
return -ENOMEM;
|
||||
pr_debug("%s: tmp=0x%x dmabuf=0x%x\n",
|
||||
__FUNCTION__,*tmp,spi_dev->dmabuf);
|
||||
|
||||
if (copy_from_user(tmp, buf, count)) {
|
||||
if(spi_dev->flags & SPI_M_DMA_MODE){
|
||||
dma_free_coherent(NULL,BUFFER_SIZE,tmp,spi_dev->dmabuf);
|
||||
}else{
|
||||
kfree(tmp);
|
||||
}
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
pr_debug("spi-dev: spi-%d writing %zd bytes.\n",
|
||||
iminor(file->f_dentry->d_inode), count);
|
||||
|
||||
ret = spi_master_send(spi_dev, tmp, count);
|
||||
if(spi_dev->flags & SPI_M_DMA_MODE){
|
||||
dma_free_coherent(NULL,BUFFER_SIZE,tmp,spi_dev->dmabuf);
|
||||
}else{
|
||||
kfree(tmp);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int spidev_ioctl (struct inode *inode, struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct spi_dev *spi_dev = (struct spi_dev *)file->private_data;
|
||||
|
||||
CRDEBUG;
|
||||
dev_dbg(&spi_dev->dev, "spi-%d ioctl, cmd: 0x%x, arg: %lx.\n",
|
||||
iminor(inode),cmd, arg);
|
||||
|
||||
switch ( cmd ) {
|
||||
case SET_SPI_FLAGS:
|
||||
spi_dev->flags = (unsigned int) arg;
|
||||
break;
|
||||
case SET_SPI_RETRIES:
|
||||
spi_dev->retries = arg;
|
||||
break;
|
||||
case SET_SPI_TIMEOUT:
|
||||
spi_dev->timeout = arg;
|
||||
break;
|
||||
default:
|
||||
printk("Invalid ioctl option\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spidev_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
unsigned int minor = iminor(inode);
|
||||
struct spi_dev *spi_dev;
|
||||
|
||||
CRDEBUG;
|
||||
|
||||
spi_dev = spi_dev_get_by_minor(minor);
|
||||
if (!spi_dev)
|
||||
return -ENODEV;
|
||||
|
||||
/* registered with adapter, passed as client to user */
|
||||
file->private_data = spi_dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_CPU_S3C6400) || defined(CONFIG_CPU_S3C6410)
|
||||
int spi_master_close(struct spi_dev *spi_dev)
|
||||
{
|
||||
int ret;
|
||||
CRDEBUG;
|
||||
ret = spi_dev->algo->close(spi_dev);
|
||||
return 0;
|
||||
}
|
||||
static int spidev_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct spi_dev *spi_dev = (struct spi_dev *)file->private_data;
|
||||
int ret;
|
||||
CRDEBUG;
|
||||
|
||||
ret = spi_master_close(spi_dev);
|
||||
file->private_data = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
|
||||
static int spidev_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
CRDEBUG;
|
||||
file->private_data = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
static struct file_operations spidev_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.read = spidev_read,
|
||||
.write = spidev_write,
|
||||
.ioctl = spidev_ioctl,
|
||||
.open = spidev_open,
|
||||
.release = spidev_release,
|
||||
};
|
||||
|
||||
//static int __init spi_dev_init(void)
|
||||
static int spi_dev_init(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
printk(KERN_INFO "spi /dev entries driver\n");
|
||||
|
||||
#if defined(CONFIG_CPU_S3C6400) || defined(CONFIG_CPU_S3C6410)
|
||||
#if(SPI_CHANNEL==0)
|
||||
res = register_chrdev(SPI_MAJOR, "spi0", &spidev_fops);
|
||||
#else
|
||||
res = register_chrdev(SPI_MAJOR, "spi1", &spidev_fops);
|
||||
#endif
|
||||
#elif defined CONFIG_HS_SPI_S3C2443
|
||||
res = register_chrdev(SPI_MAJOR, "spi0", &spidev_fops);
|
||||
#else
|
||||
res = register_chrdev(SPI_MAJOR, "spi0", &spidev_fops);
|
||||
#endif
|
||||
if (res)
|
||||
goto out;
|
||||
|
||||
// devfs_mk_dir("spi");
|
||||
return 0;
|
||||
|
||||
out:
|
||||
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
|
||||
return res;
|
||||
}
|
||||
|
||||
//static void __exit spi_dev_exit(void)
|
||||
static void spi_dev_exit(void)
|
||||
{
|
||||
CRDEBUG;
|
||||
// devfs_remove("spi");
|
||||
unregister_chrdev(SPI_MAJOR,"spi");
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Samsung Electronics");
|
||||
MODULE_DESCRIPTION("spi /dev entries driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(spi_dev_init);
|
||||
module_exit(spi_dev_exit);
|
||||
|
||||
EXPORT_SYMBOL(spi_attach_spidev);
|
||||
EXPORT_SYMBOL(spi_detach_spidev);
|
||||
|
||||
//written by vivek
|
||||
EXPORT_SYMBOL(spi_master_send);
|
||||
EXPORT_SYMBOL(spi_master_recv);
|
||||
EXPORT_SYMBOL(spi_dev_get_by_minor);
|
||||
|
||||
141
drivers/spi/spi-dev.h
Normal file
141
drivers/spi/spi-dev.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* */
|
||||
/* spi.h - definitions for the spi-bus interface */
|
||||
/* */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Copyright (C) 2006 Samsung Electronics Co. ltd.
|
||||
|
||||
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., 675 Mass Ave, Cambridge, MA 02139, USA. */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
#ifndef _LINUX_SPI_H
|
||||
#define _LINUX_SPI_H
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h> /* for struct device */
|
||||
#include <asm/semaphore.h>
|
||||
|
||||
/* --- General options ------------------------------------------------ */
|
||||
|
||||
struct spi_msg;
|
||||
struct spi_dev;
|
||||
struct spi_algorithm;
|
||||
|
||||
/*
|
||||
* The master routines are the ones normally used to transmit data to devices
|
||||
* on a bus (or/and read from them). Apart from these basic transfer functions
|
||||
* to transmit one message at a time, a more complex version can be used to
|
||||
* transmit an arbitrary number of messages without interruption.
|
||||
*/
|
||||
extern int spi_master_send(struct spi_dev *,char* ,int);
|
||||
extern int spi_master_recv(struct spi_dev *,char* ,int);
|
||||
|
||||
#define SPI_CHANNEL 0
|
||||
|
||||
/*
|
||||
* A driver is capable of handling one or more physical devices present on
|
||||
* SPI adapters. This information is used to inform the driver of adapter
|
||||
* events.
|
||||
*/
|
||||
|
||||
struct spi_dev {
|
||||
int minor;
|
||||
/* vivek, 2009-04-13 16:55 Notes: separating dmabufr and dmabufw for gspi_io*/
|
||||
|
||||
dma_addr_t dmabuf; /* handle for DMA transfer */
|
||||
dma_addr_t dmabufr; /* handle for DMA read transfer */
|
||||
dma_addr_t dmabufw; /* handle for DMA write transfer */
|
||||
unsigned int flags; /* flags for the SPI operation */
|
||||
struct semaphore bus_lock; /* semaphore for bus access */
|
||||
|
||||
struct spi_algorithm *algo; /* the algorithm to access the bus */
|
||||
void *algo_data; /* the bus control struct */
|
||||
|
||||
int timeout;
|
||||
int retries;
|
||||
struct device dev; /* the adapter device */
|
||||
};
|
||||
|
||||
/*
|
||||
* The following structs are for those who like to implement new bus drivers:
|
||||
* spi_algorithm is the interface to a class of hardware solutions which can
|
||||
* be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
|
||||
* to name two of the most common.
|
||||
*/
|
||||
struct spi_algorithm {
|
||||
char name[32]; /* textual description */
|
||||
unsigned int id;
|
||||
|
||||
/* If an adapter algorithm can't to SPI-level access, set master_xfer
|
||||
to NULL. If an adapter algorithm can do SMBus access, set
|
||||
smbus_xfer. If set to NULL, the SMBus protocol is simulated
|
||||
using common SPI messages */
|
||||
int (*master_xfer)(struct spi_dev *spi_dev,struct spi_msg *msgs,
|
||||
int num);
|
||||
|
||||
/* --- ioctl like call to set div. parameters. */
|
||||
int (*algo_control)(struct spi_dev *, unsigned int, unsigned long);
|
||||
|
||||
/* To determine what the adapter supports */
|
||||
u32 (*functionality) (struct spi_dev *);
|
||||
int (*close)(struct spi_dev *spi_dev);
|
||||
};
|
||||
|
||||
/* ----- functions exported by spi.o */
|
||||
|
||||
/* administration...
|
||||
*/
|
||||
extern int spi_attach_spidev(struct spi_dev *);
|
||||
extern int spi_detach_spidev(struct spi_dev *);
|
||||
|
||||
/*
|
||||
* SPI Message - used for pure spi transaction, also from /dev interface
|
||||
*/
|
||||
|
||||
#define SPI_M_MODE_MASTER 0x001
|
||||
#define SPI_M_MODE_SLAVE 0x002
|
||||
#define SPI_M_USE_FIFO 0x004
|
||||
#define SPI_M_CPOL_ACTHIGH 0x010
|
||||
#define SPI_M_CPOL_ACTLOW 0x020
|
||||
#define SPI_M_CPHA_FORMATA 0x040
|
||||
#define SPI_M_CPHA_FORMATB 0x080
|
||||
#define SPI_M_DMA_MODE 0x100
|
||||
#define SPI_M_INT_MODE 0x200
|
||||
#define SPI_M_POLL_MODE 0x400
|
||||
#define SPI_M_DEBUG 0x800
|
||||
#define SPI_M_FIFO_POLL 0x1000
|
||||
|
||||
|
||||
struct spi_msg {
|
||||
__u16 flags;
|
||||
__u16 len; /* msg length */
|
||||
__u8 *wbuf; /* pointer to msg data to write */
|
||||
__u8 *rbuf; /* pointer to msg data for read */
|
||||
};
|
||||
|
||||
/* ----- commands for the ioctl call: */
|
||||
/* -> spi-adapter specific ioctls */
|
||||
#define SET_SPI_RETRIES 0x0701 /* number of times a device address */
|
||||
/* should be polled when not */
|
||||
/* acknowledging */
|
||||
#define SET_SPI_TIMEOUT 0x0702 /* set timeout - call with int */
|
||||
#define SET_SPI_FLAGS 0x0704 /* set flags for h/w settings */
|
||||
|
||||
#define SPI_MAJOR 153 /* Device major number */
|
||||
/* minor 0-15 spi0 - spi15 */
|
||||
|
||||
#endif /* _LINUX_SPI_H */
|
||||
|
||||
646
drivers/spi/spi.c
Normal file
646
drivers/spi/spi.c
Normal file
@@ -0,0 +1,646 @@
|
||||
/*
|
||||
* spi.c - SPI init/core code
|
||||
*
|
||||
* Copyright (C) 2005 David Brownell
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/autoconf.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/cache.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
|
||||
/* SPI bustype and spi_master class are registered after board init code
|
||||
* provides the SPI device tables, ensuring that both are present by the
|
||||
* time controller driver registration causes spi_devices to "enumerate".
|
||||
*/
|
||||
static void spidev_release(struct device *dev)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
|
||||
/* spi masters may cleanup for released devices */
|
||||
if (spi->master->cleanup)
|
||||
spi->master->cleanup(spi);
|
||||
|
||||
spi_master_put(spi->master);
|
||||
kfree(dev);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
modalias_show(struct device *dev, struct device_attribute *a, char *buf)
|
||||
{
|
||||
const struct spi_device *spi = to_spi_device(dev);
|
||||
|
||||
return snprintf(buf, BUS_ID_SIZE + 1, "%s\n", spi->modalias);
|
||||
}
|
||||
|
||||
static struct device_attribute spi_dev_attrs[] = {
|
||||
__ATTR_RO(modalias),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/* modalias support makes "modprobe $MODALIAS" new-style hotplug work,
|
||||
* and the sysfs version makes coldplug work too.
|
||||
*/
|
||||
|
||||
static int spi_match_device(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
const struct spi_device *spi = to_spi_device(dev);
|
||||
|
||||
return strncmp(spi->modalias, drv->name, BUS_ID_SIZE) == 0;
|
||||
}
|
||||
|
||||
static int spi_uevent(struct device *dev, char **envp, int num_envp,
|
||||
char *buffer, int buffer_size)
|
||||
{
|
||||
const struct spi_device *spi = to_spi_device(dev);
|
||||
|
||||
envp[0] = buffer;
|
||||
snprintf(buffer, buffer_size, "MODALIAS=%s", spi->modalias);
|
||||
envp[1] = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/*
|
||||
* NOTE: the suspend() method for an spi_master controller driver
|
||||
* should verify that all its child devices are marked as suspended;
|
||||
* suspend requests delivered through sysfs power/state files don't
|
||||
* enforce such constraints.
|
||||
*/
|
||||
static int spi_suspend(struct device *dev, pm_message_t message)
|
||||
{
|
||||
int value;
|
||||
struct spi_driver *drv = to_spi_driver(dev->driver);
|
||||
|
||||
if (!drv || !drv->suspend)
|
||||
return 0;
|
||||
|
||||
/* suspend will stop irqs and dma; no more i/o */
|
||||
value = drv->suspend(to_spi_device(dev), message);
|
||||
if (value == 0)
|
||||
dev->power.power_state = message;
|
||||
return value;
|
||||
}
|
||||
|
||||
static int spi_resume(struct device *dev)
|
||||
{
|
||||
int value;
|
||||
struct spi_driver *drv = to_spi_driver(dev->driver);
|
||||
|
||||
if (!drv || !drv->resume)
|
||||
return 0;
|
||||
|
||||
/* resume may restart the i/o queue */
|
||||
value = drv->resume(to_spi_device(dev));
|
||||
if (value == 0)
|
||||
dev->power.power_state = PMSG_ON;
|
||||
return value;
|
||||
}
|
||||
|
||||
#else
|
||||
#define spi_suspend NULL
|
||||
#define spi_resume NULL
|
||||
#endif
|
||||
|
||||
struct bus_type spi_bus_type = {
|
||||
.name = "spi",
|
||||
.dev_attrs = spi_dev_attrs,
|
||||
.match = spi_match_device,
|
||||
.uevent = spi_uevent,
|
||||
.suspend = spi_suspend,
|
||||
.resume = spi_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(spi_bus_type);
|
||||
|
||||
|
||||
static int spi_drv_probe(struct device *dev)
|
||||
{
|
||||
const struct spi_driver *sdrv = to_spi_driver(dev->driver);
|
||||
|
||||
return sdrv->probe(to_spi_device(dev));
|
||||
}
|
||||
|
||||
static int spi_drv_remove(struct device *dev)
|
||||
{
|
||||
const struct spi_driver *sdrv = to_spi_driver(dev->driver);
|
||||
|
||||
return sdrv->remove(to_spi_device(dev));
|
||||
}
|
||||
|
||||
static void spi_drv_shutdown(struct device *dev)
|
||||
{
|
||||
const struct spi_driver *sdrv = to_spi_driver(dev->driver);
|
||||
|
||||
sdrv->shutdown(to_spi_device(dev));
|
||||
}
|
||||
|
||||
int spi_register_driver(struct spi_driver *sdrv)
|
||||
{
|
||||
sdrv->driver.bus = &spi_bus_type;
|
||||
if (sdrv->probe)
|
||||
sdrv->driver.probe = spi_drv_probe;
|
||||
if (sdrv->remove)
|
||||
sdrv->driver.remove = spi_drv_remove;
|
||||
if (sdrv->shutdown)
|
||||
sdrv->driver.shutdown = spi_drv_shutdown;
|
||||
return driver_register(&sdrv->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_register_driver);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* SPI devices should normally not be created by SPI device drivers; that
|
||||
* would make them board-specific. Similarly with SPI master drivers.
|
||||
* Device registration normally goes into like arch/.../mach.../board-YYY.c
|
||||
* with other readonly (flashable) information about mainboard devices.
|
||||
*/
|
||||
|
||||
struct boardinfo {
|
||||
struct list_head list;
|
||||
unsigned n_board_info;
|
||||
struct spi_board_info board_info[0];
|
||||
};
|
||||
|
||||
static LIST_HEAD(board_list);
|
||||
static DECLARE_MUTEX(board_lock);
|
||||
|
||||
|
||||
/* On typical mainboards, this is purely internal; and it's not needed
|
||||
* after board init creates the hard-wired devices. Some development
|
||||
* platforms may not be able to use spi_register_board_info though, and
|
||||
* this is exported so that for example a USB or parport based adapter
|
||||
* driver could add devices (which it would learn about out-of-band).
|
||||
*/
|
||||
struct spi_device *spi_new_device(struct spi_master *master,
|
||||
struct spi_board_info *chip)
|
||||
{
|
||||
struct spi_device *proxy;
|
||||
struct device *dev = master->cdev.dev;
|
||||
int status;
|
||||
|
||||
/* NOTE: caller did any chip->bus_num checks necessary */
|
||||
|
||||
if (!spi_master_get(master))
|
||||
return NULL;
|
||||
|
||||
proxy = kzalloc(sizeof *proxy, GFP_KERNEL);
|
||||
if (!proxy) {
|
||||
dev_err(dev, "can't alloc dev for cs%d\n",
|
||||
chip->chip_select);
|
||||
goto fail;
|
||||
}
|
||||
proxy->master = master;
|
||||
proxy->chip_select = chip->chip_select;
|
||||
proxy->max_speed_hz = chip->max_speed_hz;
|
||||
proxy->mode = chip->mode;
|
||||
proxy->irq = chip->irq;
|
||||
proxy->modalias = chip->modalias;
|
||||
|
||||
snprintf(proxy->dev.bus_id, sizeof proxy->dev.bus_id,
|
||||
"%s.%u", master->cdev.class_id,
|
||||
chip->chip_select);
|
||||
proxy->dev.parent = dev;
|
||||
proxy->dev.bus = &spi_bus_type;
|
||||
proxy->dev.platform_data = (void *) chip->platform_data;
|
||||
proxy->controller_data = chip->controller_data;
|
||||
proxy->controller_state = NULL;
|
||||
proxy->dev.release = spidev_release;
|
||||
|
||||
/* drivers may modify this default i/o setup */
|
||||
status = master->setup(proxy);
|
||||
if (status < 0) {
|
||||
dev_dbg(dev, "can't %s %s, status %d\n",
|
||||
"setup", proxy->dev.bus_id, status);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* driver core catches callers that misbehave by defining
|
||||
* devices that already exist.
|
||||
*/
|
||||
status = device_register(&proxy->dev);
|
||||
if (status < 0) {
|
||||
dev_dbg(dev, "can't %s %s, status %d\n",
|
||||
"add", proxy->dev.bus_id, status);
|
||||
goto fail;
|
||||
}
|
||||
dev_dbg(dev, "registered child %s\n", proxy->dev.bus_id);
|
||||
return proxy;
|
||||
|
||||
fail:
|
||||
spi_master_put(master);
|
||||
kfree(proxy);
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_new_device);
|
||||
|
||||
/*
|
||||
* Board-specific early init code calls this (probably during arch_initcall)
|
||||
* with segments of the SPI device table. Any device nodes are created later,
|
||||
* after the relevant parent SPI controller (bus_num) is defined. We keep
|
||||
* this table of devices forever, so that reloading a controller driver will
|
||||
* not make Linux forget about these hard-wired devices.
|
||||
*
|
||||
* Other code can also call this, e.g. a particular add-on board might provide
|
||||
* SPI devices through its expansion connector, so code initializing that board
|
||||
* would naturally declare its SPI devices.
|
||||
*
|
||||
* The board info passed can safely be __initdata ... but be careful of
|
||||
* any embedded pointers (platform_data, etc), they're copied as-is.
|
||||
*/
|
||||
int __init
|
||||
spi_register_board_info(struct spi_board_info const *info, unsigned n)
|
||||
{
|
||||
struct boardinfo *bi;
|
||||
|
||||
bi = kmalloc(sizeof(*bi) + n * sizeof *info, GFP_KERNEL);
|
||||
if (!bi)
|
||||
return -ENOMEM;
|
||||
bi->n_board_info = n;
|
||||
memcpy(bi->board_info, info, n * sizeof *info);
|
||||
|
||||
down(&board_lock);
|
||||
list_add_tail(&bi->list, &board_list);
|
||||
up(&board_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FIXME someone should add support for a __setup("spi", ...) that
|
||||
* creates board info from kernel command lines
|
||||
*/
|
||||
|
||||
static void __init_or_module
|
||||
scan_boardinfo(struct spi_master *master)
|
||||
{
|
||||
struct boardinfo *bi;
|
||||
struct device *dev = master->cdev.dev;
|
||||
|
||||
down(&board_lock);
|
||||
list_for_each_entry(bi, &board_list, list) {
|
||||
struct spi_board_info *chip = bi->board_info;
|
||||
unsigned n;
|
||||
|
||||
for (n = bi->n_board_info; n > 0; n--, chip++) {
|
||||
if (chip->bus_num != master->bus_num)
|
||||
continue;
|
||||
/* some controllers only have one chip, so they
|
||||
* might not use chipselects. otherwise, the
|
||||
* chipselects are numbered 0..max.
|
||||
*/
|
||||
if (chip->chip_select >= master->num_chipselect
|
||||
&& master->num_chipselect) {
|
||||
dev_dbg(dev, "cs%d > max %d\n",
|
||||
chip->chip_select,
|
||||
master->num_chipselect);
|
||||
continue;
|
||||
}
|
||||
(void) spi_new_device(master, chip);
|
||||
}
|
||||
}
|
||||
up(&board_lock);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void spi_master_release(struct class_device *cdev)
|
||||
{
|
||||
struct spi_master *master;
|
||||
|
||||
master = container_of(cdev, struct spi_master, cdev);
|
||||
kfree(master);
|
||||
}
|
||||
|
||||
static struct class spi_master_class = {
|
||||
.name = "spi_master",
|
||||
.owner = THIS_MODULE,
|
||||
.release = spi_master_release,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* spi_alloc_master - allocate SPI master controller
|
||||
* @dev: the controller, possibly using the platform_bus
|
||||
* @size: how much driver-private data to preallocate; the pointer to this
|
||||
* memory is in the class_data field of the returned class_device,
|
||||
* accessible with spi_master_get_devdata().
|
||||
*
|
||||
* This call is used only by SPI master controller drivers, which are the
|
||||
* only ones directly touching chip registers. It's how they allocate
|
||||
* an spi_master structure, prior to calling spi_register_master().
|
||||
*
|
||||
* This must be called from context that can sleep. It returns the SPI
|
||||
* master structure on success, else NULL.
|
||||
*
|
||||
* The caller is responsible for assigning the bus number and initializing
|
||||
* the master's methods before calling spi_register_master(); and (after errors
|
||||
* adding the device) calling spi_master_put() to prevent a memory leak.
|
||||
*/
|
||||
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
|
||||
{
|
||||
struct spi_master *master;
|
||||
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
master = kzalloc(size + sizeof *master, GFP_KERNEL);
|
||||
if (!master)
|
||||
return NULL;
|
||||
|
||||
class_device_initialize(&master->cdev);
|
||||
master->cdev.class = &spi_master_class;
|
||||
master->cdev.dev = get_device(dev);
|
||||
spi_master_set_devdata(master, &master[1]);
|
||||
|
||||
return master;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_alloc_master);
|
||||
|
||||
/**
|
||||
* spi_register_master - register SPI master controller
|
||||
* @master: initialized master, originally from spi_alloc_master()
|
||||
*
|
||||
* SPI master controllers connect to their drivers using some non-SPI bus,
|
||||
* such as the platform bus. The final stage of probe() in that code
|
||||
* includes calling spi_register_master() to hook up to this SPI bus glue.
|
||||
*
|
||||
* SPI controllers use board specific (often SOC specific) bus numbers,
|
||||
* and board-specific addressing for SPI devices combines those numbers
|
||||
* with chip select numbers. Since SPI does not directly support dynamic
|
||||
* device identification, boards need configuration tables telling which
|
||||
* chip is at which address.
|
||||
*
|
||||
* This must be called from context that can sleep. It returns zero on
|
||||
* success, else a negative error code (dropping the master's refcount).
|
||||
* After a successful return, the caller is responsible for calling
|
||||
* spi_unregister_master().
|
||||
*/
|
||||
int spi_register_master(struct spi_master *master)
|
||||
{
|
||||
static atomic_t dyn_bus_id = ATOMIC_INIT((1<<16) - 1);
|
||||
struct device *dev = master->cdev.dev;
|
||||
int status = -ENODEV;
|
||||
int dynamic = 0;
|
||||
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
/* convention: dynamically assigned bus IDs count down from the max */
|
||||
if (master->bus_num < 0) {
|
||||
master->bus_num = atomic_dec_return(&dyn_bus_id);
|
||||
dynamic = 1;
|
||||
}
|
||||
|
||||
/* register the device, then userspace will see it.
|
||||
* registration fails if the bus ID is in use.
|
||||
*/
|
||||
snprintf(master->cdev.class_id, sizeof master->cdev.class_id,
|
||||
"spi%u", master->bus_num);
|
||||
status = class_device_add(&master->cdev);
|
||||
if (status < 0)
|
||||
goto done;
|
||||
dev_dbg(dev, "registered master %s%s\n", master->cdev.class_id,
|
||||
dynamic ? " (dynamic)" : "");
|
||||
|
||||
/* populate children from any spi device tables */
|
||||
scan_boardinfo(master);
|
||||
status = 0;
|
||||
done:
|
||||
return status;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_register_master);
|
||||
|
||||
|
||||
static int __unregister(struct device *dev, void *unused)
|
||||
{
|
||||
/* note: before about 2.6.14-rc1 this would corrupt memory: */
|
||||
spi_unregister_device(to_spi_device(dev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_unregister_master - unregister SPI master controller
|
||||
* @master: the master being unregistered
|
||||
*
|
||||
* This call is used only by SPI master controller drivers, which are the
|
||||
* only ones directly touching chip registers.
|
||||
*
|
||||
* This must be called from context that can sleep.
|
||||
*/
|
||||
void spi_unregister_master(struct spi_master *master)
|
||||
{
|
||||
int dummy;
|
||||
|
||||
dummy = device_for_each_child(master->cdev.dev, NULL, __unregister);
|
||||
class_device_unregister(&master->cdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_unregister_master);
|
||||
|
||||
/**
|
||||
* spi_busnum_to_master - look up master associated with bus_num
|
||||
* @bus_num: the master's bus number
|
||||
*
|
||||
* This call may be used with devices that are registered after
|
||||
* arch init time. It returns a refcounted pointer to the relevant
|
||||
* spi_master (which the caller must release), or NULL if there is
|
||||
* no such master registered.
|
||||
*/
|
||||
struct spi_master *spi_busnum_to_master(u16 bus_num)
|
||||
{
|
||||
struct class_device *cdev;
|
||||
struct spi_master *master = NULL;
|
||||
struct spi_master *m;
|
||||
|
||||
down(&spi_master_class.sem);
|
||||
list_for_each_entry(cdev, &spi_master_class.children, node) {
|
||||
m = container_of(cdev, struct spi_master, cdev);
|
||||
if (m->bus_num == bus_num) {
|
||||
master = spi_master_get(m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
up(&spi_master_class.sem);
|
||||
return master;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_busnum_to_master);
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void spi_complete(void *arg)
|
||||
{
|
||||
complete(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_sync - blocking/synchronous SPI data transfers
|
||||
* @spi: device with which data will be exchanged
|
||||
* @message: describes the data transfers
|
||||
*
|
||||
* This call may only be used from a context that may sleep. The sleep
|
||||
* is non-interruptible, and has no timeout. Low-overhead controller
|
||||
* drivers may DMA directly into and out of the message buffers.
|
||||
*
|
||||
* Note that the SPI device's chip select is active during the message,
|
||||
* and then is normally disabled between messages. Drivers for some
|
||||
* frequently-used devices may want to minimize costs of selecting a chip,
|
||||
* by leaving it selected in anticipation that the next message will go
|
||||
* to the same chip. (That may increase power usage.)
|
||||
*
|
||||
* Also, the caller is guaranteeing that the memory associated with the
|
||||
* message will not be freed before this call returns.
|
||||
*
|
||||
* The return value is a negative error code if the message could not be
|
||||
* submitted, else zero. When the value is zero, then message->status is
|
||||
* also defined: it's the completion code for the transfer, either zero
|
||||
* or a negative error code from the controller driver.
|
||||
*/
|
||||
int spi_sync(struct spi_device *spi, struct spi_message *message)
|
||||
{
|
||||
DECLARE_COMPLETION_ONSTACK(done);
|
||||
int status;
|
||||
|
||||
message->complete = spi_complete;
|
||||
message->context = &done;
|
||||
status = spi_async(spi, message);
|
||||
if (status == 0)
|
||||
wait_for_completion(&done);
|
||||
message->context = NULL;
|
||||
return status;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_sync);
|
||||
|
||||
/* portable code must never pass more than 32 bytes */
|
||||
#define SPI_BUFSIZ max(32,SMP_CACHE_BYTES)
|
||||
|
||||
static u8 *buf;
|
||||
|
||||
/**
|
||||
* spi_write_then_read - SPI synchronous write followed by read
|
||||
* @spi: device with which data will be exchanged
|
||||
* @txbuf: data to be written (need not be dma-safe)
|
||||
* @n_tx: size of txbuf, in bytes
|
||||
* @rxbuf: buffer into which data will be read
|
||||
* @n_rx: size of rxbuf, in bytes (need not be dma-safe)
|
||||
*
|
||||
* This performs a half duplex MicroWire style transaction with the
|
||||
* device, sending txbuf and then reading rxbuf. The return value
|
||||
* is zero for success, else a negative errno status code.
|
||||
* This call may only be used from a context that may sleep.
|
||||
*
|
||||
* Parameters to this routine are always copied using a small buffer;
|
||||
* performance-sensitive or bulk transfer code should instead use
|
||||
* spi_{async,sync}() calls with dma-safe buffers.
|
||||
*/
|
||||
int spi_write_then_read(struct spi_device *spi,
|
||||
const u8 *txbuf, unsigned n_tx,
|
||||
u8 *rxbuf, unsigned n_rx)
|
||||
{
|
||||
static DECLARE_MUTEX(lock);
|
||||
|
||||
int status;
|
||||
struct spi_message message;
|
||||
struct spi_transfer x[2];
|
||||
u8 *local_buf;
|
||||
|
||||
/* Use preallocated DMA-safe buffer. We can't avoid copying here,
|
||||
* (as a pure convenience thing), but we can keep heap costs
|
||||
* out of the hot path ...
|
||||
*/
|
||||
if ((n_tx + n_rx) > SPI_BUFSIZ)
|
||||
return -EINVAL;
|
||||
|
||||
spi_message_init(&message);
|
||||
memset(x, 0, sizeof x);
|
||||
if (n_tx) {
|
||||
x[0].len = n_tx;
|
||||
spi_message_add_tail(&x[0], &message);
|
||||
}
|
||||
if (n_rx) {
|
||||
x[1].len = n_rx;
|
||||
spi_message_add_tail(&x[1], &message);
|
||||
}
|
||||
|
||||
/* ... unless someone else is using the pre-allocated buffer */
|
||||
if (down_trylock(&lock)) {
|
||||
local_buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
|
||||
if (!local_buf)
|
||||
return -ENOMEM;
|
||||
} else
|
||||
local_buf = buf;
|
||||
|
||||
memcpy(local_buf, txbuf, n_tx);
|
||||
x[0].tx_buf = local_buf;
|
||||
x[1].rx_buf = local_buf + n_tx;
|
||||
|
||||
/* do the i/o */
|
||||
status = spi_sync(spi, &message);
|
||||
if (status == 0) {
|
||||
memcpy(rxbuf, x[1].rx_buf, n_rx);
|
||||
status = message.status;
|
||||
}
|
||||
|
||||
if (x[0].tx_buf == buf)
|
||||
up(&lock);
|
||||
else
|
||||
kfree(local_buf);
|
||||
|
||||
return status;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_write_then_read);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __init spi_init(void)
|
||||
{
|
||||
int status;
|
||||
|
||||
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
status = -ENOMEM;
|
||||
goto err0;
|
||||
}
|
||||
|
||||
status = bus_register(&spi_bus_type);
|
||||
if (status < 0)
|
||||
goto err1;
|
||||
|
||||
status = class_register(&spi_master_class);
|
||||
if (status < 0)
|
||||
goto err2;
|
||||
return 0;
|
||||
|
||||
err2:
|
||||
bus_unregister(&spi_bus_type);
|
||||
err1:
|
||||
kfree(buf);
|
||||
buf = NULL;
|
||||
err0:
|
||||
return status;
|
||||
}
|
||||
|
||||
/* board_info is normally registered in arch_initcall(),
|
||||
* but even essential drivers wait till later
|
||||
*
|
||||
* REVISIT only boardinfo really needs static linking. the rest (device and
|
||||
* driver registration) _could_ be dynamically linked (modular) ... costs
|
||||
* include needing to have boardinfo data structures be much more public.
|
||||
*/
|
||||
subsys_initcall(spi_init);
|
||||
|
||||
515
drivers/spi/spi_bitbang.c
Normal file
515
drivers/spi/spi_bitbang.c
Normal file
@@ -0,0 +1,515 @@
|
||||
/*
|
||||
* spi_bitbang.c - polling/bitbanging SPI master controller driver utilities
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi_bitbang.h>
|
||||
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* FIRST PART (OPTIONAL): word-at-a-time spi_transfer support.
|
||||
* Use this for GPIO or shift-register level hardware APIs.
|
||||
*
|
||||
* spi_bitbang_cs is in spi_device->controller_state, which is unavailable
|
||||
* to glue code. These bitbang setup() and cleanup() routines are always
|
||||
* used, though maybe they're called from controller-aware code.
|
||||
*
|
||||
* chipselect() and friends may use use spi_device->controller_data and
|
||||
* controller registers as appropriate.
|
||||
*
|
||||
*
|
||||
* NOTE: SPI controller pins can often be used as GPIO pins instead,
|
||||
* which means you could use a bitbang driver either to get hardware
|
||||
* working quickly, or testing for differences that aren't speed related.
|
||||
*/
|
||||
|
||||
struct spi_bitbang_cs {
|
||||
unsigned nsecs; /* (clock cycle time)/2 */
|
||||
u32 (*txrx_word)(struct spi_device *spi, unsigned nsecs,
|
||||
u32 word, u8 bits);
|
||||
unsigned (*txrx_bufs)(struct spi_device *,
|
||||
u32 (*txrx_word)(
|
||||
struct spi_device *spi,
|
||||
unsigned nsecs,
|
||||
u32 word, u8 bits),
|
||||
unsigned, struct spi_transfer *);
|
||||
};
|
||||
|
||||
static unsigned bitbang_txrx_8(
|
||||
struct spi_device *spi,
|
||||
u32 (*txrx_word)(struct spi_device *spi,
|
||||
unsigned nsecs,
|
||||
u32 word, u8 bits),
|
||||
unsigned ns,
|
||||
struct spi_transfer *t
|
||||
) {
|
||||
unsigned bits = spi->bits_per_word;
|
||||
unsigned count = t->len;
|
||||
const u8 *tx = t->tx_buf;
|
||||
u8 *rx = t->rx_buf;
|
||||
|
||||
while (likely(count > 0)) {
|
||||
u8 word = 0;
|
||||
|
||||
if (tx)
|
||||
word = *tx++;
|
||||
word = txrx_word(spi, ns, word, bits);
|
||||
if (rx)
|
||||
*rx++ = word;
|
||||
count -= 1;
|
||||
}
|
||||
return t->len - count;
|
||||
}
|
||||
|
||||
static unsigned bitbang_txrx_16(
|
||||
struct spi_device *spi,
|
||||
u32 (*txrx_word)(struct spi_device *spi,
|
||||
unsigned nsecs,
|
||||
u32 word, u8 bits),
|
||||
unsigned ns,
|
||||
struct spi_transfer *t
|
||||
) {
|
||||
unsigned bits = spi->bits_per_word;
|
||||
unsigned count = t->len;
|
||||
const u16 *tx = t->tx_buf;
|
||||
u16 *rx = t->rx_buf;
|
||||
|
||||
while (likely(count > 1)) {
|
||||
u16 word = 0;
|
||||
|
||||
if (tx)
|
||||
word = *tx++;
|
||||
word = txrx_word(spi, ns, word, bits);
|
||||
if (rx)
|
||||
*rx++ = word;
|
||||
count -= 2;
|
||||
}
|
||||
return t->len - count;
|
||||
}
|
||||
|
||||
static unsigned bitbang_txrx_32(
|
||||
struct spi_device *spi,
|
||||
u32 (*txrx_word)(struct spi_device *spi,
|
||||
unsigned nsecs,
|
||||
u32 word, u8 bits),
|
||||
unsigned ns,
|
||||
struct spi_transfer *t
|
||||
) {
|
||||
unsigned bits = spi->bits_per_word;
|
||||
unsigned count = t->len;
|
||||
const u32 *tx = t->tx_buf;
|
||||
u32 *rx = t->rx_buf;
|
||||
|
||||
while (likely(count > 3)) {
|
||||
u32 word = 0;
|
||||
|
||||
if (tx)
|
||||
word = *tx++;
|
||||
word = txrx_word(spi, ns, word, bits);
|
||||
if (rx)
|
||||
*rx++ = word;
|
||||
count -= 4;
|
||||
}
|
||||
return t->len - count;
|
||||
}
|
||||
|
||||
int spi_bitbang_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
|
||||
{
|
||||
struct spi_bitbang_cs *cs = spi->controller_state;
|
||||
u8 bits_per_word;
|
||||
u32 hz;
|
||||
|
||||
if (t) {
|
||||
bits_per_word = t->bits_per_word;
|
||||
hz = t->speed_hz;
|
||||
} else {
|
||||
bits_per_word = 0;
|
||||
hz = 0;
|
||||
}
|
||||
|
||||
/* spi_transfer level calls that work per-word */
|
||||
if (!bits_per_word)
|
||||
bits_per_word = spi->bits_per_word;
|
||||
if (bits_per_word <= 8)
|
||||
cs->txrx_bufs = bitbang_txrx_8;
|
||||
else if (bits_per_word <= 16)
|
||||
cs->txrx_bufs = bitbang_txrx_16;
|
||||
else if (bits_per_word <= 32)
|
||||
cs->txrx_bufs = bitbang_txrx_32;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
/* nsecs = (clock period)/2 */
|
||||
if (!hz)
|
||||
hz = spi->max_speed_hz;
|
||||
if (hz) {
|
||||
cs->nsecs = (1000000000/2) / hz;
|
||||
if (cs->nsecs > (MAX_UDELAY_MS * 1000 * 1000))
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_bitbang_setup_transfer);
|
||||
|
||||
/**
|
||||
* spi_bitbang_setup - default setup for per-word I/O loops
|
||||
*/
|
||||
int spi_bitbang_setup(struct spi_device *spi)
|
||||
{
|
||||
struct spi_bitbang_cs *cs = spi->controller_state;
|
||||
struct spi_bitbang *bitbang;
|
||||
int retval;
|
||||
|
||||
bitbang = spi_master_get_devdata(spi->master);
|
||||
|
||||
/* REVISIT: some systems will want to support devices using lsb-first
|
||||
* bit encodings on the wire. In pure software that would be trivial,
|
||||
* just bitbang_txrx_le_cphaX() routines shifting the other way, and
|
||||
* some hardware controllers also have this support.
|
||||
*/
|
||||
if ((spi->mode & SPI_LSB_FIRST) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!cs) {
|
||||
cs = kzalloc(sizeof *cs, GFP_KERNEL);
|
||||
if (!cs)
|
||||
return -ENOMEM;
|
||||
spi->controller_state = cs;
|
||||
}
|
||||
|
||||
if (!spi->bits_per_word)
|
||||
spi->bits_per_word = 8;
|
||||
|
||||
/* per-word shift register access, in hardware or bitbanging */
|
||||
cs->txrx_word = bitbang->txrx_word[spi->mode & (SPI_CPOL|SPI_CPHA)];
|
||||
if (!cs->txrx_word)
|
||||
return -EINVAL;
|
||||
|
||||
retval = bitbang->setup_transfer(spi, NULL);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec/bit\n",
|
||||
__FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA),
|
||||
spi->bits_per_word, 2 * cs->nsecs);
|
||||
|
||||
/* NOTE we _need_ to call chipselect() early, ideally with adapter
|
||||
* setup, unless the hardware defaults cooperate to avoid confusion
|
||||
* between normal (active low) and inverted chipselects.
|
||||
*/
|
||||
|
||||
/* deselect chip (low or high) */
|
||||
spin_lock(&bitbang->lock);
|
||||
if (!bitbang->busy) {
|
||||
bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
|
||||
ndelay(cs->nsecs);
|
||||
}
|
||||
spin_unlock(&bitbang->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_bitbang_setup);
|
||||
|
||||
/**
|
||||
* spi_bitbang_cleanup - default cleanup for per-word I/O loops
|
||||
*/
|
||||
void spi_bitbang_cleanup(struct spi_device *spi)
|
||||
{
|
||||
kfree(spi->controller_state);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_bitbang_cleanup);
|
||||
|
||||
static int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t)
|
||||
{
|
||||
struct spi_bitbang_cs *cs = spi->controller_state;
|
||||
unsigned nsecs = cs->nsecs;
|
||||
|
||||
return cs->txrx_bufs(spi, cs->txrx_word, nsecs, t);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* SECOND PART ... simple transfer queue runner.
|
||||
*
|
||||
* This costs a task context per controller, running the queue by
|
||||
* performing each transfer in sequence. Smarter hardware can queue
|
||||
* several DMA transfers at once, and process several controller queues
|
||||
* in parallel; this driver doesn't match such hardware very well.
|
||||
*
|
||||
* Drivers can provide word-at-a-time i/o primitives, or provide
|
||||
* transfer-at-a-time ones to leverage dma or fifo hardware.
|
||||
*/
|
||||
static void bitbang_work(struct work_struct *work)
|
||||
{
|
||||
struct spi_bitbang *bitbang =
|
||||
container_of(work, struct spi_bitbang, work);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&bitbang->lock, flags);
|
||||
bitbang->busy = 1;
|
||||
while (!list_empty(&bitbang->queue)) {
|
||||
struct spi_message *m;
|
||||
struct spi_device *spi;
|
||||
unsigned nsecs;
|
||||
struct spi_transfer *t = NULL;
|
||||
unsigned tmp;
|
||||
unsigned cs_change;
|
||||
int status;
|
||||
int (*setup_transfer)(struct spi_device *,
|
||||
struct spi_transfer *);
|
||||
|
||||
m = container_of(bitbang->queue.next, struct spi_message,
|
||||
queue);
|
||||
list_del_init(&m->queue);
|
||||
spin_unlock_irqrestore(&bitbang->lock, flags);
|
||||
|
||||
/* FIXME this is made-up ... the correct value is known to
|
||||
* word-at-a-time bitbang code, and presumably chipselect()
|
||||
* should enforce these requirements too?
|
||||
*/
|
||||
nsecs = 100;
|
||||
|
||||
spi = m->spi;
|
||||
tmp = 0;
|
||||
cs_change = 1;
|
||||
status = 0;
|
||||
setup_transfer = NULL;
|
||||
|
||||
list_for_each_entry (t, &m->transfers, transfer_list) {
|
||||
|
||||
/* override or restore speed and wordsize */
|
||||
if (t->speed_hz || t->bits_per_word) {
|
||||
setup_transfer = bitbang->setup_transfer;
|
||||
if (!setup_transfer) {
|
||||
status = -ENOPROTOOPT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (setup_transfer) {
|
||||
status = setup_transfer(spi, t);
|
||||
if (status < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* set up default clock polarity, and activate chip;
|
||||
* this implicitly updates clock and spi modes as
|
||||
* previously recorded for this device via setup().
|
||||
* (and also deselects any other chip that might be
|
||||
* selected ...)
|
||||
*/
|
||||
if (cs_change) {
|
||||
bitbang->chipselect(spi, BITBANG_CS_ACTIVE);
|
||||
ndelay(nsecs);
|
||||
}
|
||||
cs_change = t->cs_change;
|
||||
if (!t->tx_buf && !t->rx_buf && t->len) {
|
||||
status = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* transfer data. the lower level code handles any
|
||||
* new dma mappings it needs. our caller always gave
|
||||
* us dma-safe buffers.
|
||||
*/
|
||||
if (t->len) {
|
||||
/* REVISIT dma API still needs a designated
|
||||
* DMA_ADDR_INVALID; ~0 might be better.
|
||||
*/
|
||||
if (!m->is_dma_mapped)
|
||||
t->rx_dma = t->tx_dma = 0;
|
||||
status = bitbang->txrx_bufs(spi, t);
|
||||
}
|
||||
if (status != t->len) {
|
||||
if (status > 0)
|
||||
status = -EMSGSIZE;
|
||||
break;
|
||||
}
|
||||
m->actual_length += status;
|
||||
status = 0;
|
||||
|
||||
/* protocol tweaks before next transfer */
|
||||
if (t->delay_usecs)
|
||||
udelay(t->delay_usecs);
|
||||
|
||||
if (!cs_change)
|
||||
continue;
|
||||
if (t->transfer_list.next == &m->transfers)
|
||||
break;
|
||||
|
||||
/* sometimes a short mid-message deselect of the chip
|
||||
* may be needed to terminate a mode or command
|
||||
*/
|
||||
ndelay(nsecs);
|
||||
bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
|
||||
ndelay(nsecs);
|
||||
}
|
||||
|
||||
m->status = status;
|
||||
m->complete(m->context);
|
||||
|
||||
/* restore speed and wordsize */
|
||||
if (setup_transfer)
|
||||
setup_transfer(spi, NULL);
|
||||
|
||||
/* normally deactivate chipselect ... unless no error and
|
||||
* cs_change has hinted that the next message will probably
|
||||
* be for this chip too.
|
||||
*/
|
||||
if (!(status == 0 && cs_change)) {
|
||||
ndelay(nsecs);
|
||||
bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
|
||||
ndelay(nsecs);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&bitbang->lock, flags);
|
||||
}
|
||||
bitbang->busy = 0;
|
||||
spin_unlock_irqrestore(&bitbang->lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_bitbang_transfer - default submit to transfer queue
|
||||
*/
|
||||
int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m)
|
||||
{
|
||||
struct spi_bitbang *bitbang;
|
||||
unsigned long flags;
|
||||
int status = 0;
|
||||
|
||||
m->actual_length = 0;
|
||||
m->status = -EINPROGRESS;
|
||||
|
||||
bitbang = spi_master_get_devdata(spi->master);
|
||||
|
||||
spin_lock_irqsave(&bitbang->lock, flags);
|
||||
if (!spi->max_speed_hz)
|
||||
status = -ENETDOWN;
|
||||
else {
|
||||
list_add_tail(&m->queue, &bitbang->queue);
|
||||
queue_work(bitbang->workqueue, &bitbang->work);
|
||||
}
|
||||
spin_unlock_irqrestore(&bitbang->lock, flags);
|
||||
|
||||
return status;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_bitbang_transfer);
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* spi_bitbang_start - start up a polled/bitbanging SPI master driver
|
||||
* @bitbang: driver handle
|
||||
*
|
||||
* Caller should have zero-initialized all parts of the structure, and then
|
||||
* provided callbacks for chip selection and I/O loops. If the master has
|
||||
* a transfer method, its final step should call spi_bitbang_transfer; or,
|
||||
* that's the default if the transfer routine is not initialized. It should
|
||||
* also set up the bus number and number of chipselects.
|
||||
*
|
||||
* For i/o loops, provide callbacks either per-word (for bitbanging, or for
|
||||
* hardware that basically exposes a shift register) or per-spi_transfer
|
||||
* (which takes better advantage of hardware like fifos or DMA engines).
|
||||
*
|
||||
* Drivers using per-word I/O loops should use (or call) spi_bitbang_setup,
|
||||
* spi_bitbang_cleanup and spi_bitbang_setup_transfer to handle those spi
|
||||
* master methods. Those methods are the defaults if the bitbang->txrx_bufs
|
||||
* routine isn't initialized.
|
||||
*
|
||||
* This routine registers the spi_master, which will process requests in a
|
||||
* dedicated task, keeping IRQs unblocked most of the time. To stop
|
||||
* processing those requests, call spi_bitbang_stop().
|
||||
*/
|
||||
int spi_bitbang_start(struct spi_bitbang *bitbang)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (!bitbang->master || !bitbang->chipselect)
|
||||
return -EINVAL;
|
||||
|
||||
INIT_WORK(&bitbang->work, bitbang_work);
|
||||
spin_lock_init(&bitbang->lock);
|
||||
INIT_LIST_HEAD(&bitbang->queue);
|
||||
|
||||
if (!bitbang->master->transfer)
|
||||
bitbang->master->transfer = spi_bitbang_transfer;
|
||||
if (!bitbang->txrx_bufs) {
|
||||
bitbang->use_dma = 0;
|
||||
bitbang->txrx_bufs = spi_bitbang_bufs;
|
||||
if (!bitbang->master->setup) {
|
||||
if (!bitbang->setup_transfer)
|
||||
bitbang->setup_transfer =
|
||||
spi_bitbang_setup_transfer;
|
||||
bitbang->master->setup = spi_bitbang_setup;
|
||||
bitbang->master->cleanup = spi_bitbang_cleanup;
|
||||
}
|
||||
} else if (!bitbang->master->setup)
|
||||
return -EINVAL;
|
||||
|
||||
/* this task is the only thing to touch the SPI bits */
|
||||
bitbang->busy = 0;
|
||||
bitbang->workqueue = create_singlethread_workqueue(
|
||||
bitbang->master->cdev.dev->bus_id);
|
||||
if (bitbang->workqueue == NULL) {
|
||||
status = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
/* driver may get busy before register() returns, especially
|
||||
* if someone registered boardinfo for devices
|
||||
*/
|
||||
status = spi_register_master(bitbang->master);
|
||||
if (status < 0)
|
||||
goto err2;
|
||||
|
||||
return status;
|
||||
|
||||
err2:
|
||||
destroy_workqueue(bitbang->workqueue);
|
||||
err1:
|
||||
return status;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_bitbang_start);
|
||||
|
||||
/**
|
||||
* spi_bitbang_stop - stops the task providing spi communication
|
||||
*/
|
||||
int spi_bitbang_stop(struct spi_bitbang *bitbang)
|
||||
{
|
||||
spi_unregister_master(bitbang->master);
|
||||
|
||||
WARN_ON(!list_empty(&bitbang->queue));
|
||||
|
||||
destroy_workqueue(bitbang->workqueue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_bitbang_stop);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
424
drivers/spi/spi_butterfly.c
Normal file
424
drivers/spi/spi_butterfly.c
Normal file
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* spi_butterfly.c - parport-to-butterfly adapter
|
||||
*
|
||||
* Copyright (C) 2005 David Brownell
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/parport.h>
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi_bitbang.h>
|
||||
#include <linux/spi/flash.h>
|
||||
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
|
||||
/*
|
||||
* This uses SPI to talk with an "AVR Butterfly", which is a $US20 card
|
||||
* with a battery powered AVR microcontroller and lots of goodies. You
|
||||
* can use GCC to develop firmware for this.
|
||||
*
|
||||
* See Documentation/spi/butterfly for information about how to build
|
||||
* and use this custom parallel port cable.
|
||||
*/
|
||||
|
||||
#undef HAVE_USI /* nyet */
|
||||
|
||||
|
||||
/* DATA output bits (pins 2..9 == D0..D7) */
|
||||
#define butterfly_nreset (1 << 1) /* pin 3 */
|
||||
|
||||
#define spi_sck_bit (1 << 0) /* pin 2 */
|
||||
#define spi_mosi_bit (1 << 7) /* pin 9 */
|
||||
|
||||
#define usi_sck_bit (1 << 3) /* pin 5 */
|
||||
#define usi_mosi_bit (1 << 4) /* pin 6 */
|
||||
|
||||
#define vcc_bits ((1 << 6) | (1 << 5)) /* pins 7, 8 */
|
||||
|
||||
/* STATUS input bits */
|
||||
#define spi_miso_bit PARPORT_STATUS_BUSY /* pin 11 */
|
||||
|
||||
#define usi_miso_bit PARPORT_STATUS_PAPEROUT /* pin 12 */
|
||||
|
||||
/* CONTROL output bits */
|
||||
#define spi_cs_bit PARPORT_CONTROL_SELECT /* pin 17 */
|
||||
/* USI uses no chipselect */
|
||||
|
||||
|
||||
|
||||
static inline struct butterfly *spidev_to_pp(struct spi_device *spi)
|
||||
{
|
||||
return spi->controller_data;
|
||||
}
|
||||
|
||||
static inline int is_usidev(struct spi_device *spi)
|
||||
{
|
||||
#ifdef HAVE_USI
|
||||
return spi->chip_select != 1;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
struct butterfly {
|
||||
/* REVISIT ... for now, this must be first */
|
||||
struct spi_bitbang bitbang;
|
||||
|
||||
struct parport *port;
|
||||
struct pardevice *pd;
|
||||
|
||||
u8 lastbyte;
|
||||
|
||||
struct spi_device *dataflash;
|
||||
struct spi_device *butterfly;
|
||||
struct spi_board_info info[2];
|
||||
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* these routines may be slower than necessary because they're hiding
|
||||
* the fact that there are two different SPI busses on this cable: one
|
||||
* to the DataFlash chip (or AVR SPI controller), the other to the
|
||||
* AVR USI controller.
|
||||
*/
|
||||
|
||||
static inline void
|
||||
setsck(struct spi_device *spi, int is_on)
|
||||
{
|
||||
struct butterfly *pp = spidev_to_pp(spi);
|
||||
u8 bit, byte = pp->lastbyte;
|
||||
|
||||
if (is_usidev(spi))
|
||||
bit = usi_sck_bit;
|
||||
else
|
||||
bit = spi_sck_bit;
|
||||
|
||||
if (is_on)
|
||||
byte |= bit;
|
||||
else
|
||||
byte &= ~bit;
|
||||
parport_write_data(pp->port, byte);
|
||||
pp->lastbyte = byte;
|
||||
}
|
||||
|
||||
static inline void
|
||||
setmosi(struct spi_device *spi, int is_on)
|
||||
{
|
||||
struct butterfly *pp = spidev_to_pp(spi);
|
||||
u8 bit, byte = pp->lastbyte;
|
||||
|
||||
if (is_usidev(spi))
|
||||
bit = usi_mosi_bit;
|
||||
else
|
||||
bit = spi_mosi_bit;
|
||||
|
||||
if (is_on)
|
||||
byte |= bit;
|
||||
else
|
||||
byte &= ~bit;
|
||||
parport_write_data(pp->port, byte);
|
||||
pp->lastbyte = byte;
|
||||
}
|
||||
|
||||
static inline int getmiso(struct spi_device *spi)
|
||||
{
|
||||
struct butterfly *pp = spidev_to_pp(spi);
|
||||
int value;
|
||||
u8 bit;
|
||||
|
||||
if (is_usidev(spi))
|
||||
bit = usi_miso_bit;
|
||||
else
|
||||
bit = spi_miso_bit;
|
||||
|
||||
/* only STATUS_BUSY is NOT negated */
|
||||
value = !(parport_read_status(pp->port) & bit);
|
||||
return (bit == PARPORT_STATUS_BUSY) ? value : !value;
|
||||
}
|
||||
|
||||
static void butterfly_chipselect(struct spi_device *spi, int value)
|
||||
{
|
||||
struct butterfly *pp = spidev_to_pp(spi);
|
||||
|
||||
/* set default clock polarity */
|
||||
if (value != BITBANG_CS_INACTIVE)
|
||||
setsck(spi, spi->mode & SPI_CPOL);
|
||||
|
||||
/* no chipselect on this USI link config */
|
||||
if (is_usidev(spi))
|
||||
return;
|
||||
|
||||
/* here, value == "activate or not";
|
||||
* most PARPORT_CONTROL_* bits are negated, so we must
|
||||
* morph it to value == "bit value to write in control register"
|
||||
*/
|
||||
if (spi_cs_bit == PARPORT_CONTROL_INIT)
|
||||
value = !value;
|
||||
|
||||
parport_frob_control(pp->port, spi_cs_bit, value ? spi_cs_bit : 0);
|
||||
}
|
||||
|
||||
|
||||
/* we only needed to implement one mode here, and choose SPI_MODE_0 */
|
||||
|
||||
#define spidelay(X) do{}while(0)
|
||||
//#define spidelay ndelay
|
||||
|
||||
#define EXPAND_BITBANG_TXRX
|
||||
#include <linux/spi/spi_bitbang.h>
|
||||
|
||||
static u32
|
||||
butterfly_txrx_word_mode0(struct spi_device *spi,
|
||||
unsigned nsecs,
|
||||
u32 word, u8 bits)
|
||||
{
|
||||
return bitbang_txrx_be_cpha0(spi, nsecs, 0, word, bits);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/* override default partitioning with cmdlinepart */
|
||||
static struct mtd_partition partitions[] = { {
|
||||
/* JFFS2 wants partitions of 4*N blocks for this device,
|
||||
* so sectors 0 and 1 can't be partitions by themselves.
|
||||
*/
|
||||
|
||||
/* sector 0 = 8 pages * 264 bytes/page (1 block)
|
||||
* sector 1 = 248 pages * 264 bytes/page
|
||||
*/
|
||||
.name = "bookkeeping", // 66 KB
|
||||
.offset = 0,
|
||||
.size = (8 + 248) * 264,
|
||||
// .mask_flags = MTD_WRITEABLE,
|
||||
}, {
|
||||
/* sector 2 = 256 pages * 264 bytes/page
|
||||
* sectors 3-5 = 512 pages * 264 bytes/page
|
||||
*/
|
||||
.name = "filesystem", // 462 KB
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = MTDPART_SIZ_FULL,
|
||||
} };
|
||||
|
||||
static struct flash_platform_data flash = {
|
||||
.name = "butterflash",
|
||||
.parts = partitions,
|
||||
.nr_parts = ARRAY_SIZE(partitions),
|
||||
};
|
||||
|
||||
|
||||
/* REVISIT remove this ugly global and its "only one" limitation */
|
||||
static struct butterfly *butterfly;
|
||||
|
||||
static void butterfly_attach(struct parport *p)
|
||||
{
|
||||
struct pardevice *pd;
|
||||
int status;
|
||||
struct butterfly *pp;
|
||||
struct spi_master *master;
|
||||
struct platform_device *pdev;
|
||||
|
||||
if (butterfly)
|
||||
return;
|
||||
|
||||
/* REVISIT: this just _assumes_ a butterfly is there ... no probe,
|
||||
* and no way to be selective about what it binds to.
|
||||
*/
|
||||
|
||||
/* FIXME where should master->cdev.dev come from?
|
||||
* e.g. /sys/bus/pnp0/00:0b, some PCI thing, etc
|
||||
* setting up a platform device like this is an ugly kluge...
|
||||
*/
|
||||
pdev = platform_device_register_simple("butterfly", -1, NULL, 0);
|
||||
if (IS_ERR(pdev))
|
||||
return;
|
||||
|
||||
master = spi_alloc_master(&pdev->dev, sizeof *pp);
|
||||
if (!master) {
|
||||
status = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
pp = spi_master_get_devdata(master);
|
||||
|
||||
/*
|
||||
* SPI and bitbang hookup
|
||||
*
|
||||
* use default setup(), cleanup(), and transfer() methods; and
|
||||
* only bother implementing mode 0. Start it later.
|
||||
*/
|
||||
master->bus_num = 42;
|
||||
master->num_chipselect = 2;
|
||||
|
||||
pp->bitbang.master = spi_master_get(master);
|
||||
pp->bitbang.chipselect = butterfly_chipselect;
|
||||
pp->bitbang.txrx_word[SPI_MODE_0] = butterfly_txrx_word_mode0;
|
||||
|
||||
/*
|
||||
* parport hookup
|
||||
*/
|
||||
pp->port = p;
|
||||
pd = parport_register_device(p, "spi_butterfly",
|
||||
NULL, NULL, NULL,
|
||||
0 /* FLAGS */, pp);
|
||||
if (!pd) {
|
||||
status = -ENOMEM;
|
||||
goto clean0;
|
||||
}
|
||||
pp->pd = pd;
|
||||
|
||||
status = parport_claim(pd);
|
||||
if (status < 0)
|
||||
goto clean1;
|
||||
|
||||
/*
|
||||
* Butterfly reset, powerup, run firmware
|
||||
*/
|
||||
pr_debug("%s: powerup/reset Butterfly\n", p->name);
|
||||
|
||||
/* nCS for dataflash (this bit is inverted on output) */
|
||||
parport_frob_control(pp->port, spi_cs_bit, 0);
|
||||
|
||||
/* stabilize power with chip in reset (nRESET), and
|
||||
* both spi_sck_bit and usi_sck_bit clear (CPOL=0)
|
||||
*/
|
||||
pp->lastbyte |= vcc_bits;
|
||||
parport_write_data(pp->port, pp->lastbyte);
|
||||
msleep(5);
|
||||
|
||||
/* take it out of reset; assume long reset delay */
|
||||
pp->lastbyte |= butterfly_nreset;
|
||||
parport_write_data(pp->port, pp->lastbyte);
|
||||
msleep(100);
|
||||
|
||||
|
||||
/*
|
||||
* Start SPI ... for now, hide that we're two physical busses.
|
||||
*/
|
||||
status = spi_bitbang_start(&pp->bitbang);
|
||||
if (status < 0)
|
||||
goto clean2;
|
||||
|
||||
/* Bus 1 lets us talk to at45db041b (firmware disables AVR SPI), AVR
|
||||
* (firmware resets at45, acts as spi slave) or neither (we ignore
|
||||
* both, AVR uses AT45). Here we expect firmware for the first option.
|
||||
*/
|
||||
|
||||
pp->info[0].max_speed_hz = 15 * 1000 * 1000;
|
||||
strcpy(pp->info[0].modalias, "mtd_dataflash");
|
||||
pp->info[0].platform_data = &flash;
|
||||
pp->info[0].chip_select = 1;
|
||||
pp->info[0].controller_data = pp;
|
||||
pp->dataflash = spi_new_device(pp->bitbang.master, &pp->info[0]);
|
||||
if (pp->dataflash)
|
||||
pr_debug("%s: dataflash at %s\n", p->name,
|
||||
pp->dataflash->dev.bus_id);
|
||||
|
||||
#ifdef HAVE_USI
|
||||
/* Bus 2 is only for talking to the AVR, and it can work no
|
||||
* matter who masters bus 1; needs appropriate AVR firmware.
|
||||
*/
|
||||
pp->info[1].max_speed_hz = 10 /* ?? */ * 1000 * 1000;
|
||||
strcpy(pp->info[1].modalias, "butterfly");
|
||||
// pp->info[1].platform_data = ... TBD ... ;
|
||||
pp->info[1].chip_select = 2,
|
||||
pp->info[1].controller_data = pp;
|
||||
pp->butterfly = spi_new_device(pp->bitbang.master, &pp->info[1]);
|
||||
if (pp->butterfly)
|
||||
pr_debug("%s: butterfly at %s\n", p->name,
|
||||
pp->butterfly->dev.bus_id);
|
||||
|
||||
/* FIXME setup ACK for the IRQ line ... */
|
||||
#endif
|
||||
|
||||
// dev_info(_what?_, ...)
|
||||
pr_info("%s: AVR Butterfly\n", p->name);
|
||||
butterfly = pp;
|
||||
return;
|
||||
|
||||
clean2:
|
||||
/* turn off VCC */
|
||||
parport_write_data(pp->port, 0);
|
||||
|
||||
parport_release(pp->pd);
|
||||
clean1:
|
||||
parport_unregister_device(pd);
|
||||
clean0:
|
||||
(void) spi_master_put(pp->bitbang.master);
|
||||
done:
|
||||
platform_device_unregister(pdev);
|
||||
pr_debug("%s: butterfly probe, fail %d\n", p->name, status);
|
||||
}
|
||||
|
||||
static void butterfly_detach(struct parport *p)
|
||||
{
|
||||
struct butterfly *pp;
|
||||
struct platform_device *pdev;
|
||||
int status;
|
||||
|
||||
/* FIXME this global is ugly ... but, how to quickly get from
|
||||
* the parport to the "struct butterfly" associated with it?
|
||||
* "old school" driver-internal device lists?
|
||||
*/
|
||||
if (!butterfly || butterfly->port != p)
|
||||
return;
|
||||
pp = butterfly;
|
||||
butterfly = NULL;
|
||||
|
||||
/* stop() unregisters child devices too */
|
||||
pdev = to_platform_device(pp->bitbang.master->cdev.dev);
|
||||
status = spi_bitbang_stop(&pp->bitbang);
|
||||
|
||||
/* turn off VCC */
|
||||
parport_write_data(pp->port, 0);
|
||||
msleep(10);
|
||||
|
||||
parport_release(pp->pd);
|
||||
parport_unregister_device(pp->pd);
|
||||
|
||||
(void) spi_master_put(pp->bitbang.master);
|
||||
|
||||
platform_device_unregister(pdev);
|
||||
}
|
||||
|
||||
static struct parport_driver butterfly_driver = {
|
||||
.name = "spi_butterfly",
|
||||
.attach = butterfly_attach,
|
||||
.detach = butterfly_detach,
|
||||
};
|
||||
|
||||
|
||||
static int __init butterfly_init(void)
|
||||
{
|
||||
return parport_register_driver(&butterfly_driver);
|
||||
}
|
||||
device_initcall(butterfly_init);
|
||||
|
||||
static void __exit butterfly_exit(void)
|
||||
{
|
||||
parport_unregister_driver(&butterfly_driver);
|
||||
}
|
||||
module_exit(butterfly_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Parport Adapter driver for AVR Butterfly");
|
||||
MODULE_LICENSE("GPL");
|
||||
1768
drivers/spi/spi_imx.c
Normal file
1768
drivers/spi/spi_imx.c
Normal file
File diff suppressed because it is too large
Load Diff
484
drivers/spi/spi_mpc83xx.c
Normal file
484
drivers/spi/spi_mpc83xx.c
Normal file
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* MPC83xx SPI controller driver.
|
||||
*
|
||||
* Maintainer: Kumar Gala
|
||||
*
|
||||
* Copyright (C) 2006 Polycom, Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi_bitbang.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/fsl_devices.h>
|
||||
|
||||
#include <asm/irq.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
/* SPI Controller registers */
|
||||
struct mpc83xx_spi_reg {
|
||||
u8 res1[0x20];
|
||||
__be32 mode;
|
||||
__be32 event;
|
||||
__be32 mask;
|
||||
__be32 command;
|
||||
__be32 transmit;
|
||||
__be32 receive;
|
||||
};
|
||||
|
||||
/* SPI Controller mode register definitions */
|
||||
#define SPMODE_CI_INACTIVEHIGH (1 << 29)
|
||||
#define SPMODE_CP_BEGIN_EDGECLK (1 << 28)
|
||||
#define SPMODE_DIV16 (1 << 27)
|
||||
#define SPMODE_REV (1 << 26)
|
||||
#define SPMODE_MS (1 << 25)
|
||||
#define SPMODE_ENABLE (1 << 24)
|
||||
#define SPMODE_LEN(x) ((x) << 20)
|
||||
#define SPMODE_PM(x) ((x) << 16)
|
||||
|
||||
/*
|
||||
* Default for SPI Mode:
|
||||
* SPI MODE 0 (inactive low, phase middle, MSB, 8-bit length, slow clk
|
||||
*/
|
||||
#define SPMODE_INIT_VAL (SPMODE_CI_INACTIVEHIGH | SPMODE_DIV16 | SPMODE_REV | \
|
||||
SPMODE_MS | SPMODE_LEN(7) | SPMODE_PM(0xf))
|
||||
|
||||
/* SPIE register values */
|
||||
#define SPIE_NE 0x00000200 /* Not empty */
|
||||
#define SPIE_NF 0x00000100 /* Not full */
|
||||
|
||||
/* SPIM register values */
|
||||
#define SPIM_NE 0x00000200 /* Not empty */
|
||||
#define SPIM_NF 0x00000100 /* Not full */
|
||||
|
||||
/* SPI Controller driver's private data. */
|
||||
struct mpc83xx_spi {
|
||||
/* bitbang has to be first */
|
||||
struct spi_bitbang bitbang;
|
||||
struct completion done;
|
||||
|
||||
struct mpc83xx_spi_reg __iomem *base;
|
||||
|
||||
/* rx & tx bufs from the spi_transfer */
|
||||
const void *tx;
|
||||
void *rx;
|
||||
|
||||
/* functions to deal with different sized buffers */
|
||||
void (*get_rx) (u32 rx_data, struct mpc83xx_spi *);
|
||||
u32(*get_tx) (struct mpc83xx_spi *);
|
||||
|
||||
unsigned int count;
|
||||
u32 irq;
|
||||
|
||||
unsigned nsecs; /* (clock cycle time)/2 */
|
||||
|
||||
u32 sysclk;
|
||||
void (*activate_cs) (u8 cs, u8 polarity);
|
||||
void (*deactivate_cs) (u8 cs, u8 polarity);
|
||||
};
|
||||
|
||||
static inline void mpc83xx_spi_write_reg(__be32 __iomem * reg, u32 val)
|
||||
{
|
||||
out_be32(reg, val);
|
||||
}
|
||||
|
||||
static inline u32 mpc83xx_spi_read_reg(__be32 __iomem * reg)
|
||||
{
|
||||
return in_be32(reg);
|
||||
}
|
||||
|
||||
#define MPC83XX_SPI_RX_BUF(type) \
|
||||
void mpc83xx_spi_rx_buf_##type(u32 data, struct mpc83xx_spi *mpc83xx_spi) \
|
||||
{ \
|
||||
type * rx = mpc83xx_spi->rx; \
|
||||
*rx++ = (type)data; \
|
||||
mpc83xx_spi->rx = rx; \
|
||||
}
|
||||
|
||||
#define MPC83XX_SPI_TX_BUF(type) \
|
||||
u32 mpc83xx_spi_tx_buf_##type(struct mpc83xx_spi *mpc83xx_spi) \
|
||||
{ \
|
||||
u32 data; \
|
||||
const type * tx = mpc83xx_spi->tx; \
|
||||
if (!tx) \
|
||||
return 0; \
|
||||
data = *tx++; \
|
||||
mpc83xx_spi->tx = tx; \
|
||||
return data; \
|
||||
}
|
||||
|
||||
MPC83XX_SPI_RX_BUF(u8)
|
||||
MPC83XX_SPI_RX_BUF(u16)
|
||||
MPC83XX_SPI_RX_BUF(u32)
|
||||
MPC83XX_SPI_TX_BUF(u8)
|
||||
MPC83XX_SPI_TX_BUF(u16)
|
||||
MPC83XX_SPI_TX_BUF(u32)
|
||||
|
||||
static void mpc83xx_spi_chipselect(struct spi_device *spi, int value)
|
||||
{
|
||||
struct mpc83xx_spi *mpc83xx_spi;
|
||||
u8 pol = spi->mode & SPI_CS_HIGH ? 1 : 0;
|
||||
|
||||
mpc83xx_spi = spi_master_get_devdata(spi->master);
|
||||
|
||||
if (value == BITBANG_CS_INACTIVE) {
|
||||
if (mpc83xx_spi->deactivate_cs)
|
||||
mpc83xx_spi->deactivate_cs(spi->chip_select, pol);
|
||||
}
|
||||
|
||||
if (value == BITBANG_CS_ACTIVE) {
|
||||
u32 regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
|
||||
u32 len = spi->bits_per_word;
|
||||
if (len == 32)
|
||||
len = 0;
|
||||
else
|
||||
len = len - 1;
|
||||
|
||||
/* mask out bits we are going to set */
|
||||
regval &= ~0x38ff0000;
|
||||
|
||||
if (spi->mode & SPI_CPHA)
|
||||
regval |= SPMODE_CP_BEGIN_EDGECLK;
|
||||
if (spi->mode & SPI_CPOL)
|
||||
regval |= SPMODE_CI_INACTIVEHIGH;
|
||||
|
||||
regval |= SPMODE_LEN(len);
|
||||
|
||||
if ((mpc83xx_spi->sysclk / spi->max_speed_hz) >= 64) {
|
||||
u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 64);
|
||||
regval |= SPMODE_PM(pm) | SPMODE_DIV16;
|
||||
} else {
|
||||
u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 4);
|
||||
regval |= SPMODE_PM(pm);
|
||||
}
|
||||
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
|
||||
if (mpc83xx_spi->activate_cs)
|
||||
mpc83xx_spi->activate_cs(spi->chip_select, pol);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
|
||||
{
|
||||
struct mpc83xx_spi *mpc83xx_spi;
|
||||
u32 regval;
|
||||
u8 bits_per_word;
|
||||
u32 hz;
|
||||
|
||||
mpc83xx_spi = spi_master_get_devdata(spi->master);
|
||||
|
||||
if (t) {
|
||||
bits_per_word = t->bits_per_word;
|
||||
hz = t->speed_hz;
|
||||
} else {
|
||||
bits_per_word = 0;
|
||||
hz = 0;
|
||||
}
|
||||
|
||||
/* spi_transfer level calls that work per-word */
|
||||
if (!bits_per_word)
|
||||
bits_per_word = spi->bits_per_word;
|
||||
|
||||
/* Make sure its a bit width we support [4..16, 32] */
|
||||
if ((bits_per_word < 4)
|
||||
|| ((bits_per_word > 16) && (bits_per_word != 32)))
|
||||
return -EINVAL;
|
||||
|
||||
if (bits_per_word <= 8) {
|
||||
mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
|
||||
mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
|
||||
} else if (bits_per_word <= 16) {
|
||||
mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u16;
|
||||
mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u16;
|
||||
} else if (bits_per_word <= 32) {
|
||||
mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u32;
|
||||
mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u32;
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
/* nsecs = (clock period)/2 */
|
||||
if (!hz)
|
||||
hz = spi->max_speed_hz;
|
||||
mpc83xx_spi->nsecs = (1000000000 / 2) / hz;
|
||||
if (mpc83xx_spi->nsecs > MAX_UDELAY_MS * 1000)
|
||||
return -EINVAL;
|
||||
|
||||
if (bits_per_word == 32)
|
||||
bits_per_word = 0;
|
||||
else
|
||||
bits_per_word = bits_per_word - 1;
|
||||
|
||||
regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
|
||||
|
||||
/* Mask out bits_per_wordgth */
|
||||
regval &= 0xff0fffff;
|
||||
regval |= SPMODE_LEN(bits_per_word);
|
||||
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpc83xx_spi_setup(struct spi_device *spi)
|
||||
{
|
||||
struct spi_bitbang *bitbang;
|
||||
struct mpc83xx_spi *mpc83xx_spi;
|
||||
int retval;
|
||||
|
||||
if (!spi->max_speed_hz)
|
||||
return -EINVAL;
|
||||
|
||||
bitbang = spi_master_get_devdata(spi->master);
|
||||
mpc83xx_spi = spi_master_get_devdata(spi->master);
|
||||
|
||||
if (!spi->bits_per_word)
|
||||
spi->bits_per_word = 8;
|
||||
|
||||
retval = mpc83xx_spi_setup_transfer(spi, NULL);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec\n",
|
||||
__FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA),
|
||||
spi->bits_per_word, 2 * mpc83xx_spi->nsecs);
|
||||
|
||||
/* NOTE we _need_ to call chipselect() early, ideally with adapter
|
||||
* setup, unless the hardware defaults cooperate to avoid confusion
|
||||
* between normal (active low) and inverted chipselects.
|
||||
*/
|
||||
|
||||
/* deselect chip (low or high) */
|
||||
spin_lock(&bitbang->lock);
|
||||
if (!bitbang->busy) {
|
||||
bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
|
||||
ndelay(mpc83xx_spi->nsecs);
|
||||
}
|
||||
spin_unlock(&bitbang->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpc83xx_spi_bufs(struct spi_device *spi, struct spi_transfer *t)
|
||||
{
|
||||
struct mpc83xx_spi *mpc83xx_spi;
|
||||
u32 word;
|
||||
|
||||
mpc83xx_spi = spi_master_get_devdata(spi->master);
|
||||
|
||||
mpc83xx_spi->tx = t->tx_buf;
|
||||
mpc83xx_spi->rx = t->rx_buf;
|
||||
mpc83xx_spi->count = t->len;
|
||||
INIT_COMPLETION(mpc83xx_spi->done);
|
||||
|
||||
/* enable rx ints */
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, SPIM_NE);
|
||||
|
||||
/* transmit word */
|
||||
word = mpc83xx_spi->get_tx(mpc83xx_spi);
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit, word);
|
||||
|
||||
wait_for_completion(&mpc83xx_spi->done);
|
||||
|
||||
/* disable rx ints */
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
|
||||
|
||||
return t->len - mpc83xx_spi->count;
|
||||
}
|
||||
|
||||
irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data)
|
||||
{
|
||||
struct mpc83xx_spi *mpc83xx_spi = context_data;
|
||||
u32 event;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
|
||||
/* Get interrupt events(tx/rx) */
|
||||
event = mpc83xx_spi_read_reg(&mpc83xx_spi->base->event);
|
||||
|
||||
/* We need handle RX first */
|
||||
if (event & SPIE_NE) {
|
||||
u32 rx_data = mpc83xx_spi_read_reg(&mpc83xx_spi->base->receive);
|
||||
|
||||
if (mpc83xx_spi->rx)
|
||||
mpc83xx_spi->get_rx(rx_data, mpc83xx_spi);
|
||||
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if ((event & SPIE_NF) == 0)
|
||||
/* spin until TX is done */
|
||||
while (((event =
|
||||
mpc83xx_spi_read_reg(&mpc83xx_spi->base->event)) &
|
||||
SPIE_NF) == 0)
|
||||
cpu_relax();
|
||||
|
||||
mpc83xx_spi->count -= 1;
|
||||
if (mpc83xx_spi->count) {
|
||||
if (mpc83xx_spi->tx) {
|
||||
u32 word = mpc83xx_spi->get_tx(mpc83xx_spi);
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit,
|
||||
word);
|
||||
}
|
||||
} else {
|
||||
complete(&mpc83xx_spi->done);
|
||||
}
|
||||
|
||||
/* Clear the events */
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, event);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init mpc83xx_spi_probe(struct platform_device *dev)
|
||||
{
|
||||
struct spi_master *master;
|
||||
struct mpc83xx_spi *mpc83xx_spi;
|
||||
struct fsl_spi_platform_data *pdata;
|
||||
struct resource *r;
|
||||
u32 regval;
|
||||
int ret = 0;
|
||||
|
||||
/* Get resources(memory, IRQ) associated with the device */
|
||||
master = spi_alloc_master(&dev->dev, sizeof(struct mpc83xx_spi));
|
||||
|
||||
if (master == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(dev, master);
|
||||
pdata = dev->dev.platform_data;
|
||||
|
||||
if (pdata == NULL) {
|
||||
ret = -ENODEV;
|
||||
goto free_master;
|
||||
}
|
||||
|
||||
r = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
||||
if (r == NULL) {
|
||||
ret = -ENODEV;
|
||||
goto free_master;
|
||||
}
|
||||
|
||||
mpc83xx_spi = spi_master_get_devdata(master);
|
||||
mpc83xx_spi->bitbang.master = spi_master_get(master);
|
||||
mpc83xx_spi->bitbang.chipselect = mpc83xx_spi_chipselect;
|
||||
mpc83xx_spi->bitbang.setup_transfer = mpc83xx_spi_setup_transfer;
|
||||
mpc83xx_spi->bitbang.txrx_bufs = mpc83xx_spi_bufs;
|
||||
mpc83xx_spi->sysclk = pdata->sysclk;
|
||||
mpc83xx_spi->activate_cs = pdata->activate_cs;
|
||||
mpc83xx_spi->deactivate_cs = pdata->deactivate_cs;
|
||||
mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
|
||||
mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
|
||||
|
||||
mpc83xx_spi->bitbang.master->setup = mpc83xx_spi_setup;
|
||||
init_completion(&mpc83xx_spi->done);
|
||||
|
||||
mpc83xx_spi->base = ioremap(r->start, r->end - r->start + 1);
|
||||
if (mpc83xx_spi->base == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto put_master;
|
||||
}
|
||||
|
||||
mpc83xx_spi->irq = platform_get_irq(dev, 0);
|
||||
|
||||
if (mpc83xx_spi->irq < 0) {
|
||||
ret = -ENXIO;
|
||||
goto unmap_io;
|
||||
}
|
||||
|
||||
/* Register for SPI Interrupt */
|
||||
ret = request_irq(mpc83xx_spi->irq, mpc83xx_spi_irq,
|
||||
0, "mpc83xx_spi", mpc83xx_spi);
|
||||
|
||||
if (ret != 0)
|
||||
goto unmap_io;
|
||||
|
||||
master->bus_num = pdata->bus_num;
|
||||
master->num_chipselect = pdata->max_chipselect;
|
||||
|
||||
/* SPI controller initializations */
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0);
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->command, 0);
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, 0xffffffff);
|
||||
|
||||
/* Enable SPI interface */
|
||||
regval = pdata->initial_spmode | SPMODE_INIT_VAL | SPMODE_ENABLE;
|
||||
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
|
||||
|
||||
ret = spi_bitbang_start(&mpc83xx_spi->bitbang);
|
||||
|
||||
if (ret != 0)
|
||||
goto free_irq;
|
||||
|
||||
printk(KERN_INFO
|
||||
"%s: MPC83xx SPI Controller driver at 0x%p (irq = %d)\n",
|
||||
dev->dev.bus_id, mpc83xx_spi->base, mpc83xx_spi->irq);
|
||||
|
||||
return ret;
|
||||
|
||||
free_irq:
|
||||
free_irq(mpc83xx_spi->irq, mpc83xx_spi);
|
||||
unmap_io:
|
||||
iounmap(mpc83xx_spi->base);
|
||||
put_master:
|
||||
spi_master_put(master);
|
||||
free_master:
|
||||
kfree(master);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit mpc83xx_spi_remove(struct platform_device *dev)
|
||||
{
|
||||
struct mpc83xx_spi *mpc83xx_spi;
|
||||
struct spi_master *master;
|
||||
|
||||
master = platform_get_drvdata(dev);
|
||||
mpc83xx_spi = spi_master_get_devdata(master);
|
||||
|
||||
spi_bitbang_stop(&mpc83xx_spi->bitbang);
|
||||
free_irq(mpc83xx_spi->irq, mpc83xx_spi);
|
||||
iounmap(mpc83xx_spi->base);
|
||||
spi_master_put(mpc83xx_spi->bitbang.master);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver mpc83xx_spi_driver = {
|
||||
.probe = mpc83xx_spi_probe,
|
||||
.remove = __devexit_p(mpc83xx_spi_remove),
|
||||
.driver = {
|
||||
.name = "mpc83xx_spi",
|
||||
},
|
||||
};
|
||||
|
||||
static int __init mpc83xx_spi_init(void)
|
||||
{
|
||||
return platform_driver_register(&mpc83xx_spi_driver);
|
||||
}
|
||||
|
||||
static void __exit mpc83xx_spi_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&mpc83xx_spi_driver);
|
||||
}
|
||||
|
||||
module_init(mpc83xx_spi_init);
|
||||
module_exit(mpc83xx_spi_exit);
|
||||
|
||||
MODULE_AUTHOR("Kumar Gala");
|
||||
MODULE_DESCRIPTION("Simple MPC83xx SPI Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
452
drivers/spi/spi_s3c24xx.c
Normal file
452
drivers/spi/spi_s3c24xx.c
Normal file
@@ -0,0 +1,452 @@
|
||||
/* linux/drivers/spi/spi_s3c24xx.c
|
||||
*
|
||||
* Copyright (c) 2006 Ben Dooks
|
||||
* Copyright (c) 2006 Simtec Electronics
|
||||
* Ben Dooks <ben@simtec.co.uk>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi_bitbang.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/dma.h>
|
||||
#include <asm/hardware.h>
|
||||
|
||||
#include <asm/arch/regs-gpio.h>
|
||||
#include <asm/arch/regs-spi.h>
|
||||
#include <asm/arch/spi.h>
|
||||
|
||||
struct s3c24xx_spi {
|
||||
/* bitbang has to be first */
|
||||
struct spi_bitbang bitbang;
|
||||
struct completion done;
|
||||
|
||||
void __iomem *regs;
|
||||
int irq;
|
||||
int len;
|
||||
int count;
|
||||
|
||||
void (*set_cs)(struct s3c2410_spi_info *spi,
|
||||
int cs, int pol);
|
||||
|
||||
/* data buffers */
|
||||
const unsigned char *tx;
|
||||
unsigned char *rx;
|
||||
|
||||
struct clk *clk;
|
||||
struct resource *ioarea;
|
||||
struct spi_master *master;
|
||||
struct spi_device *curdev;
|
||||
struct device *dev;
|
||||
struct s3c2410_spi_info *pdata;
|
||||
};
|
||||
|
||||
#define SPCON_DEFAULT (S3C2410_SPCON_MSTR | S3C2410_SPCON_SMOD_INT)
|
||||
#define SPPIN_DEFAULT (S3C2410_SPPIN_KEEP)
|
||||
|
||||
static inline struct s3c24xx_spi *to_hw(struct spi_device *sdev)
|
||||
{
|
||||
return spi_master_get_devdata(sdev->master);
|
||||
}
|
||||
|
||||
static void s3c24xx_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol)
|
||||
{
|
||||
s3c2410_gpio_setpin(spi->pin_cs, pol);
|
||||
}
|
||||
|
||||
static void s3c24xx_spi_chipsel(struct spi_device *spi, int value)
|
||||
{
|
||||
struct s3c24xx_spi *hw = to_hw(spi);
|
||||
unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0;
|
||||
unsigned int spcon;
|
||||
|
||||
switch (value) {
|
||||
case BITBANG_CS_INACTIVE:
|
||||
hw->set_cs(hw->pdata, spi->chip_select, cspol^1);
|
||||
break;
|
||||
|
||||
case BITBANG_CS_ACTIVE:
|
||||
spcon = readb(hw->regs + S3C2410_SPCON);
|
||||
|
||||
if (spi->mode & SPI_CPHA)
|
||||
spcon |= S3C2410_SPCON_CPHA_FMTB;
|
||||
else
|
||||
spcon &= ~S3C2410_SPCON_CPHA_FMTB;
|
||||
|
||||
if (spi->mode & SPI_CPOL)
|
||||
spcon |= S3C2410_SPCON_CPOL_HIGH;
|
||||
else
|
||||
spcon &= ~S3C2410_SPCON_CPOL_HIGH;
|
||||
|
||||
spcon |= S3C2410_SPCON_ENSCK;
|
||||
|
||||
/* write new configration */
|
||||
|
||||
writeb(spcon, hw->regs + S3C2410_SPCON);
|
||||
hw->set_cs(hw->pdata, spi->chip_select, cspol);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int s3c24xx_spi_setupxfer(struct spi_device *spi,
|
||||
struct spi_transfer *t)
|
||||
{
|
||||
struct s3c24xx_spi *hw = to_hw(spi);
|
||||
unsigned int bpw;
|
||||
unsigned int hz;
|
||||
unsigned int div;
|
||||
|
||||
bpw = t ? t->bits_per_word : spi->bits_per_word;
|
||||
hz = t ? t->speed_hz : spi->max_speed_hz;
|
||||
|
||||
if (bpw != 8) {
|
||||
dev_err(&spi->dev, "invalid bits-per-word (%d)\n", bpw);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
div = clk_get_rate(hw->clk) / hz;
|
||||
|
||||
/* is clk = pclk / (2 * (pre+1)), or is it
|
||||
* clk = (pclk * 2) / ( pre + 1) */
|
||||
|
||||
div = (div / 2) - 1;
|
||||
|
||||
if (div < 0)
|
||||
div = 1;
|
||||
|
||||
if (div > 255)
|
||||
div = 255;
|
||||
|
||||
dev_dbg(&spi->dev, "setting pre-scaler to %d (hz %d)\n", div, hz);
|
||||
writeb(div, hw->regs + S3C2410_SPPRE);
|
||||
|
||||
spin_lock(&hw->bitbang.lock);
|
||||
if (!hw->bitbang.busy) {
|
||||
hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);
|
||||
/* need to ndelay for 0.5 clocktick ? */
|
||||
}
|
||||
spin_unlock(&hw->bitbang.lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c24xx_spi_setup(struct spi_device *spi)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!spi->bits_per_word)
|
||||
spi->bits_per_word = 8;
|
||||
|
||||
if ((spi->mode & SPI_LSB_FIRST) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = s3c24xx_spi_setupxfer(spi, NULL);
|
||||
if (ret < 0) {
|
||||
dev_err(&spi->dev, "setupxfer returned %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n",
|
||||
__FUNCTION__, spi->mode, spi->bits_per_word,
|
||||
spi->max_speed_hz);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count)
|
||||
{
|
||||
return hw->tx ? hw->tx[count] : 0;
|
||||
}
|
||||
|
||||
static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
|
||||
{
|
||||
struct s3c24xx_spi *hw = to_hw(spi);
|
||||
|
||||
dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
|
||||
t->tx_buf, t->rx_buf, t->len);
|
||||
|
||||
hw->tx = t->tx_buf;
|
||||
hw->rx = t->rx_buf;
|
||||
hw->len = t->len;
|
||||
hw->count = 0;
|
||||
|
||||
/* send the first byte */
|
||||
writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT);
|
||||
wait_for_completion(&hw->done);
|
||||
|
||||
return hw->count;
|
||||
}
|
||||
|
||||
static irqreturn_t s3c24xx_spi_irq(int irq, void *dev)
|
||||
{
|
||||
struct s3c24xx_spi *hw = dev;
|
||||
unsigned int spsta = readb(hw->regs + S3C2410_SPSTA);
|
||||
unsigned int count = hw->count;
|
||||
|
||||
if (spsta & S3C2410_SPSTA_DCOL) {
|
||||
dev_dbg(hw->dev, "data-collision\n");
|
||||
complete(&hw->done);
|
||||
goto irq_done;
|
||||
}
|
||||
|
||||
if (!(spsta & S3C2410_SPSTA_READY)) {
|
||||
dev_dbg(hw->dev, "spi not ready for tx?\n");
|
||||
complete(&hw->done);
|
||||
goto irq_done;
|
||||
}
|
||||
|
||||
hw->count++;
|
||||
|
||||
if (hw->rx)
|
||||
hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT);
|
||||
|
||||
count++;
|
||||
|
||||
if (count < hw->len)
|
||||
writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT);
|
||||
else
|
||||
complete(&hw->done);
|
||||
|
||||
irq_done:
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int s3c24xx_spi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct s3c24xx_spi *hw;
|
||||
struct spi_master *master;
|
||||
struct spi_board_info *bi;
|
||||
struct resource *res;
|
||||
int err = 0;
|
||||
int i;
|
||||
|
||||
master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));
|
||||
if (master == NULL) {
|
||||
dev_err(&pdev->dev, "No memory for spi_master\n");
|
||||
err = -ENOMEM;
|
||||
goto err_nomem;
|
||||
}
|
||||
|
||||
hw = spi_master_get_devdata(master);
|
||||
memset(hw, 0, sizeof(struct s3c24xx_spi));
|
||||
|
||||
hw->master = spi_master_get(master);
|
||||
hw->pdata = pdev->dev.platform_data;
|
||||
hw->dev = &pdev->dev;
|
||||
|
||||
if (hw->pdata == NULL) {
|
||||
dev_err(&pdev->dev, "No platform data supplied\n");
|
||||
err = -ENOENT;
|
||||
goto err_no_pdata;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, hw);
|
||||
init_completion(&hw->done);
|
||||
|
||||
/* setup the state for the bitbang driver */
|
||||
|
||||
hw->bitbang.master = hw->master;
|
||||
hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;
|
||||
hw->bitbang.chipselect = s3c24xx_spi_chipsel;
|
||||
hw->bitbang.txrx_bufs = s3c24xx_spi_txrx;
|
||||
hw->bitbang.master->setup = s3c24xx_spi_setup;
|
||||
|
||||
dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);
|
||||
|
||||
/* find and map our resources */
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");
|
||||
err = -ENOENT;
|
||||
goto err_no_iores;
|
||||
}
|
||||
|
||||
hw->ioarea = request_mem_region(res->start, (res->end - res->start)+1,
|
||||
pdev->name);
|
||||
|
||||
if (hw->ioarea == NULL) {
|
||||
dev_err(&pdev->dev, "Cannot reserve region\n");
|
||||
err = -ENXIO;
|
||||
goto err_no_iores;
|
||||
}
|
||||
|
||||
hw->regs = ioremap(res->start, (res->end - res->start)+1);
|
||||
if (hw->regs == NULL) {
|
||||
dev_err(&pdev->dev, "Cannot map IO\n");
|
||||
err = -ENXIO;
|
||||
goto err_no_iomap;
|
||||
}
|
||||
|
||||
hw->irq = platform_get_irq(pdev, 0);
|
||||
if (hw->irq < 0) {
|
||||
dev_err(&pdev->dev, "No IRQ specified\n");
|
||||
err = -ENOENT;
|
||||
goto err_no_irq;
|
||||
}
|
||||
|
||||
err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "Cannot claim IRQ\n");
|
||||
goto err_no_irq;
|
||||
}
|
||||
|
||||
hw->clk = clk_get(&pdev->dev, "spi");
|
||||
if (IS_ERR(hw->clk)) {
|
||||
dev_err(&pdev->dev, "No clock for device\n");
|
||||
err = PTR_ERR(hw->clk);
|
||||
goto err_no_clk;
|
||||
}
|
||||
|
||||
/* for the moment, permanently enable the clock */
|
||||
|
||||
clk_enable(hw->clk);
|
||||
|
||||
/* program defaults into the registers */
|
||||
|
||||
writeb(0xff, hw->regs + S3C2410_SPPRE);
|
||||
writeb(SPPIN_DEFAULT, hw->regs + S3C2410_SPPIN);
|
||||
writeb(SPCON_DEFAULT, hw->regs + S3C2410_SPCON);
|
||||
|
||||
/* setup any gpio we can */
|
||||
|
||||
if (!hw->pdata->set_cs) {
|
||||
hw->set_cs = s3c24xx_spi_gpiocs;
|
||||
|
||||
s3c2410_gpio_setpin(hw->pdata->pin_cs, 1);
|
||||
s3c2410_gpio_cfgpin(hw->pdata->pin_cs, S3C2410_GPIO_OUTPUT);
|
||||
} else
|
||||
hw->set_cs = hw->pdata->set_cs;
|
||||
|
||||
/* register our spi controller */
|
||||
|
||||
err = spi_bitbang_start(&hw->bitbang);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "Failed to register SPI master\n");
|
||||
goto err_register;
|
||||
}
|
||||
|
||||
dev_dbg(hw->dev, "shutdown=%d\n", hw->bitbang.shutdown);
|
||||
|
||||
/* register all the devices associated */
|
||||
|
||||
bi = &hw->pdata->board_info[0];
|
||||
for (i = 0; i < hw->pdata->board_size; i++, bi++) {
|
||||
dev_info(hw->dev, "registering %s\n", bi->modalias);
|
||||
|
||||
bi->controller_data = hw;
|
||||
spi_new_device(master, bi);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_register:
|
||||
clk_disable(hw->clk);
|
||||
clk_put(hw->clk);
|
||||
|
||||
err_no_clk:
|
||||
free_irq(hw->irq, hw);
|
||||
|
||||
err_no_irq:
|
||||
iounmap(hw->regs);
|
||||
|
||||
err_no_iomap:
|
||||
release_resource(hw->ioarea);
|
||||
kfree(hw->ioarea);
|
||||
|
||||
err_no_iores:
|
||||
err_no_pdata:
|
||||
spi_master_put(hw->master);;
|
||||
|
||||
err_nomem:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int s3c24xx_spi_remove(struct platform_device *dev)
|
||||
{
|
||||
struct s3c24xx_spi *hw = platform_get_drvdata(dev);
|
||||
|
||||
platform_set_drvdata(dev, NULL);
|
||||
|
||||
spi_unregister_master(hw->master);
|
||||
|
||||
clk_disable(hw->clk);
|
||||
clk_put(hw->clk);
|
||||
|
||||
free_irq(hw->irq, hw);
|
||||
iounmap(hw->regs);
|
||||
|
||||
release_resource(hw->ioarea);
|
||||
kfree(hw->ioarea);
|
||||
|
||||
spi_master_put(hw->master);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int s3c24xx_spi_suspend(struct platform_device *pdev, pm_message_t msg)
|
||||
{
|
||||
struct s3c24xx_spi *hw = platform_get_drvdata(pdev);
|
||||
|
||||
clk_disable(hw->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c24xx_spi_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct s3c24xx_spi *hw = platform_get_drvdata(pdev);
|
||||
|
||||
clk_enable(hw->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define s3c24xx_spi_suspend NULL
|
||||
#define s3c24xx_spi_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver s3c24xx_spidrv = {
|
||||
.probe = s3c24xx_spi_probe,
|
||||
.remove = s3c24xx_spi_remove,
|
||||
.suspend = s3c24xx_spi_suspend,
|
||||
.resume = s3c24xx_spi_resume,
|
||||
.driver = {
|
||||
.name = "s3c2410-spi",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init s3c24xx_spi_init(void)
|
||||
{
|
||||
return platform_driver_register(&s3c24xx_spidrv);
|
||||
}
|
||||
|
||||
static void __exit s3c24xx_spi_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&s3c24xx_spidrv);
|
||||
}
|
||||
|
||||
module_init(s3c24xx_spi_init);
|
||||
module_exit(s3c24xx_spi_exit);
|
||||
|
||||
MODULE_DESCRIPTION("S3C24XX SPI Driver");
|
||||
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
|
||||
MODULE_LICENSE("GPL");
|
||||
203
drivers/spi/spi_s3c24xx_gpio.c
Normal file
203
drivers/spi/spi_s3c24xx_gpio.c
Normal file
@@ -0,0 +1,203 @@
|
||||
/* linux/drivers/spi/spi_s3c24xx_gpio.c
|
||||
*
|
||||
* Copyright (c) 2006 Ben Dooks
|
||||
* Copyright (c) 2006 Simtec Electronics
|
||||
*
|
||||
* S3C24XX GPIO based SPI driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi_bitbang.h>
|
||||
|
||||
#include <asm/arch/regs-gpio.h>
|
||||
#include <asm/arch/spi-gpio.h>
|
||||
#include <asm/hardware.h>
|
||||
|
||||
struct s3c2410_spigpio {
|
||||
struct spi_bitbang bitbang;
|
||||
|
||||
struct s3c2410_spigpio_info *info;
|
||||
struct platform_device *dev;
|
||||
};
|
||||
|
||||
static inline struct s3c2410_spigpio *spidev_to_sg(struct spi_device *spi)
|
||||
{
|
||||
return spi->controller_data;
|
||||
}
|
||||
|
||||
static inline void setsck(struct spi_device *dev, int on)
|
||||
{
|
||||
struct s3c2410_spigpio *sg = spidev_to_sg(dev);
|
||||
s3c2410_gpio_setpin(sg->info->pin_clk, on ? 1 : 0);
|
||||
}
|
||||
|
||||
static inline void setmosi(struct spi_device *dev, int on)
|
||||
{
|
||||
struct s3c2410_spigpio *sg = spidev_to_sg(dev);
|
||||
s3c2410_gpio_setpin(sg->info->pin_mosi, on ? 1 : 0);
|
||||
}
|
||||
|
||||
static inline u32 getmiso(struct spi_device *dev)
|
||||
{
|
||||
struct s3c2410_spigpio *sg = spidev_to_sg(dev);
|
||||
return s3c2410_gpio_getpin(sg->info->pin_miso) ? 1 : 0;
|
||||
}
|
||||
|
||||
#define spidelay(x) ndelay(x)
|
||||
|
||||
#define EXPAND_BITBANG_TXRX
|
||||
#include <linux/spi/spi_bitbang.h>
|
||||
|
||||
|
||||
static u32 s3c2410_spigpio_txrx_mode0(struct spi_device *spi,
|
||||
unsigned nsecs, u32 word, u8 bits)
|
||||
{
|
||||
return bitbang_txrx_be_cpha0(spi, nsecs, 0, word, bits);
|
||||
}
|
||||
|
||||
static u32 s3c2410_spigpio_txrx_mode1(struct spi_device *spi,
|
||||
unsigned nsecs, u32 word, u8 bits)
|
||||
{
|
||||
return bitbang_txrx_be_cpha1(spi, nsecs, 0, word, bits);
|
||||
}
|
||||
|
||||
static u32 s3c2410_spigpio_txrx_mode2(struct spi_device *spi,
|
||||
unsigned nsecs, u32 word, u8 bits)
|
||||
{
|
||||
return bitbang_txrx_be_cpha0(spi, nsecs, 1, word, bits);
|
||||
}
|
||||
|
||||
static u32 s3c2410_spigpio_txrx_mode3(struct spi_device *spi,
|
||||
unsigned nsecs, u32 word, u8 bits)
|
||||
{
|
||||
return bitbang_txrx_be_cpha1(spi, nsecs, 1, word, bits);
|
||||
}
|
||||
|
||||
|
||||
static void s3c2410_spigpio_chipselect(struct spi_device *dev, int value)
|
||||
{
|
||||
struct s3c2410_spigpio *sg = spidev_to_sg(dev);
|
||||
|
||||
if (sg->info && sg->info->chip_select)
|
||||
(sg->info->chip_select)(sg->info, value);
|
||||
}
|
||||
|
||||
static int s3c2410_spigpio_probe(struct platform_device *dev)
|
||||
{
|
||||
struct spi_master *master;
|
||||
struct s3c2410_spigpio *sp;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
master = spi_alloc_master(&dev->dev, sizeof(struct s3c2410_spigpio));
|
||||
if (master == NULL) {
|
||||
dev_err(&dev->dev, "failed to allocate spi master\n");
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
sp = spi_master_get_devdata(master);
|
||||
|
||||
platform_set_drvdata(dev, sp);
|
||||
|
||||
/* copy in the plkatform data */
|
||||
sp->info = dev->dev.platform_data;
|
||||
|
||||
/* setup spi bitbang adaptor */
|
||||
sp->bitbang.master = spi_master_get(master);
|
||||
sp->bitbang.chipselect = s3c2410_spigpio_chipselect;
|
||||
|
||||
sp->bitbang.txrx_word[SPI_MODE_0] = s3c2410_spigpio_txrx_mode0;
|
||||
sp->bitbang.txrx_word[SPI_MODE_1] = s3c2410_spigpio_txrx_mode1;
|
||||
sp->bitbang.txrx_word[SPI_MODE_2] = s3c2410_spigpio_txrx_mode2;
|
||||
sp->bitbang.txrx_word[SPI_MODE_3] = s3c2410_spigpio_txrx_mode3;
|
||||
|
||||
/* set state of spi pins */
|
||||
s3c2410_gpio_setpin(sp->info->pin_clk, 0);
|
||||
s3c2410_gpio_setpin(sp->info->pin_mosi, 0);
|
||||
|
||||
s3c2410_gpio_cfgpin(sp->info->pin_clk, S3C2410_GPIO_OUTPUT);
|
||||
s3c2410_gpio_cfgpin(sp->info->pin_mosi, S3C2410_GPIO_OUTPUT);
|
||||
s3c2410_gpio_cfgpin(sp->info->pin_miso, S3C2410_GPIO_INPUT);
|
||||
|
||||
ret = spi_bitbang_start(&sp->bitbang);
|
||||
if (ret)
|
||||
goto err_no_bitbang;
|
||||
|
||||
/* register the chips to go with the board */
|
||||
|
||||
for (i = 0; i < sp->info->board_size; i++) {
|
||||
dev_info(&dev->dev, "registering %p: %s\n",
|
||||
&sp->info->board_info[i],
|
||||
sp->info->board_info[i].modalias);
|
||||
|
||||
sp->info->board_info[i].controller_data = sp;
|
||||
spi_new_device(master, sp->info->board_info + i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_no_bitbang:
|
||||
spi_master_put(sp->bitbang.master);
|
||||
err:
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
static int s3c2410_spigpio_remove(struct platform_device *dev)
|
||||
{
|
||||
struct s3c2410_spigpio *sp = platform_get_drvdata(dev);
|
||||
|
||||
spi_bitbang_stop(&sp->bitbang);
|
||||
spi_master_put(sp->bitbang.master);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* all gpio should be held over suspend/resume, so we should
|
||||
* not need to deal with this
|
||||
*/
|
||||
|
||||
#define s3c2410_spigpio_suspend NULL
|
||||
#define s3c2410_spigpio_resume NULL
|
||||
|
||||
|
||||
static struct platform_driver s3c2410_spigpio_drv = {
|
||||
.probe = s3c2410_spigpio_probe,
|
||||
.remove = s3c2410_spigpio_remove,
|
||||
.suspend = s3c2410_spigpio_suspend,
|
||||
.resume = s3c2410_spigpio_resume,
|
||||
.driver = {
|
||||
.name = "s3c24xx-spi-gpio",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init s3c2410_spigpio_init(void)
|
||||
{
|
||||
return platform_driver_register(&s3c2410_spigpio_drv);
|
||||
}
|
||||
|
||||
static void __exit s3c2410_spigpio_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&s3c2410_spigpio_drv);
|
||||
}
|
||||
|
||||
module_init(s3c2410_spigpio_init);
|
||||
module_exit(s3c2410_spigpio_exit);
|
||||
|
||||
MODULE_DESCRIPTION("S3C24XX SPI Driver");
|
||||
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user