]> rtime.felk.cvut.cz Git - lincan.git/blobdiff - lincan/src/omap2_spican.c
Support for asynchronous SPI messages and callbacks
[lincan.git] / lincan / src / omap2_spican.c
index 132d3cae9aff7b572b8ec12e09319aab48c8b56a..e6fb0d287b8d99bb730c8356906b15a1055eb2ac 100644 (file)
        #include <plat/dmtimer.h>       
 #endif /* CONFIG_OC_LINCAN_OMAP_DM_TIMER */
 
+static const char* omap2_spican_defaulttype = "mcp2515";
+
+static struct workqueue_struct *omap2_spican_wq;
+
 /*******************************************************************/
 /*
  * We can't use the standard synchronous wrappers for file I/O; we
  */
 static void omap2_spican_spi_complete(void *arg)
 {
+       DEBUGMSG("SPICAN sync: complete\n");
        complete(arg);
 }
 
-static ssize_t omap2_spican_spi_sync(struct candevice_t *candev, struct spi_message *message)
+ssize_t omap2_spican_spi_sync(struct candevice_t *candev, struct spi_message *message)
 {
        DECLARE_COMPLETION_ONSTACK(done);
-       struct omap2_spican_platform_data *pdata = (struct omap2_spican_platform_data *)(candev->sysdevptr.anydev);
+       struct omap2_spican_platform_data *pdata;
        int status;
+       
+       if (candev == NULL)
+               return -ESHUTDOWN;
+       if (message == NULL)
+               panic("SPICAN: trying to send null pointered message");
 
+       pdata = (struct omap2_spican_platform_data *)(candev->sysdevptr.anydev);
        if (pdata == NULL)
                return -ESHUTDOWN;
 
@@ -54,12 +65,17 @@ static ssize_t omap2_spican_spi_sync(struct candevice_t *candev, struct spi_mess
        spin_lock_irq(&pdata->spi_lock);
        if (pdata->spi == NULL)
                status = -ESHUTDOWN;
-       else
+       else{
+               DEBUGMSG("SPICAN sync: transfer\n");
                status = spi_async(pdata->spi, message);
+       }
        spin_unlock_irq(&pdata->spi_lock);
 
        if (status == 0) {
-               wait_for_completion(&done);
+               while (!wait_for_completion_interruptible_timeout(&done, msecs_to_jiffies(10))) { 
+                       DEBUGMSG("SPICAN sync: timeout 10 msecs\n");
+               }
+//             do { ;} while (wait_for_completion_interruptible(&done));
                status = message->status;
                if (status == 0)
                        status = message->actual_length;
@@ -89,34 +105,157 @@ static ssize_t omap2_spican_spi_transfer(struct candevice_t *candev,void *tx, vo
 
        spi_message_add_tail(&t, &m);
        status = omap2_spican_spi_sync(candev, &m);
-/*     struct spi_transfer     *t;
-       struct spi_message      m;
-       int i,status;
+       return status;
+}
 
+static void hi_async_tasklet_handler(unsigned long data)
+{
+       struct omap2_spican_platform_data *pdata=(struct omap2_spican_platform_data *)data;
+       int n;
+       
        if (pdata == NULL)
-               return -1;
-       if(len > SPI_BUF_LEN) panic("long CAN spi transfer: %u",len);
+               return;
 
-       spi_message_init(&m);
+       
+       do {
+               n = find_first_bit(pdata->hi_async_done_map,OMAP2_SPICAN_PRE_ASYNC);
+               if (n == OMAP2_SPICAN_PRE_ASYNC)
+                       break;
+
+               DEBUGMSG("SPICAN async %d: high priority tasklet\n",n);
+               if (pdata->async[n].complete)
+                       pdata->async[n].complete(pdata->async[n].data,pdata->async[n].count);
+               
+               clear_bit(n,pdata->hi_async_done_map);
+               clear_bit(n,pdata->async_req_map);
+       } while (n < OMAP2_SPICAN_PRE_ASYNC);
+}
+
+static void lo_async_workqueue_handler(struct work_struct *work)
+{
+       struct omap2_spican_platform_data *pdata = container_of(work,struct omap2_spican_platform_data,lo_async_workqueue);
+       struct omap2_spican_async_t *async;
+       int n;
+
+       if (pdata == NULL)
+               return;
+
+       do {
+               n = find_first_bit(pdata->lo_async_done_map,OMAP2_SPICAN_PRE_ASYNC);
+               if (n >= OMAP2_SPICAN_PRE_ASYNC)
+                       break;
+
+               DEBUGMSG("SPICAN async %d: low priority workqueue\n",n);
+               async = pdata->async + n;
+
+               if (async->complete)
+                       async->complete(async->data,async->count);
+               
+               clear_bit(n,pdata->lo_async_done_map);
+               clear_bit(n,pdata->async_req_map);
+       } while (n < OMAP2_SPICAN_PRE_ASYNC);
+}
 
-       t = can_checked_malloc(len * sizeof(struct spi_transfer));
-       for (i=0;i<len;i++){
-               (*(t+i)).tx_buf         = tx+i;
-               (*(t+i)).rx_buf         = rx+i;
-               (*(t+i)).len            = 1;
-               (*(t+i)).cs_change = 0;
-               (*(t+i)).delay_usecs = 0;
-               (*(t+i)).speed_hz = pdata->speed_hz;
-               (*(t+i)).bits_per_word = 8;
-               spi_message_add_tail(t+i, &m);
+static void omap2_spican_spi_async_complete(void *arg)
+{
+       struct omap2_spican_async_t *as = (struct omap2_spican_async_t *)arg;
+       struct omap2_spican_platform_data *pdata = container_of(as,struct omap2_spican_platform_data,async[as->bitmap_bit]);
+       int i;
+
+//     DEBUGMSG("SPICAN async: complete\n");
+       for (i=1;i<as->count;i++)
+               can_filltimestamp(&as->data[i].timestamp);              
+       // Serve the fast handlers ASAP
+       if ((as->fasthandler) && (as->complete)){
+//             DEBUGMSG("SPICAN async: fasthandler\n");
+/*             set_bit(as->bitmap_bit,pdata->hi_async_done_map);
+               tasklet_hi_schedule(&pdata->hi_async_tasklet);*/
+               if (as->complete){
+                       as->complete(as->data,as->count);
+               }
+               clear_bit(as->bitmap_bit,pdata->lo_async_done_map);
+               clear_bit(as->bitmap_bit,pdata->async_req_map);
+//             DEBUGMSG("SPICAN async: fasthandler done\n");
+               return;
        }
-       status = omap2_spican_spi_sync(candev, &m);
-       can_checked_free(t);*/
-       return status;
+       
+       DEBUGMSG("SPICAN async %d: slowhandler register\n",as->bitmap_bit);
+       set_bit(as->bitmap_bit,pdata->lo_async_done_map);
+       queue_work(omap2_spican_wq,&pdata->lo_async_workqueue);
 }
 
+ssize_t omap2_spican_spi_async_transfer(struct candevice_t *candev, void (*callback)(void *data), struct can_spi_async_t *data, uint8_t count, uint8_t fasthandler)
+{
+       struct omap2_spican_platform_data *pdata;
+       struct omap2_spican_async_t *async;
+       int status,i;
+       int n;
+       
+       if (candev == NULL)
+               return -ESHUTDOWN;
+
+       pdata = (struct omap2_spican_platform_data *)(candev->sysdevptr.anydev);
+       if (pdata == NULL)
+               return -ESHUTDOWN;
+       
+       if (count > OMAP2_SPICAN_MAX_TRANSFERS)
+               return -ENOBUFS;
+
+       do {
+               n = find_first_zero_bit(pdata->async_req_map, OMAP2_SPICAN_PRE_ASYNC);
+               // There's no free transfer slot
+               if (n == OMAP2_SPICAN_PRE_ASYNC)
+                       return -ENOBUFS;
+       } while (test_and_set_bit(n,pdata->async_req_map));
+       
+       async = pdata->async+n;
+
+       memset(async,0,sizeof(struct omap2_spican_async_t));
+
+       for (i=0;i<count;i++){
+               if(data[i].len > SPI_MESSAGE_LENGTH)
+                       panic("too long CAN spi transfer: %u",data[i].len);
+       }
+
+       memcpy(async->data,data,count*sizeof(struct can_spi_async_t));
+       async->count = count;
+       async->bitmap_bit = n;
+       async->complete = callback;
+       async->fasthandler = fasthandler;
+
+       spi_message_init(&async->m);
+       
+       for (i=0;i<count;i++){
+               if ((async->data[i].tx_buf == NULL) && (async->data[i].rx_buf == NULL) && async->data[i].len != 0)
+                       panic("Trying to send NULL pointered buffers");
+               
+               async->t[i].tx_buf = async->data[i].tx_buf;
+               async->t[i].rx_buf = async->data[i].rx_buf;
+               async->t[i].len = async->data[i].len;
+               async->t[i].cs_change = 1;
+               async->t[i].speed_hz = pdata->speed_hz;
+               async->t[i].bits_per_word = 8;
+
+               spi_message_add_tail(&async->t[i], &async->m);
+       }
+       
+       async->m.complete = omap2_spican_spi_async_complete;
+       async->m.context = async;
+       spin_lock_irq(&pdata->spi_lock);
+       if (pdata->spi == NULL)
+               status = -ESHUTDOWN;
+       else{
+               DEBUGMSG("SPICAN async %d: transfer\n",n);
+               status = spi_async(pdata->spi, &async->m);
+       }
+       spin_unlock_irq(&pdata->spi_lock);
+       
+       if (status){
+               clear_bit(n,pdata->async_req_map);
+       }
 
-/// ---------------------- EVERYTHING'S DONE AFTER THIS POINT -----------------------------------------------
+       return status;
+}
 
 /**
  * omap2_spican_irq_handler: - GPT (general purpose timer) and waiting
@@ -135,14 +274,23 @@ static irqreturn_t omap2_spican_irq_handler(int irq, void *dev_id)
        int i;
        
        if (!dev_id)
-               return IRQ_HANDLED;
-
+               return IRQ_NONE;
+       
        pdata = (struct omap2_spican_platform_data *)(candev->sysdevptr.anydev);
        if (!pdata)
-               return IRQ_HANDLED;
+               return IRQ_NONE;
        
+/*     if (((pdata->trigger == OMAP2_SPICAN_TRIG_GPT) && (irq != pdata->timer_irq))
+               || ((pdata->trigger == OMAP2_SPICAN_TRIG_IRQ) && (irq != pdata->irq))
+       )
+               return IRQ_NONE;*/
+       if ((irq != pdata->timer_irq) && (irq != pdata->irq))
+               return IRQ_NONE;
+       
+       DEBUGMSG("SPICAN: Got interrupt");
        #ifdef CONFIG_OC_LINCAN_OMAP_DM_TIMER
-       if (pdata->trigger == OMAP2_SPICAN_TRIG_GPT){
+//     if (pdata->trigger == OMAP2_SPICAN_TRIG_GPT){
+       if (irq == pdata->timer_irq){
                // reset the timer interrupt status
                omap_dm_timer_write_status(pdata->timer_ptr, OMAP_TIMER_INT_OVERFLOW);
                omap_dm_timer_read_status(pdata->timer_ptr); // YES, you really need to do this 'wasteful' read
@@ -293,7 +441,7 @@ int omap2_spican_init_chip_data(struct candevice_t *candev, int chipnr)
 
 int omap2_spican_register_chip_data(struct canchip_t *ch,void *data){
        struct omap2_spican_platform_data *pdata = (struct omap2_spican_platform_data *)data;
-       ch->clock = pdata->mcp2515_clk;
+       ch->clock = pdata->clk;
        ch->baudrate = pdata->baudrate;
        int ret = 0;
        
@@ -387,6 +535,7 @@ int omap2_spican_program_irq(struct candevice_t *candev)
 
 /*******************************************************************/
 int omap2_spican_spi_acquire_bus(struct candevice_t *candev, short channel, int block){
+       /* Subsystem is always ready to accept messages */
        return 1;
 }
 
@@ -409,6 +558,7 @@ int omap2_spican_register(struct hwspecops_t *hwspecops)
 
        // SPI specific functions
        hwspecops->spi_transfer = omap2_spican_spi_transfer;
+       hwspecops->spi_async_transfer = omap2_spican_spi_async_transfer;
        hwspecops->spi_acquire_bus = omap2_spican_spi_acquire_bus;
        hwspecops->spi_release_bus = omap2_spican_spi_release_bus;
 
@@ -419,37 +569,47 @@ int omap2_spican_register(struct hwspecops_t *hwspecops)
 
 static int omap2_spican_probe(struct spi_device *spi)
 {
-       struct omap2_spican_platform_data       *pdata = spi->dev.platform_data;
+       struct lincan_spican_platform_data      *spidata = spi->dev.platform_data;
+       struct omap2_spican_platform_data *pdata;
        struct candevice_t *dev;
        int allocated = 0;
-
-       static struct omap2_spican_platform_data mypdata;
-       static const char* omap2_spican_defaulttype = "mcp2515";
        
        DEBUGMSG("Starting probe for omap2_spican...\n");
        
        // Structure checks and initialization
-       if (pdata == NULL){
-/*             pdata = (struct omap2_spican_platform_data *)can_checked_malloc(sizeof(struct omap2_spican_platform_data));
-               if (pdata)
-                       memset(pdata,0,sizeof(struct omap2_spican_platform_data));
-               else
-                       return -ENOMEM;
-               allocated = 1;*/
-               pdata = &mypdata; // No need to free up
-               
+       pdata = (struct omap2_spican_platform_data *)can_checked_malloc(sizeof(struct omap2_spican_platform_data));
+       if (pdata)
+               memset(pdata,0,sizeof(struct omap2_spican_platform_data));
+       else
+               return -ENOMEM;
+       
+       if (spidata)
+               memcpy(pdata,spidata,sizeof(struct lincan_spican_platform_data));
+       else{
                if (!pdata->cs_change) pdata->cs_change = OMAP2_SPICAN_CS_CHANGE;
                if (!pdata->delay_usecs) pdata->delay_usecs = OMAP2_SPICAN_DELAY_USECS;
        }
-       if (!pdata->mcp2515_clk) pdata->mcp2515_clk = OMAP2_SPICAN_MCP_CLK;
+       if (!pdata->clk) pdata->clk = OMAP2_SPICAN_MCP_CLK;
        if (!pdata->baudrate) pdata->baudrate = OMAP2_SPICAN_BAUDRATE;
        if (!pdata->speed_hz) pdata->speed_hz = OMAP2_SPICAN_SPEED_HZ;
        if (!pdata->chiptype) pdata->chiptype = omap2_spican_defaulttype;
 
-       
        spin_lock_init(&pdata->spi_lock);
        pdata->spi = spi;
        pdata->trigger = 0;
+
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+       INIT_WORK(&pdata->lo_async_workqueue, 
+                 lo_async_workqueue_handler, 
+                 &pdata->lo_async_workqueue);
+ #else
+       INIT_WORK(&pdata->lo_async_workqueue, 
+                 lo_async_workqueue_handler);
+ #endif
+       tasklet_init(&pdata->hi_async_tasklet,
+                    hi_async_tasklet_handler, 
+                    (unsigned long)pdata);
+       
        if (spi->irq) {
                DEBUGMSG("Interrupt line number %d provided.\n",spi->irq);
                pdata->trigger = OMAP2_SPICAN_TRIG_IRQ;
@@ -466,9 +626,9 @@ static int omap2_spican_probe(struct spi_device *spi)
        if (pdata->trigger == OMAP2_SPICAN_TRIG_IRQ) {
                int status = 0;
                DEBUGMSG("Requesting interrupt line %d.\n",spi->irq);
-               status = set_irq_type(pdata->irq,IRQ_TYPE_EDGE_BOTH);
+               status = set_irq_type(pdata->irq,IRQ_TYPE_EDGE_FALLING);
                if(status){
-                       CANMSG("Setting low level trigger on irq %d failed.\n", spi->irq);
+                       CANMSG("Setting falling edge trigger on irq %d failed.\n", spi->irq);
                        goto release;
                }
                status = request_irq(pdata->irq, omap2_spican_irq_handler, IRQF_DISABLED , "omap2_spican", dev);
@@ -478,7 +638,8 @@ static int omap2_spican_probe(struct spi_device *spi)
                }
        }
 #ifdef CONFIG_OC_LINCAN_OMAP_DM_TIMER
-       if (pdata->trigger == 0){
+//     if (pdata->trigger == 0){
+       {
                struct clk *gt_fclk;
                uint32_t gt_rate;
                int status = 0;
@@ -502,8 +663,8 @@ static int omap2_spican_probe(struct spi_device *spi)
                gt_rate = clk_get_rate(gt_fclk)/(1<<(prescaler+1));
         
                // set preload, and autoreload
-               // we set it to the clock rate in order to get 1 overflow every 10 usecs
-               omap_dm_timer_set_load(pdata->timer_ptr, 1, 0xFFFFFFFF - (uint32_t)(gt_rate / 100));
+               // we set it to the clock rate in order to get 1 overflow every 1 msec
+               omap_dm_timer_set_load(pdata->timer_ptr, 1, 0xFFFFFFFF - (uint32_t)(gt_rate / 1000));
 
                // Request for the irq
                pdata->timer_irq = omap_dm_timer_get_irq(pdata->timer_ptr);
@@ -517,25 +678,25 @@ static int omap2_spican_probe(struct spi_device *spi)
 
                // setup timer to trigger our IRQ on the overflow event
                omap_dm_timer_set_int_enable(pdata->timer_ptr, OMAP_TIMER_INT_OVERFLOW);
-               pdata->trigger = OMAP2_SPICAN_TRIG_GPT;
+//             pdata->trigger = OMAP2_SPICAN_TRIG_GPT;
        }
 #endif /* CONFIG_OC_LINCAN_OMAP_DM_TIMER */
 
        spi_set_drvdata(spi, dev);
 
 #ifdef CONFIG_OC_LINCAN_OMAP_DM_TIMER
-                       if (pdata->trigger == OMAP2_SPICAN_TRIG_GPT){
-                               // Everything's ready, let's roll!
-                               DEBUGMSG("Starting OMAP GPT\n");
-                               omap_dm_timer_start(pdata->timer_ptr);
-                       }
+//                     if (pdata->trigger == OMAP2_SPICAN_TRIG_GPT){
+       {
+               // Everything's ready, let's roll!
+               DEBUGMSG("Starting OMAP GPT\n");
+               omap_dm_timer_start(pdata->timer_ptr);
+       }
 #endif /* CONFIG_OC_LINCAN_OMAP_DM_TIMER */
        DEBUGMSG("Device omap2_spican successfully configured. Have fun!\n");
        return 0;
        
 release:
-       if (allocated)
-               can_checked_free(pdata);
+       can_checked_free(pdata);
        return -ENODEV;
 }
 
@@ -549,7 +710,8 @@ static int omap2_spican_remove(struct spi_device *spi)
                free_irq(pdata->irq, candev);
        }
 #ifdef CONFIG_OC_LINCAN_OMAP_DM_TIMER
-       if (pdata->trigger == OMAP2_SPICAN_TRIG_GPT){
+//     if (pdata->trigger == OMAP2_SPICAN_TRIG_GPT){
+       {
                DEBUGMSG("Stopping OMAP GPT\n");
                omap_dm_timer_stop(pdata->timer_ptr);
                // release the IRQ handler
@@ -561,6 +723,7 @@ static int omap2_spican_remove(struct spi_device *spi)
 
        DEBUGMSG("Removing omap2_spican from device structure...");
        //      make sure ops on existing fds can abort cleanly
+       deregister_hotplug_dev(candev);
        cleanup_hotplug_dev(candev);
 
        spin_lock_irq(&pdata->spi_lock);
@@ -569,6 +732,10 @@ static int omap2_spican_remove(struct spi_device *spi)
 //     candev->sysdevptr.anydev = NULL;
        spin_unlock_irq(&pdata->spi_lock);
 
+       DEBUGMSG("Kill tasklets.\n");
+       tasklet_kill(&pdata->hi_async_tasklet);
+
+       can_checked_free(pdata);
        DEBUGMSG(" done.\n");
 
        return 0;
@@ -586,11 +753,15 @@ static struct spi_driver omap2_spican_driver = {
 };
 
 int omap2_spican_init(void){
+       omap2_spican_wq = create_singlethread_workqueue("omap2_spican");
+       if (omap2_spican_wq == NULL)
+               return -1;
        return spi_register_driver(&omap2_spican_driver);
 }
 
 void omap2_spican_exit(void){
        spi_unregister_driver(&omap2_spican_driver);
+       destroy_workqueue(omap2_spican_wq);
 }
 
 #ifdef MODULE_ALIAS