#include <linux/err.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
-#include <asm/io.h>
-#include <asm/dma.h>
-#include <asm/hardware.h>
-
-#include <asm/arch/regs-gpio.h>
-#include <asm/plat-s3c24xx/regs-spi.h>
-#include <asm/arch/spi.h>
+#include <plat/regs-spi.h>
+#include <mach/spi.h>
+
+/**
+ * s3c24xx_spi_devstate - per device data
+ * @hz: Last frequency calculated for @sppre field.
+ * @mode: Last mode setting for the @spcon field.
+ * @spcon: Value to write to the SPCON register.
+ * @sppre: Value to write to the SPPRE register.
+ */
+struct s3c24xx_spi_devstate {
+ unsigned int hz;
+ unsigned int mode;
+ u8 spcon;
+ u8 sppre;
+};
struct s3c24xx_spi {
/* bitbang has to be first */
static void s3c24xx_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol)
{
- s3c2410_gpio_setpin(spi->pin_cs, pol);
+ gpio_set_value(spi->pin_cs, pol);
}
static void s3c24xx_spi_chipsel(struct spi_device *spi, int value)
{
+ struct s3c24xx_spi_devstate *cs = spi->controller_state;
struct s3c24xx_spi *hw = to_hw(spi);
unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0;
- unsigned int spcon;
+
+ /* change the chipselect state and the state of the spi engine clock */
switch (value) {
case BITBANG_CS_INACTIVE:
hw->set_cs(hw->pdata, spi->chip_select, cspol^1);
+ writeb(cs->spcon, hw->regs + S3C2410_SPCON);
break;
case BITBANG_CS_ACTIVE:
- spcon = readb(hw->regs + S3C2410_SPCON);
-
- if (spi->mode & SPI_CPHA)
- spcon |= S3C2410_SPCON_CPHA_FMTB;
- else
- spcon &= ~S3C2410_SPCON_CPHA_FMTB;
-
- if (spi->mode & SPI_CPOL)
- spcon |= S3C2410_SPCON_CPOL_HIGH;
- else
- spcon &= ~S3C2410_SPCON_CPOL_HIGH;
-
- spcon |= S3C2410_SPCON_ENSCK;
-
- /* write new configration */
-
- writeb(spcon, hw->regs + S3C2410_SPCON);
+ writeb(cs->spcon | S3C2410_SPCON_ENSCK,
+ hw->regs + S3C2410_SPCON);
hw->set_cs(hw->pdata, spi->chip_select, cspol);
-
break;
}
}
-static int s3c24xx_spi_setupxfer(struct spi_device *spi,
- struct spi_transfer *t)
+static int s3c24xx_spi_update_state(struct spi_device *spi,
+ struct spi_transfer *t)
{
struct s3c24xx_spi *hw = to_hw(spi);
+ struct s3c24xx_spi_devstate *cs = spi->controller_state;
unsigned int bpw;
unsigned int hz;
unsigned int div;
+ unsigned long clk;
bpw = t ? t->bits_per_word : spi->bits_per_word;
hz = t ? t->speed_hz : spi->max_speed_hz;
+ if (!bpw)
+ bpw = 8;
+
+ if (!hz)
+ hz = spi->max_speed_hz;
+
if (bpw != 8) {
dev_err(&spi->dev, "invalid bits-per-word (%d)\n", bpw);
return -EINVAL;
}
- div = clk_get_rate(hw->clk) / hz;
+ if (spi->mode != cs->mode) {
+ u8 spcon = SPCON_DEFAULT;
- /* is clk = pclk / (2 * (pre+1)), or is it
- * clk = (pclk * 2) / ( pre + 1) */
+ if (spi->mode & SPI_CPHA)
+ spcon |= S3C2410_SPCON_CPHA_FMTB;
- div = (div / 2) - 1;
+ if (spi->mode & SPI_CPOL)
+ spcon |= S3C2410_SPCON_CPOL_HIGH;
- if (div < 0)
- div = 1;
+ cs->mode = spi->mode;
+ cs->spcon = spcon;
+ }
- if (div > 255)
- div = 255;
+ if (cs->hz != hz) {
+ clk = clk_get_rate(hw->clk);
+ div = DIV_ROUND_UP(clk, hz * 2) - 1;
- dev_dbg(&spi->dev, "setting pre-scaler to %d (hz %d)\n", div, hz);
- writeb(div, hw->regs + S3C2410_SPPRE);
+ if (div > 255)
+ div = 255;
- spin_lock(&hw->bitbang.lock);
- if (!hw->bitbang.busy) {
- hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);
- /* need to ndelay for 0.5 clocktick ? */
+ dev_dbg(&spi->dev, "pre-scaler=%d (wanted %d, got %ld)\n",
+ div, hz, clk / (2 * (div + 1)));
+
+ cs->hz = hz;
+ cs->sppre = div;
}
- spin_unlock(&hw->bitbang.lock);
return 0;
}
-/* the spi->mode bits understood by this driver: */
-#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)
+static int s3c24xx_spi_setupxfer(struct spi_device *spi,
+ struct spi_transfer *t)
+{
+ struct s3c24xx_spi_devstate *cs = spi->controller_state;
+ struct s3c24xx_spi *hw = to_hw(spi);
+ int ret;
+
+ ret = s3c24xx_spi_update_state(spi, t);
+ if (!ret)
+ writeb(cs->sppre, hw->regs + S3C2410_SPPRE);
+
+ return ret;
+}
static int s3c24xx_spi_setup(struct spi_device *spi)
{
+ struct s3c24xx_spi_devstate *cs = spi->controller_state;
+ struct s3c24xx_spi *hw = to_hw(spi);
int ret;
- if (!spi->bits_per_word)
- spi->bits_per_word = 8;
-
- if (spi->mode & ~MODEBITS) {
- dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n",
- spi->mode & ~MODEBITS);
- return -EINVAL;
+ /* allocate settings on the first call */
+ if (!cs) {
+ cs = kzalloc(sizeof(struct s3c24xx_spi_devstate), GFP_KERNEL);
+ if (!cs) {
+ dev_err(&spi->dev, "no memory for controller state\n");
+ return -ENOMEM;
+ }
+
+ cs->spcon = SPCON_DEFAULT;
+ cs->hz = -1;
+ spi->controller_state = cs;
}
- ret = s3c24xx_spi_setupxfer(spi, NULL);
- if (ret < 0) {
- dev_err(&spi->dev, "setupxfer returned %d\n", ret);
+ /* initialise the state from the device */
+ ret = s3c24xx_spi_update_state(spi, NULL);
+ if (ret)
return ret;
- }
- dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n",
- __FUNCTION__, spi->mode, spi->bits_per_word,
- spi->max_speed_hz);
+ spin_lock(&hw->bitbang.lock);
+ if (!hw->bitbang.busy) {
+ hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);
+ /* need to ndelay for 0.5 clocktick ? */
+ }
+ spin_unlock(&hw->bitbang.lock);
return 0;
}
+static void s3c24xx_spi_cleanup(struct spi_device *spi)
+{
+ kfree(spi->controller_state);
+}
+
static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count)
{
return hw->tx ? hw->tx[count] : 0;
hw->len = t->len;
hw->count = 0;
+ init_completion(&hw->done);
+
/* send the first byte */
writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT);
+
wait_for_completion(&hw->done);
return hw->count;
return IRQ_HANDLED;
}
+static void s3c24xx_spi_initialsetup(struct s3c24xx_spi *hw)
+{
+ /* for the moment, permanently enable the clock */
+
+ clk_enable(hw->clk);
+
+ /* program defaults into the registers */
+
+ writeb(0xff, hw->regs + S3C2410_SPPRE);
+ writeb(SPPIN_DEFAULT, hw->regs + S3C2410_SPPIN);
+ writeb(SPCON_DEFAULT, hw->regs + S3C2410_SPCON);
+
+ if (hw->pdata) {
+ if (hw->set_cs == s3c24xx_spi_gpiocs)
+ gpio_direction_output(hw->pdata->pin_cs, 1);
+
+ if (hw->pdata->gpio_setup)
+ hw->pdata->gpio_setup(hw->pdata, 1);
+ }
+}
+
static int __init s3c24xx_spi_probe(struct platform_device *pdev)
{
+ struct s3c2410_spi_info *pdata;
struct s3c24xx_spi *hw;
struct spi_master *master;
- struct spi_board_info *bi;
struct resource *res;
int err = 0;
- int i;
master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));
if (master == NULL) {
memset(hw, 0, sizeof(struct s3c24xx_spi));
hw->master = spi_master_get(master);
- hw->pdata = pdev->dev.platform_data;
+ hw->pdata = pdata = pdev->dev.platform_data;
hw->dev = &pdev->dev;
- if (hw->pdata == NULL) {
+ if (pdata == NULL) {
dev_err(&pdev->dev, "No platform data supplied\n");
err = -ENOENT;
goto err_no_pdata;
platform_set_drvdata(pdev, hw);
init_completion(&hw->done);
+ /* setup the master state. */
+
+ /* the spi->mode bits understood by this driver: */
+ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
+
+ master->num_chipselect = hw->pdata->num_cs;
+ master->bus_num = pdata->bus_num;
+
/* setup the state for the bitbang driver */
hw->bitbang.master = hw->master;
hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;
hw->bitbang.chipselect = s3c24xx_spi_chipsel;
hw->bitbang.txrx_bufs = s3c24xx_spi_txrx;
- hw->bitbang.master->setup = s3c24xx_spi_setup;
+
+ hw->master->setup = s3c24xx_spi_setup;
+ hw->master->cleanup = s3c24xx_spi_cleanup;
dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);
goto err_no_iores;
}
- hw->ioarea = request_mem_region(res->start, (res->end - res->start)+1,
+ hw->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
if (hw->ioarea == NULL) {
goto err_no_iores;
}
- hw->regs = ioremap(res->start, (res->end - res->start)+1);
+ hw->regs = ioremap(res->start, resource_size(res));
if (hw->regs == NULL) {
dev_err(&pdev->dev, "Cannot map IO\n");
err = -ENXIO;
goto err_no_clk;
}
- /* for the moment, permanently enable the clock */
-
- clk_enable(hw->clk);
-
- /* program defaults into the registers */
+ /* setup any gpio we can */
- writeb(0xff, hw->regs + S3C2410_SPPRE);
- writeb(SPPIN_DEFAULT, hw->regs + S3C2410_SPPIN);
- writeb(SPCON_DEFAULT, hw->regs + S3C2410_SPCON);
+ if (!pdata->set_cs) {
+ if (pdata->pin_cs < 0) {
+ dev_err(&pdev->dev, "No chipselect pin\n");
+ goto err_register;
+ }
- /* setup any gpio we can */
+ err = gpio_request(pdata->pin_cs, dev_name(&pdev->dev));
+ if (err) {
+ dev_err(&pdev->dev, "Failed to get gpio for cs\n");
+ goto err_register;
+ }
- if (!hw->pdata->set_cs) {
hw->set_cs = s3c24xx_spi_gpiocs;
-
- s3c2410_gpio_setpin(hw->pdata->pin_cs, 1);
- s3c2410_gpio_cfgpin(hw->pdata->pin_cs, S3C2410_GPIO_OUTPUT);
+ gpio_direction_output(pdata->pin_cs, 1);
} else
- hw->set_cs = hw->pdata->set_cs;
+ hw->set_cs = pdata->set_cs;
+
+ s3c24xx_spi_initialsetup(hw);
/* register our spi controller */
goto err_register;
}
- /* register all the devices associated */
-
- bi = &hw->pdata->board_info[0];
- for (i = 0; i < hw->pdata->board_size; i++, bi++) {
- dev_info(hw->dev, "registering %s\n", bi->modalias);
-
- bi->controller_data = hw;
- spi_new_device(master, bi);
- }
-
return 0;
err_register:
+ if (hw->set_cs == s3c24xx_spi_gpiocs)
+ gpio_free(pdata->pin_cs);
+
clk_disable(hw->clk);
clk_put(hw->clk);
err_no_iores:
err_no_pdata:
- spi_master_put(hw->master);;
+ spi_master_put(hw->master);
err_nomem:
return err;
free_irq(hw->irq, hw);
iounmap(hw->regs);
+ if (hw->set_cs == s3c24xx_spi_gpiocs)
+ gpio_free(hw->pdata->pin_cs);
+
release_resource(hw->ioarea);
kfree(hw->ioarea);
#ifdef CONFIG_PM
-static int s3c24xx_spi_suspend(struct platform_device *pdev, pm_message_t msg)
+static int s3c24xx_spi_suspend(struct device *dev)
{
- struct s3c24xx_spi *hw = platform_get_drvdata(pdev);
+ struct s3c24xx_spi *hw = platform_get_drvdata(to_platform_device(dev));
+
+ if (hw->pdata && hw->pdata->gpio_setup)
+ hw->pdata->gpio_setup(hw->pdata, 0);
clk_disable(hw->clk);
return 0;
}
-static int s3c24xx_spi_resume(struct platform_device *pdev)
+static int s3c24xx_spi_resume(struct device *dev)
{
- struct s3c24xx_spi *hw = platform_get_drvdata(pdev);
+ struct s3c24xx_spi *hw = platform_get_drvdata(to_platform_device(dev));
- clk_enable(hw->clk);
+ s3c24xx_spi_initialsetup(hw);
return 0;
}
+static struct dev_pm_ops s3c24xx_spi_pmops = {
+ .suspend = s3c24xx_spi_suspend,
+ .resume = s3c24xx_spi_resume,
+};
+
+#define S3C24XX_SPI_PMOPS &s3c24xx_spi_pmops
#else
-#define s3c24xx_spi_suspend NULL
-#define s3c24xx_spi_resume NULL
-#endif
+#define S3C24XX_SPI_PMOPS NULL
+#endif /* CONFIG_PM */
-MODULE_ALIAS("s3c2410_spi"); /* for platform bus hotplug */
-static struct platform_driver s3c24xx_spidrv = {
+MODULE_ALIAS("platform:s3c2410-spi");
+static struct platform_driver s3c24xx_spi_driver = {
.remove = __exit_p(s3c24xx_spi_remove),
- .suspend = s3c24xx_spi_suspend,
- .resume = s3c24xx_spi_resume,
.driver = {
.name = "s3c2410-spi",
.owner = THIS_MODULE,
+ .pm = S3C24XX_SPI_PMOPS,
},
};
static int __init s3c24xx_spi_init(void)
{
- return platform_driver_probe(&s3c24xx_spidrv, s3c24xx_spi_probe);
+ return platform_driver_probe(&s3c24xx_spi_driver, s3c24xx_spi_probe);
}
static void __exit s3c24xx_spi_exit(void)
{
- platform_driver_unregister(&s3c24xx_spidrv);
+ platform_driver_unregister(&s3c24xx_spi_driver);
}
module_init(s3c24xx_spi_init);