--- /dev/null
+/* 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