diff options
author | Nicolas Pitre <nicolas.pitre@linaro.org> | 2011-03-25 16:44:07 -0400 |
---|---|---|
committer | Nicolas Pitre <nicolas.pitre@linaro.org> | 2011-03-25 16:44:07 -0400 |
commit | 0cd3fd78e5951c1631ee60a2026603d63c671d98 (patch) | |
tree | 5569b2135a8920aee9b4456691e995a499c31131 /drivers | |
parent | a90b3dcb115ec561b6bae4874e63ddc51720128b (diff) | |
parent | 9ced9f03d12d7539e86b0bff5bc750153c976c34 (diff) |
Merge commit '9ced9f0' (rmk/devel-stable) into linaro-2.6.38
Conflicts:
arch/arm/mach-mxs/gpio.c
drivers/usb/gadget/fsl_mxc_udc.c
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mfd/Kconfig | 11 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/ti-ssp.c | 476 | ||||
-rw-r--r-- | drivers/mtd/nand/Kconfig | 2 | ||||
-rw-r--r-- | drivers/mtd/onenand/Kconfig | 2 | ||||
-rw-r--r-- | drivers/net/Kconfig | 2 | ||||
-rw-r--r-- | drivers/spi/Kconfig | 16 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/davinci_spi.c | 11 | ||||
-rw-r--r-- | drivers/spi/ti-ssp-spi.c | 402 | ||||
-rw-r--r-- | drivers/tty/serial/Kconfig | 19 | ||||
-rw-r--r-- | drivers/tty/serial/Makefile | 1 | ||||
-rw-r--r-- | drivers/tty/serial/mxs-auart.c | 798 | ||||
-rw-r--r-- | drivers/usb/host/ehci-mxc.c | 5 | ||||
-rw-r--r-- | drivers/video/Kconfig | 9 | ||||
-rw-r--r-- | drivers/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/mxsfb.c | 919 |
17 files changed, 2655 insertions, 21 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a6dfa37a674..fdca643249e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -81,6 +81,17 @@ config MFD_DM355EVM_MSP boards. MSP430 firmware manages resets and power sequencing, inputs from buttons and the IR remote, LEDs, an RTC, and more. +config MFD_TI_SSP + tristate "TI Sequencer Serial Port support" + depends on ARCH_DAVINCI_TNETV107X + select MFD_CORE + ---help--- + Say Y here if you want support for the Sequencer Serial Port + in a Texas Instruments TNETV107X SoC. + + To compile this driver as a module, choose M here: the + module will be called ti-ssp. + config HTC_EGPIO bool "HTC EGPIO support" depends on GENERIC_HARDIRQS && GPIOLIB && ARM diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 91fe384459a..f0e25cad762 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o obj-$(CONFIG_MFD_DAVINCI_VOICECODEC) += davinci_voicecodec.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o +obj-$(CONFIG_MFD_TI_SSP) += ti-ssp.o obj-$(CONFIG_MFD_STMPE) += stmpe.o obj-$(CONFIG_MFD_TC3589X) += tc3589x.o diff --git a/drivers/mfd/ti-ssp.c b/drivers/mfd/ti-ssp.c new file mode 100644 index 00000000000..af9ab0e5ca6 --- /dev/null +++ b/drivers/mfd/ti-ssp.c @@ -0,0 +1,476 @@ +/* + * Sequencer Serial Port (SSP) driver for Texas Instruments' SoCs + * + * Copyright (C) 2010 Texas Instruments 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mfd/core.h> +#include <linux/mfd/ti_ssp.h> + +/* Register Offsets */ +#define REG_REV 0x00 +#define REG_IOSEL_1 0x04 +#define REG_IOSEL_2 0x08 +#define REG_PREDIV 0x0c +#define REG_INTR_ST 0x10 +#define REG_INTR_EN 0x14 +#define REG_TEST_CTRL 0x18 + +/* Per port registers */ +#define PORT_CFG_2 0x00 +#define PORT_ADDR 0x04 +#define PORT_DATA 0x08 +#define PORT_CFG_1 0x0c +#define PORT_STATE 0x10 + +#define SSP_PORT_CONFIG_MASK (SSP_EARLY_DIN | SSP_DELAY_DOUT) +#define SSP_PORT_CLKRATE_MASK 0x0f + +#define SSP_SEQRAM_WR_EN BIT(4) +#define SSP_SEQRAM_RD_EN BIT(5) +#define SSP_START BIT(15) +#define SSP_BUSY BIT(10) +#define SSP_PORT_ASL BIT(7) +#define SSP_PORT_CFO1 BIT(6) + +#define SSP_PORT_SEQRAM_SIZE 32 + +static const int ssp_port_base[] = {0x040, 0x080}; +static const int ssp_port_seqram[] = {0x100, 0x180}; + +struct ti_ssp { + struct resource *res; + struct device *dev; + void __iomem *regs; + spinlock_t lock; + struct clk *clk; + int irq; + wait_queue_head_t wqh; + + /* + * Some of the iosel2 register bits always read-back as 0, we need to + * remember these values so that we don't clobber previously set + * values. + */ + u32 iosel2; +}; + +static inline struct ti_ssp *dev_to_ssp(struct device *dev) +{ + return dev_get_drvdata(dev->parent); +} + +static inline int dev_to_port(struct device *dev) +{ + return to_platform_device(dev)->id; +} + +/* Register Access Helpers, rmw() functions need to run locked */ +static inline u32 ssp_read(struct ti_ssp *ssp, int reg) +{ + return __raw_readl(ssp->regs + reg); +} + +static inline void ssp_write(struct ti_ssp *ssp, int reg, u32 val) +{ + __raw_writel(val, ssp->regs + reg); +} + +static inline void ssp_rmw(struct ti_ssp *ssp, int reg, u32 mask, u32 bits) +{ + ssp_write(ssp, reg, (ssp_read(ssp, reg) & ~mask) | bits); +} + +static inline u32 ssp_port_read(struct ti_ssp *ssp, int port, int reg) +{ + return ssp_read(ssp, ssp_port_base[port] + reg); +} + +static inline void ssp_port_write(struct ti_ssp *ssp, int port, int reg, + u32 val) +{ + ssp_write(ssp, ssp_port_base[port] + reg, val); +} + +static inline void ssp_port_rmw(struct ti_ssp *ssp, int port, int reg, + u32 mask, u32 bits) +{ + ssp_rmw(ssp, ssp_port_base[port] + reg, mask, bits); +} + +static inline void ssp_port_clr_bits(struct ti_ssp *ssp, int port, int reg, + u32 bits) +{ + ssp_port_rmw(ssp, port, reg, bits, 0); +} + +static inline void ssp_port_set_bits(struct ti_ssp *ssp, int port, int reg, + u32 bits) +{ + ssp_port_rmw(ssp, port, reg, 0, bits); +} + +/* Called to setup port clock mode, caller must hold ssp->lock */ +static int __set_mode(struct ti_ssp *ssp, int port, int mode) +{ + mode &= SSP_PORT_CONFIG_MASK; + ssp_port_rmw(ssp, port, PORT_CFG_1, SSP_PORT_CONFIG_MASK, mode); + + return 0; +} + +int ti_ssp_set_mode(struct device *dev, int mode) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev); + int ret; + + spin_lock(&ssp->lock); + ret = __set_mode(ssp, port, mode); + spin_unlock(&ssp->lock); + + return ret; +} +EXPORT_SYMBOL(ti_ssp_set_mode); + +/* Called to setup iosel2, caller must hold ssp->lock */ +static void __set_iosel2(struct ti_ssp *ssp, u32 mask, u32 val) +{ + ssp->iosel2 = (ssp->iosel2 & ~mask) | val; + ssp_write(ssp, REG_IOSEL_2, ssp->iosel2); +} + +/* Called to setup port iosel, caller must hold ssp->lock */ +static void __set_iosel(struct ti_ssp *ssp, int port, u32 iosel) +{ + unsigned val, shift = port ? 16 : 0; + + /* IOSEL1 gets the least significant 16 bits */ + val = ssp_read(ssp, REG_IOSEL_1); + val &= 0xffff << (port ? 0 : 16); + val |= (iosel & 0xffff) << (port ? 16 : 0); + ssp_write(ssp, REG_IOSEL_1, val); + + /* IOSEL2 gets the most significant 16 bits */ + val = (iosel >> 16) & 0x7; + __set_iosel2(ssp, 0x7 << shift, val << shift); +} + +int ti_ssp_set_iosel(struct device *dev, u32 iosel) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev); + + spin_lock(&ssp->lock); + __set_iosel(ssp, port, iosel); + spin_unlock(&ssp->lock); + + return 0; +} +EXPORT_SYMBOL(ti_ssp_set_iosel); + +int ti_ssp_load(struct device *dev, int offs, u32* prog, int len) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev); + int i; + + if (len > SSP_PORT_SEQRAM_SIZE) + return -ENOSPC; + + spin_lock(&ssp->lock); + + /* Enable SeqRAM access */ + ssp_port_set_bits(ssp, port, PORT_CFG_2, SSP_SEQRAM_WR_EN); + + /* Copy code */ + for (i = 0; i < len; i++) { + __raw_writel(prog[i], ssp->regs + offs + 4*i + + ssp_port_seqram[port]); + } + + /* Disable SeqRAM access */ + ssp_port_clr_bits(ssp, port, PORT_CFG_2, SSP_SEQRAM_WR_EN); + + spin_unlock(&ssp->lock); + + return 0; +} +EXPORT_SYMBOL(ti_ssp_load); + +int ti_ssp_raw_read(struct device *dev) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev); + int shift = port ? 27 : 11; + + return (ssp_read(ssp, REG_IOSEL_2) >> shift) & 0xf; +} +EXPORT_SYMBOL(ti_ssp_raw_read); + +int ti_ssp_raw_write(struct device *dev, u32 val) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev), shift; + + spin_lock(&ssp->lock); + + shift = port ? 22 : 6; + val &= 0xf; + __set_iosel2(ssp, 0xf << shift, val << shift); + + spin_unlock(&ssp->lock); + + return 0; +} +EXPORT_SYMBOL(ti_ssp_raw_write); + +static inline int __xfer_done(struct ti_ssp *ssp, int port) +{ + return !(ssp_port_read(ssp, port, PORT_CFG_1) & SSP_BUSY); +} + +int ti_ssp_run(struct device *dev, u32 pc, u32 input, u32 *output) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev); + int ret; + + if (pc & ~(0x3f)) + return -EINVAL; + + /* Grab ssp->lock to serialize rmw on ssp registers */ + spin_lock(&ssp->lock); + + ssp_port_write(ssp, port, PORT_ADDR, input >> 16); + ssp_port_write(ssp, port, PORT_DATA, input & 0xffff); + ssp_port_rmw(ssp, port, PORT_CFG_1, 0x3f, pc); + + /* grab wait queue head lock to avoid race with the isr */ + spin_lock_irq(&ssp->wqh.lock); + + /* kick off sequence execution in hardware */ + ssp_port_set_bits(ssp, port, PORT_CFG_1, SSP_START); + + /* drop ssp lock; no register writes beyond this */ + spin_unlock(&ssp->lock); + + ret = wait_event_interruptible_locked_irq(ssp->wqh, + __xfer_done(ssp, port)); + spin_unlock_irq(&ssp->wqh.lock); + + if (ret < 0) + return ret; + + if (output) { + *output = (ssp_port_read(ssp, port, PORT_ADDR) << 16) | + (ssp_port_read(ssp, port, PORT_DATA) & 0xffff); + } + + ret = ssp_port_read(ssp, port, PORT_STATE) & 0x3f; /* stop address */ + + return ret; +} +EXPORT_SYMBOL(ti_ssp_run); + +static irqreturn_t ti_ssp_interrupt(int irq, void *dev_data) +{ + struct ti_ssp *ssp = dev_data; + + spin_lock(&ssp->wqh.lock); + + ssp_write(ssp, REG_INTR_ST, 0x3); + wake_up_locked(&ssp->wqh); + + spin_unlock(&ssp->wqh.lock); + + return IRQ_HANDLED; +} + +static int __devinit ti_ssp_probe(struct platform_device *pdev) +{ + static struct ti_ssp *ssp; + const struct ti_ssp_data *pdata = pdev->dev.platform_data; + int error = 0, prediv = 0xff, id; + unsigned long sysclk; + struct device *dev = &pdev->dev; + struct mfd_cell cells[2]; + + ssp = kzalloc(sizeof(*ssp), GFP_KERNEL); + if (!ssp) { + dev_err(dev, "cannot allocate device info\n"); + return -ENOMEM; + } + + ssp->dev = dev; + dev_set_drvdata(dev, ssp); + + ssp->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!ssp->res) { + error = -ENODEV; + dev_err(dev, "cannot determine register area\n"); + goto error_res; + } + + if (!request_mem_region(ssp->res->start, resource_size(ssp->res), + pdev->name)) { + error = -ENOMEM; + dev_err(dev, "cannot claim register memory\n"); + goto error_res; + } + + ssp->regs = ioremap(ssp->res->start, resource_size(ssp->res)); + if (!ssp->regs) { + error = -ENOMEM; + dev_err(dev, "cannot map register memory\n"); + goto error_map; + } + + ssp->clk = clk_get(dev, NULL); + if (IS_ERR(ssp->clk)) { + error = PTR_ERR(ssp->clk); + dev_err(dev, "cannot claim device clock\n"); + goto error_clk; + } + + ssp->irq = platform_get_irq(pdev, 0); + if (ssp->irq < 0) { + error = -ENODEV; + dev_err(dev, "unknown irq\n"); + goto error_irq; + } + + error = request_threaded_irq(ssp->irq, NULL, ti_ssp_interrupt, 0, + dev_name(dev), ssp); + if (error < 0) { + dev_err(dev, "cannot acquire irq\n"); + goto error_irq; + } + + spin_lock_init(&ssp->lock); + init_waitqueue_head(&ssp->wqh); + + /* Power on and initialize SSP */ + error = clk_enable(ssp->clk); + if (error) { + dev_err(dev, "cannot enable device clock\n"); + goto error_enable; + } + + /* Reset registers to a sensible known state */ + ssp_write(ssp, REG_IOSEL_1, 0); + ssp_write(ssp, REG_IOSEL_2, 0); + ssp_write(ssp, REG_INTR_EN, 0x3); + ssp_write(ssp, REG_INTR_ST, 0x3); + ssp_write(ssp, REG_TEST_CTRL, 0); + ssp_port_write(ssp, 0, PORT_CFG_1, SSP_PORT_ASL); + ssp_port_write(ssp, 1, PORT_CFG_1, SSP_PORT_ASL); + ssp_port_write(ssp, 0, PORT_CFG_2, SSP_PORT_CFO1); + ssp_port_write(ssp, 1, PORT_CFG_2, SSP_PORT_CFO1); + + sysclk = clk_get_rate(ssp->clk); + if (pdata && pdata->out_clock) + prediv = (sysclk / pdata->out_clock) - 1; + prediv = clamp(prediv, 0, 0xff); + ssp_rmw(ssp, REG_PREDIV, 0xff, prediv); + + memset(cells, 0, sizeof(cells)); + for (id = 0; id < 2; id++) { + const struct ti_ssp_dev_data *data = &pdata->dev_data[id]; + + cells[id].id = id; + cells[id].name = data->dev_name; + cells[id].platform_data = data->pdata; + cells[id].data_size = data->pdata_size; + } + + error = mfd_add_devices(dev, 0, cells, 2, NULL, 0); + if (error < 0) { + dev_err(dev, "cannot add mfd cells\n"); + goto error_enable; + } + + return 0; + +error_enable: + free_irq(ssp->irq, ssp); +error_irq: + clk_put(ssp->clk); +error_clk: + iounmap(ssp->regs); +error_map: + release_mem_region(ssp->res->start, resource_size(ssp->res)); +error_res: + kfree(ssp); + return error; +} + +static int __devexit ti_ssp_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ti_ssp *ssp = dev_get_drvdata(dev); + + mfd_remove_devices(dev); + clk_disable(ssp->clk); + free_irq(ssp->irq, ssp); + clk_put(ssp->clk); + iounmap(ssp->regs); + release_mem_region(ssp->res->start, resource_size(ssp->res)); + kfree(ssp); + dev_set_drvdata(dev, NULL); + return 0; +} + +static struct platform_driver ti_ssp_driver = { + .probe = ti_ssp_probe, + .remove = __devexit_p(ti_ssp_remove), + .driver = { + .name = "ti-ssp", + .owner = THIS_MODULE, + } +}; + +static int __init ti_ssp_init(void) +{ + return platform_driver_register(&ti_ssp_driver); +} +module_init(ti_ssp_init); + +static void __exit ti_ssp_exit(void) +{ + platform_driver_unregister(&ti_ssp_driver); +} +module_exit(ti_ssp_exit); + +MODULE_DESCRIPTION("Sequencer Serial Port (SSP) Driver"); +MODULE_AUTHOR("Cyril Chemparathy"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ti-ssp"); diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index c89592239bc..450afc5df0b 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -476,7 +476,7 @@ config MTD_NAND_MPC5121_NFC config MTD_NAND_MXC tristate "MXC NAND support" - depends on ARCH_MX2 || ARCH_MX25 || ARCH_MX3 || ARCH_MX51 + depends on IMX_HAVE_PLATFORM_MXC_NAND help This enables the driver for the NAND flash controller on the MXC processors. diff --git a/drivers/mtd/onenand/Kconfig b/drivers/mtd/onenand/Kconfig index 4dbd0f58eeb..4f426195f8d 100644 --- a/drivers/mtd/onenand/Kconfig +++ b/drivers/mtd/onenand/Kconfig @@ -32,7 +32,7 @@ config MTD_ONENAND_OMAP2 config MTD_ONENAND_SAMSUNG tristate "OneNAND on Samsung SOC controller support" - depends on ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_S5PV310 + depends on ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_EXYNOS4 help Support for a OneNAND flash device connected to an Samsung SOC. S3C64XX/S5PC100 use command mapping method. diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 03823327db2..9928115496e 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -1944,7 +1944,7 @@ config 68360_ENET config FEC bool "FEC ethernet controller (of ColdFire and some i.MX CPUs)" depends on M523x || M527x || M5272 || M528x || M520x || M532x || \ - MACH_MX27 || ARCH_MX35 || ARCH_MX25 || ARCH_MX5 || SOC_IMX28 + IMX_HAVE_PLATFORM_FEC || SOC_IMX28 select PHYLIB help Say Y here if you want to use the built-in 10/100 Fast ethernet diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index bb233a9cbad..7b90fc361b5 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -161,13 +161,13 @@ config SPI_IMX_VER_0_0 def_bool y if SOC_IMX21 || SOC_IMX27 config SPI_IMX_VER_0_4 - def_bool y if ARCH_MX31 + def_bool y if SOC_IMX31 config SPI_IMX_VER_0_7 - def_bool y if ARCH_MX25 || ARCH_MX35 || ARCH_MX51 || ARCH_MX53 + def_bool y if ARCH_MX25 || SOC_IMX35 || SOC_IMX51 || SOC_IMX53 config SPI_IMX_VER_2_3 - def_bool y if ARCH_MX51 || ARCH_MX53 + def_bool y if SOC_IMX51 || SOC_IMX53 config SPI_IMX tristate "Freescale i.MX SPI controllers" @@ -350,6 +350,16 @@ config SPI_TEGRA help SPI driver for NVidia Tegra SoCs +config SPI_TI_SSP + tristate "TI Sequencer Serial Port - SPI Support" + depends on MFD_TI_SSP + help + This selects an SPI master implementation using a TI sequencer + serial port. + + To compile this driver as a module, choose M here: the + module will be called ti-ssp-spi. + config SPI_TOPCLIFF_PCH tristate "Topcliff PCH SPI Controller" depends on PCI diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 86d1b5f9bbd..f3f31d98835 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s3c24xx_gpio.o obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx_hw.o obj-$(CONFIG_SPI_S3C64XX) += spi_s3c64xx.o obj-$(CONFIG_SPI_TEGRA) += spi_tegra.o +obj-$(CONFIG_SPI_TI_SSP) += ti-ssp-spi.o obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi_topcliff_pch.o obj-$(CONFIG_SPI_TXX9) += spi_txx9.o obj-$(CONFIG_SPI_XILINX) += xilinx_spi.o diff --git a/drivers/spi/davinci_spi.c b/drivers/spi/davinci_spi.c index 6beab99bf95..166a879fd9e 100644 --- a/drivers/spi/davinci_spi.c +++ b/drivers/spi/davinci_spi.c @@ -790,7 +790,6 @@ static int davinci_spi_probe(struct platform_device *pdev) struct resource *r, *mem; resource_size_t dma_rx_chan = SPI_NO_RESOURCE; resource_size_t dma_tx_chan = SPI_NO_RESOURCE; - resource_size_t dma_eventq = SPI_NO_RESOURCE; int i = 0, ret = 0; u32 spipc0; @@ -878,17 +877,13 @@ static int davinci_spi_probe(struct platform_device *pdev) r = platform_get_resource(pdev, IORESOURCE_DMA, 1); if (r) dma_tx_chan = r->start; - r = platform_get_resource(pdev, IORESOURCE_DMA, 2); - if (r) - dma_eventq = r->start; dspi->bitbang.txrx_bufs = davinci_spi_bufs; if (dma_rx_chan != SPI_NO_RESOURCE && - dma_tx_chan != SPI_NO_RESOURCE && - dma_eventq != SPI_NO_RESOURCE) { + dma_tx_chan != SPI_NO_RESOURCE) { dspi->dma.rx_channel = dma_rx_chan; dspi->dma.tx_channel = dma_tx_chan; - dspi->dma.eventq = dma_eventq; + dspi->dma.eventq = pdata->dma_event_q; ret = davinci_spi_request_dma(dspi); if (ret) @@ -897,7 +892,7 @@ static int davinci_spi_probe(struct platform_device *pdev) dev_info(&pdev->dev, "DMA: supported\n"); dev_info(&pdev->dev, "DMA: RX channel: %d, TX channel: %d, " "event queue: %d\n", dma_rx_chan, dma_tx_chan, - dma_eventq); + pdata->dma_event_q); } dspi->get_rx = davinci_spi_rx_buf_u8; diff --git a/drivers/spi/ti-ssp-spi.c b/drivers/spi/ti-ssp-spi.c new file mode 100644 index 00000000000..ee22795c797 --- /dev/null +++ b/drivers/spi/ti-ssp-spi.c @@ -0,0 +1,402 @@ +/* + * Sequencer Serial Port (SSP) based SPI master driver + * + * Copyright (C) 2010 Texas Instruments 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/mfd/ti_ssp.h> + +#define MODE_BITS (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH) + +struct ti_ssp_spi { + struct spi_master *master; + struct device *dev; + spinlock_t lock; + struct list_head msg_queue; + struct completion complete; + bool shutdown; + struct workqueue_struct *workqueue; + struct work_struct work; + u8 mode, bpw; + int cs_active; + u32 pc_en, pc_dis, pc_wr, pc_rd; + void (*select)(int cs); +}; + +static u32 ti_ssp_spi_rx(struct ti_ssp_spi *hw) +{ + u32 ret; + + ti_ssp_run(hw->dev, hw->pc_rd, 0, &ret); + return ret; +} + +static void ti_ssp_spi_tx(struct ti_ssp_spi *hw, u32 data) +{ + ti_ssp_run(hw->dev, hw->pc_wr, data << (32 - hw->bpw), NULL); +} + +static int ti_ssp_spi_txrx(struct ti_ssp_spi *hw, struct spi_message *msg, + struct spi_transfer *t) +{ + int count; + + if (hw->bpw <= 8) { + u8 *rx = t->rx_buf; + const u8 *tx = t->tx_buf; + + for (count = 0; count < t->len; count += 1) { + if (t->tx_buf) + ti_ssp_spi_tx(hw, *tx++); + if (t->rx_buf) + *rx++ = ti_ssp_spi_rx(hw); + } + } else if (hw->bpw <= 16) { + u16 *rx = t->rx_buf; + const u16 *tx = t->tx_buf; + + for (count = 0; count < t->len; count += 2) { + if (t->tx_buf) + ti_ssp_spi_tx(hw, *tx++); + if (t->rx_buf) + *rx++ = ti_ssp_spi_rx(hw); + } + } else { + u32 *rx = t->rx_buf; + const u32 *tx = t->tx_buf; + + for (count = 0; count < t->len; count += 4) { + if (t->tx_buf) + ti_ssp_spi_tx(hw, *tx++); + if (t->rx_buf) + *rx++ = ti_ssp_spi_rx(hw); + } + } + + msg->actual_length += count; /* bytes transferred */ + + dev_dbg(&msg->spi->dev, "xfer %s%s, %d bytes, %d bpw, count %d%s\n", + t->tx_buf ? "tx" : "", t->rx_buf ? "rx" : "", t->len, + hw->bpw, count, (count < t->len) ? " (under)" : ""); + + return (count < t->len) ? -EIO : 0; /* left over data */ +} + +static void ti_ssp_spi_chip_select(struct ti_ssp_spi *hw, int cs_active) +{ + cs_active = !!cs_active; + if (cs_active == hw->cs_active) + return; + ti_ssp_run(hw->dev, cs_active ? hw->pc_en : hw->pc_dis, 0, NULL); + hw->cs_active = cs_active; +} + +#define __SHIFT_OUT(bits) (SSP_OPCODE_SHIFT | SSP_OUT_MODE | \ + cs_en | clk | SSP_COUNT((bits) * 2 - 1)) +#define __SHIFT_IN(bits) (SSP_OPCODE_SHIFT | SSP_IN_MODE | \ + cs_en | clk | SSP_COUNT((bits) * 2 - 1)) + +static int ti_ssp_spi_setup_transfer(struct ti_ssp_spi *hw, u8 bpw, u8 mode) +{ + int error, idx = 0; + u32 seqram[16]; + u32 cs_en, cs_dis, clk; + u32 topbits, botbits; + + mode &= MODE_BITS; + if (mode == hw->mode && bpw == hw->bpw) + return 0; + + cs_en = (mode & SPI_CS_HIGH) ? SSP_CS_HIGH : SSP_CS_LOW; + cs_dis = (mode & SPI_CS_HIGH) ? SSP_CS_LOW : SSP_CS_HIGH; + clk = (mode & SPI_CPOL) ? SSP_CLK_HIGH : SSP_CLK_LOW; + + /* Construct instructions */ + + /* Disable Chip Select */ + hw->pc_dis = idx; + seqram[idx++] = SSP_OPCODE_DIRECT | SSP_OUT_MODE | cs_dis | clk; + seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_dis | clk; + + /* Enable Chip Select */ + hw->pc_en = idx; + seqram[idx++] = SSP_OPCODE_DIRECT | SSP_OUT_MODE | cs_en | clk; + seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk; + + /* Reads and writes need to be split for bpw > 16 */ + topbits = (bpw > 16) ? 16 : bpw; + botbits = bpw - topbits; + + /* Write */ + hw->pc_wr = idx; + seqram[idx++] = __SHIFT_OUT(topbits) | SSP_ADDR_REG; + if (botbits) + seqram[idx++] = __SHIFT_OUT(botbits) | SSP_DATA_REG; + seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk; + + /* Read */ + hw->pc_rd = idx; + if (botbits) + seqram[idx++] = __SHIFT_IN(botbits) | SSP_ADDR_REG; + seqram[idx++] = __SHIFT_IN(topbits) | SSP_DATA_REG; + seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk; + + error = ti_ssp_load(hw->dev, 0, seqram, idx); + if (error < 0) + return error; + + error = ti_ssp_set_mode(hw->dev, ((mode & SPI_CPHA) ? + 0 : SSP_EARLY_DIN)); + if (error < 0) + return error; + + hw->bpw = bpw; + hw->mode = mode; + + return error; +} + +static void ti_ssp_spi_work(struct work_struct *work) +{ + struct ti_ssp_spi *hw = container_of(work, struct ti_ssp_spi, work); + + spin_lock(&hw->lock); + + while (!list_empty(&hw->msg_queue)) { + struct spi_message *m; + struct spi_device *spi; + struct spi_transfer *t = NULL; + int status = 0; + + m = container_of(hw->msg_queue.next, struct spi_message, + queue); + + list_del_init(&m->queue); + + spin_unlock(&hw->lock); + + spi = m->spi; + + if (hw->select) + hw->select(spi->chip_select); + + list_for_each_entry(t, &m->transfers, transfer_list) { + int bpw = spi->bits_per_word; + int xfer_status; + + if (t->bits_per_word) + bpw = t->bits_per_word; + + if (ti_ssp_spi_setup_transfer(hw, bpw, spi->mode) < 0) + break; + + ti_ssp_spi_chip_select(hw, 1); + + xfer_status = ti_ssp_spi_txrx(hw, m, t); + if (xfer_status < 0) + status = xfer_status; + + if (t->delay_usecs) + udelay(t->delay_usecs); + + if (t->cs_change) + ti_ssp_spi_chip_select(hw, 0); + } + + ti_ssp_spi_chip_select(hw, 0); + m->status = status; + m->complete(m->context); + + spin_lock(&hw->lock); + } + + if (hw->shutdown) + complete(&hw->complete); + + spin_unlock(&hw->lock); +} + +static int ti_ssp_spi_setup(struct spi_device *spi) +{ + if (spi->bits_per_word > 32) + return -EINVAL; + + return 0; +} + +static int ti_ssp_spi_transfer(struct spi_device *spi, struct spi_message *m) +{ + struct ti_ssp_spi *hw; + struct spi_transfer *t; + int error = 0; + + m->actual_length = 0; + m->status = -EINPROGRESS; + + hw = spi_master_get_devdata(spi->master); + + if (list_empty(&m->transfers) || !m->complete) + return -EINVAL; + + list_for_each_entry(t, &m->transfers, transfer_list) { + if (t->len && !(t->rx_buf || t->tx_buf)) { + dev_err(&spi->dev, "invalid xfer, no buffer\n"); + return -EINVAL; + } + + if (t->len && t->rx_buf && t->tx_buf) { + dev_err(&spi->dev, "invalid xfer, full duplex\n"); + return -EINVAL; + } + + if (t->bits_per_word > 32) { + dev_err(&spi->dev, "invalid xfer width %d\n", + t->bits_per_word); + return -EINVAL; + } + } + + spin_lock(&hw->lock); + if (hw->shutdown) { + error = -ESHUTDOWN; + goto error_unlock; + } + list_add_tail(&m->queue, &hw->msg_queue); + queue_work(hw->workqueue, &hw->work); +error_unlock: + spin_unlock(&hw->lock); + return error; +} + +static int __devinit ti_ssp_spi_probe(struct platform_device *pdev) +{ + const struct ti_ssp_spi_data *pdata; + struct ti_ssp_spi *hw; + struct spi_master *master; + struct device *dev = &pdev->dev; + int error = 0; + + pdata = dev->platform_data; + if (!pdata) { + dev_err(dev, "platform data not found\n"); + return -EINVAL; + } + + master = spi_alloc_master(dev, sizeof(struct ti_ssp_spi)); + if (!master) { + dev_err(dev, "cannot allocate SPI master\n"); + return -ENOMEM; + } + + hw = spi_master_get_devdata(master); + platform_set_drvdata(pdev, hw); + + hw->master = master; + hw->dev = dev; + hw->select = pdata->select; + + spin_lock_init(&hw->lock); + init_completion(&hw->complete); + INIT_LIST_HEAD(&hw->msg_queue); + INIT_WORK(&hw->work, ti_ssp_spi_work); + + hw->workqueue = create_singlethread_workqueue(dev_name(dev)); + if (!hw->workqueue) { + error = -ENOMEM; + dev_err(dev, "work queue creation failed\n"); + goto error_wq; + } + + error = ti_ssp_set_iosel(hw->dev, pdata->iosel); + if (error < 0) { + dev_err(dev, "io setup failed\n"); + goto error_iosel; + } + + master->bus_num = pdev->id; + master->num_chipselect = pdata->num_cs; + master->mode_bits = MODE_BITS; + master->flags = SPI_MASTER_HALF_DUPLEX; + master->setup = ti_ssp_spi_setup; + master->transfer = ti_ssp_spi_transfer; + + error = spi_register_master(master); + if (error) { + dev_err(dev, "master registration failed\n"); + goto error_reg; + } + + return 0; + +error_reg: +error_iosel: + destroy_workqueue(hw->workqueue); +error_wq: + spi_master_put(master); + return error; +} + +static int __devexit ti_ssp_spi_remove(struct platform_device *pdev) +{ + struct ti_ssp_spi *hw = platform_get_drvdata(pdev); + int error; + + hw->shutdown = 1; + while (!list_empty(&hw->msg_queue)) { + error = wait_for_completion_interruptible(&hw->complete); + if (error < 0) { + hw->shutdown = 0; + return error; + } + } + destroy_workqueue(hw->workqueue); + spi_unregister_master(hw->master); + + return 0; +} + +static struct platform_driver ti_ssp_spi_driver = { + .probe = ti_ssp_spi_probe, + .remove = __devexit_p(ti_ssp_spi_remove), + .driver = { + .name = "ti-ssp-spi", + .owner = THIS_MODULE, + }, +}; + +static int __init ti_ssp_spi_init(void) +{ + return platform_driver_register(&ti_ssp_spi_driver); +} +module_init(ti_ssp_spi_init); + +static void __exit ti_ssp_spi_exit(void) +{ + platform_driver_unregister(&ti_ssp_spi_driver); +} +module_exit(ti_ssp_spi_exit); + +MODULE_DESCRIPTION("SSP SPI Master"); +MODULE_AUTHOR("Cyril Chemparathy"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ti-ssp-spi"); diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 2b8334601c8..aa9d3d11031 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -537,8 +537,8 @@ config SERIAL_S3C6400 config SERIAL_S5PV210 tristate "Samsung S5PV210 Serial port support" - depends on SERIAL_SAMSUNG && (CPU_S5PV210 || CPU_S5P6442 || CPU_S5PV310) - select SERIAL_SAMSUNG_UARTS_4 if (CPU_S5PV210 || CPU_S5PV310) + depends on SERIAL_SAMSUNG && (CPU_S5PV210 || CPU_S5P6442 || CPU_EXYNOS4210) + select SERIAL_SAMSUNG_UARTS_4 if (CPU_S5PV210 || CPU_EXYNOS4210) default y help Serial port support for Samsung's S5P Family of SoC's @@ -1596,4 +1596,19 @@ config SERIAL_PCH_UART This driver is for PCH(Platform controller Hub) UART of Intel EG20T which is an IOH(Input/Output Hub) for x86 embedded processor. Enabling PCH_DMA, this PCH UART works as DMA mode. + +config SERIAL_MXS_AUART + depends on ARCH_MXS + tristate "MXS AUART support" + select SERIAL_CORE + help + This driver supports the MXS Application UART (AUART) port. + +config SERIAL_MXS_AUART_CONSOLE + bool "MXS AUART console support" + depends on SERIAL_MXS_AUART=y + select SERIAL_CORE_CONSOLE + help + Enable a MXS AUART port to be the system console. + endmenu diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 8ea92e9c73b..c8550719de5 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -92,3 +92,4 @@ obj-$(CONFIG_SERIAL_MRST_MAX3110) += mrst_max3110.o obj-$(CONFIG_SERIAL_MFD_HSU) += mfd.o obj-$(CONFIG_SERIAL_IFX6X60) += ifx6x60.o obj-$(CONFIG_SERIAL_PCH_UART) += pch_uart.o +obj-$(CONFIG_SERIAL_MXS_AUART) += mxs-auart.o diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c new file mode 100644 index 00000000000..7e02c9c344f --- /dev/null +++ b/drivers/tty/serial/mxs-auart.c @@ -0,0 +1,798 @@ +/* + * Freescale STMP37XX/STMP378X Application UART driver + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> + +#include <asm/cacheflush.h> + +#define MXS_AUART_PORTS 5 + +#define AUART_CTRL0 0x00000000 +#define AUART_CTRL0_SET 0x00000004 +#define AUART_CTRL0_CLR 0x00000008 +#define AUART_CTRL0_TOG 0x0000000c +#define AUART_CTRL1 0x00000010 +#define AUART_CTRL1_SET 0x00000014 +#define AUART_CTRL1_CLR 0x00000018 +#define AUART_CTRL1_TOG 0x0000001c +#define AUART_CTRL2 0x00000020 +#define AUART_CTRL2_SET 0x00000024 +#define AUART_CTRL2_CLR 0x00000028 +#define AUART_CTRL2_TOG 0x0000002c +#define AUART_LINECTRL 0x00000030 +#define AUART_LINECTRL_SET 0x00000034 +#define AUART_LINECTRL_CLR 0x00000038 +#define AUART_LINECTRL_TOG 0x0000003c +#define AUART_LINECTRL2 0x00000040 +#define AUART_LINECTRL2_SET 0x00000044 +#define AUART_LINECTRL2_CLR 0x00000048 +#define AUART_LINECTRL2_TOG 0x0000004c +#define AUART_INTR 0x00000050 +#define AUART_INTR_SET 0x00000054 +#define AUART_INTR_CLR 0x00000058 +#define AUART_INTR_TOG 0x0000005c +#define AUART_DATA 0x00000060 +#define AUART_STAT 0x00000070 +#define AUART_DEBUG 0x00000080 +#define AUART_VERSION 0x00000090 +#define AUART_AUTOBAUD 0x000000a0 + +#define AUART_CTRL0_SFTRST (1 << 31) +#define AUART_CTRL0_CLKGATE (1 << 30) + +#define AUART_CTRL2_CTSEN (1 << 15) +#define AUART_CTRL2_RTS (1 << 11) +#define AUART_CTRL2_RXE (1 << 9) +#define AUART_CTRL2_TXE (1 << 8) +#define AUART_CTRL2_UARTEN (1 << 0) + +#define AUART_LINECTRL_BAUD_DIVINT_SHIFT 16 +#define AUART_LINECTRL_BAUD_DIVINT_MASK 0xffff0000 +#define AUART_LINECTRL_BAUD_DIVINT(v) (((v) & 0xffff) << 16) +#define AUART_LINECTRL_BAUD_DIVFRAC_SHIFT 8 +#define AUART_LINECTRL_BAUD_DIVFRAC_MASK 0x00003f00 +#define AUART_LINECTRL_BAUD_DIVFRAC(v) (((v) & 0x3f) << 8) +#define AUART_LINECTRL_WLEN_MASK 0x00000060 +#define AUART_LINECTRL_WLEN(v) (((v) & 0x3) << 5) +#define AUART_LINECTRL_FEN (1 << 4) +#define AUART_LINECTRL_STP2 (1 << 3) +#define AUART_LINECTRL_EPS (1 << 2) +#define AUART_LINECTRL_PEN (1 << 1) +#define AUART_LINECTRL_BRK (1 << 0) + +#define AUART_INTR_RTIEN (1 << 22) +#define AUART_INTR_TXIEN (1 << 21) +#define AUART_INTR_RXIEN (1 << 20) +#define AUART_INTR_CTSMIEN (1 << 17) +#define AUART_INTR_RTIS (1 << 6) +#define AUART_INTR_TXIS (1 << 5) +#define AUART_INTR_RXIS (1 << 4) +#define AUART_INTR_CTSMIS (1 << 1) + +#define AUART_STAT_BUSY (1 << 29) +#define AUART_STAT_CTS (1 << 28) +#define AUART_STAT_TXFE (1 << 27) +#define AUART_STAT_TXFF (1 << 25) +#define AUART_STAT_RXFE (1 << 24) +#define AUART_STAT_OERR (1 << 19) +#define AUART_STAT_BERR (1 << 18) +#define AUART_STAT_PERR (1 << 17) +#define AUART_STAT_FERR (1 << 16) + +static struct uart_driver auart_driver; + +struct mxs_auart_port { + struct uart_port port; + + unsigned int flags; + unsigned int ctrl; + + unsigned int irq; + + struct clk *clk; + struct device *dev; +}; + +static void mxs_auart_stop_tx(struct uart_port *u); + +#define to_auart_port(u) container_of(u, struct mxs_auart_port, port) + +static inline void mxs_auart_tx_chars(struct mxs_auart_port *s) +{ + struct circ_buf *xmit = &s->port.state->xmit; + + while (!(readl(s->port.membase + AUART_STAT) & + AUART_STAT_TXFF)) { + if (s->port.x_char) { + s->port.icount.tx++; + writel(s->port.x_char, + s->port.membase + AUART_DATA); + s->port.x_char = 0; + continue; + } + if (!uart_circ_empty(xmit) && !uart_tx_stopped(&s->port)) { + s->port.icount.tx++; + writel(xmit->buf[xmit->tail], + s->port.membase + AUART_DATA); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&s->port); + } else + break; + } + if (uart_circ_empty(&(s->port.state->xmit))) + writel(AUART_INTR_TXIEN, + s->port.membase + AUART_INTR_CLR); + else + writel(AUART_INTR_TXIEN, + s->port.membase + AUART_INTR_SET); + + if (uart_tx_stopped(&s->port)) + mxs_auart_stop_tx(&s->port); +} + +static void mxs_auart_rx_char(struct mxs_auart_port *s) +{ + int flag; + u32 stat; + u8 c; + + c = readl(s->port.membase + AUART_DATA); + stat = readl(s->port.membase + AUART_STAT); + + flag = TTY_NORMAL; + s->port.icount.rx++; + + if (stat & AUART_STAT_BERR) { + s->port.icount.brk++; + if (uart_handle_break(&s->port)) + goto out; + } else if (stat & AUART_STAT_PERR) { + s->port.icount.parity++; + } else if (stat & AUART_STAT_FERR) { + s->port.icount.frame++; + } + + /* + * Mask off conditions which should be ingored. + */ + stat &= s->port.read_status_mask; + + if (stat & AUART_STAT_BERR) { + flag = TTY_BREAK; + } else if (stat & AUART_STAT_PERR) + flag = TTY_PARITY; + else if (stat & AUART_STAT_FERR) + flag = TTY_FRAME; + + if (stat & AUART_STAT_OERR) + s->port.icount.overrun++; + + if (uart_handle_sysrq_char(&s->port, c)) + goto out; + + uart_insert_char(&s->port, stat, AUART_STAT_OERR, c, flag); +out: + writel(stat, s->port.membase + AUART_STAT); +} + +static void mxs_auart_rx_chars(struct mxs_auart_port *s) +{ + struct tty_struct *tty = s->port.state->port.tty; + u32 stat = 0; + + for (;;) { + stat = readl(s->port.membase + AUART_STAT); + if (stat & AUART_STAT_RXFE) + break; + mxs_auart_rx_char(s); + } + + writel(stat, s->port.membase + AUART_STAT); + tty_flip_buffer_push(tty); +} + +static int mxs_auart_request_port(struct uart_port *u) +{ + return 0; +} + +static int mxs_auart_verify_port(struct uart_port *u, + struct serial_struct *ser) +{ + if (u->type != PORT_UNKNOWN && u->type != PORT_IMX) + return -EINVAL; + return 0; +} + +static void mxs_auart_config_port(struct uart_port *u, int flags) +{ +} + +static const char *mxs_auart_type(struct uart_port *u) +{ + struct mxs_auart_port *s = to_auart_port(u); + + return dev_name(s->dev); +} + +static void mxs_auart_release_port(struct uart_port *u) +{ +} + +static void mxs_auart_set_mctrl(struct uart_port *u, unsigned mctrl) +{ + struct mxs_auart_port *s = to_auart_port(u); + + u32 ctrl = readl(u->membase + AUART_CTRL2); + + ctrl &= ~AUART_CTRL2_RTS; + if (mctrl & TIOCM_RTS) + ctrl |= AUART_CTRL2_RTS; + s->ctrl = mctrl; + writel(ctrl, u->membase + AUART_CTRL2); +} + +static u32 mxs_auart_get_mctrl(struct uart_port *u) +{ + struct mxs_auart_port *s = to_auart_port(u); + u32 stat = readl(u->membase + AUART_STAT); + int ctrl2 = readl(u->membase + AUART_CTRL2); + u32 mctrl = s->ctrl; + + mctrl &= ~TIOCM_CTS; + if (stat & AUART_STAT_CTS) + mctrl |= TIOCM_CTS; + + if (ctrl2 & AUART_CTRL2_RTS) + mctrl |= TIOCM_RTS; + + return mctrl; +} + +static void mxs_auart_settermios(struct uart_port *u, + struct ktermios *termios, + struct ktermios *old) +{ + u32 bm, ctrl, ctrl2, div; + unsigned int cflag, baud; + + cflag = termios->c_cflag; + + ctrl = AUART_LINECTRL_FEN; + ctrl2 = readl(u->membase + AUART_CTRL2); + + /* byte size */ + switch (cflag & CSIZE) { + case CS5: + bm = 0; + break; + case CS6: + bm = 1; + break; + case CS7: + bm = 2; + break; + case CS8: + bm = 3; + break; + default: + return; + } + + ctrl |= AUART_LINECTRL_WLEN(bm); + + /* parity */ + if (cflag & PARENB) { + ctrl |= AUART_LINECTRL_PEN; + if ((cflag & PARODD) == 0) + ctrl |= AUART_LINECTRL_EPS; + } + + u->read_status_mask = 0; + + if (termios->c_iflag & INPCK) + u->read_status_mask |= AUART_STAT_PERR; + if (termios->c_iflag & (BRKINT | PARMRK)) + u->read_status_mask |= AUART_STAT_BERR; + + /* + * Characters to ignore + */ + u->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + u->ignore_status_mask |= AUART_STAT_PERR; + if (termios->c_iflag & IGNBRK) { + u->ignore_status_mask |= AUART_STAT_BERR; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + u->ignore_status_mask |= AUART_STAT_OERR; + } + + /* + * ignore all characters if CREAD is not set + */ + if (cflag & CREAD) + ctrl2 |= AUART_CTRL2_RXE; + else + ctrl2 &= ~AUART_CTRL2_RXE; + + /* figure out the stop bits requested */ + if (cflag & CSTOPB) + ctrl |= AUART_LINECTRL_STP2; + + /* figure out the hardware flow control settings */ + if (cflag & CRTSCTS) + ctrl2 |= AUART_CTRL2_CTSEN; + else + ctrl2 &= ~AUART_CTRL2_CTSEN; + + /* set baud rate */ + baud = uart_get_baud_rate(u, termios, old, 0, u->uartclk); + div = u->uartclk * 32 / baud; + ctrl |= AUART_LINECTRL_BAUD_DIVFRAC(div & 0x3F); + ctrl |= AUART_LINECTRL_BAUD_DIVINT(div >> 6); + + writel(ctrl, u->membase + AUART_LINECTRL); + writel(ctrl2, u->membase + AUART_CTRL2); +} + +static irqreturn_t mxs_auart_irq_handle(int irq, void *context) +{ + u32 istatus, istat; + struct mxs_auart_port *s = context; + u32 stat = readl(s->port.membase + AUART_STAT); + + istatus = istat = readl(s->port.membase + AUART_INTR); + + if (istat & AUART_INTR_CTSMIS) { + uart_handle_cts_change(&s->port, stat & AUART_STAT_CTS); + writel(AUART_INTR_CTSMIS, + s->port.membase + AUART_INTR_CLR); + istat &= ~AUART_INTR_CTSMIS; + } + + if (istat & (AUART_INTR_RTIS | AUART_INTR_RXIS)) { + mxs_auart_rx_chars(s); + istat &= ~(AUART_INTR_RTIS | AUART_INTR_RXIS); + } + + if (istat & AUART_INTR_TXIS) { + mxs_auart_tx_chars(s); + istat &= ~AUART_INTR_TXIS; + } + + writel(istatus & (AUART_INTR_RTIS + | AUART_INTR_TXIS + | AUART_INTR_RXIS + | AUART_INTR_CTSMIS), + s->port.membase + AUART_INTR_CLR); + + return IRQ_HANDLED; +} + +static void mxs_auart_reset(struct uart_port *u) +{ + int i; + unsigned int reg; + + writel(AUART_CTRL0_SFTRST, u->membase + AUART_CTRL0_CLR); + + for (i = 0; i < 10000; i++) { + reg = readl(u->membase + AUART_CTRL0); + if (!(reg & AUART_CTRL0_SFTRST)) + break; + udelay(3); + } + writel(AUART_CTRL0_CLKGATE, u->membase + AUART_CTRL0_CLR); +} + +static int mxs_auart_startup(struct uart_port *u) +{ + struct mxs_auart_port *s = to_auart_port(u); + + clk_enable(s->clk); + + writel(AUART_CTRL0_CLKGATE, u->membase + AUART_CTRL0_CLR); + + writel(AUART_CTRL2_UARTEN, u->membase + AUART_CTRL2_SET); + + writel(AUART_INTR_RXIEN | AUART_INTR_RTIEN | AUART_INTR_CTSMIEN, + u->membase + AUART_INTR); + + /* + * Enable fifo so all four bytes of a DMA word are written to + * output (otherwise, only the LSB is written, ie. 1 in 4 bytes) + */ + writel(AUART_LINECTRL_FEN, u->membase + AUART_LINECTRL_SET); + + return 0; +} + +static void mxs_auart_shutdown(struct uart_port *u) +{ + struct mxs_auart_port *s = to_auart_port(u); + + writel(AUART_CTRL2_UARTEN, u->membase + AUART_CTRL2_CLR); + + writel(AUART_CTRL0_CLKGATE, u->membase + AUART_CTRL0_SET); + + writel(AUART_INTR_RXIEN | AUART_INTR_RTIEN | AUART_INTR_CTSMIEN, + u->membase + AUART_INTR_CLR); + + clk_disable(s->clk); +} + +static unsigned int mxs_auart_tx_empty(struct uart_port *u) +{ + if (readl(u->membase + AUART_STAT) & AUART_STAT_TXFE) + return TIOCSER_TEMT; + else + return 0; +} + +static void mxs_auart_start_tx(struct uart_port *u) +{ + struct mxs_auart_port *s = to_auart_port(u); + + /* enable transmitter */ + writel(AUART_CTRL2_TXE, u->membase + AUART_CTRL2_SET); + + mxs_auart_tx_chars(s); +} + +static void mxs_auart_stop_tx(struct uart_port *u) +{ + writel(AUART_CTRL2_TXE, u->membase + AUART_CTRL2_CLR); +} + +static void mxs_auart_stop_rx(struct uart_port *u) +{ + writel(AUART_CTRL2_RXE, u->membase + AUART_CTRL2_CLR); +} + +static void mxs_auart_break_ctl(struct uart_port *u, int ctl) +{ + if (ctl) + writel(AUART_LINECTRL_BRK, + u->membase + AUART_LINECTRL_SET); + else + writel(AUART_LINECTRL_BRK, + u->membase + AUART_LINECTRL_CLR); +} + +static void mxs_auart_enable_ms(struct uart_port *port) +{ + /* just empty */ +} + +static struct uart_ops mxs_auart_ops = { + .tx_empty = mxs_auart_tx_empty, + .start_tx = mxs_auart_start_tx, + .stop_tx = mxs_auart_stop_tx, + .stop_rx = mxs_auart_stop_rx, + .enable_ms = mxs_auart_enable_ms, + .break_ctl = mxs_auart_break_ctl, + .set_mctrl = mxs_auart_set_mctrl, + .get_mctrl = mxs_auart_get_mctrl, + .startup = mxs_auart_startup, + .shutdown = mxs_auart_shutdown, + .set_termios = mxs_auart_settermios, + .type = mxs_auart_type, + .release_port = mxs_auart_release_port, + .request_port = mxs_auart_request_port, + .config_port = mxs_auart_config_port, + .verify_port = mxs_auart_verify_port, +}; + +static struct mxs_auart_port *auart_port[MXS_AUART_PORTS]; + +#ifdef CONFIG_SERIAL_MXS_AUART_CONSOLE +static void mxs_auart_console_putchar(struct uart_port *port, int ch) +{ + unsigned int to = 1000; + + while (readl(port->membase + AUART_STAT) & AUART_STAT_TXFF) { + if (!to--) + break; + udelay(1); + } + + writel(ch, port->membase + AUART_DATA); +} + +static void +auart_console_write(struct console *co, const char *str, unsigned int count) +{ + struct mxs_auart_port *s; + struct uart_port *port; + unsigned int old_ctrl0, old_ctrl2; + unsigned int to = 1000; + + if (co->index > MXS_AUART_PORTS || co->index < 0) + return; + + s = auart_port[co->index]; + port = &s->port; + + clk_enable(s->clk); + + /* First save the CR then disable the interrupts */ + old_ctrl2 = readl(port->membase + AUART_CTRL2); + old_ctrl0 = readl(port->membase + AUART_CTRL0); + + writel(AUART_CTRL0_CLKGATE, + port->membase + AUART_CTRL0_CLR); + writel(AUART_CTRL2_UARTEN | AUART_CTRL2_TXE, + port->membase + AUART_CTRL2_SET); + + uart_console_write(port, str, count, mxs_auart_console_putchar); + + /* + * Finally, wait for transmitter to become empty + * and restore the TCR + */ + while (readl(port->membase + AUART_STAT) & AUART_STAT_BUSY) { + if (!to--) + break; + udelay(1); + } + + writel(old_ctrl0, port->membase + AUART_CTRL0); + writel(old_ctrl2, port->membase + AUART_CTRL2); + + clk_disable(s->clk); +} + +static void __init +auart_console_get_options(struct uart_port *port, int *baud, + int *parity, int *bits) +{ + unsigned int lcr_h, quot; + + if (!(readl(port->membase + AUART_CTRL2) & AUART_CTRL2_UARTEN)) + return; + + lcr_h = readl(port->membase + AUART_LINECTRL); + + *parity = 'n'; + if (lcr_h & AUART_LINECTRL_PEN) { + if (lcr_h & AUART_LINECTRL_EPS) + *parity = 'e'; + else + *parity = 'o'; + } + + if ((lcr_h & AUART_LINECTRL_WLEN_MASK) == AUART_LINECTRL_WLEN(2)) + *bits = 7; + else + *bits = 8; + + quot = ((readl(port->membase + AUART_LINECTRL) + & AUART_LINECTRL_BAUD_DIVINT_MASK)) + >> (AUART_LINECTRL_BAUD_DIVINT_SHIFT - 6); + quot |= ((readl(port->membase + AUART_LINECTRL) + & AUART_LINECTRL_BAUD_DIVFRAC_MASK)) + >> AUART_LINECTRL_BAUD_DIVFRAC_SHIFT; + if (quot == 0) + quot = 1; + + *baud = (port->uartclk << 2) / quot; +} + +static int __init +auart_console_setup(struct console *co, char *options) +{ + struct mxs_auart_port *s; + int baud = 9600; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + int ret; + + /* + * Check whether an invalid uart number has been specified, and + * if so, search for the first available port that does have + * console support. + */ + if (co->index == -1 || co->index >= ARRAY_SIZE(auart_port)) + co->index = 0; + s = auart_port[co->index]; + if (!s) + return -ENODEV; + + clk_enable(s->clk); + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + else + auart_console_get_options(&s->port, &baud, &parity, &bits); + + ret = uart_set_options(&s->port, co, baud, parity, bits, flow); + + clk_disable(s->clk); + + return ret; +} + +static struct console auart_console = { + .name = "ttyAPP", + .write = auart_console_write, + .device = uart_console_device, + .setup = auart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &auart_driver, +}; +#endif + +static struct uart_driver auart_driver = { + .owner = THIS_MODULE, + .driver_name = "ttyAPP", + .dev_name = "ttyAPP", + .major = 0, + .minor = 0, + .nr = MXS_AUART_PORTS, +#ifdef CONFIG_SERIAL_MXS_AUART_CONSOLE + .cons = &auart_console, +#endif +}; + +static int __devinit mxs_auart_probe(struct platform_device *pdev) +{ + struct mxs_auart_port *s; + u32 version; + int ret = 0; + struct resource *r; + + s = kzalloc(sizeof(struct mxs_auart_port), GFP_KERNEL); + if (!s) { + ret = -ENOMEM; + goto out; + } + + s->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(s->clk)) { + ret = PTR_ERR(s->clk); + goto out_free; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + ret = -ENXIO; + goto out_free_clk; + } + + s->port.mapbase = r->start; + s->port.membase = ioremap(r->start, resource_size(r)); + s->port.ops = &mxs_auart_ops; + s->port.iotype = UPIO_MEM; + s->port.line = pdev->id < 0 ? 0 : pdev->id; + s->port.fifosize = 16; + s->port.uartclk = clk_get_rate(s->clk); + s->port.type = PORT_IMX; + s->port.dev = s->dev = get_device(&pdev->dev); + + s->flags = 0; + s->ctrl = 0; + + s->irq = platform_get_irq(pdev, 0); + s->port.irq = s->irq; + ret = request_irq(s->irq, mxs_auart_irq_handle, 0, dev_name(&pdev->dev), s); + if (ret) + goto out_free_clk; + + platform_set_drvdata(pdev, s); + + auart_port[pdev->id] = s; + + mxs_auart_reset(&s->port); + + ret = uart_add_one_port(&auart_driver, &s->port); + if (ret) + goto out_free_irq; + + version = readl(s->port.membase + AUART_VERSION); + dev_info(&pdev->dev, "Found APPUART %d.%d.%d\n", + (version >> 24) & 0xff, + (version >> 16) & 0xff, version & 0xffff); + + return 0; + +out_free_irq: + auart_port[pdev->id] = NULL; + free_irq(s->irq, s); +out_free_clk: + clk_put(s->clk); +out_free: + kfree(s); +out: + return ret; +} + +static int __devexit mxs_auart_remove(struct platform_device *pdev) +{ + struct mxs_auart_port *s = platform_get_drvdata(pdev); + + uart_remove_one_port(&auart_driver, &s->port); + + auart_port[pdev->id] = NULL; + + clk_put(s->clk); + free_irq(s->irq, s); + kfree(s); + + return 0; +} + +static struct platform_driver mxs_auart_driver = { + .probe = mxs_auart_probe, + .remove = __devexit_p(mxs_auart_remove), + .driver = { + .name = "mxs-auart", + .owner = THIS_MODULE, + }, +}; + +static int __init mxs_auart_init(void) +{ + int r; + + r = uart_register_driver(&auart_driver); + if (r) + goto out; + + r = platform_driver_register(&mxs_auart_driver); + if (r) + goto out_err; + + return 0; +out_err: + uart_unregister_driver(&auart_driver); +out: + return r; +} + +static void __exit mxs_auart_exit(void) +{ + platform_driver_unregister(&mxs_auart_driver); + uart_unregister_driver(&auart_driver); +} + +module_init(mxs_auart_init); +module_exit(mxs_auart_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Freescale MXS application uart driver"); diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c index c8e360d7d97..25c8c10bb68 100644 --- a/drivers/usb/host/ehci-mxc.c +++ b/drivers/usb/host/ehci-mxc.c @@ -203,11 +203,6 @@ static int ehci_mxc_drv_probe(struct platform_device *pdev) mdelay(10); } - /* setup specific usb hw */ - ret = mxc_initialize_usb_hw(pdev->id, pdata->flags); - if (ret < 0) - goto err_init; - ehci = hcd_to_ehci(hcd); /* EHCI registers start at offset 0x100 */ diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 6bafb51bb43..e0ea23f07dd 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2365,6 +2365,15 @@ config FB_JZ4740 help Framebuffer support for the JZ4740 SoC. +config FB_MXS + tristate "MXS LCD framebuffer support" + depends on FB && ARCH_MXS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Framebuffer support for the MXS SoC. + source "drivers/video/omap/Kconfig" source "drivers/video/omap2/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 8c8fabdff9d..9a096aebb85 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -153,6 +153,7 @@ obj-$(CONFIG_FB_BFIN_T350MCQB) += bfin-t350mcqb-fb.o obj-$(CONFIG_FB_BFIN_7393) += bfin_adv7393fb.o obj-$(CONFIG_FB_MX3) += mx3fb.o obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o +obj-$(CONFIG_FB_MXS) += mxsfb.o # the test framebuffer is last obj-$(CONFIG_FB_VIRTUAL) += vfb.o diff --git a/drivers/video/mxsfb.c b/drivers/video/mxsfb.c new file mode 100644 index 00000000000..7d028488298 --- /dev/null +++ b/drivers/video/mxsfb.c @@ -0,0 +1,919 @@ +/* + * Copyright (C) 2010 Juergen Beisert, Pengutronix + * + * This code is based on: + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + * + * 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. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define DRIVER_NAME "mxsfb" + +/** + * @file + * @brief LCDIF driver for i.MX23 and i.MX28 + * + * The LCDIF support four modes of operation + * - MPU interface (to drive smart displays) -> not supported yet + * - VSYNC interface (like MPU interface plus Vsync) -> not supported yet + * - Dotclock interface (to drive LC displays with RGB data and sync signals) + * - DVI (to drive ITU-R BT656) -> not supported yet + * + * This driver depends on a correct setup of the pins used for this purpose + * (platform specific). + * + * For the developer: Don't forget to set the data bus width to the display + * in the imx_fb_videomode structure. You will else end up with ugly colours. + * If you fight against jitter you can vary the clock delay. This is a feature + * of the i.MX28 and you can vary it between 2 ns ... 8 ns in 2 ns steps. Give + * the required value in the imx_fb_videomode structure. + */ + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <mach/mxsfb.h> + +#define REG_SET 4 +#define REG_CLR 8 + +#define LCDC_CTRL 0x00 +#define LCDC_CTRL1 0x10 +#define LCDC_V4_CTRL2 0x20 +#define LCDC_V3_TRANSFER_COUNT 0x20 +#define LCDC_V4_TRANSFER_COUNT 0x30 +#define LCDC_V4_CUR_BUF 0x40 +#define LCDC_V4_NEXT_BUF 0x50 +#define LCDC_V3_CUR_BUF 0x30 +#define LCDC_V3_NEXT_BUF 0x40 +#define LCDC_TIMING 0x60 +#define LCDC_VDCTRL0 0x70 +#define LCDC_VDCTRL1 0x80 +#define LCDC_VDCTRL2 0x90 +#define LCDC_VDCTRL3 0xa0 +#define LCDC_VDCTRL4 0xb0 +#define LCDC_DVICTRL0 0xc0 +#define LCDC_DVICTRL1 0xd0 +#define LCDC_DVICTRL2 0xe0 +#define LCDC_DVICTRL3 0xf0 +#define LCDC_DVICTRL4 0x100 +#define LCDC_V4_DATA 0x180 +#define LCDC_V3_DATA 0x1b0 +#define LCDC_V4_DEBUG0 0x1d0 +#define LCDC_V3_DEBUG0 0x1f0 + +#define CTRL_SFTRST (1 << 31) +#define CTRL_CLKGATE (1 << 30) +#define CTRL_BYPASS_COUNT (1 << 19) +#define CTRL_VSYNC_MODE (1 << 18) +#define CTRL_DOTCLK_MODE (1 << 17) +#define CTRL_DATA_SELECT (1 << 16) +#define CTRL_SET_BUS_WIDTH(x) (((x) & 0x3) << 10) +#define CTRL_GET_BUS_WIDTH(x) (((x) >> 10) & 0x3) +#define CTRL_SET_WORD_LENGTH(x) (((x) & 0x3) << 8) +#define CTRL_GET_WORD_LENGTH(x) (((x) >> 8) & 0x3) +#define CTRL_MASTER (1 << 5) +#define CTRL_DF16 (1 << 3) +#define CTRL_DF18 (1 << 2) +#define CTRL_DF24 (1 << 1) +#define CTRL_RUN (1 << 0) + +#define CTRL1_FIFO_CLEAR (1 << 21) +#define CTRL1_SET_BYTE_PACKAGING(x) (((x) & 0xf) << 16) +#define CTRL1_GET_BYTE_PACKAGING(x) (((x) >> 16) & 0xf) + +#define TRANSFER_COUNT_SET_VCOUNT(x) (((x) & 0xffff) << 16) +#define TRANSFER_COUNT_GET_VCOUNT(x) (((x) >> 16) & 0xffff) +#define TRANSFER_COUNT_SET_HCOUNT(x) ((x) & 0xffff) +#define TRANSFER_COUNT_GET_HCOUNT(x) ((x) & 0xffff) + + +#define VDCTRL0_ENABLE_PRESENT (1 << 28) +#define VDCTRL0_VSYNC_ACT_HIGH (1 << 27) +#define VDCTRL0_HSYNC_ACT_HIGH (1 << 26) +#define VDCTRL0_DOTCLK_ACT_FAILING (1 << 25) +#define VDCTRL0_ENABLE_ACT_HIGH (1 << 24) +#define VDCTRL0_VSYNC_PERIOD_UNIT (1 << 21) +#define VDCTRL0_VSYNC_PULSE_WIDTH_UNIT (1 << 20) +#define VDCTRL0_HALF_LINE (1 << 19) +#define VDCTRL0_HALF_LINE_MODE (1 << 18) +#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff) +#define VDCTRL0_GET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff) + +#define VDCTRL2_SET_HSYNC_PERIOD(x) ((x) & 0x3ffff) +#define VDCTRL2_GET_HSYNC_PERIOD(x) ((x) & 0x3ffff) + +#define VDCTRL3_MUX_SYNC_SIGNALS (1 << 29) +#define VDCTRL3_VSYNC_ONLY (1 << 28) +#define SET_HOR_WAIT_CNT(x) (((x) & 0xfff) << 16) +#define GET_HOR_WAIT_CNT(x) (((x) >> 16) & 0xfff) +#define SET_VERT_WAIT_CNT(x) ((x) & 0xffff) +#define GET_VERT_WAIT_CNT(x) ((x) & 0xffff) + +#define VDCTRL4_SET_DOTCLK_DLY(x) (((x) & 0x7) << 29) /* v4 only */ +#define VDCTRL4_GET_DOTCLK_DLY(x) (((x) >> 29) & 0x7) /* v4 only */ +#define VDCTRL4_SYNC_SIGNALS_ON (1 << 18) +#define SET_DOTCLK_H_VALID_DATA_CNT(x) ((x) & 0x3ffff) + +#define DEBUG0_HSYNC (1 < 26) +#define DEBUG0_VSYNC (1 < 25) + +#define MIN_XRES 120 +#define MIN_YRES 120 + +#define RED 0 +#define GREEN 1 +#define BLUE 2 +#define TRANSP 3 + +enum mxsfb_devtype { + MXSFB_V3, + MXSFB_V4, +}; + +/* CPU dependent register offsets */ +struct mxsfb_devdata { + unsigned transfer_count; + unsigned cur_buf; + unsigned next_buf; + unsigned debug0; + unsigned hs_wdth_mask; + unsigned hs_wdth_shift; + unsigned ipversion; +}; + +struct mxsfb_info { + struct fb_info fb_info; + struct platform_device *pdev; + struct clk *clk; + void __iomem *base; /* registers */ + unsigned allocated_size; + int enabled; + unsigned ld_intf_width; + unsigned dotclk_delay; + const struct mxsfb_devdata *devdata; + int mapped; +}; + +#define mxsfb_is_v3(host) (host->devdata->ipversion == 3) +#define mxsfb_is_v4(host) (host->devdata->ipversion == 4) + +static const struct mxsfb_devdata mxsfb_devdata[] = { + [MXSFB_V3] = { + .transfer_count = LCDC_V3_TRANSFER_COUNT, + .cur_buf = LCDC_V3_CUR_BUF, + .next_buf = LCDC_V3_NEXT_BUF, + .debug0 = LCDC_V3_DEBUG0, + .hs_wdth_mask = 0xff, + .hs_wdth_shift = 24, + .ipversion = 3, + }, + [MXSFB_V4] = { + .transfer_count = LCDC_V4_TRANSFER_COUNT, + .cur_buf = LCDC_V4_CUR_BUF, + .next_buf = LCDC_V4_NEXT_BUF, + .debug0 = LCDC_V4_DEBUG0, + .hs_wdth_mask = 0x3fff, + .hs_wdth_shift = 18, + .ipversion = 4, + }, +}; + +#define to_imxfb_host(x) (container_of(x, struct mxsfb_info, fb_info)) + +/* mask and shift depends on architecture */ +static inline u32 set_hsync_pulse_width(struct mxsfb_info *host, unsigned val) +{ + return (val & host->devdata->hs_wdth_mask) << + host->devdata->hs_wdth_shift; +} + +static inline u32 get_hsync_pulse_width(struct mxsfb_info *host, unsigned val) +{ + return (val >> host->devdata->hs_wdth_shift) & + host->devdata->hs_wdth_mask; +} + +static const struct fb_bitfield def_rgb565[] = { + [RED] = { + .offset = 11, + .length = 5, + }, + [GREEN] = { + .offset = 5, + .length = 6, + }, + [BLUE] = { + .offset = 0, + .length = 5, + }, + [TRANSP] = { /* no support for transparency */ + .length = 0, + } +}; + +static const struct fb_bitfield def_rgb666[] = { + [RED] = { + .offset = 16, + .length = 6, + }, + [GREEN] = { + .offset = 8, + .length = 6, + }, + [BLUE] = { + .offset = 0, + .length = 6, + }, + [TRANSP] = { /* no support for transparency */ + .length = 0, + } +}; + +static const struct fb_bitfield def_rgb888[] = { + [RED] = { + .offset = 16, + .length = 8, + }, + [GREEN] = { + .offset = 8, + .length = 8, + }, + [BLUE] = { + .offset = 0, + .length = 8, + }, + [TRANSP] = { /* no support for transparency */ + .length = 0, + } +}; + +static inline unsigned chan_to_field(unsigned chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxsfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + const struct fb_bitfield *rgb = NULL; + + if (var->xres < MIN_XRES) + var->xres = MIN_XRES; + if (var->yres < MIN_YRES) + var->yres = MIN_YRES; + + var->xres_virtual = var->xres; + + var->yres_virtual = var->yres; + + switch (var->bits_per_pixel) { + case 16: + /* always expect RGB 565 */ + rgb = def_rgb565; + break; + case 32: + switch (host->ld_intf_width) { + case STMLCDIF_8BIT: + pr_debug("Unsupported LCD bus width mapping\n"); + break; + case STMLCDIF_16BIT: + case STMLCDIF_18BIT: + /* 24 bit to 18 bit mapping */ + rgb = def_rgb666; + break; + case STMLCDIF_24BIT: + /* real 24 bit */ + rgb = def_rgb888; + break; + } + break; + default: + pr_debug("Unsupported colour depth: %u\n", var->bits_per_pixel); + return -EINVAL; + } + + /* + * Copy the RGB parameters for this display + * from the machine specific parameters. + */ + var->red = rgb[RED]; + var->green = rgb[GREEN]; + var->blue = rgb[BLUE]; + var->transp = rgb[TRANSP]; + + return 0; +} + +static void mxsfb_enable_controller(struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + u32 reg; + + dev_dbg(&host->pdev->dev, "%s\n", __func__); + + clk_enable(host->clk); + clk_set_rate(host->clk, PICOS2KHZ(fb_info->var.pixclock) * 1000U); + + /* if it was disabled, re-enable the mode again */ + writel(CTRL_DOTCLK_MODE, host->base + LCDC_CTRL + REG_SET); + + /* enable the SYNC signals first, then the DMA engine */ + reg = readl(host->base + LCDC_VDCTRL4); + reg |= VDCTRL4_SYNC_SIGNALS_ON; + writel(reg, host->base + LCDC_VDCTRL4); + + writel(CTRL_RUN, host->base + LCDC_CTRL + REG_SET); + + host->enabled = 1; +} + +static void mxsfb_disable_controller(struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + unsigned loop; + u32 reg; + + dev_dbg(&host->pdev->dev, "%s\n", __func__); + + /* + * Even if we disable the controller here, it will still continue + * until its FIFOs are running out of data + */ + writel(CTRL_DOTCLK_MODE, host->base + LCDC_CTRL + REG_CLR); + + loop = 1000; + while (loop) { + reg = readl(host->base + LCDC_CTRL); + if (!(reg & CTRL_RUN)) + break; + loop--; + } + + writel(VDCTRL4_SYNC_SIGNALS_ON, host->base + LCDC_VDCTRL4 + REG_CLR); + + clk_disable(host->clk); + + host->enabled = 0; +} + +static int mxsfb_set_par(struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + u32 ctrl, vdctrl0, vdctrl4; + int line_size, fb_size; + int reenable = 0; + + line_size = fb_info->var.xres * (fb_info->var.bits_per_pixel >> 3); + fb_size = fb_info->var.yres_virtual * line_size; + + if (fb_size > fb_info->fix.smem_len) + return -ENOMEM; + + fb_info->fix.line_length = line_size; + + /* + * It seems, you can't re-program the controller if it is still running. + * This may lead into shifted pictures (FIFO issue?). + * So, first stop the controller and drain its FIFOs + */ + if (host->enabled) { + reenable = 1; + mxsfb_disable_controller(fb_info); + } + + /* clear the FIFOs */ + writel(CTRL1_FIFO_CLEAR, host->base + LCDC_CTRL1 + REG_SET); + + ctrl = CTRL_BYPASS_COUNT | CTRL_MASTER | + CTRL_SET_BUS_WIDTH(host->ld_intf_width);; + + switch (fb_info->var.bits_per_pixel) { + case 16: + dev_dbg(&host->pdev->dev, "Setting up RGB565 mode\n"); + ctrl |= CTRL_SET_WORD_LENGTH(0); + writel(CTRL1_SET_BYTE_PACKAGING(0xf), host->base + LCDC_CTRL1); + break; + case 32: + dev_dbg(&host->pdev->dev, "Setting up RGB888/666 mode\n"); + ctrl |= CTRL_SET_WORD_LENGTH(3); + switch (host->ld_intf_width) { + case STMLCDIF_8BIT: + dev_dbg(&host->pdev->dev, + "Unsupported LCD bus width mapping\n"); + return -EINVAL; + case STMLCDIF_16BIT: + case STMLCDIF_18BIT: + /* 24 bit to 18 bit mapping */ + ctrl |= CTRL_DF24; /* ignore the upper 2 bits in + * each colour component + */ + break; + case STMLCDIF_24BIT: + /* real 24 bit */ + break; + } + /* do not use packed pixels = one pixel per word instead */ + writel(CTRL1_SET_BYTE_PACKAGING(0x7), host->base + LCDC_CTRL1); + break; + default: + dev_dbg(&host->pdev->dev, "Unhandled color depth of %u\n", + fb_info->var.bits_per_pixel); + return -EINVAL; + } + + writel(ctrl, host->base + LCDC_CTRL); + + writel(TRANSFER_COUNT_SET_VCOUNT(fb_info->var.yres) | + TRANSFER_COUNT_SET_HCOUNT(fb_info->var.xres), + host->base + host->devdata->transfer_count); + + vdctrl0 = VDCTRL0_ENABLE_PRESENT | /* always in DOTCLOCK mode */ + VDCTRL0_VSYNC_PERIOD_UNIT | + VDCTRL0_VSYNC_PULSE_WIDTH_UNIT | + VDCTRL0_SET_VSYNC_PULSE_WIDTH(fb_info->var.vsync_len); + if (fb_info->var.sync & FB_SYNC_HOR_HIGH_ACT) + vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH; + if (fb_info->var.sync & FB_SYNC_VERT_HIGH_ACT) + vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH; + if (fb_info->var.sync & FB_SYNC_DATA_ENABLE_HIGH_ACT) + vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH; + if (fb_info->var.sync & FB_SYNC_DOTCLK_FAILING_ACT) + vdctrl0 |= VDCTRL0_DOTCLK_ACT_FAILING; + + writel(vdctrl0, host->base + LCDC_VDCTRL0); + + /* frame length in lines */ + writel(fb_info->var.upper_margin + fb_info->var.vsync_len + + fb_info->var.lower_margin + fb_info->var.yres, + host->base + LCDC_VDCTRL1); + + /* line length in units of clocks or pixels */ + writel(set_hsync_pulse_width(host, fb_info->var.hsync_len) | + VDCTRL2_SET_HSYNC_PERIOD(fb_info->var.left_margin + + fb_info->var.hsync_len + fb_info->var.right_margin + + fb_info->var.xres), + host->base + LCDC_VDCTRL2); + + writel(SET_HOR_WAIT_CNT(fb_info->var.left_margin + + fb_info->var.hsync_len) | + SET_VERT_WAIT_CNT(fb_info->var.upper_margin + + fb_info->var.vsync_len), + host->base + LCDC_VDCTRL3); + + vdctrl4 = SET_DOTCLK_H_VALID_DATA_CNT(fb_info->var.xres); + if (mxsfb_is_v4(host)) + vdctrl4 |= VDCTRL4_SET_DOTCLK_DLY(host->dotclk_delay); + writel(vdctrl4, host->base + LCDC_VDCTRL4); + + writel(fb_info->fix.smem_start + + fb_info->fix.line_length * fb_info->var.yoffset, + host->base + host->devdata->next_buf); + + if (reenable) + mxsfb_enable_controller(fb_info); + + return 0; +} + +static int mxsfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *fb_info) +{ + unsigned int val; + int ret = -EINVAL; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fb_info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + + switch (fb_info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 12 or 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fb_info->pseudo_palette; + + val = chan_to_field(red, &fb_info->var.red); + val |= chan_to_field(green, &fb_info->var.green); + val |= chan_to_field(blue, &fb_info->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +static int mxsfb_blank(int blank, struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + if (host->enabled) + mxsfb_disable_controller(fb_info); + break; + + case FB_BLANK_UNBLANK: + if (!host->enabled) + mxsfb_enable_controller(fb_info); + break; + } + return 0; +} + +static int mxsfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fb_info) +{ + struct mxsfb_info *host = to_imxfb_host(fb_info); + unsigned offset; + + if (var->xoffset != 0) + return -EINVAL; + + offset = fb_info->fix.line_length * var->yoffset; + + /* update on next VSYNC */ + writel(fb_info->fix.smem_start + offset, + host->base + host->devdata->next_buf); + + return 0; +} + +static struct fb_ops mxsfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mxsfb_check_var, + .fb_set_par = mxsfb_set_par, + .fb_setcolreg = mxsfb_setcolreg, + .fb_blank = mxsfb_blank, + .fb_pan_display = mxsfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int __devinit mxsfb_restore_mode(struct mxsfb_info *host) +{ + struct fb_info *fb_info = &host->fb_info; + unsigned line_count; + unsigned period; + unsigned long pa, fbsize; + int bits_per_pixel, ofs; + u32 transfer_count, vdctrl0, vdctrl2, vdctrl3, vdctrl4, ctrl; + struct fb_videomode vmode; + + /* Only restore the mode when the controller is running */ + ctrl = readl(host->base + LCDC_CTRL); + if (!(ctrl & CTRL_RUN)) + return -EINVAL; + + vdctrl0 = readl(host->base + LCDC_VDCTRL0); + vdctrl2 = readl(host->base + LCDC_VDCTRL2); + vdctrl3 = readl(host->base + LCDC_VDCTRL3); + vdctrl4 = readl(host->base + LCDC_VDCTRL4); + + transfer_count = readl(host->base + host->devdata->transfer_count); + + vmode.xres = TRANSFER_COUNT_GET_HCOUNT(transfer_count); + vmode.yres = TRANSFER_COUNT_GET_VCOUNT(transfer_count); + + switch (CTRL_GET_WORD_LENGTH(ctrl)) { + case 0: + bits_per_pixel = 16; + break; + case 3: + bits_per_pixel = 32; + case 1: + default: + return -EINVAL; + } + + fb_info->var.bits_per_pixel = bits_per_pixel; + + vmode.pixclock = KHZ2PICOS(clk_get_rate(host->clk) / 1000U); + vmode.hsync_len = get_hsync_pulse_width(host, vdctrl2); + vmode.left_margin = GET_HOR_WAIT_CNT(vdctrl3) - vmode.hsync_len; + vmode.right_margin = VDCTRL2_GET_HSYNC_PERIOD(vdctrl2) - vmode.hsync_len - + vmode.left_margin - vmode.xres; + vmode.vsync_len = VDCTRL0_GET_VSYNC_PULSE_WIDTH(vdctrl0); + period = readl(host->base + LCDC_VDCTRL1); + vmode.upper_margin = GET_VERT_WAIT_CNT(vdctrl3) - vmode.vsync_len; + vmode.lower_margin = period - vmode.vsync_len - vmode.upper_margin - vmode.yres; + + vmode.vmode = FB_VMODE_NONINTERLACED; + + vmode.sync = 0; + if (vdctrl0 & VDCTRL0_HSYNC_ACT_HIGH) + vmode.sync |= FB_SYNC_HOR_HIGH_ACT; + if (vdctrl0 & VDCTRL0_VSYNC_ACT_HIGH) + vmode.sync |= FB_SYNC_VERT_HIGH_ACT; + + pr_debug("Reconstructed video mode:\n"); + pr_debug("%dx%d, hsync: %u left: %u, right: %u, vsync: %u, upper: %u, lower: %u\n", + vmode.xres, vmode.yres, + vmode.hsync_len, vmode.left_margin, vmode.right_margin, + vmode.vsync_len, vmode.upper_margin, vmode.lower_margin); + pr_debug("pixclk: %ldkHz\n", PICOS2KHZ(vmode.pixclock)); + + fb_add_videomode(&vmode, &fb_info->modelist); + + host->ld_intf_width = CTRL_GET_BUS_WIDTH(ctrl); + host->dotclk_delay = VDCTRL4_GET_DOTCLK_DLY(vdctrl4); + + fb_info->fix.line_length = vmode.xres * (bits_per_pixel >> 3); + + pa = readl(host->base + host->devdata->cur_buf); + fbsize = fb_info->fix.line_length * vmode.yres; + if (pa < fb_info->fix.smem_start) + return -EINVAL; + if (pa + fbsize > fb_info->fix.smem_start + fb_info->fix.smem_len) + return -EINVAL; + ofs = pa - fb_info->fix.smem_start; + if (ofs) { + memmove(fb_info->screen_base, fb_info->screen_base + ofs, fbsize); + writel(fb_info->fix.smem_start, host->base + host->devdata->next_buf); + } + + line_count = fb_info->fix.smem_len / fb_info->fix.line_length; + fb_info->fix.ypanstep = 1; + + clk_enable(host->clk); + host->enabled = 1; + + return 0; +} + +static int __devinit mxsfb_init_fbinfo(struct mxsfb_info *host) +{ + struct fb_info *fb_info = &host->fb_info; + struct fb_var_screeninfo *var = &fb_info->var; + struct mxsfb_platform_data *pdata = host->pdev->dev.platform_data; + dma_addr_t fb_phys; + void *fb_virt; + unsigned fb_size = pdata->fb_size; + + fb_info->fbops = &mxsfb_ops; + fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_READS_FAST; + strlcpy(fb_info->fix.id, "mxs", sizeof(fb_info->fix.id)); + fb_info->fix.type = FB_TYPE_PACKED_PIXELS; + fb_info->fix.ypanstep = 1; + fb_info->fix.visual = FB_VISUAL_TRUECOLOR, + fb_info->fix.accel = FB_ACCEL_NONE; + + var->bits_per_pixel = pdata->default_bpp ? pdata->default_bpp : 16; + var->nonstd = 0; + var->activate = FB_ACTIVATE_NOW; + var->accel_flags = 0; + var->vmode = FB_VMODE_NONINTERLACED; + + host->dotclk_delay = pdata->dotclk_delay; + host->ld_intf_width = pdata->ld_intf_width; + + /* Memory allocation for framebuffer */ + if (pdata->fb_phys) { + if (!fb_size) + return -EINVAL; + + fb_phys = pdata->fb_phys; + + if (!request_mem_region(fb_phys, fb_size, host->pdev->name)) + return -ENOMEM; + + fb_virt = ioremap(fb_phys, fb_size); + if (!fb_virt) { + release_mem_region(fb_phys, fb_size); + return -ENOMEM; + } + host->mapped = 1; + } else { + if (!fb_size) + fb_size = SZ_2M; /* default */ + fb_virt = alloc_pages_exact(fb_size, GFP_DMA); + if (!fb_virt) + return -ENOMEM; + + fb_phys = virt_to_phys(fb_virt); + } + + fb_info->fix.smem_start = fb_phys; + fb_info->screen_base = fb_virt; + fb_info->screen_size = fb_info->fix.smem_len = fb_size; + + if (mxsfb_restore_mode(host)) + memset(fb_virt, 0, fb_size); + + return 0; +} + +static void __devexit mxsfb_free_videomem(struct mxsfb_info *host) +{ + struct fb_info *fb_info = &host->fb_info; + + if (host->mapped) { + iounmap(fb_info->screen_base); + release_mem_region(fb_info->fix.smem_start, + fb_info->screen_size); + } else { + free_pages_exact(fb_info->screen_base, fb_info->fix.smem_len); + } +} + +static int __devinit mxsfb_probe(struct platform_device *pdev) +{ + struct mxsfb_platform_data *pdata = pdev->dev.platform_data; + struct resource *res; + struct mxsfb_info *host; + struct fb_info *fb_info; + struct fb_modelist *modelist; + int i, ret; + + if (!pdata) { + dev_err(&pdev->dev, "No platformdata. Giving up\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Cannot get memory IO resource\n"); + return -ENODEV; + } + + if (!request_mem_region(res->start, resource_size(res), pdev->name)) + return -EBUSY; + + fb_info = framebuffer_alloc(sizeof(struct mxsfb_info), &pdev->dev); + if (!fb_info) { + dev_err(&pdev->dev, "Failed to allocate fbdev\n"); + ret = -ENOMEM; + goto error_alloc_info; + } + + host = to_imxfb_host(fb_info); + + host->base = ioremap(res->start, resource_size(res)); + if (!host->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto error_ioremap; + } + + host->pdev = pdev; + platform_set_drvdata(pdev, host); + + host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data]; + + host->clk = clk_get(&host->pdev->dev, NULL); + if (IS_ERR(host->clk)) { + ret = PTR_ERR(host->clk); + goto error_getclock; + } + + fb_info->pseudo_palette = kmalloc(sizeof(u32) * 16, GFP_KERNEL); + if (!fb_info->pseudo_palette) { + ret = -ENOMEM; + goto error_pseudo_pallette; + } + + INIT_LIST_HEAD(&fb_info->modelist); + + ret = mxsfb_init_fbinfo(host); + if (ret != 0) + goto error_init_fb; + + for (i = 0; i < pdata->mode_count; i++) + fb_add_videomode(&pdata->mode_list[i], &fb_info->modelist); + + modelist = list_first_entry(&fb_info->modelist, + struct fb_modelist, list); + fb_videomode_to_var(&fb_info->var, &modelist->mode); + + /* init the color fields */ + mxsfb_check_var(&fb_info->var, fb_info); + + platform_set_drvdata(pdev, fb_info); + + ret = register_framebuffer(fb_info); + if (ret != 0) { + dev_err(&pdev->dev,"Failed to register framebuffer\n"); + goto error_register; + } + + if (!host->enabled) { + writel(0, host->base + LCDC_CTRL); + mxsfb_set_par(fb_info); + mxsfb_enable_controller(fb_info); + } + + dev_info(&pdev->dev, "initialized\n"); + + return 0; + +error_register: + if (host->enabled) + clk_disable(host->clk); + fb_destroy_modelist(&fb_info->modelist); +error_init_fb: + kfree(fb_info->pseudo_palette); +error_pseudo_pallette: + clk_put(host->clk); +error_getclock: + iounmap(host->base); +error_ioremap: + framebuffer_release(fb_info); +error_alloc_info: + release_mem_region(res->start, resource_size(res)); + + return ret; +} + +static int __devexit mxsfb_remove(struct platform_device *pdev) +{ + struct fb_info *fb_info = platform_get_drvdata(pdev); + struct mxsfb_info *host = to_imxfb_host(fb_info); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (host->enabled) + mxsfb_disable_controller(fb_info); + + unregister_framebuffer(fb_info); + kfree(fb_info->pseudo_palette); + mxsfb_free_videomem(host); + iounmap(host->base); + clk_put(host->clk); + + framebuffer_release(fb_info); + release_mem_region(res->start, resource_size(res)); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_device_id mxsfb_devtype[] = { + { + .name = "imx23-fb", + .driver_data = MXSFB_V3, + }, { + .name = "imx28-fb", + .driver_data = MXSFB_V4, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, mxsfb_devtype); + +static struct platform_driver mxsfb_driver = { + .probe = mxsfb_probe, + .remove = __devexit_p(mxsfb_remove), + .id_table = mxsfb_devtype, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init mxsfb_init(void) +{ + return platform_driver_register(&mxsfb_driver); +} + +static void __exit mxsfb_exit(void) +{ + platform_driver_unregister(&mxsfb_driver); +} + +module_init(mxsfb_init); +module_exit(mxsfb_exit); + +MODULE_DESCRIPTION("Freescale mxs framebuffer driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_LICENSE("GPL"); |