/* linux/drivers/spi/spi_s3c24xx.c * * Copyright (c) 2006 Ben Dooks * Copyright (c) 2006 Simtec Electronics * Ben Dooks * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct s3c24xx_spi { /* bitbang has to be first */ struct spi_bitbang bitbang; struct completion done; void __iomem *regs; int irq; int len; int count; void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol); /* data buffers */ const unsigned char *tx; unsigned char *rx; struct clk *clk; struct resource *ioarea; struct spi_master *master; struct spi_device *curdev; struct device *dev; struct s3c2410_spi_info *pdata; }; #define SPCON_DEFAULT (S3C2410_SPCON_MSTR | S3C2410_SPCON_SMOD_INT) #define SPPIN_DEFAULT (S3C2410_SPPIN_KEEP) static inline struct s3c24xx_spi *to_hw(struct spi_device *sdev) { return spi_master_get_devdata(sdev->master); } static void s3c24xx_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol) { gpio_set_value(spi->pin_cs, pol); } static void s3c24xx_spi_chipsel(struct spi_device *spi, int value) { struct s3c24xx_spi *hw = to_hw(spi); unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0; unsigned int spcon; switch (value) { case BITBANG_CS_INACTIVE: hw->set_cs(hw->pdata, spi->chip_select, cspol^1); 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); hw->set_cs(hw->pdata, spi->chip_select, cspol); break; } } static int s3c24xx_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t) { struct s3c24xx_spi *hw = to_hw(spi); unsigned int bpw; unsigned int hz; unsigned int div; bpw = t ? t->bits_per_word : spi->bits_per_word; hz = t ? t->speed_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; /* is clk = pclk / (2 * (pre+1)), or is it * clk = (pclk * 2) / ( pre + 1) */ div /= 2; if (div > 0) div -= 1; if (div > 255) div = 255; dev_dbg(&spi->dev, "setting pre-scaler to %d (hz %d)\n", div, hz); writeb(div, hw->regs + S3C2410_SPPRE); 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; } /* the spi->mode bits understood by this driver: */ #define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH) static int s3c24xx_spi_setup(struct spi_device *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; } ret = s3c24xx_spi_setupxfer(spi, NULL); if (ret < 0) { dev_err(&spi->dev, "setupxfer returned %d\n", ret); return ret; } dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n", __func__, spi->mode, spi->bits_per_word, spi->max_speed_hz); return 0; } static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count) { return hw->tx ? hw->tx[count] : 0; } static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t) { struct s3c24xx_spi *hw = to_hw(spi); dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n", t->tx_buf, t->rx_buf, t->len); hw->tx = t->tx_buf; hw->rx = t->rx_buf; 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; } static irqreturn_t s3c24xx_spi_irq(int irq, void *dev) { struct s3c24xx_spi *hw = dev; unsigned int spsta = readb(hw->regs + S3C2410_SPSTA); unsigned int count = hw->count; if (spsta & S3C2410_SPSTA_DCOL) { dev_dbg(hw->dev, "data-collision\n"); complete(&hw->done); goto irq_done; } if (!(spsta & S3C2410_SPSTA_READY)) { dev_dbg(hw->dev, "spi not ready for tx?\n"); complete(&hw->done); goto irq_done; } hw->count++; if (hw->rx) hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT); count++; if (count < hw->len) writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT); else complete(&hw->done); irq_done: 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 resource *res; int err = 0; master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi)); if (master == NULL) { dev_err(&pdev->dev, "No memory for spi_master\n"); err = -ENOMEM; goto err_nomem; } hw = spi_master_get_devdata(master); memset(hw, 0, sizeof(struct s3c24xx_spi)); hw->master = spi_master_get(master); hw->pdata = pdata = pdev->dev.platform_data; hw->dev = &pdev->dev; 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. */ 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; dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang); /* find and map our resources */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); err = -ENOENT; goto err_no_iores; } hw->ioarea = request_mem_region(res->start, (res->end - res->start)+1, pdev->name); if (hw->ioarea == NULL) { dev_err(&pdev->dev, "Cannot reserve region\n"); err = -ENXIO; goto err_no_iores; } hw->regs = ioremap(res->start, (res->end - res->start)+1); if (hw->regs == NULL) { dev_err(&pdev->dev, "Cannot map IO\n"); err = -ENXIO; goto err_no_iomap; } hw->irq = platform_get_irq(pdev, 0); if (hw->irq < 0) { dev_err(&pdev->dev, "No IRQ specified\n"); err = -ENOENT; goto err_no_irq; } err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw); if (err) { dev_err(&pdev->dev, "Cannot claim IRQ\n"); goto err_no_irq; } hw->clk = clk_get(&pdev->dev, "spi"); if (IS_ERR(hw->clk)) { dev_err(&pdev->dev, "No clock for device\n"); err = PTR_ERR(hw->clk); goto err_no_clk; } /* setup any gpio we can */ if (!pdata->set_cs) { if (pdata->pin_cs < 0) { dev_err(&pdev->dev, "No chipselect pin\n"); goto err_register; } 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; } hw->set_cs = s3c24xx_spi_gpiocs; gpio_direction_output(pdata->pin_cs, 1); } else hw->set_cs = pdata->set_cs; s3c24xx_spi_initialsetup(hw); /* register our spi controller */ err = spi_bitbang_start(&hw->bitbang); if (err) { dev_err(&pdev->dev, "Failed to register SPI master\n"); goto err_register; } 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_clk: free_irq(hw->irq, hw); err_no_irq: iounmap(hw->regs); err_no_iomap: release_resource(hw->ioarea); kfree(hw->ioarea); err_no_iores: err_no_pdata: spi_master_put(hw->master);; err_nomem: return err; } static int __exit s3c24xx_spi_remove(struct platform_device *dev) { struct s3c24xx_spi *hw = platform_get_drvdata(dev); platform_set_drvdata(dev, NULL); spi_unregister_master(hw->master); clk_disable(hw->clk); clk_put(hw->clk); 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); spi_master_put(hw->master); return 0; } #ifdef CONFIG_PM static int s3c24xx_spi_suspend(struct platform_device *pdev, pm_message_t msg) { struct s3c24xx_spi *hw = platform_get_drvdata(pdev); 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) { struct s3c24xx_spi *hw = platform_get_drvdata(pdev); s3c24xx_spi_initialsetup(hw); return 0; } #else #define s3c24xx_spi_suspend NULL #define s3c24xx_spi_resume NULL #endif 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, }, }; static int __init s3c24xx_spi_init(void) { return platform_driver_probe(&s3c24xx_spi_driver, s3c24xx_spi_probe); } static void __exit s3c24xx_spi_exit(void) { platform_driver_unregister(&s3c24xx_spi_driver); } module_init(s3c24xx_spi_init); module_exit(s3c24xx_spi_exit); MODULE_DESCRIPTION("S3C24XX SPI Driver"); MODULE_AUTHOR("Ben Dooks, "); MODULE_LICENSE("GPL");