564 lines
13 KiB
C
564 lines
13 KiB
C
|
/*
|
||
|
* ------------------------------------------------------------------------
|
||
|
* spi_stm.c SPI/SSC driver for STMicroelectronics platforms
|
||
|
* ------------------------------------------------------------------------
|
||
|
*
|
||
|
* Copyright (c) 2008 STMicroelectronics Limited
|
||
|
* Author: Angus Clark <Angus.Clark@st.com>
|
||
|
*
|
||
|
* May be copied or modified under the terms of the GNU General Public
|
||
|
* License Version 2.0 only. See linux/COPYING for more information.
|
||
|
*
|
||
|
* ------------------------------------------------------------------------
|
||
|
* Changelog:
|
||
|
* 2008-01-24 (angus.clark@st.com)
|
||
|
* - Initial version
|
||
|
* 2008-08-28 (angus.clark@st.com)
|
||
|
* - Updates to fit with changes to 'ssc_pio_t'
|
||
|
* - SSC accesses now all 32-bit, for compatibility with 7141 Comms block
|
||
|
* - Updated to handle 7141 PIO ALT configuration
|
||
|
* - Support for user-defined, per-bus, chip_select function. Specified
|
||
|
* in board setup
|
||
|
* - Bug fix for rx_bytes_pending updates
|
||
|
*
|
||
|
* ------------------------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/wait.h>
|
||
|
#include <linux/workqueue.h>
|
||
|
#include <linux/completion.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/param.h>
|
||
|
#include <linux/gpio.h>
|
||
|
#include <linux/spi/spi.h>
|
||
|
#include <linux/spi/spi_bitbang.h>
|
||
|
#include <linux/stm/platform.h>
|
||
|
#include <linux/stm/ssc.h>
|
||
|
|
||
|
#define NAME "spi-stm"
|
||
|
|
||
|
struct spi_stm {
|
||
|
/* SSC SPI Controller */
|
||
|
struct spi_bitbang bitbang;
|
||
|
void __iomem *base;
|
||
|
struct clk *clk;
|
||
|
struct platform_device *pdev;
|
||
|
|
||
|
/* Resources */
|
||
|
struct resource r_mem;
|
||
|
struct resource r_irq;
|
||
|
struct stm_pad_state *pad_state;
|
||
|
|
||
|
/* SSC SPI current transaction */
|
||
|
const u8 *tx_ptr;
|
||
|
u8 *rx_ptr;
|
||
|
u16 bits_per_word;
|
||
|
u16 bytes_per_word;
|
||
|
unsigned int baud;
|
||
|
unsigned int words_remaining;
|
||
|
struct completion done;
|
||
|
};
|
||
|
|
||
|
static void spi_stm_gpio_chipselect(struct spi_device *spi, int value)
|
||
|
{
|
||
|
unsigned int out;
|
||
|
|
||
|
if (spi->chip_select == (typeof(spi->chip_select))(STM_GPIO_INVALID))
|
||
|
return;
|
||
|
|
||
|
if (!spi->controller_data) {
|
||
|
if (gpio_request(spi->chip_select, "spi_stm cs")) {
|
||
|
dev_err(&spi->dev, "failed to allocate CS pin\n");
|
||
|
return;
|
||
|
}
|
||
|
spi->controller_data = (void *)1;
|
||
|
gpio_direction_output(spi->chip_select,
|
||
|
spi->mode & SPI_CS_HIGH);
|
||
|
}
|
||
|
|
||
|
if (value == BITBANG_CS_ACTIVE)
|
||
|
out = spi->mode & SPI_CS_HIGH ? 1 : 0;
|
||
|
else
|
||
|
out = spi->mode & SPI_CS_HIGH ? 0 : 1;
|
||
|
|
||
|
gpio_set_value(spi->chip_select, out);
|
||
|
|
||
|
dev_dbg(&spi->dev, "%s PIO%d[%d] -> %d \n",
|
||
|
value == BITBANG_CS_ACTIVE ? "select" : "deselect",
|
||
|
stm_gpio_port(spi->chip_select),
|
||
|
stm_gpio_pin(spi->chip_select), out);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static int spi_stm_setup_transfer(struct spi_device *spi,
|
||
|
struct spi_transfer *t)
|
||
|
{
|
||
|
struct spi_stm *spi_stm;
|
||
|
u32 hz;
|
||
|
u8 bits_per_word;
|
||
|
u32 reg;
|
||
|
u32 sscbrg;
|
||
|
|
||
|
spi_stm = spi_master_get_devdata(spi->master);
|
||
|
bits_per_word = (t) ? t->bits_per_word : 0;
|
||
|
hz = (t) ? t->speed_hz : 0;
|
||
|
|
||
|
/* If not specified, use defaults */
|
||
|
if (!bits_per_word)
|
||
|
bits_per_word = spi->bits_per_word;
|
||
|
if (!hz)
|
||
|
hz = spi->max_speed_hz;
|
||
|
|
||
|
/* Actually, can probably support 2-16 without any other change!!! */
|
||
|
if (bits_per_word != 8 && bits_per_word != 16) {
|
||
|
dev_err(&spi->dev, "unsupported bits_per_word=%d\n",
|
||
|
bits_per_word);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
spi_stm->bits_per_word = bits_per_word;
|
||
|
|
||
|
/* Set SSC_BRF */
|
||
|
sscbrg = clk_get_rate(spi_stm->clk) / (2*hz);
|
||
|
if (sscbrg < 0x07 || sscbrg > (0x1 << 16)) {
|
||
|
dev_err(&spi->dev, "baudrate outside valid range"
|
||
|
" %d (sscbrg = %d)\n", hz, sscbrg);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
spi_stm->baud = clk_get_rate(spi_stm->clk) / (2 * sscbrg);
|
||
|
if (sscbrg == (0x1 << 16)) /* 16-bit counter wraps */
|
||
|
sscbrg = 0x0;
|
||
|
ssc_store32(spi_stm, SSC_BRG, sscbrg);
|
||
|
|
||
|
dev_dbg(&spi->dev, "setting baudrate: target = %u hz, "
|
||
|
"actual = %u hz, sscbrg = %u\n",
|
||
|
hz, spi_stm->baud, sscbrg);
|
||
|
|
||
|
/* Set SSC_CTL and enable SSC */
|
||
|
reg = ssc_load32(spi_stm, SSC_CTL);
|
||
|
reg |= SSC_CTL_MS;
|
||
|
|
||
|
if (spi->mode & SPI_CPOL)
|
||
|
reg |= SSC_CTL_PO;
|
||
|
else
|
||
|
reg &= ~SSC_CTL_PO;
|
||
|
|
||
|
if (spi->mode & SPI_CPHA)
|
||
|
reg |= SSC_CTL_PH;
|
||
|
else
|
||
|
reg &= ~SSC_CTL_PH;
|
||
|
|
||
|
if ((spi->mode & SPI_LSB_FIRST) == 0)
|
||
|
reg |= SSC_CTL_HB;
|
||
|
else
|
||
|
reg &= ~SSC_CTL_HB;
|
||
|
|
||
|
if (spi->mode & SPI_LOOP)
|
||
|
reg |= SSC_CTL_LPB;
|
||
|
else
|
||
|
reg &= ~SSC_CTL_LPB;
|
||
|
|
||
|
reg &= 0xfffffff0;
|
||
|
reg |= (bits_per_word - 1);
|
||
|
|
||
|
reg |= SSC_CTL_EN_TX_FIFO | SSC_CTL_EN_RX_FIFO;
|
||
|
reg |= SSC_CTL_EN;
|
||
|
|
||
|
dev_dbg(&spi->dev, "ssc_ctl = 0x%04x\n", reg);
|
||
|
ssc_store32(spi_stm, SSC_CTL, reg);
|
||
|
|
||
|
/* Clear the status register */
|
||
|
ssc_load32(spi_stm, SSC_RBUF);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void spi_stm_cleanup(struct spi_device *spi)
|
||
|
{
|
||
|
if (spi->controller_data) {
|
||
|
gpio_free(spi->chip_select);
|
||
|
spi->controller_data = (void *)0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* the spi->mode bits understood by this driver: */
|
||
|
#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST | SPI_LOOP | SPI_CS_HIGH)
|
||
|
static int spi_stm_setup(struct spi_device *spi)
|
||
|
{
|
||
|
struct spi_stm *spi_stm;
|
||
|
|
||
|
spi_stm = spi_master_get_devdata(spi->master);
|
||
|
|
||
|
if (spi->mode & ~MODEBITS) {
|
||
|
dev_err(&spi->dev, "unsupported mode bits %x\n",
|
||
|
spi->mode & ~MODEBITS);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!spi->max_speed_hz) {
|
||
|
dev_err(&spi->dev, "max_speed_hz unspecified\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!spi->bits_per_word)
|
||
|
spi->bits_per_word = 8;
|
||
|
|
||
|
return spi_stm_setup_transfer(spi, NULL);
|
||
|
}
|
||
|
|
||
|
/* Load the TX FIFO */
|
||
|
static void ssc_write_tx_fifo(struct spi_stm *spi_stm)
|
||
|
{
|
||
|
|
||
|
uint32_t count;
|
||
|
uint32_t word = 0;
|
||
|
int i;
|
||
|
|
||
|
if (spi_stm->words_remaining > 8)
|
||
|
count = 8;
|
||
|
else
|
||
|
count = spi_stm->words_remaining;
|
||
|
|
||
|
for (i = 0; i < count; i++) {
|
||
|
if (spi_stm->tx_ptr) {
|
||
|
if (spi_stm->bytes_per_word == 1) {
|
||
|
word = *spi_stm->tx_ptr++;
|
||
|
} else {
|
||
|
word = *spi_stm->tx_ptr++;
|
||
|
word = *spi_stm->tx_ptr++ | (word << 8);
|
||
|
}
|
||
|
}
|
||
|
ssc_store32(spi_stm, SSC_TBUF, word);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Read the RX FIFO */
|
||
|
static void ssc_read_rx_fifo(struct spi_stm *spi_stm)
|
||
|
{
|
||
|
|
||
|
uint32_t count;
|
||
|
uint32_t word = 0;
|
||
|
int i;
|
||
|
|
||
|
if (spi_stm->words_remaining > 8)
|
||
|
count = 8;
|
||
|
else
|
||
|
count = spi_stm->words_remaining;
|
||
|
|
||
|
for (i = 0; i < count; i++) {
|
||
|
word = ssc_load32(spi_stm, SSC_RBUF);
|
||
|
if (spi_stm->rx_ptr) {
|
||
|
if (spi_stm->bytes_per_word == 1) {
|
||
|
*spi_stm->rx_ptr++ = (uint8_t)word;
|
||
|
} else {
|
||
|
*spi_stm->rx_ptr++ = (word >> 8);
|
||
|
*spi_stm->rx_ptr++ = word & 0xff;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
spi_stm->words_remaining -= count;
|
||
|
}
|
||
|
|
||
|
/* Interrupt fired when TX shift register becomes empty */
|
||
|
static irqreturn_t spi_stm_irq(int irq, void *dev_id)
|
||
|
{
|
||
|
struct spi_stm *spi_stm = (struct spi_stm *)dev_id;
|
||
|
|
||
|
/* Read RX FIFO */
|
||
|
ssc_read_rx_fifo(spi_stm);
|
||
|
|
||
|
/* Fill TX FIFO */
|
||
|
if (spi_stm->words_remaining) {
|
||
|
ssc_write_tx_fifo(spi_stm);
|
||
|
} else {
|
||
|
/* TX/RX complete */
|
||
|
ssc_store32(spi_stm, SSC_IEN, 0x0);
|
||
|
complete(&spi_stm->done);
|
||
|
}
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int spi_stm_txrx_bufs(struct spi_device *spi, struct spi_transfer *t)
|
||
|
{
|
||
|
struct spi_stm *spi_stm;
|
||
|
uint32_t ctl = 0;
|
||
|
|
||
|
spi_stm = spi_master_get_devdata(spi->master);
|
||
|
|
||
|
/* Setup transfer */
|
||
|
spi_stm->tx_ptr = t->tx_buf;
|
||
|
spi_stm->rx_ptr = t->rx_buf;
|
||
|
|
||
|
if (spi_stm->bits_per_word > 8) {
|
||
|
/* Anything greater than 8 bits-per-word requires 2
|
||
|
* bytes-per-word in the RX/TX buffers */
|
||
|
spi_stm->bytes_per_word = 2;
|
||
|
spi_stm->words_remaining = t->len/2;
|
||
|
} else if (spi_stm->bits_per_word == 8 &&
|
||
|
((t->len & 0x1) == 0)) {
|
||
|
/* If transfer is even-length, and 8 bits-per-word, then
|
||
|
* implement as half-length 16 bits-per-word transfer */
|
||
|
spi_stm->bytes_per_word = 2;
|
||
|
spi_stm->words_remaining = t->len/2;
|
||
|
|
||
|
/* Set SSC_CTL to 16 bits-per-word */
|
||
|
ctl = ssc_load32(spi_stm, SSC_CTL);
|
||
|
ssc_store32(spi_stm, SSC_CTL, (ctl | 0xf));
|
||
|
|
||
|
ssc_load32(spi_stm, SSC_RBUF);
|
||
|
|
||
|
} else {
|
||
|
spi_stm->bytes_per_word = 1;
|
||
|
spi_stm->words_remaining = t->len;
|
||
|
}
|
||
|
|
||
|
INIT_COMPLETION(spi_stm->done);
|
||
|
|
||
|
/* Start transfer by writing to the TX FIFO */
|
||
|
ssc_write_tx_fifo(spi_stm);
|
||
|
ssc_store32(spi_stm, SSC_IEN, SSC_IEN_TEEN);
|
||
|
|
||
|
/* Wait for transfer to complete */
|
||
|
wait_for_completion(&spi_stm->done);
|
||
|
|
||
|
/* Restore SSC_CTL if necessary */
|
||
|
if (ctl)
|
||
|
ssc_store32(spi_stm, SSC_CTL, ctl);
|
||
|
|
||
|
return t->len;
|
||
|
|
||
|
}
|
||
|
|
||
|
static int __init spi_stm_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct stm_plat_ssc_data *plat_data = pdev->dev.platform_data;
|
||
|
struct spi_master *master;
|
||
|
struct resource *res;
|
||
|
struct spi_stm *spi_stm;
|
||
|
u32 reg;
|
||
|
int status = 0;
|
||
|
|
||
|
master = spi_alloc_master(&pdev->dev, sizeof(struct spi_stm));
|
||
|
if (!master) {
|
||
|
dev_err(&pdev->dev, "failed to allocate spi master\n");
|
||
|
status = -ENOMEM;
|
||
|
goto err0;
|
||
|
}
|
||
|
|
||
|
platform_set_drvdata(pdev, master);
|
||
|
|
||
|
spi_stm = spi_master_get_devdata(master);
|
||
|
spi_stm->bitbang.master = spi_master_get(master);
|
||
|
spi_stm->bitbang.setup_transfer = spi_stm_setup_transfer;
|
||
|
spi_stm->bitbang.txrx_bufs = spi_stm_txrx_bufs;
|
||
|
spi_stm->bitbang.master->setup = spi_stm_setup;
|
||
|
spi_stm->bitbang.master->cleanup = spi_stm_cleanup;
|
||
|
|
||
|
if (plat_data->spi_chipselect)
|
||
|
spi_stm->bitbang.chipselect = plat_data->spi_chipselect;
|
||
|
else
|
||
|
spi_stm->bitbang.chipselect = spi_stm_gpio_chipselect;
|
||
|
|
||
|
/* the spi->mode bits understood by this driver: */
|
||
|
master->mode_bits = MODEBITS;
|
||
|
|
||
|
/* chip_select field of spi_device is declared as u8 and therefore
|
||
|
* limits number of GPIOs that can be used as a CS line. Sorry. */
|
||
|
master->num_chipselect =
|
||
|
sizeof(((struct spi_device *)0)->chip_select) * 256;
|
||
|
master->bus_num = pdev->id;
|
||
|
init_completion(&spi_stm->done);
|
||
|
|
||
|
/* Get resources */
|
||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
|
if (!res) {
|
||
|
dev_err(&pdev->dev, "failed to find IOMEM resource\n");
|
||
|
status = -ENOENT;
|
||
|
goto err1;
|
||
|
}
|
||
|
spi_stm->r_mem = *res;
|
||
|
|
||
|
if (!request_mem_region(res->start,
|
||
|
res->end - res->start + 1, NAME)) {
|
||
|
dev_err(&pdev->dev, "request memory region failed [0x%x]\n",
|
||
|
res->start);
|
||
|
status = -EBUSY;
|
||
|
goto err1;
|
||
|
}
|
||
|
|
||
|
spi_stm->base = ioremap_nocache(res->start, res->end - res->start + 1);
|
||
|
if (!spi_stm->base) {
|
||
|
dev_err(&pdev->dev, "ioremap memory failed [0x%x]\n",
|
||
|
res->start);
|
||
|
status = -ENXIO;
|
||
|
goto err2;
|
||
|
}
|
||
|
|
||
|
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||
|
if (!res) {
|
||
|
dev_err(&pdev->dev, "failed to find IRQ resource\n");
|
||
|
status = -ENOENT;
|
||
|
goto err3;
|
||
|
}
|
||
|
spi_stm->r_irq = *res;
|
||
|
|
||
|
if (request_irq(res->start, spi_stm_irq,
|
||
|
IRQF_DISABLED, dev_name(&pdev->dev), spi_stm)) {
|
||
|
dev_err(&pdev->dev, "irq request failed\n");
|
||
|
status = -EBUSY;
|
||
|
goto err3;
|
||
|
}
|
||
|
|
||
|
spi_stm->pad_state = stm_pad_claim(plat_data->pad_config,
|
||
|
dev_name(&pdev->dev));
|
||
|
if (!spi_stm->pad_state) {
|
||
|
dev_err(&pdev->dev, "pads request failed\n");
|
||
|
status = -EBUSY;
|
||
|
goto err4;
|
||
|
}
|
||
|
|
||
|
/* Disable I2C and Reset SSC */
|
||
|
ssc_store32(spi_stm, SSC_I2C, 0x0);
|
||
|
reg = ssc_load16(spi_stm, SSC_CTL);
|
||
|
reg |= SSC_CTL_SR;
|
||
|
ssc_store32(spi_stm, SSC_CTL, reg);
|
||
|
|
||
|
udelay(1);
|
||
|
reg = ssc_load32(spi_stm, SSC_CTL);
|
||
|
reg &= ~SSC_CTL_SR;
|
||
|
ssc_store32(spi_stm, SSC_CTL, reg);
|
||
|
|
||
|
/* Set SSC into slave mode before reconfiguring PIO pins */
|
||
|
reg = ssc_load32(spi_stm, SSC_CTL);
|
||
|
reg &= ~SSC_CTL_MS;
|
||
|
ssc_store32(spi_stm, SSC_CTL, reg);
|
||
|
|
||
|
spi_stm->clk = clk_get(&pdev->dev, "comms_clk");
|
||
|
if (!spi_stm->clk) {
|
||
|
dev_err(&pdev->dev, "Comms clock not found!\n");
|
||
|
goto err5;
|
||
|
}
|
||
|
|
||
|
clk_enable(spi_stm->clk);
|
||
|
/* Start "bitbang" worker */
|
||
|
status = spi_bitbang_start(&spi_stm->bitbang);
|
||
|
if (status) {
|
||
|
dev_err(&pdev->dev, "bitbang start failed [%d]\n", status);
|
||
|
goto err5;
|
||
|
}
|
||
|
|
||
|
dev_info(&pdev->dev, "registered SPI Bus %d\n", master->bus_num);
|
||
|
|
||
|
return status;
|
||
|
|
||
|
err5:
|
||
|
stm_pad_release(spi_stm->pad_state);
|
||
|
err4:
|
||
|
free_irq(spi_stm->r_irq.start, spi_stm);
|
||
|
err3:
|
||
|
iounmap(spi_stm->base);
|
||
|
err2:
|
||
|
release_mem_region(spi_stm->r_mem.start,
|
||
|
resource_size(&spi_stm->r_mem));
|
||
|
err1:
|
||
|
spi_master_put(spi_stm->bitbang.master);
|
||
|
platform_set_drvdata(pdev, NULL);
|
||
|
err0:
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
static int spi_stm_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct spi_stm *spi_stm;
|
||
|
struct spi_master *master;
|
||
|
|
||
|
master = platform_get_drvdata(pdev);
|
||
|
spi_stm = spi_master_get_devdata(master);
|
||
|
|
||
|
spi_bitbang_stop(&spi_stm->bitbang);
|
||
|
|
||
|
clk_disable(spi_stm->clk);
|
||
|
|
||
|
stm_pad_release(spi_stm->pad_state);
|
||
|
free_irq(spi_stm->r_irq.start, spi_stm);
|
||
|
iounmap(spi_stm->base);
|
||
|
release_mem_region(spi_stm->r_mem.start,
|
||
|
resource_size(&spi_stm->r_mem));
|
||
|
|
||
|
spi_master_put(spi_stm->bitbang.master);
|
||
|
platform_set_drvdata(pdev, NULL);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#ifdef CONFIG_PM
|
||
|
static int spi_stm_suspend(struct device *dev)
|
||
|
{
|
||
|
struct spi_master *master = dev_get_drvdata(dev);
|
||
|
struct spi_stm *spi_stm;
|
||
|
|
||
|
spi_stm = spi_master_get_devdata(master);
|
||
|
|
||
|
ssc_store32(spi_stm, SSC_IEN, 0);
|
||
|
|
||
|
clk_disable(spi_stm->clk);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int spi_stm_resume(struct device *dev)
|
||
|
{
|
||
|
struct spi_master *master = dev_get_drvdata(dev);
|
||
|
struct spi_stm *spi_stm;
|
||
|
|
||
|
spi_stm = spi_master_get_devdata(master);
|
||
|
|
||
|
clk_enable(spi_stm->clk);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct dev_pm_ops spi_stm_pm = {
|
||
|
.suspend = spi_stm_suspend,
|
||
|
.resume = spi_stm_resume,
|
||
|
.freeze = spi_stm_suspend,
|
||
|
.restore = spi_stm_resume,
|
||
|
.runtime_suspend = spi_stm_suspend,
|
||
|
.runtime_resume = spi_stm_resume,
|
||
|
};
|
||
|
#else
|
||
|
static struct dev_pm_ops spi_stm_pm;
|
||
|
#endif
|
||
|
|
||
|
static struct platform_driver spi_stm_driver = {
|
||
|
.driver.name = NAME,
|
||
|
.driver.owner = THIS_MODULE,
|
||
|
.driver.pm = &spi_stm_pm,
|
||
|
.probe = spi_stm_probe,
|
||
|
.remove = spi_stm_remove,
|
||
|
};
|
||
|
|
||
|
|
||
|
static int __init spi_stm_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&spi_stm_driver);
|
||
|
}
|
||
|
|
||
|
static void __exit spi_stm_exit(void)
|
||
|
{
|
||
|
platform_driver_unregister(&spi_stm_driver);
|
||
|
}
|
||
|
|
||
|
module_init(spi_stm_init);
|
||
|
module_exit(spi_stm_exit);
|
||
|
|
||
|
MODULE_AUTHOR("STMicroelectronics <www.st.com>");
|
||
|
MODULE_DESCRIPTION("STM SSC SPI driver");
|
||
|
MODULE_LICENSE("GPL");
|