atmel SoC SPI controller with dmaengine and DT
Subsystems (8)
cover atmel SoC SPI controller with dmaengine and DT
Hi,
This set of patches is to add dmaengine and DT support for atmel spi. The work is based on Jean-Christophe, Nicolas and Richard's work. I have tested it on at91sam9x5ek, at91sam9m10g45ek.
It is based on v3.7-rc5.
Best Regards,
Wenyou Yang.
Jean-Christophe PLAGNIOL-VILLARD (3):
of: add dma-mask binding
of_spi: add generic binding support to specify cs gpio spi/atmel_spi: add DT support
Nicolas Ferre (5):
spi/atmel_spi: add physical base address spi/atmel_spi: call unmapping on transfers buffers spi/atmel_spi: status information passed through controller data spi/atmel_spi: add flag to controller data for lock operations spi/atmel_spi: add dmaengine support
Richard Genoud (6):
spi/atmel_spi: Fix spi-atmel driver to adapt to slave_config changes spi/atmel_spi: correct 16 bits transfers using PIO spi/atmel_spi: correct 16 bits transfer with DMA
ARM: at91: add clocks for spi DT entries
ARM: dts: add spi nodes for atmel SoC
ARM: dts: add spi nodes for atmel boards
Wenyou Yang (3):
spi/atmel_spi: add version propety as the spi data spi/atmel_spi: add function to read the spi data from the dts
mtd: m25p80: change the m25p80_read to reading page to page
Documentation/devicetree/bindings/spi/spi-bus.txt | 6 + .../devicetree/bindings/spi/spi_atmel.txt | 27 + arch/arm/boot/dts/at91sam9260.dtsi | 34 + arch/arm/boot/dts/at91sam9263.dtsi | 34 + arch/arm/boot/dts/at91sam9263ek.dts | 9 + arch/arm/boot/dts/at91sam9g20ek_common.dtsi | 9 + arch/arm/boot/dts/at91sam9g25ek.dts | 9 + arch/arm/boot/dts/at91sam9g45.dtsi | 34 + arch/arm/boot/dts/at91sam9m10g45ek.dts | 9 + arch/arm/boot/dts/at91sam9n12.dtsi | 34 + arch/arm/boot/dts/at91sam9n12ek.dts | 9 + arch/arm/boot/dts/at91sam9x5.dtsi | 34 + arch/arm/mach-at91/at91sam9260.c | 2 + arch/arm/mach-at91/at91sam9g45.c | 2 + arch/arm/mach-at91/at91sam9n12.c | 2 + arch/arm/mach-at91/at91sam9x5.c | 2 + drivers/mtd/devices/m25p80.c | 44 +- drivers/of/platform.c | 23 +- drivers/spi/spi-atmel.c | 728 ++++++++++++++++++-- drivers/spi/spi.c | 55 +- include/linux/spi/spi.h | 3 + 21 files changed, 1045 insertions(+), 64 deletions(-) create mode 100644 Documentation/devicetree/bindings/spi/spi_atmel.txt
Signature · 1 line
1.7.9.5
PATCH 1/17 of: add dma-mask binding
From: Jean-Christophe PLAGNIOL-VILLARD <[email protected]>
This will allow each device to specify its dma-mask for this we use the coherent_dma_mask as pointer. By default the dma-mask will be set to DMA_BIT_MASK(32).
The microblaze architecture hook is drop
--- drivers/of/platform.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-)
drivers/of/platform.c +16 -7
@@ -130,6 +130,21 @@ void of_device_make_bus_id(struct device *dev)dev_set_name (dev,"%s.%d" , node->name, magic -1 ); } +static void of_get_dma_mask (struct device *dev,struct device_node *np) +{ +const __be32 *prop; +int len; + + prop =of_get_property (np,"dma-mask" , &len); + + dev->dma_mask = &dev->coherent_dma_mask; + +if (!prop) + dev->coherent_dma_mask =DMA_BIT_MASK (32 ); +else + dev->coherent_dma_mask =of_read_number (prop, len /4 ); +} +/** * of_device_alloc - Allocate and initialize an of_device * @np: device node to assign to device
@@ -171,10 +186,8 @@ struct platform_device *of_device_alloc(struct device_node *np,WARN_ON (of_irq_to_resource_table (np, res, num_irq) != num_irq); } +of_get_dma_mask (&dev->dev, np); dev->dev.of_node =of_node_get (np); -#if defined(CONFIG_MICROBLAZE ) - dev->dev.dma_mask = &dev->archdata.dma_mask; -#endif dev->dev.parent = parent;if (bus_id)
@@ -211,10 +224,6 @@ struct platform_device *of_platform_device_create_pdata(if (!dev)return NULL ; -#if defined(CONFIG_MICROBLAZE ) - dev->archdata.dma_mask =0xffffffffUL ; -#endif - dev->dev.coherent_dma_mask =DMA_BIT_MASK (32 ); dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data;
Signature · 1 line
1.7.9.5
PATCH 1/17 of: add dma-mask binding
Quoted 5 lines · Wenyou Yang
From: Jean-Christophe PLAGNIOL-VILLARD <[email protected]>
This will allow each device to specify its dma-mask for this we use the
coherent_dma_maskas pointer. By default the dma-mask will be set toDMA_BIT_MASK(32).
Do you really have a use case this is not DMA_BIT_MASK(32)?
Quoted 15 lines
The microblaze architecture hook is drop
Signed-off-by Jean-Christophe PLAGNIOL-VILLARD <[email protected]>Cc grant.likely at secretlab.caCc rob.herring at calxeda.comCc devicetree-discuss at lists.ozlabs.org--- drivers/of/platform.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-)drivers/of/platform.c +0
-0
drivers/of/platform.c void of_device_make_bus_id(struct device *dev) · line 138
Quoted 9 lines
drivers/of/platform.c +6
-0@@ -130,6 +130,21 @@ void of_device_make_bus_id(struct device *dev)dev_set_name (dev,"%s.%d" , node->name, magic -1 ); } +static void of_get_dma_mask (struct device *dev,struct device_node *np) +{ +const __be32 *prop; +int len; + + prop =of_get_property (np,"dma-mask" , &len);
dma-ranges may work for this purpose.
drivers/of/platform.c void of_device_make_bus_id(struct device *dev) · line 140
Quoted 2 lines
drivers/of/platform.c +2
-0+ + dev->dma_mask = &dev->coherent_dma_mask;
I don't really know, but I suspect this is wrong.
Quoted 34 lines
drivers/of/platform.c +8
-7+ +if (!prop) + dev->coherent_dma_mask =DMA_BIT_MASK (32 ); +else + dev->coherent_dma_mask =of_read_number (prop, len /4 ); +} +/** * of_device_alloc - Allocate and initialize an of_device * @np: device node to assign to device @@ -171,10 +186,8 @@ struct platform_device *of_device_alloc(struct device_node *np,WARN_ON (of_irq_to_resource_table (np, res, num_irq) != num_irq); } +of_get_dma_mask (&dev->dev, np); dev->dev.of_node =of_node_get (np); -#if defined(CONFIG_MICROBLAZE ) - dev->dev.dma_mask = &dev->archdata.dma_mask; -#endif dev->dev.parent = parent;if (bus_id)@@ -211,10 +224,6 @@ struct platform_device *of_platform_device_create_pdata(if (!dev)return NULL ; -#if defined(CONFIG_MICROBLAZE ) - dev->archdata.dma_mask =0xffffffffUL ; -#endif - dev->dev.coherent_dma_mask =DMA_BIT_MASK (32 ); dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data;
PATCH 1/17 of: add dma-mask binding
Quoted 8 lines · Rob Herring
Quoted 5 lines · Wenyou Yang
From: Jean-Christophe PLAGNIOL-VILLARD <[email protected]>
This will allow each device to specify its dma-mask for this we use the
coherent_dma_maskas pointer. By default the dma-mask will be set toDMA_BIT_MASK(32).Do you really have a use case this is not
DMA_BIT_MASK(32)?
yes as exmample on 64bit platfrom it will be 64 on x86 it's also 24, on power pc 40
Quoted 28 lines
Quoted 16 lines
The microblaze architecture hook is drop
Signed-off-by Jean-Christophe PLAGNIOL-VILLARD <[email protected]>Cc grant.likely at secretlab.caCc rob.herring at calxeda.comCc devicetree-discuss at lists.ozlabs.org--- drivers/of/platform.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-)drivers/of/platform.c +0
-0drivers/of/platform.c void of_device_make_bus_id(struct device *dev) · line 138
Quoted 9 lines
drivers/of/platform.c +6
-0@@ -130,6 +130,21 @@ void of_device_make_bus_id(struct device *dev)dev_set_name (dev,"%s.%d" , node->name, magic -1 ); } +static void of_get_dma_mask (struct device *dev,struct device_node *np) +{ +const __be32 *prop; +int len; + + prop =of_get_property (np,"dma-mask" , &len);dma-ranges may work for this purpose.
Quoted 4 lines
drivers/of/platform.c void of_device_make_bus_id(struct device *dev) · line 140
Quoted 2 lines
drivers/of/platform.c +2
-0+ + dev->dma_mask = &dev->coherent_dma_mask;I don't really know, but I suspect this is wrong.
no this is correct was suggest by Russell
we already do so on other ARM or SH as example
the dma expect a pointer for dma_mask but the value is the same as coherent
Quoted 35 lines
Quoted 35 lines
drivers/of/platform.c +8
-7+ +if (!prop) + dev->coherent_dma_mask =DMA_BIT_MASK (32 ); +else + dev->coherent_dma_mask =of_read_number (prop, len /4 ); +} +/** * of_device_alloc - Allocate and initialize an of_device * @np: device node to assign to device @@ -171,10 +186,8 @@ struct platform_device *of_device_alloc(struct device_node *np,WARN_ON (of_irq_to_resource_table (np, res, num_irq) != num_irq); } +of_get_dma_mask (&dev->dev, np); dev->dev.of_node =of_node_get (np); -#if defined(CONFIG_MICROBLAZE ) - dev->dev.dma_mask = &dev->archdata.dma_mask; -#endif dev->dev.parent = parent;if (bus_id)@@ -211,10 +224,6 @@ struct platform_device *of_platform_device_create_pdata(if (!dev)return NULL ; -#if defined(CONFIG_MICROBLAZE ) - dev->archdata.dma_mask =0xffffffffUL ; -#endif - dev->dev.coherent_dma_mask =DMA_BIT_MASK (32 ); dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data;
PATCH 1/17 of: add dma-mask binding
Quoted 39 lines · Jean-Christophe PLAGNIOL-VILLARD
Quoted 8 lines · Rob Herring
Quoted 5 lines · Wenyou Yang
From: Jean-Christophe PLAGNIOL-VILLARD <[email protected]>
This will allow each device to specify its dma-mask for this we use the
coherent_dma_maskas pointer. By default the dma-mask will be set toDMA_BIT_MASK(32).Do you really have a use case this is not
DMA_BIT_MASK(32)?yes as exmample on 64bit platfrom it will be 64 on x86 it's also 24, on power pc 40
Quoted 28 lines
Quoted 16 lines
The microblaze architecture hook is drop
Signed-off-by Jean-Christophe PLAGNIOL-VILLARD <[email protected]>Cc grant.likely at secretlab.caCc rob.herring at calxeda.comCc devicetree-discuss at lists.ozlabs.org--- drivers/of/platform.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-)drivers/of/platform.c +0
-0drivers/of/platform.c void of_device_make_bus_id(struct device *dev) · line 138
Quoted 9 lines
drivers/of/platform.c +6
-0@@ -130,6 +130,21 @@ void of_device_make_bus_id(struct device *dev)dev_set_name (dev,"%s.%d" , node->name, magic -1 ); } +static void of_get_dma_mask (struct device *dev,struct device_node *np) +{ +const __be32 *prop; +int len; + + prop =of_get_property (np,"dma-mask" , &len);dma-ranges may work for this purpose.
Quoted 8 lines
Quoted 4 lines
drivers/of/platform.c void of_device_make_bus_id(struct device *dev) · line 140
Quoted 2 lines
drivers/of/platform.c +2
-0+ + dev->dma_mask = &dev->coherent_dma_mask;I don't really know, but I suspect this is wrong.
no this is correct was suggest by Russell
we already do so on other ARM or SH as examplethe dma expect a pointer for
dma_maskbut the value is the same as coherent
Okay. Then perhaps this part should be a separate patch as that is useful on its own for 32-bit machines with no DMA address restrictions (most modern ARM h/w).
My comment on using dma-ranges still stands though. The form is <parent-address child-address size>. Normally, parent and child would be the same. I think the mask would be "parent address + size - 1". The simple case is <0 0 0> for all of 32-bit memory. I'm using 0 for full 32-bit size here as #size-cells is typically 1 and I can't imagine that 0 size would be useful.
Rob
Quoted 35 lines
Quoted 35 lines
Quoted 35 lines
drivers/of/platform.c +8
-7+ +if (!prop) + dev->coherent_dma_mask =DMA_BIT_MASK (32 ); +else + dev->coherent_dma_mask =of_read_number (prop, len /4 ); +} +/** * of_device_alloc - Allocate and initialize an of_device * @np: device node to assign to device @@ -171,10 +186,8 @@ struct platform_device *of_device_alloc(struct device_node *np,WARN_ON (of_irq_to_resource_table (np, res, num_irq) != num_irq); } +of_get_dma_mask (&dev->dev, np); dev->dev.of_node =of_node_get (np); -#if defined(CONFIG_MICROBLAZE ) - dev->dev.dma_mask = &dev->archdata.dma_mask; -#endif dev->dev.parent = parent;if (bus_id)@@ -211,10 +224,6 @@ struct platform_device *of_platform_device_create_pdata(if (!dev)return NULL ; -#if defined(CONFIG_MICROBLAZE ) - dev->archdata.dma_mask =0xffffffffUL ; -#endif - dev->dev.coherent_dma_mask =DMA_BIT_MASK (32 ); dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data;
PATCH 1/17 of: add dma-mask binding
Quoted 7 lines · Rob Herring
Quoted 6 lines · Jean-Christophe PLAGNIOL-VILLARD
Quoted 5 lines · Rob Herring
Review
Quoted 2 lines · Wenyou Yang
(quoted diff) +2
-0+ + dev->dma_mask = &dev->coherent_dma_mask;I don't really know, but I suspect this is wrong.
Quoted 8 lines
Quoted 4 lines
no this is correct was suggest by Russell
we already do so on other ARM or SH as examplethe dma expect a pointer for
dma_maskbut the value is the same as coherentOkay. Then perhaps this part should be a separate patch as that is useful on its own for 32-bit machines with no DMA address restrictions (most modern ARM h/w).
I'm not sure that I did make the exact suggestion being alluded to above (I think I may have made the suggestion that dev->dma_mask should be pointed at a dma_mask, and it might be a good idea that it should be part of struct device.)
I've always shy'd away from making it the same thing as the coherent DMA mask, because there are drivers around which modify the value pointed to by dev->dma_mask via standard DMA API calls. (Eg, when they know that the device is only N-bit capable.)
With that set to the same as dev->coherent_dma_mask, it ends up modifying the coherent DMA mask too which may or may not be entirely a good thing; I suspect ultimately that depends on the driver.
My thoughts on this though is that the whole thing is a mess. I'm not sure why we ended up with a separate coherent_dma_mask from the main dma_mask, and why we ended up with dma_mask being a pointer to something allocated elsewhere... it all seems like it's unnecessarily complicated and was designed to cause confusion...
PATCH 2/17 of_spi: add generic binding support to specify cs gpio
In reply to Wenyou YangFrom: Jean-Christophe PLAGNIOL-VILLARD <[email protected]>
This will allow to use gpio for chip select with no modification in the driver binding
When use the cs-gpios, the gpio number will be passed via the cs_gpio field and the number of chip select will automatically increased.
---
Hi, Richard,
This patch based on the original patch from Jean-Christophe,
[PATCH] of_spi: add generic binding support to specify cs gpio
and merged the patch from Richard Genoud,
[PATCH] [BUG] SPI: array out of bound => no CS
Could you sign your signature in this patch?
Best Regards,
Wenyou Yang
Documentation/devicetree/bindings/spi/spi-bus.txt | 6 +++ drivers/spi/spi.c | 55 +++++++++++++++++++-- include/linux/spi/spi.h | 3 ++ 3 files changed, 61 insertions(+), 3 deletions(-)
Documentation/devicetree/bindings/spi/spi-bus.txt +6 -0
@@ -12,6 +12,7 @@ The SPI master node requires the following properties: - #size-cells - should be zero. - compatible - name of SPI bus controller following generic names recommended practice. +- cs-gpios - (optional) gpios chip select. No other properties are required in the SPI bus node. It is assumed that a driver for an SPI bus device will understand that it is an SPI bus. However, the binding does not attempt to define the specific method for
@@ -21,6 +22,8 @@ assumption that board specific platform code will be used to manage chip selects. Individual drivers can define additional properties to support describing the chip select layout. +If cs-gpios is used the number of chip select will automatically increased. + Optional property: - num-cs : total number of chipselects
@@ -37,6 +40,9 @@ contain the following properties. - spi-cs-high - (optional) Empty property indicating device requires chip select active high +If a gpio chipselect is used for the SPI slave the gpio number will be passed +via the controller_data + SPI example for an MPC5200 SPI bus: spi at f00 { #address-cells = <1>;
drivers/spi/spi.c +52 -3
97 lines not on this page · raw message
include/linux/spi/spi.h +3 -0
@@ -90,6 +90,7 @@ struct spi_device {void *controller_state;void *controller_data;char modalias[SPI_NAME_SIZE ]; +int cs_gpio;/* chip select gpio */ /* * likely need more hooks for more protocol options affecting how
@@ -362,6 +363,8 @@ struct spi_master {int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);int (*unprepare_transfer_hardware)(struct spi_master *master); +/* gpio chip select */ +int *cs_gpios; };static inline void *spi_master_get_devdata (struct spi_master *master)
Signature · 1 line
1.7.9.5
PATCH 3/17 spi/atmel_spi: add physical base address
In reply to Wenyou YangFrom: Nicolas Ferre <[email protected]>
Needed for future use with dmaengine enabled driver.
--- drivers/spi/spi-atmel.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/spi/spi-atmel.c +2 -0
@@ -188,6 +188,7 @@struct atmel_spi {spinlock_t lock; +resource_size_t phybase;void __iomem *regs;int irq;struct clk *clk;
@@ -961,6 +962,7 @@ static int __devinit atmel_spi_probe(struct platform_device *pdev) as->regs =ioremap (regs->start,resource_size (regs));if (!as->regs) goto out_free_buffer; + as->phybase = regs->start; as->irq = irq; as->clk = clk;
Signature · 1 line
1.7.9.5
PATCH 4/17 spi/atmel_spi: call unmapping on transfers buffers
In reply to Wenyou YangFrom: Nicolas Ferre <[email protected]>
--- drivers/spi/spi-atmel.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-)
drivers/spi/spi-atmel.c +5 -3
@@ -1010,6 +1010,7 @@ static int __devexit atmel_spi_remove(struct platform_device *pdev)struct spi_master *master =platform_get_drvdata (pdev);struct atmel_spi *as =spi_master_get_devdata (master);struct spi_message *msg; +struct spi_transfer *xfer;/* reset the hardware and block queue progress */ spin_lock_irq (&as->lock);
@@ -1021,9 +1022,10 @@ static int __devexit atmel_spi_remove(struct platform_device *pdev)/* Terminate remaining queued transfers */ list_for_each_entry (msg, &as->queue, queue) { -/* REVISIT unmapping the dma is a NOP on ARM and AVR32 -* but we shouldn't depend on that... -*/ +list_for_each_entry (xfer, &msg->transfers, transfer_list) { +if (!msg->is_dma_mapped) +atmel_spi_dma_unmap_xfer (master, xfer); + } msg->status = -ESHUTDOWN ; msg->complete (msg->context); }
Signature · 1 line
1.7.9.5
PATCH 5/17 spi/atmel_spi: status information passed through controller data
In reply to Wenyou YangFrom: Nicolas Ferre <[email protected]>
The status of transfer is stored in controller data structure so that it can be used not only by atmel_spi_msg_done() function. This will be useful for upcoming dmaengine enabled driver.
--- drivers/spi/spi-atmel.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-)
drivers/spi/spi-atmel.c +8 -5
@@ -201,6 +201,7 @@ struct atmel_spi {unsigned long current_remaining_bytes;struct spi_transfer *next_transfer;unsigned long next_remaining_bytes; +int done_status;void *buffer;dma_addr_t buffer_dma;
@@ -544,15 +545,15 @@ static void atmel_spi_dma_unmap_xfer(struct spi_master *master,static void atmel_spi_msg_done (struct spi_master *master,struct atmel_spi *as, -struct spi_message *msg,int status,int stay) +struct spi_message *msg,int stay) { -if (!stay || status <0 ) +if (!stay || as->done_status <0 )cs_deactivate (as, msg->spi);else as->stay = msg->spi;list_del (&msg->queue); - msg->status = status; + msg->status = as->done_status; dev_dbg(master->dev.parent, "xfer complete: %u bytes transferred\n",
@@ -564,6 +565,7 @@ atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as, as->current_transfer =NULL ; as->next_transfer =NULL ; + as->done_status =0 ;/* continue if needed */ if (list_empty (&as->queue) || as->stopping)
@@ -641,7 +643,8 @@ atmel_spi_interrupt(int irq, void *dev_id)/* Clear any overrun happening while cleaning up */ spi_readl (as,SR ); -atmel_spi_msg_done (master, as, msg, -EIO ,0 ); + as->done_status = -EIO ; +atmel_spi_msg_done (master, as, msg,0 ); }else if (pending & (SPI_BIT (RXBUFF ) |SPI_BIT (ENDRX ))) { ret =IRQ_HANDLED ;
@@ -659,7 +662,7 @@ atmel_spi_interrupt(int irq, void *dev_id)if (atmel_spi_xfer_is_last (msg, xfer)) {/* report completed message */ -atmel_spi_msg_done (master, as, msg,0 , +atmel_spi_msg_done (master, as, msg, xfer->cs_change); }else {if (xfer->cs_change) {
Signature · 1 line
1.7.9.5
PATCH 6/17 spi/atmel_spi: add flag to controller data for lock operations
In reply to Wenyou YangFrom: Nicolas Ferre <[email protected]>
Will allow to drop the lock during DMA operations.
--- drivers/spi/spi-atmel.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-)
drivers/spi/spi-atmel.c +19 -12
@@ -187,6 +187,7 @@*/ struct atmel_spi {spinlock_t lock; +unsigned long flags;resource_size_t phybase;void __iomem *regs;
@@ -323,6 +324,16 @@ static void cs_deactivate(struct atmel_spi *as, struct spi_device *spi)gpio_set_value (asd->npcs_pin, !active); } +static void atmel_spi_lock (struct atmel_spi *as) +{ +spin_lock_irqsave (&as->lock, as->flags); +} + +static void atmel_spi_unlock (struct atmel_spi *as) +{ +spin_unlock_irqrestore (&as->lock, as->flags); +} +static inline int atmel_spi_xfer_is_last (struct spi_message *msg,struct spi_transfer *xfer) {
@@ -559,9 +570,9 @@ atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as,"xfer complete: %u bytes transferred\n" , msg->actual_length); -spin_unlock (&as->lock); +atmel_spi_unlock (as); msg->complete (msg->context); -spin_lock (&as->lock); +atmel_spi_lock (as); as->current_transfer =NULL ; as->next_transfer =NULL ;
@@ -788,13 +799,11 @@ static int atmel_spi_setup(struct spi_device *spi) spi->controller_state = asd;gpio_direction_output (npcs_pin, !(spi->mode &SPI_CS_HIGH )); }else { -unsigned long flags; - -spin_lock_irqsave (&as->lock, flags); +atmel_spi_lock (as);if (as->stay == spi) as->stay =NULL ;cs_deactivate (as, spi); -spin_unlock_irqrestore (&as->lock, flags); +atmel_spi_unlock (as); } asd->csr = csr;
@@ -813,7 +822,6 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg) {struct atmel_spi *as;struct spi_transfer *xfer; -unsigned long flags;struct device *controller = spi->master->dev.parent;u8 bits;struct atmel_spi_device *asd;
@@ -878,11 +886,11 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg) msg->status = -EINPROGRESS ; msg->actual_length =0 ; -spin_lock_irqsave (&as->lock, flags); +atmel_spi_lock (as);list_add_tail (&msg->queue, &as->queue);if (!as->current_transfer)atmel_spi_next_message (spi->master); -spin_unlock_irqrestore (&as->lock, flags); +atmel_spi_unlock (as);return 0 ; }
@@ -892,17 +900,16 @@ static void atmel_spi_cleanup(struct spi_device *spi)struct atmel_spi *as =spi_master_get_devdata (spi->master);struct atmel_spi_device *asd = spi->controller_state;unsigned gpio = (unsigned ) spi->controller_data; -unsigned long flags;if (!asd)return ; -spin_lock_irqsave (&as->lock, flags); +atmel_spi_lock (as);if (as->stay == spi) { as->stay =NULL ;cs_deactivate (as, spi); } -spin_unlock_irqrestore (&as->lock, flags); +atmel_spi_unlock (as); spi->controller_state =NULL ;gpio_free (gpio);
Signature · 1 line
1.7.9.5
PATCH 6/17 spi/atmel_spi: add flag to controller data for lock operations
Quoted 26 lines · Wenyou Yang
From: Nicolas Ferre <[email protected]>
Will allow to drop the lock during DMA operations.
Signed-off-by Nicolas Ferre <[email protected]>Cc grant.likely at secretlab.caCc spi-devel-general at lists.sourceforge.net--- drivers/spi/spi-atmel.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-)drivers/spi/spi-atmel.c +1
-0@@ -187,6 +187,7 @@*/ struct atmel_spi {spinlock_t lock; +unsigned long flags;resource_size_t phybase;void __iomem *regs;@@ -323,6 +324,16 @@ static void cs_deactivate(struct atmel_spi *as, struct spi_device *spi)gpio_set_value (asd->npcs_pin, !active); }
drivers/spi/spi-atmel.c static void cs_deactivate(struct atmel_spi *as, struct spi_device *spi) · line 335
Quoted 10 lines
drivers/spi/spi-atmel.c +9
-0+static void atmel_spi_lock (struct atmel_spi *as) +{ +spin_lock_irqsave (&as->lock, as->flags); +} + +static void atmel_spi_unlock (struct atmel_spi *as) +{ +spin_unlock_irqrestore (&as->lock, as->flags); +}
May be this can be called directly.
Quoted 87 lines
drivers/spi/spi-atmel.c +9
-13+static inline int atmel_spi_xfer_is_last (struct spi_message *msg,struct spi_transfer *xfer) {@@ -559,9 +570,9 @@ atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as,"xfer complete: %u bytes transferred\n" , msg->actual_length); -spin_unlock (&as->lock); +atmel_spi_unlock (as); msg->complete (msg->context); -spin_lock (&as->lock); +atmel_spi_lock (as); as->current_transfer =NULL ; as->next_transfer =NULL ;@@ -788,13 +799,11 @@ static int atmel_spi_setup(struct spi_device *spi) spi->controller_state = asd;gpio_direction_output (npcs_pin, !(spi->mode &SPI_CS_HIGH )); }else { -unsigned long flags; - -spin_lock_irqsave (&as->lock, flags); +atmel_spi_lock (as);if (as->stay == spi) as->stay =NULL ;cs_deactivate (as, spi); -spin_unlock_irqrestore (&as->lock, flags); +atmel_spi_unlock (as); } asd->csr = csr;@@ -813,7 +822,6 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg) {struct atmel_spi *as;struct spi_transfer *xfer; -unsigned long flags;struct device *controller = spi->master->dev.parent;u8 bits;struct atmel_spi_device *asd;@@ -878,11 +886,11 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg) msg->status = -EINPROGRESS ; msg->actual_length =0 ; -spin_lock_irqsave (&as->lock, flags); +atmel_spi_lock (as);list_add_tail (&msg->queue, &as->queue);if (!as->current_transfer)atmel_spi_next_message (spi->master); -spin_unlock_irqrestore (&as->lock, flags); +atmel_spi_unlock (as);return 0 ; }@@ -892,17 +900,16 @@ static void atmel_spi_cleanup(struct spi_device *spi)struct atmel_spi *as =spi_master_get_devdata (spi->master);struct atmel_spi_device *asd = spi->controller_state;unsigned gpio = (unsigned ) spi->controller_data; -unsigned long flags;if (!asd)return ; -spin_lock_irqsave (&as->lock, flags); +atmel_spi_lock (as);if (as->stay == spi) { as->stay =NULL ;cs_deactivate (as, spi); } -spin_unlock_irqrestore (&as->lock, flags); +atmel_spi_unlock (as); spi->controller_state =NULL ;gpio_free (gpio); --1.7.9.5
------------------------------------------------------------------------------
Everyone hates slow websites. So do we.
Make your web apps faster with AppDynamics
Download AppDynamics Lite for free today:
http://p.sf.net/sfu/appdyn_d2d_nov
_______________________________________________
spi-devel-general mailing list
spi-devel-general at lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/spi-devel-general
PATCH 7/17 spi/atmel_spi: add dmaengine support
In reply to Wenyou YangFrom: Nicolas Ferre <[email protected]>
Add dmaengine support.
According to the SoC dma type, to select the SPI xfer mode: PDC or dmaengine.
---
Hi, Richard,
This patch is based on the original patch from Nicolas
[PATCH] spi/atmel_spi: add dmaengine support
and merged the patches from Richard Genoud,
[PATCH] spi-atmel: update with dmaengine interface
[PATCH] spi-atmel: fix __init/__devinit sections mismatch
and Wenyou Yang add the code to support DTS section for selecting the SPI xfer mode,
and fix not supporting NPCS1,2,3 chip select only NPCS0 BUG.
Could you sign your signature in this patch?
Best Regards,
Wenyou Yang
drivers/spi/spi-atmel.c | 530 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 509 insertions(+), 21 deletions(-)
drivers/spi/spi-atmel.c +509 -21
@@ -15,6 +15,7 @@#include <linux/platform_device.h> #include <linux/delay.h> #include <linux/dma-mapping.h> +#include <linux/dmaengine.h> #include <linux/err.h> #include <linux/interrupt.h> #include <linux/spi/spi.h>
@@ -24,6 +25,7 @@#include <mach/board.h> #include <asm/gpio.h> #include <mach/cpu.h> +#include <linux/platform_data/dma-atmel.h> /* SPI register offsets */ #define SPI_CR 0x0000
@@ -179,6 +181,27 @@#define spi_writel (port,reg,value) \ __raw_writel((value), (port)->regs + SPI_##reg) +/* dma_type: the dma type supported by the spi. +* dma_type = 0 (no used), = 1 (pdc), = 2 (dma) +*/ +struct atmel_spi_data { +u8 dma_type; +struct at_dma_slave dma_slave; +}; + +/* use PIO for small transfers, avoiding DMA setup/teardown overhead and +* cache operations; better heuristics consider wordsize and bitrate. +*/ +#define DMA_MIN_BYTES 16 + +struct atmel_spi_dma { +struct dma_chan *chan_rx; +struct dma_chan *chan_tx; +struct scatterlist sgrx; +struct scatterlist sgtx; +struct dma_async_tx_descriptor *data_desc_rx; +struct dma_async_tx_descriptor *data_desc_tx; +};/* * The core SPI transfer engine just talks to a register bank to set up
@@ -198,14 +221,23 @@ struct atmel_spi {u8 stopping;struct list_head queue; +struct tasklet_struct tasklet;struct spi_transfer *current_transfer;unsigned long current_remaining_bytes;struct spi_transfer *next_transfer;unsigned long next_remaining_bytes;int done_status; +struct atmel_spi_data data; + +bool use_dma; +bool use_pdc; +/* scratch buffer */ void *buffer;dma_addr_t buffer_dma; + +/* dmaengine data */ +struct atmel_spi_dma dma; };/* Controller-specific per-slave state */
@@ -271,9 +303,9 @@ static void cs_activate(struct atmel_spi *as, struct spi_device *spi)* switches to the correct idle polarity before we * toggle the CS. */ -spi_writel (as,CSR0 , asd->csr); -spi_writel (as,MR ,SPI_BF (PCS ,0x0e ) |SPI_BIT (MODFDIS ) - |SPI_BIT (MSTR )); +spi_writel (as,CSR0 +4 * spi->chip_select, asd->csr); +spi_writel (as,MR ,SPI_BF (PCS , ~(0x01 << spi->chip_select)) + |SPI_BIT (MODFDIS ) |SPI_BIT (MSTR )); mr =spi_readl (as,MR );gpio_set_value (asd->npcs_pin, active); }else {
@@ -334,6 +366,23 @@ static void atmel_spi_unlock(struct atmel_spi *as)spin_unlock_irqrestore (&as->lock, as->flags); } +static inline bool atmel_spi_use_dma (struct atmel_spi *as, +struct spi_transfer *xfer) +{ +if ((as->use_dma) && (xfer->len >=DMA_MIN_BYTES )) +return true; +else +return false; +} + +static inline bool atmel_spi_use_pdc (struct atmel_spi *as) +{ +if (as->use_pdc) +return true; +else +return false; +} +static inline int atmel_spi_xfer_is_last (struct spi_message *msg,struct spi_transfer *xfer) {
@@ -345,6 +394,209 @@ static inline int atmel_spi_xfer_can_be_chained(struct spi_transfer *xfer)return xfer->delay_usecs ==0 && !xfer->cs_change; } +static bool filter (struct dma_chan *chan,void *slave) +{ +struct at_dma_slave *sl = slave; + +if (sl->dma_dev == chan->device->dev) { + chan->private = sl; +return true; + }else { +return false; + } +} + +static int __devinit atmel_spi_configure_dma (struct atmel_spi *as) +{ +struct at_dma_slave *sdata = (struct at_dma_slave *)&as->data.dma_slave; + +if (sdata && sdata->dma_dev) { +dma_cap_mask_t mask; + +/* setup DMA addresses */ + sdata->rx_reg = (dma_addr_t )as->phybase +SPI_RDR ; + sdata->tx_reg = (dma_addr_t )as->phybase +SPI_TDR ; + +/* Try to grab two DMA channels */ +dma_cap_zero (mask); +dma_cap_set (DMA_SLAVE , mask); + as->dma.chan_tx =dma_request_channel (mask, filter, sdata); +if (as->dma.chan_tx) + as->dma.chan_rx = +dma_request_channel (mask, filter, sdata); + } +if (!as->dma.chan_rx || !as->dma.chan_tx) { +if (as->dma.chan_rx) +dma_release_channel (as->dma.chan_rx); +if (as->dma.chan_tx) +dma_release_channel (as->dma.chan_tx); +dev_err (&as->pdev->dev,"DMA channel not available, " \ +"unable to use SPI\n" ); +return -EBUSY ; + } + +dev_info (&as->pdev->dev,"Using %s (tx) and " \ +" %s (rx) for DMA transfers\n" , +dma_chan_name (as->dma.chan_tx), +dma_chan_name (as->dma.chan_rx)); + +return 0 ; +} + +static void atmel_spi_stop_dma (struct atmel_spi *as) +{ +if (as->dma.chan_rx) + as->dma.chan_rx->device->device_control (as->dma.chan_rx, +DMA_TERMINATE_ALL ,0 ); +if (as->dma.chan_tx) + as->dma.chan_tx->device->device_control (as->dma.chan_tx, +DMA_TERMINATE_ALL ,0 ); +} + +static void atmel_spi_release_dma (struct atmel_spi *as) +{ +if (as->dma.chan_rx) +dma_release_channel (as->dma.chan_rx); +if (as->dma.chan_tx) +dma_release_channel (as->dma.chan_tx); +} + +/* This function is called by the DMA driver from tasklet context */ +static void dma_callback (void *data) +{ +struct spi_master *master = data; +struct atmel_spi *as =spi_master_get_devdata (master); + +/* trigger SPI tasklet */ +tasklet_schedule (&as->tasklet); +} + +/* +* Next transfer using PIO. +* lock is held, spi tasklet is blocked +*/ +static void atmel_spi_next_xfer_pio (struct spi_master *master, +struct spi_transfer *xfer) +{ +struct atmel_spi *as =spi_master_get_devdata (master); + +dev_vdbg (master->dev.parent,"atmel_spi_next_xfer_pio\n" ); + + as->current_remaining_bytes = xfer->len; + +/* Make sure data is not remaining in RDR */ +spi_readl (as,RDR ); +while (spi_readl (as,SR ) &SPI_BIT (RDRF )) { +spi_readl (as,RDR ); +cpu_relax (); + } + +if (xfer->tx_buf) +spi_writel (as,TDR , *(u8 *)(xfer->tx_buf)); +else +spi_writel (as,TDR ,0 ); + +dev_dbg (master->dev.parent, +" start pio xfer %p: len %u tx %p rx %p\n" , + xfer, xfer->len, xfer->tx_buf, xfer->rx_buf); + +/* Enable relevant interrupts */ +spi_writel (as,IER ,SPI_BIT (RDRF ) |SPI_BIT (OVRES )); +} + +/* +* Submit next transfer for DMA. +* lock is held, spi tasklet is blocked +*/ +static int atmel_spi_next_xfer_dma (struct spi_master *master, +struct spi_transfer *xfer) +{ +struct atmel_spi *as =spi_master_get_devdata (master); +struct dma_chan *rxchan = as->dma.chan_rx; +struct dma_chan *txchan = as->dma.chan_tx; +struct dma_async_tx_descriptor *rxdesc; +struct dma_async_tx_descriptor *txdesc; +dma_cookie_t cookie; + +dev_vdbg (master->dev.parent,"atmel_spi_next_xfer_dma\n" ); + +/* Check that the channels are available */ +if (!rxchan || !txchan) +return -ENODEV ; + +/* release lock for DMA operations */ +atmel_spi_unlock (as); + +/* prepare the RX dma transfer */ +sg_init_table (&as->dma.sgrx,1 ); +sg_dma_len (&as->dma.sgrx) = xfer->len; +if (xfer->rx_buf) + as->dma.sgrx.dma_address = xfer->rx_dma; +else + as->dma.sgrx.dma_address = as->buffer_dma; + +/* prepare the TX dma transfer */ +sg_init_table (&as->dma.sgtx,1 ); +sg_dma_len (&as->dma.sgtx) = xfer->len; +if (xfer->tx_buf) { + as->dma.sgtx.dma_address = xfer->tx_dma; + }else { + as->dma.sgtx.dma_address = as->buffer_dma; +memset (as->buffer,0 , xfer->len); + } + +/* Send both scatterlists */ + rxdesc = rxchan->device->device_prep_slave_sg (rxchan, + &as->dma.sgrx, +1 , +DMA_FROM_DEVICE , +DMA_PREP_INTERRUPT |DMA_CTRL_ACK , +NULL ); +if (!rxdesc) + goto err_dma; + + txdesc = txchan->device->device_prep_slave_sg (txchan, + &as->dma.sgtx, +1 , +DMA_TO_DEVICE , +DMA_PREP_INTERRUPT |DMA_CTRL_ACK , +NULL ); +if (!txdesc) + goto err_dma; + +dev_dbg (master->dev.parent, +" start dma xfer %p: len %u tx %p/%08x rx %p/%08x\n" , + xfer, xfer->len, xfer->tx_buf, xfer->tx_dma, + xfer->rx_buf, xfer->rx_dma); + +/* Enable relevant interrupts */ +spi_writel (as,IER ,SPI_BIT (OVRES )); + +/* Put the callback on the RX transfer only, that should finish last */ + rxdesc->callback = dma_callback; + rxdesc->callback_param = master; + +/* Submit and fire RX and TX with TX last so we're ready to read! */ + cookie = rxdesc->tx_submit (rxdesc); +if (dma_submit_error (cookie)) + goto err_dma; + cookie = txdesc->tx_submit (txdesc); +if (dma_submit_error (cookie)) + goto err_dma; + rxchan->device->device_issue_pending (rxchan); + txchan->device->device_issue_pending (txchan); + +/* take back lock */ +atmel_spi_lock (as); +return 0 ; + +err_dma: +spi_writel (as,IDR ,SPI_BIT (OVRES )); +atmel_spi_stop_dma (as); +atmel_spi_lock (as); +return -ENOMEM ; +} + static void atmel_spi_next_xfer_data(struct spi_master *master, struct spi_transfer *xfer, dma_addr_t *tx_dma,
@@ -377,10 +629,10 @@ static void atmel_spi_next_xfer_data(struct spi_master *master, }/* -* Submit next transfer for DMA. +* Submit next transfer for PDC. * lock is held, spi irq is blocked */ -static void atmel_spi_next_xfer (struct spi_master *master, +static void atmel_spi_next_xfer_pdc (struct spi_master *master,struct spi_message *msg) {struct atmel_spi *as =spi_master_get_devdata (master);
@@ -477,6 +729,44 @@ static void atmel_spi_next_xfer(struct spi_master *master,spi_writel (as,PTCR ,SPI_BIT (TXTEN ) |SPI_BIT (RXTEN )); } +/* +* Choose way to submit next transfer and start it. +* lock is held, spi tasklet is blocked +*/ +static void atmel_spi_next_xfer (struct spi_master *master, +struct spi_message *msg) +{ +struct atmel_spi *as =spi_master_get_devdata (master); +struct spi_transfer *xfer; + +dev_vdbg (&msg->spi->dev,"atmel_spi_next_xfer\n" ); + +if (as->use_pdc) +atmel_spi_next_xfer_pdc (master, msg); +else { +if (!as->current_transfer) + xfer =list_entry (msg->transfers.next, + struct spi_transfer, transfer_list); +else + xfer =list_entry ( + as->current_transfer->transfer_list.next, + struct spi_transfer, transfer_list); + + as->current_transfer = xfer; + +/* quick (and *really* not optimal) workaround for DMA BUG */ +if (atmel_spi_use_dma (as, xfer)) { +if (!atmel_spi_next_xfer_dma (master, xfer)) +return ; +else +dev_err (&msg->spi->dev,"unable to use DMA, fallback to PIO\n" ); + } + +/* use PIO if xfer is short or error appened using DMA */ +atmel_spi_next_xfer_pio (master, xfer); + } +} +static void atmel_spi_next_message (struct spi_master *master) {struct atmel_spi *as =spi_master_get_devdata (master);
@@ -554,6 +844,11 @@ static void atmel_spi_dma_unmap_xfer(struct spi_master *master, xfer->len, DMA_FROM_DEVICE); } +static void atmel_spi_disable_pdc_transfer (struct atmel_spi *as) +{ +spi_writel (as,PTCR ,SPI_BIT (RXTDIS ) |SPI_BIT (TXTDIS )); +} +static void atmel_spi_msg_done (struct spi_master *master,struct atmel_spi *as,struct spi_message *msg,int stay)
@@ -579,19 +874,170 @@ atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as, as->done_status =0 ;/* continue if needed */ -if (list_empty (&as->queue) || as->stopping) -spi_writel (as,PTCR ,SPI_BIT (RXTDIS ) |SPI_BIT (TXTDIS )); -else +if (list_empty (&as->queue) || as->stopping) { +if (as->use_pdc) +atmel_spi_disable_pdc_transfer (as); + }else atmel_spi_next_message (master); } -static irqreturn_t -atmel_spi_interrupt (int irq,void *dev_id) +/* Called from IRQ +* lock is held +* +* Must update "current_remaining_bytes" to keep track of data +* to transfer. +*/ +static void +atmel_spi_pump_pio_data (struct atmel_spi *as,struct spi_transfer *xfer) { -struct spi_master *master = dev_id; +u8 *txp; +u8 *rxp; +unsigned long xfer_pos = xfer->len - as->current_remaining_bytes; + +if (xfer->rx_buf) { + rxp = ((u8 *)xfer->rx_buf) + xfer_pos; + *rxp =spi_readl (as,RDR ); + }else { +spi_readl (as,RDR ); + } + + as->current_remaining_bytes--; + +if (as->current_remaining_bytes) { +if (xfer->tx_buf) { + txp = ((u8 *)xfer->tx_buf) + xfer_pos +1 ; +spi_writel (as,TDR , *txp); + }else { +spi_writel (as,TDR ,0 ); + } + } +} + +/* Tasklet +* Called from DMA callback + pio transfer and overrun IRQ. +*/ +static void atmel_spi_tasklet_func (unsigned long data) +{ +struct spi_master *master = (struct spi_master *)data;struct atmel_spi *as =spi_master_get_devdata (master);struct spi_message *msg;struct spi_transfer *xfer; + +dev_vdbg (master->dev.parent,"atmel_spi_tasklet_func\n" ); + +atmel_spi_lock (as); + + xfer = as->current_transfer; + +if (xfer ==NULL ) +/* already been there */ + goto tasklet_out; + + msg = list_entry(as->queue.next, struct spi_message, queue); + +if (as->done_status <0 ) { +/* error happened (overrun) */ +if (atmel_spi_use_dma (as, xfer)) +atmel_spi_stop_dma (as); + }else { +/* only update length if no error */ + msg->actual_length += xfer->len; + } + +if (atmel_spi_use_dma (as, xfer)) { +if (!msg->is_dma_mapped) +atmel_spi_dma_unmap_xfer (master, xfer); + } + +if (xfer->delay_usecs) +udelay (xfer->delay_usecs); + +if (atmel_spi_xfer_is_last (msg, xfer) || as->done_status <0 ) { +/* report completed (or erroneous) message */ +atmel_spi_msg_done (master, as, msg, xfer->cs_change); + }else { +if (xfer->cs_change) { +cs_deactivate (as, msg->spi); +udelay (1 ); +cs_activate (as, msg->spi); + } + +/* +* Not done yet. Submit the next transfer. +* +* FIXME handle protocol options for xfer +*/ +atmel_spi_next_xfer (master, msg); + } + +tasklet_out: +atmel_spi_unlock (as); +} + +static int atmel_spi_interrupt_dma (struct atmel_spi *as, +struct spi_master *master) +{ +u32 status, pending, imr; +struct spi_transfer *xfer; +int ret =IRQ_NONE ; + + imr =spi_readl (as,IMR ); + status =spi_readl (as,SR ); + pending = status & imr; + +if (pending &SPI_BIT (OVRES )) { + ret =IRQ_HANDLED ; +spi_writel (as,IDR ,SPI_BIT (OVRES )); +dev_warn (master->dev.parent,"overrun\n" ); + +/* +* When we get an overrun, we disregard the current +* transfer. Data will not be copied back from any +* bounce buffer and msg->actual_len will not be +* updated with the last xfer. +* +* We will also not process any remaning transfers in +* the message. +* +* All actions are done in tasklet with done_status indication +*/ + as->done_status = -EIO ; +smp_wmb (); + +/* Clear any overrun happening while cleaning up */ +spi_readl (as,SR ); + +tasklet_schedule (&as->tasklet); + + }else if (pending &SPI_BIT (RDRF )) { +atmel_spi_lock (as); + +if (as->current_remaining_bytes) { + ret =IRQ_HANDLED ; + xfer = as->current_transfer; +atmel_spi_pump_pio_data (as, xfer); +if (!as->current_remaining_bytes) { +/* no more data to xfer, kick tasklet */ +spi_writel (as,IDR , pending); +tasklet_schedule (&as->tasklet); + } + } + +atmel_spi_unlock (as); + }else { +WARN_ONCE (pending,"IRQ not handled, pending = %x\n" , pending); + ret =IRQ_HANDLED ; +spi_writel (as,IDR , pending); + } + +return ret; +} + +static int atmel_spi_interrupt_pdc (struct atmel_spi *as, +struct spi_master *master) +{ +struct spi_message *msg; +struct spi_transfer *xfer;u32 status, pending, imr;int ret =IRQ_NONE ;
@@ -703,6 +1149,27 @@ atmel_spi_interrupt(int irq, void *dev_id)return ret; } +/* Interrupt +* +* No need for locking in this Interrupt handler: done_status is the +* only information modified. What we need is the update of this field +* before tasklet runs. This is ensured by using barrier. +*/ +static irqreturn_t +atmel_spi_interrupt (int irq,void *dev_id) +{ +struct spi_master *master = dev_id; +struct atmel_spi *as =spi_master_get_devdata (master); +int ret; + +if (as->use_pdc) + ret =atmel_spi_interrupt_pdc (as, master); +else + ret =atmel_spi_interrupt_dma (as, master); + +return ret; +} +static int atmel_spi_setup (struct spi_device *spi) {struct atmel_spi *as;
@@ -861,13 +1328,11 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)/* * DMA map early, for performance (empties dcache ASAP) and -* better fault reporting. This is a DMA-only driver. -* -* NOTE that if dma_unmap_single() ever starts to do work on -* platforms supported by this driver, we would need to clean -* up mappings for previously-mapped transfers. +* better fault reporting. */ -if (!msg->is_dma_mapped) { +if (!msg->is_dma_mapped + && (atmel_spi_use_dma (as, xfer) + ||atmel_spi_use_pdc (as))) {if (atmel_spi_dma_map_xfer (as, xfer) <0 )return -ENOMEM ; }
@@ -968,6 +1433,8 @@ static int __devinit atmel_spi_probe(struct platform_device *pdev)spin_lock_init (&as->lock);INIT_LIST_HEAD (&as->queue); +tasklet_init (&as->tasklet, atmel_spi_tasklet_func, + (unsigned long )master); as->pdev = pdev; as->regs =ioremap (regs->start,resource_size (regs));if (!as->regs)
@@ -986,7 +1453,16 @@ static int __devinit atmel_spi_probe(struct platform_device *pdev)spi_writel (as,CR ,SPI_BIT (SWRST ));spi_writel (as,CR ,SPI_BIT (SWRST ));/* AT91SAM9263 Rev B workaround */ spi_writel (as,MR ,SPI_BIT (MSTR ) |SPI_BIT (MODFDIS )); -spi_writel (as,PTCR ,SPI_BIT (RXTDIS ) |SPI_BIT (TXTDIS )); + + as->use_dma = false; + as->use_pdc = false; + +if (as->data.dma_type ==2 ) { +if (atmel_spi_configure_dma (as) ==0 ) + as->use_dma = true; + }else if (as->data.dma_type ==1 ) + as->use_pdc = true; +spi_writel (as,CR ,SPI_BIT (SPIEN ));/* go! */
@@ -995,11 +1471,14 @@ static int __devinit atmel_spi_probe(struct platform_device *pdev) ret =spi_register_master (master);if (ret) - goto out_reset_hw; + goto out_free_dma;return 0 ; -out_reset_hw: +out_free_dma: +if (as->use_dma) +atmel_spi_release_dma (as); +spi_writel (as,CR ,SPI_BIT (SWRST ));spi_writel (as,CR ,SPI_BIT (SWRST ));/* AT91SAM9263 Rev B workaround */ clk_disable (clk);
@@ -1007,6 +1486,7 @@ out_reset_hw: out_unmap_regs:iounmap (as->regs); out_free_buffer: +tasklet_kill (&as->tasklet);dma_free_coherent (&pdev->dev,BUFFER_SIZE , as->buffer, as->buffer_dma); out_free:
@@ -1025,6 +1505,11 @@ static int __devexit atmel_spi_remove(struct platform_device *pdev)/* reset the hardware and block queue progress */ spin_lock_irq (&as->lock); as->stopping =1 ; +if (as->use_dma) { +atmel_spi_stop_dma (as); +atmel_spi_release_dma (as); + } +spi_writel (as,CR ,SPI_BIT (SWRST ));spi_writel (as,CR ,SPI_BIT (SWRST ));/* AT91SAM9263 Rev B workaround */ spi_readl (as,SR );
@@ -1033,13 +1518,16 @@ static int __devexit atmel_spi_remove(struct platform_device *pdev)/* Terminate remaining queued transfers */ list_for_each_entry (msg, &as->queue, queue) {list_for_each_entry (xfer, &msg->transfers, transfer_list) { -if (!msg->is_dma_mapped) +if (!msg->is_dma_mapped + && (atmel_spi_use_dma (as, xfer) + ||atmel_spi_use_pdc (as)))atmel_spi_dma_unmap_xfer (master, xfer); } msg->status = -ESHUTDOWN ; msg->complete (msg->context); } +tasklet_kill (&as->tasklet);dma_free_coherent (&pdev->dev,BUFFER_SIZE , as->buffer, as->buffer_dma);
Signature · 1 line
1.7.9.5
PATCH 8/17 spi/atmel_spi: Fix spi-atmel driver to adapt to slave_config changes
In reply to Wenyou YangFrom: Richard Genoud <[email protected]>
This is the following of the patch e2b35f3dbfc080f15b72834d08f04f0269dbe9be
--- drivers/spi/spi-atmel.c | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-)
drivers/spi/spi-atmel.c +35 -9
@@ -409,14 +409,21 @@ static bool filter(struct dma_chan *chan, void *slave)static int __devinit atmel_spi_configure_dma (struct atmel_spi *as) {struct at_dma_slave *sdata = (struct at_dma_slave *)&as->data.dma_slave; +struct dma_slave_config slave_config; +int err; + +memset (&slave_config,0 ,sizeof (slave_config)); + slave_config.dst_addr = (dma_addr_t )as->phybase +SPI_TDR ; + slave_config.src_addr = (dma_addr_t )as->phybase +SPI_RDR ; + slave_config.dst_addr_width =DMA_SLAVE_BUSWIDTH_1_BYTE ; + slave_config.src_addr_width =DMA_SLAVE_BUSWIDTH_1_BYTE ; + slave_config.src_maxburst =1 ; + slave_config.dst_maxburst =1 ; + slave_config.device_fc = false;if (sdata && sdata->dma_dev) {dma_cap_mask_t mask; -/* setup DMA addresses */ - sdata->rx_reg = (dma_addr_t )as->phybase +SPI_RDR ; - sdata->tx_reg = (dma_addr_t )as->phybase +SPI_TDR ; -/* Try to grab two DMA channels */ dma_cap_zero (mask);dma_cap_set (DMA_SLAVE , mask);
@@ -426,13 +433,26 @@ static int __devinit atmel_spi_configure_dma(struct atmel_spi *as)dma_request_channel (mask, filter, sdata); }if (!as->dma.chan_rx || !as->dma.chan_tx) { -if (as->dma.chan_rx) -dma_release_channel (as->dma.chan_rx); -if (as->dma.chan_tx) -dma_release_channel (as->dma.chan_tx);dev_err (&as->pdev->dev,"DMA channel not available, " \"unable to use SPI\n" ); -return -EBUSY ; + err = -EBUSY ; + goto error; + } + + slave_config.direction =DMA_TO_DEVICE ; +if (dmaengine_slave_config (as->dma.chan_tx, &slave_config)) { +dev_err (&as->pdev->dev, +"failed to configure tx dma channel\n" ); + err = -EINVAL ; + goto error; + } + + slave_config.direction =DMA_FROM_DEVICE ; +if (dmaengine_slave_config (as->dma.chan_rx, &slave_config)) { +dev_err (&as->pdev->dev, +"failed to configure rx dma channel\n" ); + err = -EINVAL ; + goto error; } dev_info(&as->pdev->dev, "Using %s (tx) and " \
@@ -441,6 +461,12 @@ static int __devinit atmel_spi_configure_dma(struct atmel_spi *as) dma_chan_name(as->dma.chan_rx));return 0 ; +error: +if (as->dma.chan_rx) +dma_release_channel (as->dma.chan_rx); +if (as->dma.chan_tx) +dma_release_channel (as->dma.chan_tx); +return err; }static void atmel_spi_stop_dma (struct atmel_spi *as)
Signature · 1 line
1.7.9.5
PATCH 9/17 spi/atmel_spi: correct 16 bits transfers using PIO
In reply to Wenyou YangFrom: Richard Genoud <[email protected]>
--- drivers/spi/spi-atmel.c | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-)
drivers/spi/spi-atmel.c +37 -9
@@ -518,13 +518,17 @@ static void atmel_spi_next_xfer_pio(struct spi_master *master, }if (xfer->tx_buf) -spi_writel (as,TDR , *(u8 *)(xfer->tx_buf)); +if (xfer->bits_per_word >8 ) +spi_writel (as,TDR , *(u16 *)(xfer->tx_buf)); +else +spi_writel (as,TDR , *(u8 *)(xfer->tx_buf));else spi_writel (as,TDR ,0 );dev_dbg (master->dev.parent, -" start pio xfer %p: len %u tx %p rx %p\n" , - xfer, xfer->len, xfer->tx_buf, xfer->rx_buf); +" start pio xfer %p: len %u tx %p rx %p bitpw %d\n" , + xfer, xfer->len, xfer->tx_buf, xfer->rx_buf, + xfer->bits_per_word);/* Enable relevant interrupts */ spi_writel (as,IER ,SPI_BIT (RDRF ) |SPI_BIT (OVRES ));
@@ -918,21 +922,39 @@ atmel_spi_pump_pio_data(struct atmel_spi *as, struct spi_transfer *xfer) {u8 *txp;u8 *rxp; +u16 *txp16; +u16 *rxp16;unsigned long xfer_pos = xfer->len - as->current_remaining_bytes;if (xfer->rx_buf) { - rxp = ((u8 *)xfer->rx_buf) + xfer_pos; - *rxp =spi_readl (as,RDR ); +if (xfer->bits_per_word >8 ) { + rxp16 = (u16 *)(((u8 *)xfer->rx_buf) + xfer_pos); + *rxp16 =spi_readl (as,RDR ); + }else { + rxp = ((u8 *)xfer->rx_buf) + xfer_pos; + *rxp =spi_readl (as,RDR ); + } }else {spi_readl (as,RDR ); } - - as->current_remaining_bytes--; +if (xfer->bits_per_word >8 ) { + as->current_remaining_bytes -=2 ; +if (as->current_remaining_bytes <0 ) + as->current_remaining_bytes =0 ; + }else { + as->current_remaining_bytes--; + }if (as->current_remaining_bytes) {if (xfer->tx_buf) { - txp = ((u8 *)xfer->tx_buf) + xfer_pos +1 ; -spi_writel (as,TDR , *txp); +if (xfer->bits_per_word >8 ) { + txp16 = (u16 *)(((u8 *)xfer->tx_buf) + + xfer_pos +2 ); +spi_writel (as,TDR , *txp16); + }else { + txp = ((u8 *)xfer->tx_buf) + xfer_pos +1 ; +spi_writel (as,TDR , *txp); + } }else {spi_writel (as,TDR ,0 ); }
@@ -1345,6 +1367,12 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)return -ENOPROTOOPT ; } } +if (xfer->bits_per_word >8 ) { +if (xfer->len %2 ) { +dev_dbg (&spi->dev,"buffer len should be 16 bits aligned\n" ); +return -EINVAL ; + } + }/* FIXME implement these protocol options!! */ if (xfer->speed_hz) {
Signature · 1 line
1.7.9.5
PATCH 10/17 spi/atmel_spi: correct 16 bits transfer with DMA
In reply to Wenyou YangFrom: Richard Genoud <[email protected]>
--- drivers/spi/spi-atmel.c | 53 +++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 18 deletions(-)
drivers/spi/spi-atmel.c +35 -18
@@ -249,6 +249,8 @@ struct atmel_spi_device {#define BUFFER_SIZE PAGE_SIZE#define INVALID_DMA_ADDRESS 0xffffffff +static struct dma_slave_config slave_config; +/* * Version 2 of the SPI controller has * - CR.LASTXFER
@@ -406,17 +408,41 @@ static bool filter(struct dma_chan *chan, void *slave) } } +static int atmel_spi_set_dma_xfer_width (struct atmel_spi *as,u8 bits_per_word) +{ +int err =0 ; + +if (bits_per_word >8 ) { + slave_config.dst_addr_width =DMA_SLAVE_BUSWIDTH_2_BYTES ; + slave_config.src_addr_width =DMA_SLAVE_BUSWIDTH_2_BYTES ; + }else { + slave_config.dst_addr_width =DMA_SLAVE_BUSWIDTH_1_BYTE ; + slave_config.src_addr_width =DMA_SLAVE_BUSWIDTH_1_BYTE ; + } + + slave_config.direction =DMA_TO_DEVICE ; +if (dmaengine_slave_config (as->dma.chan_tx, &slave_config)) { +dev_err (&as->pdev->dev, +"failed to configure tx dma channel\n" ); + err = -EINVAL ; + } + + slave_config.direction =DMA_FROM_DEVICE ; +if (dmaengine_slave_config (as->dma.chan_rx, &slave_config)) { +dev_err (&as->pdev->dev, +"failed to configure rx dma channel\n" ); + err = -EINVAL ; + } +return err; +} +static int __devinit atmel_spi_configure_dma (struct atmel_spi *as) {struct at_dma_slave *sdata = (struct at_dma_slave *)&as->data.dma_slave; -struct dma_slave_config slave_config;int err; -memset (&slave_config,0 ,sizeof (slave_config)); slave_config.dst_addr = (dma_addr_t )as->phybase +SPI_TDR ; slave_config.src_addr = (dma_addr_t )as->phybase +SPI_RDR ; - slave_config.dst_addr_width =DMA_SLAVE_BUSWIDTH_1_BYTE ; - slave_config.src_addr_width =DMA_SLAVE_BUSWIDTH_1_BYTE ; slave_config.src_maxburst =1 ; slave_config.dst_maxburst =1 ; slave_config.device_fc = false;
@@ -439,21 +465,9 @@ static int __devinit atmel_spi_configure_dma(struct atmel_spi *as) goto error; } - slave_config.direction =DMA_TO_DEVICE ; -if (dmaengine_slave_config (as->dma.chan_tx, &slave_config)) { -dev_err (&as->pdev->dev, -"failed to configure tx dma channel\n" ); - err = -EINVAL ; + err =atmel_spi_set_dma_xfer_width (as,8 ); +if (err) goto error; - } - - slave_config.direction =DMA_FROM_DEVICE ; -if (dmaengine_slave_config (as->dma.chan_rx, &slave_config)) { -dev_err (&as->pdev->dev, -"failed to configure rx dma channel\n" ); - err = -EINVAL ; - goto error; - } dev_info(&as->pdev->dev, "Using %s (tx) and " \ " %s (rx) for DMA transfers\n",
@@ -575,6 +589,9 @@ static int atmel_spi_next_xfer_dma(struct spi_master *master,memset (as->buffer,0 , xfer->len); } +if (atmel_spi_set_dma_xfer_width (as, xfer->bits_per_word)) + goto err_dma; +/* Send both scatterlists */ rxdesc = rxchan->device->device_prep_slave_sg(rxchan, &as->dma.sgrx,
Signature · 1 line
1.7.9.5
PATCH 11/17 spi/atmel_spi: add DT support
In reply to Wenyou YangFrom: Jean-Christophe PLAGNIOL-VILLARD <[email protected]>
The atmel_spi use only gpio for chip select.
---
Hi, Richard,
This patches is based on the original patch from Jean-Christophe
[PATCH] spi/atmel: add DT support
and merged the patch from Richard Genoud
[PATCH] spi-atmel OF: complete documentation
Could you sign your signature in this patch?
Best Regards,
Wenyou Yang
.../devicetree/bindings/spi/spi_atmel.txt | 23 ++++++++++++++++++++ drivers/spi/spi-atmel.c | 21 ++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 Documentation/devicetree/bindings/spi/spi_atmel.txt
Documentation/devicetree/bindings/spi/spi_atmel.txt new file +23 -0
@@ -0,0 +1,23 @@ +Atmel SPI device + +Required properties: +- compatible : should be "atmel,at91rm9200-spi". +- reg: Address and length of the register set for the device +- interrupts: Should contain macb interrupt +- cs-gpio: Should contain the GPIOs used for chipselect. +- dma-mask: device coherent dma mask. + +spi0: spi at f0000000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xf0000000 0x100>; + interrupts = <13 4>; + cs-gpios = <&pioA 14 0 + &pioA 7 0 /* conflicts with TXD2 */ + &pioA 1 0 /* conflicts with RXD0 */ + &pioB 3 0 /* conflicts with ERXDV */ + >; + dma-mask = <0xffffffff>; + status = "disabled"; +};
drivers/spi/spi-atmel.c +17 -4
@@ -20,6 +20,7 @@#include <linux/interrupt.h> #include <linux/spi/spi.h> #include <linux/slab.h> +#include <linux/of.h> #include <asm/io.h> #include <mach/board.h>
@@ -1242,7 +1243,7 @@ static int atmel_spi_setup(struct spi_device *spi)u32 scbr, csr;unsigned int bits = spi->bits_per_word;unsigned long bus_hz; -unsigned int npcs_pin; +int npcs_pin;int ret; as =spi_master_get_devdata (spi->master);
@@ -1314,7 +1315,9 @@ static int atmel_spi_setup(struct spi_device *spi) csr |=SPI_BF (DLYBCT ,0 );/* chipselect must have been muxed as GPIO (e.g. in board setup) */ - npcs_pin = (unsigned int )spi->controller_data; +if (!gpio_is_valid (spi->cs_gpio)) + spi->cs_gpio = (int )spi->controller_data; + npcs_pin = spi->cs_gpio; asd = spi->controller_state;if (!asd) { asd =kzalloc (sizeof (struct atmel_spi_device ),GFP_KERNEL );
@@ -1435,7 +1438,7 @@ static void atmel_spi_cleanup(struct spi_device *spi) {struct atmel_spi *as =spi_master_get_devdata (spi->master);struct atmel_spi_device *asd = spi->controller_state; -unsigned gpio = (unsigned ) spi->controller_data; +unsigned gpio = spi->cs_gpio;if (!asd)return ;
@@ -1485,7 +1488,8 @@ static int __devinit atmel_spi_probe(struct platform_device *pdev) master->mode_bits =SPI_CPOL |SPI_CPHA |SPI_CS_HIGH ; master->bus_num = pdev->id; - master->num_chipselect =4 ; + master->dev.of_node = pdev->dev.of_node; + master->num_chipselect = master->dev.of_node ?0 :4 ; master->setup = atmel_spi_setup; master->transfer = atmel_spi_transfer; master->cleanup = atmel_spi_cleanup;
@@ -1637,11 +1641,20 @@ static int atmel_spi_resume(struct platform_device *pdev)#define atmel_spi_resume NULL#endif +#if defined(CONFIG_OF ) +static const struct of_device_id atmel_spi_dt_ids[] = { + { .compatible ="atmel,at91rm9200-spi" }, + {/* sentinel */ } +}; + +MODULE_DEVICE_TABLE (of, atmel_spi_dt_ids); +#endif static struct platform_driver atmel_spi_driver = { .driver = { .name ="atmel_spi" , .owner =THIS_MODULE , + .of_match_table =of_match_ptr (atmel_spi_dt_ids), }, .suspend = atmel_spi_suspend, .resume = atmel_spi_resume,
Signature · 1 line
1.7.9.5
PATCH 12/17 spi/atmel_spi: add version propety as the spi data
In reply to Wenyou YangTo remove the function atmel_spi_is_v2() which depends on the SOC, for future using DTS section to decide the IP version of SOC.
--- drivers/spi/spi-atmel.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-)
drivers/spi/spi-atmel.c +19 -7
@@ -187,6 +187,7 @@*/ struct atmel_spi_data {u8 dma_type; +u8 version;struct at_dma_slave dma_slave; };
@@ -264,9 +265,20 @@ static struct dma_slave_config slave_config;* register, but I haven't checked that it exists on all chips, and * this is cheaper anyway. */ -static bool atmel_spi_is_v2 (void ) +static bool atmel_spi_is_v1 (struct atmel_spi *as) { -return !cpu_is_at91rm9200 (); +if (as->data.version ==1 ) +return true; +else +return false; +} + +static bool atmel_spi_is_v2 (struct atmel_spi *as) +{ +if (as->data.version ==2 ) +return true; +else +return false; }/*
@@ -300,7 +312,7 @@ static void cs_activate(struct atmel_spi *as, struct spi_device *spi)unsigned active = spi->mode &SPI_CS_HIGH ;u32 mr; -if (atmel_spi_is_v2 ()) { +if (atmel_spi_is_v2 (as)) {/* * Always use CSR0. This ensures that the clock * switches to the correct idle polarity before we
@@ -355,7 +367,7 @@ static void cs_deactivate(struct atmel_spi *as, struct spi_device *spi) asd->npcs_pin, active ?" (low)" :"" , mr); -if (atmel_spi_is_v2 () || spi->chip_select !=0 ) +if (atmel_spi_is_v2 (as) || spi->chip_select !=0 )gpio_set_value (asd->npcs_pin, !active); }
@@ -1266,7 +1278,7 @@ static int atmel_spi_setup(struct spi_device *spi) }/* see notes above re chipselect */ -if (!atmel_spi_is_v2 () +if (atmel_spi_is_v1 (as) && spi->chip_select ==0 && (spi->mode &SPI_CS_HIGH )) {dev_dbg (&spi->dev,"setup: can't be active-high\n" );
@@ -1275,7 +1287,7 @@ static int atmel_spi_setup(struct spi_device *spi)/* v1 chips start out at half the peripheral bus speed. */ bus_hz =clk_get_rate (as->clk); -if (!atmel_spi_is_v2 ()) +if (atmel_spi_is_v1 (as)) bus_hz /=2 ;if (spi->max_speed_hz) {
@@ -1347,7 +1359,7 @@ static int atmel_spi_setup(struct spi_device *spi)"setup: %lu Hz bpw %u mode 0x%x -> csr%d %08x\n" , bus_hz / scbr, bits, spi->mode, spi->chip_select, csr); -if (!atmel_spi_is_v2 ()) +if (atmel_spi_is_v1 (as))spi_writel (as,CSR0 +4 * spi->chip_select, csr);return 0 ;
Signature · 1 line
1.7.9.5
PATCH 13/17 spi/atmel_spi: add function to read the spi data from the dts
In reply to Wenyou YangThe spi data include: dma_type and version.dma_type to decide the SPI xfer mode: = 1(pdc), = 2(dmaengine), 0(no dma, using PIO) version to give the SPI ip version.
--- .../devicetree/bindings/spi/spi_atmel.txt | 4 +++ drivers/spi/spi-atmel.c | 28 ++++++++++++++++++++ 2 files changed, 32 insertions(+)
Documentation/devicetree/bindings/spi/spi_atmel.txt +4 -0
@@ -6,6 +6,8 @@ Required properties: - interrupts: Should contain macb interrupt - cs-gpio: Should contain the GPIOs used for chipselect. - dma-mask: device coherent dma mask. +- dma_type: The dma type supported by the spi of SoC: = 0 (no used), = 1 (pdc), = 2 (dma) +- version: The version of the spi IP. spi0: spi at f0000000 { #address-cells = <1>;
@@ -19,5 +21,7 @@ spi0: spi at f0000000 { &pioB 3 0 /* conflicts with ERXDV */ >; dma-mask = <0xffffffff>; + dma_type = <1>; + version = <2>; status = "disabled"; };
drivers/spi/spi-atmel.c +28 -0
@@ -1467,6 +1467,30 @@ static void atmel_spi_cleanup(struct spi_device *spi)kfree (asd); } +static int of_get_atmel_spi_data (struct device_node *np,struct atmel_spi *as) +{ +const __be32 *val; + + val =of_get_property (np,"dma_type" ,NULL ); +if (!val) { +pr_err ("%s: have no 'dma_type' property\n" , + np->full_name); +return -EINVAL ; + } + + as->data.dma_type =be32_to_cpup (val); + + val =of_get_property (np,"version" ,NULL ); +if (!val) { +pr_err ("%s: have no 'version' property\n" , np->full_name); +return -EINVAL ; + } + + as->data.version =be32_to_cpup (val); + +return 0 ; +} +/*-------------------------------------------------------------------------*/ static int __devinit atmel_spi_probe (struct platform_device *pdev)
@@ -1535,6 +1559,10 @@ static int __devinit atmel_spi_probe(struct platform_device *pdev)if (ret) goto out_unmap_regs; + ret =of_get_atmel_spi_data (pdev->dev.of_node, as); +if (ret) + goto out_unmap_regs; +/* Initialize the hardware */ clk_enable (clk);spi_writel (as,CR ,SPI_BIT (SWRST ));
Signature · 1 line
1.7.9.5
PATCH 14/17 ARM: at91: add clocks for spi DT entries
In reply to Wenyou YangFrom: Richard Genoud <[email protected]>
---
Hi, Richard,
This patch is based on the original patch from Richard Genoud,
[PATCH] sam9x5: declare SPI clocks
and referring to this patch, Wenyou Yang declare the spi clocks for
other at91 SoC.
Is it ok for you?
Best Regards
Wenyou Yang
arch/arm/mach-at91/at91sam9260.c | 2 ++ arch/arm/mach-at91/at91sam9g45.c | 2 ++ arch/arm/mach-at91/at91sam9n12.c | 2 ++ arch/arm/mach-at91/at91sam9x5.c | 2 ++ 4 files changed, 8 insertions(+)
arch/arm/mach-at91/at91sam9260.c +2 -0
@@ -230,6 +230,8 @@ static struct clk_lookup periph_clocks_lookups[] = {CLKDEV_CON_DEV_ID ("t1_clk" ,"fffdc000.timer" , &tc4_clk),CLKDEV_CON_DEV_ID ("t2_clk" ,"fffdc000.timer" , &tc5_clk),CLKDEV_CON_DEV_ID ("hclk" ,"500000.ohci" , &ohci_clk), +CLKDEV_CON_DEV_ID ("spi_clk" ,"fffc8000.spi" , &spi0_clk), +CLKDEV_CON_DEV_ID ("spi_clk" ,"fffcc000.spi" , &spi1_clk),/* fake hclk clock */ CLKDEV_CON_DEV_ID ("hclk" ,"at91_ohci" , &ohci_clk), CLKDEV_CON_ID("pioA", &pioA_clk),
arch/arm/mach-at91/at91sam9g45.c +2 -0
@@ -256,6 +256,8 @@ static struct clk_lookup periph_clocks_lookups[] = {CLKDEV_CON_DEV_ID ("t0_clk" ,"fffd4000.timer" , &tcb0_clk),CLKDEV_CON_DEV_ID ("hclk" ,"700000.ohci" , &uhphs_clk),CLKDEV_CON_DEV_ID ("ehci_clk" ,"800000.ehci" , &uhphs_clk), +CLKDEV_CON_DEV_ID ("spi_clk" ,"fffa4000.spi" , &spi0_clk), +CLKDEV_CON_DEV_ID ("spi_clk" ,"fffa8000.spi" , &spi1_clk),CLKDEV_CON_DEV_ID (NULL ,"fff84000.i2c" , &twi0_clk), CLKDEV_CON_DEV_ID(NULL, "fff88000.i2c", &twi1_clk),/* fake hclk clock */
arch/arm/mach-at91/at91sam9n12.c +2 -0
@@ -169,6 +169,8 @@ static struct clk_lookup periph_clocks_lookups[] = {CLKDEV_CON_DEV_ID ("t0_clk" ,"f8008000.timer" , &tcb_clk),CLKDEV_CON_DEV_ID ("t0_clk" ,"f800c000.timer" , &tcb_clk),CLKDEV_CON_DEV_ID ("dma_clk" ,"ffffec00.dma-controller" , &dma_clk), +CLKDEV_CON_DEV_ID ("spi_clk" ,"f0000000.spi" , &spi0_clk), +CLKDEV_CON_DEV_ID ("spi_clk" ,"f0004000.spi" , &spi1_clk),CLKDEV_CON_DEV_ID (NULL ,"f8010000.i2c" , &twi0_clk),CLKDEV_CON_DEV_ID (NULL ,"f8014000.i2c" , &twi1_clk), CLKDEV_CON_ID("pioA", &pioAB_clk),
arch/arm/mach-at91/at91sam9x5.c +2 -0
@@ -231,6 +231,8 @@ static struct clk_lookup periph_clocks_lookups[] = {CLKDEV_CON_DEV_ID ("t0_clk" ,"f800c000.timer" , &tcb0_clk),CLKDEV_CON_DEV_ID ("dma_clk" ,"ffffec00.dma-controller" , &dma0_clk),CLKDEV_CON_DEV_ID ("dma_clk" ,"ffffee00.dma-controller" , &dma1_clk), +CLKDEV_CON_DEV_ID ("spi_clk" ,"f0000000.spi" , &spi0_clk), +CLKDEV_CON_DEV_ID ("spi_clk" ,"f0004000.spi" , &spi1_clk),CLKDEV_CON_DEV_ID (NULL ,"f8010000.i2c" , &twi0_clk),CLKDEV_CON_DEV_ID (NULL ,"f8014000.i2c" , &twi1_clk), CLKDEV_CON_DEV_ID(NULL, "f8018000.i2c", &twi2_clk),
Signature · 1 line
1.7.9.5
PATCH 15/17 ARM: dts: add spi nodes for atmel SoC
In reply to Wenyou YangFrom: Richard Genoud <[email protected]>
---
Hi, Richard,
This patches is based on the original patch from Richard Genoud
[PATCH] spi-atmel: add sam9x5 SPI in device tree
and referring to this patch, Wenyou Yang add spi nodes for
other atmel SoC.
Is it OK for you?
Best Regards,
Wenyou Yang
arch/arm/boot/dts/at91sam9260.dtsi | 34 ++++++++++++++++++++++++++++++++++ arch/arm/boot/dts/at91sam9263.dtsi | 34 ++++++++++++++++++++++++++++++++++ arch/arm/boot/dts/at91sam9g45.dtsi | 34 ++++++++++++++++++++++++++++++++++ arch/arm/boot/dts/at91sam9n12.dtsi | 34 ++++++++++++++++++++++++++++++++++ arch/arm/boot/dts/at91sam9x5.dtsi | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 170 insertions(+)
arch/arm/boot/dts/at91sam9260.dtsi +34 -0
@@ -246,6 +246,40 @@ trigger-external; }; }; + + spi0: spi at fffc8000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xfffc8000 0x200>; + interrupts = <12 4 3>; + cs-gpios = <&pioA 3 0 + &pioC 11 0 + &pioC 16 0 + &pioC 17 0 + >; + dma-mask = <0xffffffff>; + dma_type = <1>; + version = <2>; + status = "disabled"; + }; + + spi1: spi at fffcc000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xfffcc000 0x200>; + interrupts = <13 4 3>; + cs-gpios = <&pioB 3 0 + &pioC 5 0 + &pioC 4 0 + &pioC 3 0 + >; + dma-mask = <0xffffffff>; + dma_type = <1>; + version = <2>; + status = "disabled"; + }; }; nand0: nand at 40000000 {
arch/arm/boot/dts/at91sam9263.dtsi +34 -0
@@ -195,6 +195,40 @@ #size-cells = <0>; status = "disabled"; }; + + spi0: spi at fffa4000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xfffa4000 0x200>; + interrupts = <14 4 3>; + cs-gpios = <&pioA 5 0 + &pioA 3 0 + &pioA 4 0 + &pioB 11 0 + >; + dma-mask = <0xffffffff>; + dma_type = <1>; + version = <2>; + status = "disabled"; + }; + + spi1: spi at fffa8000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xfffa8000 0x200>; + interrupts = <15 4 3>; + cs-gpios = <&pioB 15 0 + &pioB 16 0 + &pioB 17 0 + &pioB 18 0 + >; + dma-mask = <0xffffffff>; + dma_type = <1>; + version = <2>; + status = "disabled"; + }; }; nand0: nand at 40000000 {
arch/arm/boot/dts/at91sam9g45.dtsi +34 -0
@@ -262,6 +262,40 @@ trigger-value = <0x6>; }; }; + + spi0: spi at fffa4000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xfffa4000 0x200>; + interrupts = <14 4 3>; + cs-gpios = <&pioB 3 0 + &pioB 18 0 + &pioB 19 0 + &pioD 27 0 + >; + dma-mask = <0xffffffff>; + dma_type = <1>; + version = <2>; + status = "disabled"; + }; + + spi1: spi at fffa8000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xfffa8000 0x200>; + interrupts = <15 4 3>; + cs-gpios = <&pioB 17 0 + &pioD 28 0 + &pioD 18 0 + &pioD 19 0 + >; + dma-mask = <0xffffffff>; + dma_type = <1>; + version = <2>; + status = "disabled"; + }; }; nand0: nand at 40000000 {
arch/arm/boot/dts/at91sam9n12.dtsi +34 -0
@@ -202,6 +202,40 @@ #size-cells = <0>; status = "disabled"; }; + + spi0: spi at f0000000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xf0000000 0x100>; + interrupts = <13 4 3>; + cs-gpios = <&pioA 14 0 + &pioA 7 0 + &pioA 1 0 + &pioB 3 0 + >; + dma-mask = <0xffffffff>; + dma_type = <2>; + version = <2>; + status = "disabled"; + }; + + spi1: spi at f0004000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xf0004000 0x100>; + interrupts = <14 4 3>; + cs-gpios = <&pioA 8 0 + &pioA 0 0 + &pioA 31 0 + &pioA 30 0 + >; + dma-mask = <0xffffffff>; + dma_type = <2>; + version = <2>; + status = "disabled"; + }; }; nand0: nand at 40000000 {
arch/arm/boot/dts/at91sam9x5.dtsi +34 -0
@@ -263,6 +263,40 @@ trigger-value = <0x6>; }; }; + + spi0: spi at f0000000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xf0000000 0x100>; + interrupts = <13 4 3>; + cs-gpios = <&pioA 14 0 + &pioA 7 0 /* conflicts with TXD2 */ + &pioA 1 0 /* conflicts with RXD0 */ + &pioB 3 0 /* conflicts with ERXDV */ + >; + dma-mask = <0xffffffff>; + dma_type = <2>; + version = <2>; + status = "disabled"; + }; + + spi1: spi at f0004000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "atmel,at91rm9200-spi"; + reg = <0xf0004000 0x100>; + interrupts = <14 4 3>; + cs-gpios = <&pioA 8 0 /* conflitcs with RXD2 */ + &pioA 0 0 /* conflitcs with TXD0 */ + &pioA 31 0 /* conflitcs with TWCK0 */ + &pioA 30 0 /* conflitcs with TWD0 */ + >; + dma-mask = <0xffffffff>; + dma_type = <2>; + version = <2>; + status = "disabled"; + }; }; nand0: nand at 40000000 {
Signature · 1 line
1.7.9.5
PATCH 16/17 ARM: dts: add spi nodes for atmel boards
In reply to Wenyou YangFrom: Richard Genoud <[email protected]>
---
Hi, Richard,
This patch is based on the orignal patch from Richard Genoud
[PATCH] sam9x5ek DTS: enable SPI dataflash
and referring to this patch Wenyou Yang add spi nodes for other atmel boards.
and remove the cs-gpios property for sam9x5ek boards.
Is it ok for you?
Best Regards,
Wenyou Yang
arch/arm/boot/dts/at91sam9263ek.dts | 9 +++++++++ arch/arm/boot/dts/at91sam9g20ek_common.dtsi | 9 +++++++++ arch/arm/boot/dts/at91sam9g25ek.dts | 9 +++++++++ arch/arm/boot/dts/at91sam9m10g45ek.dts | 9 +++++++++ arch/arm/boot/dts/at91sam9n12ek.dts | 9 +++++++++ 5 files changed, 45 insertions(+)
arch/arm/boot/dts/at91sam9263ek.dts +9 -0
@@ -50,6 +50,15 @@ atmel,vbus-gpio = <&pioA 25 0>; status = "okay"; }; + + spi0: spi at fffa4000 { + status = "okay"; + mtd_dataflash at 0 { + compatible = "atmel,at45", "atmel,dataflash"; + spi-max-frequency = <50000000>; + reg = <0>; + }; + }; }; nand0: nand at 40000000 {
arch/arm/boot/dts/at91sam9g20ek_common.dtsi +9 -0
@@ -51,6 +51,15 @@ atmel,vbus-gpio = <&pioC 5 0>; status = "okay"; }; + + spi0: spi at fffc8000 { + status = "okay"; + mtd_dataflash at 0 { + compatible = "atmel,at45", "atmel,dataflash"; + spi-max-frequency = <50000000>; + reg = <1>; + }; + }; }; nand0: nand at 40000000 {
arch/arm/boot/dts/at91sam9g25ek.dts +9 -0
@@ -44,6 +44,15 @@ i2c2: i2c at f8018000 { status = "okay"; }; + + spi0: spi at f0000000 { + status = "okay"; + m25p80 at 0 { + compatible = "atmel,at25df321a"; + spi-max-frequency = <50000000>; + reg = <0>; + }; + }; }; usb0: ohci at 00600000 {
arch/arm/boot/dts/at91sam9m10g45ek.dts +9 -0
@@ -54,6 +54,15 @@ i2c1: i2c at fff88000 { status = "okay"; }; + + spi0: spi at fffa4000{ + status = "okay"; + mtd_dataflash at 0 { + compatible = "atmel,at45", "atmel,dataflash"; + spi-max-frequency = <13000000>; + reg = <0>; + }; + }; }; nand0: nand at 40000000 {
arch/arm/boot/dts/at91sam9n12ek.dts +9 -0
@@ -45,6 +45,15 @@ i2c1: i2c at f8014000 { status = "okay"; }; + + spi0: spi at f0000000 { + status = "okay"; + m25p80 at 0 { + compatible = "atmel,at25df321a"; + spi-max-frequency = <50000000>; + reg = <0>; + }; + }; }; nand0: nand at 40000000 {
Signature · 1 line
1.7.9.5
PATCH 17/17 mtd: m25p80: change the m25p80_read to reading page to page
In reply to Wenyou YangWhen run "flashcp /bin/busybox /dev/mtdX", it arised a OOPS. changing to fix the [BUG].
--- drivers/mtd/devices/m25p80.c | 44 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-)
drivers/mtd/devices/m25p80.c +41 -3
@@ -340,6 +340,7 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct m25p *flash = mtd_to_m25p(mtd); +u32 page_offset, page_size;struct spi_transfer t[2 ];struct spi_message m;
@@ -358,7 +359,6 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len,spi_message_add_tail (&t[0 ], &m); t[1 ].rx_buf = buf; - t[1 ].len = len;spi_message_add_tail (&t[1 ], &m);mutex_lock (&flash->lock);
@@ -379,9 +379,47 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len, flash->command[0 ] =OPCODE_READ ;m25p_addr2cmd (flash, from, flash->command); -spi_sync (flash->spi, &m); + page_offset = from & (flash->page_size -1 ); + +/* do all the bytes fit onto one page? */ +if (page_offset + len <= flash->page_size) { + t[1 ].len = len; + +spi_sync (flash->spi, &m); + + *retlen = m.actual_length -m25p_cmdsz (flash) + -FAST_READ_DUMMY_BYTE ; + + }else { +u32 i; + +/* the size of data remaining on the first page */ + page_size = flash->page_size - page_offset; + + t[1 ].len = page_size; +spi_sync (flash->spi, &m); + + *retlen = m.actual_length -m25p_cmdsz (flash) + -FAST_READ_DUMMY_BYTE ; + +/* write everything in flash->page_size chunks */ +for (i = page_size; i < len; i += page_size) { + page_size = len - i; +if (page_size > flash->page_size) + page_size = flash->page_size; + +/* write the next page to flash */ +m25p_addr2cmd (flash, from + i, flash->command); + + t[1 ].rx_buf = buf + i; + t[1 ].len = page_size; + +spi_sync (flash->spi, &m); - *retlen = m.actual_length -m25p_cmdsz (flash) -FAST_READ_DUMMY_BYTE ; + *retlen += m.actual_length -m25p_cmdsz (flash) + -FAST_READ_DUMMY_BYTE ; + } + }mutex_unlock (&flash->lock);
Signature · 1 line
1.7.9.5
PATCH 17/17 mtd: m25p80: change the m25p80_read to reading page to page
Hi Wenyou Yang,
Quoted 1 line · Wenyou Yang
When run "flashcp
/bin/busybox/dev/mtdX", it arised a OOPS. changing to fix the [BUG].
Please post the BUG printout, and include it with the commit log.
baruch
Quoted 79 lines
Signed-off-by Wenyou Yang <[email protected]>Cc dwmw2 at infradead.orgCc linux-mtd at lists.infradead.org--- drivers/mtd/devices/m25p80.c | 44 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-)drivers/mtd/devices/m25p80.c +41
-3@@ -340,6 +340,7 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct m25p *flash = mtd_to_m25p(mtd); +u32 page_offset, page_size;struct spi_transfer t[2 ];struct spi_message m;@@ -358,7 +359,6 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len,spi_message_add_tail (&t[0 ], &m); t[1 ].rx_buf = buf; - t[1 ].len = len;spi_message_add_tail (&t[1 ], &m);mutex_lock (&flash->lock);@@ -379,9 +379,47 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len, flash->command[0 ] =OPCODE_READ ;m25p_addr2cmd (flash, from, flash->command); -spi_sync (flash->spi, &m); + page_offset = from & (flash->page_size -1 ); + +/* do all the bytes fit onto one page? */ +if (page_offset + len <= flash->page_size) { + t[1 ].len = len; + +spi_sync (flash->spi, &m); + + *retlen = m.actual_length -m25p_cmdsz (flash) + -FAST_READ_DUMMY_BYTE ; + + }else { +u32 i; + +/* the size of data remaining on the first page */ + page_size = flash->page_size - page_offset; + + t[1 ].len = page_size; +spi_sync (flash->spi, &m); + + *retlen = m.actual_length -m25p_cmdsz (flash) + -FAST_READ_DUMMY_BYTE ; + +/* write everything in flash->page_size chunks */ +for (i = page_size; i < len; i += page_size) { + page_size = len - i; +if (page_size > flash->page_size) + page_size = flash->page_size; + +/* write the next page to flash */ +m25p_addr2cmd (flash, from + i, flash->command); + + t[1 ].rx_buf = buf + i; + t[1 ].len = page_size; + +spi_sync (flash->spi, &m); - *retlen = m.actual_length -m25p_cmdsz (flash) -FAST_READ_DUMMY_BYTE ; + *retlen += m.actual_length -m25p_cmdsz (flash) + -FAST_READ_DUMMY_BYTE ; + } + }mutex_unlock (&flash->lock);Signature · 1 line
1.7.9.5
Signature · 3 lines
http://baruch.siach.name/blog/ ~. .~ Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
- baruch at tkos.co.il - tel: +972.2.679.5364, http://www.tkos.co.il -PATCH 17/17 mtd: m25p80: change the m25p80_read to reading page to page
Hi Baruch,
Thanks a lot for your feedback.
I looked through the drive code carefully again, I think I should fix this bug inside my driver. I will fix in the next version.
Best Regards
Wenyou Yang
Quoted 107 lines
-----Original Message-----
From: Baruch Siach [mailto:baruch at tkos.co.il]
Sent: 2012?11?12? 17:13
To: Yang, Wenyou
Cc: linux-arm-kernel at lists.infradead.org; dwmw2 at infradead.org; Lin, JM; Ferre, Nicolas; linux-mtd at lists.infradead.org
Subject: Re: [PATCH 17/17] mtd: m25p80: change them25p80_readto reading page to pageHi Wenyou Yang,
Quoted 1 line · Wenyou Yang
When run "flashcp
/bin/busybox/dev/mtdX", it arised a OOPS. changing to fix the[BUG].
Please post the BUG printout, and include it with the commit log.
baruch
Quoted 5 lines
Signed-off-by Wenyou Yang <[email protected]>Cc dwmw2 at infradead.orgCc linux-mtd at lists.infradead.org--- drivers/mtd/devices/m25p80.c | 44+++++++++++++++++++++++++++++++++++++++---
drivers/mtd/devices/m25p80.c static int m25p80_read(struct mtd_info *mtd, loff_t from,
Quoted 7 lines
1 file changed, 41 insertions(+), 3 deletions(-)drivers/mtd/devices/m25p80.c +0
-0@@ -340,6 +340,7 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from,
size_tlen,drivers/mtd/devices/m25p80.c static int m25p80_read(struct mtd_info *mtd, loff_t from,
Quoted 8 lines
drivers/mtd/devices/m25p80.c +1
-0size_t *retlen, u_char *buf) { struct m25p *flash = mtd_to_m25p(mtd); +u32 page_offset, page_size;struct spi_transfer t[2 ];struct spi_message m;@@ -358,7 +359,6 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from,
size_tlen,drivers/mtd/devices/m25p80.c static int m25p80_read(struct mtd_info *mtd, loff_t from,
Quoted 8 lines
drivers/mtd/devices/m25p80.c +0
-1spi_message_add_tail (&t[0 ], &m); t[1 ].rx_buf = buf; - t[1 ].len = len;spi_message_add_tail (&t[1 ], &m);mutex_lock (&flash->lock);@@ -379,9 +379,47 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from,
size_tlen,Quoted 50 lines
drivers/mtd/devices/m25p80.c +40
-3flash->command[0 ] =OPCODE_READ ;m25p_addr2cmd (flash, from, flash->command); -spi_sync (flash->spi, &m); + page_offset = from & (flash->page_size -1 ); + +/* do all the bytes fit onto one page? */ +if (page_offset + len <= flash->page_size) { + t[1 ].len = len; + +spi_sync (flash->spi, &m); + + *retlen = m.actual_length -m25p_cmdsz (flash) + -FAST_READ_DUMMY_BYTE ; + + }else { +u32 i; + +/* the size of data remaining on the first page */ + page_size = flash->page_size - page_offset; + + t[1 ].len = page_size; +spi_sync (flash->spi, &m); + + *retlen = m.actual_length -m25p_cmdsz (flash) + -FAST_READ_DUMMY_BYTE ; + +/* write everything in flash->page_size chunks */ +for (i = page_size; i < len; i += page_size) { + page_size = len - i; +if (page_size > flash->page_size) + page_size = flash->page_size; + +/* write the next page to flash */ +m25p_addr2cmd (flash, from + i, flash->command); + + t[1 ].rx_buf = buf + i; + t[1 ].len = page_size; + +spi_sync (flash->spi, &m); - *retlen = m.actual_length -m25p_cmdsz (flash) -FAST_READ_DUMMY_BYTE ; + *retlen += m.actual_length -m25p_cmdsz (flash) + -FAST_READ_DUMMY_BYTE ; + } + }mutex_unlock (&flash->lock); --Quoted 1 line
1.7.9.5
--
http://baruch.siach.name/blog/ ~. .~ Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
- baruch at tkos.co.il - tel: +972.2.679.5364, http://www.tkos.co.il -