2006-05-20 22:00:15 +00:00
|
|
|
/*
|
2009-06-18 23:49:08 +00:00
|
|
|
* MPC8xxx SPI controller driver.
|
2006-05-20 22:00:15 +00:00
|
|
|
*
|
|
|
|
* 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>
|
2009-06-18 23:49:01 +00:00
|
|
|
#include <linux/bug.h>
|
2009-03-31 22:24:37 +00:00
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/err.h>
|
2009-06-18 23:49:05 +00:00
|
|
|
#include <linux/io.h>
|
2006-05-20 22:00:15 +00:00
|
|
|
#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>
|
2009-03-31 22:24:37 +00:00
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/of_platform.h>
|
|
|
|
#include <linux/gpio.h>
|
|
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include <linux/of_spi.h>
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-03-31 22:24:37 +00:00
|
|
|
#include <sysdev/fsl_soc.h>
|
2006-05-20 22:00:15 +00:00
|
|
|
#include <asm/irq.h>
|
|
|
|
|
|
|
|
/* SPI Controller registers */
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi_reg {
|
2006-05-20 22:00:15 +00:00
|
|
|
u8 res1[0x20];
|
|
|
|
__be32 mode;
|
|
|
|
__be32 event;
|
|
|
|
__be32 mask;
|
|
|
|
__be32 command;
|
|
|
|
__be32 transmit;
|
|
|
|
__be32 receive;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* SPI Controller mode register definitions */
|
2007-07-31 07:38:45 +00:00
|
|
|
#define SPMODE_LOOP (1 << 30)
|
2006-05-20 22:00:15 +00:00
|
|
|
#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)
|
2007-07-17 11:04:12 +00:00
|
|
|
#define SPMODE_OP (1 << 14)
|
2008-05-12 21:02:30 +00:00
|
|
|
#define SPMODE_CG(x) ((x) << 7)
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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. */
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi {
|
|
|
|
struct mpc8xxx_spi_reg __iomem *base;
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
/* rx & tx bufs from the spi_transfer */
|
|
|
|
const void *tx;
|
|
|
|
void *rx;
|
|
|
|
|
|
|
|
/* functions to deal with different sized buffers */
|
2009-06-18 23:49:08 +00:00
|
|
|
void (*get_rx) (u32 rx_data, struct mpc8xxx_spi *);
|
|
|
|
u32(*get_tx) (struct mpc8xxx_spi *);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
unsigned int count;
|
2009-03-31 22:24:37 +00:00
|
|
|
unsigned int irq;
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
unsigned nsecs; /* (clock cycle time)/2 */
|
|
|
|
|
2007-08-10 20:01:01 +00:00
|
|
|
u32 spibrg; /* SPIBRG input clock */
|
2007-07-17 11:04:12 +00:00
|
|
|
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;
|
|
|
|
|
2008-05-12 21:02:30 +00:00
|
|
|
struct workqueue_struct *workqueue;
|
|
|
|
struct work_struct work;
|
|
|
|
|
|
|
|
struct list_head queue;
|
|
|
|
spinlock_t lock;
|
|
|
|
|
|
|
|
struct completion done;
|
|
|
|
};
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
struct spi_mpc8xxx_cs {
|
2008-05-12 21:02:30 +00:00
|
|
|
/* functions to deal with different sized buffers */
|
2009-06-18 23:49:08 +00:00
|
|
|
void (*get_rx) (u32 rx_data, struct mpc8xxx_spi *);
|
|
|
|
u32 (*get_tx) (struct mpc8xxx_spi *);
|
2008-05-12 21:02:30 +00:00
|
|
|
u32 rx_shift; /* RX data reg shift when in qe mode */
|
|
|
|
u32 tx_shift; /* TX data reg shift when in qe mode */
|
|
|
|
u32 hw_mode; /* Holds HW mode register settings */
|
2006-05-20 22:00:15 +00:00
|
|
|
};
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static inline void mpc8xxx_spi_write_reg(__be32 __iomem *reg, u32 val)
|
2006-05-20 22:00:15 +00:00
|
|
|
{
|
|
|
|
out_be32(reg, val);
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static inline u32 mpc8xxx_spi_read_reg(__be32 __iomem *reg)
|
2006-05-20 22:00:15 +00:00
|
|
|
{
|
|
|
|
return in_be32(reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MPC83XX_SPI_RX_BUF(type) \
|
2009-03-31 22:24:35 +00:00
|
|
|
static \
|
2009-06-18 23:49:08 +00:00
|
|
|
void mpc8xxx_spi_rx_buf_##type(u32 data, struct mpc8xxx_spi *mpc8xxx_spi) \
|
2006-05-20 22:00:15 +00:00
|
|
|
{ \
|
2009-06-18 23:49:08 +00:00
|
|
|
type *rx = mpc8xxx_spi->rx; \
|
|
|
|
*rx++ = (type)(data >> mpc8xxx_spi->rx_shift); \
|
|
|
|
mpc8xxx_spi->rx = rx; \
|
2006-05-20 22:00:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#define MPC83XX_SPI_TX_BUF(type) \
|
2009-03-31 22:24:35 +00:00
|
|
|
static \
|
2009-06-18 23:49:08 +00:00
|
|
|
u32 mpc8xxx_spi_tx_buf_##type(struct mpc8xxx_spi *mpc8xxx_spi) \
|
2006-05-20 22:00:15 +00:00
|
|
|
{ \
|
|
|
|
u32 data; \
|
2009-06-18 23:49:08 +00:00
|
|
|
const type *tx = mpc8xxx_spi->tx; \
|
2006-12-30 00:48:39 +00:00
|
|
|
if (!tx) \
|
|
|
|
return 0; \
|
2009-06-18 23:49:08 +00:00
|
|
|
data = *tx++ << mpc8xxx_spi->tx_shift; \
|
|
|
|
mpc8xxx_spi->tx = tx; \
|
2006-05-20 22:00:15 +00:00
|
|
|
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)
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static void mpc8xxx_spi_chipselect(struct spi_device *spi, int value)
|
2006-05-20 22:00:15 +00:00
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi *mpc8xxx_spi = spi_master_get_devdata(spi->master);
|
2009-03-31 22:24:36 +00:00
|
|
|
struct fsl_spi_platform_data *pdata = spi->dev.parent->platform_data;
|
|
|
|
bool pol = spi->mode & SPI_CS_HIGH;
|
2009-06-18 23:49:08 +00:00
|
|
|
struct spi_mpc8xxx_cs *cs = spi->controller_state;
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
if (value == BITBANG_CS_INACTIVE) {
|
2009-03-31 22:24:36 +00:00
|
|
|
if (pdata->cs_control)
|
|
|
|
pdata->cs_control(spi, !pol);
|
2006-05-20 22:00:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (value == BITBANG_CS_ACTIVE) {
|
2009-06-18 23:49:08 +00:00
|
|
|
u32 regval = mpc8xxx_spi_read_reg(&mpc8xxx_spi->base->mode);
|
spi_mpc83xx: fix prescale modulus calculation
Long ago I've noticed (but didn't pay much attention) that
spi_mpc83xx using PM calculations that differs from what
specs describe. I.e.
u8 pm = mpc83xx_spi->spibrg / (spi->max_speed_hz * 4);
While specs says: "The SPI baud rate generator clock source (either
system clock or system clock divided by 16, depending on DIV16 bit) is
divided by 4 * ([PM] + 1), a range from 4 to 64.".
Thus " - 1" is missing in the spi_mpc83xx's formula.
Why nobody noticed that bug? Probably because sysclk usually less then
user expects, e.g. you expect 200 MHz, but real clock is 198 MHz,
and integer rounding helps when this formula is used.
Suppose it's SPI in QE, SYSCLK at 198 MHz, thus SPIBRG at 99MHz, 25 MHz
requested.
PM = (99MHz / ( 25 MHz * 4 )), PM == 0, output SPICLK will be 24.75 MHz
At lower frequencies this bug is more noticeable, though.
And this bug shows itself in all its beauty if SYSCLK is equal or a bit
more than you expect (200 MHz SYSCLK, 100 MHz SPIBRG):
PM = (100MHz / ( 25 MHz * 4 )), PM == 1, output SPICLK will be 12.625 MHz!
Signed-off-by: Anton Vorontsov <avorontsov@ru.mvista.com>
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>
2007-08-10 20:01:02 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi->rx_shift = cs->rx_shift;
|
|
|
|
mpc8xxx_spi->tx_shift = cs->tx_shift;
|
|
|
|
mpc8xxx_spi->get_rx = cs->get_rx;
|
|
|
|
mpc8xxx_spi->get_tx = cs->get_tx;
|
2008-05-12 21:02:30 +00:00
|
|
|
|
|
|
|
if (cs->hw_mode != regval) {
|
|
|
|
unsigned long flags;
|
2009-06-18 23:49:08 +00:00
|
|
|
__be32 __iomem *mode = &mpc8xxx_spi->base->mode;
|
2008-05-12 21:02:30 +00:00
|
|
|
|
|
|
|
regval = cs->hw_mode;
|
|
|
|
/* Turn off IRQs locally to minimize time that
|
|
|
|
* SPI is disabled
|
|
|
|
*/
|
|
|
|
local_irq_save(flags);
|
|
|
|
/* Turn off SPI unit prior changing mode */
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_write_reg(mode, regval & ~SPMODE_ENABLE);
|
|
|
|
mpc8xxx_spi_write_reg(mode, regval);
|
2008-05-12 21:02:30 +00:00
|
|
|
local_irq_restore(flags);
|
2006-05-20 22:00:15 +00:00
|
|
|
}
|
2009-03-31 22:24:36 +00:00
|
|
|
if (pdata->cs_control)
|
|
|
|
pdata->cs_control(spi, pol);
|
2006-05-20 22:00:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static
|
2009-06-18 23:49:08 +00:00
|
|
|
int mpc8xxx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
|
2006-05-20 22:00:15 +00:00
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi *mpc8xxx_spi;
|
2006-05-20 22:00:15 +00:00
|
|
|
u32 regval;
|
2008-05-12 21:02:30 +00:00
|
|
|
u8 bits_per_word, pm;
|
2006-05-20 22:00:15 +00:00
|
|
|
u32 hz;
|
2009-06-18 23:49:08 +00:00
|
|
|
struct spi_mpc8xxx_cs *cs = spi->controller_state;
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi = spi_master_get_devdata(spi->master);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2008-05-12 21:02:30 +00:00
|
|
|
if (!hz)
|
|
|
|
hz = spi->max_speed_hz;
|
|
|
|
|
|
|
|
cs->rx_shift = 0;
|
|
|
|
cs->tx_shift = 0;
|
2006-05-20 22:00:15 +00:00
|
|
|
if (bits_per_word <= 8) {
|
2009-06-18 23:49:08 +00:00
|
|
|
cs->get_rx = mpc8xxx_spi_rx_buf_u8;
|
|
|
|
cs->get_tx = mpc8xxx_spi_tx_buf_u8;
|
|
|
|
if (mpc8xxx_spi->qe_mode) {
|
2008-05-12 21:02:30 +00:00
|
|
|
cs->rx_shift = 16;
|
|
|
|
cs->tx_shift = 24;
|
2007-07-17 11:04:12 +00:00
|
|
|
}
|
2006-05-20 22:00:15 +00:00
|
|
|
} else if (bits_per_word <= 16) {
|
2009-06-18 23:49:08 +00:00
|
|
|
cs->get_rx = mpc8xxx_spi_rx_buf_u16;
|
|
|
|
cs->get_tx = mpc8xxx_spi_tx_buf_u16;
|
|
|
|
if (mpc8xxx_spi->qe_mode) {
|
2008-05-12 21:02:30 +00:00
|
|
|
cs->rx_shift = 16;
|
|
|
|
cs->tx_shift = 16;
|
2007-07-17 11:04:12 +00:00
|
|
|
}
|
2006-05-20 22:00:15 +00:00
|
|
|
} else if (bits_per_word <= 32) {
|
2009-06-18 23:49:08 +00:00
|
|
|
cs->get_rx = mpc8xxx_spi_rx_buf_u32;
|
|
|
|
cs->get_tx = mpc8xxx_spi_tx_buf_u32;
|
2006-05-20 22:00:15 +00:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
if (mpc8xxx_spi->qe_mode && spi->mode & SPI_LSB_FIRST) {
|
2008-05-12 21:02:30 +00:00
|
|
|
cs->tx_shift = 0;
|
2007-07-31 07:38:42 +00:00
|
|
|
if (bits_per_word <= 8)
|
2008-05-12 21:02:30 +00:00
|
|
|
cs->rx_shift = 8;
|
2007-07-31 07:38:42 +00:00
|
|
|
else
|
2008-05-12 21:02:30 +00:00
|
|
|
cs->rx_shift = 0;
|
2007-07-31 07:38:42 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi->rx_shift = cs->rx_shift;
|
|
|
|
mpc8xxx_spi->tx_shift = cs->tx_shift;
|
|
|
|
mpc8xxx_spi->get_rx = cs->get_rx;
|
|
|
|
mpc8xxx_spi->get_tx = cs->get_tx;
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
if (bits_per_word == 32)
|
|
|
|
bits_per_word = 0;
|
|
|
|
else
|
|
|
|
bits_per_word = bits_per_word - 1;
|
|
|
|
|
2007-07-31 07:38:41 +00:00
|
|
|
/* mask out bits we are going to set */
|
2008-05-12 21:02:30 +00:00
|
|
|
cs->hw_mode &= ~(SPMODE_LEN(0xF) | SPMODE_DIV16
|
|
|
|
| SPMODE_PM(0xF));
|
|
|
|
|
|
|
|
cs->hw_mode |= SPMODE_LEN(bits_per_word);
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
if ((mpc8xxx_spi->spibrg / hz) > 64) {
|
2008-09-13 09:33:14 +00:00
|
|
|
cs->hw_mode |= SPMODE_DIV16;
|
2009-06-18 23:49:08 +00:00
|
|
|
pm = mpc8xxx_spi->spibrg / (hz * 64);
|
2009-06-18 23:49:01 +00:00
|
|
|
|
|
|
|
WARN_ONCE(pm > 16, "%s: Requested speed is too low: %d Hz. "
|
|
|
|
"Will use %d Hz instead.\n", dev_name(&spi->dev),
|
2009-06-18 23:49:08 +00:00
|
|
|
hz, mpc8xxx_spi->spibrg / 1024);
|
2009-06-18 23:49:01 +00:00
|
|
|
if (pm > 16)
|
2008-09-13 09:33:14 +00:00
|
|
|
pm = 16;
|
2008-07-24 04:29:52 +00:00
|
|
|
} else
|
2009-06-18 23:49:08 +00:00
|
|
|
pm = mpc8xxx_spi->spibrg / (hz * 4);
|
2008-07-24 04:29:52 +00:00
|
|
|
if (pm)
|
|
|
|
pm--;
|
|
|
|
|
|
|
|
cs->hw_mode |= SPMODE_PM(pm);
|
2009-06-18 23:49:08 +00:00
|
|
|
regval = mpc8xxx_spi_read_reg(&mpc8xxx_spi->base->mode);
|
2008-05-12 21:02:30 +00:00
|
|
|
if (cs->hw_mode != regval) {
|
|
|
|
unsigned long flags;
|
2009-06-18 23:49:08 +00:00
|
|
|
__be32 __iomem *mode = &mpc8xxx_spi->base->mode;
|
2008-05-12 21:02:30 +00:00
|
|
|
|
|
|
|
regval = cs->hw_mode;
|
|
|
|
/* Turn off IRQs locally to minimize time
|
|
|
|
* that SPI is disabled
|
|
|
|
*/
|
|
|
|
local_irq_save(flags);
|
|
|
|
/* Turn off SPI unit prior changing mode */
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_write_reg(mode, regval & ~SPMODE_ENABLE);
|
|
|
|
mpc8xxx_spi_write_reg(mode, regval);
|
2008-05-12 21:02:30 +00:00
|
|
|
local_irq_restore(flags);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static int mpc8xxx_spi_bufs(struct spi_device *spi, struct spi_transfer *t)
|
2008-05-12 21:02:30 +00:00
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi *mpc8xxx_spi;
|
2008-05-12 21:02:30 +00:00
|
|
|
u32 word, len, bits_per_word;
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi = spi_master_get_devdata(spi->master);
|
2008-05-12 21:02:30 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi->tx = t->tx_buf;
|
|
|
|
mpc8xxx_spi->rx = t->rx_buf;
|
2008-05-12 21:02:30 +00:00
|
|
|
bits_per_word = spi->bits_per_word;
|
|
|
|
if (t->bits_per_word)
|
|
|
|
bits_per_word = t->bits_per_word;
|
|
|
|
len = t->len;
|
2008-09-13 09:33:15 +00:00
|
|
|
if (bits_per_word > 8) {
|
|
|
|
/* invalid length? */
|
|
|
|
if (len & 1)
|
|
|
|
return -EINVAL;
|
2008-05-12 21:02:30 +00:00
|
|
|
len /= 2;
|
2008-09-13 09:33:15 +00:00
|
|
|
}
|
|
|
|
if (bits_per_word > 16) {
|
|
|
|
/* invalid length? */
|
|
|
|
if (len & 1)
|
|
|
|
return -EINVAL;
|
2008-05-12 21:02:30 +00:00
|
|
|
len /= 2;
|
2008-09-13 09:33:15 +00:00
|
|
|
}
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi->count = len;
|
2008-09-13 09:33:15 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
INIT_COMPLETION(mpc8xxx_spi->done);
|
2008-05-12 21:02:30 +00:00
|
|
|
|
|
|
|
/* enable rx ints */
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_write_reg(&mpc8xxx_spi->base->mask, SPIM_NE);
|
2008-05-12 21:02:30 +00:00
|
|
|
|
|
|
|
/* transmit word */
|
2009-06-18 23:49:08 +00:00
|
|
|
word = mpc8xxx_spi->get_tx(mpc8xxx_spi);
|
|
|
|
mpc8xxx_spi_write_reg(&mpc8xxx_spi->base->transmit, word);
|
2008-05-12 21:02:30 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
wait_for_completion(&mpc8xxx_spi->done);
|
2008-05-12 21:02:30 +00:00
|
|
|
|
|
|
|
/* disable rx ints */
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_write_reg(&mpc8xxx_spi->base->mask, 0);
|
2008-05-12 21:02:30 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
return mpc8xxx_spi->count;
|
2008-05-12 21:02:30 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static void mpc8xxx_spi_do_one_msg(struct spi_message *m)
|
2008-05-12 21:02:30 +00:00
|
|
|
{
|
2009-06-18 23:49:06 +00:00
|
|
|
struct spi_device *spi = m->spi;
|
|
|
|
struct spi_transfer *t;
|
|
|
|
unsigned int cs_change;
|
|
|
|
const int nsecs = 50;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
cs_change = 1;
|
|
|
|
status = 0;
|
|
|
|
list_for_each_entry(t, &m->transfers, transfer_list) {
|
|
|
|
if (t->bits_per_word || t->speed_hz) {
|
|
|
|
/* Don't allow changes if CS is active */
|
|
|
|
status = -EINVAL;
|
|
|
|
|
|
|
|
if (cs_change)
|
2009-06-18 23:49:08 +00:00
|
|
|
status = mpc8xxx_spi_setup_transfer(spi, t);
|
2009-06-18 23:49:06 +00:00
|
|
|
if (status < 0)
|
2008-05-12 21:02:30 +00:00
|
|
|
break;
|
2009-06-18 23:49:06 +00:00
|
|
|
}
|
2008-05-12 21:02:30 +00:00
|
|
|
|
2009-06-18 23:49:06 +00:00
|
|
|
if (cs_change) {
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_chipselect(spi, BITBANG_CS_ACTIVE);
|
2009-06-18 23:49:06 +00:00
|
|
|
ndelay(nsecs);
|
|
|
|
}
|
|
|
|
cs_change = t->cs_change;
|
|
|
|
if (t->len)
|
2009-06-18 23:49:08 +00:00
|
|
|
status = mpc8xxx_spi_bufs(spi, t);
|
2009-06-18 23:49:06 +00:00
|
|
|
if (status) {
|
|
|
|
status = -EMSGSIZE;
|
|
|
|
break;
|
2008-05-12 21:02:30 +00:00
|
|
|
}
|
2009-06-18 23:49:06 +00:00
|
|
|
m->actual_length += t->len;
|
2008-05-12 21:02:30 +00:00
|
|
|
|
2009-06-18 23:49:06 +00:00
|
|
|
if (t->delay_usecs)
|
|
|
|
udelay(t->delay_usecs);
|
2008-05-12 21:02:30 +00:00
|
|
|
|
2009-06-18 23:49:06 +00:00
|
|
|
if (cs_change) {
|
2008-05-12 21:02:30 +00:00
|
|
|
ndelay(nsecs);
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_chipselect(spi, BITBANG_CS_INACTIVE);
|
2009-06-18 23:49:06 +00:00
|
|
|
ndelay(nsecs);
|
2008-05-12 21:02:30 +00:00
|
|
|
}
|
2009-06-18 23:49:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
m->status = status;
|
|
|
|
m->complete(m->context);
|
|
|
|
|
|
|
|
if (status || !cs_change) {
|
|
|
|
ndelay(nsecs);
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_chipselect(spi, BITBANG_CS_INACTIVE);
|
2009-06-18 23:49:06 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_setup_transfer(spi, NULL);
|
2009-06-18 23:49:06 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static void mpc8xxx_spi_work(struct work_struct *work)
|
2009-06-18 23:49:06 +00:00
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi *mpc8xxx_spi = container_of(work, struct mpc8xxx_spi,
|
2009-06-18 23:49:06 +00:00
|
|
|
work);
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
spin_lock_irq(&mpc8xxx_spi->lock);
|
|
|
|
while (!list_empty(&mpc8xxx_spi->queue)) {
|
|
|
|
struct spi_message *m = container_of(mpc8xxx_spi->queue.next,
|
2009-06-18 23:49:06 +00:00
|
|
|
struct spi_message, queue);
|
|
|
|
|
|
|
|
list_del_init(&m->queue);
|
2009-06-18 23:49:08 +00:00
|
|
|
spin_unlock_irq(&mpc8xxx_spi->lock);
|
2008-05-12 21:02:30 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_do_one_msg(m);
|
2008-05-12 21:02:30 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
spin_lock_irq(&mpc8xxx_spi->lock);
|
2008-05-12 21:02:30 +00:00
|
|
|
}
|
2009-06-18 23:49:08 +00:00
|
|
|
spin_unlock_irq(&mpc8xxx_spi->lock);
|
2006-05-20 22:00:15 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static int mpc8xxx_spi_setup(struct spi_device *spi)
|
2006-05-20 22:00:15 +00:00
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi *mpc8xxx_spi;
|
2006-05-20 22:00:15 +00:00
|
|
|
int retval;
|
2008-05-12 21:02:30 +00:00
|
|
|
u32 hw_mode;
|
2009-06-18 23:49:08 +00:00
|
|
|
struct spi_mpc8xxx_cs *cs = spi->controller_state;
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
if (!spi->max_speed_hz)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2008-05-12 21:02:30 +00:00
|
|
|
if (!cs) {
|
|
|
|
cs = kzalloc(sizeof *cs, GFP_KERNEL);
|
|
|
|
if (!cs)
|
|
|
|
return -ENOMEM;
|
|
|
|
spi->controller_state = cs;
|
|
|
|
}
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi = spi_master_get_devdata(spi->master);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2008-05-12 21:02:30 +00:00
|
|
|
hw_mode = cs->hw_mode; /* Save orginal settings */
|
2009-06-18 23:49:08 +00:00
|
|
|
cs->hw_mode = mpc8xxx_spi_read_reg(&mpc8xxx_spi->base->mode);
|
2008-05-12 21:02:30 +00:00
|
|
|
/* mask out bits we are going to set */
|
|
|
|
cs->hw_mode &= ~(SPMODE_CP_BEGIN_EDGECLK | SPMODE_CI_INACTIVEHIGH
|
|
|
|
| SPMODE_REV | SPMODE_LOOP);
|
|
|
|
|
|
|
|
if (spi->mode & SPI_CPHA)
|
|
|
|
cs->hw_mode |= SPMODE_CP_BEGIN_EDGECLK;
|
|
|
|
if (spi->mode & SPI_CPOL)
|
|
|
|
cs->hw_mode |= SPMODE_CI_INACTIVEHIGH;
|
|
|
|
if (!(spi->mode & SPI_LSB_FIRST))
|
|
|
|
cs->hw_mode |= SPMODE_REV;
|
|
|
|
if (spi->mode & SPI_LOOP)
|
|
|
|
cs->hw_mode |= SPMODE_LOOP;
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
retval = mpc8xxx_spi_setup_transfer(spi, NULL);
|
2008-05-12 21:02:30 +00:00
|
|
|
if (retval < 0) {
|
|
|
|
cs->hw_mode = hw_mode; /* Restore settings */
|
2006-05-20 22:00:15 +00:00
|
|
|
return retval;
|
2008-05-12 21:02:30 +00:00
|
|
|
}
|
2006-05-20 22:00:15 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static irqreturn_t mpc8xxx_spi_irq(s32 irq, void *context_data)
|
2006-05-20 22:00:15 +00:00
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi *mpc8xxx_spi = context_data;
|
2006-05-20 22:00:15 +00:00
|
|
|
u32 event;
|
|
|
|
irqreturn_t ret = IRQ_NONE;
|
|
|
|
|
|
|
|
/* Get interrupt events(tx/rx) */
|
2009-06-18 23:49:08 +00:00
|
|
|
event = mpc8xxx_spi_read_reg(&mpc8xxx_spi->base->event);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
/* We need handle RX first */
|
|
|
|
if (event & SPIE_NE) {
|
2009-06-18 23:49:08 +00:00
|
|
|
u32 rx_data = mpc8xxx_spi_read_reg(&mpc8xxx_spi->base->receive);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
if (mpc8xxx_spi->rx)
|
|
|
|
mpc8xxx_spi->get_rx(rx_data, mpc8xxx_spi);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
ret = IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((event & SPIE_NF) == 0)
|
|
|
|
/* spin until TX is done */
|
|
|
|
while (((event =
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_read_reg(&mpc8xxx_spi->base->event)) &
|
2006-05-20 22:00:15 +00:00
|
|
|
SPIE_NF) == 0)
|
2009-06-18 23:49:05 +00:00
|
|
|
cpu_relax();
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi->count -= 1;
|
|
|
|
if (mpc8xxx_spi->count) {
|
|
|
|
u32 word = mpc8xxx_spi->get_tx(mpc8xxx_spi);
|
|
|
|
mpc8xxx_spi_write_reg(&mpc8xxx_spi->base->transmit, word);
|
2006-05-20 22:00:15 +00:00
|
|
|
} else {
|
2009-06-18 23:49:08 +00:00
|
|
|
complete(&mpc8xxx_spi->done);
|
2006-05-20 22:00:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Clear the events */
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_write_reg(&mpc8xxx_spi->base->event, event);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2009-06-18 23:49:08 +00:00
|
|
|
static int mpc8xxx_spi_transfer(struct spi_device *spi,
|
2008-05-12 21:02:30 +00:00
|
|
|
struct spi_message *m)
|
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi *mpc8xxx_spi = spi_master_get_devdata(spi->master);
|
2008-05-12 21:02:30 +00:00
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
m->actual_length = 0;
|
|
|
|
m->status = -EINPROGRESS;
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
spin_lock_irqsave(&mpc8xxx_spi->lock, flags);
|
|
|
|
list_add_tail(&m->queue, &mpc8xxx_spi->queue);
|
|
|
|
queue_work(mpc8xxx_spi->workqueue, &mpc8xxx_spi->work);
|
|
|
|
spin_unlock_irqrestore(&mpc8xxx_spi->lock, flags);
|
2008-05-12 21:02:30 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static void mpc8xxx_spi_cleanup(struct spi_device *spi)
|
2008-05-12 21:02:30 +00:00
|
|
|
{
|
|
|
|
kfree(spi->controller_state);
|
|
|
|
}
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-03-31 22:24:37 +00:00
|
|
|
static struct spi_master * __devinit
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_probe(struct device *dev, struct resource *mem, unsigned int irq)
|
2006-05-20 22:00:15 +00:00
|
|
|
{
|
2009-03-31 22:24:37 +00:00
|
|
|
struct fsl_spi_platform_data *pdata = dev->platform_data;
|
2006-05-20 22:00:15 +00:00
|
|
|
struct spi_master *master;
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi *mpc8xxx_spi;
|
2006-05-20 22:00:15 +00:00
|
|
|
u32 regval;
|
|
|
|
int ret = 0;
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
master = spi_alloc_master(dev, sizeof(struct mpc8xxx_spi));
|
2006-05-20 22:00:15 +00:00
|
|
|
if (master == NULL) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2009-03-31 22:24:37 +00:00
|
|
|
dev_set_drvdata(dev, master);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-06-17 23:26:04 +00:00
|
|
|
/* the spi->mode bits understood by this driver: */
|
|
|
|
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH
|
|
|
|
| SPI_LSB_FIRST | SPI_LOOP;
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
master->setup = mpc8xxx_spi_setup;
|
|
|
|
master->transfer = mpc8xxx_spi_transfer;
|
|
|
|
master->cleanup = mpc8xxx_spi_cleanup;
|
|
|
|
|
|
|
|
mpc8xxx_spi = spi_master_get_devdata(master);
|
|
|
|
mpc8xxx_spi->qe_mode = pdata->qe_mode;
|
|
|
|
mpc8xxx_spi->get_rx = mpc8xxx_spi_rx_buf_u8;
|
|
|
|
mpc8xxx_spi->get_tx = mpc8xxx_spi_tx_buf_u8;
|
|
|
|
mpc8xxx_spi->spibrg = pdata->sysclk;
|
|
|
|
|
|
|
|
mpc8xxx_spi->rx_shift = 0;
|
|
|
|
mpc8xxx_spi->tx_shift = 0;
|
|
|
|
if (mpc8xxx_spi->qe_mode) {
|
|
|
|
mpc8xxx_spi->rx_shift = 16;
|
|
|
|
mpc8xxx_spi->tx_shift = 24;
|
2007-07-17 11:04:12 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
init_completion(&mpc8xxx_spi->done);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi->base = ioremap(mem->start, mem->end - mem->start + 1);
|
|
|
|
if (mpc8xxx_spi->base == NULL) {
|
2006-05-20 22:00:15 +00:00
|
|
|
ret = -ENOMEM;
|
|
|
|
goto put_master;
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi->irq = irq;
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
/* Register for SPI Interrupt */
|
2009-06-18 23:49:08 +00:00
|
|
|
ret = request_irq(mpc8xxx_spi->irq, mpc8xxx_spi_irq,
|
|
|
|
0, "mpc8xxx_spi", mpc8xxx_spi);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
if (ret != 0)
|
|
|
|
goto unmap_io;
|
|
|
|
|
|
|
|
master->bus_num = pdata->bus_num;
|
|
|
|
master->num_chipselect = pdata->max_chipselect;
|
|
|
|
|
|
|
|
/* SPI controller initializations */
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_write_reg(&mpc8xxx_spi->base->mode, 0);
|
|
|
|
mpc8xxx_spi_write_reg(&mpc8xxx_spi->base->mask, 0);
|
|
|
|
mpc8xxx_spi_write_reg(&mpc8xxx_spi->base->command, 0);
|
|
|
|
mpc8xxx_spi_write_reg(&mpc8xxx_spi->base->event, 0xffffffff);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
/* Enable SPI interface */
|
|
|
|
regval = pdata->initial_spmode | SPMODE_INIT_VAL | SPMODE_ENABLE;
|
2007-07-17 11:04:12 +00:00
|
|
|
if (pdata->qe_mode)
|
|
|
|
regval |= SPMODE_OP;
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi_write_reg(&mpc8xxx_spi->base->mode, regval);
|
|
|
|
spin_lock_init(&mpc8xxx_spi->lock);
|
|
|
|
init_completion(&mpc8xxx_spi->done);
|
|
|
|
INIT_WORK(&mpc8xxx_spi->work, mpc8xxx_spi_work);
|
|
|
|
INIT_LIST_HEAD(&mpc8xxx_spi->queue);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi->workqueue = create_singlethread_workqueue(
|
2009-03-24 23:38:21 +00:00
|
|
|
dev_name(master->dev.parent));
|
2009-06-18 23:49:08 +00:00
|
|
|
if (mpc8xxx_spi->workqueue == NULL) {
|
2008-05-12 21:02:30 +00:00
|
|
|
ret = -EBUSY;
|
2006-05-20 22:00:15 +00:00
|
|
|
goto free_irq;
|
2008-05-12 21:02:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = spi_register_master(master);
|
|
|
|
if (ret < 0)
|
|
|
|
goto unreg_master;
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
printk(KERN_INFO
|
2009-06-18 23:49:08 +00:00
|
|
|
"%s: MPC8xxx SPI Controller driver at 0x%p (irq = %d)\n",
|
|
|
|
dev_name(dev), mpc8xxx_spi->base, mpc8xxx_spi->irq);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-03-31 22:24:37 +00:00
|
|
|
return master;
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2008-05-12 21:02:30 +00:00
|
|
|
unreg_master:
|
2009-06-18 23:49:08 +00:00
|
|
|
destroy_workqueue(mpc8xxx_spi->workqueue);
|
2006-05-20 22:00:15 +00:00
|
|
|
free_irq:
|
2009-06-18 23:49:08 +00:00
|
|
|
free_irq(mpc8xxx_spi->irq, mpc8xxx_spi);
|
2006-05-20 22:00:15 +00:00
|
|
|
unmap_io:
|
2009-06-18 23:49:08 +00:00
|
|
|
iounmap(mpc8xxx_spi->base);
|
2006-05-20 22:00:15 +00:00
|
|
|
put_master:
|
|
|
|
spi_master_put(master);
|
|
|
|
err:
|
2009-03-31 22:24:37 +00:00
|
|
|
return ERR_PTR(ret);
|
2006-05-20 22:00:15 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static int __devexit mpc8xxx_spi_remove(struct device *dev)
|
2006-05-20 22:00:15 +00:00
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi *mpc8xxx_spi;
|
2006-05-20 22:00:15 +00:00
|
|
|
struct spi_master *master;
|
|
|
|
|
2009-03-31 22:24:37 +00:00
|
|
|
master = dev_get_drvdata(dev);
|
2009-06-18 23:49:08 +00:00
|
|
|
mpc8xxx_spi = spi_master_get_devdata(master);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
flush_workqueue(mpc8xxx_spi->workqueue);
|
|
|
|
destroy_workqueue(mpc8xxx_spi->workqueue);
|
2008-05-12 21:02:30 +00:00
|
|
|
spi_unregister_master(master);
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
free_irq(mpc8xxx_spi->irq, mpc8xxx_spi);
|
|
|
|
iounmap(mpc8xxx_spi->base);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi_probe_info {
|
2009-03-31 22:24:37 +00:00
|
|
|
struct fsl_spi_platform_data pdata;
|
|
|
|
int *gpios;
|
|
|
|
bool *alow_flags;
|
|
|
|
};
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static struct mpc8xxx_spi_probe_info *
|
2009-03-31 22:24:37 +00:00
|
|
|
to_of_pinfo(struct fsl_spi_platform_data *pdata)
|
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
return container_of(pdata, struct mpc8xxx_spi_probe_info, pdata);
|
2009-03-31 22:24:37 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static void mpc8xxx_spi_cs_control(struct spi_device *spi, bool on)
|
2009-03-31 22:24:37 +00:00
|
|
|
{
|
|
|
|
struct device *dev = spi->dev.parent;
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi_probe_info *pinfo = to_of_pinfo(dev->platform_data);
|
2009-03-31 22:24:37 +00:00
|
|
|
u16 cs = spi->chip_select;
|
|
|
|
int gpio = pinfo->gpios[cs];
|
|
|
|
bool alow = pinfo->alow_flags[cs];
|
|
|
|
|
|
|
|
gpio_set_value(gpio, on ^ alow);
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static int of_mpc8xxx_spi_get_chipselects(struct device *dev)
|
2009-03-31 22:24:37 +00:00
|
|
|
{
|
|
|
|
struct device_node *np = dev_archdata_get_node(&dev->archdata);
|
|
|
|
struct fsl_spi_platform_data *pdata = dev->platform_data;
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi_probe_info *pinfo = to_of_pinfo(pdata);
|
2009-03-31 22:24:37 +00:00
|
|
|
unsigned int ngpios;
|
|
|
|
int i = 0;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ngpios = of_gpio_count(np);
|
|
|
|
if (!ngpios) {
|
|
|
|
/*
|
|
|
|
* SPI w/o chip-select line. One SPI device is still permitted
|
|
|
|
* though.
|
|
|
|
*/
|
|
|
|
pdata->max_chipselect = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-16 22:31:15 +00:00
|
|
|
pinfo->gpios = kmalloc(ngpios * sizeof(*pinfo->gpios), GFP_KERNEL);
|
2009-03-31 22:24:37 +00:00
|
|
|
if (!pinfo->gpios)
|
|
|
|
return -ENOMEM;
|
2009-06-16 22:31:15 +00:00
|
|
|
memset(pinfo->gpios, -1, ngpios * sizeof(*pinfo->gpios));
|
2009-03-31 22:24:37 +00:00
|
|
|
|
2009-06-16 22:31:15 +00:00
|
|
|
pinfo->alow_flags = kzalloc(ngpios * sizeof(*pinfo->alow_flags),
|
2009-03-31 22:24:37 +00:00
|
|
|
GFP_KERNEL);
|
|
|
|
if (!pinfo->alow_flags) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto err_alloc_flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; i < ngpios; i++) {
|
|
|
|
int gpio;
|
|
|
|
enum of_gpio_flags flags;
|
|
|
|
|
|
|
|
gpio = of_get_gpio_flags(np, i, &flags);
|
|
|
|
if (!gpio_is_valid(gpio)) {
|
|
|
|
dev_err(dev, "invalid gpio #%d: %d\n", i, gpio);
|
|
|
|
goto err_loop;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = gpio_request(gpio, dev_name(dev));
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "can't request gpio #%d: %d\n", i, ret);
|
|
|
|
goto err_loop;
|
|
|
|
}
|
|
|
|
|
|
|
|
pinfo->gpios[i] = gpio;
|
|
|
|
pinfo->alow_flags[i] = flags & OF_GPIO_ACTIVE_LOW;
|
|
|
|
|
|
|
|
ret = gpio_direction_output(pinfo->gpios[i],
|
|
|
|
pinfo->alow_flags[i]);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "can't set output direction for gpio "
|
|
|
|
"#%d: %d\n", i, ret);
|
|
|
|
goto err_loop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pdata->max_chipselect = ngpios;
|
2009-06-18 23:49:08 +00:00
|
|
|
pdata->cs_control = mpc8xxx_spi_cs_control;
|
2009-03-31 22:24:37 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_loop:
|
|
|
|
while (i >= 0) {
|
|
|
|
if (gpio_is_valid(pinfo->gpios[i]))
|
|
|
|
gpio_free(pinfo->gpios[i]);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(pinfo->alow_flags);
|
|
|
|
pinfo->alow_flags = NULL;
|
|
|
|
err_alloc_flags:
|
|
|
|
kfree(pinfo->gpios);
|
|
|
|
pinfo->gpios = NULL;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static int of_mpc8xxx_spi_free_chipselects(struct device *dev)
|
2009-03-31 22:24:37 +00:00
|
|
|
{
|
|
|
|
struct fsl_spi_platform_data *pdata = dev->platform_data;
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi_probe_info *pinfo = to_of_pinfo(pdata);
|
2009-03-31 22:24:37 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!pinfo->gpios)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (i = 0; i < pdata->max_chipselect; i++) {
|
|
|
|
if (gpio_is_valid(pinfo->gpios[i]))
|
|
|
|
gpio_free(pinfo->gpios[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(pinfo->gpios);
|
|
|
|
kfree(pinfo->alow_flags);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static int __devinit of_mpc8xxx_spi_probe(struct of_device *ofdev,
|
2009-03-31 22:24:37 +00:00
|
|
|
const struct of_device_id *ofid)
|
|
|
|
{
|
|
|
|
struct device *dev = &ofdev->dev;
|
|
|
|
struct device_node *np = ofdev->node;
|
2009-06-18 23:49:08 +00:00
|
|
|
struct mpc8xxx_spi_probe_info *pinfo;
|
2009-03-31 22:24:37 +00:00
|
|
|
struct fsl_spi_platform_data *pdata;
|
|
|
|
struct spi_master *master;
|
|
|
|
struct resource mem;
|
|
|
|
struct resource irq;
|
|
|
|
const void *prop;
|
|
|
|
int ret = -ENOMEM;
|
|
|
|
|
|
|
|
pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL);
|
|
|
|
if (!pinfo)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
pdata = &pinfo->pdata;
|
|
|
|
dev->platform_data = pdata;
|
|
|
|
|
|
|
|
/* Allocate bus num dynamically. */
|
|
|
|
pdata->bus_num = -1;
|
|
|
|
|
|
|
|
/* SPI controller is either clocked from QE or SoC clock. */
|
|
|
|
pdata->sysclk = get_brgfreq();
|
|
|
|
if (pdata->sysclk == -1) {
|
|
|
|
pdata->sysclk = fsl_get_sys_freq();
|
|
|
|
if (pdata->sysclk == -1) {
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto err_clk;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
prop = of_get_property(np, "mode", NULL);
|
|
|
|
if (prop && !strcmp(prop, "cpu-qe"))
|
|
|
|
pdata->qe_mode = 1;
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
ret = of_mpc8xxx_spi_get_chipselects(dev);
|
2009-03-31 22:24:37 +00:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
ret = of_address_to_resource(np, 0, &mem);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
ret = of_irq_to_resource(np, 0, &irq);
|
|
|
|
if (!ret) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
master = mpc8xxx_spi_probe(dev, &mem, irq.start);
|
2009-03-31 22:24:37 +00:00
|
|
|
if (IS_ERR(master)) {
|
|
|
|
ret = PTR_ERR(master);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
of_register_spi_devices(master, np);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
2009-06-18 23:49:08 +00:00
|
|
|
of_mpc8xxx_spi_free_chipselects(dev);
|
2009-03-31 22:24:37 +00:00
|
|
|
err_clk:
|
|
|
|
kfree(pinfo);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static int __devexit of_mpc8xxx_spi_remove(struct of_device *ofdev)
|
2009-03-31 22:24:37 +00:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
ret = mpc8xxx_spi_remove(&ofdev->dev);
|
2009-03-31 22:24:37 +00:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2009-06-18 23:49:08 +00:00
|
|
|
of_mpc8xxx_spi_free_chipselects(&ofdev->dev);
|
2009-03-31 22:24:37 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static const struct of_device_id of_mpc8xxx_spi_match[] = {
|
2009-03-31 22:24:37 +00:00
|
|
|
{ .compatible = "fsl,spi" },
|
|
|
|
{},
|
|
|
|
};
|
2009-06-18 23:49:08 +00:00
|
|
|
MODULE_DEVICE_TABLE(of, of_mpc8xxx_spi_match);
|
2009-03-31 22:24:37 +00:00
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static struct of_platform_driver of_mpc8xxx_spi_driver = {
|
|
|
|
.name = "mpc8xxx_spi",
|
|
|
|
.match_table = of_mpc8xxx_spi_match,
|
|
|
|
.probe = of_mpc8xxx_spi_probe,
|
|
|
|
.remove = __devexit_p(of_mpc8xxx_spi_remove),
|
2009-03-31 22:24:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_MPC832x_RDB
|
|
|
|
/*
|
|
|
|
* XXX XXX XXX
|
|
|
|
* This is "legacy" platform driver, was used by the MPC8323E-RDB boards
|
|
|
|
* only. The driver should go away soon, since newer MPC8323E-RDB's device
|
|
|
|
* tree can work with OpenFirmware driver. But for now we support old trees
|
|
|
|
* as well.
|
|
|
|
*/
|
2009-06-18 23:49:08 +00:00
|
|
|
static int __devinit plat_mpc8xxx_spi_probe(struct platform_device *pdev)
|
2009-03-31 22:24:37 +00:00
|
|
|
{
|
|
|
|
struct resource *mem;
|
|
|
|
unsigned int irq;
|
|
|
|
struct spi_master *master;
|
|
|
|
|
|
|
|
if (!pdev->dev.platform_data)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
if (!mem)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
|
|
if (!irq)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
master = mpc8xxx_spi_probe(&pdev->dev, mem, irq);
|
2009-03-31 22:24:37 +00:00
|
|
|
if (IS_ERR(master))
|
|
|
|
return PTR_ERR(master);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static int __devexit plat_mpc8xxx_spi_remove(struct platform_device *pdev)
|
2009-03-31 22:24:37 +00:00
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
return mpc8xxx_spi_remove(&pdev->dev);
|
2009-03-31 22:24:37 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
MODULE_ALIAS("platform:mpc8xxx_spi");
|
|
|
|
static struct platform_driver mpc8xxx_spi_driver = {
|
|
|
|
.probe = plat_mpc8xxx_spi_probe,
|
|
|
|
.remove = __exit_p(plat_mpc8xxx_spi_remove),
|
2006-05-20 22:00:15 +00:00
|
|
|
.driver = {
|
2009-06-18 23:49:08 +00:00
|
|
|
.name = "mpc8xxx_spi",
|
2008-04-11 04:29:20 +00:00
|
|
|
.owner = THIS_MODULE,
|
2006-05-20 22:00:15 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2009-03-31 22:24:37 +00:00
|
|
|
static bool legacy_driver_failed;
|
|
|
|
|
|
|
|
static void __init legacy_driver_register(void)
|
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
legacy_driver_failed = platform_driver_register(&mpc8xxx_spi_driver);
|
2009-03-31 22:24:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit legacy_driver_unregister(void)
|
|
|
|
{
|
|
|
|
if (legacy_driver_failed)
|
|
|
|
return;
|
2009-06-18 23:49:08 +00:00
|
|
|
platform_driver_unregister(&mpc8xxx_spi_driver);
|
2009-03-31 22:24:37 +00:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
static void __init legacy_driver_register(void) {}
|
|
|
|
static void __exit legacy_driver_unregister(void) {}
|
|
|
|
#endif /* CONFIG_MPC832x_RDB */
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static int __init mpc8xxx_spi_init(void)
|
2006-05-20 22:00:15 +00:00
|
|
|
{
|
2009-03-31 22:24:37 +00:00
|
|
|
legacy_driver_register();
|
2009-06-18 23:49:08 +00:00
|
|
|
return of_register_platform_driver(&of_mpc8xxx_spi_driver);
|
2006-05-20 22:00:15 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
static void __exit mpc8xxx_spi_exit(void)
|
2006-05-20 22:00:15 +00:00
|
|
|
{
|
2009-06-18 23:49:08 +00:00
|
|
|
of_unregister_platform_driver(&of_mpc8xxx_spi_driver);
|
2009-03-31 22:24:37 +00:00
|
|
|
legacy_driver_unregister();
|
2006-05-20 22:00:15 +00:00
|
|
|
}
|
|
|
|
|
2009-06-18 23:49:08 +00:00
|
|
|
module_init(mpc8xxx_spi_init);
|
|
|
|
module_exit(mpc8xxx_spi_exit);
|
2006-05-20 22:00:15 +00:00
|
|
|
|
|
|
|
MODULE_AUTHOR("Kumar Gala");
|
2009-06-18 23:49:08 +00:00
|
|
|
MODULE_DESCRIPTION("Simple MPC8xxx SPI Driver");
|
2006-05-20 22:00:15 +00:00
|
|
|
MODULE_LICENSE("GPL");
|