mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-18 17:54:13 +08:00
d1e44d9ce8
Shrink the runtime footprint of various SPI drivers: - Move the probe() routine into the init section where practical, using platform_driver_probe() to make that safe. This often saves around 1KB. Using platform_driver_probe() can also be a correctness fix, if the probe routine is already marked __init but the driver struct keeps a dangling pointer to it after init section removal. - Likewise move remove() routines into the exit sections. These changes would be inappropriate iff the platform devices were actually hotpluggable (e.g. they're found on optional addon cards, or in an FPGA that's dynamically reprogrammed). In these cases, that's not the situation; it's an SOC controller and the only device is initialized before these drivers. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
554 lines
14 KiB
C
554 lines
14 KiB
C
/*
|
|
* MPC83xx SPI controller driver.
|
|
*
|
|
* Maintainer: Kumar Gala
|
|
*
|
|
* Copyright (C) 2006 Polycom, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/device.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/spi/spi_bitbang.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/fsl_devices.h>
|
|
|
|
#include <asm/irq.h>
|
|
#include <asm/io.h>
|
|
|
|
/* SPI Controller registers */
|
|
struct mpc83xx_spi_reg {
|
|
u8 res1[0x20];
|
|
__be32 mode;
|
|
__be32 event;
|
|
__be32 mask;
|
|
__be32 command;
|
|
__be32 transmit;
|
|
__be32 receive;
|
|
};
|
|
|
|
/* SPI Controller mode register definitions */
|
|
#define SPMODE_LOOP (1 << 30)
|
|
#define SPMODE_CI_INACTIVEHIGH (1 << 29)
|
|
#define SPMODE_CP_BEGIN_EDGECLK (1 << 28)
|
|
#define SPMODE_DIV16 (1 << 27)
|
|
#define SPMODE_REV (1 << 26)
|
|
#define SPMODE_MS (1 << 25)
|
|
#define SPMODE_ENABLE (1 << 24)
|
|
#define SPMODE_LEN(x) ((x) << 20)
|
|
#define SPMODE_PM(x) ((x) << 16)
|
|
#define SPMODE_OP (1 << 14)
|
|
|
|
/*
|
|
* Default for SPI Mode:
|
|
* SPI MODE 0 (inactive low, phase middle, MSB, 8-bit length, slow clk
|
|
*/
|
|
#define SPMODE_INIT_VAL (SPMODE_CI_INACTIVEHIGH | SPMODE_DIV16 | SPMODE_REV | \
|
|
SPMODE_MS | SPMODE_LEN(7) | SPMODE_PM(0xf))
|
|
|
|
/* SPIE register values */
|
|
#define SPIE_NE 0x00000200 /* Not empty */
|
|
#define SPIE_NF 0x00000100 /* Not full */
|
|
|
|
/* SPIM register values */
|
|
#define SPIM_NE 0x00000200 /* Not empty */
|
|
#define SPIM_NF 0x00000100 /* Not full */
|
|
|
|
/* SPI Controller driver's private data. */
|
|
struct mpc83xx_spi {
|
|
/* bitbang has to be first */
|
|
struct spi_bitbang bitbang;
|
|
struct completion done;
|
|
|
|
struct mpc83xx_spi_reg __iomem *base;
|
|
|
|
/* rx & tx bufs from the spi_transfer */
|
|
const void *tx;
|
|
void *rx;
|
|
|
|
/* functions to deal with different sized buffers */
|
|
void (*get_rx) (u32 rx_data, struct mpc83xx_spi *);
|
|
u32(*get_tx) (struct mpc83xx_spi *);
|
|
|
|
unsigned int count;
|
|
u32 irq;
|
|
|
|
unsigned nsecs; /* (clock cycle time)/2 */
|
|
|
|
u32 spibrg; /* SPIBRG input clock */
|
|
u32 rx_shift; /* RX data reg shift when in qe mode */
|
|
u32 tx_shift; /* TX data reg shift when in qe mode */
|
|
|
|
bool qe_mode;
|
|
|
|
void (*activate_cs) (u8 cs, u8 polarity);
|
|
void (*deactivate_cs) (u8 cs, u8 polarity);
|
|
};
|
|
|
|
static inline void mpc83xx_spi_write_reg(__be32 __iomem * reg, u32 val)
|
|
{
|
|
out_be32(reg, val);
|
|
}
|
|
|
|
static inline u32 mpc83xx_spi_read_reg(__be32 __iomem * reg)
|
|
{
|
|
return in_be32(reg);
|
|
}
|
|
|
|
#define MPC83XX_SPI_RX_BUF(type) \
|
|
void mpc83xx_spi_rx_buf_##type(u32 data, struct mpc83xx_spi *mpc83xx_spi) \
|
|
{ \
|
|
type * rx = mpc83xx_spi->rx; \
|
|
*rx++ = (type)(data >> mpc83xx_spi->rx_shift); \
|
|
mpc83xx_spi->rx = rx; \
|
|
}
|
|
|
|
#define MPC83XX_SPI_TX_BUF(type) \
|
|
u32 mpc83xx_spi_tx_buf_##type(struct mpc83xx_spi *mpc83xx_spi) \
|
|
{ \
|
|
u32 data; \
|
|
const type * tx = mpc83xx_spi->tx; \
|
|
if (!tx) \
|
|
return 0; \
|
|
data = *tx++ << mpc83xx_spi->tx_shift; \
|
|
mpc83xx_spi->tx = tx; \
|
|
return data; \
|
|
}
|
|
|
|
MPC83XX_SPI_RX_BUF(u8)
|
|
MPC83XX_SPI_RX_BUF(u16)
|
|
MPC83XX_SPI_RX_BUF(u32)
|
|
MPC83XX_SPI_TX_BUF(u8)
|
|
MPC83XX_SPI_TX_BUF(u16)
|
|
MPC83XX_SPI_TX_BUF(u32)
|
|
|
|
static void mpc83xx_spi_chipselect(struct spi_device *spi, int value)
|
|
{
|
|
struct mpc83xx_spi *mpc83xx_spi;
|
|
u8 pol = spi->mode & SPI_CS_HIGH ? 1 : 0;
|
|
|
|
mpc83xx_spi = spi_master_get_devdata(spi->master);
|
|
|
|
if (value == BITBANG_CS_INACTIVE) {
|
|
if (mpc83xx_spi->deactivate_cs)
|
|
mpc83xx_spi->deactivate_cs(spi->chip_select, pol);
|
|
}
|
|
|
|
if (value == BITBANG_CS_ACTIVE) {
|
|
u32 regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
|
|
u32 len = spi->bits_per_word;
|
|
u8 pm;
|
|
|
|
if (len == 32)
|
|
len = 0;
|
|
else
|
|
len = len - 1;
|
|
|
|
/* mask out bits we are going to set */
|
|
regval &= ~(SPMODE_CP_BEGIN_EDGECLK | SPMODE_CI_INACTIVEHIGH
|
|
| SPMODE_LEN(0xF) | SPMODE_DIV16
|
|
| SPMODE_PM(0xF) | SPMODE_REV | SPMODE_LOOP);
|
|
|
|
if (spi->mode & SPI_CPHA)
|
|
regval |= SPMODE_CP_BEGIN_EDGECLK;
|
|
if (spi->mode & SPI_CPOL)
|
|
regval |= SPMODE_CI_INACTIVEHIGH;
|
|
if (!(spi->mode & SPI_LSB_FIRST))
|
|
regval |= SPMODE_REV;
|
|
if (spi->mode & SPI_LOOP)
|
|
regval |= SPMODE_LOOP;
|
|
|
|
regval |= SPMODE_LEN(len);
|
|
|
|
if ((mpc83xx_spi->spibrg / spi->max_speed_hz) >= 64) {
|
|
pm = mpc83xx_spi->spibrg / (spi->max_speed_hz * 64) - 1;
|
|
if (pm > 0x0f) {
|
|
dev_err(&spi->dev, "Requested speed is too "
|
|
"low: %d Hz. Will use %d Hz instead.\n",
|
|
spi->max_speed_hz,
|
|
mpc83xx_spi->spibrg / 1024);
|
|
pm = 0x0f;
|
|
}
|
|
regval |= SPMODE_PM(pm) | SPMODE_DIV16;
|
|
} else {
|
|
pm = mpc83xx_spi->spibrg / (spi->max_speed_hz * 4);
|
|
if (pm)
|
|
pm--;
|
|
regval |= SPMODE_PM(pm);
|
|
}
|
|
|
|
/* Turn off SPI unit prior changing mode */
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0);
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
|
|
if (mpc83xx_spi->activate_cs)
|
|
mpc83xx_spi->activate_cs(spi->chip_select, pol);
|
|
}
|
|
}
|
|
|
|
static
|
|
int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
|
|
{
|
|
struct mpc83xx_spi *mpc83xx_spi;
|
|
u32 regval;
|
|
u8 bits_per_word;
|
|
u32 hz;
|
|
|
|
mpc83xx_spi = spi_master_get_devdata(spi->master);
|
|
|
|
if (t) {
|
|
bits_per_word = t->bits_per_word;
|
|
hz = t->speed_hz;
|
|
} else {
|
|
bits_per_word = 0;
|
|
hz = 0;
|
|
}
|
|
|
|
/* spi_transfer level calls that work per-word */
|
|
if (!bits_per_word)
|
|
bits_per_word = spi->bits_per_word;
|
|
|
|
/* Make sure its a bit width we support [4..16, 32] */
|
|
if ((bits_per_word < 4)
|
|
|| ((bits_per_word > 16) && (bits_per_word != 32)))
|
|
return -EINVAL;
|
|
|
|
mpc83xx_spi->rx_shift = 0;
|
|
mpc83xx_spi->tx_shift = 0;
|
|
if (bits_per_word <= 8) {
|
|
mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
|
|
mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
|
|
if (mpc83xx_spi->qe_mode) {
|
|
mpc83xx_spi->rx_shift = 16;
|
|
mpc83xx_spi->tx_shift = 24;
|
|
}
|
|
} else if (bits_per_word <= 16) {
|
|
mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u16;
|
|
mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u16;
|
|
if (mpc83xx_spi->qe_mode) {
|
|
mpc83xx_spi->rx_shift = 16;
|
|
mpc83xx_spi->tx_shift = 16;
|
|
}
|
|
} else if (bits_per_word <= 32) {
|
|
mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u32;
|
|
mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u32;
|
|
} else
|
|
return -EINVAL;
|
|
|
|
if (mpc83xx_spi->qe_mode && spi->mode & SPI_LSB_FIRST) {
|
|
mpc83xx_spi->tx_shift = 0;
|
|
if (bits_per_word <= 8)
|
|
mpc83xx_spi->rx_shift = 8;
|
|
else
|
|
mpc83xx_spi->rx_shift = 0;
|
|
}
|
|
|
|
/* nsecs = (clock period)/2 */
|
|
if (!hz)
|
|
hz = spi->max_speed_hz;
|
|
mpc83xx_spi->nsecs = (1000000000 / 2) / hz;
|
|
if (mpc83xx_spi->nsecs > MAX_UDELAY_MS * 1000)
|
|
return -EINVAL;
|
|
|
|
if (bits_per_word == 32)
|
|
bits_per_word = 0;
|
|
else
|
|
bits_per_word = bits_per_word - 1;
|
|
|
|
regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
|
|
|
|
/* mask out bits we are going to set */
|
|
regval &= ~(SPMODE_LEN(0xF) | SPMODE_REV);
|
|
regval |= SPMODE_LEN(bits_per_word);
|
|
if (!(spi->mode & SPI_LSB_FIRST))
|
|
regval |= SPMODE_REV;
|
|
|
|
/* Turn off SPI unit prior changing mode */
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0);
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* the spi->mode bits understood by this driver: */
|
|
#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH \
|
|
| SPI_LSB_FIRST | SPI_LOOP)
|
|
|
|
static int mpc83xx_spi_setup(struct spi_device *spi)
|
|
{
|
|
struct spi_bitbang *bitbang;
|
|
struct mpc83xx_spi *mpc83xx_spi;
|
|
int retval;
|
|
|
|
if (spi->mode & ~MODEBITS) {
|
|
dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n",
|
|
spi->mode & ~MODEBITS);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!spi->max_speed_hz)
|
|
return -EINVAL;
|
|
|
|
bitbang = spi_master_get_devdata(spi->master);
|
|
mpc83xx_spi = spi_master_get_devdata(spi->master);
|
|
|
|
if (!spi->bits_per_word)
|
|
spi->bits_per_word = 8;
|
|
|
|
retval = mpc83xx_spi_setup_transfer(spi, NULL);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec\n",
|
|
__FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA),
|
|
spi->bits_per_word, 2 * mpc83xx_spi->nsecs);
|
|
|
|
/* NOTE we _need_ to call chipselect() early, ideally with adapter
|
|
* setup, unless the hardware defaults cooperate to avoid confusion
|
|
* between normal (active low) and inverted chipselects.
|
|
*/
|
|
|
|
/* deselect chip (low or high) */
|
|
spin_lock(&bitbang->lock);
|
|
if (!bitbang->busy) {
|
|
bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
|
|
ndelay(mpc83xx_spi->nsecs);
|
|
}
|
|
spin_unlock(&bitbang->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mpc83xx_spi_bufs(struct spi_device *spi, struct spi_transfer *t)
|
|
{
|
|
struct mpc83xx_spi *mpc83xx_spi;
|
|
u32 word;
|
|
|
|
mpc83xx_spi = spi_master_get_devdata(spi->master);
|
|
|
|
mpc83xx_spi->tx = t->tx_buf;
|
|
mpc83xx_spi->rx = t->rx_buf;
|
|
mpc83xx_spi->count = t->len;
|
|
INIT_COMPLETION(mpc83xx_spi->done);
|
|
|
|
/* enable rx ints */
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, SPIM_NE);
|
|
|
|
/* transmit word */
|
|
word = mpc83xx_spi->get_tx(mpc83xx_spi);
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit, word);
|
|
|
|
wait_for_completion(&mpc83xx_spi->done);
|
|
|
|
/* disable rx ints */
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
|
|
|
|
return t->len - mpc83xx_spi->count;
|
|
}
|
|
|
|
irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data)
|
|
{
|
|
struct mpc83xx_spi *mpc83xx_spi = context_data;
|
|
u32 event;
|
|
irqreturn_t ret = IRQ_NONE;
|
|
|
|
/* Get interrupt events(tx/rx) */
|
|
event = mpc83xx_spi_read_reg(&mpc83xx_spi->base->event);
|
|
|
|
/* We need handle RX first */
|
|
if (event & SPIE_NE) {
|
|
u32 rx_data = mpc83xx_spi_read_reg(&mpc83xx_spi->base->receive);
|
|
|
|
if (mpc83xx_spi->rx)
|
|
mpc83xx_spi->get_rx(rx_data, mpc83xx_spi);
|
|
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
|
|
if ((event & SPIE_NF) == 0)
|
|
/* spin until TX is done */
|
|
while (((event =
|
|
mpc83xx_spi_read_reg(&mpc83xx_spi->base->event)) &
|
|
SPIE_NF) == 0)
|
|
cpu_relax();
|
|
|
|
mpc83xx_spi->count -= 1;
|
|
if (mpc83xx_spi->count) {
|
|
u32 word = mpc83xx_spi->get_tx(mpc83xx_spi);
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit, word);
|
|
} else {
|
|
complete(&mpc83xx_spi->done);
|
|
}
|
|
|
|
/* Clear the events */
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, event);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __init mpc83xx_spi_probe(struct platform_device *dev)
|
|
{
|
|
struct spi_master *master;
|
|
struct mpc83xx_spi *mpc83xx_spi;
|
|
struct fsl_spi_platform_data *pdata;
|
|
struct resource *r;
|
|
u32 regval;
|
|
int ret = 0;
|
|
|
|
/* Get resources(memory, IRQ) associated with the device */
|
|
master = spi_alloc_master(&dev->dev, sizeof(struct mpc83xx_spi));
|
|
|
|
if (master == NULL) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
platform_set_drvdata(dev, master);
|
|
pdata = dev->dev.platform_data;
|
|
|
|
if (pdata == NULL) {
|
|
ret = -ENODEV;
|
|
goto free_master;
|
|
}
|
|
|
|
r = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
|
if (r == NULL) {
|
|
ret = -ENODEV;
|
|
goto free_master;
|
|
}
|
|
mpc83xx_spi = spi_master_get_devdata(master);
|
|
mpc83xx_spi->bitbang.master = spi_master_get(master);
|
|
mpc83xx_spi->bitbang.chipselect = mpc83xx_spi_chipselect;
|
|
mpc83xx_spi->bitbang.setup_transfer = mpc83xx_spi_setup_transfer;
|
|
mpc83xx_spi->bitbang.txrx_bufs = mpc83xx_spi_bufs;
|
|
mpc83xx_spi->activate_cs = pdata->activate_cs;
|
|
mpc83xx_spi->deactivate_cs = pdata->deactivate_cs;
|
|
mpc83xx_spi->qe_mode = pdata->qe_mode;
|
|
mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
|
|
mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
|
|
|
|
if (mpc83xx_spi->qe_mode)
|
|
mpc83xx_spi->spibrg = pdata->sysclk / 2;
|
|
else
|
|
mpc83xx_spi->spibrg = pdata->sysclk;
|
|
|
|
mpc83xx_spi->rx_shift = 0;
|
|
mpc83xx_spi->tx_shift = 0;
|
|
if (mpc83xx_spi->qe_mode) {
|
|
mpc83xx_spi->rx_shift = 16;
|
|
mpc83xx_spi->tx_shift = 24;
|
|
}
|
|
|
|
mpc83xx_spi->bitbang.master->setup = mpc83xx_spi_setup;
|
|
init_completion(&mpc83xx_spi->done);
|
|
|
|
mpc83xx_spi->base = ioremap(r->start, r->end - r->start + 1);
|
|
if (mpc83xx_spi->base == NULL) {
|
|
ret = -ENOMEM;
|
|
goto put_master;
|
|
}
|
|
|
|
mpc83xx_spi->irq = platform_get_irq(dev, 0);
|
|
|
|
if (mpc83xx_spi->irq < 0) {
|
|
ret = -ENXIO;
|
|
goto unmap_io;
|
|
}
|
|
|
|
/* Register for SPI Interrupt */
|
|
ret = request_irq(mpc83xx_spi->irq, mpc83xx_spi_irq,
|
|
0, "mpc83xx_spi", mpc83xx_spi);
|
|
|
|
if (ret != 0)
|
|
goto unmap_io;
|
|
|
|
master->bus_num = pdata->bus_num;
|
|
master->num_chipselect = pdata->max_chipselect;
|
|
|
|
/* SPI controller initializations */
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0);
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->command, 0);
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, 0xffffffff);
|
|
|
|
/* Enable SPI interface */
|
|
regval = pdata->initial_spmode | SPMODE_INIT_VAL | SPMODE_ENABLE;
|
|
if (pdata->qe_mode)
|
|
regval |= SPMODE_OP;
|
|
|
|
mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
|
|
|
|
ret = spi_bitbang_start(&mpc83xx_spi->bitbang);
|
|
|
|
if (ret != 0)
|
|
goto free_irq;
|
|
|
|
printk(KERN_INFO
|
|
"%s: MPC83xx SPI Controller driver at 0x%p (irq = %d)\n",
|
|
dev->dev.bus_id, mpc83xx_spi->base, mpc83xx_spi->irq);
|
|
|
|
return ret;
|
|
|
|
free_irq:
|
|
free_irq(mpc83xx_spi->irq, mpc83xx_spi);
|
|
unmap_io:
|
|
iounmap(mpc83xx_spi->base);
|
|
put_master:
|
|
spi_master_put(master);
|
|
free_master:
|
|
kfree(master);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static int __exit mpc83xx_spi_remove(struct platform_device *dev)
|
|
{
|
|
struct mpc83xx_spi *mpc83xx_spi;
|
|
struct spi_master *master;
|
|
|
|
master = platform_get_drvdata(dev);
|
|
mpc83xx_spi = spi_master_get_devdata(master);
|
|
|
|
spi_bitbang_stop(&mpc83xx_spi->bitbang);
|
|
free_irq(mpc83xx_spi->irq, mpc83xx_spi);
|
|
iounmap(mpc83xx_spi->base);
|
|
spi_master_put(mpc83xx_spi->bitbang.master);
|
|
|
|
return 0;
|
|
}
|
|
|
|
MODULE_ALIAS("mpc83xx_spi"); /* for platform bus hotplug */
|
|
static struct platform_driver mpc83xx_spi_driver = {
|
|
.remove = __exit_p(mpc83xx_spi_remove),
|
|
.driver = {
|
|
.name = "mpc83xx_spi",
|
|
},
|
|
};
|
|
|
|
static int __init mpc83xx_spi_init(void)
|
|
{
|
|
return platform_driver_probe(&mpc83xx_spi_driver, mpc83xx_spi_probe);
|
|
}
|
|
|
|
static void __exit mpc83xx_spi_exit(void)
|
|
{
|
|
platform_driver_unregister(&mpc83xx_spi_driver);
|
|
}
|
|
|
|
module_init(mpc83xx_spi_init);
|
|
module_exit(mpc83xx_spi_exit);
|
|
|
|
MODULE_AUTHOR("Kumar Gala");
|
|
MODULE_DESCRIPTION("Simple MPC83xx SPI Driver");
|
|
MODULE_LICENSE("GPL");
|