2008-08-06 00:01:09 +04:00
/*
2011-06-06 11:16:30 +04:00
* Marvell Orion SPI controller driver
2008-08-06 00:01:09 +04:00
*
* Author : Shadi Ammouri < shadi @ marvell . com >
* Copyright ( C ) 2007 - 2008 Marvell Ltd .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/platform_device.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/spi/spi.h>
2011-07-03 23:44:29 +04:00
# include <linux/module.h>
2014-06-21 15:22:37 +04:00
# include <linux/pm_runtime.h>
2012-07-23 14:08:09 +04:00
# include <linux/of.h>
2014-09-28 17:24:04 +04:00
# include <linux/of_device.h>
2012-04-06 19:17:26 +04:00
# include <linux/clk.h>
2013-07-29 08:10:21 +04:00
# include <linux/sizes.h>
2008-08-06 00:01:09 +04:00
# include <asm/unaligned.h>
# define DRIVER_NAME "orion_spi"
2014-06-21 15:22:37 +04:00
/* Runtime PM autosuspend timeout: PM is fairly light on this driver */
# define SPI_AUTOSUSPEND_TIMEOUT 200
2015-01-16 06:10:47 +03:00
/* Some SoCs using this driver support up to 8 chip selects.
* It is up to the implementer to only use the chip selects
* that are available .
*/
# define ORION_NUM_CHIPSELECTS 8
2008-08-06 00:01:09 +04:00
# define ORION_SPI_WAIT_RDY_MAX_LOOP 2000 /* in usec */
# define ORION_SPI_IF_CTRL_REG 0x00
# define ORION_SPI_IF_CONFIG_REG 0x04
# define ORION_SPI_DATA_OUT_REG 0x08
# define ORION_SPI_DATA_IN_REG 0x0c
# define ORION_SPI_INT_CAUSE_REG 0x10
2012-11-21 23:23:35 +04:00
# define ORION_SPI_MODE_CPOL (1 << 11)
# define ORION_SPI_MODE_CPHA (1 << 12)
2008-08-06 00:01:09 +04:00
# define ORION_SPI_IF_8_16_BIT_MODE (1 << 5)
# define ORION_SPI_CLK_PRESCALE_MASK 0x1F
2014-09-28 17:24:04 +04:00
# define ARMADA_SPI_CLK_PRESCALE_MASK 0xDF
2012-11-21 23:23:35 +04:00
# define ORION_SPI_MODE_MASK (ORION_SPI_MODE_CPOL | \
ORION_SPI_MODE_CPHA )
2015-01-16 06:10:47 +03:00
# define ORION_SPI_CS_MASK 0x1C
# define ORION_SPI_CS_SHIFT 2
# define ORION_SPI_CS(cs) ((cs << ORION_SPI_CS_SHIFT) & \
ORION_SPI_CS_MASK )
2008-08-06 00:01:09 +04:00
2014-09-28 17:24:04 +04:00
enum orion_spi_type {
ORION_SPI ,
ARMADA_SPI ,
} ;
struct orion_spi_dev {
enum orion_spi_type typ ;
unsigned int min_divisor ;
unsigned int max_divisor ;
u32 prescale_mask ;
} ;
2008-08-06 00:01:09 +04:00
struct orion_spi {
struct spi_master * master ;
void __iomem * base ;
2012-04-06 19:17:26 +04:00
struct clk * clk ;
2014-09-28 17:24:04 +04:00
const struct orion_spi_dev * devdata ;
2008-08-06 00:01:09 +04:00
} ;
static inline void __iomem * spi_reg ( struct orion_spi * orion_spi , u32 reg )
{
return orion_spi - > base + reg ;
}
static inline void
orion_spi_setbits ( struct orion_spi * orion_spi , u32 reg , u32 mask )
{
void __iomem * reg_addr = spi_reg ( orion_spi , reg ) ;
u32 val ;
val = readl ( reg_addr ) ;
val | = mask ;
writel ( val , reg_addr ) ;
}
static inline void
orion_spi_clrbits ( struct orion_spi * orion_spi , u32 reg , u32 mask )
{
void __iomem * reg_addr = spi_reg ( orion_spi , reg ) ;
u32 val ;
val = readl ( reg_addr ) ;
val & = ~ mask ;
writel ( val , reg_addr ) ;
}
static int orion_spi_baudrate_set ( struct spi_device * spi , unsigned int speed )
{
u32 tclk_hz ;
u32 rate ;
u32 prescale ;
u32 reg ;
struct orion_spi * orion_spi ;
2014-09-28 17:24:04 +04:00
const struct orion_spi_dev * devdata ;
2008-08-06 00:01:09 +04:00
orion_spi = spi_master_get_devdata ( spi - > master ) ;
2014-09-28 17:24:04 +04:00
devdata = orion_spi - > devdata ;
2008-08-06 00:01:09 +04:00
2012-04-06 19:17:26 +04:00
tclk_hz = clk_get_rate ( orion_spi - > clk ) ;
2008-08-06 00:01:09 +04:00
2014-09-28 17:24:04 +04:00
if ( devdata - > typ = = ARMADA_SPI ) {
unsigned int clk , spr , sppr , sppr2 , err ;
unsigned int best_spr , best_sppr , best_err ;
2008-08-06 00:01:09 +04:00
2014-09-28 17:24:04 +04:00
best_err = speed ;
best_spr = 0 ;
best_sppr = 0 ;
2008-08-06 00:01:09 +04:00
2014-09-28 17:24:04 +04:00
/* Iterate over the valid range looking for best fit */
for ( sppr = 0 ; sppr < 8 ; sppr + + ) {
sppr2 = 0x1 < < sppr ;
spr = tclk_hz / sppr2 ;
spr = DIV_ROUND_UP ( spr , speed ) ;
if ( ( spr = = 0 ) | | ( spr > 15 ) )
continue ;
2008-08-06 00:01:09 +04:00
2014-09-28 17:24:04 +04:00
clk = tclk_hz / ( spr * sppr2 ) ;
err = speed - clk ;
2008-08-06 00:01:09 +04:00
2014-09-28 17:24:04 +04:00
if ( err < best_err ) {
best_spr = spr ;
best_sppr = sppr ;
best_err = err ;
}
}
2008-08-06 00:01:09 +04:00
2014-09-28 17:24:04 +04:00
if ( ( best_sppr = = 0 ) & & ( best_spr = = 0 ) )
return - EINVAL ;
prescale = ( ( best_sppr & 0x6 ) < < 5 ) |
( ( best_sppr & 0x1 ) < < 4 ) | best_spr ;
} else {
/*
* the supported rates are : 4 , 6 , 8. . .30
* round up as we look for equal or less speed
*/
rate = DIV_ROUND_UP ( tclk_hz , speed ) ;
rate = roundup ( rate , 2 ) ;
/* check if requested speed is too small */
if ( rate > 30 )
return - EINVAL ;
2008-08-06 00:01:09 +04:00
2014-09-28 17:24:04 +04:00
if ( rate < 4 )
rate = 4 ;
/* Convert the rate to SPI clock divisor value. */
prescale = 0x10 + rate / 2 ;
}
2008-08-06 00:01:09 +04:00
reg = readl ( spi_reg ( orion_spi , ORION_SPI_IF_CONFIG_REG ) ) ;
2014-09-28 17:24:04 +04:00
reg = ( ( reg & ~ devdata - > prescale_mask ) | prescale ) ;
2008-08-06 00:01:09 +04:00
writel ( reg , spi_reg ( orion_spi , ORION_SPI_IF_CONFIG_REG ) ) ;
return 0 ;
}
2012-11-21 23:23:35 +04:00
static void
orion_spi_mode_set ( struct spi_device * spi )
{
u32 reg ;
struct orion_spi * orion_spi ;
orion_spi = spi_master_get_devdata ( spi - > master ) ;
reg = readl ( spi_reg ( orion_spi , ORION_SPI_IF_CONFIG_REG ) ) ;
reg & = ~ ORION_SPI_MODE_MASK ;
if ( spi - > mode & SPI_CPOL )
reg | = ORION_SPI_MODE_CPOL ;
if ( spi - > mode & SPI_CPHA )
reg | = ORION_SPI_MODE_CPHA ;
writel ( reg , spi_reg ( orion_spi , ORION_SPI_IF_CONFIG_REG ) ) ;
}
2008-08-06 00:01:09 +04:00
/*
* called only when no transfer is active on the bus
*/
static int
orion_spi_setup_transfer ( struct spi_device * spi , struct spi_transfer * t )
{
struct orion_spi * orion_spi ;
unsigned int speed = spi - > max_speed_hz ;
unsigned int bits_per_word = spi - > bits_per_word ;
int rc ;
orion_spi = spi_master_get_devdata ( spi - > master ) ;
if ( ( t ! = NULL ) & & t - > speed_hz )
speed = t - > speed_hz ;
if ( ( t ! = NULL ) & & t - > bits_per_word )
bits_per_word = t - > bits_per_word ;
2012-11-21 23:23:35 +04:00
orion_spi_mode_set ( spi ) ;
2008-08-06 00:01:09 +04:00
rc = orion_spi_baudrate_set ( spi , speed ) ;
if ( rc )
return rc ;
2014-02-11 16:51:36 +04:00
if ( bits_per_word = = 16 )
orion_spi_setbits ( orion_spi , ORION_SPI_IF_CONFIG_REG ,
ORION_SPI_IF_8_16_BIT_MODE ) ;
else
orion_spi_clrbits ( orion_spi , ORION_SPI_IF_CONFIG_REG ,
ORION_SPI_IF_8_16_BIT_MODE ) ;
return 0 ;
2008-08-06 00:01:09 +04:00
}
2015-01-12 06:13:59 +03:00
static void orion_spi_set_cs ( struct spi_device * spi , bool enable )
2008-08-06 00:01:09 +04:00
{
2015-01-12 06:13:59 +03:00
struct orion_spi * orion_spi ;
orion_spi = spi_master_get_devdata ( spi - > master ) ;
2015-01-16 06:10:47 +03:00
orion_spi_clrbits ( orion_spi , ORION_SPI_IF_CTRL_REG , ORION_SPI_CS_MASK ) ;
orion_spi_setbits ( orion_spi , ORION_SPI_IF_CTRL_REG ,
ORION_SPI_CS ( spi - > chip_select ) ) ;
2015-01-12 06:13:59 +03:00
/* Chip select logic is inverted from spi_set_cs */
if ( ! enable )
2008-08-06 00:01:09 +04:00
orion_spi_setbits ( orion_spi , ORION_SPI_IF_CTRL_REG , 0x1 ) ;
else
orion_spi_clrbits ( orion_spi , ORION_SPI_IF_CTRL_REG , 0x1 ) ;
}
static inline int orion_spi_wait_till_ready ( struct orion_spi * orion_spi )
{
int i ;
for ( i = 0 ; i < ORION_SPI_WAIT_RDY_MAX_LOOP ; i + + ) {
if ( readl ( spi_reg ( orion_spi , ORION_SPI_INT_CAUSE_REG ) ) )
return 1 ;
2014-09-02 06:51:39 +04:00
udelay ( 1 ) ;
2008-08-06 00:01:09 +04:00
}
return - 1 ;
}
static inline int
orion_spi_write_read_8bit ( struct spi_device * spi ,
const u8 * * tx_buf , u8 * * rx_buf )
{
void __iomem * tx_reg , * rx_reg , * int_reg ;
struct orion_spi * orion_spi ;
orion_spi = spi_master_get_devdata ( spi - > master ) ;
tx_reg = spi_reg ( orion_spi , ORION_SPI_DATA_OUT_REG ) ;
rx_reg = spi_reg ( orion_spi , ORION_SPI_DATA_IN_REG ) ;
int_reg = spi_reg ( orion_spi , ORION_SPI_INT_CAUSE_REG ) ;
/* clear the interrupt cause register */
writel ( 0x0 , int_reg ) ;
if ( tx_buf & & * tx_buf )
writel ( * ( * tx_buf ) + + , tx_reg ) ;
else
writel ( 0 , tx_reg ) ;
if ( orion_spi_wait_till_ready ( orion_spi ) < 0 ) {
dev_err ( & spi - > dev , " TXS timed out \n " ) ;
return - 1 ;
}
if ( rx_buf & & * rx_buf )
* ( * rx_buf ) + + = readl ( rx_reg ) ;
return 1 ;
}
static inline int
orion_spi_write_read_16bit ( struct spi_device * spi ,
const u16 * * tx_buf , u16 * * rx_buf )
{
void __iomem * tx_reg , * rx_reg , * int_reg ;
struct orion_spi * orion_spi ;
orion_spi = spi_master_get_devdata ( spi - > master ) ;
tx_reg = spi_reg ( orion_spi , ORION_SPI_DATA_OUT_REG ) ;
rx_reg = spi_reg ( orion_spi , ORION_SPI_DATA_IN_REG ) ;
int_reg = spi_reg ( orion_spi , ORION_SPI_INT_CAUSE_REG ) ;
/* clear the interrupt cause register */
writel ( 0x0 , int_reg ) ;
if ( tx_buf & & * tx_buf )
writel ( __cpu_to_le16 ( get_unaligned ( ( * tx_buf ) + + ) ) , tx_reg ) ;
else
writel ( 0 , tx_reg ) ;
if ( orion_spi_wait_till_ready ( orion_spi ) < 0 ) {
dev_err ( & spi - > dev , " TXS timed out \n " ) ;
return - 1 ;
}
if ( rx_buf & & * rx_buf )
put_unaligned ( __le16_to_cpu ( readl ( rx_reg ) ) , ( * rx_buf ) + + ) ;
return 1 ;
}
static unsigned int
orion_spi_write_read ( struct spi_device * spi , struct spi_transfer * xfer )
{
unsigned int count ;
int word_len ;
word_len = spi - > bits_per_word ;
count = xfer - > len ;
if ( word_len = = 8 ) {
const u8 * tx = xfer - > tx_buf ;
u8 * rx = xfer - > rx_buf ;
do {
if ( orion_spi_write_read_8bit ( spi , & tx , & rx ) < 0 )
goto out ;
count - - ;
} while ( count ) ;
} else if ( word_len = = 16 ) {
const u16 * tx = xfer - > tx_buf ;
u16 * rx = xfer - > rx_buf ;
do {
if ( orion_spi_write_read_16bit ( spi , & tx , & rx ) < 0 )
goto out ;
count - = 2 ;
} while ( count ) ;
}
out :
return xfer - > len - count ;
}
2015-01-12 06:13:59 +03:00
static int orion_spi_transfer_one ( struct spi_master * master ,
struct spi_device * spi ,
struct spi_transfer * t )
2008-08-06 00:01:09 +04:00
{
2012-07-23 15:16:55 +04:00
int status = 0 ;
2008-08-06 00:01:09 +04:00
2015-01-12 06:13:59 +03:00
status = orion_spi_setup_transfer ( spi , t ) ;
2012-07-23 15:16:55 +04:00
if ( status < 0 )
2015-01-12 06:13:59 +03:00
return status ;
2008-08-06 00:01:09 +04:00
2015-01-12 06:13:59 +03:00
if ( t - > len )
orion_spi_write_read ( spi , t ) ;
2012-07-23 15:16:55 +04:00
2015-01-12 06:13:59 +03:00
return status ;
}
2012-07-23 15:16:55 +04:00
2015-01-12 06:13:59 +03:00
static int orion_spi_setup ( struct spi_device * spi )
{
return orion_spi_setup_transfer ( spi , NULL ) ;
2008-08-06 00:01:09 +04:00
}
2013-02-05 17:27:35 +04:00
static int orion_spi_reset ( struct orion_spi * orion_spi )
2008-08-06 00:01:09 +04:00
{
/* Verify that the CS is deasserted */
2015-01-12 06:13:59 +03:00
orion_spi_clrbits ( orion_spi , ORION_SPI_IF_CTRL_REG , 0x1 ) ;
2008-08-06 00:01:09 +04:00
return 0 ;
}
2014-09-28 17:24:04 +04:00
static const struct orion_spi_dev orion_spi_dev_data = {
. typ = ORION_SPI ,
. min_divisor = 4 ,
. max_divisor = 30 ,
. prescale_mask = ORION_SPI_CLK_PRESCALE_MASK ,
} ;
static const struct orion_spi_dev armada_spi_dev_data = {
. typ = ARMADA_SPI ,
. min_divisor = 1 ,
. max_divisor = 1920 ,
. prescale_mask = ARMADA_SPI_CLK_PRESCALE_MASK ,
} ;
static const struct of_device_id orion_spi_of_match_table [ ] = {
{ . compatible = " marvell,orion-spi " , . data = & orion_spi_dev_data , } ,
{ . compatible = " marvell,armada-370-spi " , . data = & armada_spi_dev_data , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , orion_spi_of_match_table ) ;
2013-02-05 17:27:35 +04:00
static int orion_spi_probe ( struct platform_device * pdev )
2008-08-06 00:01:09 +04:00
{
2014-09-28 17:24:04 +04:00
const struct of_device_id * of_id ;
const struct orion_spi_dev * devdata ;
2008-08-06 00:01:09 +04:00
struct spi_master * master ;
struct orion_spi * spi ;
struct resource * r ;
2012-04-06 19:17:26 +04:00
unsigned long tclk_hz ;
2008-08-06 00:01:09 +04:00
int status = 0 ;
2013-10-14 05:35:08 +04:00
master = spi_alloc_master ( & pdev - > dev , sizeof ( * spi ) ) ;
2008-08-06 00:01:09 +04:00
if ( master = = NULL ) {
dev_dbg ( & pdev - > dev , " master allocation failed \n " ) ;
return - ENOMEM ;
}
if ( pdev - > id ! = - 1 )
master - > bus_num = pdev - > id ;
2012-07-23 14:08:09 +04:00
if ( pdev - > dev . of_node ) {
2014-07-28 01:53:19 +04:00
u32 cell_index ;
2014-09-02 06:51:39 +04:00
2014-07-28 01:53:19 +04:00
if ( ! of_property_read_u32 ( pdev - > dev . of_node , " cell-index " ,
& cell_index ) )
master - > bus_num = cell_index ;
2012-07-23 14:08:09 +04:00
}
2008-08-06 00:01:09 +04:00
2009-06-18 03:26:04 +04:00
/* we support only mode 0, and no options */
2012-11-21 23:23:35 +04:00
master - > mode_bits = SPI_CPHA | SPI_CPOL ;
2015-01-12 06:13:59 +03:00
master - > set_cs = orion_spi_set_cs ;
master - > transfer_one = orion_spi_transfer_one ;
2008-08-06 00:01:09 +04:00
master - > num_chipselect = ORION_NUM_CHIPSELECTS ;
2015-01-12 06:13:59 +03:00
master - > setup = orion_spi_setup ;
2014-02-11 16:51:36 +04:00
master - > bits_per_word_mask = SPI_BPW_MASK ( 8 ) | SPI_BPW_MASK ( 16 ) ;
2014-06-21 15:22:37 +04:00
master - > auto_runtime_pm = true ;
2008-08-06 00:01:09 +04:00
2013-05-23 14:20:40 +04:00
platform_set_drvdata ( pdev , master ) ;
2008-08-06 00:01:09 +04:00
spi = spi_master_get_devdata ( master ) ;
spi - > master = master ;
2014-09-28 17:24:04 +04:00
of_id = of_match_device ( orion_spi_of_match_table , & pdev - > dev ) ;
2014-10-21 09:57:48 +04:00
devdata = ( of_id ) ? of_id - > data : & orion_spi_dev_data ;
2014-09-28 17:24:04 +04:00
spi - > devdata = devdata ;
2013-12-09 14:21:22 +04:00
spi - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
2012-04-06 19:17:26 +04:00
if ( IS_ERR ( spi - > clk ) ) {
status = PTR_ERR ( spi - > clk ) ;
goto out ;
}
2014-06-21 14:32:23 +04:00
status = clk_prepare_enable ( spi - > clk ) ;
if ( status )
goto out ;
2012-04-06 19:17:26 +04:00
tclk_hz = clk_get_rate ( spi - > clk ) ;
2014-09-28 17:24:04 +04:00
master - > max_speed_hz = DIV_ROUND_UP ( tclk_hz , devdata - > min_divisor ) ;
master - > min_speed_hz = DIV_ROUND_UP ( tclk_hz , devdata - > max_divisor ) ;
2008-08-06 00:01:09 +04:00
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-07-28 17:38:06 +04:00
spi - > base = devm_ioremap_resource ( & pdev - > dev , r ) ;
if ( IS_ERR ( spi - > base ) ) {
status = PTR_ERR ( spi - > base ) ;
2012-04-06 19:17:26 +04:00
goto out_rel_clk ;
2008-08-06 00:01:09 +04:00
}
2014-06-21 15:22:37 +04:00
pm_runtime_set_active ( & pdev - > dev ) ;
pm_runtime_use_autosuspend ( & pdev - > dev ) ;
pm_runtime_set_autosuspend_delay ( & pdev - > dev , SPI_AUTOSUSPEND_TIMEOUT ) ;
pm_runtime_enable ( & pdev - > dev ) ;
2014-07-20 18:03:14 +04:00
status = orion_spi_reset ( spi ) ;
if ( status < 0 )
2014-06-21 15:22:37 +04:00
goto out_rel_pm ;
pm_runtime_mark_last_busy ( & pdev - > dev ) ;
pm_runtime_put_autosuspend ( & pdev - > dev ) ;
2008-08-06 00:01:09 +04:00
2012-07-23 14:08:09 +04:00
master - > dev . of_node = pdev - > dev . of_node ;
2014-06-21 15:22:37 +04:00
status = spi_register_master ( master ) ;
2008-08-06 00:01:09 +04:00
if ( status < 0 )
2014-06-21 15:22:37 +04:00
goto out_rel_pm ;
2008-08-06 00:01:09 +04:00
return status ;
2014-06-21 15:22:37 +04:00
out_rel_pm :
pm_runtime_disable ( & pdev - > dev ) ;
2012-04-06 19:17:26 +04:00
out_rel_clk :
clk_disable_unprepare ( spi - > clk ) ;
2008-08-06 00:01:09 +04:00
out :
spi_master_put ( master ) ;
return status ;
}
2013-02-05 17:27:35 +04:00
static int orion_spi_remove ( struct platform_device * pdev )
2008-08-06 00:01:09 +04:00
{
2014-06-21 15:22:37 +04:00
struct spi_master * master = platform_get_drvdata ( pdev ) ;
struct orion_spi * spi = spi_master_get_devdata ( master ) ;
2008-08-06 00:01:09 +04:00
2014-06-21 15:22:37 +04:00
pm_runtime_get_sync ( & pdev - > dev ) ;
2012-04-06 19:17:26 +04:00
clk_disable_unprepare ( spi - > clk ) ;
2014-06-21 15:22:37 +04:00
spi_unregister_master ( master ) ;
pm_runtime_disable ( & pdev - > dev ) ;
2008-08-06 00:01:09 +04:00
return 0 ;
}
MODULE_ALIAS ( " platform: " DRIVER_NAME ) ;
2014-12-13 02:41:15 +03:00
# ifdef CONFIG_PM
2014-06-21 15:22:37 +04:00
static int orion_spi_runtime_suspend ( struct device * dev )
{
struct spi_master * master = dev_get_drvdata ( dev ) ;
struct orion_spi * spi = spi_master_get_devdata ( master ) ;
clk_disable_unprepare ( spi - > clk ) ;
return 0 ;
}
static int orion_spi_runtime_resume ( struct device * dev )
{
struct spi_master * master = dev_get_drvdata ( dev ) ;
struct orion_spi * spi = spi_master_get_devdata ( master ) ;
return clk_prepare_enable ( spi - > clk ) ;
}
# endif
static const struct dev_pm_ops orion_spi_pm_ops = {
SET_RUNTIME_PM_OPS ( orion_spi_runtime_suspend ,
orion_spi_runtime_resume ,
NULL )
} ;
2008-08-06 00:01:09 +04:00
static struct platform_driver orion_spi_driver = {
. driver = {
. name = DRIVER_NAME ,
2014-06-21 15:22:37 +04:00
. pm = & orion_spi_pm_ops ,
2012-07-23 14:08:09 +04:00
. of_match_table = of_match_ptr ( orion_spi_of_match_table ) ,
2008-08-06 00:01:09 +04:00
} ,
2013-02-04 16:26:26 +04:00
. probe = orion_spi_probe ,
2013-02-05 17:27:35 +04:00
. remove = orion_spi_remove ,
2008-08-06 00:01:09 +04:00
} ;
2013-02-04 16:26:26 +04:00
module_platform_driver ( orion_spi_driver ) ;
2008-08-06 00:01:09 +04:00
MODULE_DESCRIPTION ( " Orion SPI driver " ) ;
MODULE_AUTHOR ( " Shadi Ammouri <shadi@marvell.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;