]> rtime.felk.cvut.cz Git - lincan.git/blobdiff - lincan/src/omap2_spican.c
Spican1 support added
[lincan.git] / lincan / src / omap2_spican.c
diff --git a/lincan/src/omap2_spican.c b/lincan/src/omap2_spican.c
new file mode 100644 (file)
index 0000000..132d3ca
--- /dev/null
@@ -0,0 +1,598 @@
+/* omap2_spican.c
+ * Linux CAN-bus device driver.
+ * Written by Arnaud Westenberg email:arnaud@wanadoo.nl
+ * Rewritten for new CAN queues by Pavel Pisa - OCERA team member
+ * email:pisa@cmp.felk.cvut.cz
+ * This software is released under the GPL-License.
+ * Version lincan-0.3  17 Jun 2004
+ */ 
+
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <linux/clk.h>         
+#include <linux/spi/spi.h>
+
+#include "../include/can.h"
+#include "../include/can_sysdep.h"
+#include "../include/main.h"
+#include "../include/setup.h"
+#include "../include/omap2_spican.h"
+#include "../include/mcp2515.h"
+
+#ifdef CONFIG_OC_LINCAN_OMAP_DM_TIMER
+       #include <asm/io.h>                     
+       #include <asm/uaccess.h>
+       #include <plat/dmtimer.h>       
+#endif /* CONFIG_OC_LINCAN_OMAP_DM_TIMER */
+
+/*******************************************************************/
+/*
+ * We can't use the standard synchronous wrappers for file I/O; we
+ * need to protect against async removal of the underlying spi_device.
+ */
+static void omap2_spican_spi_complete(void *arg)
+{
+       complete(arg);
+}
+
+static 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);
+       int status;
+
+       if (pdata == NULL)
+               return -ESHUTDOWN;
+
+       message->complete = omap2_spican_spi_complete;
+       message->context = &done;
+       spin_lock_irq(&pdata->spi_lock);
+       if (pdata->spi == NULL)
+               status = -ESHUTDOWN;
+       else
+               status = spi_async(pdata->spi, message);
+       spin_unlock_irq(&pdata->spi_lock);
+
+       if (status == 0) {
+               wait_for_completion(&done);
+               status = message->status;
+               if (status == 0)
+                       status = message->actual_length;
+       }
+       return status;
+}
+
+static ssize_t omap2_spican_spi_transfer(struct candevice_t *candev,void *tx, void *rx, uint16_t len)
+{
+       struct omap2_spican_platform_data *pdata = (struct omap2_spican_platform_data *)(candev->sysdevptr.anydev);
+       struct spi_transfer     t = {
+               .tx_buf         = tx,
+               .rx_buf         = rx,
+               .len            = len,
+               .cs_change = 1,
+               .speed_hz = pdata->speed_hz,
+               .bits_per_word = 8,
+       };
+       struct spi_message      m;
+       int i,status;
+
+       if (pdata == NULL)
+               return -1;
+       if(len > SPI_BUF_LEN) panic("long CAN spi transfer: %u",len);
+
+       spi_message_init(&m);
+
+       spi_message_add_tail(&t, &m);
+       status = omap2_spican_spi_sync(candev, &m);
+/*     struct spi_transfer     *t;
+       struct spi_message      m;
+       int i,status;
+
+       if (pdata == NULL)
+               return -1;
+       if(len > SPI_BUF_LEN) panic("long CAN spi transfer: %u",len);
+
+       spi_message_init(&m);
+
+       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);
+       }
+       status = omap2_spican_spi_sync(candev, &m);
+       can_checked_free(t);*/
+       return status;
+}
+
+
+/// ---------------------- EVERYTHING'S DONE AFTER THIS POINT -----------------------------------------------
+
+/**
+ * omap2_spican_irq_handler: - GPT (general purpose timer) and waiting
+ * thread interrupt handler
+ * @irq: the interrupt line number
+ * @dev_id: pointer to can device data
+ * 
+ * Return Value: Interrupt handled status is returned
+ * File: src/omap2_spican.c
+ */
+static irqreturn_t omap2_spican_irq_handler(int irq, void *dev_id)
+{
+       struct candevice_t *candev = dev_id;
+       struct omap2_spican_platform_data *pdata;
+       unsigned long flags;
+       int i;
+       
+       if (!dev_id)
+               return IRQ_HANDLED;
+
+       pdata = (struct omap2_spican_platform_data *)(candev->sysdevptr.anydev);
+       if (!pdata)
+               return IRQ_HANDLED;
+       
+       #ifdef CONFIG_OC_LINCAN_OMAP_DM_TIMER
+       if (pdata->trigger == OMAP2_SPICAN_TRIG_GPT){
+               // 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
+       }
+       #endif /* CONFIG_OC_LINCAN_OMAP_DM_TIMER */
+       for (i=0;i<candev->nr_all_chips;i++){
+               if (candev->chip[i]->chipspecops->irq_handler)
+                       candev->chip[i]->chipspecops->irq_handler(irq, candev->chip[i]);
+       }
+
+       return IRQ_HANDLED;
+       
+}
+/**
+ * omap2_spican_request_io: - reserve io or memory range for can board
+ * @candev: pointer to candevice/board which asks for io. Field @io_addr
+ *     of @candev is used in most cases to define start of the range
+ *
+ * The function omap2_spican_request_io() is used to reserve the io-memory. If your
+ * hardware uses a dedicated memory range as hardware control registers you
+ * will have to add the code to reserve this memory as well. 
+ * %IO_RANGE is the io-memory range that gets reserved, please adjust according
+ * your hardware. Example: #define IO_RANGE 0x100 for i82527 chips or
+ * #define IO_RANGE 0x20 for sja1000 chips in basic CAN mode.
+ * Return Value: The function returns zero on success or %-ENODEV on failure
+ * File: src/omap2_spican.c
+ */
+int omap2_spican_request_io(struct candevice_t *candev)
+{
+       return 0;
+}
+
+/**
+ * omap2_spican_release_io - free reserved io memory range
+ * @candev: pointer to candevice/board which releases io
+ *
+ * The function omap2_spican_release_io() is used to free reserved io-memory.
+ * In case you have reserved more io memory, don't forget to free it here.
+ * IO_RANGE is the io-memory range that gets released, please adjust according
+ * your hardware. Example: #define IO_RANGE 0x100 for i82527 chips or
+ * #define IO_RANGE 0x20 for sja1000 chips in basic CAN mode.
+ * Return Value: The function always returns zero
+ * File: src/omap2_spican.c
+ */
+int omap2_spican_release_io(struct candevice_t *candev)
+{
+       return 0;
+}
+
+/**
+ * omap2_spican_reset - hardware reset routine
+ * @candev: Pointer to candevice/board structure
+ *
+ * The function omap2_spican_reset() is used to give a hardware reset. This is 
+ * rather hardware specific so I haven't included example code. Don't forget to 
+ * check the reset status of the chip before returning.
+ * Return Value: The function returns zero on success or %-ENODEV on failure
+ * File: src/omap2_spican.c
+ */
+int omap2_spican_reset(struct candevice_t *candev)
+{
+       int i;
+       DEBUGMSG("Resetting all underlying chips\n");
+       for(i=0;i<OMAP2_SPICAN_NCHIPS;i++)
+               (candev->chip[i]->chipspecops->reset_chip)(candev->chip[i]);
+
+       return 0;
+}
+
+/**
+ * omap2_spican_init_hw_data - Initialize hardware cards
+ * @candev: Pointer to candevice/board structure
+ *
+ * The function omap2_spican_init_hw_data() is used to initialize the hardware
+ * structure containing information about the installed CAN-board.
+ * %RESET_ADDR represents the io-address of the hardware reset register.
+ * %NR_82527 represents the number of Intel 82527 chips on the board.
+ * %NR_SJA1000 represents the number of Philips sja1000 chips on the board.
+ * The flags entry can currently only be %CANDEV_PROGRAMMABLE_IRQ to indicate that
+ * the hardware uses programmable interrupts.
+ * Return Value: The function always returns zero
+ * File: src/omap2_spican.c
+ */
+int omap2_spican_init_hw_data(struct candevice_t *candev) 
+{
+       candev->dev_base_addr = 0;
+       candev->nr_82527_chips = 0;
+       candev->nr_sja1000_chips = 0;
+       candev->nr_mcp2515_chips = OMAP2_SPICAN_NCHIPS;
+       candev->nr_all_chips = candev->nr_82527_chips + 
+               candev->nr_sja1000_chips + 
+               candev->nr_mcp2515_chips;
+       candev->flags &= ~CANDEV_PROGRAMMABLE_IRQ;
+
+       return 0;
+}
+
+/**
+ * omap2_spican_init_chip_data - Initialize chips
+ * @candev: Pointer to candevice/board structure
+ * @chipnr: Number of the CAN chip on the hardware card
+ *
+ * The function omap2_spican_init_chip_data() is used to initialize the hardware
+ * structure containing information about the CAN chips.
+ * %CHIP_TYPE represents the type of CAN chip. %CHIP_TYPE can be "i82527" or
+ * "sja1000".
+ * The @chip_base_addr entry represents the start of the 'official' memory map
+ * of the installed chip. It's likely that this is the same as the @io_addr
+ * argument supplied at module loading time.
+ * The @clock entry holds the chip clock value in Hz.
+ * The entry @sja_cdr_reg holds hardware specific options for the Clock Divider
+ * register. Options defined in the %sja1000.h file:
+ * %sjaCDR_CLKOUT_MASK, %sjaCDR_CLK_OFF, %sjaCDR_RXINPEN, %sjaCDR_CBP, %sjaCDR_PELICAN
+ * The entry @sja_ocr_reg holds hardware specific options for the Output Control
+ * register. Options defined in the %sja1000.h file:
+ * %sjaOCR_MODE_BIPHASE, %sjaOCR_MODE_TEST, %sjaOCR_MODE_NORMAL, %sjaOCR_MODE_CLOCK,
+ * %sjaOCR_TX0_LH, %sjaOCR_TX1_ZZ.
+ * The entry @int_clk_reg holds hardware specific options for the Clock Out
+ * register. Options defined in the %i82527.h file:
+ * %iCLK_CD0, %iCLK_CD1, %iCLK_CD2, %iCLK_CD3, %iCLK_SL0, %iCLK_SL1.
+ * The entry @int_bus_reg holds hardware specific options for the Bus 
+ * Configuration register. Options defined in the %i82527.h file:
+ * %iBUS_DR0, %iBUS_DR1, %iBUS_DT1, %iBUS_POL, %iBUS_CBY.
+ * The entry @int_cpu_reg holds hardware specific options for the cpu interface
+ * register. Options defined in the %i82527.h file:
+ * %iCPU_CEN, %iCPU_MUX, %iCPU_SLP, %iCPU_PWD, %iCPU_DMC, %iCPU_DSC, %iCPU_RST.
+ * Return Value: The function always returns zero
+ * File: src/omap2_spican.c
+ */
+int omap2_spican_init_chip_data(struct candevice_t *candev, int chipnr)
+{
+       if(chipnr >= OMAP2_SPICAN_NCHIPS || chipnr < 0) {
+               CANMSG("Error: chipnr=%d\n",chipnr);
+               return -ENODEV;
+       }
+       
+       mcp2515_fill_chipspecops(candev->chip[chipnr]);
+/*     candev->chip[chipnr]->chip_base_addr = 0;
+       candev->chip[chipnr]->spi_channel = 2;*/
+       candev->chip[chipnr]->chip_data = can_checked_malloc(sizeof(MCP2515_PRIV));
+       if(candev->chip[chipnr]->chip_data == NULL) return -ENOMEM;
+       memset(candev->chip[chipnr]->chip_data,0,sizeof(MCP2515_PRIV));
+
+       return 0;
+}
+
+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->baudrate = pdata->baudrate;
+       int ret = 0;
+       
+       ch->flags |= CHIP_IRQ_CUSTOM;
+//     if (pdata->trigger == OMAP2_SPICAN_TRIG_IRQ){
+//             ch->chip_irq = pdata->irq;
+//     }
+
+       ret = ch->chipspecops->chip_config(ch);
+       if (ret){
+               CANMSG("Error configuring chip.\n");
+       }
+       else
+               ch->flags |= CHIP_CONFIGURED; 
+       return ret;
+}
+
+/**
+ * omap2_spican_init_obj_data - Initialize message buffers
+ * @chip: Pointer to chip specific structure
+ * @objnr: Number of the message buffer
+ *
+ * The function omap2_spican_init_obj_data() is used to initialize the hardware
+ * structure containing information about the different message objects on the
+ * CAN chip. In case of the sja1000 there's only one message object but on the
+ * i82527 chip there are 15.
+ * The code below is for a i82527 chip and initializes the object base addresses
+ * The entry @obj_base_addr represents the first memory address of the message 
+ * object. In case of the sja1000 @obj_base_addr is taken the same as the chips
+ * base address.
+ * Unless the hardware uses a segmented memory map, flags can be set zero.
+ * Return Value: The function always returns zero
+ * File: src/omap2_spican.c
+ */
+int omap2_spican_init_obj_data(struct canchip_t *chip, int objnr)
+{
+       chip->msgobj[objnr]->obj_base_addr=0;
+       chip->msgobj[objnr]->obj_flags=0;
+       
+       return 0;
+}
+
+/**
+ * omap2_spican_write_register - Low level write register routine
+ * @data: data to be written
+ * @address: memory address to write to
+ *
+ * The function omap2_spican_write_register() is used to write to hardware registers
+ * on the CAN chip. You should only have to edit this function if your hardware
+ * uses some specific write process.
+ * Return Value: The function does not return a value
+ * File: src/omap2_spican.c
+ */
+void omap2_spican_write_register(unsigned data, can_ioptr_t address)
+{
+       panic("omap2_spican_write_register");
+}
+
+/**
+ * omap2_spican_read_register - Low level read register routine
+ * @address: memory address to read from
+ *
+ * The function omap2_spican_read_register() is used to read from hardware registers
+ * on the CAN chip. You should only have to edit this function if your hardware
+ * uses some specific read process.
+ * Return Value: The function returns the value stored in @address
+ * File: src/omap2_spican.c
+ */
+unsigned omap2_spican_read_register(can_ioptr_t address)
+{
+       panic("omap2_spican_read_register");
+       return 0;
+}
+
+/**
+ * omap2_spican_program_irq - program interrupts
+ * @candev: Pointer to candevice/board structure
+ *
+ * The function omap2_spican_program_irq() is used for hardware that uses 
+ * programmable interrupts. If your hardware doesn't use programmable interrupts
+ * you should not set the @candevices_t->flags entry to %CANDEV_PROGRAMMABLE_IRQ and 
+ * leave this function unedited. Again this function is hardware specific so 
+ * there's no example code.
+ * Return value: The function returns zero on success or %-ENODEV on failure
+ * File: src/omap2_spican.c
+ */
+int omap2_spican_program_irq(struct candevice_t *candev)
+{
+       return 0;
+}
+
+/*******************************************************************/
+int omap2_spican_spi_acquire_bus(struct candevice_t *candev, short channel, int block){
+       return 1;
+}
+
+/*******************************************************************/
+void omap2_spican_spi_release_bus(struct candevice_t *candev, short channel){
+       return;
+}
+
+int omap2_spican_register(struct hwspecops_t *hwspecops)
+{
+       hwspecops->request_io = omap2_spican_request_io;
+       hwspecops->release_io = omap2_spican_release_io;
+       hwspecops->reset = omap2_spican_reset;
+       hwspecops->init_hw_data = omap2_spican_init_hw_data;
+       hwspecops->init_chip_data = omap2_spican_init_chip_data;
+       hwspecops->init_obj_data = omap2_spican_init_obj_data;
+       hwspecops->write_register = omap2_spican_write_register;
+       hwspecops->read_register = omap2_spican_read_register;
+       hwspecops->program_irq = omap2_spican_program_irq;
+
+       // SPI specific functions
+       hwspecops->spi_transfer = omap2_spican_spi_transfer;
+       hwspecops->spi_acquire_bus = omap2_spican_spi_acquire_bus;
+       hwspecops->spi_release_bus = omap2_spican_spi_release_bus;
+
+       return 0;
+}
+
+/*******************************************************************/
+
+static int omap2_spican_probe(struct spi_device *spi)
+{
+       struct omap2_spican_platform_data       *pdata = spi->dev.platform_data;
+       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
+               
+               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->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 (spi->irq) {
+               DEBUGMSG("Interrupt line number %d provided.\n",spi->irq);
+               pdata->trigger = OMAP2_SPICAN_TRIG_IRQ;
+               pdata->irq = spi->irq;
+       }
+       else
+               DEBUGMSG("no IRQ given, trying to find timers. Performance loss will be observed.\n");
+
+       /* Register device data in LinCAN - 1 chip, 2 msgobjs */
+       dev = register_hotplug_dev("omap2_spican",omap2_spican_register_chip_data,pdata);
+       if (!dev)
+               goto release;
+
+       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);
+               if(status){
+                       CANMSG("Setting low level trigger on irq %d failed.\n", spi->irq);
+                       goto release;
+               }
+               status = request_irq(pdata->irq, omap2_spican_irq_handler, IRQF_DISABLED , "omap2_spican", dev);
+               if(status){
+                       CANMSG("request_irq failed on irq %d\n", spi->irq);
+                       goto release;
+               }
+       }
+#ifdef CONFIG_OC_LINCAN_OMAP_DM_TIMER
+       if (pdata->trigger == 0){
+               struct clk *gt_fclk;
+               uint32_t gt_rate;
+               int status = 0;
+               int prescaler = 0;
+
+               DEBUGMSG("Setting up OMAP GPT\n");
+               // Get the timer
+               pdata->timer_ptr = omap_dm_timer_request();
+               if(pdata->timer_ptr == NULL){
+                       CANMSG("No more gp timers available, bailing out\n");
+                       goto release;
+               }
+               
+               // Initialize timer
+               omap_dm_timer_set_source(pdata->timer_ptr, OMAP_TIMER_SRC_SYS_CLK); // We use the system clock 38.4 MHz
+               
+               omap_dm_timer_set_prescaler(pdata->timer_ptr, prescaler);               // Set prescaler to 2^(n+1)
+               // get clock rate in Hz
+               gt_fclk = omap_dm_timer_get_fclk(pdata->timer_ptr);
+               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));
+
+               // Request for the irq
+               pdata->timer_irq = omap_dm_timer_get_irq(pdata->timer_ptr);
+               DEBUGMSG("Requesting irq %d for OMAP GPT\n", pdata->timer_irq);
+               status = request_irq(pdata->timer_irq, omap2_spican_irq_handler, IRQF_DISABLED | IRQF_TIMER , "omap2_spican", dev);
+               if(status){
+                       CANMSG("request_irq failed (on irq %d), bailing out\n", pdata->timer_irq);
+                 omap_dm_timer_free(pdata->timer_ptr);
+                       goto release;
+               }
+
+               // 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;
+       }
+#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);
+                       }
+#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);
+       return -ENODEV;
+}
+
+static int omap2_spican_remove(struct spi_device *spi)
+{
+       struct candevice_t *candev = spi_get_drvdata(spi);
+       struct omap2_spican_platform_data *pdata = (struct omap2_spican_platform_data *)(candev->sysdevptr.anydev);
+
+       if (pdata->trigger == OMAP2_SPICAN_TRIG_IRQ){
+               // release the IRQ handler
+               free_irq(pdata->irq, candev);
+       }
+#ifdef CONFIG_OC_LINCAN_OMAP_DM_TIMER
+       if (pdata->trigger == OMAP2_SPICAN_TRIG_GPT){
+               DEBUGMSG("Stopping OMAP GPT\n");
+               omap_dm_timer_stop(pdata->timer_ptr);
+               // release the IRQ handler
+               free_irq(pdata->timer_irq, candev);
+         // release the timer
+         omap_dm_timer_free(pdata->timer_ptr);
+       }
+#endif
+
+       DEBUGMSG("Removing omap2_spican from device structure...");
+       //      make sure ops on existing fds can abort cleanly
+       cleanup_hotplug_dev(candev);
+
+       spin_lock_irq(&pdata->spi_lock);
+       pdata->spi = NULL;
+       spi_set_drvdata(spi, NULL);
+//     candev->sysdevptr.anydev = NULL;
+       spin_unlock_irq(&pdata->spi_lock);
+
+       DEBUGMSG(" done.\n");
+
+       return 0;
+}
+
+static struct spi_driver omap2_spican_driver = {
+       .driver = {
+               .name   = "omap2_spican",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = omap2_spican_probe,
+       .remove         = omap2_spican_remove,
+/*     .suspend        = omap2_spican_suspend,
+       .resume         = omap2_spican_resume,*/
+};
+
+int omap2_spican_init(void){
+       return spi_register_driver(&omap2_spican_driver);
+}
+
+void omap2_spican_exit(void){
+       spi_unregister_driver(&omap2_spican_driver);
+}
+
+#ifdef MODULE_ALIAS
+MODULE_ALIAS("spi:omap2_spican");
+#endif