You can subscribe to this list here.
2005 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
(1) |
Sep
(18) |
Oct
(21) |
Nov
(68) |
Dec
(130) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2006 |
Jan
(12) |
Feb
(41) |
Mar
(44) |
Apr
(60) |
May
(31) |
Jun
(43) |
Jul
(45) |
Aug
(54) |
Sep
(19) |
Oct
(12) |
Nov
(14) |
Dec
(30) |
2007 |
Jan
(87) |
Feb
(41) |
Mar
(19) |
Apr
(33) |
May
(69) |
Jun
(100) |
Jul
(100) |
Aug
(86) |
Sep
(30) |
Oct
(149) |
Nov
(45) |
Dec
(59) |
2008 |
Jan
(136) |
Feb
(124) |
Mar
(67) |
Apr
(107) |
May
(168) |
Jun
(83) |
Jul
(160) |
Aug
(72) |
Sep
(166) |
Oct
(159) |
Nov
(192) |
Dec
(185) |
2009 |
Jan
(108) |
Feb
(189) |
Mar
(151) |
Apr
(219) |
May
(273) |
Jun
(297) |
Jul
(217) |
Aug
(157) |
Sep
(181) |
Oct
(146) |
Nov
(239) |
Dec
(173) |
2010 |
Jan
(175) |
Feb
(206) |
Mar
(210) |
Apr
(180) |
May
(165) |
Jun
(126) |
Jul
(150) |
Aug
(198) |
Sep
(305) |
Oct
(280) |
Nov
(279) |
Dec
(263) |
2011 |
Jan
(232) |
Feb
(182) |
Mar
(124) |
Apr
(126) |
May
(133) |
Jun
(257) |
Jul
(153) |
Aug
(134) |
Sep
(136) |
Oct
(120) |
Nov
(185) |
Dec
(132) |
2012 |
Jan
(158) |
Feb
(145) |
Mar
(257) |
Apr
(197) |
May
(253) |
Jun
(266) |
Jul
(406) |
Aug
(327) |
Sep
(334) |
Oct
(253) |
Nov
(197) |
Dec
(227) |
2013 |
Jan
(243) |
Feb
(355) |
Mar
(263) |
Apr
(256) |
May
(154) |
Jun
(291) |
Jul
(319) |
Aug
(159) |
Sep
(181) |
Oct
(212) |
Nov
(76) |
Dec
(143) |
2014 |
Jan
(322) |
Feb
(523) |
Mar
(364) |
Apr
(836) |
May
(629) |
Jun
(592) |
Jul
(579) |
Aug
(459) |
Sep
(574) |
Oct
(639) |
Nov
(470) |
Dec
(368) |
2015 |
Jan
(504) |
Feb
(554) |
Mar
(686) |
Apr
(463) |
May
(305) |
Jun
(285) |
Jul
(188) |
Aug
(151) |
Sep
(171) |
Oct
(149) |
Nov
(164) |
Dec
(164) |
2016 |
Jan
(189) |
Feb
(158) |
Mar
(154) |
Apr
(153) |
May
(187) |
Jun
(184) |
Jul
(200) |
Aug
(228) |
Sep
(252) |
Oct
(270) |
Nov
(286) |
Dec
(361) |
2017 |
Jan
(346) |
Feb
(266) |
Mar
(251) |
Apr
(132) |
May
(175) |
Jun
(161) |
Jul
(195) |
Aug
(43) |
Sep
|
Oct
|
Nov
|
Dec
|
From: Pavel M. <pa...@uc...> - 2005-10-05 11:43:26
|
Hi! > >+#ifdef CONFIG_PM > >+ > >+/* Suspend/resume in "struct device_driver" don't really need that > >+ * strange third parameter, so we just make it a constant and expect > >+ * SPI drivers to ignore it just like most platform drivers do. > >+ * > > > So you just ignored my letter on that subject :( > The fact that you don't need it doesn't mean that other people won't. > The fact that there's no clean way to suspend USB doesn't mean that > there shouldn't be one for SPI. The third parameter really must die. Just because you *can* use it does not mean you should. > >+ * 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; > >+ > >+ if (!dev->driver || !dev->driver->suspend) > >+ return 0; > >+ > >+ /* suspend will stop irqs and dma; no more i/o */ > >+ value = dev->driver->suspend(dev, message, SUSPEND_POWER_DOWN); > > > > > So driver->suspend is going to be called 3 timer with SUSPEND_POWER_DOWN > parameter, right? > I'm afraid that won't work :( No, it is going to be called once. You perhaps should not pass it "SUSPEND_POWER_DOWN" but something else; but drivers should really ignore it. [You are calling it for normal suspend, right? Not just before power down.] > Especially in our case, where we *do need* preparation steps that are > taken in *normal* suspend sequence - i. e. we need to set up the wakeup > credentials for the *SPI* since the wakeup's gonna happen from a call > incoming through the network module residing on the SPI bus! You should not need more than one phase. Pavel -- if you have sharp zaurus hardware you don't need... you know my address |
From: Adam B. <am...@ne...> - 2005-10-05 09:25:34
|
On Wed, Oct 05, 2005 at 12:06:37PM +0400, Vitaly Wool wrote: > David, > > >+#ifdef CONFIG_PM > >+ > >+/* Suspend/resume in "struct device_driver" don't really need that > >+ * strange third parameter, so we just make it a constant and expect > >+ * SPI drivers to ignore it just like most platform drivers do. > >+ * > > > > > So you just ignored my letter on that subject :( > The fact that you don't need it doesn't mean that other people won't. > The fact that there's no clean way to suspend USB doesn't mean that > there shouldn't be one for SPI. > > >+ * 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. But we should, right? It seems like a child device should never be in a higher suspend state than its parents. The rule doesn't have to hold with driver-initiated runtime power management, but when the user requests a suspend via power/state, it's reasonable to assume every child should be suspended first. > >+ */ > >+static int spi_suspend(struct device *dev, pm_message_t message) > >+{ > >+ int value; > >+ > >+ if (!dev->driver || !dev->driver->suspend) > >+ return 0; > >+ > >+ /* suspend will stop irqs and dma; no more i/o */ > >+ value = dev->driver->suspend(dev, message, SUSPEND_POWER_DOWN); > > > > > So driver->suspend is going to be called 3 timer with SUSPEND_POWER_DOWN > parameter, right? > I'm afraid that won't work :( > Especially in our case, where we *do need* preparation steps that are > taken in *normal* suspend sequence - i. e. we need to set up the wakeup > credentials for the *SPI* since the wakeup's gonna happen from a call > incoming through the network module residing on the SPI bus! So... 1.) suspend all child devices 2.) calculate their wake requirements 3.) suspend the parent to a degree compatible with those requirements Right? Thanks, Adam |
From: Vitaly W. <vw...@ru...> - 2005-10-05 09:12:19
|
Russell King wrote: >The third parameter is obsolete and should only be used to select _one_ >of the tree suspend calls you will get. > >Any additional suspend calls should _not_ create extra usage of this >parameter. It's a left over from Pat's first driver model incarnation >which is specific to the platform device drivers. (Mainly it exists >because no one can be bothered to clean it up.) > > > Oops... I mixed it with state parameter. We need to track down the state parameter to setup wakeups. The mixture was mostly provocated by using 'pm_message_t' instead of 'state' in David's suspend fucntion which doesn't look right to me by itself. Vitaly |
From: Russell K. <rmk...@ar...> - 2005-10-05 09:01:54
|
On Wed, Oct 05, 2005 at 12:06:37PM +0400, Vitaly Wool wrote: > David, > > >+#ifdef CONFIG_PM > >+ > >+/* Suspend/resume in "struct device_driver" don't really need that > >+ * strange third parameter, so we just make it a constant and expect > >+ * SPI drivers to ignore it just like most platform drivers do. > >+ * > > > > > So you just ignored my letter on that subject :( > The fact that you don't need it doesn't mean that other people won't. > The fact that there's no clean way to suspend USB doesn't mean that > there shouldn't be one for SPI. The third parameter is obsolete and should only be used to select _one_ of the tree suspend calls you will get. Any additional suspend calls should _not_ create extra usage of this parameter. It's a left over from Pat's first driver model incarnation which is specific to the platform device drivers. (Mainly it exists because no one can be bothered to clean it up.) -- Russell King Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/ maintainer of: 2.6 Serial core |
From: Vitaly W. <vw...@ru...> - 2005-10-05 08:06:30
|
Hi David, David Brownell wrote: >Hi Vitaly, > > > >>can you please describe the data flow in case of DMA transfer? Thanks! >> >> > >In the current model, the controller driver handles it (assuming >that it uses DMA not PIO): > > - Use dma_map_single() at some point between the master->transfer() > call and the time the DMA address is handed to DMA hardware. > > So that implies calling dma_map_single() for the memory allocated in stack? I'm afraid I know at least one target which won't work with this approach (Philips ARM926-driven board with ARM PrimeCell DMA controller). > - Probably store those addresses in the spi_transfer struct, using > rx_dma and/or tx_dma as appropriate. > > - After the DMA transfer completes, call dma_unmap_single() before > calling spi_message.complete(spi_message.context). > >There are two fancier approaches to consider for sometime later, both >of which have been used for several years now by host-side USB. > > * SPI controller drivers could require the mappings to already > have been done, and stored in {rx,tx}_dma fields. When those > drivers are using DMA, they'd only use those DMA addresses. > > The way host-side USB does that is by having usbcore do work > on all the submit and giveback paths. Currently this SPI > framework doesn't do "core" work on those paths; spi_async() > just calls directly to the controller driver (no overhead!) > and the completion reports likewise go right from controller > driver back to the submitter (also, no overhead). > > * Drivers for SPI slave chips might sometimes want to provide their > own rx_dma and/or tx_dma values ... either because they mapped the > buffers themselves -- great for dma_map_sg()! -- or because the > memory came from dma_alloc_coherent(). > > Of course we want to use scatter-gather lists. The DMA controller mentioned above can handle only 0xFFF transfer units at a transfer so we have to split the large transfers into SG lists. > This implies that the controller drivers are ready to accept > those DMA addresses, and that there are per-buffer flags saying > whether its DMA address is also valid (vs just its CPU address). > >Note that the slave-side USB support only supports the latter, which >means the USB peripheral controller drivers with DMA support decide >on a per-request basis whether to do the DMA mappings. I'd lean >towards doing that with SPI; it's a bunch simpler. > > Doesn't look straightforward to me. Moreover, that looks like it may imply redundant data copying. Can you please elaborate what you meant by 'readiness to accept DMA addresses' for the controller drivers? As far as I see it now, the whole thing looks wrong. The thing that we suggest (i. e. abstract handles for memory allocation set to kmalloc by default) is looking far better IMHO and doesn't require any flags which usage increases uncertainty in the core. >Right now DMA isn't on my priority list, but I'd be glad to see >someone else take further steps there. Anyone doing bulk I/O to an >SPI flash chip at 50 MHz is surely going to want DMA for it! > > Yep, as well as for SPI SD/MMC controllers, SPI WiFi modules, SPI Bluetooth modules, etc. etc. Best regards, Vitaly |
From: Vitaly W. <vw...@ru...> - 2005-10-05 08:05:07
|
David, >+#ifdef CONFIG_PM >+ >+/* Suspend/resume in "struct device_driver" don't really need that >+ * strange third parameter, so we just make it a constant and expect >+ * SPI drivers to ignore it just like most platform drivers do. >+ * > > So you just ignored my letter on that subject :( The fact that you don't need it doesn't mean that other people won't. The fact that there's no clean way to suspend USB doesn't mean that there shouldn't be one for SPI. >+ * 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; >+ >+ if (!dev->driver || !dev->driver->suspend) >+ return 0; >+ >+ /* suspend will stop irqs and dma; no more i/o */ >+ value = dev->driver->suspend(dev, message, SUSPEND_POWER_DOWN); > > So driver->suspend is going to be called 3 timer with SUSPEND_POWER_DOWN parameter, right? I'm afraid that won't work :( Especially in our case, where we *do need* preparation steps that are taken in *normal* suspend sequence - i. e. we need to set up the wakeup credentials for the *SPI* since the wakeup's gonna happen from a call incoming through the network module residing on the SPI bus! Vitaly |
From: Vitaly W. <vw...@ru...> - 2005-10-05 07:55:28
|
BTW, haven't seen any place where message->complete() is called... Can you please point out one? Vitaly David Brownell wrote: >Following this will be two patches, refreshing the minimalist SPI stack >I've sent before. Notable changes are: > > - Various updates to support real hardware, including reporting the > IRQ associated with an SPI slave chip, providing void* handles for > various flavors of board and controller state, dma_addr_t for I/O > buffers, some control over protocol delays, and more. > > - New spi_alloc_master(). The driver model is happier if drivers > don't allocate the class devices; this helps "rmmod" and friends, > kind of handy for debugging drivers. It allocates controller > specific memory not unlike alloc_netdev(). > > - Various cleanup, notably removing Kconfig for all those drivers > that don't yet exist. That was added purely to illustrate the > potential scope of an SPI framework, when more folk were asking > just why a Serial Peripheral Interface (*) was useful. > > - More kerneldoc. No Documentation/DocBook/spi.html though. > > - Now there's a real ADS7864 touchscreen/sensor driver; lightly > tested, but it emits the right sort of input events and gives > syfs access to the temperature, battery, and voltage sensors. > >This version seems real enough to integrate with. > >One goal is promote reuse of driver code -- for SPI controllers and >slave chips connected using SPI -- while fitting them better into the >driver model framework. Today, SPI devices only seem to get drivers >that are board-specific; there's a fair amount of reinvent-the-wheel, >and drivers that are unsuitable for upstream merging. > >I can now report this seems to be working with real controllers and >real slave chips ... two of each to start with, but as yet there's no >mix'n'match (with e.g. that touchscreen driver being used with a PXA >SSP controller, not just OMAP MicroWire). That should just take a >little bit of time and debugging. > >- Dave > >(*) And distinguish it from Singapore Paranormal Investigators. ;) > >- >To unsubscribe from this list: send the line "unsubscribe linux-kernel" in >the body of a message to maj...@vg... >More majordomo info at http://vger.kernel.org/majordomo-info.html >Please read the FAQ at http://www.tux.org/lkml/ > > > > |
From: Stephen S. <sg...@gm...> - 2005-10-05 07:29:28
|
This is a prototype interrupt driven SPI "controller" for Intel's PXA2xx series SOC. The driver plugs into the lightweight SPI framework developed b= y David Brownell. Hardwired configuration information is provided via spi_board_info structures initialized in arch/arm/mach_pxa board initialization code (see include/linux/spi.h for details). The driver is built around a spi_message fifo serviced by two tasklets. The first tasklet (pump_messages) is responsible for queuing SPI transactions and scheduling SPI transfers. The second tasklet (pump_transfers) is responsible to setting up and launching the interrupt driven transfers. Per transfer chip select and delay control is available. This is a prototype driver, so you mileage will vary. It has only been tested on the NSSP port. Known Limitations: Does not handle invert chip select polarity. Heavy loaded systems may see transaction failures. Wordsize support is untested. Internal NSSP chip select is not support (i.e. NSSPSRFM) ---- snip ---- drivers/spi/Kconfig | 12 drivers/spi/Makefile | 2 drivers/spi/pxa2xx_spi_ssp.c | 741 ++++++++++++++++++++++++++++++ include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h | 36 + 4 files changed, 791 insertions(+) --- linux-2.6.12-spi/drivers/spi/Kconfig=092005-10-04 14:07:18.000000000 -0= 700 +++ linux-2.6.12-spi-pxa/drivers/spi/Kconfig=092005-10-04 14:00:12.27944900= 0 -0700 @@ -65,6 +65,12 @@ comment "SPI Master Controller Drivers" +config SPI_PXA_SSP + tristate "PXA SSP controller as SPI master" + depends on ARCH_PXA + help + This implements SPI master mode using an SSP controller. + # # Add new SPI master controllers in alphabetical order above this line # @@ -77,6 +83,12 @@ comment "SPI Protocol Masters" +config SPI_CS8415A + tristate "CS8415A SPD/IF decoder" + help + This chip provides an 8 channel SPD/IF switcher with complete + SPD/IF decoding. + # # Add new SPI protocol masters in alphabetical order above this line # --- linux-2.6.12-spi/drivers/spi/Makefile=092005-10-04 14:07:18.000000000 -= 0700 +++ linux-2.6.12-spi-pxa/drivers/spi/Makefile=092005-10-04 14:00:12.279449000 -0700 @@ -11,9 +11,11 @@ obj-$(CONFIG_SPI_MASTER) +=3D spi.o # SPI master controller drivers (bus) +obj-$(CONFIG_SPI_PXA_SSP) +=3D pxa2xx_spi_ssp.o # ... add above this line ... # SPI protocol drivers (device/link on bus) +obj-$(CONFIG_SPI_CS8415A) +=3D cs8415a.o # ... add above this line ... # SPI slave controller drivers (upstream link) --- linux-2.6.12-spi/drivers/spi/pxa2xx_spi_ssp.c=091969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/drivers/spi/pxa2xx_spi_ssp.c=092005-10-04 12:50:10.699272000 -0700 @@ -0,0 +1,741 @@ +/* + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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. + */ + +/* + * This is a prototype interrupt driven SPI "controller" for Intel's PXA2x= x + * series SOC. The driver plugs into the lightweight SPI framework develop= ed by + * David Brownell. Hardwired configuration information is provided via + * spi_board_info structures initialized in arch/arm/mach_pxa board + * initialization code (see include/linux/spi.h for details). NEED TO ADD + * PXA SPECIFIED INITIALIZATION INFORMATION. + * + * This follow code snippet demostrates a sample board configuration using + * the PXA255 NSSP port connect to a CS8415A chip via GPIO chip select 2. + * + * static struct cs8415a_platform_data cs8415a_platform_info =3D { + *=09.enabled =3D 1, + *=09.muted =3D 1, + *=09.channel =3D 0, + *=09.mask_interrupt =3D cs8415a_mask_interrupt, + *=09.unmask_interrupt =3D cs8415a_unmask_interrupt, + *=09.service_requested =3D cs8415a_service_requested, + * }; + * + * static struct pxa2xx_spi_chip cs8415a_chip_info =3D { + *=09.mode =3D SPI_MODE_3, + *=09.tx_threshold =3D 12, + *=09.rx_threshold =3D 4, + *=09.bits_per_word =3D 8, + *=09.chip_select_gpio =3D 2, + *=09.timeout_microsecs =3D 64, + * }; + * + * static struct spi_board_info streetracer_spi_board_info[] __initdata = =3D { + *=09{ + *=09=09.modalias =3D "cs8415a", + *=09=09.max_speed_hz =3D 3686400, + *=09=09.bus_num =3D 2, + *=09=09.chip_select =3D 0, + *=09=09.platform_data =3D &cs8415a_platform_info, + *=09=09.controller_data =3D &cs8415a_chip_info, + *=09=09.irq =3D STREETRACER_APCI_IRQ, + *=09}, + * }; + * + * static struct resource pxa_spi_resources[] =3D { + *=09[0] =3D { + *=09=09.start=09=3D __PREG(SSCR0_P(2)), + *=09=09.end=09=3D __PREG(SSCR0_P(2)) + 0x2c, + *=09=09.flags=09=3D IORESOURCE_MEM, + *=09}, + *=09[1] =3D { + *=09=09.start=09=3D IRQ_NSSP, + *=09=09.end=09=3D IRQ_NSSP, + *=09=09.flags=09=3D IORESOURCE_IRQ, + *=09}, + * }; + * + * static struct pxa2xx_spi_master pxa_nssp_master_info =3D { + *=09.bus_num =3D 2, + *=09.clock_enable =3D CKEN9_NSSP, + *=09.num_chipselect =3D 3, + * }; + * + * static struct platform_device pxa_spi_ssp =3D { + *=09.name =3D "pxa2xx-spi-ssp", + *=09.id =3D -1, + *=09.resource =3D pxa_spi_resources, + *=09.num_resources =3D ARRAY_SIZE(pxa_spi_resources), + *=09.dev =3D { + *=09=09.platform_data =3D &pxa_nssp_master_info, + *=09}, + * }; + * + * static void __init streetracer_init(void) + * { + *=09platform_device_register(&pxa_spi_ssp); + *=09spi_register_board_info(streetracer_spi_board_info, + * =09=09=09=09ARRAY_SIZE(streetracer_spi_board_info)); + * } + * + * The driver is built around a spi_message fifo serviced by two tasklets.= The + * first tasklet (pump_messages) is responsible for queuing SPI transactio= ns + * and scheduling SPI transfers. The second tasklet (pump_transfers) is + * responsible to setting up and launching the interrupt driven transfers. + * Per transfer chip select and delay control is available. + * + * This is a prototype driver, so you mileage will vary. It has only been + * tested on the NSSP port. + * + * Known Limitations: + * =09Does not handle invert chip select polarity. + * =09Heavy loaded systems may see transaction failures. + * =09Wordsize support is untested. + * =09Internal NSSP chip select is not support (i.e. NSSPSRFM) + * =09Module hangs during unload. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/spi.h> +#include <linux/ioport.h> +#include <linux/errno.h> +#include <linux/interrupt.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/hardware.h> +#include <asm/delay.h> + +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/pxa2xx_spi_ssp.h> + +MODULE_AUTHOR("Stephen Street"); +MODULE_DESCRIPTION("PXA2xx SSP SPI Contoller"); +MODULE_LICENSE("GPL"); + +#define MAX_SPEED_HZ 3686400 +#define MAX_BUSES 3 + +#define GET_IRQ_STATUS(x) (__REG(sssr)&(SSSR_TINT|SSSR_RFS|SSSR_TFS|SSSR_R= OR)) + +struct transfer_state { +=09int index; +=09int len; +=09u32 gpio; +=09void *tx; +=09void *tx_end; +=09void *rx; +=09void *rx_end; +=09void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state); +=09void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state); +}; + +struct master_data { +=09spinlock_t lock; +=09struct spi_master *master; +=09struct list_head queue; +=09struct tasklet_struct pump_messages; +=09struct tasklet_struct pump_transfers; +=09struct spi_message* cur_msg; +=09struct transfer_state cur_state; +=09u32 sscr0; +=09u32 sscr1; +=09u32 sssr; +=09u32 ssitr; +=09u32 ssdr; +=09u32 ssto; +=09u32 sspsp; +}; + +struct chip_data { +=09u32 cr0; +=09u32 cr1; +=09u32 to; +=09u32 psp; +=09u16 cs_gpio; +=09u32 timeout; +=09u8 n_bytes; +=09void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state); +=09void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state); +}; + +static inline void flush(struct master_data *drv_data) +{ +=09u32 sssr =3D drv_data->sssr; +=09u32 ssdr =3D drv_data->ssdr; +=09 +=09do { +=09=09while (__REG(sssr) & SSSR_RNE) { +=09=09=09(void)__REG(ssdr); +=09=09} +=09} while (__REG(sssr) & SSSR_BSY); +=09__REG(sssr) =3D SSSR_ROR ; +} + +static inline void save_state(struct master_data *drv_data, +=09=09=09=09struct chip_data *chip) +{ +=09/* Save critical register */ +=09chip->cr0 =3D __REG(drv_data->sscr0); +=09chip->cr1 =3D __REG(drv_data->sscr1); +=09chip->to =3D __REG(drv_data->ssto); +=09chip->psp =3D __REG(drv_data->sspsp); +=09 +=09/* Disable clock */ +=09__REG(drv_data->sscr0) &=3D ~SSCR0_SSE; +} + +static inline void restore_state(struct master_data *drv_data, +=09=09=09=09=09struct chip_data *chip) +{ +=09/* Clear status and disable clock*/ +=09__REG(drv_data->sssr) =3D SSSR_ROR | SSSR_TUR | SSSR_BCE; +=09__REG(drv_data->sscr0) =3D chip->cr0 & ~SSCR0_SSE; +=09 +=09/* Load the registers */ +=09__REG(drv_data->sscr1) =3D chip->cr1; +=09__REG(drv_data->ssto) =3D chip->to; +=09__REG(drv_data->sspsp) =3D chip->psp; +=09__REG(drv_data->sscr0) =3D chip->cr0; +} + +static inline void dump_state(struct master_data* drv_data) +{ +=09u32 sscr0 =3D drv_data->sscr0; +=09u32 sscr1 =3D drv_data->sscr1; +=09u32 sssr =3D drv_data->sssr; +=09u32 ssto =3D drv_data->ssto; +=09u32 sspsp =3D drv_data->sspsp; +=09 +=09pr_debug("SSP dump: sscr0=3D0x%08x, sscr1=3D0x%08x, " +=09=09=09"ssto=3D0x%08x, sspsp=3D0x%08x, sssr=3D0x%08x\n", +=09=09=09__REG(sscr0), __REG(sscr1), __REG(ssto), +=09=09=09__REG(sspsp), __REG(sssr)); +} + +static void null_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { +=09=09__REG(ssdr) =3D 0; +=09=09++state->tx; +=09} +} + +static void null_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { +=09=09(void)(__REG(ssdr)); +=09=09++state->rx; +=09} +} + +static void u8_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { +=09=09__REG(ssdr) =3D *(u8 *)(state->tx); +=09=09++state->tx; +=09} +} + +static void u8_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { +=09=09*(u8 *)(state->rx) =3D __REG(ssdr); +=09=09++state->rx; +=09} +} + +static void u16_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { +=09=09__REG(ssdr) =3D *(u16 *)(state->tx); +=09=09state->tx +=3D 2; +=09} +} + +static void u16_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { +=09=09*(u16 *)(state->rx) =3D __REG(ssdr); +=09=09state->rx +=3D 2; +=09} +} +static void u32_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { +=09=09__REG(ssdr) =3D *(u32 *)(state->tx); +=09=09state->tx +=3D 4; +=09} +} + +static void u32_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { +=09=09*(u32 *)(state->rx) =3D __REG(ssdr); +=09=09state->rx +=3D 4; +=09} +} + +static irqreturn_t ssp_int(int irq, void *dev_id, struct pt_regs *regs) +{ +=09struct master_data *drv_data =3D (struct master_data *)dev_id; +=09struct transfer_state *state; +=09u32 sssr =3D drv_data->sssr; +=09u32 ssdr =3D drv_data->ssdr; +=09u32 sscr1 =3D drv_data->sscr1; +=09u32 ssto =3D drv_data->ssto; +=09u32 irq_status; +=09struct spi_message *msg; +=09 +=09if (!drv_data->cur_msg || !drv_data->cur_msg->state) { +=09=09printk(KERN_ERR "pxs2xx_spi_ssp: bad message or message " +=09=09=09=09"state in interrupt handler\n"); +=09} +=09state =3D (struct transfer_state *)drv_data->cur_msg->state; +=09msg =3D drv_data->cur_msg; + +=09while ((irq_status =3D GET_IRQ_STATUS(sssr))) { + +=09=09if (irq_status & SSSR_ROR) { + +=09=09=09/* Clear and disable interrupts */ +=09=09=09__REG(ssto) =3D 0; +=09=09=09__REG(sssr) =3D SSSR_TINT | SSSR_ROR; +=09=09=09__REG(sscr1) &=3D ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); +=09=09=09 +=09=09=09flush(drv_data); +=09=09=09 +=09=09=09printk(KERN_WARNING "fifo overun: " +=09=09=09=09=09"index=3D%d tx_len=3D%d rx_len%d\n", +=09=09=09=09=09state->index, +=09=09=09=09=09(state->tx_end - state->tx), +=09=09=09=09=09(state->rx_end - state->rx)); + +=09=09=09state->index =3D -2; +=09=09=09tasklet_schedule(&drv_data->pump_transfers); + +=09=09=09return IRQ_HANDLED; +=09=09} + +=09=09 +=09=09/* Pump data */ +=09=09state->read(sssr, ssdr, state); +=09=09state->write(sssr, ssdr, state); +=09=09 +=09=09if ((irq_status & SSSR_TINT) || (state->rx <=3D state->rx_end)) { +=09=09=09 +=09=09=09/* Look for false positive timeout */ +=09=09=09if (state->rx < state->rx_end) { +=09=09=09=09__REG(sssr) =3D SSSR_TINT; +=09=09=09=09break; +=09=09=09} +=09=09=09 +=09=09=09/* Clear timeout */ +=09=09=09__REG(ssto) =3D 0; +=09=09=09__REG(sssr) =3D SSSR_TINT | SSSR_ROR ; +=09=09=09__REG(sscr1) &=3D ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); +=09=09=09 +=09=09=09msg->actual_length +=3D msg->transfers[state->index].len; +=09 +=09=09=09if (msg->transfers[state->index].cs_change)=09 +=09=09=09=09/* Fix me, need to handle cs polarity */ +=09=09=09=09GPSR(state->gpio) =3D GPIO_bit(state->gpio); + +=09=09=09/* Schedule transfer tasklet */ +=09=09=09++state->index; +=09=09=09tasklet_schedule(&drv_data->pump_transfers); +=09=09=09 +=09=09=09return IRQ_HANDLED; +=09=09} +=09} +=09 +=09return IRQ_HANDLED; +} + +static void pump_transfers(unsigned long data) +{ +=09struct master_data *drv_data =3D (struct master_data *)data; +=09struct spi_message *message =3D drv_data->cur_msg; +=09struct chip_data *chip; +=09struct transfer_state * state; +=09struct spi_transfer *transfer; +=09u32 sscr1 =3D drv_data->sscr1; +=09u32 ssto =3D drv_data->ssto; +=09 +=09if (!message) { +=09=09printk(KERN_ERR "pxs2xx_spi_ssp: bad pump_transfers " +=09=09=09=09"schedule\n"); +=09=09tasklet_schedule(&drv_data->pump_messages); +=09=09return; +=09} +=09 +=09state =3D (struct transfer_state *)message->state; +=09if (!state) { +=09=09printk(KERN_ERR "pxs2xx_spi_ssp: bad message state\n"); +=09=09drv_data->cur_msg =3D NULL; +=09=09tasklet_schedule(&drv_data->pump_messages); +=09=09return; +=09} +=09 +=09chip =3D spi_get_ctldata(message->dev); +=09if (!chip) { +=09=09printk(KERN_ERR "pxs2xx_spi_ssp: bad chip data\n"); +=09=09drv_data->cur_msg =3D NULL; +=09=09tasklet_schedule(&drv_data->pump_messages); +=09=09return; +=09} +=09 +=09/* Handle for abort */ +=09if (state->index =3D=3D -2) { +=09=09 +=09=09message->status =3D -EIO; +=09=09if (message->complete) +=09=09=09message->complete(message->context); + +=09=09drv_data->cur_msg =3D NULL; +=09=09save_state(drv_data, chip); + +=09=09tasklet_schedule(&drv_data->pump_messages);=09=09 +=09=09 +=09=09return; +=09} + +=09/* Handle end of message */ +=09if (state->index =3D=3D message->n_transfer) { +=09=09 +=09=09if (!message->transfers[state->index].cs_change)=09 +=09=09=09/* Fix me, need to handle cs polarity */ +=09=09=09GPSR(state->gpio) =3D GPIO_bit(state->gpio); +=09=09=09 +=09=09message->status =3D 0; +=09=09if (message->complete) +=09=09=09message->complete(message->context); + +=09=09drv_data->cur_msg =3D NULL; +=09=09save_state(drv_data, chip); + +=09=09tasklet_schedule(&drv_data->pump_messages);=09=09 +=09=09 +=09=09return; +=09} +=09 +=09/* Handle start of message */ +=09if (state->index =3D=3D -1) { +=09=09 +=09=09restore_state(drv_data, chip); + +=09=09flush(drv_data); + +=09=09++state->index; +=09} +=09 +=09/* Delay if requested at end of transfer*/ +=09if (state->index > 1) { +=09=09transfer =3D message->transfers + (state->index - 1); +=09=09if (transfer->delay_usecs) +=09=09=09udelay(transfer->delay_usecs); +=09} + +=09/* Setup the transfer state */=09 +=09transfer =3D message->transfers + state->index; +=09state->gpio =3D chip->cs_gpio;=09=09 +=09state->tx =3D (void *)transfer->tx_buf; +=09state->tx_end =3D state->tx + (transfer->len * chip->n_bytes); +=09state->rx =3D transfer->rx_buf; +=09state->rx_end =3D state->rx + (transfer->len * chip->n_bytes); +=09state->write =3D state->tx ? chip->write : null_writer; +=09state->read =3D state->rx ? chip->read : null_reader; +=09 +=09/* Fix me, need to handle cs polarity */ +=09GPCR(chip->cs_gpio) =3D GPIO_bit(chip->cs_gpio); +=09 +=09/* Go baby, go */ +=09__REG(ssto) =3D chip->timeout; +=09__REG(sscr1) |=3D (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); +} + + +static void pump_messages(unsigned long data) +{ +=09struct master_data *drv_data =3D (struct master_data *)data; + +=09spin_lock(&drv_data->lock); + +=09/* Check for list empty */=09 +=09if (list_empty(&drv_data->queue)) { +=09=09spin_unlock(&drv_data->lock); +=09=09return; +=09} +=09 +=09/* Check to see if we are already running */ +=09if (drv_data->cur_msg) { +=09=09spin_unlock(&drv_data->lock); +=09=09return; +=09}=09=09 + +=09/* Extract head of queue and check for tasklet reschedule */ +=09drv_data->cur_msg =3D list_entry(drv_data->queue.next, +=09=09=09=09=09struct spi_message, queue); +=09list_del_init(&drv_data->cur_msg->queue); +=09 +=09/* Setup message transfer and schedule transfer pump */ +=09drv_data->cur_msg->state =3D &drv_data->cur_state; +=09drv_data->cur_state.index =3D -1; +=09drv_data->cur_state.len =3D 0; +=09tasklet_schedule(&drv_data->pump_transfers); +=09=09=09 +=09spin_unlock(&drv_data->lock); +} + +static int transfer(struct spi_device *spi, struct spi_message *msg) +{ +=09struct master_data *drv_data =3D class_get_devdata(&spi->master->cdev); + +=09msg->actual_length =3D 0; +=09msg->status =3D 0; +=09=09 +=09spin_lock_bh(&drv_data->lock); +=09list_add_tail(&msg->queue, &drv_data->queue); +=09spin_unlock_bh(&drv_data->lock); +=09 +=09tasklet_schedule(&drv_data->pump_messages); +=09 +=09return 0; +} + +static int setup(struct spi_device *spi) +{ +=09struct pxa2xx_spi_chip *chip_info; +=09struct chip_data *chip; +=09 +=09chip_info =3D (struct pxa2xx_spi_chip *)spi->platform_data; +=09 +=09/* Only alloc on first setup */ +=09chip =3D spi_get_ctldata(spi); +=09if (chip =3D=3D NULL) { +=09=09chip =3D kcalloc(1, sizeof(struct chip_data), GFP_KERNEL); +=09=09if (!chip) +=09=09=09return -ENOMEM; + +=09=09spi->mode =3D chip_info->mode; +=09=09spi->bits_per_word =3D chip_info->bits_per_word; +=09} +=09 +=09chip->cs_gpio =3D chip_info->chip_select_gpio; +=09chip->cr0 =3D SSCR0_SerClkDiv((MAX_SPEED_HZ / spi->max_speed_hz) + 2) +=09=09=09| SSCR0_Motorola +=09=09=09| SSCR0_DataSize(spi->bits_per_word) +=09=09=09| SSCR0_SSE +=09=09=09| (spi->bits_per_word > 16 ? SSCR0_EDSS : 0); +=09chip->cr1 =3D SSCR1_RxTresh(chip_info->rx_threshold) +=09=09=09| SSCR1_TxTresh(chip_info->tx_threshold) +=09=09=09| (((spi->mode & SPI_CPHA) !=3D 0) << 4) +=09=09=09| (((spi->mode & SPI_CPOL) !=3D 0) << 3); +=09chip->to =3D 0; +=09chip->psp =3D 0; +=09chip->timeout =3D (chip_info->timeout_microsecs * 10000) / 2712; +=09 +=09if (spi->bits_per_word <=3D 8) { +=09=09chip->n_bytes =3D 1; +=09=09chip->read =3D u8_reader; +=09=09chip->write =3D u8_writer; +=09} +=09else if (spi->bits_per_word <=3D 16) { +=09=09chip->n_bytes =3D 2; +=09=09chip->read =3D u16_reader; +=09=09chip->write =3D u16_writer; +=09} +=09else if (spi->bits_per_word <=3D 32) { +=09=09chip->n_bytes =3D 4; +=09=09chip->read =3D u32_reader; +=09=09chip->write =3D u32_writer; +=09} +=09else { +=09=09printk(KERN_ERR "pxa2xx_spi_ssp: invalid wordsize\n"); +=09=09kfree(chip); +=09=09return -ENODEV; +=09} +=09=09 +=09spi_set_ctldata(spi, chip); +=09 +=09dev_dbg(&spi->dev, "gpio=3D%u sscr0=3D0x%08x sscr1=3D0x%08x " +=09=09=09=09"ssto=3D0x%08x sspsp=3D0x%08x\n", +=09=09=09=09chip->cs_gpio, chip->cr0, +=09=09=09=09chip->cr1, chip->to, chip->psp); +=09=09=09 +=09return 0; +} + +static void cleanup(const struct spi_device *spi) +{ +=09struct chip_data *chip =3D spi_get_ctldata((struct spi_device *)spi); +=09 +=09if (chip) +=09=09kfree(chip); +=09 +=09dev_dbg(&spi->dev, "spi_device %u.%u cleanup\n", +=09=09=09=09spi->master->bus_num, spi->chip_select); +} + +static int probe(struct device *dev) +{ +=09struct platform_device *pdev =3D to_platform_device(dev); +=09struct pxa2xx_spi_master *platform_info; +=09struct spi_master *master; +=09struct master_data *drv_data =3D 0; +=09struct resource *memory_resource; +=09int irq; +=09int status =3D 0; + +=09platform_info =3D (struct pxa2xx_spi_master *)pdev->dev.platform_data; +=09 +=09master =3D spi_alloc_master(dev, sizeof(struct master_data)); +=09if (!master) +=09=09return -ENOMEM; +=09drv_data =3D class_get_devdata(&master->cdev); +=09drv_data->master =3D master;=09 +=09=09 +=09INIT_LIST_HEAD(&drv_data->queue); +=09spin_lock_init(&drv_data->lock); + +=09tasklet_init(&drv_data->pump_messages, +=09=09=09pump_messages, +=09=09=09(unsigned long)drv_data); + +=09tasklet_init(&drv_data->pump_transfers, +=09=09=09pump_transfers, +=09=09=09(unsigned long)drv_data); +=09 +=09master->bus_num =3D platform_info->bus_num; +=09master->num_chipselect =3D platform_info->num_chipselect; +=09master->cleanup =3D cleanup; +=09master->setup =3D setup; +=09master->transfer =3D transfer; +=09 +=09/* Setup register addresses */ +=09memory_resource =3D platform_get_resource(pdev, IORESOURCE_MEM, 0); +=09if (!memory_resource) { +=09=09dev_dbg(dev, "can not find platform io memory\n"); +=09=09status =3D -ENODEV; +=09=09goto out_error_memory; +=09} +=09 +=09drv_data->sscr0 =3D memory_resource->start + 0x00000000; +=09drv_data->sscr1 =3D memory_resource->start + 0x00000004; +=09drv_data->sssr =3D memory_resource->start + 0x00000008; +=09drv_data->ssitr =3D memory_resource->start + 0x0000000c; +=09drv_data->ssdr =3D memory_resource->start + 0x00000010; +=09drv_data->ssto =3D memory_resource->start + 0x00000028; +=09drv_data->sspsp =3D memory_resource->start + 0x0000002c; +=09 +=09/* Attach to IRQ */ +=09irq =3D platform_get_irq(pdev, 0); +=09if (irq =3D=3D 0) { +=09=09dev_dbg(dev, "problem getting IORESOURCE_IRQ[0]\n"); +=09=09status =3D -ENODEV; +=09=09goto out_error_memory; +=09} +=09 +=09status =3D request_irq(irq, ssp_int, SA_INTERRUPT, dev->bus_id, drv_dat= a); +=09if (status < 0) { +=09=09dev_dbg(dev, "problem requesting IORESOURCE_IRQ %u\n", irq); +=09=09goto out_error_memory; +=09} +=09 +=09/* Enable SOC clock */ +=09pxa_set_cken(platform_info->clock_enable, 1); +=09=09 +=09/* Load default SSP configuration */ +=09__REG(drv_data->sscr0) =3D 0; +=09__REG(drv_data->sscr1) =3D SSCR1_RxTresh(4) | SSCR1_TxTresh(12); +=09__REG(drv_data->sscr0) =3D SSCR0_SerClkDiv(2) +=09=09=09=09=09| SSCR0_Motorola +=09=09=09=09=09| SSCR0_DataSize(8); +=09__REG(drv_data->ssto) =3D 0; +=09__REG(drv_data->sspsp) =3D 0; +=09 +=09dev_set_drvdata(dev, master); +=09status =3D spi_register_master(master); +=09if (status !=3D 0) { +=09=09goto out_error_irq; +=09} +=09=09 +=09return status; + +out_error_irq: +=09free_irq(irq, drv_data); +=09 +out_error_memory: +=09class_device_put(&master->cdev); + +=09return status; +} + +static int remove(struct device *dev) +{ +=09struct platform_device *pdev =3D to_platform_device(dev); +=09struct spi_master *master =3D dev_get_drvdata(dev); +=09struct master_data *drv_data =3D class_get_devdata(&master->cdev); +=09struct pxa2xx_spi_master *platform_info; +=09 +=09int irq; +=09unsigned long flags; +=09 +=09platform_info =3D (struct pxa2xx_spi_master *)pdev->dev.platform_data; + +=09spin_lock_irqsave(&drv_data->lock, flags); + +=09__REG(drv_data->sscr0) =3D 0; +=09pxa_set_cken(platform_info->clock_enable, 0); + +=09irq =3D platform_get_irq(pdev, 0); +=09if (irq !=3D 0) +=09=09free_irq(irq, drv_data); +=09 +=09spin_unlock_irqrestore(&drv_data->lock, flags); + +=09spi_unregister_master(master); +=09 +=09return 0; +} + +static struct device_driver driver =3D { +=09.name =3D "pxa2xx-spi-ssp", +=09.bus =3D &platform_bus_type, +=09.owner =3D THIS_MODULE, +=09.probe =3D probe, +=09.remove =3D remove, +}; + +static int pxa2xx_spi_ssp_init(void) +{ +=09driver_register(&driver); +=09 +=09return 0; +} +module_init(pxa2xx_spi_ssp_init); + +static void pxa2xx_spi_ssp_exit(void) +{ +=09driver_unregister(&driver); +} +module_exit(pxa2xx_spi_ssp_exit); --- linux-2.6.12-spi/include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h=091969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h=092005-1= 0-04 12:50:22.922007000 -0700 @@ -0,0 +1,36 @@ +/* Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 PXA2XX_SPI_SSP_H_ +#define PXA2XX_SPI_SSP_H_ + +struct pxa2xx_spi_master { +=09u16 bus_num; +=09u32 clock_enable; +=09u16 num_chipselect; +}; + +struct pxa2xx_spi_chip { +=09u8 mode; +=09u8 tx_threshold; +=09u8 rx_threshold; +=09u8 bits_per_word; +=09u16 chip_select_gpio; +=09u32 timeout_microsecs; +}; + +#endif /*PXA2XX_SPI_SSP_H_*/ |
From: Stephen S. <sg...@gm...> - 2005-10-05 04:40:54
|
Following this will be two REPOSTED patches (I can not operate an e-mail client correctly, lost all my tabs and my brain), releasing an initial "SPI controller" implementation running on David Brownell's "simple SPI framework" and a prototype "SPI protocol" driver for the Cirrus Logic CS8415A S/PDIF decoder chip. The controller should run on any PXA2xx SSP port and has been tested on the PXA255 NSSP port. Complete board setup and description facilities per the the SPI framework are supported. Your comments and suggestions encouraged! You can e-mail me directly if you have any question regarding running SPI controller on your board. Thank you David for helping me make this real! Stephen Street "Infected by numerous newbie problems" |
From: Stephen S. <sg...@gm...> - 2005-10-05 04:21:22
|
This is a preliminary "SPI protocol" driver for the Cirrus Logic CS8415A S/PDIF decoder chip. This driver demonstrates some but not all of the features of David Brownell's "simple SPI framework" and is intended to demonstrate and test the PXA SSP driver posted previously. ---- snip ---- drivers/spi/cs8415a.c | 561 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/spi/cirrus.h | 200 +++++++++++++++ include/linux/spi/cs8415a.h | 156 ++++++++++++ 3 files changed, 917 insertions(+) --- linux-2.6.12-spi/include/linux/spi/cirrus.h=091969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/include/linux/spi/cirrus.h=092005-10-04 13:05:06.897768000 -0700 @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 CIRRUS_H_ +#define CIRRUS_H_ + +#include <linux/list.h> +#include <linux/spi.h> +#include <linux/spinlock.h> +#include <linux/device.h> + +#define READ_CMD 0x21 +#define WRITE_CMD 0x20 +#define TO_MAP(x) ((x & 0x7f) | 0x80) +#define MAX_POOL_NAME_SIZE 64 + +struct cirrus_message_pool { +}; + +struct cirrus_notification { +}; + +struct cirrus_pooled_message { +}; + +inline void _cirrus_setup_async(struct spi_message *message, +=09=09=09=09=09void (*chip_callback)(void *context), +=09=09=09=09=09void (*user_callback)(int length)) +{ +}; + +inline void *__cirrus_pool_alloc(struct cirrus_message_pool *pool) +{ +=09return NULL;=09 +} + +inline void __cirrus_pool_free(struct cirrus_message_pool *pool, void* ptr= ) +{ +} + +inline struct spi_message *_cirrus_alloc_read(struct cirrus_message_pool *pool, +=09=09=09=09=09=09=09u8 address, +=09=09=09=09=09=09=09const char* buffer, +=09=09=09=09=09=09=09size_t length) +{ +=09return NULL; +} + +inline struct spi_message *_cirrus_alloc_write(struct cirrus_message_pool *pool, +=09=09=09=09=09=09=09u8 address, +=09=09=09=09=09=09=09unsigned char* buffer, +=09=09=09=09=09=09=09unsigned int length) +{ +=09return NULL; +} + +inline void _cirrus_free(struct cirrus_message_pool *pool, +=09=09=09=09struct spi_message* message) +{ +} + +inline struct cirrus_message_pool *_cirrus_create_pool(const char* name, +=09=09=09=09=09=09=09size_t pool_size, +=09=09=09=09=09=09=09size_t max_message_size) +{ +=09return NULL; +} + +inline void _cirrus_release_pool(struct cirrus_message_pool *pool) +{ +} + +inline ssize_t _cirrus_read_sync(struct device *dev, u8 address, +=09=09=09=09=09const char *buffer, size_t length) +{ +=09u8 map_buffer[] =3D { WRITE_CMD,=09TO_MAP(address) }; +=09u8 read_cmd_buffer[] =3D { READ_CMD }; +=09struct spi_transfer transfers[3]; +=09struct spi_message message; +=09 +=09transfers[0].tx_buf =3D map_buffer; +=09transfers[0].rx_buf =3D NULL; +=09transfers[0].len =3D ARRAY_SIZE(map_buffer); +=09transfers[0].cs_change =3D 0; +=09transfers[0].delay_usecs =3D 0; + +=09transfers[1].tx_buf =3D read_cmd_buffer; +=09transfers[1].rx_buf =3D NULL; +=09transfers[1].len =3D ARRAY_SIZE(read_cmd_buffer); +=09transfers[1].cs_change =3D 1; +=09transfers[1].delay_usecs =3D 0; +=09 +=09transfers[2].tx_buf =3D NULL; +=09transfers[2].rx_buf =3D (void *)buffer; +=09transfers[2].len =3D length; +=09transfers[2].cs_change =3D 0; +=09transfers[2].delay_usecs =3D 0; + +=09message.transfers =3D transfers; +=09message.n_transfer =3D ARRAY_SIZE(transfers); + +=09spi_sync(to_spi_device(dev), &message); +=09 +=09return message.status < 0 ? message.status : message.actual_length; +} + +inline int _cirrus_write_sync(struct device *dev, u8 address, +=09=09=09=09const unsigned char *buffer, +=09=09=09=09unsigned int length) +{ +=09u8 map_buffer[] =3D { WRITE_CMD, TO_MAP(address) }; +=09struct spi_transfer transfers[2]; +=09struct spi_message message; +=09 +=09transfers[0].tx_buf =3D map_buffer, +=09transfers[0].rx_buf =3D NULL; +=09transfers[0].len =3D ARRAY_SIZE(map_buffer), +=09transfers[0].cs_change =3D 1; +=09transfers[0].delay_usecs =3D 0; +=09 +=09transfers[1].tx_buf =3D (unsigned char *)buffer, +=09transfers[1].rx_buf =3D NULL; +=09transfers[1].len =3D length, +=09transfers[1].cs_change =3D 0; +=09transfers[1].delay_usecs =3D 0; + +=09message.transfers =3D transfers; +=09message.n_transfer =3D ARRAY_SIZE(transfers); + +=09spi_sync(to_spi_device(dev), &message); +=09 +=09return message.status < 0 ? message.status : message.actual_length; +} + +inline int _cirrus_read_register_sync(struct device* dev, u8 address) +{ +=09u8 map_buffer[] =3D { WRITE_CMD, TO_MAP(address) }; +=09u8 read_buffer[] =3D { READ_CMD, 0 }; +=09struct spi_transfer transfers[2]; +=09struct spi_message message; +=09int status; +=09 +=09transfers[0].tx_buf =3D map_buffer, +=09transfers[0].rx_buf =3D NULL; +=09transfers[0].len =3D ARRAY_SIZE(map_buffer), +=09transfers[0].cs_change =3D 1; +=09transfers[0].delay_usecs =3D 0; + +=09transfers[1].tx_buf =3D read_buffer, +=09transfers[1].rx_buf =3D read_buffer, +=09transfers[1].len =3D ARRAY_SIZE(read_buffer), +=09transfers[1].cs_change =3D 0; +=09transfers[1].delay_usecs =3D 0; + +=09message.transfers =3D transfers; +=09message.n_transfer =3D ARRAY_SIZE(transfers); +=09 +=09status =3D spi_sync(to_spi_device(dev), &message); +=09if (status < 0) +=09=09return status; +=09 +=09return message.status < 0 ? message.status : read_buffer[1]; +} + +inline int _cirrus_write_register_sync(struct device* dev, u8 address, u8 = data) +{ +=09u8 write_buffer[] =3D { WRITE_CMD, TO_MAP(address), data }; +=09struct spi_transfer transfers[1]; +=09struct spi_message message; + +=09transfers[0].tx_buf =3D write_buffer, +=09transfers[0].rx_buf =3D NULL; +=09transfers[0].len =3D ARRAY_SIZE(write_buffer), +=09transfers[0].cs_change =3D 0; +=09transfers[0].delay_usecs =3D 0; + +=09message.transfers =3D transfers; +=09message.n_transfer =3D ARRAY_SIZE(transfers); +=09 +=09spi_sync(to_spi_device(dev), &message); +=09 +=09return message.status; +} + +#endif /*CIRRUS_H_*/ --- linux-2.6.12-spi/include/linux/spi/cs8415a.h=091969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/include/linux/spi/cs8415a.h=092005-10-04 14:19:40.422850000 -0700 @@ -0,0 +1,156 @@ +/* cs8415a.h - Definitions for the CS8415A chip + * + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 CS8415A_H_ +#define CS8415A_H_ + +#include <linux/list.h> + +#define CS8415A_CTL1 0x01 +#define CS8415A_CTL2 0x02 +#define CS8415A_CSC 0x04 +#define CS8415A_SOF 0x06 +#define CS8415A_I1S 0x07 +#define CS8415A_I2S 0x08 +#define CS8415A_I1MK 0x09 +#define CS8415A_I1MM 0x0a +#define CS8415A_I1ML 0x0b +#define CS8415A_I2MK 0x0c +#define CS8415A_I2MM 0x0d +#define CS8415A_I2ML 0x0e +#define CS8415A_RCS 0x0f +#define CS8415A_RER 0x10 +#define CS8415A_REM 0x11 +#define CS8415A_CSDBC 0x12 +#define CS8415A_UDBC 0x13 +#define CS8415A_QCSB 0x14 +#define CS8415A_ORR 0x1e +#define CS8415A_CUDB 0x20 +#define CS8415A_VER 0x7f + +#define CS8415A_SWCLK (1<<7) +#define CS8415A_MUTESAO (1<<5) +#define CS8415A_INT(x) ((x&3)<<1) +#define CS8415A_HOLD(x) ((x&3)<<5) +#define CS8415A_RMCKF (1<<4) +#define CS8415A_MMR (1<<3) +#define CS8415A_MUX(x) (x&7) +#define CS8415A_RUN (1<<6) +#define CS8415A_SOMS (1<<7) +#define CS8415A_SOSF (1<<6) +#define CS8415A_SORES(x) ((x&3)<<4) +#define CS8415A_SOJUST (1<<3) +#define CS8415A_SODEL (1<<2) +#define CS8415A_SOSPOL (1<<1) +#define CS8415A_SOLRPOL (1) +#define CS8415A_OSLIP (1<<6) +#define CS8415A_DETC (1<<2) +#define CS8415A_RERR (1) +#define CS8415A_DETU (1<<3) +#define CS8415A_QCH (1<<1) +#define CS8415A_AUX(x) ((x&f)<<4) +#define CS8415A_PRO (1<<3) +#define CS8415A_AUDIO (1<<2) +#define CS8415A_COPY (1<<1) +#define CS8415A_ORIG (1) +#define CS8415A_QCRC (1<<6) +#define CS8415A_CCRC (1<<5) +#define CS8415A_UNLOCK (1<<4) +#define CS8415A_V (1<<3) +#define CS8415A_CONF (1<<2) +#define CS8415A_BIP (1<<1) +#define CS8415A_PAR (1) +#define CS8415A_BSEL (1<<5) +#define CS8415A_CBMR (1<<4) +#define CS8415A_DETCI (1<<3) +#define CS8415A_CAM (1<<1) +#define CS8415A_CHS (1) +#define CS8415A_DETUI (1<<1) + +struct cs8415a_platform_data { +=09int enabled; +=09int muted; +=09int channel; +=09void (*mask_interrupt)(void); +=09void (*unmask_interrupt)(void); +=09int (*service_requested)(void); +}; + +struct cs8415a_event { +=09u16 events; +=09void (*event_handler)(u8 event, unsigned char *buffer, unsigned int len= gth); +=09struct list_head event_list; +}; + +extern int cs8415a_get_version(struct device *dev); + +extern int cs8415a_get_enabled(struct device *dev); + +extern int cs8415a_set_enabled(struct device *dev, int value); + +extern int cs8415a_get_muted(struct device *dev); + +extern int cs8415a_set_muted(struct device *dev, int value); + +extern int cs8415a_get_channel(struct device *dev); + +extern int cs8415a_set_channel(struct device *dev, int value); + +extern int cs8415a_is_pll_locked(struct device *dev); + +extern int cs8415a_get_channel_status(struct device *dev); + +extern int cs8415a_read_qch(struct device *dev, unsigned char *buffer); + +extern int cs8415a_read_ubit(struct device *dev, unsigned char *buffer); + +extern int cs8415a_read_cbit(struct device *dev, unsigned char* buffer); + +extern int cs8415a_add_event_handler(struct device *dev, +=09=09=09=09=09struct cs8415a_event *event); +extern int cs8415a_remove_event_handler(struct device *dev, +=09=09=09=09=09=09struct cs8415a_event *event); + +extern int _cs8415a_read_register(struct device *dev, u8 address); + +extern int _cs8415a_write_register(struct device *dev, u8 address, u8 valu= e); + +extern int _cs8415a_read(struct device *dev, u8 address, +=09=09=09=09unsigned char *buffer, unsigned int length); + +extern int _cs8415a_write(struct device *dev, u8 address, +=09=09=09=09unsigned char *buffer, unsigned int length); + +extern int _cs8415a_read_register_async(struct device *dev, u8 address, +=09=09=09=09=09=09void (*done)(int value)); + +extern int _cs8415a_write_register_async(struct device *dev, u8 address, +=09=09=09=09=09=09u8 value, +=09=09=09=09=09=09void (*done)(int status)); +extern int _cs8415a_read_async(struct device *dev, u8 address, +=09=09=09=09=09unsigned char *buffer, +=09=09=09=09=09unsigned int length, +=09=09=09=09=09void (*done)(int length)); + +extern int _cs8415a_write_async(struct device *dev, u8 address, +=09=09=09=09=09unsigned char *buffer, +=09=09=09=09=09unsigned int length, +=09=09=09=09=09void (*done)(int length)); + +#endif /*CS8415A_H_*/ --- linux-2.6.12-spi/drivers/spi/cs8415a.c=091969-12-31 16:00:00.000000000 = -0800 +++ linux-2.6.12-spi-pxa/drivers/spi/cs8415a.c=092005-10-04 14:00:12.279449000 -0700 @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/spi.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/spi.h> +#include <linux/spi/cs8415a.h> +#include <linux/spi/cirrus.h> + +#include <asm/hardirq.h> +#include <asm/semaphore.h> +#include <asm/arch/streetracer.h> + +MODULE_AUTHOR("Stephen Street"); +MODULE_DESCRIPTION("CS8415A SPI Protocol Driver"); +MODULE_LICENSE("GPL"); + +#define VALID_CS8415A_VERSION (0x41) + +struct cs8415a_driver_data { +=09spinlock_t lock; +=09struct semaphore user_lock; +=09struct cirrus_message_pool *read_async_pool; +=09struct cirrus_message_pool *write_async_pool; +=09struct list_head event_handlers; +=09int chip_version; +=09int enabled; +=09int muted; +=09int channel; +}; + +int _cs8415a_read_register(struct device *dev, u8 address) +{ +=09return _cirrus_read_register_sync(dev, address); +} +EXPORT_SYMBOL(_cs8415a_read_register); + +int _cs8415a_write_register(struct device *dev, u8 address, u8 value) +{ +=09return _cirrus_write_register_sync(dev, address, value); +} +EXPORT_SYMBOL(_cs8415a_write_register); + +int _cs8415a_read(struct device *dev, u8 address, +=09=09=09unsigned char *buffer, unsigned int length) +{ +=09return _cirrus_read_sync(dev, address, buffer, length); +} +EXPORT_SYMBOL(_cs8415a_read); + +int _cs8415a_write(struct device *dev, u8 address, +=09=09=09unsigned char *buffer, unsigned int length) +{ +=09return _cirrus_write_sync(dev, address, buffer, length); +} +EXPORT_SYMBOL(_cs8415a_write); + +int _cs8415a_read_register_async(struct device *dev, u8 address, +=09=09=09=09=09void (*done)(int value)) +{ +=09return -1; +} +EXPORT_SYMBOL(_cs8415a_read_register_async); + +int _cs8415a_write_register_async(struct device *dev, u8 address, +=09=09=09=09=09u8 value, void (*done)(int status)) +{ +=09return -1; +} +EXPORT_SYMBOL(_cs8415a_write_register_async); + +int _cs8415a_read_async(struct device *dev, u8 address, +=09=09=09=09unsigned char *buffer, unsigned int length, +=09=09=09=09void (*done)(int length)) +{ +=09return -1; +} +EXPORT_SYMBOL(_cs8415a_read_async); + +int _cs8415a_write_async(struct device *dev, u8 address, +=09=09=09=09unsigned char *buffer, unsigned int length, +=09=09=09=09void (*done)(int length)) +{ +=09return -1; +} +EXPORT_SYMBOL(_cs8415a_write_async); + +static int cs8415a_reset(struct device *dev) +{ +=09unsigned char clear[6]; +=09struct cs8415a_driver_data *driver_data =3D dev_get_drvdata(dev); +=09int status; +=09 +=09memset(clear, 0, sizeof(clear)); +=09 +=09status =3D _cs8415a_write_register(dev, CS8415A_CSC, 0); +=09if (status < 0) +=09=09return status; +=09=09 +=09status =3D _cs8415a_write_register(dev, CS8415A_CTL1, +=09=09=09=09=09=09CS8415A_INT(0) +=09=09=09=09=09=09|CS8415A_MUTESAO); +=09if (status < 0) +=09=09return status; + +=09status =3D _cs8415a_write_register(dev, CS8415A_CTL2, CS8415A_HOLD(1)); +=09if (status < 0) +=09=09return status; + +=09status =3D _cs8415a_write_register(dev, CS8415A_SOF, +=09=09=09=09=09=09CS8415A_SOMS +=09=09=09=09=09=09| CS8415A_SODEL +=09=09=09=09=09=09| CS8415A_SOLRPOL); +=09if (status < 0) +=09=09return status; + +=09status =3D _cs8415a_write(dev, CS8415A_I1MK, clear, sizeof(clear)); +=09if (status < 0) +=09=09return status; + +=09status =3D _cs8415a_read_register(dev, CS8415A_RER); +=09if (status < 0) +=09=09return status; + +=09status =3D _cs8415a_read(dev, CS8415A_I1S, clear, 2); +=09if (status < 0) +=09=09return status; +=09 +=09driver_data->enabled =3D 0; +=09driver_data->channel =3D 0; +=09driver_data->muted =3D 1; +=09 +=09return 0; +} + +static irqreturn_t cs8415a_int(int irq, void *dev_id, struct pt_regs *regs= ) +{ +=09struct cs8415a_driver_data *driver_data; +=09 +=09driver_data =3D dev_get_drvdata((struct device *)dev_id); + +=09spin_lock(&driver_data->lock); +=09 +=09spin_unlock(&driver_data->lock); +=09 +=09return IRQ_HANDLED; +} + +int cs8415a_get_version(struct device *dev) +{ +=09struct cs8415a_driver_data *driver_data =3D dev_get_drvdata(dev); +=09 +=09return driver_data->chip_version; +} +EXPORT_SYMBOL(cs8415a_get_version); + +static ssize_t version_show(struct device *dev, char *buf) +{ +=09return sprintf(buf, "0x%02x\n", cs8415a_get_version(dev)); +} + +DEVICE_ATTR(version, 0444, version_show, NULL); + +extern int cs8415a_get_enabled(struct device *dev) +{ +=09struct cs8415a_driver_data *driver_data =3D dev_get_drvdata(dev); +=09int value; + +=09if (down_interruptible(&driver_data->user_lock)) +=09=09return -ERESTARTSYS; +=09=09 +=09value =3D driver_data->enabled; +=09 +=09up(&driver_data->user_lock); +=09 +=09return value; +} +EXPORT_SYMBOL(cs8415a_get_enabled); + +extern int cs8415a_set_enabled(struct device *dev, int value) +{ +=09struct cs8415a_driver_data *driver_data =3D dev_get_drvdata(dev); +=09int status; +=09 +=09if (down_interruptible(&driver_data->user_lock)) +=09=09return -ERESTARTSYS; +=09=09 +=09status =3D _cs8415a_write_register(dev, CS8415A_CSC, +=09=09=09=09=09=09(u8)(value ? CS8415A_RUN : 0)); +=09if (status =3D=3D 0) +=09=09driver_data->enabled =3D value ? 1 : 0; +=09=09 +=09up(&driver_data->user_lock); + +=09return status; +} +EXPORT_SYMBOL(cs8415a_set_enabled); + +static ssize_t enabled_show(struct device *dev, char *buf) +{ +=09return snprintf(buf, PAGE_SIZE, "%d\n", cs8415a_get_enabled(dev)); +} + +static ssize_t enabled_store(struct device *dev, const char *buf, size_t c= ount) +{ +=09int status; +=09unsigned int value; +=09 +=09status =3D sscanf(buf, "%u", &value); +=09 +=09if (status !=3D 1 || (value !=3D 0 && value !=3D 1)) { +=09=09return -EINVAL; +=09} +=09 +=09cs8415a_set_enabled(dev, value); +=09 +=09return 1; +} + +DEVICE_ATTR(enabled, 0644, enabled_show, enabled_store); + +int cs8415a_get_muted(struct device *dev) +{ +=09struct cs8415a_driver_data *driver_data =3D dev_get_drvdata(dev); +=09int value; + +=09if (down_interruptible(&driver_data->user_lock)) +=09=09return -ERESTARTSYS; +=09 +=09value =3D driver_data->muted; +=09 +=09up(&driver_data->user_lock); +=09 +=09return value; +} +EXPORT_SYMBOL(cs8415a_get_muted); + +int cs8415a_set_muted(struct device *dev, int value) +{ +=09struct cs8415a_driver_data *driver_data =3D dev_get_drvdata(dev); +=09int status; +=09 +=09if (down_interruptible(&driver_data->user_lock)) +=09=09return -ERESTARTSYS; +=09=09 +=09status =3D _cs8415a_write_register(dev, CS8415A_CTL1, +=09=09=09=09=09=09(u8)(value?CS8415A_MUTESAO:0)); +=09if (status =3D=3D 0) +=09=09driver_data->muted =3D value ? 1 : 0; +=09 +=09up(&driver_data->user_lock); +=09 +=09return status; +} +EXPORT_SYMBOL(cs8415a_set_muted); + +static ssize_t muted_show(struct device *dev, char *buf) +{ +=09return snprintf(buf, PAGE_SIZE, "%d\n", cs8415a_get_muted(dev)); +} + +static ssize_t muted_store(struct device *dev, const char *buf, size_t cou= nt) +{ +=09int status; +=09unsigned int value; +=09 +=09status =3D sscanf(buf, "%u", &value); +=09 +=09if (status !=3D 1 || (value !=3D 0 && value !=3D 1)) { +=09=09return -EINVAL; +=09} +=09 +=09cs8415a_set_muted(dev, value); +=09 +=09return 1; +} +DEVICE_ATTR(muted, 0644, muted_show, muted_store); + +int cs8415a_get_channel(struct device *dev) +{ +=09struct cs8415a_driver_data *driver_data =3D dev_get_drvdata(dev); +=09int value; +=09 +=09if (down_interruptible(&driver_data->user_lock)) +=09=09return -ERESTARTSYS; +=09 +=09value =3D driver_data->channel; +=09 +=09up(&driver_data->user_lock); +=09 +=09return value; +} +EXPORT_SYMBOL(cs8415a_get_channel); + +int cs8415a_set_channel(struct device *dev, int value) +{ +=09struct cs8415a_driver_data *driver_data =3D dev_get_drvdata(dev); +=09int status; +=09 +=09if (down_interruptible(&driver_data->user_lock)) +=09=09return -ERESTARTSYS; +=09 +=09status =3D _cs8415a_write_register(dev, CS8415A_CTL2, +=09=09=09=09=09=09(u8)(CS8415A_HOLD(1) +=09=09=09=09=09=09=09| CS8415A_MUX(value))); +=09if (status =3D=3D 0) +=09=09driver_data->channel =3D value; +=09=09 +=09up(&driver_data->user_lock); + +=09return status; +} +EXPORT_SYMBOL(cs8415a_set_channel); + +static ssize_t channel_show(struct device *dev, char *buf) +{ +=09return snprintf(buf, PAGE_SIZE, "%d\n", cs8415a_get_channel(dev)); +} + +static ssize_t channel_store(struct device *dev, const char *buf, size_t c= ount) +{ +=09int status; +=09unsigned int value; +=09 +=09status =3D sscanf(buf, "%u", &value); +=09 +=09if (status !=3D 1 || value < 0 || value > 7) { +=09=09return -EINVAL; +=09} +=09 +=09cs8415a_set_channel(dev, value); +=09 +=09return 1; +} +DEVICE_ATTR(channel, 0644, channel_show, channel_store); + +int cs8415a_get_channel_status(struct device *dev) +{ +=09struct cs8415a_driver_data *driver_data =3D dev_get_drvdata(dev); +=09int value; +=09 +=09if (down_interruptible(&driver_data->user_lock)) +=09=09return -ERESTARTSYS; +=09 +=09value =3D _cs8415a_read_register(dev, CS8415A_RCS); +=09 +=09up(&driver_data->user_lock); +=09 +=09return value; +} +static ssize_t channel_status_show(struct device *dev, char *buf) +{ +=09return snprintf(buf, PAGE_SIZE, "0x%01x\n", +=09=09=09=09cs8415a_get_channel_status(dev)); +} +DEVICE_ATTR(channel_status, 0444, channel_status_show, NULL); + +static int cs8415a_spi_probe(struct device *dev) +{ +=09struct spi_device *spi_dev; +=09struct cs8415a_driver_data *driver_data; +=09int status; + +=09/* Allocate driver data */ +=09driver_data =3D kcalloc(1, sizeof(struct cs8415a_driver_data), +=09=09=09=09GFP_KERNEL); +=09if (!driver_data) { +=09=09dev_err(dev, "problem allocating driver memory\n"); +=09=09status =3D -ENOMEM; +=09=09goto out_error; +=09} +=09 +=09spin_lock_init(&driver_data->lock); +=09init_MUTEX(&driver_data->user_lock); +=09INIT_LIST_HEAD(&driver_data->event_handlers); +=09 +=09dev_set_drvdata(dev, driver_data); +=09 +=09/* Initialize the message pool */ +/* +=09driver_data->read_async_pool =3D _cirrus_create_pool(0); +=09driver_data->write_async_pool =3D _cirrus_create_pool(0); +=09if (!driver_data->read_async_pool || !driver_data->write_async_pool) { +=09=09dev_err(dev, "problem creating pools\n"); +=09=09status =3D -ENOMEM; +=09=09goto out_error_memalloc; +=09} +*/ +=09 +=09/* Read and validate version number */ +=09driver_data->chip_version =3D _cs8415a_read_register(dev, CS8415A_VER); +=09if (driver_data->chip_version < 0) { +=09=09dev_err(dev, "problem reading chip version\n"); +=09=09status =3D -ENODEV; +=09=09goto out_error_memalloc; +=09} + +=09if (driver_data->chip_version !=3D VALID_CS8415A_VERSION) { +=09=09dev_err(dev, "problem reading chip version " +=09=09=09=09"found version 0x%02x\n", +=09=09=09=09driver_data->chip_version); +=09=09status =3D -ENODEV; +=09=09goto out_error_memalloc; +=09} +=09 +=09spi_dev =3D to_spi_device(dev); + +=09/* Attach to IRQ */=09 +=09if (spi_dev->irq =3D=3D 0) { +=09=09dev_err(dev, "problem getting irq\n"); +=09=09status =3D -ENODEV; +=09=09goto out_error_memalloc; +=09} + +=09status =3D request_irq(spi_dev->irq, cs8415a_int, +=09=09=09=09SA_SHIRQ, dev->bus_id, dev); +=09if (status < 0) { +=09=09dev_err(dev, "problem requesting IRQ %u\n", spi_dev->irq); +=09=09status =3D -ENODEV; +=09=09goto out_error_memalloc; +=09} +=09 +=09status =3D cs8415a_reset(dev); +=09if (status !=3D 0) { +=09=09dev_err(dev, "problem resetting\n"); +=09=09status =3D -ENODEV; +=09=09goto out_error_irqalloc; +=09} +=09 +=09status =3D device_create_file(dev, &dev_attr_version); +=09if (status < 0) { +=09=09dev_err(dev, "problem creating attribute %s\n", +=09=09=09=09dev_attr_version.attr.name); +=09=09goto out_error_attralloc; +=09} +=09 +=09status =3D device_create_file(dev, &dev_attr_enabled); +=09if (status < 0) { +=09=09dev_err(dev, "problem creating attribute %s\n", +=09=09=09=09dev_attr_enabled.attr.name); +=09=09goto out_error_attralloc; +=09} +=09 +=09status =3D device_create_file(dev, &dev_attr_muted); +=09if (status < 0) { +=09=09dev_err(dev, "problem creating attribute %s\n", +=09=09=09=09dev_attr_muted.attr.name); +=09=09goto out_error_attralloc; +=09} +=09 +=09status =3D device_create_file(dev, &dev_attr_channel); +=09if (status < 0) { +=09=09dev_err(dev, "problem creating attribute %s\n", +=09=09=09=09dev_attr_channel.attr.name); +=09=09goto out_error_attralloc; +=09} + +=09status =3D device_create_file(dev, &dev_attr_channel_status); +=09if (status < 0) { +=09=09dev_err(dev, "problem creating attribute %s\n", +=09=09=09=09dev_attr_channel_status.attr.name); +=09=09goto out_error_attralloc; +=09} + +=09dev_dbg(dev, "found chip version 0x%02x\n", driver_data->chip_version); + +=09return 0; +out_error_attralloc: +=09device_remove_file(dev, &dev_attr_version); +=09device_remove_file(dev, &dev_attr_enabled); +=09device_remove_file(dev, &dev_attr_muted); +=09device_remove_file(dev, &dev_attr_channel); +=09device_remove_file(dev, &dev_attr_channel_status); +=09 +out_error_irqalloc: +=09free_irq(spi_dev->irq, dev); + +out_error_memalloc: +=09if (driver_data->read_async_pool) +=09=09_cirrus_release_pool(driver_data->read_async_pool); +=09=09 +=09if (driver_data->write_async_pool) +=09=09_cirrus_release_pool(driver_data->read_async_pool); +=09 +=09dev_set_drvdata(dev, NULL); + +=09kfree(driver_data); +=09 +out_error: +=09 +=09return status; +} + +static int cs8415a_spi_remove(struct device *dev) +{ +=09struct spi_device *spi_dev =3D to_spi_device(dev); +=09struct cs8415a_driver_data *driver_data =3D dev_get_drvdata(dev); +=09unsigned long flags; + +=09cs8415a_reset(dev); + +=09spin_lock_irqsave(&driver_data->lock, flags); + +=09if (spi_dev->irq !=3D 0) +=09=09free_irq(spi_dev->irq, dev); +=09 +=09spin_unlock_irqrestore(&driver_data->lock, flags); + +=09device_remove_file(dev, &dev_attr_version); +=09device_remove_file(dev, &dev_attr_enabled); +=09device_remove_file(dev, &dev_attr_muted); +=09device_remove_file(dev, &dev_attr_channel); +=09device_remove_file(dev, &dev_attr_channel_status); + +=09_cirrus_release_pool(driver_data->read_async_pool);=09 +=09_cirrus_release_pool(driver_data->write_async_pool);=09 +=09 +=09kfree(driver_data); +=09 +=09return 0; +} + +struct device_driver cs8415a_spi =3D { +=09.name =3D "cs8415a", +=09.bus =3D &spi_bus_type, +=09.owner =3D THIS_MODULE, +=09.probe =3D cs8415a_spi_probe, +=09.remove =3D cs8415a_spi_remove, +}; + +static int __init cs8415a_spi_init(void) +{ +=09return driver_register(&cs8415a_spi); +} +module_init(cs8415a_spi_init); + +static void __exit cs8415a_spi_exit(void) +{ +=09driver_unregister(&cs8415a_spi); +} +module_exit(cs8415a_spi_exit); |
From: Stephen S. <st...@st...> - 2005-10-04 22:28:37
|
This is a preliminary "SPI protocol" driver for the Cirrus Logic CS8415A SPD/IF decoder chip. This driver demostrates some but not all of the features of David Brownell's "simple SPI framework" and is intended to demonstrate and test the PXA SSP driver posted previously. ---- snip ---- drivers/spi/cs8415a.c | 561 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/spi/cirrus.h | 200 +++++++++++++++ include/linux/spi/cs8415a.h | 156 ++++++++++++ 3 files changed, 917 insertions(+) --- linux-2.6.12-spi/include/linux/spi/cirrus.h 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/include/linux/spi/cirrus.h 2005-10-04 13:05:06.897768000 -0700 @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 CIRRUS_H_ +#define CIRRUS_H_ + +#include <linux/list.h> +#include <linux/spi.h> +#include <linux/spinlock.h> +#include <linux/device.h> + +#define READ_CMD 0x21 +#define WRITE_CMD 0x20 +#define TO_MAP(x) ((x & 0x7f) | 0x80) +#define MAX_POOL_NAME_SIZE 64 + +struct cirrus_message_pool { +}; + +struct cirrus_notification { +}; + +struct cirrus_pooled_message { +}; + +inline void _cirrus_setup_async(struct spi_message *message, + void (*chip_callback)(void *context), + void (*user_callback)(int length)) +{ +}; + +inline void *__cirrus_pool_alloc(struct cirrus_message_pool *pool) +{ + return NULL; +} + +inline void __cirrus_pool_free(struct cirrus_message_pool *pool, void* ptr) +{ +} + +inline struct spi_message *_cirrus_alloc_read(struct cirrus_message_pool *pool, + u8 address, + const char* buffer, + size_t length) +{ + return NULL; +} + +inline struct spi_message *_cirrus_alloc_write(struct cirrus_message_pool *pool, + u8 address, + unsigned char* buffer, + unsigned int length) +{ + return NULL; +} + +inline void _cirrus_free(struct cirrus_message_pool *pool, + struct spi_message* message) +{ +} + +inline struct cirrus_message_pool *_cirrus_create_pool(const char* name, + size_t pool_size, + size_t max_message_size) +{ + return NULL; +} + +inline void _cirrus_release_pool(struct cirrus_message_pool *pool) +{ +} + +inline ssize_t _cirrus_read_sync(struct device *dev, u8 address, + const char *buffer, size_t length) +{ + u8 map_buffer[] = { WRITE_CMD, TO_MAP(address) }; + u8 read_cmd_buffer[] = { READ_CMD }; + struct spi_transfer transfers[3]; + struct spi_message message; + + transfers[0].tx_buf = map_buffer; + transfers[0].rx_buf = NULL; + transfers[0].len = ARRAY_SIZE(map_buffer); + transfers[0].cs_change = 0; + transfers[0].delay_usecs = 0; + + transfers[1].tx_buf = read_cmd_buffer; + transfers[1].rx_buf = NULL; + transfers[1].len = ARRAY_SIZE(read_cmd_buffer); + transfers[1].cs_change = 1; + transfers[1].delay_usecs = 0; + + transfers[2].tx_buf = NULL; + transfers[2].rx_buf = (void *)buffer; + transfers[2].len = length; + transfers[2].cs_change = 0; + transfers[2].delay_usecs = 0; + + message.transfers = transfers; + message.n_transfer = ARRAY_SIZE(transfers); + + spi_sync(to_spi_device(dev), &message); + + return message.status < 0 ? message.status : message.actual_length; +} + +inline int _cirrus_write_sync(struct device *dev, u8 address, + const unsigned char *buffer, + unsigned int length) +{ + u8 map_buffer[] = { WRITE_CMD, TO_MAP(address) }; + struct spi_transfer transfers[2]; + struct spi_message message; + + transfers[0].tx_buf = map_buffer, + transfers[0].rx_buf = NULL; + transfers[0].len = ARRAY_SIZE(map_buffer), + transfers[0].cs_change = 1; + transfers[0].delay_usecs = 0; + + transfers[1].tx_buf = (unsigned char *)buffer, + transfers[1].rx_buf = NULL; + transfers[1].len = length, + transfers[1].cs_change = 0; + transfers[1].delay_usecs = 0; + + message.transfers = transfers; + message.n_transfer = ARRAY_SIZE(transfers); + + spi_sync(to_spi_device(dev), &message); + + return message.status < 0 ? message.status : message.actual_length; +} + +inline int _cirrus_read_register_sync(struct device* dev, u8 address) +{ + u8 map_buffer[] = { WRITE_CMD, TO_MAP(address) }; + u8 read_buffer[] = { READ_CMD, 0 }; + struct spi_transfer transfers[2]; + struct spi_message message; + int status; + + transfers[0].tx_buf = map_buffer, + transfers[0].rx_buf = NULL; + transfers[0].len = ARRAY_SIZE(map_buffer), + transfers[0].cs_change = 1; + transfers[0].delay_usecs = 0; + + transfers[1].tx_buf = read_buffer, + transfers[1].rx_buf = read_buffer, + transfers[1].len = ARRAY_SIZE(read_buffer), + transfers[1].cs_change = 0; + transfers[1].delay_usecs = 0; + + message.transfers = transfers; + message.n_transfer = ARRAY_SIZE(transfers); + + status = spi_sync(to_spi_device(dev), &message); + if (status < 0) + return status; + + return message.status < 0 ? message.status : read_buffer[1]; +} + +inline int _cirrus_write_register_sync(struct device* dev, u8 address, u8 data) +{ + u8 write_buffer[] = { WRITE_CMD, TO_MAP(address), data }; + struct spi_transfer transfers[1]; + struct spi_message message; + + transfers[0].tx_buf = write_buffer, + transfers[0].rx_buf = NULL; + transfers[0].len = ARRAY_SIZE(write_buffer), + transfers[0].cs_change = 0; + transfers[0].delay_usecs = 0; + + message.transfers = transfers; + message.n_transfer = ARRAY_SIZE(transfers); + + spi_sync(to_spi_device(dev), &message); + + return message.status; +} + +#endif /*CIRRUS_H_*/ --- linux-2.6.12-spi/include/linux/spi/cs8415a.h 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/include/linux/spi/cs8415a.h 2005-10-04 14:19:40.422850000 -0700 @@ -0,0 +1,156 @@ +/* cs8415a.h - Definitions for the CS8415A chip + * + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 CS8415A_H_ +#define CS8415A_H_ + +#include <linux/list.h> + +#define CS8415A_CTL1 0x01 +#define CS8415A_CTL2 0x02 +#define CS8415A_CSC 0x04 +#define CS8415A_SOF 0x06 +#define CS8415A_I1S 0x07 +#define CS8415A_I2S 0x08 +#define CS8415A_I1MK 0x09 +#define CS8415A_I1MM 0x0a +#define CS8415A_I1ML 0x0b +#define CS8415A_I2MK 0x0c +#define CS8415A_I2MM 0x0d +#define CS8415A_I2ML 0x0e +#define CS8415A_RCS 0x0f +#define CS8415A_RER 0x10 +#define CS8415A_REM 0x11 +#define CS8415A_CSDBC 0x12 +#define CS8415A_UDBC 0x13 +#define CS8415A_QCSB 0x14 +#define CS8415A_ORR 0x1e +#define CS8415A_CUDB 0x20 +#define CS8415A_VER 0x7f + +#define CS8415A_SWCLK (1<<7) +#define CS8415A_MUTESAO (1<<5) +#define CS8415A_INT(x) ((x&3)<<1) +#define CS8415A_HOLD(x) ((x&3)<<5) +#define CS8415A_RMCKF (1<<4) +#define CS8415A_MMR (1<<3) +#define CS8415A_MUX(x) (x&7) +#define CS8415A_RUN (1<<6) +#define CS8415A_SOMS (1<<7) +#define CS8415A_SOSF (1<<6) +#define CS8415A_SORES(x) ((x&3)<<4) +#define CS8415A_SOJUST (1<<3) +#define CS8415A_SODEL (1<<2) +#define CS8415A_SOSPOL (1<<1) +#define CS8415A_SOLRPOL (1) +#define CS8415A_OSLIP (1<<6) +#define CS8415A_DETC (1<<2) +#define CS8415A_RERR (1) +#define CS8415A_DETU (1<<3) +#define CS8415A_QCH (1<<1) +#define CS8415A_AUX(x) ((x&f)<<4) +#define CS8415A_PRO (1<<3) +#define CS8415A_AUDIO (1<<2) +#define CS8415A_COPY (1<<1) +#define CS8415A_ORIG (1) +#define CS8415A_QCRC (1<<6) +#define CS8415A_CCRC (1<<5) +#define CS8415A_UNLOCK (1<<4) +#define CS8415A_V (1<<3) +#define CS8415A_CONF (1<<2) +#define CS8415A_BIP (1<<1) +#define CS8415A_PAR (1) +#define CS8415A_BSEL (1<<5) +#define CS8415A_CBMR (1<<4) +#define CS8415A_DETCI (1<<3) +#define CS8415A_CAM (1<<1) +#define CS8415A_CHS (1) +#define CS8415A_DETUI (1<<1) + +struct cs8415a_platform_data { + int enabled; + int muted; + int channel; + void (*mask_interrupt)(void); + void (*unmask_interrupt)(void); + int (*service_requested)(void); +}; + +struct cs8415a_event { + u16 events; + void (*event_handler)(u8 event, unsigned char *buffer, unsigned int length); + struct list_head event_list; +}; + +extern int cs8415a_get_version(struct device *dev); + +extern int cs8415a_get_enabled(struct device *dev); + +extern int cs8415a_set_enabled(struct device *dev, int value); + +extern int cs8415a_get_muted(struct device *dev); + +extern int cs8415a_set_muted(struct device *dev, int value); + +extern int cs8415a_get_channel(struct device *dev); + +extern int cs8415a_set_channel(struct device *dev, int value); + +extern int cs8415a_is_pll_locked(struct device *dev); + +extern int cs8415a_get_channel_status(struct device *dev); + +extern int cs8415a_read_qch(struct device *dev, unsigned char *buffer); + +extern int cs8415a_read_ubit(struct device *dev, unsigned char *buffer); + +extern int cs8415a_read_cbit(struct device *dev, unsigned char* buffer); + +extern int cs8415a_add_event_handler(struct device *dev, + struct cs8415a_event *event); +extern int cs8415a_remove_event_handler(struct device *dev, + struct cs8415a_event *event); + +extern int _cs8415a_read_register(struct device *dev, u8 address); + +extern int _cs8415a_write_register(struct device *dev, u8 address, u8 value); + +extern int _cs8415a_read(struct device *dev, u8 address, + unsigned char *buffer, unsigned int length); + +extern int _cs8415a_write(struct device *dev, u8 address, + unsigned char *buffer, unsigned int length); + +extern int _cs8415a_read_register_async(struct device *dev, u8 address, + void (*done)(int value)); + +extern int _cs8415a_write_register_async(struct device *dev, u8 address, + u8 value, + void (*done)(int status)); +extern int _cs8415a_read_async(struct device *dev, u8 address, + unsigned char *buffer, + unsigned int length, + void (*done)(int length)); + +extern int _cs8415a_write_async(struct device *dev, u8 address, + unsigned char *buffer, + unsigned int length, + void (*done)(int length)); + +#endif /*CS8415A_H_*/ --- linux-2.6.12-spi/drivers/spi/cs8415a.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/drivers/spi/cs8415a.c 2005-10-04 14:00:12.279449000 -0700 @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/spi.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/spi.h> +#include <linux/spi/cs8415a.h> +#include <linux/spi/cirrus.h> + +#include <asm/hardirq.h> +#include <asm/semaphore.h> +#include <asm/arch/streetracer.h> + +MODULE_AUTHOR("Stephen Street"); +MODULE_DESCRIPTION("CS8415A SPI Protocol Driver"); +MODULE_LICENSE("GPL"); + +#define VALID_CS8415A_VERSION (0x41) + +struct cs8415a_driver_data { + spinlock_t lock; + struct semaphore user_lock; + struct cirrus_message_pool *read_async_pool; + struct cirrus_message_pool *write_async_pool; + struct list_head event_handlers; + int chip_version; + int enabled; + int muted; + int channel; +}; + +int _cs8415a_read_register(struct device *dev, u8 address) +{ + return _cirrus_read_register_sync(dev, address); +} +EXPORT_SYMBOL(_cs8415a_read_register); + +int _cs8415a_write_register(struct device *dev, u8 address, u8 value) +{ + return _cirrus_write_register_sync(dev, address, value); +} +EXPORT_SYMBOL(_cs8415a_write_register); + +int _cs8415a_read(struct device *dev, u8 address, + unsigned char *buffer, unsigned int length) +{ + return _cirrus_read_sync(dev, address, buffer, length); +} +EXPORT_SYMBOL(_cs8415a_read); + +int _cs8415a_write(struct device *dev, u8 address, + unsigned char *buffer, unsigned int length) +{ + return _cirrus_write_sync(dev, address, buffer, length); +} +EXPORT_SYMBOL(_cs8415a_write); + +int _cs8415a_read_register_async(struct device *dev, u8 address, + void (*done)(int value)) +{ + return -1; +} +EXPORT_SYMBOL(_cs8415a_read_register_async); + +int _cs8415a_write_register_async(struct device *dev, u8 address, + u8 value, void (*done)(int status)) +{ + return -1; +} +EXPORT_SYMBOL(_cs8415a_write_register_async); + +int _cs8415a_read_async(struct device *dev, u8 address, + unsigned char *buffer, unsigned int length, + void (*done)(int length)) +{ + return -1; +} +EXPORT_SYMBOL(_cs8415a_read_async); + +int _cs8415a_write_async(struct device *dev, u8 address, + unsigned char *buffer, unsigned int length, + void (*done)(int length)) +{ + return -1; +} +EXPORT_SYMBOL(_cs8415a_write_async); + +static int cs8415a_reset(struct device *dev) +{ + unsigned char clear[6]; + struct cs8415a_driver_data *driver_data = dev_get_drvdata(dev); + int status; + + memset(clear, 0, sizeof(clear)); + + status = _cs8415a_write_register(dev, CS8415A_CSC, 0); + if (status < 0) + return status; + + status = _cs8415a_write_register(dev, CS8415A_CTL1, + CS8415A_INT(0) + |CS8415A_MUTESAO); + if (status < 0) + return status; + + status = _cs8415a_write_register(dev, CS8415A_CTL2, CS8415A_HOLD(1)); + if (status < 0) + return status; + + status = _cs8415a_write_register(dev, CS8415A_SOF, + CS8415A_SOMS + | CS8415A_SODEL + | CS8415A_SOLRPOL); + if (status < 0) + return status; + + status = _cs8415a_write(dev, CS8415A_I1MK, clear, sizeof(clear)); + if (status < 0) + return status; + + status = _cs8415a_read_register(dev, CS8415A_RER); + if (status < 0) + return status; + + status = _cs8415a_read(dev, CS8415A_I1S, clear, 2); + if (status < 0) + return status; + + driver_data->enabled = 0; + driver_data->channel = 0; + driver_data->muted = 1; + + return 0; +} + +static irqreturn_t cs8415a_int(int irq, void *dev_id, struct pt_regs *regs) +{ + struct cs8415a_driver_data *driver_data; + + driver_data = dev_get_drvdata((struct device *)dev_id); + + spin_lock(&driver_data->lock); + + spin_unlock(&driver_data->lock); + + return IRQ_HANDLED; +} + +int cs8415a_get_version(struct device *dev) +{ + struct cs8415a_driver_data *driver_data = dev_get_drvdata(dev); + + return driver_data->chip_version; +} +EXPORT_SYMBOL(cs8415a_get_version); + +static ssize_t version_show(struct device *dev, char *buf) +{ + return sprintf(buf, "0x%02x\n", cs8415a_get_version(dev)); +} + +DEVICE_ATTR(version, 0444, version_show, NULL); + +extern int cs8415a_get_enabled(struct device *dev) +{ + struct cs8415a_driver_data *driver_data = dev_get_drvdata(dev); + int value; + + if (down_interruptible(&driver_data->user_lock)) + return -ERESTARTSYS; + + value = driver_data->enabled; + + up(&driver_data->user_lock); + + return value; +} +EXPORT_SYMBOL(cs8415a_get_enabled); + +extern int cs8415a_set_enabled(struct device *dev, int value) +{ + struct cs8415a_driver_data *driver_data = dev_get_drvdata(dev); + int status; + + if (down_interruptible(&driver_data->user_lock)) + return -ERESTARTSYS; + + status = _cs8415a_write_register(dev, CS8415A_CSC, + (u8)(value ? CS8415A_RUN : 0)); + if (status == 0) + driver_data->enabled = value ? 1 : 0; + + up(&driver_data->user_lock); + + return status; +} +EXPORT_SYMBOL(cs8415a_set_enabled); + +static ssize_t enabled_show(struct device *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", cs8415a_get_enabled(dev)); +} + +static ssize_t enabled_store(struct device *dev, const char *buf, size_t count) +{ + int status; + unsigned int value; + + status = sscanf(buf, "%u", &value); + + if (status != 1 || (value != 0 && value != 1)) { + return -EINVAL; + } + + cs8415a_set_enabled(dev, value); + + return 1; +} + +DEVICE_ATTR(enabled, 0644, enabled_show, enabled_store); + +int cs8415a_get_muted(struct device *dev) +{ + struct cs8415a_driver_data *driver_data = dev_get_drvdata(dev); + int value; + + if (down_interruptible(&driver_data->user_lock)) + return -ERESTARTSYS; + + value = driver_data->muted; + + up(&driver_data->user_lock); + + return value; +} +EXPORT_SYMBOL(cs8415a_get_muted); + +int cs8415a_set_muted(struct device *dev, int value) +{ + struct cs8415a_driver_data *driver_data = dev_get_drvdata(dev); + int status; + + if (down_interruptible(&driver_data->user_lock)) + return -ERESTARTSYS; + + status = _cs8415a_write_register(dev, CS8415A_CTL1, + (u8)(value?CS8415A_MUTESAO:0)); + if (status == 0) + driver_data->muted = value ? 1 : 0; + + up(&driver_data->user_lock); + + return status; +} +EXPORT_SYMBOL(cs8415a_set_muted); + +static ssize_t muted_show(struct device *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", cs8415a_get_muted(dev)); +} + +static ssize_t muted_store(struct device *dev, const char *buf, size_t count) +{ + int status; + unsigned int value; + + status = sscanf(buf, "%u", &value); + + if (status != 1 || (value != 0 && value != 1)) { + return -EINVAL; + } + + cs8415a_set_muted(dev, value); + + return 1; +} +DEVICE_ATTR(muted, 0644, muted_show, muted_store); + +int cs8415a_get_channel(struct device *dev) +{ + struct cs8415a_driver_data *driver_data = dev_get_drvdata(dev); + int value; + + if (down_interruptible(&driver_data->user_lock)) + return -ERESTARTSYS; + + value = driver_data->channel; + + up(&driver_data->user_lock); + + return value; +} +EXPORT_SYMBOL(cs8415a_get_channel); + +int cs8415a_set_channel(struct device *dev, int value) +{ + struct cs8415a_driver_data *driver_data = dev_get_drvdata(dev); + int status; + + if (down_interruptible(&driver_data->user_lock)) + return -ERESTARTSYS; + + status = _cs8415a_write_register(dev, CS8415A_CTL2, + (u8)(CS8415A_HOLD(1) + | CS8415A_MUX(value))); + if (status == 0) + driver_data->channel = value; + + up(&driver_data->user_lock); + + return status; +} +EXPORT_SYMBOL(cs8415a_set_channel); + +static ssize_t channel_show(struct device *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", cs8415a_get_channel(dev)); +} + +static ssize_t channel_store(struct device *dev, const char *buf, size_t count) +{ + int status; + unsigned int value; + + status = sscanf(buf, "%u", &value); + + if (status != 1 || value < 0 || value > 7) { + return -EINVAL; + } + + cs8415a_set_channel(dev, value); + + return 1; +} +DEVICE_ATTR(channel, 0644, channel_show, channel_store); + +int cs8415a_get_channel_status(struct device *dev) +{ + struct cs8415a_driver_data *driver_data = dev_get_drvdata(dev); + int value; + + if (down_interruptible(&driver_data->user_lock)) + return -ERESTARTSYS; + + value = _cs8415a_read_register(dev, CS8415A_RCS); + + up(&driver_data->user_lock); + + return value; +} +static ssize_t channel_status_show(struct device *dev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%01x\n", + cs8415a_get_channel_status(dev)); +} +DEVICE_ATTR(channel_status, 0444, channel_status_show, NULL); + +static int cs8415a_spi_probe(struct device *dev) +{ + struct spi_device *spi_dev; + struct cs8415a_driver_data *driver_data; + int status; + + /* Allocate driver data */ + driver_data = kcalloc(1, sizeof(struct cs8415a_driver_data), + GFP_KERNEL); + if (!driver_data) { + dev_err(dev, "problem allocating driver memory\n"); + status = -ENOMEM; + goto out_error; + } + + spin_lock_init(&driver_data->lock); + init_MUTEX(&driver_data->user_lock); + INIT_LIST_HEAD(&driver_data->event_handlers); + + dev_set_drvdata(dev, driver_data); + + /* Initialize the message pool */ +/* + driver_data->read_async_pool = _cirrus_create_pool(0); + driver_data->write_async_pool = _cirrus_create_pool(0); + if (!driver_data->read_async_pool || !driver_data->write_async_pool) { + dev_err(dev, "problem creating pools\n"); + status = -ENOMEM; + goto out_error_memalloc; + } +*/ + + /* Read and validate version number */ + driver_data->chip_version = _cs8415a_read_register(dev, CS8415A_VER); + if (driver_data->chip_version < 0) { + dev_err(dev, "problem reading chip version\n"); + status = -ENODEV; + goto out_error_memalloc; + } + + if (driver_data->chip_version != VALID_CS8415A_VERSION) { + dev_err(dev, "problem reading chip version " + "found version 0x%02x\n", + driver_data->chip_version); + status = -ENODEV; + goto out_error_memalloc; + } + + spi_dev = to_spi_device(dev); + + /* Attach to IRQ */ + if (spi_dev->irq == 0) { + dev_err(dev, "problem getting irq\n"); + status = -ENODEV; + goto out_error_memalloc; + } + + status = request_irq(spi_dev->irq, cs8415a_int, + SA_SHIRQ, dev->bus_id, dev); + if (status < 0) { + dev_err(dev, "problem requesting IRQ %u\n", spi_dev->irq); + status = -ENODEV; + goto out_error_memalloc; + } + + status = cs8415a_reset(dev); + if (status != 0) { + dev_err(dev, "problem resetting\n"); + status = -ENODEV; + goto out_error_irqalloc; + } + + status = device_create_file(dev, &dev_attr_version); + if (status < 0) { + dev_err(dev, "problem creating attribute %s\n", + dev_attr_version.attr.name); + goto out_error_attralloc; + } + + status = device_create_file(dev, &dev_attr_enabled); + if (status < 0) { + dev_err(dev, "problem creating attribute %s\n", + dev_attr_enabled.attr.name); + goto out_error_attralloc; + } + + status = device_create_file(dev, &dev_attr_muted); + if (status < 0) { + dev_err(dev, "problem creating attribute %s\n", + dev_attr_muted.attr.name); + goto out_error_attralloc; + } + + status = device_create_file(dev, &dev_attr_channel); + if (status < 0) { + dev_err(dev, "problem creating attribute %s\n", + dev_attr_channel.attr.name); + goto out_error_attralloc; + } + + status = device_create_file(dev, &dev_attr_channel_status); + if (status < 0) { + dev_err(dev, "problem creating attribute %s\n", + dev_attr_channel_status.attr.name); + goto out_error_attralloc; + } + + dev_dbg(dev, "found chip version 0x%02x\n", driver_data->chip_version); + + return 0; +out_error_attralloc: + device_remove_file(dev, &dev_attr_version); + device_remove_file(dev, &dev_attr_enabled); + device_remove_file(dev, &dev_attr_muted); + device_remove_file(dev, &dev_attr_channel); + device_remove_file(dev, &dev_attr_channel_status); + +out_error_irqalloc: + free_irq(spi_dev->irq, dev); + +out_error_memalloc: + if (driver_data->read_async_pool) + _cirrus_release_pool(driver_data->read_async_pool); + + if (driver_data->write_async_pool) + _cirrus_release_pool(driver_data->read_async_pool); + + dev_set_drvdata(dev, NULL); + + kfree(driver_data); + +out_error: + + return status; +} + +static int cs8415a_spi_remove(struct device *dev) +{ + struct spi_device *spi_dev = to_spi_device(dev); + struct cs8415a_driver_data *driver_data = dev_get_drvdata(dev); + unsigned long flags; + + cs8415a_reset(dev); + + spin_lock_irqsave(&driver_data->lock, flags); + + if (spi_dev->irq != 0) + free_irq(spi_dev->irq, dev); + + spin_unlock_irqrestore(&driver_data->lock, flags); + + device_remove_file(dev, &dev_attr_version); + device_remove_file(dev, &dev_attr_enabled); + device_remove_file(dev, &dev_attr_muted); + device_remove_file(dev, &dev_attr_channel); + device_remove_file(dev, &dev_attr_channel_status); + + _cirrus_release_pool(driver_data->read_async_pool); + _cirrus_release_pool(driver_data->write_async_pool); + + kfree(driver_data); + + return 0; +} + +struct device_driver cs8415a_spi = { + .name = "cs8415a", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .probe = cs8415a_spi_probe, + .remove = cs8415a_spi_remove, +}; + +static int __init cs8415a_spi_init(void) +{ + return driver_register(&cs8415a_spi); +} +module_init(cs8415a_spi_init); + +static void __exit cs8415a_spi_exit(void) +{ + driver_unregister(&cs8415a_spi); +} +module_exit(cs8415a_spi_exit); |
From: Stephen S. <st...@st...> - 2005-10-04 22:28:36
|
This is a prototype interrupt driven SPI "controller" for Intel's PXA2xx series SOC. The driver plugs into the lightweight SPI framework developed by David Brownell. Hardwired configuration information is provided via spi_board_info structures initialized in arch/arm/mach_pxa board initialization code (see include/linux/spi.h for details). The driver is built around a spi_message fifo serviced by two tasklets. The first tasklet (pump_messages) is responsible for queuing SPI transactions and scheduling SPI transfers. The second tasklet (pump_transfers) is responsible to setting up and launching the interrupt driven transfers. Per transfer chip select and delay control is available. This is a prototype driver, so you mileage will vary. It has only been tested on the NSSP port. Known Limitations: Does not handle invert chip select polarity. Heavy loaded systems may see transaction failures. Wordsize support is untested. Internal NSSP chip select is not support (i.e. NSSPSRFM) ---- snip ---- drivers/spi/Kconfig | 12 drivers/spi/Makefile | 2 drivers/spi/pxa2xx_spi_ssp.c | 741 ++++++++++++++++++++++++++++++ include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h | 36 + 4 files changed, 791 insertions(+) --- linux-2.6.12-spi/drivers/spi/Kconfig 2005-10-04 14:07:18.000000000 -0700 +++ linux-2.6.12-spi-pxa/drivers/spi/Kconfig 2005-10-04 14:00:12.279449000 -0700 @@ -65,6 +65,12 @@ comment "SPI Master Controller Drivers" +config SPI_PXA_SSP + tristate "PXA SSP controller as SPI master" + depends on ARCH_PXA + help + This implements SPI master mode using an SSP controller. + # # Add new SPI master controllers in alphabetical order above this line # @@ -77,6 +83,12 @@ comment "SPI Protocol Masters" +config SPI_CS8415A + tristate "CS8415A SPD/IF decoder" + help + This chip provides an 8 channel SPD/IF switcher with complete + SPD/IF decoding. + # # Add new SPI protocol masters in alphabetical order above this line # --- linux-2.6.12-spi/drivers/spi/Makefile 2005-10-04 14:07:18.000000000 -0700 +++ linux-2.6.12-spi-pxa/drivers/spi/Makefile 2005-10-04 14:00:12.279449000 -0700 @@ -11,9 +11,11 @@ obj-$(CONFIG_SPI_MASTER) += spi.o # SPI master controller drivers (bus) +obj-$(CONFIG_SPI_PXA_SSP) += pxa2xx_spi_ssp.o # ... add above this line ... # SPI protocol drivers (device/link on bus) +obj-$(CONFIG_SPI_CS8415A) += cs8415a.o # ... add above this line ... # SPI slave controller drivers (upstream link) --- linux-2.6.12-spi/drivers/spi/pxa2xx_spi_ssp.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/drivers/spi/pxa2xx_spi_ssp.c 2005-10-04 12:50:10.699272000 -0700 @@ -0,0 +1,741 @@ +/* + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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. + */ + +/* + * This is a prototype interrupt driven SPI "controller" for Intel's PXA2xx + * series SOC. The driver plugs into the lightweight SPI framework developed by + * David Brownell. Hardwired configuration information is provided via + * spi_board_info structures initialized in arch/arm/mach_pxa board + * initialization code (see include/linux/spi.h for details). NEED TO ADD + * PXA SPECIFIED INITIALIZATION INFORMATION. + * + * This follow code snippet demostrates a sample board configuration using + * the PXA255 NSSP port connect to a CS8415A chip via GPIO chip select 2. + * + * static struct cs8415a_platform_data cs8415a_platform_info = { + * .enabled = 1, + * .muted = 1, + * .channel = 0, + * .mask_interrupt = cs8415a_mask_interrupt, + * .unmask_interrupt = cs8415a_unmask_interrupt, + * .service_requested = cs8415a_service_requested, + * }; + * + * static struct pxa2xx_spi_chip cs8415a_chip_info = { + * .mode = SPI_MODE_3, + * .tx_threshold = 12, + * .rx_threshold = 4, + * .bits_per_word = 8, + * .chip_select_gpio = 2, + * .timeout_microsecs = 64, + * }; + * + * static struct spi_board_info streetracer_spi_board_info[] __initdata = { + * { + * .modalias = "cs8415a", + * .max_speed_hz = 3686400, + * .bus_num = 2, + * .chip_select = 0, + * .platform_data = &cs8415a_platform_info, + * .controller_data = &cs8415a_chip_info, + * .irq = STREETRACER_APCI_IRQ, + * }, + * }; + * + * static struct resource pxa_spi_resources[] = { + * [0] = { + * .start = __PREG(SSCR0_P(2)), + * .end = __PREG(SSCR0_P(2)) + 0x2c, + * .flags = IORESOURCE_MEM, + * }, + * [1] = { + * .start = IRQ_NSSP, + * .end = IRQ_NSSP, + * .flags = IORESOURCE_IRQ, + * }, + * }; + * + * static struct pxa2xx_spi_master pxa_nssp_master_info = { + * .bus_num = 2, + * .clock_enable = CKEN9_NSSP, + * .num_chipselect = 3, + * }; + * + * static struct platform_device pxa_spi_ssp = { + * .name = "pxa2xx-spi-ssp", + * .id = -1, + * .resource = pxa_spi_resources, + * .num_resources = ARRAY_SIZE(pxa_spi_resources), + * .dev = { + * .platform_data = &pxa_nssp_master_info, + * }, + * }; + * + * static void __init streetracer_init(void) + * { + * platform_device_register(&pxa_spi_ssp); + * spi_register_board_info(streetracer_spi_board_info, + * ARRAY_SIZE(streetracer_spi_board_info)); + * } + * + * The driver is built around a spi_message fifo serviced by two tasklets. The + * first tasklet (pump_messages) is responsible for queuing SPI transactions + * and scheduling SPI transfers. The second tasklet (pump_transfers) is + * responsible to setting up and launching the interrupt driven transfers. + * Per transfer chip select and delay control is available. + * + * This is a prototype driver, so you mileage will vary. It has only been + * tested on the NSSP port. + * + * Known Limitations: + * Does not handle invert chip select polarity. + * Heavy loaded systems may see transaction failures. + * Wordsize support is untested. + * Internal NSSP chip select is not support (i.e. NSSPSRFM) + * Module hangs during unload. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/spi.h> +#include <linux/ioport.h> +#include <linux/errno.h> +#include <linux/interrupt.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/hardware.h> +#include <asm/delay.h> + +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/pxa2xx_spi_ssp.h> + +MODULE_AUTHOR("Stephen Street"); +MODULE_DESCRIPTION("PXA2xx SSP SPI Contoller"); +MODULE_LICENSE("GPL"); + +#define MAX_SPEED_HZ 3686400 +#define MAX_BUSES 3 + +#define GET_IRQ_STATUS(x) (__REG(sssr)&(SSSR_TINT|SSSR_RFS|SSSR_TFS|SSSR_ROR)) + +struct transfer_state { + int index; + int len; + u32 gpio; + void *tx; + void *tx_end; + void *rx; + void *rx_end; + void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state); + void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state); +}; + +struct master_data { + spinlock_t lock; + struct spi_master *master; + struct list_head queue; + struct tasklet_struct pump_messages; + struct tasklet_struct pump_transfers; + struct spi_message* cur_msg; + struct transfer_state cur_state; + u32 sscr0; + u32 sscr1; + u32 sssr; + u32 ssitr; + u32 ssdr; + u32 ssto; + u32 sspsp; +}; + +struct chip_data { + u32 cr0; + u32 cr1; + u32 to; + u32 psp; + u16 cs_gpio; + u32 timeout; + u8 n_bytes; + void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state); + void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state); +}; + +static inline void flush(struct master_data *drv_data) +{ + u32 sssr = drv_data->sssr; + u32 ssdr = drv_data->ssdr; + + do { + while (__REG(sssr) & SSSR_RNE) { + (void)__REG(ssdr); + } + } while (__REG(sssr) & SSSR_BSY); + __REG(sssr) = SSSR_ROR ; +} + +static inline void save_state(struct master_data *drv_data, + struct chip_data *chip) +{ + /* Save critical register */ + chip->cr0 = __REG(drv_data->sscr0); + chip->cr1 = __REG(drv_data->sscr1); + chip->to = __REG(drv_data->ssto); + chip->psp = __REG(drv_data->sspsp); + + /* Disable clock */ + __REG(drv_data->sscr0) &= ~SSCR0_SSE; +} + +static inline void restore_state(struct master_data *drv_data, + struct chip_data *chip) +{ + /* Clear status and disable clock*/ + __REG(drv_data->sssr) = SSSR_ROR | SSSR_TUR | SSSR_BCE; + __REG(drv_data->sscr0) = chip->cr0 & ~SSCR0_SSE; + + /* Load the registers */ + __REG(drv_data->sscr1) = chip->cr1; + __REG(drv_data->ssto) = chip->to; + __REG(drv_data->sspsp) = chip->psp; + __REG(drv_data->sscr0) = chip->cr0; +} + +static inline void dump_state(struct master_data* drv_data) +{ + u32 sscr0 = drv_data->sscr0; + u32 sscr1 = drv_data->sscr1; + u32 sssr = drv_data->sssr; + u32 ssto = drv_data->ssto; + u32 sspsp = drv_data->sspsp; + + pr_debug("SSP dump: sscr0=0x%08x, sscr1=0x%08x, " + "ssto=0x%08x, sspsp=0x%08x, sssr=0x%08x\n", + __REG(sscr0), __REG(sscr1), __REG(ssto), + __REG(sspsp), __REG(sssr)); +} + +static void null_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ + while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { + __REG(ssdr) = 0; + ++state->tx; + } +} + +static void null_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ + while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { + (void)(__REG(ssdr)); + ++state->rx; + } +} + +static void u8_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ + while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { + __REG(ssdr) = *(u8 *)(state->tx); + ++state->tx; + } +} + +static void u8_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ + while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { + *(u8 *)(state->rx) = __REG(ssdr); + ++state->rx; + } +} + +static void u16_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ + while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { + __REG(ssdr) = *(u16 *)(state->tx); + state->tx += 2; + } +} + +static void u16_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ + while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { + *(u16 *)(state->rx) = __REG(ssdr); + state->rx += 2; + } +} +static void u32_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ + while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { + __REG(ssdr) = *(u32 *)(state->tx); + state->tx += 4; + } +} + +static void u32_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ + while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { + *(u32 *)(state->rx) = __REG(ssdr); + state->rx += 4; + } +} + +static irqreturn_t ssp_int(int irq, void *dev_id, struct pt_regs *regs) +{ + struct master_data *drv_data = (struct master_data *)dev_id; + struct transfer_state *state; + u32 sssr = drv_data->sssr; + u32 ssdr = drv_data->ssdr; + u32 sscr1 = drv_data->sscr1; + u32 ssto = drv_data->ssto; + u32 irq_status; + struct spi_message *msg; + + if (!drv_data->cur_msg || !drv_data->cur_msg->state) { + printk(KERN_ERR "pxs2xx_spi_ssp: bad message or message " + "state in interrupt handler\n"); + } + state = (struct transfer_state *)drv_data->cur_msg->state; + msg = drv_data->cur_msg; + + while ((irq_status = GET_IRQ_STATUS(sssr))) { + + if (irq_status & SSSR_ROR) { + + /* Clear and disable interrupts */ + __REG(ssto) = 0; + __REG(sssr) = SSSR_TINT | SSSR_ROR; + __REG(sscr1) &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); + + flush(drv_data); + + printk(KERN_WARNING "fifo overun: " + "index=%d tx_len=%d rx_len%d\n", + state->index, + (state->tx_end - state->tx), + (state->rx_end - state->rx)); + + state->index = -2; + tasklet_schedule(&drv_data->pump_transfers); + + return IRQ_HANDLED; + } + + + /* Pump data */ + state->read(sssr, ssdr, state); + state->write(sssr, ssdr, state); + + if ((irq_status & SSSR_TINT) || (state->rx <= state->rx_end)) { + + /* Look for false positive timeout */ + if (state->rx < state->rx_end) { + __REG(sssr) = SSSR_TINT; + break; + } + + /* Clear timeout */ + __REG(ssto) = 0; + __REG(sssr) = SSSR_TINT | SSSR_ROR ; + __REG(sscr1) &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); + + msg->actual_length += msg->transfers[state->index].len; + + if (msg->transfers[state->index].cs_change) + /* Fix me, need to handle cs polarity */ + GPSR(state->gpio) = GPIO_bit(state->gpio); + + /* Schedule transfer tasklet */ + ++state->index; + tasklet_schedule(&drv_data->pump_transfers); + + return IRQ_HANDLED; + } + } + + return IRQ_HANDLED; +} + +static void pump_transfers(unsigned long data) +{ + struct master_data *drv_data = (struct master_data *)data; + struct spi_message *message = drv_data->cur_msg; + struct chip_data *chip; + struct transfer_state * state; + struct spi_transfer *transfer; + u32 sscr1 = drv_data->sscr1; + u32 ssto = drv_data->ssto; + + if (!message) { + printk(KERN_ERR "pxs2xx_spi_ssp: bad pump_transfers " + "schedule\n"); + tasklet_schedule(&drv_data->pump_messages); + return; + } + + state = (struct transfer_state *)message->state; + if (!state) { + printk(KERN_ERR "pxs2xx_spi_ssp: bad message state\n"); + drv_data->cur_msg = NULL; + tasklet_schedule(&drv_data->pump_messages); + return; + } + + chip = spi_get_ctldata(message->dev); + if (!chip) { + printk(KERN_ERR "pxs2xx_spi_ssp: bad chip data\n"); + drv_data->cur_msg = NULL; + tasklet_schedule(&drv_data->pump_messages); + return; + } + + /* Handle for abort */ + if (state->index == -2) { + + message->status = -EIO; + if (message->complete) + message->complete(message->context); + + drv_data->cur_msg = NULL; + save_state(drv_data, chip); + + tasklet_schedule(&drv_data->pump_messages); + + return; + } + + /* Handle end of message */ + if (state->index == message->n_transfer) { + + if (!message->transfers[state->index].cs_change) + /* Fix me, need to handle cs polarity */ + GPSR(state->gpio) = GPIO_bit(state->gpio); + + message->status = 0; + if (message->complete) + message->complete(message->context); + + drv_data->cur_msg = NULL; + save_state(drv_data, chip); + + tasklet_schedule(&drv_data->pump_messages); + + return; + } + + /* Handle start of message */ + if (state->index == -1) { + + restore_state(drv_data, chip); + + flush(drv_data); + + ++state->index; + } + + /* Delay if requested at end of transfer*/ + if (state->index > 1) { + transfer = message->transfers + (state->index - 1); + if (transfer->delay_usecs) + udelay(transfer->delay_usecs); + } + + /* Setup the transfer state */ + transfer = message->transfers + state->index; + state->gpio = chip->cs_gpio; + state->tx = (void *)transfer->tx_buf; + state->tx_end = state->tx + (transfer->len * chip->n_bytes); + state->rx = transfer->rx_buf; + state->rx_end = state->rx + (transfer->len * chip->n_bytes); + state->write = state->tx ? chip->write : null_writer; + state->read = state->rx ? chip->read : null_reader; + + /* Fix me, need to handle cs polarity */ + GPCR(chip->cs_gpio) = GPIO_bit(chip->cs_gpio); + + /* Go baby, go */ + __REG(ssto) = chip->timeout; + __REG(sscr1) |= (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); +} + + +static void pump_messages(unsigned long data) +{ + struct master_data *drv_data = (struct master_data *)data; + + spin_lock(&drv_data->lock); + + /* Check for list empty */ + if (list_empty(&drv_data->queue)) { + spin_unlock(&drv_data->lock); + return; + } + + /* Check to see if we are already running */ + if (drv_data->cur_msg) { + spin_unlock(&drv_data->lock); + return; + } + + /* Extract head of queue and check for tasklet reschedule */ + drv_data->cur_msg = list_entry(drv_data->queue.next, + struct spi_message, queue); + list_del_init(&drv_data->cur_msg->queue); + + /* Setup message transfer and schedule transfer pump */ + drv_data->cur_msg->state = &drv_data->cur_state; + drv_data->cur_state.index = -1; + drv_data->cur_state.len = 0; + tasklet_schedule(&drv_data->pump_transfers); + + spin_unlock(&drv_data->lock); +} + +static int transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct master_data *drv_data = class_get_devdata(&spi->master->cdev); + + msg->actual_length = 0; + msg->status = 0; + + spin_lock_bh(&drv_data->lock); + list_add_tail(&msg->queue, &drv_data->queue); + spin_unlock_bh(&drv_data->lock); + + tasklet_schedule(&drv_data->pump_messages); + + return 0; +} + +static int setup(struct spi_device *spi) +{ + struct pxa2xx_spi_chip *chip_info; + struct chip_data *chip; + + chip_info = (struct pxa2xx_spi_chip *)spi->platform_data; + + /* Only alloc on first setup */ + chip = spi_get_ctldata(spi); + if (chip == NULL) { + chip = kcalloc(1, sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + spi->mode = chip_info->mode; + spi->bits_per_word = chip_info->bits_per_word; + } + + chip->cs_gpio = chip_info->chip_select_gpio; + chip->cr0 = SSCR0_SerClkDiv((MAX_SPEED_HZ / spi->max_speed_hz) + 2) + | SSCR0_Motorola + | SSCR0_DataSize(spi->bits_per_word) + | SSCR0_SSE + | (spi->bits_per_word > 16 ? SSCR0_EDSS : 0); + chip->cr1 = SSCR1_RxTresh(chip_info->rx_threshold) + | SSCR1_TxTresh(chip_info->tx_threshold) + | (((spi->mode & SPI_CPHA) != 0) << 4) + | (((spi->mode & SPI_CPOL) != 0) << 3); + chip->to = 0; + chip->psp = 0; + chip->timeout = (chip_info->timeout_microsecs * 10000) / 2712; + + if (spi->bits_per_word <= 8) { + chip->n_bytes = 1; + chip->read = u8_reader; + chip->write = u8_writer; + } + else if (spi->bits_per_word <= 16) { + chip->n_bytes = 2; + chip->read = u16_reader; + chip->write = u16_writer; + } + else if (spi->bits_per_word <= 32) { + chip->n_bytes = 4; + chip->read = u32_reader; + chip->write = u32_writer; + } + else { + printk(KERN_ERR "pxa2xx_spi_ssp: invalid wordsize\n"); + kfree(chip); + return -ENODEV; + } + + spi_set_ctldata(spi, chip); + + dev_dbg(&spi->dev, "gpio=%u sscr0=0x%08x sscr1=0x%08x " + "ssto=0x%08x sspsp=0x%08x\n", + chip->cs_gpio, chip->cr0, + chip->cr1, chip->to, chip->psp); + + return 0; +} + +static void cleanup(const struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata((struct spi_device *)spi); + + if (chip) + kfree(chip); + + dev_dbg(&spi->dev, "spi_device %u.%u cleanup\n", + spi->master->bus_num, spi->chip_select); +} + +static int probe(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pxa2xx_spi_master *platform_info; + struct spi_master *master; + struct master_data *drv_data = 0; + struct resource *memory_resource; + int irq; + int status = 0; + + platform_info = (struct pxa2xx_spi_master *)pdev->dev.platform_data; + + master = spi_alloc_master(dev, sizeof(struct master_data)); + if (!master) + return -ENOMEM; + drv_data = class_get_devdata(&master->cdev); + drv_data->master = master; + + INIT_LIST_HEAD(&drv_data->queue); + spin_lock_init(&drv_data->lock); + + tasklet_init(&drv_data->pump_messages, + pump_messages, + (unsigned long)drv_data); + + tasklet_init(&drv_data->pump_transfers, + pump_transfers, + (unsigned long)drv_data); + + master->bus_num = platform_info->bus_num; + master->num_chipselect = platform_info->num_chipselect; + master->cleanup = cleanup; + master->setup = setup; + master->transfer = transfer; + + /* Setup register addresses */ + memory_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!memory_resource) { + dev_dbg(dev, "can not find platform io memory\n"); + status = -ENODEV; + goto out_error_memory; + } + + drv_data->sscr0 = memory_resource->start + 0x00000000; + drv_data->sscr1 = memory_resource->start + 0x00000004; + drv_data->sssr = memory_resource->start + 0x00000008; + drv_data->ssitr = memory_resource->start + 0x0000000c; + drv_data->ssdr = memory_resource->start + 0x00000010; + drv_data->ssto = memory_resource->start + 0x00000028; + drv_data->sspsp = memory_resource->start + 0x0000002c; + + /* Attach to IRQ */ + irq = platform_get_irq(pdev, 0); + if (irq == 0) { + dev_dbg(dev, "problem getting IORESOURCE_IRQ[0]\n"); + status = -ENODEV; + goto out_error_memory; + } + + status = request_irq(irq, ssp_int, SA_INTERRUPT, dev->bus_id, drv_data); + if (status < 0) { + dev_dbg(dev, "problem requesting IORESOURCE_IRQ %u\n", irq); + goto out_error_memory; + } + + /* Enable SOC clock */ + pxa_set_cken(platform_info->clock_enable, 1); + + /* Load default SSP configuration */ + __REG(drv_data->sscr0) = 0; + __REG(drv_data->sscr1) = SSCR1_RxTresh(4) | SSCR1_TxTresh(12); + __REG(drv_data->sscr0) = SSCR0_SerClkDiv(2) + | SSCR0_Motorola + | SSCR0_DataSize(8); + __REG(drv_data->ssto) = 0; + __REG(drv_data->sspsp) = 0; + + dev_set_drvdata(dev, master); + status = spi_register_master(master); + if (status != 0) { + goto out_error_irq; + } + + return status; + +out_error_irq: + free_irq(irq, drv_data); + +out_error_memory: + class_device_put(&master->cdev); + + return status; +} + +static int remove(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct spi_master *master = dev_get_drvdata(dev); + struct master_data *drv_data = class_get_devdata(&master->cdev); + struct pxa2xx_spi_master *platform_info; + + int irq; + unsigned long flags; + + platform_info = (struct pxa2xx_spi_master *)pdev->dev.platform_data; + + spin_lock_irqsave(&drv_data->lock, flags); + + __REG(drv_data->sscr0) = 0; + pxa_set_cken(platform_info->clock_enable, 0); + + irq = platform_get_irq(pdev, 0); + if (irq != 0) + free_irq(irq, drv_data); + + spin_unlock_irqrestore(&drv_data->lock, flags); + + spi_unregister_master(master); + + return 0; +} + +static struct device_driver driver = { + .name = "pxa2xx-spi-ssp", + .bus = &platform_bus_type, + .owner = THIS_MODULE, + .probe = probe, + .remove = remove, +}; + +static int pxa2xx_spi_ssp_init(void) +{ + driver_register(&driver); + + return 0; +} +module_init(pxa2xx_spi_ssp_init); + +static void pxa2xx_spi_ssp_exit(void) +{ + driver_unregister(&driver); +} +module_exit(pxa2xx_spi_ssp_exit); --- linux-2.6.12-spi/include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h 2005-10-04 12:50:22.922007000 -0700 @@ -0,0 +1,36 @@ +/* Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 PXA2XX_SPI_SSP_H_ +#define PXA2XX_SPI_SSP_H_ + +struct pxa2xx_spi_master { + u16 bus_num; + u32 clock_enable; + u16 num_chipselect; +}; + +struct pxa2xx_spi_chip { + u8 mode; + u8 tx_threshold; + u8 rx_threshold; + u8 bits_per_word; + u16 chip_select_gpio; + u32 timeout_microsecs; +}; + +#endif /*PXA2XX_SPI_SSP_H_*/ |
From: Stephen S. <st...@st...> - 2005-10-04 22:28:34
|
Following this will be two patches, releasing an initial "SPI controller" implementation running on David Brownell's "simple SPI framework" and a prototype "SPI protocol" driver for the Cirrus Logic CS8415A SPD/IF decoder chip. The controller should run on any PXA2xx SSP port and has been tested on the PXA255 NSSP port. Complete board setup and description facilities per the the SPI framework are supported. Your comments and suggestions encouraged! You can e-mail me directly if you have any question regarding running SPI controller on your board. Thank you David for helping me make this real! Stephen Street |
From: Vitaly W. <vw...@ru...> - 2005-10-04 19:09:05
|
Hi, can you please describe the data flow in case of DMA transfer? Thanks! Vitaly David Brownell wrote: >Following this will be two patches, refreshing the minimalist SPI stack >I've sent before. Notable changes are: > > - Various updates to support real hardware, including reporting the > IRQ associated with an SPI slave chip, providing void* handles for > various flavors of board and controller state, dma_addr_t for I/O > buffers, some control over protocol delays, and more. > > - New spi_alloc_master(). The driver model is happier if drivers > don't allocate the class devices; this helps "rmmod" and friends, > kind of handy for debugging drivers. It allocates controller > specific memory not unlike alloc_netdev(). > > - Various cleanup, notably removing Kconfig for all those drivers > that don't yet exist. That was added purely to illustrate the > potential scope of an SPI framework, when more folk were asking > just why a Serial Peripheral Interface (*) was useful. > > - More kerneldoc. No Documentation/DocBook/spi.html though. > > - Now there's a real ADS7864 touchscreen/sensor driver; lightly > tested, but it emits the right sort of input events and gives > syfs access to the temperature, battery, and voltage sensors. > >This version seems real enough to integrate with. > >One goal is promote reuse of driver code -- for SPI controllers and >slave chips connected using SPI -- while fitting them better into the >driver model framework. Today, SPI devices only seem to get drivers >that are board-specific; there's a fair amount of reinvent-the-wheel, >and drivers that are unsuitable for upstream merging. > >I can now report this seems to be working with real controllers and >real slave chips ... two of each to start with, but as yet there's no >mix'n'match (with e.g. that touchscreen driver being used with a PXA >SSP controller, not just OMAP MicroWire). That should just take a >little bit of time and debugging. > >- Dave > >(*) And distinguish it from Singapore Paranormal Investigators. ;) > >- >To unsubscribe from this list: send the line "unsubscribe linux-kernel" in >the body of a message to maj...@vg... >More majordomo info at http://vger.kernel.org/majordomo-info.html >Please read the FAQ at http://www.tux.org/lkml/ > > > > |
From: <dpe...@gm...> - 2005-10-02 16:49:10
|
Hello all, > around the I/O model of a queue of async messages; and even > names for some data structures. It seems we are talking about similar things, aren't we ? > <linux/spi/spi.h> ... main header > <linux/spi/CHIP.h> ... platform_data, for CHIP.c driver > > Not all chips would need them, but it might be nice to have > some place other than <linux/CHIP.h> for such things. The > platform_data would have various important data that can't be > ... chip variants, initialization data, and similar stuff > that differs between boards is knowable only by > board-specific init code, yet is needed by board-agnostic driver code. I would prefer not to have subdirectory spi in include/linux. Take a look to pci, for example. I guess that chip data are spi-bus specific, and should not be exported to world. > that way internally. But other drivers shouldn't be forced > to allocate kernel threads when they don't need them. Really :) ? I'd like to have the worker thread for bus (and all devices on the bus) instead of several workqueues (one per each device on bus, right ?) > Hmm, this seems to be missing a few important things ... from > the last SPI patch I posted to this list (see the URL right above): > > struct bus_type spi_bus_type = { > .name = "spi", > .dev_attrs = spi_dev_attrs, > .match = spi_match_device, > .hotplug = spi_hotplug, > .suspend = spi_suspend, > .resume = spi_resume, > }; > > That supports new-school "modprobe $MODALIAS" hotplugging and > .../modalias style coldplugging, as well as passing PM calls > down to the drivers. (Those last recently got some tweaking, > to work better through sysfs.) And the core is STILL only > about 2 KB on ARM; significantly less than yours. Are you counting bytes on your sources ? Or bytes in object files ? As for spi_bus_type, I agree. Hotplu/suspend/resume have to be included. > You don't seem to have any ability to record essential > board-specific information that the drivers will need. I > hope you're not planning on making that stuff clutter up the > driver files?? board-specific.c files seem the better model, > with a way to pass that data to the drivers that need it > (using the driver model). > > That minimally includes stuff like the IRQ used by that chip, > the clock rate it supports on this board, and the SPI > clocking mode (0, 1, 2, 3) used to get data in and out of the > chip. But there seem to be a few other things needed too, > given the ways SPI chips tweak the protocol. This is responsibility of bus driver. The driver for device on the SPI bus might request the hardware info from the bus driver, which is referenced via spi_device->device->parent. > > > > + /* > > + * all messages for current > selected_device > > + * are processed. > > + * let's switch to another device > > + */ > > Why are you hard-wiring such an unfair scheduling policy ... > and preventing use of better ones? I'd use FIFO rather than > something as unfair as that; and FIFO is much simpler to code, too. OK, the policy is hardcoded and seems to be not the only available. This can be solved by adding a function to pull out the message that is "next by current". Does this sound reasonable ? > > > > +{ > > + int ret; > > + struct spimsg *msg = spimsg_alloc(dev, SPI_M_RD, len, NULL); > > + > > + ret = spi_transfer(msg, NULL); > > + memcpy(buf, spimsg_buffer_rd(msg), len); > > I don't really understand why you'd want to make this so > expensive though. Why not just do the IO directly into the > buffer provided for that purpose? One controller might > require dma bounce buffers; but don't penalize all others by > imposing those same costs. Drivers might want to allocate theyr own buffers, for example, using dma_alloc_coherent. Such drivers also need to store the dma handle somewhere. Drivers might use pre-allocated buffers. > > Also, spimsg_alloc() is huge ... even if you expect the > inliner will remove some of it. It's doing several dynamic > allocations. I honestly don't understand why there's a need > for even _one_ dynamic allocation in this "core" code path > (much less the memcpy). The allocations might be avoided if drivers provide their callback to "allocate" buffer. Then, there is the only alloc -- for spi_msg itself > Also, you don't have any "board specific init" component in > this code... spi_bus_populate calls the callback to initialize device with void* context. > > > > + +--------------+ +---------+ > > + | platform_bus | | spi_bus | > > + +--------------+ +---------+ > > + |..| | > > + |..|--------+ +---------------+ > > + +------------+| is parent to | SPI devices | > > + | SPI busses |+-------------> | | > > + +------------+ +---------------+ > > + | | > > + +----------------+ +----------------------+ > > + | SPI bus driver | | SPI device driver | > > + +----------------+ +----------------------+ > > That seems wierd even if I assume "platform_bus" is just an example. > For example there are two rather different "spi bus" notions > there, and it looks like neither one is the physical parent > of any SPI device ... "SPI busses" means several 'struct device' that corresponds to real device that acts as spi controller. "spi_bus" is the variable of type "bus_type" > > + msg->devbuf_rd = drv->alloc ? > > + drv->alloc(len, GFP_KERNEL) : kmalloc(len, > GFP_KERNEL); > > + msg->databuf_rd = drv->get_buffer ? > > + drv->get_buffer(device, msg->devbuf_rd) : > msg->devbuf_rd; > > Oy. More dynamic allocation. (Repeated for write buffers > too ...) See above; don't force such costs on all drivers, > few will ever need it. That's not necessarily allocation. That depends on driver that uses spimsg_alloc, and possibly provides callback for allocating buffers/accessing them > > +#define SPI_MAJOR 153 -- cheers, dmitry pervushin |
From: dmitry p. <dpe...@gm...> - 2005-09-28 13:14:51
|
Hello people, Here is the revised SPI-core patch. Changes are as follows: - spi_device_add now allocates and returns struct spi_device* instead of taking caller-allocated spi_device* as parameter; - spi_device_release changed to deallocate spi_device* allocated by call to spi_device_add; - new function spi_bus_populate2; it populates bus in the same manner as spi_bus_populate does, but parameter is array of struct spi_device_desc instead of string delimited by '\0's; - code style fixes; - documentation fixes. -----------------8<- cut here ------------------------------------ The supplied patch is starting point for implementing drivers for various SPI busses as well as devices connected to these busses. Currently, the SPI core supports only for MASTER mode for systems running Linux. Documentation/spi.txt | 374 ++++++++++++++++++++++++++++++++++++ arch/arm/Kconfig | 2 drivers/Kconfig | 2 drivers/Makefile | 1 drivers/spi/Kconfig | 33 +++ drivers/spi/Makefile | 14 + drivers/spi/spi-core.c | 506 +++++++++++++++++++++++++++++++++++++++++++++++++ drivers/spi/spi-dev.c | 219 +++++++++++++++++++++ include/linux/spi.h | 232 ++++++++++++++++++++++ Signed-off-by: dmitry pervushin <dpe...@ru...> PATCH FOLLOWS Index: linux-2.6.10/arch/arm/Kconfig =================================================================== --- linux-2.6.10.orig/arch/arm/Kconfig +++ linux-2.6.10/arch/arm/Kconfig @@ -834,6 +834,8 @@ source "drivers/ssi/Kconfig" source "drivers/mmc/Kconfig" +source "drivers/spi/Kconfig" + endmenu source "ktools/Kconfig" Index: linux-2.6.10/drivers/Kconfig =================================================================== --- linux-2.6.10.orig/drivers/Kconfig +++ linux-2.6.10/drivers/Kconfig @@ -42,6 +42,8 @@ source "drivers/char/Kconfig" source "drivers/i2c/Kconfig" +source "drivers/spi/Kconfig" + source "drivers/w1/Kconfig" source "drivers/misc/Kconfig" Index: linux-2.6.10/drivers/Makefile =================================================================== --- linux-2.6.10.orig/drivers/Makefile +++ linux-2.6.10/drivers/Makefile @@ -67,3 +67,4 @@ obj-$(CONFIG_DPM) += dpm/ obj-$(CONFIG_MMC) += mmc/ obj-y += firmware/ obj-$(CONFIG_EVENT_BROKER) += evb/ +obj-$(CONFIG_SPI) += spi/ Index: linux-2.6.10/drivers/spi/Kconfig =================================================================== --- /dev/null +++ linux-2.6.10/drivers/spi/Kconfig @@ -0,0 +1,33 @@ +# +# SPI device configuration +# +menu "SPI support" + +config SPI + default Y + tristate "SPI (Serial Peripheral Interface) bus support" + default false + help + Say Y if you need to enable SPI support on your kernel. + Say M if you want to create the spi-core loadable module. + +config SPI_DEBUG + bool "SPI debug output" + depends on SPI + default false + help + Say Y there if you'd like to see debug output from SPI drivers + If unsure, say N + +config SPI_CHARDEV + default Y + tristate "SPI device interface" + depends on SPI + help + Say Y here to use /dev/spiNN device files. They make it possible to have user-space + programs use the SPI bus. + This support is also available as a module. If so, the module + will be called spi-dev. + +endmenu + Index: linux-2.6.10/drivers/spi/Makefile =================================================================== --- /dev/null +++ linux-2.6.10/drivers/spi/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for the kernel spi bus driver. +# + +obj-$(CONFIG_SPI) += spi-core.o +# bus drivers +# ...functional drivers +# ...and the common spi-dev driver +obj-$(CONFIG_SPI_CHARDEV) += spi-dev.o + +ifeq ($(CONFIG_SPI_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif + Index: linux-2.6.10/drivers/spi/spi-core.c =================================================================== --- /dev/null +++ linux-2.6.10/drivers/spi/spi-core.c @@ -0,0 +1,506 @@ +/* + * drivers/spi/spi-core.c + * + * Copyright (C) 2005 MontaVista Software, Inc <so...@mv...> + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/proc_fs.h> +#include <linux/kmod.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/kthread.h> +#include <linux/spi.h> +#include <asm/atomic.h> + +static int spi_thread(void *context); + +/* + * spi_bus_match_name + * + * Drivers and devices on SPI bus are matched by name, just like the + * platform devices, with exception of SPI_DEV_CHAR. Driver with this name + * will be matched against any device + */ +static int spi_bus_match_name(struct device *dev, struct device_driver *drv) +{ + return !strcmp(drv->name, SPI_DEV_CHAR) || + !strcmp(TO_SPI_DEV(dev)->name, drv->name); +} + +struct bus_type spi_bus = { + .name = "spi", + .match = spi_bus_match_name, +}; + +/* + * spi_bus_driver_init + * + * This function initializes the spi_bus_data structure for the + * bus. Functions has to be called when bus driver gets probed + * + * Parameters: + * spi_bus_driver* pointer to bus driver structure + * device* platform device to be attached to + * Return value: + * 0 on success, error code otherwise + */ +int spi_bus_driver_init(struct spi_bus_driver *bus, struct device *dev) +{ + struct spi_bus_data *pd = + kmalloc(sizeof(struct spi_bus_data), GFP_KERNEL); + int err = 0; + + if (!pd) { + err = -ENOMEM; + goto init_failed_1; + } + atomic_set(&pd->exiting, 0); + pd->bus = bus; + init_MUTEX(&pd->lock); + INIT_LIST_HEAD(&pd->msgs); + init_waitqueue_head(&pd->queue); + pd->thread = kthread_run(spi_thread, pd, "%s-work", dev->bus_id); + if (IS_ERR(pd->thread)) { + err = PTR_ERR(pd->thread); + goto init_failed_2; + } + dev->platform_data = pd; + return 0; + +init_failed_2: + kfree(pd); + init_failed_1: + return err; +} + +/* + * __spi_bus_free + * + * This function called as part of unregistering bus device driver. It + * calls spi_device_del for each child (SPI) device on the bus + * + * Parameters: + * struct device* dev the 'bus' device + * void* context not used. Will be NULL + */ +int __spi_bus_free(struct device *dev, void *context) +{ + struct spi_bus_data *pd = dev->platform_data; + + atomic_inc(&pd->exiting); + kthread_stop(pd->thread); + kfree(pd); + + dev_dbg(dev, "unregistering children\n"); + /* + * NOTE: the loop below might needs redesign. Currently + * we delete devices from the head of children list + * until the list is empty; that's because the function + * device_for_each_child will hold the semaphore needed + * for deletion of device + */ + while (!list_empty(&dev->children)) { + struct device *child = + list_entry(dev->children.next, struct device, node); + spi_device_del(TO_SPI_DEV(child)); + } + return 0; +} + +/* + * spi_bus_driver_unregister + * + * unregisters the SPI bus from the system. Before unregistering, it deletes + * each SPI device on the bus using call to __spi_device_free + * + * Parameters: + * struct spi_bus_driver* bus_driver the bus driver + * Return value: + * void + */ +void spi_bus_driver_unregister(struct spi_bus_driver *bus_driver) +{ + if (bus_driver) { + driver_for_each_dev(&bus_driver->driver, NULL, __spi_bus_free); + driver_unregister(&bus_driver->driver); + } +} + +/* + * spi_device_release + * + * Pointer to this function will be put to dev->release place + * This function gets called as a part of device removing + * + * Parameters: + * struct device* dev + * Return value: + * none + */ +void spi_device_release(struct device *dev) +{ + struct spi_device* sdev = TO_SPI_DEV(dev); + + kfree( sdev ); +} + +/* + * spi_device_add + * + * Add the new (discovered) SPI device to the bus. Mostly used by bus drivers + * + * Parameters: + * struct device* parent the 'bus' device + * char* name name of device. Should not be NULL + * Return value: + * pointer to allocated spi_device structure; NULL on error + */ +struct spi_device* spi_device_add(struct device *parent, char *name) +{ + struct spi_device* dev; + + if (!name) + goto dev_add_out; + + dev = kmalloc(sizeof(struct spi_device), GFP_KERNEL); + if( !dev ) + goto dev_add_out; + + memset(&dev->dev, 0, sizeof(dev->dev)); + dev->dev.parent = parent; + dev->dev.bus = &spi_bus; + strncpy(dev->name, name, sizeof(dev->name)); + strncpy(dev->dev.bus_id, name, sizeof(dev->dev.bus_id)); + dev->dev.release = spi_device_release; + + if (device_register(&dev->dev)<0) { + dev_dbg(parent, " device '%s' cannot be added\n", name); + goto dev_add_out_2; + } + return dev; + +dev_add_out_2: + kfree(dev); +dev_add_out: + return NULL; +} + +/* + * spi_queue + * + * Queue the message to be processed asynchronously + * + * Parameters: + * struct spi_msg* msg message to be sent + * Return value: + * 0 on no errors, negative error code otherwise + */ +int spi_queue(struct spi_msg *msg) +{ + struct device *dev = &msg->device->dev; + struct spi_bus_data *pd = dev->parent->platform_data; + + down(&pd->lock); + list_add_tail(&msg->link, &pd->msgs); + dev_dbg(dev->parent, "message has been queued\n"); + up(&pd->lock); + wake_up_interruptible(&pd->queue); + return 0; +} + +/* + * __spi_transfer_callback + * + * callback for synchronously processed message. If spi_transfer determines + * that there is no callback provided neither by msg->status nor callback + * parameter, the __spi_transfer_callback will be used, and spi_transfer + * does not return until transfer is finished + * + * Parameters: + * struct spimsg* msg message that is being processed now + * int code status of processing + */ +static void __spi_transfer_callback(struct spi_msg *msg, int code) +{ + if (code & (SPIMSG_OK | SPIMSG_FAILED)) + complete((struct completion *)msg->context); +} + +/* + * spi_transfer + * + * Process the SPI message, by queuing it to the driver and either + * immediately return or waiting till the end-of-processing + * + * Parameters: + * struct spi_msg* msg message to process + * callback user-supplied callback. If both msg->status and + * callback are set, the error code of -EINVAL + * will be returned + * Return value: + * 0 on success, error code otherwise. This code does not reflect + * status of message, just status of queueing + */ +int spi_transfer(struct spi_msg *msg, void (*callback) (struct spi_msg *, int)) +{ + struct completion msg_done; + int err = -EINVAL; + + if (callback && !msg->status) { + msg->status = callback; + callback = NULL; + } + + if (!callback) { + if (!msg->status) { + init_completion(&msg_done); + msg->context = &msg_done; + msg->status = __spi_transfer_callback; + spi_queue(msg); + wait_for_completion(&msg_done); + err = 0; + } else { + err = spi_queue(msg); + } + } + + return err; +} + +/* + * spi_thread + * + * This function is started as separate thread to perform actual + * transfers on SPI bus + * + * Parameters: + * void* context pointer to struct spi_bus_data + */ +static int spi_thread_awake(struct spi_bus_data *bd) +{ + int ret; + + if (atomic_read(&bd->exiting)) { + return 1; + } + down(&bd->lock); + ret = !list_empty(&bd->msgs); + up(&bd->lock); + return ret; +} + +static int spi_thread(void *context) +{ + struct spi_bus_data *bd = context; + struct spi_msg *msg; + int xfer_status; + int found; + + while (!kthread_should_stop()) { + + wait_event_interruptible(bd->queue, spi_thread_awake(bd)); + + if (atomic_read(&bd->exiting)) + goto thr_exit; + + down(&bd->lock); + while (!list_empty(&bd->msgs)) { + /* + * this part is locked by bus_data->lock, + * to protect spi_msg extraction + */ + found = 0; + list_for_each_entry(msg, &bd->msgs, link) { + if (!bd->selected_device) { + bd->selected_device = msg->device; + if (bd->bus->select) + bd->bus->select(bd-> + selected_device); + found = 1; + break; + } + if (msg->device == bd->selected_device) { + found = 1; + break; + } + } + if (!found) { + /* + * all messages for current selected_device + * are processed. + * let's switch to another device + */ + msg = + list_entry(bd->msgs.next, struct spi_msg, + link); + if (bd->bus->deselect) + bd->bus->deselect(bd->selected_device); + bd->selected_device = msg->device; + if (bd->bus->select) + bd->bus->select(bd->selected_device); + } + list_del(&msg->link); + up(&bd->lock); + + /* + * and this part is locked by device's lock; + * spi_queue will be able to queue new + * messages + */ + spi_device_lock(&msg->device); + if (msg->status) + msg->status(msg, SPIMSG_STARTED); + if (bd->bus->set_clock && msg->clock) + bd->bus->set_clock(msg->device->dev.parent, + msg->clock); + xfer_status = bd->bus->xfer(msg); + if (msg->status) { + msg->status(msg, SPIMSG_DONE); + msg->status(msg, + xfer_status ? SPIMSG_OK : + SPIMSG_FAILED); + } + spi_device_unlock(&msg->device); + + /* lock the bus_data again... */ + down(&bd->lock); + } + if (bd->bus->deselect) + bd->bus->deselect(bd->selected_device); + bd->selected_device = NULL; + /* device has been just deselected, unlocking the bus */ + up(&bd->lock); + } +thr_exit: + return 0; +} + +/* + * spi_write + * send data to a device on an SPI bus + * Parameters: + * spi_device* dev the target device + * char* buf buffer to be sent + * int len buffer length + * Return: + * the number of bytes transferred, or negative error code. + */ +int spi_write(struct spi_device *dev, const char *buf, int len) +{ + struct spi_msg *msg = spimsg_alloc(dev, SPI_M_WR, len, NULL); + int ret; + + memcpy(spimsg_buffer_wr(msg), buf, len); + ret = spi_transfer(msg, NULL); + return ret == 1 ? len : ret; +} + +/* + * spi_write + * receive data from a device on an SPI bus + * Parameters: + * spi_device* dev the target device + * char* buf buffer to be sent + * int len number of bytes to receive + * Return: + * the number of bytes transferred, or negative error code. + */ +int spi_read(struct spi_device *dev, char *buf, int len) +{ + int ret; + struct spimsg *msg = spimsg_alloc(dev, SPI_M_RD, len, NULL); + + ret = spi_transfer(msg, NULL); + memcpy(buf, spimsg_buffer_rd(msg), len); + return ret == 1 ? len : ret; +} + +/* + * spi_bus_populate and spi_bus_populate2 + * + * These two functions intended to populate the SPI bus corresponding to + * device passed as 1st parameter. The difference is in the way to describe + * new SPI slave devices: the spi_bus_populate takes the ASCII string delimited + * by '\0', where each section matches one SPI device name _and_ its parameters, + * and the spi_bus_populate2 takes the array of structures spi_device_desc. + * + * If some device cannot be added because of spi_device_add fail, the function will + * not try to parse the rest of list + * + * Return: + * the number of devices that were successfully added + */ +int spi_bus_populate(struct device *parent, + char *devices, + void (*callback) (struct device * bus, + struct spi_device * new_dev)) +{ + struct spi_device *new_device; + int count = 0; + + while (devices[0]) { + dev_dbg(parent, " discovered new SPI device, name '%s'\n", + devices); + if ((new_device = spi_device_add(parent, devices)) == NULL) + break; + if (callback) + callback(parent, new_device); + devices += (strlen(devices) + 1); + count++; + } + return count; +} + +int spi_bus_populate2(struct device *parent, + struct spi_device_desc* devices, + void (*callback) (struct device* bus, + struct spi_device *new_dev, + void* params)) +{ + struct spi_device *new_device; + int count = 0; + + while (devices->name) { + dev_dbg(parent, " discovered new SPI device, name '%s'\n", + devices->name ); + if ((new_device = spi_device_add(parent, devices->name)) == NULL) + break; + if (callback) + callback(parent, new_device, devices->params); + devices++; + count++; + } + return count; +} + +int __init spi_core_init(void) +{ + return bus_register(&spi_bus); +} + +subsys_initcall(spi_core_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("dmitry pervushin <dpe...@ru...>"); + +EXPORT_SYMBOL_GPL(spi_queue); +EXPORT_SYMBOL_GPL(spi_device_add); +EXPORT_SYMBOL_GPL(spi_bus_driver_unregister); +EXPORT_SYMBOL_GPL(spi_bus_populate); +EXPORT_SYMBOL_GPL(spi_bus_populate2); +EXPORT_SYMBOL_GPL(spi_transfer); +EXPORT_SYMBOL_GPL(spi_write); +EXPORT_SYMBOL_GPL(spi_read); +EXPORT_SYMBOL_GPL(spi_bus); +EXPORT_SYMBOL_GPL(spi_bus_driver_init); Index: linux-2.6.10/drivers/spi/spi-dev.c =================================================================== --- /dev/null +++ linux-2.6.10/drivers/spi/spi-dev.c @@ -0,0 +1,219 @@ +/* + spi-dev.c - spi driver, char device interface + + Copyright (C) 2005 MontaVista Software, Inc <so...@mv...> + + 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/init.h> +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/version.h> +#include <linux/smp_lock.h> + +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/spi.h> + +#define SPI_TRANSFER_MAX 65535L + +struct spidev_driver_data { + int minor; +}; + +static ssize_t spidev_read(struct file *file, char *buf, size_t count, + loff_t * offset); +static ssize_t spidev_write(struct file *file, const char *buf, size_t count, + loff_t * offset); + +static int spidev_open(struct inode *inode, struct file *file); +static int spidev_release(struct inode *inode, struct file *file); +static int __init spidev_init(void); + +static void spidev_cleanup(void); + +static int spidev_probe(struct device *dev); +static int spidev_remove(struct device *dev); + +static struct file_operations spidev_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = spidev_read, + .write = spidev_write, + .open = spidev_open, + .release = spidev_release, +}; + +static struct class_simple *spidev_class; + +static struct spi_driver spidev_driver = { + .driver = { + .name = SPI_DEV_CHAR, + .probe = spidev_probe, + .remove = spidev_remove, + }, +}; + +static int spidev_minor; + +static int spidev_probe(struct device *dev) +{ + struct spidev_driver_data *drvdata; + + drvdata = kmalloc(sizeof(struct spidev_driver_data), GFP_KERNEL); + if (!drvdata) { + dev_dbg(dev, "allocating drvdata failed\n"); + return -ENOMEM; + } + + drvdata->minor = spidev_minor++; + dev_dbg(dev, "setting device's(%p) minor to %d\n", dev, drvdata->minor); + dev_set_drvdata(dev, drvdata); + + class_simple_device_add(spidev_class, + MKDEV(SPI_MAJOR, drvdata->minor), + NULL, "spi%d", drvdata->minor); + dev_dbg(dev, " added\n"); + return 0; +} + +static int spidev_remove(struct device *dev) +{ + struct spidev_driver_data *drvdata; + + drvdata = (struct spidev_driver_data *)dev_get_drvdata(dev); + class_simple_device_remove(MKDEV(SPI_MAJOR, drvdata->minor)); + kfree(drvdata); + dev_dbg(dev, " removed\n"); + return 0; +} + +static ssize_t spidev_read(struct file *file, char *buf, size_t count, + loff_t * offset) +{ + struct spi_device *dev = (struct spi_device *)file->private_data; + if (count > SPI_TRANSFER_MAX) + count = SPI_TRANSFER_MAX; + return spi_read(dev, buf, count); +} + +static ssize_t spidev_write(struct file *file, const char *buf, size_t count, + loff_t * offset) +{ + struct spi_device *dev = (struct spi_device *)file->private_data; + if (count > SPI_TRANSFER_MAX) + count = SPI_TRANSFER_MAX; + return spi_write(dev, buf, count); +} + +struct spidev_openclose { + unsigned int minor; + struct file *file; +}; + +static int spidev_do_open(struct device *the_dev, void *context) +{ + struct spidev_openclose *o = (struct spidev_openclose *)context; + struct spi_device *dev = TO_SPI_DEV(the_dev); + struct spidev_driver_data *drvdata; + + drvdata = (struct spidev_driver_data *)dev_get_drvdata(the_dev); + if (!drvdata) { + pr_debug("%s: oops, drvdata is NULL !\n", __FUNCTION__); + goto do_open_fail; + } + + pr_debug("drvdata->minor = %d vs %d\n", drvdata->minor, o->minor); + if (drvdata->minor == o->minor) { + get_device(&dev->dev); + o->file->private_data = dev; + return 1; + } + +do_open_fail: + return 0; +} + +int spidev_open(struct inode *inode, struct file *file) +{ + struct spidev_openclose o; + int status; + + o.minor = iminor(inode); + o.file = file; + status = driver_for_each_dev(&spidev_driver.driver, &o, spidev_do_open); + if (status == 0) { + status = -ENODEV; + } + return status < 0 ? status : 0; +} + +static int spidev_release(struct inode *inode, struct file *file) +{ + struct spi_device *dev = file->private_data; + + if (dev) + put_device(&dev->dev); + file->private_data = NULL; + + return 0; +} + +static int __init spidev_init(void) +{ + int res; + + if ((res = register_chrdev(SPI_MAJOR, "spi", &spidev_fops)) != 0) { + goto out; + } + + spidev_class = class_simple_create(THIS_MODULE, "spi"); + if (IS_ERR(spidev_class)) { + printk(KERN_ERR "%s: error creating class\n", __FUNCTION__); + res = -EINVAL; + goto out_unreg; + } + + if ((res = spi_driver_add(&spidev_driver)) != 0) + goto out_unreg; + + printk("SPI /dev entries driver.\n"); + return 0; + +out_unreg: + unregister_chrdev(SPI_MAJOR, "spi"); +out: + printk(KERN_ERR "%s: Driver initialization failed\n", __FILE__); + return res; +} + +static void spidev_cleanup(void) +{ + spi_driver_del(&spidev_driver); + class_simple_destroy(spidev_class); + unregister_chrdev(SPI_MAJOR, "spi"); +} + +MODULE_AUTHOR("dmitry pervushin <dpe...@ru...>"); +MODULE_DESCRIPTION("SPI /dev entries driver"); +MODULE_LICENSE("GPL"); + +module_init(spidev_init); +module_exit(spidev_cleanup); Index: linux-2.6.10/Documentation/spi.txt =================================================================== --- /dev/null +++ linux-2.6.10/Documentation/spi.txt @@ -0,0 +1,374 @@ +Documentation/spi.txt +======================================================== +Table of contents +1. Introduction -- what is SPI ? +2. Purposes of this code +3. SPI devices stack +3.1 SPI outline +3.2 How the SPI devices gets discovered and probed ? +3.3 DMA and SPI messages +4. SPI functions and structures reference +5. How to contact authors +======================================================== + +1. What is SPI ? +---------------- +SPI stands for "Serial Peripheral Interface", a full-duplex synchronous +serial interface for connecting low-/medium-bandwidth external devices +using four wires. SPI devices communicate using a master/slave relation- +ship over two data lines and two control lines: +- Master Out Slave In (MOSI): supplies the output data from the master + to the inputs of the slaves; +- Master In Slave Out (MISO): supplies the output data from a slave to + the input of the master. It is important to note that there can be no + more than one slave that is transmitting data during any particular + transfer; +- Serial Clock (SCLK): a control line driven by the master, regulating + the flow of data bits; +- Slave Select (SS): a control line that allows slaves to be turned on + and off with hardware control. +More information is also available at http://en.wikipedia.org/wiki/Serial_Peripheral_Interface + +2. Purposes of this code +------------------------ +The supplied patch is starting point for implementing drivers for various +SPI busses as well as devices connected to these busses. Currently, the +SPI core supports only for MASTER mode for system running Linux. + +3. SPI devices stack +-------------------- + +3.1 The SPI outline + +The SPI infrastructure deals with several levels of abstraction. They are +"SPI bus", "SPI bus driver", "SPI slave device" and "SPI device driver". The +"SPI bus" is hardware device, which usually called "SPI adapter", and has +"SPI slave devices" connected. From the Linux' point of view, the "SPI bus" is +structure of type platform_device, and "SPI slave device" is structure of type +spi_device. The "SPI bus driver" is the driver which controls the whole +SPI bus (and, particularly, creates and destroys "SPI slave devices" on the bus), +and "SPI device driver" is driver that controls the only device on the SPI +bus, controlled by "SPI bus driver". "SPI device driver" can indirectly +call "SPI bus driver" to send/receive messages using API provided by SPI +core, and provide its own interface to the kernel and/or userland. +So, the device stack looks as follows: + + +--------------+ +---------+ + | platform_bus | | spi_bus | + +--------------+ +---------+ + |..| | + |..|--------+ +---------------+ + +------------+| is parent to | SPI devices | + | SPI busses |+-------------> | | + +------------+ +---------------+ + | | + +----------------+ +----------------------+ + | SPI bus driver | | SPI device driver | + +----------------+ +----------------------+ + +3.2 How do the SPI devices gets discovered and probed ? + +In general, the SPI bus driver cannot effective discover devices +on its bus. Fortunately, the devices on SPI bus usually implemented +onboard, so the following method has been chosen: the SPI bus driver +calls the function named spi_bus_populate and passed the `topology +string' to it. The function will parse the string and call the callback +for each device, just before registering it. This allows bus driver +to determine parameters like CS# for each device, retrieve them from +string and store somewhere like spi_device->platform_data. An example: + err = spi_bus_populate( the_spi_bus, + "Dev1 0 1 2\0" "Dev2 2 1 0\0", + extract_name ) +In this example, function like extract_name would put the '\0' on the +1st space of device's name, so names will become just "Dev1", "Dev2", +and the rest of string will become parameters of device. + +The another way is to create array of structures spi_device_desc and pass +this array to function spi_bus_populate2, like this: + struct spi_device_desc spi_slaves[] = { + [0] = { + .name = "device1", + .param = device1_params, + }, + [1] = { + .name = "device2", + .param = NULL, + } + [2] = { + NULL, NULL + }; + spi_bus_populate2( the_spi_bus, spi_slaves, callback ); + +3.3. DMA and SPI messages +------------------------- + +To handle DMA transfers on SPI bus, any device driver might provide special +callbacks to allocate/free/get access to buffer. These callbacks are defined +in subsection iii of section 4. +To send data using DMA, the buffers should be allocated using +dma_alloc_coherent function. Usually buffers are allocated statically or +using kmalloc function. +To allow drivers to allocate buffers in non-standard +When one allocates the structure for spi message, it needs to provide target +device. If its driver wants to allocate buffer in driver-specific way, it may +provide its own allocation/free methods: alloc and free. If driver does not +provide these methods, kmalloc and kfree will be used. +After allocation, the buffer must be accessed to copy the buffer to be send +or retrieve buffer that has been just received from device. If buffer was +allocated using driver's alloc method, it(buffer) will be accessed using +get_buffer. Driver should provide accessible buffer that corresponds buffer +allocated by driver's alloc method. If there is no get_buffer method, +the result of alloc will be used. +After reading/writing from/to buffer, it will be released by call to driver's +release_buffer method. + + +4. SPI functions are structures reference +----------------------------------------- +This section describes structures and functions that listed +in include/linux/spi.h + +i. struct spi_msg +~~~~~~~~~~~~~~~~~ + +struct spi_msg { + unsigned char flags; + unsigned short len; + unsigned long clock; + struct spi_device* device; + void *context; + struct list_head link; + void (*status)( struct spi_msg* msg, int code ); + void *devbuf_rd, *devbuf_wr; + u8 *databuf_rd, *databuf_wr; +}; +This structure represents the message that SPI device driver sends to the +SPI bus driver to handle. +Fields: + flags combination of message flags + SPI_M_RD "read" operation (from device to host) + SPI_M_WR "write" operation (from host to device) + SPI_M_CS assert the CS signal before sending the message + SPI_M_CSREL clear the CS signal after sending the message + SPI_M_CSPOL set clock polarity to high + SPI_M_CPHA set clock phase to high + len length, in bytes, of allocated buffer + clock reserved, set to zero + device the target device of the message + context user-defined field; to associate any user data with the message + link used by bus driver to queue messages + status user-provided callback function to inform about message flow + devbuf_rd, devbuf_wr + so-called "device buffers". These buffers allocated by the + device driver, if device driver provides approproate callback. + Otherwise, the kmalloc API will be used. + databuf_rd, databuf_wr + pointers to access content of device buffers. They are acquired + using get_buffer callback, if device driver provides one. + Otherwise, they are just pointers to corresponding + device buffers + +struct spi_msg* spimsg_alloc( struct spi_device* device, + unsigned flags, + unsigned short len, + void (*status)( struct spi_msg*, int code ) ) +This functions is called to allocate the spi_msg structure and set the +corresponding fields in structure. If device->platform_data provides callbacks +to handle buffers, alloc/get_buffer are to be used. Returns NULL on errors. + +struct void spimsg_free( struct spi_msg* msg ) +Deallocate spi_msg as well as internal buffers. If msg->device->platform_data +provides callbacks to handle buffers, release_buffer and free are to be used. + +u8* spimsg_buffer_rd( struct spi_msg* msg ) +u8* spimsg_buffer_wr( struct spi_msg* msg ) +u8* spimsg_buffer( struct spi_msg* ) +Return the corresponding data buffer, which can be directly modified by driver. +spimsg_buffer checks flags and return either databuf_rd or databuf_wr basing on +value of `flags' in spi_msg structure. + +ii. struct spi_device +~~~~~~~~~~~~~~~~~~~~~ + +struct spi_device +{ + char name[ BUS_ID_SIZE ]; + struct device dev; +}; +This structure represents the physical device on SPI bus. The SPI bus driver +will create and register this structure for you. + name the name of the device. It should match to the SPI device + driver name + dev field used to be registered with core + +struct spi_device* spi_device_add( struct device* parent, + char* name ) +This function registers the device on the spi bus, and set its parent +to `parent', which represents the SPI bus. The device name will be set to name, +that should be non-empty, non-NULL string. Returns pointer to allocated +spi_device structure, NULL on error. + +void spi_device_del( struct spi_device* dev ) +Unregister the SPI device. Return value is ignored + +iii. struct spi_driver +~~~~~~~~~~~~~~~~~~~~~~ + +struct spi_driver { + void* (*alloc)( size_t, int ); + void (*free)( const void* ); + unsigned char* (*get_buffer)( struct spi_device*, void* ); + void (*release_buffer)( struct spi_device*, unsigned char*); + void (*control)( struct spi_device*, int mode, u32 ctl ); + struct device_driver driver; +}; +This structure represents the SPI device driver object. Before registering, +all fields of driver sub-structure should be properly filled, e.g., the +`bus_type' should be set to spi_bus. Otherwise, the driver will be incorrectly +registered and its callbacks might never been called. An example of will- +formed spi_driver structure: + extern struct bus_type spi_bus; + static struct spi_driver pnx4008_eeprom_driver = { + .driver = { + .bus = &spi_bus, + .name = "pnx4008-eeprom", + .probe = pnx4008_spiee_probe, + .remove = pnx4008_spiee_remove, + .suspend = pnx4008_spiee_suspend, + .resume = pnx4008_spiee_resume, + }, +}; +The method control gets called during the processing of SPI message. +For detailed description of malloc/free/get_buffer/release_buffer, please +look to section 3.3, "DMA and SPI messages" + + +int spi_driver_add( struct spi_driver* driver ) +Register the SPI device driver with core; returns 0 on no errors, error code +otherwise. + +void spi_driver_del( struct spi_driver* driver ) +Unregisters the SPI device driver; return value ignored. + +iv. struct spi_bus_driver +~~~~~~~~~~~~~~~~~~~~~~~~~ +To handle transactions over the SPI bus, the spi_bus_driver structure must +be prepared and registered with core. Any transactions, either synchronous +or asynchronous, go through spi_bus_driver->xfer function. + +struct spi_bus_driver +{ + int (*xfer)( struct spi_msg* msgs ); + void (*select) ( struct spi_device* arg ); + void (*deselect)( struct spi_device* arg ); + + struct device_driver driver; +}; + +Fields: + xfer pointer to function to execute actual transaction on SPI bus + msg message to handle + select pointer to function that gets called when bus needs to + select another device to be target of transfers + deselect + pointer to function that gets called before another device + is selected to be the target of transfers + + +spi_bus_driver_register( struct spi_bus_driver* ) + +Register the SPI bus driver with the system. The driver sub-structure should +be properly filled before using this function, otherwise you may get unpredi- +ctable results when trying to exchange data. An example of correctly prepared +spi_bus_driver structure: + static struct spi_bus_driver spipnx_driver = { + .driver = { + .bus = &platform_bus_type, + .name = "spipnx", + .probe = spipnx_probe, + .remove = spipnx_remove, + .suspend = spipnx_suspend, + .resume = spipnx_resume, + }, + .xfer = spipnx_xfer, +}; +The driver and corresponding platform device are matched by name, so, in +order the example abive to work, the platform_device named "spipnx" should +be registered somewhere. + +void spi_bus_driver_unregister( struct spi_bus_driver* ) + +Unregister the SPI bus driver registered by call to spi_buys_driver_register +function; returns void. + +int spi_bus_populate( struct device* parent, + char* devices, + void (*callback)( struct device* parent, struct spi_device* new_one ) ) +This function usually called by SPI bus drivers in order to populate the SPI +bus (see also section 3.2, "How the SPI devices gets discovered and probed ?"). +After creating the spi_device, the spi_bus_populate calls the `callback' +function to allow to modify spi_device's fields before registering it with core. + parent pointer to SPI bus + devices string representing the current topology of SPI bus. It should + be formed like + "dev-1_and_its_info\0dev-2_and_its_info\0another_device\0\0" + the spi_bus_populate delimits this string by '\0' characters, + creates spi_device and after calling the callback registers the + spi_device + callback + pointer to function which could modify spi_device fields just + before registering them with core + +int spi_bus_populate2 (struct device *parent, struct spi_device_desc *devices, + void (*callback) (struct device* parent, struct spi_device* new_dev, + void *params )) +This is another way to populate the bus; but instead of string with device names and +parameters, the array of structures spi_device_desc is passed. Each item in array describes +the SPI slave device to create. + + +v. spi_transfer and spi_queue +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The driver that uses SPI core can initiate transfers either by calling +spi_transfer function (that will wait till the transfer is funished) or +queueing the message using spi_queue function (you need to provide function +that will be called during message is processed). In any way, you need to +prepare the spimsg structure and know the target device to your message to +be sent. + +int spi_transfer( struct spi_msg msgs, + void (*callback)( struct spi_msg* msg, int ) ) +If callback is zero, start synchronous transfer. Otherwise, queue +the message. + msg message to be handled + callback the callback function to be called during + message processing. If NULL, the function + will wait until end of processing. + +int spi_queue( struct spi_device* device, + struct spi_msg* msg ) +Queue the only message to the device. Returns status of queueing. To obtain +status of message processing, you have to provide `status' callback in message +and examine its parameters + msg message to be queued + +vi. the spi_bus variable +~~~~~~~~~~~~~~~~~~~~~~~~ +This variable is created during initialization of spi core, and has to be +specified as `bus' on any SPI device driver (look to section iii, "struct +spi_driver" ). If you do not specify spi_bus, your driver will be never +matched to spi_device and never be probed with hardware. Note that +spi_bus.match points to function that matches drivers and devices by name, +so SPI devices and their drivers should have the same name. + +5. How to contact authors +------------------------- +Do you have any comments ? Enhancements ? Device driver ? Feel free +to contact me: + dpe...@gm... + di...@pe... +Visit our project page: + http://spi-devel.sourceforge.net +Subscribe to mailing list: + spi...@li... + Index: linux-2.6.10/include/linux/spi.h =================================================================== --- /dev/null +++ linux-2.6.10/include/linux/spi.h @@ -0,0 +1,232 @@ +/* + * linux/include/linux/spi/spi.h + * + * Copyright (C) 2005 MontaVista Software, Inc <so...@mv...> + * + * 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. + * + * Derived from l3.h by Jamey Hicks + */ +#ifndef SPI_H +#define SPI_H + +#include <linux/types.h> + +struct spi_device; +struct spi_driver; +struct spi_msg; +struct spi_bus_driver; + +extern struct bus_type spi_bus; + +struct spi_bus_data { + struct semaphore lock; + struct list_head msgs; + atomic_t exiting; + struct task_struct *thread; + wait_queue_head_t queue; + struct spi_device *selected_device; + struct spi_bus_driver *bus; +}; + +#define TO_SPI_BUS_DRIVER(drv) container_of( drv, struct spi_bus_driver, driver ) +struct spi_bus_driver { + int (*xfer) (struct spi_msg * msg); + void (*select) (struct spi_device * dev); + void (*deselect) (struct spi_device * dev); + void (*set_clock) (struct device * bus_device, u32 clock_hz); + struct device_driver driver; +}; + +#define TO_SPI_DEV(device) container_of( device, struct spi_device, dev ) +struct spi_device { + char name[BUS_ID_SIZE]; + struct device dev; +}; + +#define TO_SPI_DRIVER(drv) container_of( drv, struct spi_driver, driver ) +struct spi_driver { + void *(*alloc) (size_t, int); + void (*free) (const void *); + unsigned char *(*get_buffer) (struct spi_device *, void *); + void (*release_buffer) (struct spi_device *, unsigned char *); + void (*control) (struct spi_device *, int mode, u32 ctl); + struct device_driver driver; +}; + +#define SPI_DEV_DRV( device ) TO_SPI_DRIVER( (device)->dev.driver ) + +#define spi_device_lock( dev ) /* down( dev->dev.sem ) */ +#define spi_device_unlock( dev ) /* up( dev->dev.sem ) */ + +/* + * struct spi_msg + * + * This structure represent the SPI message internally. You should never use fields of this structure directly + * Please use corresponding functions to create/destroy/access fields + * + */ +struct spi_msg { + unsigned char flags; +#define SPI_M_RD 0x01 +#define SPI_M_WR 0x02 /**< Write mode flag */ +#define SPI_M_CSREL 0x04 /**< CS release level at end of the frame */ +#define SPI_M_CS 0x08 /**< CS active level at begining of frame ( default low ) */ +#define SPI_M_CPOL 0x10 /**< Clock polarity */ +#define SPI_M_CPHA 0x20 /**< Clock Phase */ + unsigned short len; /* msg length */ + unsigned long clock; + struct spi_device *device; + void *context; + struct list_head link; + void (*status) (struct spi_msg * msg, int code); + void *devbuf_rd, *devbuf_wr; + u8 *databuf_rd, *databuf_wr; +}; + +static inline struct spi_msg *spimsg_alloc(struct spi_device *device, + unsigned flags, + unsigned short len, + void (*status) (struct spi_msg *, + int code)) +{ + struct spi_msg *msg; + struct spi_driver *drv = SPI_DEV_DRV(device); + + msg = kmalloc(sizeof(struct spi_msg), GFP_KERNEL); + if (!msg) + return NULL; + memset(msg, 0, sizeof(struct spi_msg)); + msg->len = len; + msg->status = status; + msg->device = device; + msg->flags = flags; + INIT_LIST_HEAD(&msg->link); + if (flags & SPI_M_RD) { + msg->devbuf_rd = drv->alloc ? + drv->alloc(len, GFP_KERNEL) : kmalloc(len, GFP_KERNEL); + msg->databuf_rd = drv->get_buffer ? + drv->get_buffer(device, msg->devbuf_rd) : msg->devbuf_rd; + } + if (flags & SPI_M_WR) { + msg->devbuf_wr = drv->alloc ? + drv->alloc(len, GFP_KERNEL) : kmalloc(len, GFP_KERNEL); + msg->databuf_wr = drv->get_buffer ? + drv->get_buffer(device, msg->devbuf_wr) : msg->devbuf_wr; + } + pr_debug("%s: msg = %p, RD=(%p,%p) WR=(%p,%p). Actual flags = %s+%s\n", + __FUNCTION__, + msg, + msg->devbuf_rd, msg->databuf_rd, + msg->devbuf_wr, msg->databuf_wr, + msg->flags & SPI_M_RD ? "RD" : "~rd", + msg->flags & SPI_M_WR ? "WR" : "~wr"); + return msg; +} + +static inline void spimsg_free(struct spi_msg *msg) +{ + void (*do_free) (const void *) = kfree; + struct spi_driver *drv = SPI_DEV_DRV(msg->device); + + if (msg) { + if (drv->free) + do_free = drv->free; + if (drv->release_buffer) { + if (msg->databuf_rd) + drv->release_buffer(msg->device, + msg->databuf_rd); + if (msg->databuf_wr) + drv->release_buffer(msg->device, + msg->databuf_wr); + } + if (msg->devbuf_rd) + do_free(msg->devbuf_rd); + if (msg->devbuf_wr) + do_free(msg->devbuf_wr); + kfree(msg); + } +} + +static inline u8 *spimsg_buffer_rd(struct spi_msg *msg) +{ + return msg ? msg->databuf_rd : NULL; +} + +static inline u8 *spimsg_buffer_wr(struct spi_msg *msg) +{ + return msg ? msg->databuf_wr : NULL; +} + +static inline u8 *spimsg_buffer(struct spi_msg *msg) +{ + if (!msg) + return NULL; + if ((msg->flags & (SPI_M_RD | SPI_M_WR)) == (SPI_M_RD | SPI_M_WR)) { + printk(KERN_ERR "%s: what buffer do you really want ?\n", + __FUNCTION__); + return NULL; + } + if (msg->flags & SPI_M_RD) + return msg->databuf_rd; + if (msg->flags & SPI_M_WR) + return msg->databuf_wr; +} + +#define SPIMSG_OK 0x01 +#define SPIMSG_FAILED 0x80 +#define SPIMSG_STARTED 0x02 +#define SPIMSG_DONE 0x04 + +#define SPI_MAJOR 153 + +struct spi_driver; +struct spi_device; + +static inline int spi_bus_driver_register(struct spi_bus_driver *bus_driver) +{ + return driver_register(&bus_driver->driver); +} + +void spi_bus_driver_unregister(struct spi_bus_driver *); +int spi_bus_driver_init(struct spi_bus_driver *driver, struct device *dev); +struct spi_device* spi_device_add(struct device *parent, char *name); + +static inline void spi_device_del(struct spi_device *dev) +{ + device_unregister(&dev->dev); +} +static inline int spi_driver_add(struct spi_driver *drv) +{ + drv->driver.bus = &spi_bus; + return driver_register(&drv->driver); +} +static inline void spi_driver_del(struct spi_driver *drv) +{ + driver_unregister(&drv->driver); +} + +#define SPI_DEV_CHAR "spi-char" + +extern int spi_write(struct spi_device *dev, const char *buf, int len); +extern int spi_read(struct spi_device *dev, char *buf, int len); + +extern int spi_queue(struct spi_msg *message); +extern int spi_transfer(struct spi_msg *message, + void (*status) (struct spi_msg *, int)); +extern int spi_bus_populate(struct device *parent, char *device, + void (*assign) (struct device *parent, + struct spi_device *)); +struct spi_device_desc { + char* name; + void* params; +}; +extern int spi_bus_populate2(struct device *parent, + struct spi_device_desc *devices, + void (*assign) (struct device *parent, + struct spi_device *, + void *)); + +#endif /* SPI_H */ |
From: dmitry p. <dpe...@gm...> - 2005-09-27 15:20:03
|
On Tue, 2005-09-27 at 07:54 -0700, Greg KH wrote: > Not good, reference counted structures almost always should be > dynamically created. Please change this to also be true for SPI, > otherwise you will have a lot of nasty issues with devices that can be > removed at any point in time. Hmm. I thought a bit about this and it seems reasonable. I'll change this to dynamic allocation. > thanks, > > greg k-h |
From: Greg KH <gr...@kr...> - 2005-09-27 14:55:17
|
On Tue, Sep 27, 2005 at 06:49:57PM +0400, dmitry pervushin wrote: > On Tue, 2005-09-27 at 07:35 -0700, Greg KH wrote: > > Please read up on how the lifetime rules work for devices, and what > > needs to happen in the release function (hint, take a look at other > > busses, like USB and PCI for examples of what needs to be done.) > As far as I can see, pci_release_device deletes the pci_dev using kfree. Yes. > But here we have statically allocated spi_device structures -- > spi_device_add does not allocate spi_device, but uses caller-allocated > one. Not good, reference counted structures almost always should be dynamically created. Please change this to also be true for SPI, otherwise you will have a lot of nasty issues with devices that can be removed at any point in time. thanks, greg k-h |
From: dmitry p. <dpe...@gm...> - 2005-09-27 14:50:09
|
On Tue, 2005-09-27 at 07:35 -0700, Greg KH wrote: > Please read up on how the lifetime rules work for devices, and what > needs to happen in the release function (hint, take a look at other > busses, like USB and PCI for examples of what needs to be done.) As far as I can see, pci_release_device deletes the pci_dev using kfree. But here we have statically allocated spi_device structures -- spi_device_add does not allocate spi_device, but uses caller-allocated one. > > thanks, > > greg k-h > > > ------------------------------------------------------- > SF.Net email is sponsored by: > Tame your development challenges with Apache's Geronimo App Server. Download > it for free - -and be entered to win a 42" plasma tv or your very own > Sony(tm)PSP. Click here to play: http://sourceforge.net/geronimo.php > _______________________________________________ > spi-devel-general mailing list > spi...@li... > https://lists.sourceforge.net/lists/listinfo/spi-devel-general > > |
From: Greg KH <gr...@kr...> - 2005-09-27 14:35:53
|
On Tue, Sep 27, 2005 at 06:27:16PM +0400, dmitry pervushin wrote: > On Tue, 2005-09-27 at 05:43 -0700, Greg KH wrote: > > This is ALWAYS wrong, please fix your code. See the many times I have > > been over this issue in the archives. > Do you mean this comment ? The spi_device_release does nothing, just to > prevent compains from device_release function :) Think about why the kernel complains about a non-existant release function. Just replacing that complaint with a function that does nothing does NOT solve the issue, it only makes the warning go away. Please read up on how the lifetime rules work for devices, and what needs to happen in the release function (hint, take a look at other busses, like USB and PCI for examples of what needs to be done.) thanks, greg k-h |
From: dmitry p. <dpe...@gm...> - 2005-09-27 14:27:27
|
On Tue, 2005-09-27 at 05:43 -0700, Greg KH wrote: > This is ALWAYS wrong, please fix your code. See the many times I have > been over this issue in the archives. Do you mean this comment ? The spi_device_release does nothing, just to prevent compains from device_release function :) > > Also, please fix your coding style to match the kernel if you wish to > have a chance to get it included. :) Hmm... running Lindent... done. Thank you once more, for your valuable comments :) |
From: Greg KH <gr...@kr...> - 2005-09-27 12:43:56
|
On Mon, Sep 26, 2005 at 03:12:14PM +0400, dmitry pervushin wrote: > +/* > + * spi_device_release > + * > + * Pointer to this function will be put to dev->release place > + * This function gets called as a part of device removing > + * > + * Parameters: > + * struct device* dev > + * Return value: > + * none > + */ > +void spi_device_release( struct device* dev ) > +{ > +/* just a placeholder */ > +} This is ALWAYS wrong, please fix your code. See the many times I have been over this issue in the archives. Also, please fix your coding style to match the kernel if you wish to have a chance to get it included. :) thanks, greg k-h |
From: dmitry p. <dpe...@gm...> - 2005-09-27 07:39:15
|
On Mon, 2005-09-26 at 10:20 -0600, Grant Likely wrote: > Would "SPI slave" or "SPI slave device" be better terminology than > "SPI device"? That way the terminology matches how SPI hardware docs > are usually written. (not a big deal, just thought I'd ask) The term "SPI slave device" looks correct.. I am correcting the doc :) > > + err = spi_bus_populate( the_spi_bus, > > + "Dev1 0 1 2\0" "Dev2 2 1 0\0", > > + extract_name ) > In my mind, this is not ideal. For example, the MPC5200 has 4 PSC > ports which can be in SPI mode. The SPI bus driver should/will not > know what devices are attached to it. It should be the responsibility > of the board setup code to populate the bus.... on the other hand, > perhaps the bus driver should look to it's platform_device structure > to find a table of attached devices. Generic platform_device parsing > code could be used by all SPI bus drivers. The spi_bus_populate is not the only way to populate the bus; the bus driver can discover SPI devices on his own and directly call spi_device_add, isn't it ? > > +In this example, function like extract_name would put the '\0' on the > > +1st space of device's name, so names will become just "Dev1", "Dev2", > > +and the rest of string will become parameters of device. > I don't think it's wise to use '\0' as a delimiter. Sure it makes > parsing really simple when the string passed in is formed correctly, > but if someone misses the last '\0' you have no way to know where the > string ends. It also makes it difficult support passing a device > string from the kernel command line. You're right. Using spi_populate_bus is the simplest way, that may lead to errors... From the other hand, if we used another char to delimit device name and its parameters, there would be person who would want this character in device name... I think that we can add another approach to populate the bus ? > > > +4. SPI functions are structures reference > > +----------------------------------------- > > +This section describes structures and functions that listed > > +in include/linux/spi.h > I would like to see this function and structure reference in the spi.h > file itself rather than here. Better chance of it being kept up to > date that way. Yes; but I personally prefer to look to the only place instead of spi.h/spi-core.c. I'll try to keep the things consistent :) > > +This structure represents the message that SPI device driver sends to the > > +SPI bus driver to handle. > Is there any way for the SPI device to constrain the clock rate for a > transfer? For example, if the devices maximum speed is lower than the > bus maximum speed. Thank you for this comment; the `clock' field is initially intended to do this. Device driver might set the field to maximum speed, and bus driver would analyze the field in its xfer function and send the message on lower speed. Moreover, there is the `set_clock' callback in spi_bus_driver. If msg specifies its own clock value, the bus driver's set_clock will be called just before transferring the message. > Overall, I like. It looks like it does what I need it to. If I get a > chance this week I'll port my SPI drivers to it and try it out on my > MPC5200 board. Thank you! If your drivers are going to open source, could you also sent them to spi mailing list, to prepare the consolidated patch ? I hope if there is no significant troubles, the current core will go to the mainstream kernel :) |
From: Jesper J. <jes...@gm...> - 2005-09-26 20:23:24
|
On Monday 26 September 2005 13:12, dmitry pervushin wrote: > Hello guys, > > I am attaching the next incarnation of SPI core; feel free to comment it. > A few small style comments below. General notes: Please use only tabs for indentation. Please get rid of all the trailing whitespace. A small sed script like this will do: sed -r s/"[ \t]+$"/""/ Please use only one statement pr line. Please get rid of the extra whitespace after opening paren and before closing paren: not like ( this ), but like (this). Please use a single space after if. Like this: if (foo), not if(foo). For pointer variables, "type *name" is usually prefered, not "type* name" or "type * name". See the changes I've made below for more details (note: I may have missed some bits, if so, please correct what I missed as well) :-) See Documentation/CodingStyle for yet more details and rationale. [snip] > + */ > +static int spi_bus_match_name(struct device *dev, struct device_driver *drv) > +{ > + return !strcmp (drv->name, SPI_DEV_CHAR) || return !strcmp(drv->name, SPI_DEV_CHAR) || [snip] > + * Parameters: > + * struct device* dev the 'bus' device > + * void* context not used. Will be NULL * struct device *dev the 'bus' device * void *context not used. Will be NULL [snip] > +int __spi_bus_free(struct device *dev, void *context) > +{ > + struct spi_bus_data *pd = dev->platform_data; > + > + atomic_inc(&pd->exiting); > + kthread_stop(pd->thread); > + kfree(pd); > + > + dev_dbg( dev, "unregistering children\n" ); dev_dbg(dev, "unregistering children\n"); > + /* > + * NOTE: the loop below might needs redesign. Currently > + * we delete devices from the head of children list > + * until the list is empty; that's because the function > + * device_for_each_child will hold the semaphore needed > + * for deletion of device > + */ > + while( !list_empty( &dev->children ) ) { > + struct device* child = list_entry ( dev->children.next, struct device, node ); > + spi_device_del (TO_SPI_DEV (child) ); while(!list_empty(&dev->children)) { struct device *child = list_entry(dev->children.next, struct device, node); spi_device_del(TO_SPI_DEV(child)); [snip] > + * spi_bus_driver_unregister > + * > + * unregisters the SPI bus from the system. Before unregistering, it deletes > + * each SPI device on the bus using call to __spi_device_free > + * > + * Parameters: > + * struct spi_bus_driver* bus_driver the bus driver * struct spi_bus_driver *bus_driver the bus driver [snip] > +void spi_bus_driver_unregister(struct spi_bus_driver *bus_driver) > +{ > + if (bus_driver) { > + driver_for_each_dev( &bus_driver->driver, NULL, __spi_bus_free); driver_for_each_dev(&bus_driver->driver, NULL, __spi_bus_free); [snip] > + * struct device* dev * struct device *dev > + * Return value: > + * none > + */ > +void spi_device_release( struct device* dev ) void spi_device_release(struct device *dev) [snip] > + * struct device* parent the 'bus' device > + * struct spi_device* dev new device to be added > + * char* name name of device. Should not be NULL * struct device *parent the 'bus' device * struct spi_device *dev new device to be added * char *name name of device. Should not be NULL [snip] > +int spi_device_add(struct device *parent, struct spi_device *dev, char *name) > +{ > + if (!name || !dev) > + return -EINVAL; > + > + memset(&dev->dev, 0, sizeof(dev->dev)); > + dev->dev.parent = parent; > + dev->dev.bus = &spi_bus; > + strncpy( dev->name, name, sizeof(dev->name)); > + strncpy( dev->dev.bus_id, name, sizeof( dev->dev.bus_id ) ); strncpy(dev->dev.bus_id, name, sizeof(dev->dev.bus_id)); [snip] > + * spi_queue > + * > + * Queue the message to be processed asynchronously > + * > + * Parameters: > + * struct spi_msg* msg message to be sent * struct spi_msg *msg message to be sent > + * Return value: > + * 0 on no errors, negative error code otherwise > + */ > +int spi_queue( struct spi_msg *msg) int spi_queue(struct spi_msg *msg) > +{ > + struct device* dev = &msg->device->dev; struct device *dev = &msg->device->dev; > + struct spi_bus_data *pd = dev->parent->platform_data; > + > + down(&pd->lock); > + list_add_tail(&msg->link, &pd->msgs); > + dev_dbg(dev->parent, "message has been queued\n" ); dev_dbg(dev->parent, "message has been queued\n"); [snip] > + * __spi_transfer_callback > + * > + * callback for synchronously processed message. If spi_transfer determines > + * that there is no callback provided neither by msg->status nor callback > + * parameter, the __spi_transfer_callback will be used, and spi_transfer > + * does not return until transfer is finished > + * > + * Parameters: > + * struct spimsg* msg message that is being processed now * struct spimsg *msg message that is being processed now > + * int code status of processing > + */ > +static void __spi_transfer_callback( struct spi_msg* msg, int code ) static void __spi_transfer_callback(struct spi_msg *msg, int code) > +{ > + if( code & (SPIMSG_OK|SPIMSG_FAILED) ) > + complete( (struct completion*)msg->context ); if (code & (SPIMSG_OK|SPIMSG_FAILED)) complete((struct completion*)msg->context); [snip] > + * spi_transfer > + * > + * Process the SPI message, by queuing it to the driver and either > + * immediately return or waiting till the end-of-processing > + * > + * Parameters: > + * struct spi_msg* msg message to process * struct spi_msg *msg message to process [snip] > +int spi_transfer( struct spi_msg* msg, void (*callback)( struct spi_msg*, int ) ) int spi_transfer(struct spi_msg *msg, void (*callback)(struct spi_msg*, int)) > +{ > + struct completion msg_done; > + int err = -EINVAL; > + > + if( callback && !msg->status ) { if (callback && !msg->status) { [snip] > + if( !callback ) { > + if( !msg->status ) { > + init_completion( &msg_done ); > + msg->context = &msg_done; > + msg->status = __spi_transfer_callback; > + spi_queue( msg ); > + wait_for_completion( &msg_done ); > + err = 0; > + } else { > + err = spi_queue( msg ); if (!callback) { if (!msg->status) { init_completion(&msg_done); msg->context = &msg_done; msg->status = __spi_transfer_callback; spi_queue(msg); wait_for_completion(&msg_done); err = 0; } else { err = spi_queue(msg); [snip] > + * spi_thread > + * > + * This function is started as separate thread to perform actual > + * transfers on SPI bus > + * > + * Parameters: > + * void* context pointer to struct spi_bus_data * void *context pointer to struct spi_bus_data [snip] > + while (!kthread_should_stop()) { > + ^^^^^ superfluous blank line. > + wait_event_interruptible(bd->queue, spi_thread_awake(bd)); [snip] > + if( bd->bus->set_clock && msg->clock ) > + bd->bus->set_clock( > + msg->device->dev.parent, msg->clock ); > + xfer_status = bd->bus->xfer( msg ); if (bd->bus->set_clock && msg->clock) bd->bus->set_clock( <-- this line has trailing whitespace. msg->device->dev.parent, msg->clock); xfer_status = bd->bus->xfer(msg); [snip] > + * spi_write > + * send data to a device on an SPI bus > + * Parameters: > + * spi_device* dev the target device > + * char* buf buffer to be sent * spi_device *dev the target device * char *buf buffer to be sent [snip] > + ret = spi_transfer( msg, NULL ); ret = spi_transfer(msg, NULL); [snip] > + * spi_write > + * receive data from a device on an SPI bus > + * Parameters: > + * spi_device* dev the target device > + * char* buf buffer to be sent * spi_device *dev the target device * char *buf buffer to be sent [snip] > +int spi_read(struct spi_device *dev, char *buf, int len) > +{ > + int ret; > + struct spimsg *msg = spimsg_alloc(dev, SPI_M_RD, len, NULL); > + > + ret = spi_transfer( msg, NULL ); ret = spi_transfer(msg, NULL); [snip] > +int spi_bus_populate(struct device *parent, > + char *devices, > + void (*callback) (struct device * bus, > + struct spi_device * new_dev)) int spi_bus_populate(struct device *parent, char *devices, void (*callback)(struct device *bus, struct spi_device *new_dev)) [snip] > + while (devices[0]) { > + dev_dbg(parent, "discovered new SPI device, name '%s'\n", > + devices); > + new_device = kmalloc(sizeof(struct spi_device), GFP_KERNEL); > + if (!new_device) { > + break; > + } > + if (spi_device_add(parent, new_device, devices)) { > + break; > + } > + if (callback) { > + callback(parent, new_device); > + } if (!new_device) break; if (spi_device_add(parent, new_device, devices)) break; if (callback) callback(parent, new_device); We usually don't use curly braces for if statements when the body of the if is only a single statement. [snip] > +int __init spi_core_init( void ) int __init spi_core_init(void) [snip] > +++ linux-2.6.10/drivers/spi/spi-dev.c [snip] [snip] > +#include <linux/init.h> > +#include <asm/uaccess.h> > +#include <linux/spi.h> #include <linux/init.h> #include <linux/spi.h> #include <asm/uaccess.h> conventionally, asm/ includes are placed last. [snip] > +static ssize_t spidev_read(struct file *file, char *buf, size_t count, > + loff_t * offset); > +static ssize_t spidev_write(struct file *file, const char *buf, size_t count, > + loff_t * offset); static ssize_t spidev_read(struct file *file, char *buf, size_t count, loff_t *offset); static ssize_t spidev_write(struct file *file, const char *buf, size_t count, loff_t *offset); [snip] > +static int spidev_probe(struct device *dev) > +{ > + struct spidev_driver_data *drvdata; > + > + drvdata = kmalloc(sizeof(struct spidev_driver_data), GFP_KERNEL); > + if ( !drvdata) { > + dev_dbg( dev, "allocating drvdata failed\n" ); if (!drvdata) { dev_dbg(dev, "allocating drvdata failed\n"); [snip] > + dev_dbg( dev, " added\n" ); dev_dbg(dev, " added\n"); [snip] > +static int spidev_remove(struct device *dev) > +{ > + struct spidev_driver_data *drvdata; > + > + drvdata = (struct spidev_driver_data *)dev_get_drvdata(dev); > + class_simple_device_remove(MKDEV(SPI_MAJOR, drvdata->minor)); > + kfree(drvdata); > + dev_dbg( dev, " removed\n" ); dev_dbg(dev, " removed\n"); [snip] > +static ssize_t spidev_read(struct file *file, char *buf, size_t count, > + loff_t * offset) static ssize_t spidev_read(struct file *file, char *buf, size_t count, loff_t *offset) > +{ > + struct spi_device *dev = (struct spi_device *)file->private_data; > + if( count > SPI_TRANSFER_MAX ) count = SPI_TRANSFER_MAX; > + return spi_read(dev, buf, count ); if (count > SPI_TRANSFER_MAX) count = SPI_TRANSFER_MAX; return spi_read(dev, buf, count); [snip] > +static ssize_t spidev_write(struct file *file, const char *buf, size_t count, > + loff_t * offset) static ssize_t spidev_write(struct file *file, const char *buf, size_t count, loff_t *offset) > +{ > + struct spi_device *dev = (struct spi_device *)file->private_data; > + if( count > SPI_TRANSFER_MAX ) count = SPI_TRANSFER_MAX; > + return spi_write( dev, buf, count ); if (count > SPI_TRANSFER_MAX) count = SPI_TRANSFER_MAX; return spi_write(dev, buf, count); [snip] > + if (NULL == drvdata) { if (drvdata == NULL) { debatable, but I believe the most common style is what I changed it to. [snip] > +++ linux-2.6.10/include/linux/spi.h [snip] > +/* > + * linux/include/linux/spi/spi.h > + * > + * Copyright (C) 2005 MontaVista Software, Inc <so...@mv...> > + * > + * 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. > + * > + * Derived from l3.h by Jamey Hicks > + */ + > +#ifndef SPI_H Blank line between end of comment and start of code at the top of file seems to be most common. [snip] > +struct spi_bus_data > +{ > + struct semaphore lock; > + struct list_head msgs; > + atomic_t exiting; > + struct task_struct* thread; > + wait_queue_head_t queue; > + struct spi_device* selected_device; > + struct spi_bus_driver* bus; struct task_struct *thread; wait_queue_head_t queue; struct spi_device *selected_device; struct spi_bus_driver *bus; [snip] > +#define TO_SPI_BUS_DRIVER(drv) container_of( drv, struct spi_bus_driver, driver ) #define TO_SPI_BUS_DRIVER(drv) container_of(drv, struct spi_bus_driver, driver) > +struct spi_bus_driver > +{ > + int (*xfer)( struct spi_msg* msg ); > + void (*select)( struct spi_device* dev ); > + void (*deselect)( struct spi_device* dev ); > + void (*set_clock)( struct device* bus_device, u32 clock_hz ); > + struct device_driver driver; int (*xfer)(struct spi_msg *msg); void (*select)(struct spi_device *dev); void (*deselect)(struct spi_device *dev); void (*set_clock)(struct device *bus_device, u32 clock_hz); [snip] > +#define TO_SPI_DEV(device) container_of( device, struct spi_device, dev ) #define TO_SPI_DEV(device) container_of(device, struct spi_device, dev) > +struct spi_device > +{ > + char name[ BUS_ID_SIZE ]; char name[BUS_ID_SIZE]; [snip] > +#define TO_SPI_DRIVER(drv) container_of( drv, struct spi_driver, driver ) #define TO_SPI_DRIVER(drv) container_of(drv, struct spi_driver, driver) > +struct spi_driver { > + void* (*alloc)( size_t, int ); > + void (*free)( const void* ); > + unsigned char* (*get_buffer)( struct spi_device*, void* ); > + void (*release_buffer)( struct spi_device*, unsigned char*); > + void (*control)( struct spi_device*, int mode, u32 ctl ); struct spi_driver { void *(*alloc)(size_t, int); void (*free)(const void *); unsigned char *(*get_buffer)(struct spi_device *, void *); void (*release_buffer)(struct spi_device *, unsigned char *); void (*control)(struct spi_device *, int mode, u32 ctl); [snip] > +#define SPI_DEV_DRV( device ) TO_SPI_DRIVER( (device)->dev.driver ) #define SPI_DEV_DRV(device) TO_SPI_DRIVER((device)->dev.driver) > + > +#define spi_device_lock( dev ) /* down( dev->dev.sem ) */ > +#define spi_device_unlock( dev ) /* up( dev->dev.sem ) */ #define spi_device_lock(dev) /* down(dev->dev.sem) */ #define spi_device_unlock(dev) /* up(dev->dev.sem) */ > +/* > + * struct spi_msg > + * > + * This structure represent the SPI message internally. You should never use fields of this structure directly > + * Please use corresponding functions to create/destroy/access fields * This structure represent the SPI message internally. * You should never use fields of this structure directly. * Please use corresponding functions to create/destroy/access fields [snip] > +struct spi_msg { > + unsigned char flags; > +#define SPI_M_RD 0x01 > +#define SPI_M_WR 0x02 /**< Write mode flag */ > +#define SPI_M_CSREL 0x04 /**< CS release level at end of the frame */ > +#define SPI_M_CS 0x08 /**< CS active level at begining of frame ( default low ) */ > +#define SPI_M_CPOL 0x10 /**< Clock polarity */ > +#define SPI_M_CPHA 0x20 /**< Clock Phase */ > + unsigned short len; /* msg length */ > + unsigned long clock; > + struct spi_device* device; > + void *context; > + struct list_head link; > + void (*status)( struct spi_msg* msg, int code ); #define SPI_M_WR 0x02 /* Write mode flag */ #define SPI_M_CSREL 0x04 /* CS release level at end of the frame */ #define SPI_M_CS 0x08 /* CS active level at begining of frame (default low) */ #define SPI_M_CPOL 0x10 /* Clock polarity */ #define SPI_M_CPHA 0x20 /* Clock Phase */ unsigned short len; /* msg length */ unsigned long clock; struct spi_device *device; void *context; struct list_head link; void (*status)(struct spi_msg *msg, int code); [snip] > +static inline struct spi_msg* spimsg_alloc( struct spi_device* device, > + unsigned flags, > + unsigned short len, > + void (*status)( struct spi_msg*, int code ) ) static inline struct spi_msg* spimsg_alloc( struct spi_device* device, unsigned flags, unsigned short len, void (*status)(struct spi_msg *, int code)) > +{ > + struct spi_msg* msg; > + struct spi_driver* drv = SPI_DEV_DRV( device ); > + > + msg = kmalloc( sizeof( struct spi_msg ), GFP_KERNEL ); > + if( !msg ) > + return NULL; > + memset( msg, 0, sizeof( struct spi_msg ) ); struct spi_msg *msg; struct spi_driver *drv = SPI_DEV_DRV(device); msg = kmalloc(sizeof(struct spi_msg), GFP_KERNEL); if (!msg) return NULL; memset(msg, 0, sizeof(struct spi_msg)); In addition to the spacing changes you also seem to be using spaces for indentation here instead of tabs. Please use only tabs for indentation - this is true for other locations in the file as well, but I'm only mentioning it once here. [snip] > + INIT_LIST_HEAD( &msg->link ); > + if( flags & SPI_M_RD ) { > + msg->devbuf_rd = drv->alloc ? > + drv->alloc( len, GFP_KERNEL ):kmalloc( len, GFP_KERNEL); > + msg->databuf_rd = drv->get_buffer ? > + drv->get_buffer( device, msg->devbuf_rd ) : msg->devbuf_rd; > + } INIT_LIST_HEAD(&msg->link); if (flags & SPI_M_RD) { msg->devbuf_rd = drv->alloc ? drv->alloc(len, GFP_KERNEL) : kmalloc(len, GFP_KERNEL); msg->databuf_rd = drv->get_buffer ? drv->get_buffer(device, msg->devbuf_rd) : msg->devbuf_rd; } > + if( flags & SPI_M_WR ) { > + msg->devbuf_wr = drv->alloc ? > + drv->alloc( len, GFP_KERNEL ):kmalloc( len, GFP_KERNEL); > + msg->databuf_wr = drv->get_buffer ? > + drv->get_buffer( device, msg->devbuf_wr ) : msg->devbuf_wr; > + } if (flags & SPI_M_WR) { msg->devbuf_wr = drv->alloc ? drv->alloc(len, GFP_KERNEL) : kmalloc(len, GFP_KERNEL); msg->databuf_wr = drv->get_buffer ? drv->get_buffer(device, msg->devbuf_wr) : msg->devbuf_wr; } > + pr_debug( "%s: msg = %p, RD=(%p,%p) WR=(%p,%p). Actual flags = %s+%s\n", > + __FUNCTION__, > + msg, > + msg->devbuf_rd, msg->databuf_rd, > + msg->devbuf_wr, msg->databuf_wr, > + msg->flags & SPI_M_RD ? "RD" : "~rd", > + msg->flags & SPI_M_WR ? "WR" : "~wr" ); pr_debug("%s: msg = %p, RD=(%p,%p) WR=(%p,%p). Actual flags = %s+%s\n", __FUNCTION__, msg, msg->devbuf_rd, msg->databuf_rd, msg->devbuf_wr, msg->databuf_wr, msg->flags & SPI_M_RD ? "RD" : "~rd", msg->flags & SPI_M_WR ? "WR" : "~wr"); [snip] > +static inline void spimsg_free( struct spi_msg * msg ) > +{ > + void (*do_free)( const void* ) = kfree; > + struct spi_driver* drv = SPI_DEV_DRV( msg->device ); static inline void spimsg_free(struct spi_msg *msg) { void (*do_free)(const void *) = kfree; struct spi_driver *drv = SPI_DEV_DRV(msg->device); > + > + if( msg ) { > + if( drv->free ) > + do_free = drv->free; > + if( drv->release_buffer ) { > + if( msg->databuf_rd) > + drv->release_buffer( msg->device, msg->databuf_rd ); > + if( msg->databuf_wr) > + drv->release_buffer( msg->device, msg->databuf_wr ); > + } > + if( msg->devbuf_rd ) > + do_free( msg->devbuf_rd ); > + if( msg->devbuf_wr) > + do_free( msg->devbuf_wr ); > + kfree( msg ); > + } if (msg) { if (drv->free) do_free = drv->free; if (drv->release_buffer) { if (msg->databuf_rd) drv->release_buffer(msg->device, msg->databuf_rd); if (msg->databuf_wr) drv->release_buffer( msg->device, msg->databuf_wr); } if (msg->devbuf_rd) do_free( msg->devbuf_rd); if (msg->devbuf_wr) do_free( msg->devbuf_wr); kfree(msg); } [snip] > +static inline u8* spimsg_buffer_rd( struct spi_msg* msg ) static inline u8 *spimsg_buffer_rd(struct spi_msg *msg) [snip] > +static inline u8* spimsg_buffer_wr( struct spi_msg* msg ) static inline u8 *spimsg_buffer_wr(struct spi_msg *msg) [snip] > +static inline u8* spimsg_buffer( struct spi_msg* msg ) > +{ > + if( !msg ) return NULL; > + if( ( msg->flags & (SPI_M_RD|SPI_M_WR) ) == (SPI_M_RD|SPI_M_WR) ) { > + printk( KERN_ERR"%s: what buffer do you really want ?\n", __FUNCTION__ ); > + return NULL; > + } > + if( msg->flags & SPI_M_RD) return msg->databuf_rd; > + if( msg->flags & SPI_M_WR) return msg->databuf_wr; > +} static inline u8 *spimsg_buffer(struct spi_msg* msg) { if (!msg) return NULL; if ((msg->flags & (SPI_M_RD|SPI_M_WR)) == (SPI_M_RD|SPI_M_WR)) { printk(KERN_ERR "%s: what buffer do you really want ?\n", __FUNCTION__ ); return NULL; } if (msg->flags & SPI_M_RD) return msg->databuf_rd; if (msg->flags & SPI_M_WR) return msg->databuf_wr; } > + > +#define SPIMSG_OK 0x01 > +#define SPIMSG_FAILED 0x80 > +#define SPIMSG_STARTED 0x02 > +#define SPIMSG_DONE 0x04 > + > +#define SPI_MAJOR 98 #define SPIMSG_OK 0x01 #define SPIMSG_FAILED 0x80 #define SPIMSG_STARTED 0x02 #define SPIMSG_DONE 0x04 #define SPI_MAJOR 98 It may not be obvious what change I made here, so I'll tell you. You were mixing spaces and tabs between the defined named and the value, I've changed it to only use a single tab (it still lines up nicely). [snip] > +static inline int spi_bus_driver_register( struct spi_bus_driver* bus_driver ) > +{ > + return driver_register( &bus_driver->driver ); > +} static inline int spi_bus_driver_register(struct spi_bus_driver *bus_driver) { return driver_register(&bus_driver->driver); } > + > +void spi_bus_driver_unregister( struct spi_bus_driver* ); > +int spi_bus_driver_init( struct spi_bus_driver* driver, struct device* dev ); > +int spi_device_add( struct device* parent, struct spi_device*, char* name ); void spi_bus_driver_unregister(struct spi_bus_driver *); int spi_bus_driver_init(struct spi_bus_driver * driver, struct device *dev); int spi_device_add(struct device *parent, struct spi_device *, char *name); > +static inline void spi_device_del( struct spi_device* dev ) > +{ > + device_unregister( &dev->dev ); > +} static inline void spi_device_del(struct spi_device *dev) { device_unregister(&dev->dev); } > +static inline int spi_driver_add( struct spi_driver* drv ) > +{ > + return driver_register( &drv->driver ); > +} static inline int spi_driver_add(struct spi_driver *drv) { return driver_register(&drv->driver); } > +static inline void spi_driver_del( struct spi_driver* drv ) > +{ > + driver_unregister( &drv->driver ); > +} static inline void spi_driver_del(struct spi_driver *drv) { driver_unregister(&drv->driver); } [snip] > +extern int spi_queue( struct spi_msg* message ); > +extern int spi_transfer( struct spi_msg* message, void (*status)( struct spi_msg*, int ) ); > +extern int spi_bus_populate( struct device* parent, char* device, void (*assign)( struct device* parent, struct spi_device* ) ); extern int spi_queue(struct spi_msg* message); extern int spi_transfer(struct spi_msg *message, void (*status)(struct spi_msg *, int)); extern int spi_bus_populate(struct device *parent, char *device, void (*assign)(struct device *parent, struct spi_device *)); > + > +#endif /* SPI_H */ #endif /* SPI_H */ -- Jesper Juhl <jes...@gm...> |
From: Vitaly W. <vw...@ru...> - 2005-09-26 16:45:32
|
My POV is that those lines should go away. Best regards, Vitaly Val...@vt... wrote: >On Mon, 26 Sep 2005 15:12:14 +0400, dmitry pervushin said: > > >>Hello guys, >> >>I am attaching the next incarnation of SPI core; feel free to comment it. >> >> > > > >>+/* The devfs code is contributed by Philipp Matthias Hahn >>+ <pm...@ti...> */ >> >> > > > >>+/* devfs code corrected to support automatic device addition/deletion >>+ by Vitaly Wool <vw...@ru...> (C) 2004 MontaVista Software, Inc. >>+ */ >> >> > >I'd like to thank Vitaly and Philipp for their work, which was probably useful >at the time, but I've always wondered - when cleaning up code, should such comments >be removed too, or left as historical reminders? The MAINTAINERS file seems >to get cleaned most of the time, the CREDITS doesn't - which way should >in-source comments go? > > > |