summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/clocksource/vf_pit_timer.c30
-rw-r--r--drivers/dma/fsl-edma.c92
-rw-r--r--drivers/gpio/gpio-vf610.c197
-rw-r--r--drivers/iio/adc/vf610_adc.c228
-rw-r--r--drivers/input/touchscreen/Kconfig19
-rw-r--r--drivers/input/touchscreen/Makefile2
-rw-r--r--drivers/input/touchscreen/colibri-vf50-ts.c411
-rw-r--r--drivers/input/touchscreen/fusion_F0710A.c503
-rw-r--r--drivers/input/touchscreen/fusion_F0710A.h87
-rw-r--r--drivers/irqchip/Kconfig1
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-nvic.c28
-rw-r--r--drivers/irqchip/irq-vf610-gpc.c123
-rw-r--r--drivers/irqchip/irq-vf610-mscm-ir.c151
-rw-r--r--drivers/mmc/host/sdhci-esdhc-imx.c2
-rw-r--r--drivers/mmc/host/sdhci-pltfm.c36
-rw-r--r--drivers/mmc/host/sdhci-pltfm.h2
-rw-r--r--drivers/mtd/nand/Kconfig11
-rw-r--r--drivers/mtd/nand/Makefile1
-rw-r--r--drivers/mtd/nand/nand_base.c41
-rw-r--r--drivers/mtd/nand/vf610_nfc.c889
-rw-r--r--drivers/pinctrl/freescale/pinctrl-imx.c62
-rw-r--r--drivers/pinctrl/freescale/pinctrl-imx.h3
-rw-r--r--drivers/pinctrl/freescale/pinctrl-vf610.c6
-rw-r--r--drivers/pwm/pwm-fsl-ftm.c58
-rw-r--r--drivers/soc/Kconfig1
-rw-r--r--drivers/soc/Makefile1
-rw-r--r--drivers/soc/fsl/Kconfig9
-rw-r--r--drivers/soc/fsl/Makefile1
-rw-r--r--drivers/soc/fsl/soc-vf610.c168
-rw-r--r--drivers/spi/spi-fsl-dspi.c19
-rw-r--r--drivers/tty/serial/fsl_lpuart.c208
-rw-r--r--drivers/usb/chipidea/ci.h6
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c6
-rw-r--r--drivers/usb/chipidea/core.c83
-rw-r--r--drivers/video/backlight/gpio_backlight.c10
-rw-r--r--drivers/video/fbdev/Kconfig11
-rw-r--r--drivers/video/fbdev/Makefile1
-rw-r--r--drivers/video/fbdev/fsl-dcu-fb.c1275
-rw-r--r--drivers/video/logo/Kconfig4
-rw-r--r--drivers/video/logo/Makefile2
-rw-r--r--drivers/video/logo/logo.c4
42 files changed, 4595 insertions, 198 deletions
diff --git a/drivers/clocksource/vf_pit_timer.c b/drivers/clocksource/vf_pit_timer.c
index b45ac6229b57..6608ad7ec278 100644
--- a/drivers/clocksource/vf_pit_timer.c
+++ b/drivers/clocksource/vf_pit_timer.c
@@ -36,6 +36,7 @@
static void __iomem *clksrc_base;
static void __iomem *clkevt_base;
static unsigned long cycle_per_jiffy;
+static void __iomem *timer_base;
static inline void pit_timer_enable(void)
{
@@ -57,12 +58,17 @@ static u64 pit_read_sched_clock(void)
return ~__raw_readl(clksrc_base + PITCVAL);
}
-static int __init pit_clocksource_init(unsigned long rate)
+static void pit_load_and_start_clocksource(int clksrc_ldval)
{
- /* set the max load value and start the clock source counter */
__raw_writel(0, clksrc_base + PITTCTRL);
- __raw_writel(~0UL, clksrc_base + PITLDVAL);
+ __raw_writel(clksrc_ldval, clksrc_base + PITLDVAL);
__raw_writel(PITTCTRL_TEN, clksrc_base + PITTCTRL);
+}
+
+static int __init pit_clocksource_init(unsigned long rate)
+{
+ /* set the max load value and start the clock source counter */
+ pit_load_and_start_clocksource(~0UL);
sched_clock_register(pit_read_sched_clock, 32, rate);
return clocksource_mmio_init(clksrc_base + PITCVAL, "vf-pit", rate,
@@ -122,12 +128,29 @@ static irqreturn_t pit_timer_interrupt(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static unsigned int src_ldval;
+
+static void pit_resume(struct clock_event_device *evt)
+{
+ /* Enable the PIT module on resume */
+ __raw_writel(~PITMCR_MDIS, timer_base + PITMCR);
+
+ pit_load_and_start_clocksource(src_ldval);
+}
+
+static void pit_suspend(struct clock_event_device *evt)
+{
+ src_ldval = __raw_readl(clksrc_base + PITLDVAL);
+}
+
static struct clock_event_device clockevent_pit = {
.name = "VF pit timer",
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
.set_mode = pit_set_mode,
.set_next_event = pit_set_next_event,
.rating = 300,
+ .resume = pit_resume,
+ .suspend = pit_suspend,
};
static struct irqaction pit_timer_irq = {
@@ -162,7 +185,6 @@ static int __init pit_clockevent_init(unsigned long rate, int irq)
static void __init pit_timer_init(struct device_node *np)
{
struct clk *pit_clk;
- void __iomem *timer_base;
unsigned long clk_rate;
int irq;
diff --git a/drivers/dma/fsl-edma.c b/drivers/dma/fsl-edma.c
index 09e2842d15ec..cf8b06cd0c83 100644
--- a/drivers/dma/fsl-edma.c
+++ b/drivers/dma/fsl-edma.c
@@ -116,6 +116,10 @@
BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) | \
BIT(DMA_SLAVE_BUSWIDTH_8_BYTES)
+enum fsl_edma_pm_state {
+ RUNNING = 0,
+ SUSPENDED,
+};
struct fsl_edma_hw_tcd {
__le32 saddr;
@@ -147,6 +151,9 @@ struct fsl_edma_slave_config {
struct fsl_edma_chan {
struct virt_dma_chan vchan;
enum dma_status status;
+ enum fsl_edma_pm_state pm_state;
+ bool idle;
+ u32 slave_id;
struct fsl_edma_engine *edma;
struct fsl_edma_desc *edesc;
struct fsl_edma_slave_config fsc;
@@ -298,6 +305,7 @@ static int fsl_edma_terminate_all(struct dma_chan *chan)
spin_lock_irqsave(&fsl_chan->vchan.lock, flags);
fsl_edma_disable_request(fsl_chan);
fsl_chan->edesc = NULL;
+ fsl_chan->idle = true;
vchan_get_all_descriptors(&fsl_chan->vchan, &head);
spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
vchan_dma_desc_free_list(&fsl_chan->vchan, &head);
@@ -313,6 +321,7 @@ static int fsl_edma_pause(struct dma_chan *chan)
if (fsl_chan->edesc) {
fsl_edma_disable_request(fsl_chan);
fsl_chan->status = DMA_PAUSED;
+ fsl_chan->idle = true;
}
spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
return 0;
@@ -327,6 +336,7 @@ static int fsl_edma_resume(struct dma_chan *chan)
if (fsl_chan->edesc) {
fsl_edma_enable_request(fsl_chan);
fsl_chan->status = DMA_IN_PROGRESS;
+ fsl_chan->idle = false;
}
spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
return 0;
@@ -648,6 +658,7 @@ static void fsl_edma_xfer_desc(struct fsl_edma_chan *fsl_chan)
fsl_edma_set_tcd_regs(fsl_chan, fsl_chan->edesc->tcd[0].vtcd);
fsl_edma_enable_request(fsl_chan);
fsl_chan->status = DMA_IN_PROGRESS;
+ fsl_chan->idle = false;
}
static irqreturn_t fsl_edma_tx_handler(int irq, void *dev_id)
@@ -676,6 +687,7 @@ static irqreturn_t fsl_edma_tx_handler(int irq, void *dev_id)
vchan_cookie_complete(&fsl_chan->edesc->vdesc);
fsl_chan->edesc = NULL;
fsl_chan->status = DMA_COMPLETE;
+ fsl_chan->idle = true;
} else {
vchan_cyclic_callback(&fsl_chan->edesc->vdesc);
}
@@ -704,6 +716,7 @@ static irqreturn_t fsl_edma_err_handler(int irq, void *dev_id)
edma_writeb(fsl_edma, EDMA_CERR_CERR(ch),
fsl_edma->membase + EDMA_CERR);
fsl_edma->chans[ch].status = DMA_ERROR;
+ fsl_edma->chans[ch].idle = true;
}
}
return IRQ_HANDLED;
@@ -724,6 +737,12 @@ static void fsl_edma_issue_pending(struct dma_chan *chan)
spin_lock_irqsave(&fsl_chan->vchan.lock, flags);
+ if (unlikely(fsl_chan->pm_state != RUNNING)) {
+ spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
+ /* cannot submit due to suspend */
+ return;
+ }
+
if (vchan_issue_pending(&fsl_chan->vchan) && !fsl_chan->edesc)
fsl_edma_xfer_desc(fsl_chan);
@@ -735,6 +754,7 @@ static struct dma_chan *fsl_edma_xlate(struct of_phandle_args *dma_spec,
{
struct fsl_edma_engine *fsl_edma = ofdma->of_dma_data;
struct dma_chan *chan, *_chan;
+ struct fsl_edma_chan *fsl_chan;
unsigned long chans_per_mux = fsl_edma->n_chans / DMAMUX_NR;
if (dma_spec->args_count != 2)
@@ -748,8 +768,10 @@ static struct dma_chan *fsl_edma_xlate(struct of_phandle_args *dma_spec,
chan = dma_get_slave_channel(chan);
if (chan) {
chan->device->privatecnt++;
- fsl_edma_chan_mux(to_fsl_edma_chan(chan),
- dma_spec->args[1], true);
+ fsl_chan = to_fsl_edma_chan(chan);
+ fsl_chan->slave_id = dma_spec->args[1];
+ fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id,
+ true);
mutex_unlock(&fsl_edma->fsl_edma_mutex);
return chan;
}
@@ -881,10 +903,6 @@ static int fsl_edma_probe(struct platform_device *pdev)
}
- ret = fsl_edma_irq_init(pdev, fsl_edma);
- if (ret)
- return ret;
-
fsl_edma->big_endian = of_property_read_bool(np, "big-endian");
INIT_LIST_HEAD(&fsl_edma->dma_dev.channels);
@@ -892,7 +910,9 @@ static int fsl_edma_probe(struct platform_device *pdev)
struct fsl_edma_chan *fsl_chan = &fsl_edma->chans[i];
fsl_chan->edma = fsl_edma;
-
+ fsl_chan->pm_state = RUNNING;
+ fsl_chan->slave_id = 0;
+ fsl_chan->idle = true;
fsl_chan->vchan.desc_free = fsl_edma_free_desc;
vchan_init(&fsl_chan->vchan, &fsl_edma->dma_dev);
@@ -900,6 +920,11 @@ static int fsl_edma_probe(struct platform_device *pdev)
fsl_edma_chan_mux(fsl_chan, 0, false);
}
+ edma_writel(fsl_edma, ~0, fsl_edma->membase + EDMA_INTR);
+ ret = fsl_edma_irq_init(pdev, fsl_edma);
+ if (ret)
+ return ret;
+
dma_cap_set(DMA_PRIVATE, fsl_edma->dma_dev.cap_mask);
dma_cap_set(DMA_SLAVE, fsl_edma->dma_dev.cap_mask);
dma_cap_set(DMA_CYCLIC, fsl_edma->dma_dev.cap_mask);
@@ -958,6 +983,58 @@ static int fsl_edma_remove(struct platform_device *pdev)
return 0;
}
+static int fsl_edma_suspend_late(struct device *dev)
+{
+ struct fsl_edma_engine *fsl_edma = dev_get_drvdata(dev);
+ struct fsl_edma_chan *fsl_chan;
+ unsigned long flags;
+ int i;
+
+ for (i = 0; i < fsl_edma->n_chans; i++) {
+ fsl_chan = &fsl_edma->chans[i];
+ spin_lock_irqsave(&fsl_chan->vchan.lock, flags);
+ /* Make sure chan is idle or can not into suspend. */
+ if (unlikely(!fsl_chan->idle))
+ goto out;
+ fsl_chan->pm_state = SUSPENDED;
+ spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
+ }
+ return 0;
+
+out:
+ for (; i >= 0; i--) {
+ fsl_chan = &fsl_edma->chans[i];
+ fsl_chan->pm_state = RUNNING;
+ spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
+ }
+ return -EBUSY;
+}
+
+static int fsl_edma_resume_early(struct device *dev)
+{
+ struct fsl_edma_engine *fsl_edma = dev_get_drvdata(dev);
+ struct fsl_edma_chan *fsl_chan;
+ int i;
+
+ for (i = 0; i < fsl_edma->n_chans; i++) {
+ fsl_chan = &fsl_edma->chans[i];
+ fsl_chan->pm_state = RUNNING;
+ edma_writew(fsl_edma, 0x0, fsl_edma->membase + EDMA_TCD_CSR(i));
+ if (fsl_chan->slave_id != 0)
+ fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id, true);
+ }
+
+ edma_writel(fsl_edma, EDMA_CR_ERGA | EDMA_CR_ERCA,
+ fsl_edma->membase + EDMA_CR);
+
+ return 0;
+}
+
+static const struct dev_pm_ops fsl_edma_pm_ops = {
+ .suspend_late = fsl_edma_suspend_late,
+ .resume_early = fsl_edma_resume_early,
+};
+
static const struct of_device_id fsl_edma_dt_ids[] = {
{ .compatible = "fsl,vf610-edma", },
{ /* sentinel */ }
@@ -968,6 +1045,7 @@ static struct platform_driver fsl_edma_driver = {
.driver = {
.name = "fsl-edma",
.of_match_table = fsl_edma_dt_ids,
+ .pm = &fsl_edma_pm_ops,
},
.probe = fsl_edma_probe,
.remove = fsl_edma_remove,
diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c
index 7bd9f209ffa8..bd6b30d181f0 100644
--- a/drivers/gpio/gpio-vf610.c
+++ b/drivers/gpio/gpio-vf610.c
@@ -36,7 +36,10 @@ struct vf610_gpio_port {
void __iomem *base;
void __iomem *gpio_base;
u8 irqc[VF610_GPIO_PER_PORT];
+ struct platform_device *pdev_wkpu;
+ s8 fsl_wakeup[VF610_GPIO_PER_PORT];
int irq;
+ u32 state;
};
#define GPIO_PDOR 0x00
@@ -60,11 +63,26 @@ struct vf610_gpio_port {
#define PORT_INT_EITHER_EDGE 0xb
#define PORT_INT_LOGIC_ONE 0xc
+#define WKPU_WISR 0x14
+#define WKPU_IRER 0x18
+#define WKPU_WRER 0x1c
+#define WKPU_WIREER 0x28
+#define WKPU_WIFEER 0x2c
+#define WKPU_WIFER 0x30
+#define WKPU_WIPUER 0x34
+
+static struct irq_chip vf610_gpio_irq_chip;
+
static const struct of_device_id vf610_gpio_dt_ids[] = {
{ .compatible = "fsl,vf610-gpio" },
{ /* sentinel */ }
};
+static const struct of_device_id vf610_wkpu_dt_ids[] = {
+ { .compatible = "fsl,vf610-wkpu" },
+ { /* sentinel */ }
+};
+
static inline void vf610_gpio_writel(u32 val, void __iomem *reg)
{
writel_relaxed(val, reg);
@@ -149,8 +167,26 @@ static void vf610_gpio_irq_ack(struct irq_data *d)
static int vf610_gpio_irq_set_type(struct irq_data *d, u32 type)
{
struct vf610_gpio_port *port = irq_data_get_irq_chip_data(d);
+ s8 wkpu_gpio = port->fsl_wakeup[d->hwirq];
u8 irqc;
+ if (wkpu_gpio >= 0) {
+ void __iomem *base = platform_get_drvdata(port->pdev_wkpu);
+ u32 wireer, wifeer;
+ u32 mask = 1 << wkpu_gpio;
+
+ wireer = vf610_gpio_readl(base + WKPU_WIREER) & ~mask;
+ wifeer = vf610_gpio_readl(base + WKPU_WIFEER) & ~mask;
+
+ if (type & IRQ_TYPE_EDGE_RISING)
+ wireer |= mask;
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ wifeer |= mask;
+
+ vf610_gpio_writel(wireer, base + WKPU_WIREER);
+ vf610_gpio_writel(wifeer, base + WKPU_WIFEER);
+ }
+
switch (type) {
case IRQ_TYPE_EDGE_RISING:
irqc = PORT_INT_RISING_EDGE;
@@ -173,6 +209,11 @@ static int vf610_gpio_irq_set_type(struct irq_data *d, u32 type)
port->irqc[d->hwirq] = irqc;
+ if (type & IRQ_TYPE_LEVEL_MASK)
+ __irq_set_handler_locked(d->irq, handle_level_irq);
+ else
+ __irq_set_handler_locked(d->irq, handle_edge_irq);
+
return 0;
}
@@ -196,6 +237,29 @@ static void vf610_gpio_irq_unmask(struct irq_data *d)
static int vf610_gpio_irq_set_wake(struct irq_data *d, u32 enable)
{
struct vf610_gpio_port *port = irq_data_get_irq_chip_data(d);
+ s8 wkpu_gpio = port->fsl_wakeup[d->hwirq];
+
+ if (wkpu_gpio >= 0) {
+ void __iomem *base = NULL;
+ u32 wrer, irer;
+
+ base = platform_get_drvdata(port->pdev_wkpu);
+
+ /* WKPU wakeup flag for LPSTOPx modes... */
+ wrer = vf610_gpio_readl(base + WKPU_WRER);
+ irer = vf610_gpio_readl(base + WKPU_IRER);
+
+ if (enable) {
+ wrer |= 1 << wkpu_gpio;
+ irer |= 1 << wkpu_gpio;
+ } else {
+ wrer &= ~(1 << wkpu_gpio);
+ irer &= ~(1 << wkpu_gpio);
+ }
+
+ vf610_gpio_writel(wrer, base + WKPU_WRER);
+ vf610_gpio_writel(irer, base + WKPU_IRER);
+ }
if (enable)
enable_irq_wake(port->irq);
@@ -214,6 +278,77 @@ static struct irq_chip vf610_gpio_irq_chip = {
.irq_set_wake = vf610_gpio_irq_set_wake,
};
+static int __maybe_unused vf610_gpio_suspend(struct device *dev)
+{
+ struct vf610_gpio_port *port = dev_get_drvdata(dev);
+
+ port->state = vf610_gpio_readl(port->gpio_base + GPIO_PDOR);
+
+ /*
+ * There is no need to store Port state since we maintain the state
+ * alread in the irqc array
+ */
+
+ return 0;
+}
+
+static int __maybe_unused vf610_gpio_resume(struct device *dev)
+{
+ struct vf610_gpio_port *port = dev_get_drvdata(dev);
+ int i;
+
+ vf610_gpio_writel(port->state, port->gpio_base + GPIO_PDOR);
+
+ for (i = 0; i < port->gc.ngpio; i++) {
+ u32 irqc = port->irqc[i] << PORT_PCR_IRQC_OFFSET;
+
+ vf610_gpio_writel(irqc, port->base + PORT_PCR(i));
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops vf610_gpio_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(vf610_gpio_suspend, vf610_gpio_resume)
+};
+
+static int vf610_gpio_wkpu(struct device_node *np, struct vf610_gpio_port *port)
+{
+ struct platform_device *pdev = NULL;
+ struct of_phandle_args arg;
+ int i, ret;
+
+ for (i = 0; i < VF610_GPIO_PER_PORT; i++)
+ port->fsl_wakeup[i] = -1;
+
+ for (i = 0;;i++) {
+ int gpioid, wakeupid, cnt;
+
+ ret = of_parse_phandle_with_fixed_args(np, "fsl,gpio-wakeup",
+ 3, i, &arg);
+
+ if (ret == -ENOENT)
+ break;
+
+ if (!pdev)
+ pdev = of_find_device_by_node(arg.np);
+ of_node_put(arg.np);
+ if (!pdev)
+ return -EPROBE_DEFER;
+
+ gpioid = arg.args[0];
+ wakeupid = arg.args[1];
+ cnt = arg.args[2];
+
+ while (cnt-- && gpioid < VF610_GPIO_PER_PORT)
+ port->fsl_wakeup[gpioid++] = wakeupid++;
+ }
+
+ port->pdev_wkpu = pdev;
+
+ return 0;
+}
+
static int vf610_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -241,6 +376,10 @@ static int vf610_gpio_probe(struct platform_device *pdev)
if (port->irq < 0)
return port->irq;
+ ret = vf610_gpio_wkpu(np, port);
+ if (ret < 0)
+ return ret;
+
gc = &port->gc;
gc->of_node = np;
gc->dev = dev;
@@ -263,7 +402,7 @@ static int vf610_gpio_probe(struct platform_device *pdev)
vf610_gpio_writel(~0, port->base + PORT_ISFR);
ret = gpiochip_irqchip_add(gc, &vf610_gpio_irq_chip, 0,
- handle_simple_irq, IRQ_TYPE_NONE);
+ handle_edge_irq, IRQ_TYPE_NONE);
if (ret) {
dev_err(dev, "failed to add irqchip\n");
gpiochip_remove(gc);
@@ -271,6 +410,7 @@ static int vf610_gpio_probe(struct platform_device *pdev)
}
gpiochip_set_chained_irqchip(gc, &vf610_gpio_irq_chip, port->irq,
vf610_gpio_irq_handler);
+ platform_set_drvdata(pdev, port);
return 0;
}
@@ -278,6 +418,7 @@ static int vf610_gpio_probe(struct platform_device *pdev)
static struct platform_driver vf610_gpio_driver = {
.driver = {
.name = "gpio-vf610",
+ .pm = &vf610_gpio_pm_ops,
.of_match_table = vf610_gpio_dt_ids,
},
.probe = vf610_gpio_probe,
@@ -289,6 +430,60 @@ static int __init gpio_vf610_init(void)
}
device_initcall(gpio_vf610_init);
+static irqreturn_t vf610_wkpu_irq(int irq, void *data)
+{
+ void __iomem *base = data;
+ u32 wisr;
+
+ wisr = vf610_gpio_readl(base + WKPU_WISR);
+ vf610_gpio_writel(wisr, base + WKPU_WISR);
+ pr_debug("%s, WKPU interrupt received, flags %08x\n", __func__, wisr);
+
+ return 0;
+}
+
+static int vf610_wkpu_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *iores;
+ void __iomem *base;
+ int irq, err;
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(dev, iores);
+
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ err = devm_request_irq(dev, irq, vf610_wkpu_irq, 0, "wkpu-vf610", base);
+ if (err) {
+ dev_err(dev, "Error requesting IRQ!\n");
+ return err;
+ }
+
+ platform_set_drvdata(pdev, base);
+
+ return 0;
+}
+
+static struct platform_driver vf610_wkpu_driver = {
+ .driver = {
+ .name = "wkpu-vf610",
+ .of_match_table = vf610_wkpu_dt_ids,
+ },
+ .probe = vf610_wkpu_probe,
+};
+
+static int __init vf610_wkpu_init(void)
+{
+ return platform_driver_register(&vf610_wkpu_driver);
+}
+device_initcall(vf610_wkpu_init);
+
MODULE_AUTHOR("Stefan Agner <stefan@agner.ch>");
MODULE_DESCRIPTION("Freescale VF610 GPIO");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c
index 56292ae4538d..f4df2a709f05 100644
--- a/drivers/iio/adc/vf610_adc.c
+++ b/drivers/iio/adc/vf610_adc.c
@@ -68,6 +68,9 @@
#define VF610_ADC_CLK_DIV8 0x60
#define VF610_ADC_CLK_MASK 0x60
#define VF610_ADC_ADLSMP_LONG 0x10
+#define VF610_ADC_ADSTS_SHORT 0x100
+#define VF610_ADC_ADSTS_NORMAL 0x200
+#define VF610_ADC_ADSTS_LONG 0x300
#define VF610_ADC_ADSTS_MASK 0x300
#define VF610_ADC_ADLPC_EN 0x80
#define VF610_ADC_ADHSC_EN 0x400
@@ -98,6 +101,8 @@
#define VF610_ADC_CALF 0x2
#define VF610_ADC_TIMEOUT msecs_to_jiffies(100)
+#define DEFAULT_SAMPLE_TIME 1000
+
enum clk_sel {
VF610_ADCIOC_BUSCLK_SET,
VF610_ADCIOC_ALTCLK_SET,
@@ -118,15 +123,34 @@ enum average_sel {
VF610_ADC_SAMPLE_32,
};
+enum conversion_mode_sel {
+ VF610_ADC_CONV_NORMAL,
+ VF610_ADC_CONV_HIGH_SPEED,
+ VF610_ADC_CONV_LOW_POWER,
+};
+
+enum lst_adder_sel {
+ VF610_ADCK_CYCLES_3,
+ VF610_ADCK_CYCLES_5,
+ VF610_ADCK_CYCLES_7,
+ VF610_ADCK_CYCLES_9,
+ VF610_ADCK_CYCLES_13,
+ VF610_ADCK_CYCLES_17,
+ VF610_ADCK_CYCLES_21,
+ VF610_ADCK_CYCLES_25,
+};
+
struct vf610_adc_feature {
enum clk_sel clk_sel;
enum vol_ref vol_ref;
+ enum conversion_mode_sel conv_mode;
int clk_div;
int sample_rate;
int res_mode;
+ u32 lst_adder_index;
+ u32 default_sample_time;
- bool lpm;
bool calibration;
bool ovwren;
};
@@ -139,6 +163,8 @@ struct vf610_adc {
u32 vref_uv;
u32 value;
struct regulator *vref;
+
+ u32 max_adck_rate[3];
struct vf610_adc_feature adc_feature;
u32 sample_freq_avail[5];
@@ -147,47 +173,40 @@ struct vf610_adc {
};
static const u32 vf610_hw_avgs[] = { 1, 4, 8, 16, 32 };
-
-#define VF610_ADC_CHAN(_idx, _chan_type) { \
- .type = (_chan_type), \
- .indexed = 1, \
- .channel = (_idx), \
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
- .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
- BIT(IIO_CHAN_INFO_SAMP_FREQ), \
-}
-
-#define VF610_ADC_TEMPERATURE_CHAN(_idx, _chan_type) { \
- .type = (_chan_type), \
- .channel = (_idx), \
- .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
-}
-
-static const struct iio_chan_spec vf610_adc_iio_channels[] = {
- VF610_ADC_CHAN(0, IIO_VOLTAGE),
- VF610_ADC_CHAN(1, IIO_VOLTAGE),
- VF610_ADC_CHAN(2, IIO_VOLTAGE),
- VF610_ADC_CHAN(3, IIO_VOLTAGE),
- VF610_ADC_CHAN(4, IIO_VOLTAGE),
- VF610_ADC_CHAN(5, IIO_VOLTAGE),
- VF610_ADC_CHAN(6, IIO_VOLTAGE),
- VF610_ADC_CHAN(7, IIO_VOLTAGE),
- VF610_ADC_CHAN(8, IIO_VOLTAGE),
- VF610_ADC_CHAN(9, IIO_VOLTAGE),
- VF610_ADC_CHAN(10, IIO_VOLTAGE),
- VF610_ADC_CHAN(11, IIO_VOLTAGE),
- VF610_ADC_CHAN(12, IIO_VOLTAGE),
- VF610_ADC_CHAN(13, IIO_VOLTAGE),
- VF610_ADC_CHAN(14, IIO_VOLTAGE),
- VF610_ADC_CHAN(15, IIO_VOLTAGE),
- VF610_ADC_TEMPERATURE_CHAN(26, IIO_TEMP),
- /* sentinel */
-};
+static const u32 vf610_lst_adder[] = { 3, 5, 7, 9, 13, 17, 21, 25 };
static inline void vf610_adc_calculate_rates(struct vf610_adc *info)
{
+ struct vf610_adc_feature *adc_feature = &info->adc_feature;
unsigned long adck_rate, ipg_rate = clk_get_rate(info->clk);
- int i;
+ u32 adck_period, lst_addr_min;
+ int divisor, i;
+
+ adck_rate = info->max_adck_rate[adc_feature->conv_mode];
+
+ if (adck_rate) {
+ /* calculate clk divider which is within specification */
+ divisor = ipg_rate / adck_rate;
+ adc_feature->clk_div = 1 << fls(divisor + 1);
+ } else {
+ /* fall-back value using a safe divisor */
+ adc_feature->clk_div = 8;
+ }
+
+ adck_rate = ipg_rate / info->adc_feature.clk_div;
+
+ /*
+ * Determine the long sample time adder value to be used based
+ * on the default minimum sample time provided.
+ */
+ adck_period = NSEC_PER_SEC / adck_rate;
+ lst_addr_min = adc_feature->default_sample_time / adck_period;
+ for (i = 0; i < ARRAY_SIZE(vf610_lst_adder); i++) {
+ if (vf610_lst_adder[i] > lst_addr_min) {
+ adc_feature->lst_adder_index = i;
+ break;
+ }
+ }
/*
* Calculate ADC sample frequencies
@@ -198,12 +217,12 @@ static inline void vf610_adc_calculate_rates(struct vf610_adc *info)
* SFCAdder: fixed to 6 ADCK cycles
* AverageNum: 1, 4, 8, 16, 32 samples for hardware average.
* BCT (Base Conversion Time): fixed to 25 ADCK cycles for 12 bit mode
- * LSTAdder(Long Sample Time): fixed to 3 ADCK cycles
+ * LSTAdder(Long Sample Time): 3, 5, 7, 9, 13, 17, 21, 25 ADCK cycles
*/
- adck_rate = ipg_rate / info->adc_feature.clk_div;
for (i = 0; i < ARRAY_SIZE(vf610_hw_avgs); i++)
info->sample_freq_avail[i] =
- adck_rate / (6 + vf610_hw_avgs[i] * (25 + 3));
+ adck_rate / (6 + vf610_hw_avgs[i] *
+ (25 + vf610_lst_adder[adc_feature->lst_adder_index]));
}
static inline void vf610_adc_cfg_init(struct vf610_adc *info)
@@ -219,10 +238,8 @@ static inline void vf610_adc_cfg_init(struct vf610_adc *info)
adc_feature->res_mode = 12;
adc_feature->sample_rate = 1;
- adc_feature->lpm = true;
- /* Use a save ADCK which is below 20MHz on all devices */
- adc_feature->clk_div = 8;
+ adc_feature->conv_mode = VF610_ADC_CONV_LOW_POWER;
vf610_adc_calculate_rates(info);
}
@@ -304,10 +321,12 @@ static void vf610_adc_cfg_set(struct vf610_adc *info)
cfg_data = readl(info->regs + VF610_REG_ADC_CFG);
cfg_data &= ~VF610_ADC_ADLPC_EN;
- if (adc_feature->lpm)
+ if (adc_feature->conv_mode == VF610_ADC_CONV_LOW_POWER)
cfg_data |= VF610_ADC_ADLPC_EN;
cfg_data &= ~VF610_ADC_ADHSC_EN;
+ if (adc_feature->conv_mode == VF610_ADC_CONV_HIGH_SPEED)
+ cfg_data |= VF610_ADC_ADHSC_EN;
writel(cfg_data, info->regs + VF610_REG_ADC_CFG);
}
@@ -363,8 +382,40 @@ static void vf610_adc_sample_set(struct vf610_adc *info)
break;
}
- /* Use the short sample mode */
- cfg_data &= ~(VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_MASK);
+ /*
+ * Set ADLSMP and ADSTS based on the Long Sample Time Adder value
+ * determined.
+ */
+ switch (adc_feature->lst_adder_index) {
+ case VF610_ADCK_CYCLES_3:
+ break;
+ case VF610_ADCK_CYCLES_5:
+ cfg_data |= VF610_ADC_ADSTS_SHORT;
+ break;
+ case VF610_ADCK_CYCLES_7:
+ cfg_data |= VF610_ADC_ADSTS_NORMAL;
+ break;
+ case VF610_ADCK_CYCLES_9:
+ cfg_data |= VF610_ADC_ADSTS_LONG;
+ break;
+ case VF610_ADCK_CYCLES_13:
+ cfg_data |= VF610_ADC_ADLSMP_LONG;
+ break;
+ case VF610_ADCK_CYCLES_17:
+ cfg_data |= VF610_ADC_ADLSMP_LONG;
+ cfg_data |= VF610_ADC_ADSTS_SHORT;
+ break;
+ case VF610_ADCK_CYCLES_21:
+ cfg_data |= VF610_ADC_ADLSMP_LONG;
+ cfg_data |= VF610_ADC_ADSTS_NORMAL;
+ break;
+ case VF610_ADCK_CYCLES_25:
+ cfg_data |= VF610_ADC_ADLSMP_LONG;
+ cfg_data |= VF610_ADC_ADSTS_NORMAL;
+ break;
+ default:
+ dev_err(info->dev, "error in sample time select\n");
+ }
/* update hardware average selection */
cfg_data &= ~VF610_ADC_AVGS_MASK;
@@ -409,6 +460,81 @@ static void vf610_adc_hw_init(struct vf610_adc *info)
vf610_adc_cfg_set(info);
}
+static int vf610_set_conversion_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int mode)
+{
+ struct vf610_adc *info = iio_priv(indio_dev);
+
+ mutex_lock(&indio_dev->mlock);
+ info->adc_feature.conv_mode = mode;
+ vf610_adc_calculate_rates(info);
+ vf610_adc_hw_init(info);
+ mutex_unlock(&indio_dev->mlock);
+
+ return 0;
+}
+
+static int vf610_get_conversion_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct vf610_adc *info = iio_priv(indio_dev);
+
+ return info->adc_feature.conv_mode;
+}
+
+static const char * const vf610_conv_modes[] = { "normal", "high-speed",
+ "low-power" };
+
+static const struct iio_enum vf610_conversion_mode = {
+ .items = vf610_conv_modes,
+ .num_items = ARRAY_SIZE(vf610_conv_modes),
+ .get = vf610_get_conversion_mode,
+ .set = vf610_set_conversion_mode,
+};
+
+static const struct iio_chan_spec_ext_info vf610_ext_info[] = {
+ IIO_ENUM("conversion_mode", IIO_SHARED_BY_DIR, &vf610_conversion_mode),
+ {},
+};
+
+#define VF610_ADC_CHAN(_idx, _chan_type) { \
+ .type = (_chan_type), \
+ .indexed = 1, \
+ .channel = (_idx), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .ext_info = vf610_ext_info, \
+}
+
+#define VF610_ADC_TEMPERATURE_CHAN(_idx, _chan_type) { \
+ .type = (_chan_type), \
+ .channel = (_idx), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
+}
+
+static const struct iio_chan_spec vf610_adc_iio_channels[] = {
+ VF610_ADC_CHAN(0, IIO_VOLTAGE),
+ VF610_ADC_CHAN(1, IIO_VOLTAGE),
+ VF610_ADC_CHAN(2, IIO_VOLTAGE),
+ VF610_ADC_CHAN(3, IIO_VOLTAGE),
+ VF610_ADC_CHAN(4, IIO_VOLTAGE),
+ VF610_ADC_CHAN(5, IIO_VOLTAGE),
+ VF610_ADC_CHAN(6, IIO_VOLTAGE),
+ VF610_ADC_CHAN(7, IIO_VOLTAGE),
+ VF610_ADC_CHAN(8, IIO_VOLTAGE),
+ VF610_ADC_CHAN(9, IIO_VOLTAGE),
+ VF610_ADC_CHAN(10, IIO_VOLTAGE),
+ VF610_ADC_CHAN(11, IIO_VOLTAGE),
+ VF610_ADC_CHAN(12, IIO_VOLTAGE),
+ VF610_ADC_CHAN(13, IIO_VOLTAGE),
+ VF610_ADC_CHAN(14, IIO_VOLTAGE),
+ VF610_ADC_CHAN(15, IIO_VOLTAGE),
+ VF610_ADC_TEMPERATURE_CHAN(26, IIO_TEMP),
+ /* sentinel */
+};
+
static int vf610_adc_read_data(struct vf610_adc *info)
{
int result;
@@ -651,6 +777,14 @@ static int vf610_adc_probe(struct platform_device *pdev)
info->vref_uv = regulator_get_voltage(info->vref);
+ of_property_read_u32_array(pdev->dev.of_node, "fsl,adck-max-frequency",
+ info->max_adck_rate, 3);
+
+ ret = of_property_read_u32(pdev->dev.of_node, "min-sample-time",
+ &info->adc_feature.default_sample_time);
+ if (ret)
+ info->adc_feature.default_sample_time = DEFAULT_SAMPLE_TIME;
+
platform_set_drvdata(pdev, indio_dev);
init_completion(&info->completion);
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 80f6386709bf..c901680d23d7 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -594,6 +594,13 @@ config TOUCHSCREEN_MIGOR
To compile this driver as a module, choose M here: the
module will be called migor_ts.
+config TOUCHSCREEN_FUSION_F0710A
+ tristate "TouchRevolution Fusion F0710A Touchscreens"
+ depends on I2C
+ help
+ Say Y here if you want to support the multi-touch input driver for
+ the TouchRevolution Fusion 7 and 10 panels.
+
config TOUCHSCREEN_TOUCHRIGHT
tristate "Touchright serial touchscreen"
select SERIO
@@ -1027,4 +1034,16 @@ config TOUCHSCREEN_ZFORCE
To compile this driver as a module, choose M here: the
module will be called zforce_ts.
+config TOUCHSCREEN_COLIBRI_VF50
+ tristate "Toradex Colibri on board touchscreen driver"
+ depends on IIO && VF610_ADC
+ help
+ Say Y here if you have a Colibri VF50 and plan to use
+ the on-board provided 4-wire touchscreen driver.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called colibri_vf50_ts.
+
endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 44deea743d02..dce1b68d5dcb 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -84,3 +84,5 @@ obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
obj-$(CONFIG_TOUCHSCREEN_SX8654) += sx8654.o
obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o
obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o
+obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o
+obj-$(CONFIG_TOUCHSCREEN_FUSION_F0710A) += fusion_F0710A.o
diff --git a/drivers/input/touchscreen/colibri-vf50-ts.c b/drivers/input/touchscreen/colibri-vf50-ts.c
new file mode 100644
index 000000000000..74c5dd4311a7
--- /dev/null
+++ b/drivers/input/touchscreen/colibri-vf50-ts.c
@@ -0,0 +1,411 @@
+/* Copyright 2015 Toradex AG
+ *
+ * Toradex Colibri VF50 Touchscreen driver
+ *
+ * Originally authored by Stefan Agner for 3.0 kernel
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/types.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#define DRIVER_NAME "colibri-vf50-ts"
+#define DRV_VERSION "1.0"
+
+#define VF_ADC_MAX ((1 << 12) - 1)
+
+#define COLI_TOUCH_MIN_DELAY_US 1000
+#define COLI_TOUCH_MAX_DELAY_US 2000
+
+static int min_pressure = 200;
+
+struct vf50_touch_device {
+ struct platform_device *pdev;
+ struct input_dev *ts_input;
+ struct iio_channel *channels;
+ struct gpio_desc *gpio_xp;
+ struct gpio_desc *gpio_xm;
+ struct gpio_desc *gpio_yp;
+ struct gpio_desc *gpio_ym;
+ struct gpio_desc *gpio_pen_detect;
+ int pen_irq;
+ bool stop_touchscreen;
+};
+
+/*
+ * Enables given plates and measures touch parameters using ADC
+ */
+static int adc_ts_measure(struct iio_channel *channel,
+ struct gpio_desc *plate_p, struct gpio_desc *plate_m)
+{
+ int i, value = 0, val = 0;
+ int ret;
+
+ gpiod_set_value(plate_p, 1);
+ gpiod_set_value(plate_m, 1);
+
+ /* Use hrtimer sleep since msleep sleeps 10ms+ */
+ usleep_range(COLI_TOUCH_MIN_DELAY_US, COLI_TOUCH_MAX_DELAY_US);
+
+ for (i = 0; i < 5; i++) {
+ ret = iio_read_channel_raw(channel, &val);
+ if (ret < 0) {
+ value = ret;
+ goto error_iio_read;
+ }
+
+ value += val;
+ }
+
+ value /= 5;
+
+error_iio_read:
+ gpiod_set_value(plate_p, 0);
+ gpiod_set_value(plate_m, 0);
+
+ return value;
+}
+
+/*
+ * Enable touch detection using falling edge detection on XM
+ */
+static void vf50_ts_enable_touch_detection(struct vf50_touch_device *vf50_ts)
+{
+ /* Enable plate YM (needs to be strong GND, high active) */
+ gpiod_set_value(vf50_ts->gpio_ym, 1);
+
+ /*
+ * Let the platform mux to idle state in order to enable
+ * Pull-up on GPIO
+ */
+ pinctrl_pm_select_idle_state(&vf50_ts->pdev->dev);
+}
+
+/*
+ * ADC touch screen sampling bottom half irq handler
+ */
+static irqreturn_t vf50_ts_irq_bh(int irq, void *private)
+{
+ struct vf50_touch_device *vf50_ts = (struct vf50_touch_device *)private;
+ struct device *dev = &vf50_ts->pdev->dev;
+ int val_x, val_y, val_z1, val_z2, val_p = 0;
+ bool discard_val_on_start = true;
+
+ /* Disable the touch detection plates */
+ gpiod_set_value(vf50_ts->gpio_ym, 0);
+
+ /* Let the platform mux to default state in order to mux as ADC */
+ pinctrl_pm_select_default_state(dev);
+
+ while (!vf50_ts->stop_touchscreen) {
+ /* X-Direction */
+ val_x = adc_ts_measure(&vf50_ts->channels[0],
+ vf50_ts->gpio_xp, vf50_ts->gpio_xm);
+ if (val_x < 0)
+ break;
+
+ /* Y-Direction */
+ val_y = adc_ts_measure(&vf50_ts->channels[1],
+ vf50_ts->gpio_yp, vf50_ts->gpio_ym);
+ if (val_y < 0)
+ break;
+
+ /*
+ * Touch pressure
+ * Measure on XP/YM
+ */
+ val_z1 = adc_ts_measure(&vf50_ts->channels[2],
+ vf50_ts->gpio_yp, vf50_ts->gpio_xm);
+ if (val_z1 < 0)
+ break;
+ val_z2 = adc_ts_measure(&vf50_ts->channels[3],
+ vf50_ts->gpio_yp, vf50_ts->gpio_xm);
+ if (val_z2 < 0)
+ break;
+
+ /*
+ * According to datasheet of our touchscreen,
+ * resistance on X axis is 400~1200..
+ */
+
+ /* Validate signal (avoid calculation using noise) */
+ if (val_z1 > 64 && val_x > 64) {
+ /*
+ * Calculate resistance between the plates
+ * lower resistance means higher pressure
+ */
+ int r_x = (1000 * val_x) / VF_ADC_MAX;
+
+ val_p = (r_x * val_z2) / val_z1 - r_x;
+
+ } else {
+ val_p = 2000;
+ }
+
+ val_p = 2000 - val_p;
+ dev_dbg(dev, "Measured values: x: %d, y: %d, z1: %d, z2: %d, "
+ "p: %d\n", val_x, val_y, val_z1, val_z2, val_p);
+
+ /*
+ * If touch pressure is too low, stop measuring and reenable
+ * touch detection
+ */
+ if (val_p < min_pressure || val_p > 2000)
+ break;
+
+ /*
+ * The pressure may not be enough for the first x and the
+ * second y measurement, but, the pressure is ok when the
+ * driver is doing the third and fourth measurement. To
+ * take care of this, we drop the first measurement always.
+ */
+ if (discard_val_on_start) {
+ discard_val_on_start = false;
+ } else {
+ /*
+ * Report touch position and sleep for
+ * next measurement
+ */
+ input_report_abs(vf50_ts->ts_input,
+ ABS_X, VF_ADC_MAX - val_x);
+ input_report_abs(vf50_ts->ts_input,
+ ABS_Y, VF_ADC_MAX - val_y);
+ input_report_abs(vf50_ts->ts_input,
+ ABS_PRESSURE, val_p);
+ input_report_key(vf50_ts->ts_input, BTN_TOUCH, 1);
+ input_sync(vf50_ts->ts_input);
+ }
+
+ msleep(10);
+ }
+
+ /* Report no more touch, reenable touch detection */
+ input_report_abs(vf50_ts->ts_input, ABS_PRESSURE, 0);
+ input_report_key(vf50_ts->ts_input, BTN_TOUCH, 0);
+ input_sync(vf50_ts->ts_input);
+
+ vf50_ts_enable_touch_detection(vf50_ts);
+
+ /* Wait the pull-up to be stable on high */
+ msleep(10);
+
+ return IRQ_HANDLED;
+}
+
+static int vf50_ts_open(struct input_dev *dev_input)
+{
+ int ret;
+ struct vf50_touch_device *touchdev = input_get_drvdata(dev_input);
+ struct device *dev = &touchdev->pdev->dev;
+
+ dev_dbg(dev, "Input device %s opened, starting touch detection\n",
+ dev_input->name);
+
+ touchdev->stop_touchscreen = false;
+
+ ret = gpiod_direction_output(touchdev->gpio_xp, 0);
+ if (ret) {
+ dev_err(dev, "Could not set gpio xp as output %d\n", ret);
+ return ret;
+ }
+
+ ret = gpiod_direction_output(touchdev->gpio_xm, 0);
+ if (ret) {
+ dev_err(dev, "Could not set gpio xm as output %d\n", ret);
+ return ret;
+ }
+
+ ret = gpiod_direction_output(touchdev->gpio_yp, 0);
+ if (ret) {
+ dev_err(dev, "Could not set gpio yp as output %d\n", ret);
+ return ret;
+ }
+
+ ret = gpiod_direction_output(touchdev->gpio_ym, 0);
+ if (ret) {
+ dev_err(dev, "Could not set gpio ym as output %d\n", ret);
+ return ret;
+ }
+
+ ret = gpiod_direction_input(touchdev->gpio_pen_detect);
+ if (ret) {
+ dev_err(dev,
+ "Could not set gpio pen detect as input %d\n", ret);
+ return ret;
+ }
+
+ /* Mux detection before request IRQ, wait for pull-up to settle */
+ vf50_ts_enable_touch_detection(touchdev);
+ msleep(10);
+
+ return 0;
+}
+
+static void vf50_ts_close(struct input_dev *dev_input)
+{
+ struct vf50_touch_device *touchdev = input_get_drvdata(dev_input);
+ struct device *dev = &touchdev->pdev->dev;
+
+ touchdev->stop_touchscreen = true;
+
+ dev_dbg(dev, "Input device %s closed, disable touch detection\n",
+ dev_input->name);
+}
+
+static inline int vf50_ts_get_gpiod(struct device *dev,
+ struct gpio_desc **gpio_d, const char *con_id)
+{
+ int ret;
+
+ *gpio_d = devm_gpiod_get(dev, con_id);
+ if (IS_ERR(*gpio_d)) {
+ ret = PTR_ERR(*gpio_d);
+ dev_err(dev, "Could not get gpio_%s %d\n", con_id, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int vf50_ts_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+ struct vf50_touch_device *touchdev;
+ struct input_dev *input;
+ struct iio_channel *channels;
+
+ channels = iio_channel_get_all(dev);
+ if (IS_ERR(channels))
+ return PTR_ERR(channels);
+
+ touchdev = devm_kzalloc(dev, sizeof(*touchdev), GFP_KERNEL);
+ if (touchdev == NULL) {
+ ret = -ENOMEM;
+ goto error_release_channels;
+ }
+
+ input = devm_input_allocate_device(dev);
+ if (!input) {
+ dev_err(dev, "Failed to allocate TS input device\n");
+ ret = -ENOMEM;
+ goto error_release_channels;
+ }
+
+ platform_set_drvdata(pdev, touchdev);
+
+ touchdev->pdev = pdev;
+ touchdev->channels = channels;
+
+ input->name = DRIVER_NAME;
+ input->id.bustype = BUS_HOST;
+ input->dev.parent = dev;
+ input->open = vf50_ts_open;
+ input->close = vf50_ts_close;
+
+ _set_bit(EV_ABS, input->evbit);
+ _set_bit(EV_KEY, input->evbit);
+ _set_bit(BTN_TOUCH, input->keybit);
+ input_set_abs_params(input, ABS_X, 0, VF_ADC_MAX, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, VF_ADC_MAX, 0, 0);
+ input_set_abs_params(input, ABS_PRESSURE, 0, VF_ADC_MAX, 0, 0);
+
+ touchdev->ts_input = input;
+ input_set_drvdata(input, touchdev);
+ ret = input_register_device(input);
+ if (ret) {
+ dev_err(dev, "Failed to register input device\n");
+ goto error_release_channels;
+ }
+
+ ret = vf50_ts_get_gpiod(dev, &touchdev->gpio_xp, "xp");
+ if (ret)
+ goto error_release_channels;
+
+ ret = vf50_ts_get_gpiod(dev, &touchdev->gpio_xm, "xm");
+ if (ret)
+ goto error_release_channels;
+
+ ret = vf50_ts_get_gpiod(dev, &touchdev->gpio_yp, "yp");
+ if (ret)
+ goto error_release_channels;
+
+ ret = vf50_ts_get_gpiod(dev, &touchdev->gpio_ym, "ym");
+ if (ret)
+ goto error_release_channels;
+
+ ret = vf50_ts_get_gpiod(dev, &touchdev->gpio_pen_detect,
+ "pen-detect");
+ if (ret)
+ goto error_release_channels;
+
+ touchdev->pen_irq = gpiod_to_irq(touchdev->gpio_pen_detect);
+ if (touchdev->pen_irq < 0) {
+ ret = touchdev->pen_irq;
+ dev_err(dev, "Unable to get IRQ for GPIO\n");
+ goto error_release_channels;
+ }
+
+ ret = devm_request_threaded_irq(dev, touchdev->pen_irq, NULL,
+ vf50_ts_irq_bh, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "vf50 touch", touchdev);
+ if (ret < 0) {
+ dev_err(dev, "Unable to request IRQ %d\n", touchdev->pen_irq);
+ goto error_release_channels;
+ }
+
+ return 0;
+
+error_release_channels:
+ iio_channel_release_all(channels);
+ return ret;
+}
+
+static int vf50_ts_remove(struct platform_device *pdev)
+{
+ struct vf50_touch_device *touchdev = platform_get_drvdata(pdev);
+
+ iio_channel_release_all(touchdev->channels);
+
+ return 0;
+}
+
+static const struct of_device_id vf50_touch_of_match[] = {
+ { .compatible = "toradex,vf50-touchctrl", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, vf50_touch_of_match);
+
+static struct platform_driver __refdata vf50_touch_driver = {
+ .driver = {
+ .name = "toradex,vf50_touchctrl",
+ .owner = THIS_MODULE,
+ .of_match_table = vf50_touch_of_match,
+ },
+ .probe = vf50_ts_probe,
+ .remove = vf50_ts_remove,
+};
+
+module_platform_driver(vf50_touch_driver);
+
+module_param(min_pressure, int, 0600);
+MODULE_PARM_DESC(min_pressure, "Minimum pressure for touch detection");
+MODULE_AUTHOR("Sanchayan Maity");
+MODULE_DESCRIPTION("Colibri VF50 Touchscreen driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRV_VERSION);
diff --git a/drivers/input/touchscreen/fusion_F0710A.c b/drivers/input/touchscreen/fusion_F0710A.c
new file mode 100644
index 000000000000..edecf98d968e
--- /dev/null
+++ b/drivers/input/touchscreen/fusion_F0710A.c
@@ -0,0 +1,503 @@
+/*
+ * "fusion_F0710A" touchscreen driver
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <asm/irq.h>
+#include <linux/gpio.h>
+#include <linux/input/fusion_F0710A.h>
+#include <linux/input/mt.h>
+#include <linux/slab.h>
+#include <linux/of_gpio.h>
+
+
+#include "fusion_F0710A.h"
+
+#define DRV_NAME "fusion_F0710A"
+#define MAX_TOUCHES 2
+
+static struct fusion_F0710A_data fusion_F0710A;
+
+static unsigned short normal_i2c[] = { fusion_F0710A_I2C_SLAVE_ADDR, I2C_CLIENT_END };
+
+static int fusion_F0710A_write_u8(u8 addr, u8 data)
+{
+ return i2c_smbus_write_byte_data(fusion_F0710A.client, addr, data);
+}
+
+static int fusion_F0710A_read_u8(u8 addr)
+{
+ return i2c_smbus_read_byte_data(fusion_F0710A.client, addr);
+}
+
+static int fusion_F0710A_read_block(u8 addr, u8 len, u8 *data)
+{
+ u8 msgbuf0[1] = { addr };
+ u16 slave = fusion_F0710A.client->addr;
+ u16 flags = fusion_F0710A.client->flags;
+ struct i2c_msg msg[2] = { { slave, flags, 1, msgbuf0 },
+ { slave, flags | I2C_M_RD, len, data }
+ };
+
+ return i2c_transfer(fusion_F0710A.client->adapter, msg, ARRAY_SIZE(msg));
+}
+
+static int fusion_F0710A_register_input(void)
+{
+ int ret;
+ struct input_dev *dev;
+
+ dev = fusion_F0710A.input = input_allocate_device();
+ if (dev == NULL)
+ return -ENOMEM;
+
+ dev->name = "fusion_F0710A";
+
+ set_bit(EV_KEY, dev->evbit);
+ set_bit(EV_ABS, dev->evbit);
+ set_bit(EV_SYN, dev->evbit);
+ set_bit(BTN_TOUCH, dev->keybit);
+
+ input_mt_init_slots(dev, MAX_TOUCHES, 0);
+ input_set_abs_params(dev, ABS_MT_POSITION_X, 0, fusion_F0710A.info.xres-1, 0, 0);
+ input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, fusion_F0710A.info.yres-1, 0, 0);
+ input_set_abs_params(dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
+
+ input_set_abs_params(dev, ABS_X, 0, fusion_F0710A.info.xres-1, 0, 0);
+ input_set_abs_params(dev, ABS_Y, 0, fusion_F0710A.info.yres-1, 0, 0);
+ input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0);
+
+ ret = input_register_device(dev);
+ if (ret < 0)
+ goto bail1;
+
+ return 0;
+
+bail1:
+ input_free_device(dev);
+ return ret;
+}
+
+#define WC_RETRY_COUNT 3
+static int fusion_F0710A_write_complete(void)
+{
+ int ret, i;
+
+ for(i=0; i<WC_RETRY_COUNT; i++)
+ {
+ ret = fusion_F0710A_write_u8(fusion_F0710A_SCAN_COMPLETE, 0);
+ if(ret == 0)
+ break;
+ else
+ dev_err(&fusion_F0710A.client->dev, "Write complete failed(%d): %d\n", i, ret);
+ }
+
+ return ret;
+}
+
+#define DATA_START fusion_F0710A_DATA_INFO
+#define DATA_END fusion_F0710A_SEC_TIDTS
+#define DATA_LEN (DATA_END - DATA_START + 1)
+#define DATA_OFF(x) ((x) - DATA_START)
+
+static int fusion_F0710A_read_sensor(void)
+{
+ int ret;
+ u8 data[DATA_LEN];
+
+#define DATA(x) (data[DATA_OFF(x)])
+ /* To ensure data coherency, read the sensor with a single transaction. */
+ ret = fusion_F0710A_read_block(DATA_START, DATA_LEN, data);
+ if (ret < 0) {
+ dev_err(&fusion_F0710A.client->dev,
+ "Read block failed: %d\n", ret);
+
+ return ret;
+ }
+
+ fusion_F0710A.f_num = DATA(fusion_F0710A_DATA_INFO)&0x03;
+
+ fusion_F0710A.y1 = DATA(fusion_F0710A_POS_X1_HI) << 8;
+ fusion_F0710A.y1 |= DATA(fusion_F0710A_POS_X1_LO);
+ fusion_F0710A.x1 = DATA(fusion_F0710A_POS_Y1_HI) << 8;
+ fusion_F0710A.x1 |= DATA(fusion_F0710A_POS_Y1_LO);
+ fusion_F0710A.z1 = DATA(fusion_F0710A_FIR_PRESS);
+ fusion_F0710A.tip1 = DATA(fusion_F0710A_FIR_TIDTS)&0x0f;
+ fusion_F0710A.tid1 = (DATA(fusion_F0710A_FIR_TIDTS)&0xf0)>>4;
+
+
+ fusion_F0710A.y2 = DATA(fusion_F0710A_POS_X2_HI) << 8;
+ fusion_F0710A.y2 |= DATA(fusion_F0710A_POS_X2_LO);
+ fusion_F0710A.x2 = DATA(fusion_F0710A_POS_Y2_HI) << 8;
+ fusion_F0710A.x2 |= DATA(fusion_F0710A_POS_Y2_LO);
+ fusion_F0710A.z2 = DATA(fusion_F0710A_SEC_PRESS);
+ fusion_F0710A.tip2 = DATA(fusion_F0710A_SEC_TIDTS)&0x0f;
+ fusion_F0710A.tid2 =(DATA(fusion_F0710A_SEC_TIDTS)&0xf0)>>4;
+#undef DATA
+
+ return 0;
+}
+
+#define val_cut_max(x, max, reverse) \
+do \
+{ \
+ if(x > max) \
+ x = max; \
+ if(reverse) \
+ x = (max) - (x); \
+} \
+while(0)
+
+static void fusion_F0710A_wq(struct work_struct *work)
+{
+ struct input_dev *dev = fusion_F0710A.input;
+
+ if (fusion_F0710A_read_sensor() < 0)
+ goto restore_irq;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "tip1, tid1, x1, y1, z1 (%x,%x,%d,%d,%d); tip2, tid2, x2, y2, z2 (%x,%x,%d,%d,%d)\n",
+ fusion_F0710A.tip1, fusion_F0710A.tid1, fusion_F0710A.x1, fusion_F0710A.y1, fusion_F0710A.z1,
+ fusion_F0710A.tip2, fusion_F0710A.tid2, fusion_F0710A.x2, fusion_F0710A.y2, fusion_F0710A.z2);
+#endif /* DEBUG */
+
+ val_cut_max(fusion_F0710A.x1, fusion_F0710A.info.xres-1, fusion_F0710A.info.xy_reverse);
+ val_cut_max(fusion_F0710A.y1, fusion_F0710A.info.yres-1, fusion_F0710A.info.xy_reverse);
+ val_cut_max(fusion_F0710A.x2, fusion_F0710A.info.xres-1, fusion_F0710A.info.xy_reverse);
+ val_cut_max(fusion_F0710A.y2, fusion_F0710A.info.yres-1, fusion_F0710A.info.xy_reverse);
+
+ if (fusion_F0710A.tid1) {
+ input_mt_slot(dev, fusion_F0710A.tid1 - 1);
+ input_mt_report_slot_state(dev, MT_TOOL_FINGER, fusion_F0710A.tip1);
+ if (fusion_F0710A.tip1) {
+ input_report_abs(dev, ABS_MT_POSITION_X, fusion_F0710A.x1);
+ input_report_abs(dev, ABS_MT_POSITION_Y, fusion_F0710A.y1);
+ input_report_abs(dev, ABS_MT_PRESSURE, fusion_F0710A.z1);
+ }
+ }
+
+ if (fusion_F0710A.tid2) {
+ input_mt_slot(dev, fusion_F0710A.tid2 - 1);
+ input_mt_report_slot_state(dev, MT_TOOL_FINGER, fusion_F0710A.tip2);
+ if (fusion_F0710A.tip2) {
+ input_report_abs(dev, ABS_MT_POSITION_X, fusion_F0710A.x2);
+ input_report_abs(dev, ABS_MT_POSITION_Y, fusion_F0710A.y2);
+ input_report_abs(dev, ABS_MT_PRESSURE, fusion_F0710A.z2);
+ }
+ }
+
+ input_mt_report_pointer_emulation(dev, false);
+ input_sync(dev);
+
+restore_irq:
+ enable_irq(fusion_F0710A.client->irq);
+
+ /* Clear fusion_F0710A interrupt */
+ fusion_F0710A_write_complete();
+}
+static DECLARE_WORK(fusion_F0710A_work, fusion_F0710A_wq);
+
+static irqreturn_t fusion_F0710A_interrupt(int irq, void *dev_id)
+{
+ disable_irq_nosync(fusion_F0710A.client->irq);
+
+ queue_work(fusion_F0710A.workq, &fusion_F0710A_work);
+
+ return IRQ_HANDLED;
+}
+
+const static u8* g_ver_product[4] = {
+ "10Z8", "70Z7", "43Z6", ""
+};
+
+static int of_fusion_F0710A_get_pins(struct device_node *np,
+ unsigned int *int_pin, unsigned int *reset_pin)
+{
+ if (of_gpio_count(np) < 2)
+ return -ENODEV;
+
+ *int_pin = of_get_gpio(np, 0);
+ *reset_pin = of_get_gpio(np, 1);
+
+ if (!gpio_is_valid(*int_pin) || !gpio_is_valid(*reset_pin)) {
+ pr_err("%s: invalid GPIO pins, int=%d/reset=%d\n",
+ np->full_name, *int_pin, *reset_pin);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int fusion_F0710A_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
+{
+ struct device_node *np = i2c->dev.of_node;
+ struct fusion_f0710a_init_data *pdata = i2c->dev.platform_data;
+ int ret;
+ u8 ver_product, ver_id;
+ u32 version;
+
+ if (np != NULL) {
+ pdata = i2c->dev.platform_data =
+ devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL);
+ if (pdata == NULL) {
+ dev_err(&i2c->dev, "No platform data for Fusion driver\n");
+ return -ENODEV;
+ }
+ /* the dtb did the pinmuxing for us */
+ pdata->pinmux_fusion_pins = NULL;
+ ret = of_fusion_F0710A_get_pins(i2c->dev.of_node,
+ &pdata->gpio_int, &pdata->gpio_reset);
+ if (ret)
+ return ret;
+ }
+ else if (pdata == NULL) {
+ dev_err(&i2c->dev, "No platform data for Fusion driver\n");
+ return -ENODEV;
+ }
+
+ /* Request pinmuxing, if necessary */
+ if (pdata->pinmux_fusion_pins != NULL) {
+ ret = pdata->pinmux_fusion_pins();
+ if (ret < 0) {
+ dev_err(&i2c->dev, "muxing GPIOs failed\n");
+ return -ENODEV;
+ }
+ }
+
+ if ((gpio_request(pdata->gpio_int, "Fusion pen down interrupt") == 0) &&
+ (gpio_direction_input(pdata->gpio_int) == 0)) {
+ gpio_export(pdata->gpio_int, 0);
+ } else {
+ dev_warn(&i2c->dev, "Could not obtain GPIO for Fusion pen down\n");
+ return -ENODEV;
+ }
+
+ if ((gpio_request(pdata->gpio_reset, "Fusion reset") == 0) &&
+ (gpio_direction_output(pdata->gpio_reset, 1) == 0)) {
+
+ /* Generate a 0 => 1 edge explicitly, and wait for startup... */
+ gpio_set_value(pdata->gpio_reset, 0);
+ msleep(10);
+ gpio_set_value(pdata->gpio_reset, 1);
+ /* Wait for startup (up to 125ms according to datasheet) */
+ msleep(125);
+
+ gpio_export(pdata->gpio_reset, 0);
+ } else {
+ dev_warn(&i2c->dev, "Could not obtain GPIO for Fusion reset\n");
+ ret = -ENODEV;
+ goto bail0;
+ }
+
+ /* Use Pen Down GPIO as sampling interrupt */
+ i2c->irq = gpio_to_irq(pdata->gpio_int);
+ irq_set_irq_type(i2c->irq, IRQ_TYPE_LEVEL_HIGH);
+
+ if(!i2c->irq)
+ {
+ dev_err(&i2c->dev, "fusion_F0710A irq < 0 \n");
+ ret = -ENOMEM;
+ goto bail1;
+ }
+
+ /* Attach the I2C client */
+ fusion_F0710A.client = i2c;
+ i2c_set_clientdata(i2c, &fusion_F0710A);
+
+ dev_info(&i2c->dev, "Touchscreen registered with bus id (%d) with slave address 0x%x\n",
+ i2c_adapter_id(fusion_F0710A.client->adapter), fusion_F0710A.client->addr);
+
+ /* Read out a lot of registers */
+ ret = fusion_F0710A_read_u8(fusion_F0710A_VIESION_INFO_LO);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "query failed: %d\n", ret);
+ goto bail1;
+ }
+ ver_product = (((u8)ret) & 0xc0) >> 6;
+ version = (10 + ((((u32)ret)&0x30) >> 4)) * 100000;
+ version += (((u32)ret)&0xf) * 1000;
+ /* Read out a lot of registers */
+ ret = fusion_F0710A_read_u8(fusion_F0710A_VIESION_INFO);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "query failed: %d\n", ret);
+ goto bail1;
+ }
+ ver_id = ((u8)(ret) & 0x6) >> 1;
+ version += ((((u32)ret) & 0xf8) >> 3) * 10;
+ version += (((u32)ret) & 0x1) + 1; /* 0 is build 1, 1 is build 2 */
+ dev_info(&i2c->dev, "version product %s(%d)\n", g_ver_product[ver_product] ,ver_product);
+ dev_info(&i2c->dev, "version id %s(%d)\n", ver_id ? "1.4" : "1.0", ver_id);
+ dev_info(&i2c->dev, "version series (%d)\n", version);
+
+ switch(ver_product)
+ {
+ case fusion_F0710A_VIESION_07: /* 7 inch */
+ fusion_F0710A.info.xres = fusion_F0710A07_XMAX;
+ fusion_F0710A.info.yres = fusion_F0710A07_YMAX;
+ fusion_F0710A.info.xy_reverse = fusion_F0710A07_REV;
+ break;
+ case fusion_F0710A_VIESION_43: /* 4.3 inch */
+ fusion_F0710A.info.xres = fusion_F0710A43_XMAX;
+ fusion_F0710A.info.yres = fusion_F0710A43_YMAX;
+ fusion_F0710A.info.xy_reverse = fusion_F0710A43_REV;
+ break;
+ default: /* fusion_F0710A_VIESION_10 10 inch */
+ fusion_F0710A.info.xres = fusion_F0710A10_XMAX;
+ fusion_F0710A.info.yres = fusion_F0710A10_YMAX;
+ fusion_F0710A.info.xy_reverse = fusion_F0710A10_REV;
+ break;
+ }
+
+ /* Register the input device. */
+ ret = fusion_F0710A_register_input();
+ if (ret < 0) {
+ dev_err(&i2c->dev, "can't register input: %d\n", ret);
+ goto bail1;
+ }
+
+ /* Create a worker thread */
+ fusion_F0710A.workq = create_singlethread_workqueue(DRV_NAME);
+ if (fusion_F0710A.workq == NULL) {
+ dev_err(&i2c->dev, "can't create work queue\n");
+ ret = -ENOMEM;
+ goto bail2;
+ }
+
+
+ /* Register for the interrupt and enable it. Our handler will
+ * start getting invoked after this call. */
+ ret = request_irq(i2c->irq, fusion_F0710A_interrupt, IRQF_TRIGGER_RISING,
+ i2c->name, &fusion_F0710A);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "can't get irq %d: %d\n", i2c->irq, ret);
+ goto bail3;
+ }
+ /* clear the irq first */
+ ret = fusion_F0710A_write_u8(fusion_F0710A_SCAN_COMPLETE, 0);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "Clear irq failed: %d\n", ret);
+ goto bail4;
+ }
+
+ return 0;
+
+bail4:
+ free_irq(i2c->irq, &fusion_F0710A);
+
+bail3:
+ destroy_workqueue(fusion_F0710A.workq);
+ fusion_F0710A.workq = NULL;
+
+bail2:
+ input_unregister_device(fusion_F0710A.input);
+bail1:
+ gpio_free(pdata->gpio_reset);
+bail0:
+ gpio_free(pdata->gpio_int);
+
+ return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int fusion_F0710A_suspend(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ disable_irq(i2c->irq);
+ flush_workqueue(fusion_F0710A.workq);
+
+ return 0;
+}
+
+static int fusion_F0710A_resume(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ enable_irq(i2c->irq);
+
+ return 0;
+}
+#endif
+
+static int fusion_F0710A_remove(struct i2c_client *i2c)
+{
+ struct fusion_f0710a_init_data *pdata = i2c->dev.platform_data;
+
+ gpio_free(pdata->gpio_int);
+ gpio_free(pdata->gpio_reset);
+ destroy_workqueue(fusion_F0710A.workq);
+ free_irq(i2c->irq, &fusion_F0710A);
+ input_unregister_device(fusion_F0710A.input);
+ i2c_set_clientdata(i2c, NULL);
+
+ dev_info(&i2c->dev, "driver removed\n");
+
+ return 0;
+}
+
+static struct i2c_device_id fusion_F0710A_id[] = {
+ {"fusion_F0710A", 0},
+ {},
+};
+
+
+static const struct of_device_id fusion_F0710A_dt_ids[] = {
+ {
+ .compatible = "touchrevolution,fusion-f0710a",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, fusion_F0710A_dt_ids);
+
+static const struct dev_pm_ops fusion_F0710A_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(fusion_F0710A_suspend, fusion_F0710A_resume)
+};
+
+static struct i2c_driver fusion_F0710A_i2c_drv = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = DRV_NAME,
+ .pm = &fusion_F0710A_pm_ops,
+ .of_match_table = fusion_F0710A_dt_ids,
+ },
+ .probe = fusion_F0710A_probe,
+ .remove = fusion_F0710A_remove,
+ .id_table = fusion_F0710A_id,
+ .address_list = normal_i2c,
+};
+
+static int __init fusion_F0710A_init( void )
+{
+ int ret;
+
+ memset(&fusion_F0710A, 0, sizeof(fusion_F0710A));
+
+ /* Probe for fusion_F0710A on I2C. */
+ ret = i2c_add_driver(&fusion_F0710A_i2c_drv);
+ if (ret < 0) {
+ printk(KERN_WARNING DRV_NAME " can't add i2c driver: %d\n", ret);
+ }
+
+ return ret;
+}
+
+static void __exit fusion_F0710A_exit( void )
+{
+ i2c_del_driver(&fusion_F0710A_i2c_drv);
+}
+module_init(fusion_F0710A_init);
+module_exit(fusion_F0710A_exit);
+
+MODULE_DESCRIPTION("fusion_F0710A Touchscreen Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/input/touchscreen/fusion_F0710A.h b/drivers/input/touchscreen/fusion_F0710A.h
new file mode 100644
index 000000000000..85f8210345a9
--- /dev/null
+++ b/drivers/input/touchscreen/fusion_F0710A.h
@@ -0,0 +1,87 @@
+/*
+ * "fusion_F0710A" touchscreen driver
+ *
+ * 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.
+ */
+
+/* I2C slave address */
+#define fusion_F0710A_I2C_SLAVE_ADDR 0x10
+
+/* I2C registers */
+#define fusion_F0710A_DATA_INFO 0x00
+
+/* First Point*/
+#define fusion_F0710A_POS_X1_HI 0x01 /* 16-bit register, MSB */
+#define fusion_F0710A_POS_X1_LO 0x02 /* 16-bit register, LSB */
+#define fusion_F0710A_POS_Y1_HI 0x03 /* 16-bit register, MSB */
+#define fusion_F0710A_POS_Y1_LO 0x04 /* 16-bit register, LSB */
+#define fusion_F0710A_FIR_PRESS 0X05
+#define fusion_F0710A_FIR_TIDTS 0X06
+
+/* Second Point */
+#define fusion_F0710A_POS_X2_HI 0x07 /* 16-bit register, MSB */
+#define fusion_F0710A_POS_X2_LO 0x08 /* 16-bit register, LSB */
+#define fusion_F0710A_POS_Y2_HI 0x09 /* 16-bit register, MSB */
+#define fusion_F0710A_POS_Y2_LO 0x0A /* 16-bit register, LSB */
+#define fusion_F0710A_SEC_PRESS 0x0B
+#define fusion_F0710A_SEC_TIDTS 0x0C
+
+#define fusion_F0710A_VIESION_INFO_LO 0X0E
+#define fusion_F0710A_VIESION_INFO 0X0F
+
+#define fusion_F0710A_RESET 0x10
+#define fusion_F0710A_SCAN_COMPLETE 0x11
+
+
+#define fusion_F0710A_VIESION_10 0
+#define fusion_F0710A_VIESION_07 1
+#define fusion_F0710A_VIESION_43 2
+
+/* fusion_F0710A 10 inch panel */
+#define fusion_F0710A10_XMAX 2275
+#define fusion_F0710A10_YMAX 1275
+#define fusion_F0710A10_REV 1
+
+/* fusion_F0710A 7 inch panel */
+#define fusion_F0710A07_XMAX 1500
+#define fusion_F0710A07_YMAX 900
+#define fusion_F0710A07_REV 0
+
+/* fusion_F0710A 4.3 inch panel */
+#define fusion_F0710A43_XMAX 900
+#define fusion_F0710A43_YMAX 500
+#define fusion_F0710A43_REV 0
+
+#define fusion_F0710A_SAVE_PT1 0x1
+#define fusion_F0710A_SAVE_PT2 0x2
+
+
+
+/* fusion_F0710A touch screen information */
+struct fusion_F0710A_info {
+ int xres; /* x resolution */
+ int yres; /* y resolution */
+ int xy_reverse; /* if need reverse in the x,y value x=xres-1-x, y=yres-1-y*/
+};
+
+struct fusion_F0710A_data {
+ struct fusion_F0710A_info info;
+ struct i2c_client *client;
+ struct workqueue_struct *workq;
+ struct input_dev *input;
+ u16 x1;
+ u16 y1;
+ u8 z1;
+ u8 tip1;
+ u8 tid1;
+ u16 x2;
+ u16 y2;
+ u8 z2;
+ u8 tip2;
+ u8 tid2;
+ u8 f_num;
+ u8 save_points;
+};
+
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 6de62a96e79c..99b9a9792975 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -30,6 +30,7 @@ config ARM_GIC_V3_ITS
config ARM_NVIC
bool
select IRQ_DOMAIN
+ select IRQ_DOMAIN_HIERARCHY
select GENERIC_IRQ_CHIP
config ARM_VIC
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index dda4927e47a6..e17ec6c715cb 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_TB10X_IRQC) += irq-tb10x.o
obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o
obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o
+obj-$(CONFIG_SOC_VF610) += irq-vf610-gpc.o
obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o
obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o
obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o
diff --git a/drivers/irqchip/irq-nvic.c b/drivers/irqchip/irq-nvic.c
index 4ff0805fca01..5fac9100f6cb 100644
--- a/drivers/irqchip/irq-nvic.c
+++ b/drivers/irqchip/irq-nvic.c
@@ -49,6 +49,31 @@ nvic_handle_irq(irq_hw_number_t hwirq, struct pt_regs *regs)
handle_IRQ(irq, regs);
}
+static int nvic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ int i, ret;
+ irq_hw_number_t hwirq;
+ unsigned int type = IRQ_TYPE_NONE;
+ struct of_phandle_args *irq_data = arg;
+
+ ret = irq_domain_xlate_onecell(domain, irq_data->np, irq_data->args,
+ irq_data->args_count, &hwirq, &type);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < nr_irqs; i++)
+ irq_map_generic_chip(domain, virq + i, hwirq + i);
+
+ return 0;
+}
+
+static const struct irq_domain_ops nvic_irq_domain_ops = {
+ .xlate = irq_domain_xlate_onecell,
+ .alloc = nvic_irq_domain_alloc,
+ .free = irq_domain_free_irqs_top,
+};
+
static int __init nvic_of_init(struct device_node *node,
struct device_node *parent)
{
@@ -70,7 +95,8 @@ static int __init nvic_of_init(struct device_node *node,
irqs = NVIC_MAX_IRQ;
nvic_irq_domain =
- irq_domain_add_linear(node, irqs, &irq_generic_chip_ops, NULL);
+ irq_domain_add_linear(node, irqs, &nvic_irq_domain_ops, NULL);
+
if (!nvic_irq_domain) {
pr_warn("Failed to allocate irq domain\n");
return -ENOMEM;
diff --git a/drivers/irqchip/irq-vf610-gpc.c b/drivers/irqchip/irq-vf610-gpc.c
new file mode 100644
index 000000000000..5527e103f9b0
--- /dev/null
+++ b/drivers/irqchip/irq-vf610-gpc.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2015 Toradex AG
+ * Author: Stefan Agner <stefan@agner.ch>
+ *
+ * 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.
+ *
+ *
+ * The GPC (General Power Controller) irqchip driver takes care of the
+ * interrupt wakeup functionality.
+ *
+ * o All peripheral interrupts of the Vybrid SoC can be used as wakeup
+ * source from STOP mode. In LPSTOP mode however, the GPC is unpowered
+ * too and cannot be used to as a wakeup source. The WKPU (Wakeup Unit)
+ * is responsible for wakeups from LPSTOP modes.
+ */
+
+#include <linux/cpu_pm.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/syscon.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+
+#include "irqchip.h"
+
+#define IMR_NUM 4
+#define VF610_GPC_IMR1 0x044
+#define VF610_GPC_MAX_IRQS (IMR_NUM * 32)
+
+static void __iomem *gpc_base;
+
+static int vf610_gpc_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+ unsigned int idx = d->hwirq / 32;
+ void __iomem *reg_imr = gpc_base + VF610_GPC_IMR1 + (idx * 4);
+ u32 mask = 1 << d->hwirq % 32;
+
+ if (on)
+ writel_relaxed(readl_relaxed(reg_imr) & ~mask, reg_imr);
+ else
+ writel_relaxed(readl_relaxed(reg_imr) | mask, reg_imr);
+
+ /*
+ * Do *not* call into the parent, as the GIC doesn't have any
+ * wake-up facility...
+ */
+ return 0;
+}
+
+static struct irq_chip vf610_gpc_chip = {
+ .name = "vf610-gpc",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_enable = irq_chip_enable_parent,
+ .irq_disable = irq_chip_disable_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_retrigger = irq_chip_retrigger_hierarchy,
+ .irq_set_wake = vf610_gpc_irq_set_wake,
+};
+
+static int vf610_gpc_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *data)
+{
+ struct of_phandle_args *args = data;
+ struct of_phandle_args parent_args;
+ irq_hw_number_t hwirq;
+ int i;
+
+ if (args->args_count != 2)
+ return -EINVAL;
+
+ hwirq = args->args[0];
+ for (i = 0; i < nr_irqs; i++)
+ irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+ &vf610_gpc_chip, NULL);
+
+ parent_args = *args;
+ parent_args.np = domain->parent->of_node;
+ return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_args);
+}
+
+static const struct irq_domain_ops gpc_irq_domain_ops = {
+ .xlate = irq_domain_xlate_twocell,
+ .alloc = vf610_gpc_domain_alloc,
+ .free = irq_domain_free_irqs_common,
+};
+
+static int __init vf610_gpc_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct irq_domain *domain, *domain_parent;
+ int i;
+
+ domain_parent = irq_find_host(parent);
+ if (!domain_parent) {
+ pr_err("vf610_gpc: interrupt-parent not found\n");
+ return -EINVAL;
+ }
+
+ gpc_base = of_io_request_and_map(node, 0, "gpc");
+ if (WARN_ON(!gpc_base))
+ return -ENOMEM;
+
+ domain = irq_domain_add_hierarchy(domain_parent, 0, VF610_GPC_MAX_IRQS,
+ node, &gpc_irq_domain_ops, NULL);
+ if (!domain) {
+ iounmap(gpc_base);
+ return -ENOMEM;
+ }
+
+ /* Initially mask all interrupts for wakeup */
+ for (i = 0; i < IMR_NUM; i++)
+ writel_relaxed(~0, gpc_base + VF610_GPC_IMR1 + i * 4);
+
+ return 0;
+}
+IRQCHIP_DECLARE(vf610_gpc, "fsl,vf610-gpc", vf610_gpc_of_init);
diff --git a/drivers/irqchip/irq-vf610-mscm-ir.c b/drivers/irqchip/irq-vf610-mscm-ir.c
index 9521057d4744..8fe2cacd6eca 100644
--- a/drivers/irqchip/irq-vf610-mscm-ir.c
+++ b/drivers/irqchip/irq-vf610-mscm-ir.c
@@ -23,14 +23,17 @@
* variants of Vybrid.
*/
+#include <linux/bitops.h>
#include <linux/cpu_pm.h>
#include <linux/io.h>
+#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/mfd/syscon.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/regmap.h>
@@ -38,19 +41,42 @@
#define MSCM_CPxNUM 0x4
+#define MSCM_IRCP0IR 0x0
+#define MSCM_IRCP1IR 0x4
+#define MSCM_IRCPnIR(n) ((n) * 0x4 + MSCM_IRCP0IR)
+#define MSCM_IRCPnIR_INT(n) (0x1 << (n))
+#define MSCM_IRCPGIR 0x20
+
+#define MSCM_INTID_MASK 0x3
+#define MSCM_INTID(n) ((n) & MSCM_INTID_MASK)
+#define MSCM_CPUTL(n) (((n) == 0 ? 1 : 2) << 16)
+
#define MSCM_IRSPRC(n) (0x80 + 2 * (n))
#define MSCM_IRSPRC_CPEN_MASK 0x3
#define MSCM_IRSPRC_NUM 112
+#define MSCM_CPU2CPU_NUM 4
+
struct vf610_mscm_ir_chip_data {
void __iomem *mscm_ir_base;
- u16 cpu_mask;
+ u16 cpu_id;
u16 saved_irsprc[MSCM_IRSPRC_NUM];
+ bool is_nvic;
+ struct device_node *cpu2cpu_node;
+};
+
+struct mscm_cpu2cpu_irq_data {
+ int intid;
+ int irq;
+ irq_handler_t handler;
+ void *priv;
};
static struct vf610_mscm_ir_chip_data *mscm_ir_data;
+static struct mscm_cpu2cpu_irq_data cpu2cpu_irq_data[MSCM_CPU2CPU_NUM];
+
static inline void vf610_mscm_ir_save(struct vf610_mscm_ir_chip_data *data)
{
int i;
@@ -94,24 +120,23 @@ static void vf610_mscm_ir_enable(struct irq_data *data)
u16 irsprc;
irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
- irsprc &= MSCM_IRSPRC_CPEN_MASK;
-
- WARN_ON(irsprc & ~chip_data->cpu_mask);
-
- writew_relaxed(chip_data->cpu_mask,
+ writew_relaxed(irsprc | BIT(chip_data->cpu_id),
chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
- irq_chip_unmask_parent(data);
+ irq_chip_enable_parent(data);
}
static void vf610_mscm_ir_disable(struct irq_data *data)
{
irq_hw_number_t hwirq = data->hwirq;
struct vf610_mscm_ir_chip_data *chip_data = data->chip_data;
+ u16 irsprc;
- writew_relaxed(0x0, chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
+ irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
+ writew_relaxed(irsprc & ~BIT(chip_data->cpu_id),
+ chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
- irq_chip_mask_parent(data);
+ irq_chip_disable_parent(data);
}
static struct irq_chip vf610_mscm_ir_irq_chip = {
@@ -143,10 +168,17 @@ static int vf610_mscm_ir_domain_alloc(struct irq_domain *domain, unsigned int vi
domain->host_data);
gic_data.np = domain->parent->of_node;
- gic_data.args_count = 3;
- gic_data.args[0] = GIC_SPI;
- gic_data.args[1] = irq_data->args[0];
- gic_data.args[2] = irq_data->args[1];
+
+ if (mscm_ir_data->is_nvic) {
+ gic_data.args_count = 1;
+ gic_data.args[0] = irq_data->args[0];
+ } else {
+ gic_data.args_count = 3;
+ gic_data.args[0] = GIC_SPI;
+ gic_data.args[1] = irq_data->args[0];
+ gic_data.args[2] = irq_data->args[1];
+ }
+
return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_data);
}
@@ -156,6 +188,91 @@ static const struct irq_domain_ops mscm_irq_domain_ops = {
.free = irq_domain_free_irqs_common,
};
+
+static irqreturn_t mscm_cpu2cpu_irq_handler(int irq, void *dev_id)
+{
+ irqreturn_t ret;
+ struct mscm_cpu2cpu_irq_data *data = dev_id;
+ void __iomem *mscm_base = mscm_ir_data->mscm_ir_base;
+ int cpu_id = mscm_ir_data->cpu_id;
+
+
+ ret = data->handler(data->intid, data->priv);
+ if (ret == IRQ_HANDLED)
+ writel(MSCM_IRCPnIR_INT(data->intid), mscm_base + MSCM_IRCPnIR(cpu_id));
+
+ return ret;
+}
+
+int mscm_request_cpu2cpu_irq(unsigned int intid, irq_handler_t handler,
+ const char *name, void *priv)
+{
+ int irq;
+ struct mscm_cpu2cpu_irq_data *data;
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return -EINVAL;
+
+ irq = of_irq_get(mscm_ir_data->cpu2cpu_node, intid);
+ if (irq < 0)
+ return irq;
+
+ data = &cpu2cpu_irq_data[intid];
+ data->intid = intid;
+ data->irq = irq;
+ data->handler = handler;
+ data->priv = priv;
+
+ return request_irq(irq, mscm_cpu2cpu_irq_handler, 0, name, data);
+}
+EXPORT_SYMBOL(mscm_request_cpu2cpu_irq);
+
+void mscm_free_cpu2cpu_irq(unsigned int intid, void *priv)
+{
+ struct mscm_cpu2cpu_irq_data *data;
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return;
+
+ data = &cpu2cpu_irq_data[intid];
+
+ if (data->irq < 0)
+ return;
+
+ free_irq(data->irq, data);
+}
+EXPORT_SYMBOL(mscm_free_cpu2cpu_irq);
+
+void mscm_trigger_cpu2cpu_irq(unsigned int intid, int cpuid)
+{
+ void __iomem *mscm_base = mscm_ir_data->mscm_ir_base;
+
+ writel(MSCM_INTID(intid) | MSCM_CPUTL(cpuid), mscm_base + MSCM_IRCPGIR);
+}
+EXPORT_SYMBOL(mscm_trigger_cpu2cpu_irq);
+
+void mscm_enable_cpu2cpu_irq(unsigned int intid)
+{
+ struct mscm_cpu2cpu_irq_data *data = &cpu2cpu_irq_data[intid];
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return;
+
+ enable_irq(data->irq);
+}
+EXPORT_SYMBOL(mscm_enable_cpu2cpu_irq);
+
+void mscm_disable_cpu2cpu_irq(unsigned int intid)
+{
+ struct mscm_cpu2cpu_irq_data *data = &cpu2cpu_irq_data[intid];
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return;
+
+ disable_irq(data->irq);
+}
+EXPORT_SYMBOL(mscm_disable_cpu2cpu_irq);
+
static int __init vf610_mscm_ir_of_init(struct device_node *node,
struct device_node *parent)
{
@@ -188,8 +305,7 @@ static int __init vf610_mscm_ir_of_init(struct device_node *node,
goto out_unmap;
}
- regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid);
- mscm_ir_data->cpu_mask = 0x1 << cpuid;
+ mscm_ir_data->cpu_id = regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid);
domain = irq_domain_add_hierarchy(domain_parent, 0,
MSCM_IRSPRC_NUM, node,
@@ -199,8 +315,13 @@ static int __init vf610_mscm_ir_of_init(struct device_node *node,
goto out_unmap;
}
+ if (of_device_is_compatible(domain->parent->of_node, "arm,armv7m-nvic"))
+ mscm_ir_data->is_nvic = true;
+
cpu_pm_register_notifier(&mscm_ir_notifier_block);
+ mscm_ir_data->cpu2cpu_node = of_get_child_by_name(node, "cpu2cpu");
+
return 0;
out_unmap:
diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c
index 461698b038f7..cf05cfcc279d 100644
--- a/drivers/mmc/host/sdhci-esdhc-imx.c
+++ b/drivers/mmc/host/sdhci-esdhc-imx.c
@@ -1170,7 +1170,7 @@ static int sdhci_esdhc_runtime_resume(struct device *dev)
#endif
static const struct dev_pm_ops sdhci_esdhc_pmops = {
- SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend, sdhci_pltfm_resume)
+ SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_rpm_suspend, sdhci_pltfm_rpm_resume)
SET_RUNTIME_PM_OPS(sdhci_esdhc_runtime_suspend,
sdhci_esdhc_runtime_resume, NULL)
};
diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c
index a207f5aaf62f..38c03cd66052 100644
--- a/drivers/mmc/host/sdhci-pltfm.c
+++ b/drivers/mmc/host/sdhci-pltfm.c
@@ -31,6 +31,7 @@
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/pm_runtime.h>
#ifdef CONFIG_PPC
#include <asm/machdep.h>
#endif
@@ -256,6 +257,41 @@ const struct dev_pm_ops sdhci_pltfm_pmops = {
.resume = sdhci_pltfm_resume,
};
EXPORT_SYMBOL_GPL(sdhci_pltfm_pmops);
+
+int sdhci_pltfm_rpm_suspend(struct device *dev)
+{
+ int ret;
+ struct sdhci_host *host = dev_get_drvdata(dev);
+
+ pm_runtime_get_sync(dev);
+ ret = sdhci_suspend_host(host);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ if (ret)
+ return ret;
+
+ return pm_runtime_force_suspend(dev);
+}
+EXPORT_SYMBOL_GPL(sdhci_pltfm_rpm_suspend);
+
+int sdhci_pltfm_rpm_resume(struct device *dev)
+{
+ int ret;
+ struct sdhci_host *host = dev_get_drvdata(dev);
+
+ ret = pm_runtime_force_resume(dev);
+
+ if (ret)
+ return ret;
+
+ pm_runtime_get_sync(dev);
+ ret = sdhci_resume_host(host);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sdhci_pltfm_rpm_resume);
#endif /* CONFIG_PM */
static int __init sdhci_pltfm_drv_init(void)
diff --git a/drivers/mmc/host/sdhci-pltfm.h b/drivers/mmc/host/sdhci-pltfm.h
index 04bc2481e5c3..ac5f6ea9b55f 100644
--- a/drivers/mmc/host/sdhci-pltfm.h
+++ b/drivers/mmc/host/sdhci-pltfm.h
@@ -114,6 +114,8 @@ static inline void *sdhci_pltfm_priv(struct sdhci_pltfm_host *host)
extern int sdhci_pltfm_suspend(struct device *dev);
extern int sdhci_pltfm_resume(struct device *dev);
extern const struct dev_pm_ops sdhci_pltfm_pmops;
+extern int sdhci_pltfm_rpm_suspend(struct device *dev);
+extern int sdhci_pltfm_rpm_resume(struct device *dev);
#define SDHCI_PLTFM_PMOPS (&sdhci_pltfm_pmops)
#else
#define SDHCI_PLTFM_PMOPS NULL
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 5897d8d8fa5a..b3c57c2e141d 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -455,6 +455,17 @@ config MTD_NAND_MPC5121_NFC
This enables the driver for the NAND flash controller on the
MPC5121 SoC.
+config MTD_NAND_VF610_NFC
+ tristate "Support for Freescale NFC for VF610/MPC5125"
+ depends on (SOC_VF610 || COMPILE_TEST)
+ help
+ Enables support for NAND Flash Controller on some Freescale
+ processors like the VF610, MPC5125, MCF54418 or Kinetis K70.
+ The driver supports a maximum 2k page size. With 2k pages and
+ 64 bytes or more of OOB, hardware ECC with up to 32-bit error
+ correction is supported. Hardware ECC is only enabled through
+ device tree.
+
config MTD_NAND_MXC
tristate "MXC NAND support"
depends on ARCH_MXC
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 582bbd05aff7..e97ca7bef832 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o
obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o
obj-$(CONFIG_MTD_NAND_NUC900) += nuc900_nand.o
obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o
+obj-$(CONFIG_MTD_NAND_VF610_NFC) += vf610_nfc.o
obj-$(CONFIG_MTD_NAND_RICOH) += r852.o
obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o
obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index c2e1232cd45c..c6cf775ee431 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -48,6 +48,7 @@
#include <linux/leds.h>
#include <linux/io.h>
#include <linux/mtd/partitions.h>
+#include <linux/of_mtd.h>
/* Define default oob placement schemes for large and small page devices */
static struct nand_ecclayout nand_oob_8 = {
@@ -3798,6 +3799,39 @@ ident_done:
return type;
}
+static int nand_dt_init(struct mtd_info *mtd, struct nand_chip *chip,
+ struct device_node *dn)
+{
+ int ecc_mode, ecc_strength, ecc_step;
+
+ if (of_get_nand_bus_width(dn) == 16)
+ chip->options |= NAND_BUSWIDTH_16;
+
+ if (of_get_nand_on_flash_bbt(dn))
+ chip->bbt_options |= NAND_BBT_USE_FLASH;
+
+ ecc_mode = of_get_nand_ecc_mode(dn);
+ ecc_strength = of_get_nand_ecc_strength(dn);
+ ecc_step = of_get_nand_ecc_step_size(dn);
+
+ if ((ecc_step >= 0 && !(ecc_strength >= 0)) ||
+ (!(ecc_step >= 0) && ecc_strength >= 0)) {
+ pr_err("must set both strength and step size in DT\n");
+ return -EINVAL;
+ }
+
+ if (ecc_mode >= 0)
+ chip->ecc.mode = ecc_mode;
+
+ if (ecc_strength >= 0)
+ chip->ecc.strength = ecc_strength;
+
+ if (ecc_step > 0)
+ chip->ecc.size = ecc_step;
+
+ return 0;
+}
+
/**
* nand_scan_ident - [NAND Interface] Scan for the NAND device
* @mtd: MTD device structure
@@ -3815,6 +3849,13 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
int i, nand_maf_id, nand_dev_id;
struct nand_chip *chip = mtd->priv;
struct nand_flash_dev *type;
+ int ret;
+
+ if (chip->dn) {
+ ret = nand_dt_init(mtd, chip, chip->dn);
+ if (ret)
+ return ret;
+ }
/* Set the default functions */
nand_set_defaults(chip, chip->options & NAND_BUSWIDTH_16);
diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c
new file mode 100644
index 000000000000..6cf81654fb81
--- /dev/null
+++ b/drivers/mtd/nand/vf610_nfc.c
@@ -0,0 +1,889 @@
+/*
+ * Copyright 2009-2015 Freescale Semiconductor, Inc. and others
+ *
+ * Description: MPC5125, VF610, MCF54418 and Kinetis K70 Nand driver.
+ * Jason ported to M54418TWR and MVFA5 (VF610).
+ * Authors: Stefan Agner <stefan.agner@toradex.com>
+ * Bill Pringlemeir <bpringlemeir@nbsps.com>
+ * Shaohui Xie <b21989@freescale.com>
+ * Jason Jin <Jason.jin@freescale.com>
+ *
+ * Based on original driver mpc5121_nfc.c.
+ *
+ * This 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.
+ *
+ * Limitations:
+ * - Untested on MPC5125 and M54418.
+ * - DMA and pipelining not used.
+ * - 2K pages or less.
+ * - HW ECC: Only 2K page with 64+ OOB.
+ * - HW ECC: Only 24 and 32-bit error correction implemented.
+ */
+
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/of_mtd.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define DRV_NAME "vf610_nfc"
+
+/* Register Offsets */
+#define NFC_FLASH_CMD1 0x3F00
+#define NFC_FLASH_CMD2 0x3F04
+#define NFC_COL_ADDR 0x3F08
+#define NFC_ROW_ADDR 0x3F0c
+#define NFC_ROW_ADDR_INC 0x3F14
+#define NFC_FLASH_STATUS1 0x3F18
+#define NFC_FLASH_STATUS2 0x3F1c
+#define NFC_CACHE_SWAP 0x3F28
+#define NFC_SECTOR_SIZE 0x3F2c
+#define NFC_FLASH_CONFIG 0x3F30
+#define NFC_IRQ_STATUS 0x3F38
+
+/* Addresses for NFC MAIN RAM BUFFER areas */
+#define NFC_MAIN_AREA(n) ((n) * 0x1000)
+
+#define PAGE_2K 0x0800
+#define OOB_64 0x0040
+#define OOB_MAX 0x0100
+
+/*
+ * NFC_CMD2[CODE] values. See section:
+ * - 31.4.7 Flash Command Code Description, Vybrid manual
+ * - 23.8.6 Flash Command Sequencer, MPC5125 manual
+ *
+ * Briefly these are bitmasks of controller cycles.
+ */
+#define READ_PAGE_CMD_CODE 0x7EE0
+#define READ_ONFI_PARAM_CMD_CODE 0x4860
+#define PROGRAM_PAGE_CMD_CODE 0x7FC0
+#define ERASE_CMD_CODE 0x4EC0
+#define READ_ID_CMD_CODE 0x4804
+#define RESET_CMD_CODE 0x4040
+#define STATUS_READ_CMD_CODE 0x4068
+
+/* NFC ECC mode define */
+#define ECC_BYPASS 0
+#define ECC_45_BYTE 6
+#define ECC_60_BYTE 7
+
+/*** Register Mask and bit definitions */
+
+/* NFC_FLASH_CMD1 Field */
+#define CMD_BYTE2_MASK 0xFF000000
+#define CMD_BYTE2_SHIFT 24
+
+/* NFC_FLASH_CM2 Field */
+#define CMD_BYTE1_MASK 0xFF000000
+#define CMD_BYTE1_SHIFT 24
+#define CMD_CODE_MASK 0x00FFFF00
+#define CMD_CODE_SHIFT 8
+#define BUFNO_MASK 0x00000006
+#define BUFNO_SHIFT 1
+#define START_BIT BIT(0)
+
+/* NFC_COL_ADDR Field */
+#define COL_ADDR_MASK 0x0000FFFF
+#define COL_ADDR_SHIFT 0
+
+/* NFC_ROW_ADDR Field */
+#define ROW_ADDR_MASK 0x00FFFFFF
+#define ROW_ADDR_SHIFT 0
+#define ROW_ADDR_CHIP_SEL_RB_MASK 0xF0000000
+#define ROW_ADDR_CHIP_SEL_RB_SHIFT 28
+#define ROW_ADDR_CHIP_SEL_MASK 0x0F000000
+#define ROW_ADDR_CHIP_SEL_SHIFT 24
+
+/* NFC_FLASH_STATUS2 Field */
+#define STATUS_BYTE1_MASK 0x000000FF
+
+/* NFC_FLASH_CONFIG Field */
+#define CONFIG_ECC_SRAM_ADDR_MASK 0x7FC00000
+#define CONFIG_ECC_SRAM_ADDR_SHIFT 22
+#define CONFIG_ECC_SRAM_REQ_BIT BIT(21)
+#define CONFIG_DMA_REQ_BIT BIT(20)
+#define CONFIG_ECC_MODE_MASK 0x000E0000
+#define CONFIG_ECC_MODE_SHIFT 17
+#define CONFIG_FAST_FLASH_BIT BIT(16)
+#define CONFIG_16BIT BIT(7)
+#define CONFIG_BOOT_MODE_BIT BIT(6)
+#define CONFIG_ADDR_AUTO_INCR_BIT BIT(5)
+#define CONFIG_BUFNO_AUTO_INCR_BIT BIT(4)
+#define CONFIG_PAGE_CNT_MASK 0xF
+#define CONFIG_PAGE_CNT_SHIFT 0
+
+/* NFC_IRQ_STATUS Field */
+#define IDLE_IRQ_BIT BIT(29)
+#define IDLE_EN_BIT BIT(20)
+#define CMD_DONE_CLEAR_BIT BIT(18)
+#define IDLE_CLEAR_BIT BIT(17)
+
+/*
+ * ECC status - seems to consume 8 bytes (double word). The documented
+ * status byte is located in the lowest byte of the second word (which is
+ * the 4th or 7th byte depending on endianness).
+ * Calculate an offset to store the ECC status at the end of the buffer.
+ */
+#define ECC_SRAM_ADDR (PAGE_2K + OOB_MAX - 8)
+
+#define ECC_STATUS 0x4
+#define ECC_STATUS_MASK 0x80
+#define ECC_STATUS_ERR_COUNT 0x3F
+
+enum vf610_nfc_alt_buf {
+ ALT_BUF_DATA = 0,
+ ALT_BUF_ID = 1,
+ ALT_BUF_STAT = 2,
+ ALT_BUF_ONFI = 3,
+};
+
+enum vf610_nfc_variant {
+ NFC_VFC610 = 1,
+};
+
+struct vf610_nfc {
+ struct mtd_info mtd;
+ struct nand_chip chip;
+ struct device *dev;
+ void __iomem *regs;
+ struct completion cmd_done;
+ uint buf_offset;
+ int write_sz;
+ /* Status and ID are in alternate locations. */
+ enum vf610_nfc_alt_buf alt_buf;
+ enum vf610_nfc_variant variant;
+ struct clk *clk;
+ bool use_hw_ecc;
+ u32 ecc_mode;
+};
+
+#define mtd_to_nfc(_mtd) container_of(_mtd, struct vf610_nfc, mtd)
+
+static struct nand_ecclayout vf610_nfc_ecc45 = {
+ .eccbytes = 45,
+ .eccpos = {19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63},
+ .oobfree = {
+ {.offset = 2,
+ .length = 17} }
+};
+
+static struct nand_ecclayout vf610_nfc_ecc60 = {
+ .eccbytes = 60,
+ .eccpos = { 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 26, 27,
+ 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63 },
+ .oobfree = {
+ {.offset = 2,
+ .length = 2} }
+};
+
+static inline u32 vf610_nfc_read(struct vf610_nfc *nfc, uint reg)
+{
+ return readl(nfc->regs + reg);
+}
+
+static inline void vf610_nfc_write(struct vf610_nfc *nfc, uint reg, u32 val)
+{
+ writel(val, nfc->regs + reg);
+}
+
+static inline void vf610_nfc_set(struct vf610_nfc *nfc, uint reg, u32 bits)
+{
+ vf610_nfc_write(nfc, reg, vf610_nfc_read(nfc, reg) | bits);
+}
+
+static inline void vf610_nfc_clear(struct vf610_nfc *nfc, uint reg, u32 bits)
+{
+ vf610_nfc_write(nfc, reg, vf610_nfc_read(nfc, reg) & ~bits);
+}
+
+static inline void vf610_nfc_set_field(struct vf610_nfc *nfc, u32 reg,
+ u32 mask, u32 shift, u32 val)
+{
+ vf610_nfc_write(nfc, reg,
+ (vf610_nfc_read(nfc, reg) & (~mask)) | val << shift);
+}
+
+static inline void vf610_nfc_memcpy(void *dst, const void __iomem *src,
+ size_t n)
+{
+ /*
+ * Use this accessor for the internal SRAM buffers. On the ARM
+ * Freescale Vybrid SoC it's known that the driver can treat
+ * the SRAM buffer as if it's memory. Other platform might need
+ * to treat the buffers differently.
+ *
+ * For the time being, use memcpy
+ */
+ memcpy(dst, src, n);
+}
+
+/* Clear flags for upcoming command */
+static inline void vf610_nfc_clear_status(struct vf610_nfc *nfc)
+{
+ u32 tmp = vf610_nfc_read(nfc, NFC_IRQ_STATUS);
+
+ tmp |= CMD_DONE_CLEAR_BIT | IDLE_CLEAR_BIT;
+ vf610_nfc_write(nfc, NFC_IRQ_STATUS, tmp);
+}
+
+static void vf610_nfc_done(struct vf610_nfc *nfc)
+{
+ unsigned long timeout = msecs_to_jiffies(100);
+
+ /*
+ * Barrier is needed after this write. This write need
+ * to be done before reading the next register the first
+ * time.
+ * vf610_nfc_set implicates such a barrier by using writel
+ * to write to the register.
+ */
+ vf610_nfc_set(nfc, NFC_IRQ_STATUS, IDLE_EN_BIT);
+ vf610_nfc_set(nfc, NFC_FLASH_CMD2, START_BIT);
+
+ if (!wait_for_completion_timeout(&nfc->cmd_done, timeout))
+ dev_warn(nfc->dev, "Timeout while waiting for BUSY.\n");
+
+ vf610_nfc_clear_status(nfc);
+}
+
+static u8 vf610_nfc_get_id(struct vf610_nfc *nfc, int col)
+{
+ u32 flash_id;
+
+ if (col < 4) {
+ flash_id = vf610_nfc_read(nfc, NFC_FLASH_STATUS1);
+ flash_id >>= (3 - col) * 8;
+ } else {
+ flash_id = vf610_nfc_read(nfc, NFC_FLASH_STATUS2);
+ flash_id >>= 24;
+ }
+
+ return flash_id & 0xff;
+}
+
+static u8 vf610_nfc_get_status(struct vf610_nfc *nfc)
+{
+ return vf610_nfc_read(nfc, NFC_FLASH_STATUS2) & STATUS_BYTE1_MASK;
+}
+
+static void vf610_nfc_send_command(struct vf610_nfc *nfc, u32 cmd_byte1,
+ u32 cmd_code)
+{
+ u32 tmp;
+
+ vf610_nfc_clear_status(nfc);
+
+ tmp = vf610_nfc_read(nfc, NFC_FLASH_CMD2);
+ tmp &= ~(CMD_BYTE1_MASK | CMD_CODE_MASK | BUFNO_MASK);
+ tmp |= cmd_byte1 << CMD_BYTE1_SHIFT;
+ tmp |= cmd_code << CMD_CODE_SHIFT;
+ vf610_nfc_write(nfc, NFC_FLASH_CMD2, tmp);
+}
+
+static void vf610_nfc_send_commands(struct vf610_nfc *nfc, u32 cmd_byte1,
+ u32 cmd_byte2, u32 cmd_code)
+{
+ u32 tmp;
+
+ vf610_nfc_send_command(nfc, cmd_byte1, cmd_code);
+
+ tmp = vf610_nfc_read(nfc, NFC_FLASH_CMD1);
+ tmp &= ~CMD_BYTE2_MASK;
+ tmp |= cmd_byte2 << CMD_BYTE2_SHIFT;
+ vf610_nfc_write(nfc, NFC_FLASH_CMD1, tmp);
+}
+
+static irqreturn_t vf610_nfc_irq(int irq, void *data)
+{
+ struct mtd_info *mtd = data;
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+
+ vf610_nfc_clear(nfc, NFC_IRQ_STATUS, IDLE_EN_BIT);
+ complete(&nfc->cmd_done);
+
+ return IRQ_HANDLED;
+}
+
+static void vf610_nfc_addr_cycle(struct vf610_nfc *nfc, int column, int page)
+{
+ if (column != -1) {
+ if (nfc->chip.options & NAND_BUSWIDTH_16)
+ column = column / 2;
+ vf610_nfc_set_field(nfc, NFC_COL_ADDR, COL_ADDR_MASK,
+ COL_ADDR_SHIFT, column);
+ }
+ if (page != -1)
+ vf610_nfc_set_field(nfc, NFC_ROW_ADDR, ROW_ADDR_MASK,
+ ROW_ADDR_SHIFT, page);
+}
+
+static inline void vf610_nfc_ecc_mode(struct vf610_nfc *nfc, int ecc_mode)
+{
+ vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG,
+ CONFIG_ECC_MODE_MASK,
+ CONFIG_ECC_MODE_SHIFT, ecc_mode);
+}
+
+static inline void vf610_nfc_transfer_size(struct vf610_nfc *nfc, int size)
+{
+ vf610_nfc_write(nfc, NFC_SECTOR_SIZE, size);
+}
+
+static void vf610_nfc_command(struct mtd_info *mtd, unsigned command,
+ int column, int page)
+{
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+ int trfr_sz = nfc->chip.options & NAND_BUSWIDTH_16 ? 1 : 0;
+
+ nfc->buf_offset = max(column, 0);
+ nfc->alt_buf = ALT_BUF_DATA;
+
+ switch (command) {
+ case NAND_CMD_SEQIN:
+ /* Use valid column/page from preread... */
+ vf610_nfc_addr_cycle(nfc, column, page);
+ nfc->buf_offset = 0;
+
+ /*
+ * SEQIN => data => PAGEPROG sequence is done by the controller
+ * hence we do not need to issue the command here...
+ */
+ return;
+ case NAND_CMD_PAGEPROG:
+ trfr_sz += nfc->write_sz;
+
+ if (nfc->use_hw_ecc)
+ vf610_nfc_ecc_mode(nfc, nfc->ecc_mode);
+ else
+ vf610_nfc_ecc_mode(nfc, ECC_BYPASS);
+
+ vf610_nfc_transfer_size(nfc, trfr_sz);
+ vf610_nfc_send_commands(nfc, NAND_CMD_SEQIN,
+ command, PROGRAM_PAGE_CMD_CODE);
+ break;
+
+ case NAND_CMD_RESET:
+ vf610_nfc_transfer_size(nfc, 0);
+ vf610_nfc_send_command(nfc, command, RESET_CMD_CODE);
+ break;
+
+ case NAND_CMD_READOOB:
+ trfr_sz += mtd->oobsize;
+ column = mtd->writesize;
+ vf610_nfc_transfer_size(nfc, trfr_sz);
+ vf610_nfc_send_commands(nfc, NAND_CMD_READ0,
+ NAND_CMD_READSTART, READ_PAGE_CMD_CODE);
+ vf610_nfc_addr_cycle(nfc, column, page);
+ vf610_nfc_ecc_mode(nfc, ECC_BYPASS);
+ break;
+
+ case NAND_CMD_READ0:
+ trfr_sz += mtd->writesize + mtd->oobsize;
+ vf610_nfc_transfer_size(nfc, trfr_sz);
+ vf610_nfc_ecc_mode(nfc, nfc->ecc_mode);
+ vf610_nfc_send_commands(nfc, NAND_CMD_READ0,
+ NAND_CMD_READSTART, READ_PAGE_CMD_CODE);
+ vf610_nfc_addr_cycle(nfc, column, page);
+ break;
+
+ case NAND_CMD_PARAM:
+ nfc->alt_buf = ALT_BUF_ONFI;
+ trfr_sz = 3 * sizeof(struct nand_onfi_params);
+ vf610_nfc_transfer_size(nfc, trfr_sz);
+ vf610_nfc_send_command(nfc, command, READ_ONFI_PARAM_CMD_CODE);
+ vf610_nfc_set_field(nfc, NFC_ROW_ADDR, ROW_ADDR_MASK,
+ ROW_ADDR_SHIFT, column);
+ vf610_nfc_ecc_mode(nfc, ECC_BYPASS);
+ break;
+
+ case NAND_CMD_ERASE1:
+ vf610_nfc_transfer_size(nfc, 0);
+ vf610_nfc_send_commands(nfc, command,
+ NAND_CMD_ERASE2, ERASE_CMD_CODE);
+ vf610_nfc_addr_cycle(nfc, column, page);
+ break;
+
+ case NAND_CMD_READID:
+ nfc->alt_buf = ALT_BUF_ID;
+ nfc->buf_offset = 0;
+ vf610_nfc_transfer_size(nfc, 0);
+ vf610_nfc_send_command(nfc, command, READ_ID_CMD_CODE);
+ vf610_nfc_set_field(nfc, NFC_ROW_ADDR, ROW_ADDR_MASK,
+ ROW_ADDR_SHIFT, column);
+ break;
+
+ case NAND_CMD_STATUS:
+ nfc->alt_buf = ALT_BUF_STAT;
+ vf610_nfc_transfer_size(nfc, 0);
+ vf610_nfc_send_command(nfc, command, STATUS_READ_CMD_CODE);
+ break;
+ default:
+ return;
+ }
+
+ vf610_nfc_done(nfc);
+
+ nfc->use_hw_ecc = false;
+ nfc->write_sz = 0;
+}
+
+static void vf610_nfc_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+ uint c = nfc->buf_offset;
+
+ /* Alternate buffers are only supported through read_byte */
+ WARN_ON(nfc->alt_buf);
+
+ vf610_nfc_memcpy(buf, nfc->regs + NFC_MAIN_AREA(0) + c, len);
+
+ nfc->buf_offset += len;
+}
+
+static void vf610_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
+ int len)
+{
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+ uint c = nfc->buf_offset;
+ uint l;
+
+ l = min_t(uint, len, mtd->writesize + mtd->oobsize - c);
+ vf610_nfc_memcpy(nfc->regs + NFC_MAIN_AREA(0) + c, buf, l);
+
+ nfc->write_sz += l;
+ nfc->buf_offset += l;
+}
+
+static uint8_t vf610_nfc_read_byte(struct mtd_info *mtd)
+{
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+ u8 tmp;
+ uint c = nfc->buf_offset;
+
+ switch (nfc->alt_buf) {
+ case ALT_BUF_ID:
+ tmp = vf610_nfc_get_id(nfc, c);
+ break;
+ case ALT_BUF_STAT:
+ tmp = vf610_nfc_get_status(nfc);
+ break;
+#ifdef __LITTLE_ENDIAN
+ case ALT_BUF_ONFI:
+ /* Reverse byte since the controller uses big endianness */
+ c = nfc->buf_offset ^ 0x3;
+ /* fall-through */
+#endif
+ default:
+ tmp = *((u8 *)(nfc->regs + NFC_MAIN_AREA(0) + c));
+ break;
+ }
+ nfc->buf_offset++;
+ return tmp;
+}
+
+static u16 vf610_nfc_read_word(struct mtd_info *mtd)
+{
+ u16 tmp;
+
+ vf610_nfc_read_buf(mtd, (u_char *)&tmp, sizeof(tmp));
+ return tmp;
+}
+
+/* If not provided, upper layers apply a fixed delay. */
+static int vf610_nfc_dev_ready(struct mtd_info *mtd)
+{
+ /* NFC handles R/B internally; always ready. */
+ return 1;
+}
+
+/*
+ * This function supports Vybrid only (MPC5125 would have full RB and four CS)
+ */
+static void vf610_nfc_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+ u32 tmp = vf610_nfc_read(nfc, NFC_ROW_ADDR);
+
+ /* Vybrid only (MPC5125 would have full RB and four CS) */
+ if (nfc->variant != NFC_VFC610)
+ return;
+
+ tmp &= ~(ROW_ADDR_CHIP_SEL_RB_MASK | ROW_ADDR_CHIP_SEL_MASK);
+
+ if (chip >= 0) {
+ tmp |= 1 << ROW_ADDR_CHIP_SEL_RB_SHIFT;
+ tmp |= BIT(chip) << ROW_ADDR_CHIP_SEL_SHIFT;
+ }
+
+ vf610_nfc_write(nfc, NFC_ROW_ADDR, tmp);
+}
+
+/* Count the number of 0's in buff up to max_bits */
+static inline int count_written_bits(uint8_t *buff, int size, int max_bits)
+{
+ uint32_t *buff32 = (uint32_t *)buff;
+ int k, written_bits = 0;
+
+ for (k = 0; k < (size / 4); k++) {
+ written_bits += hweight32(~buff32[k]);
+ if (unlikely(written_bits > max_bits))
+ break;
+ }
+
+ return written_bits;
+}
+
+static inline int vf610_nfc_correct_data(struct mtd_info *mtd, uint8_t *dat,
+ uint8_t *oob, int page)
+{
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+ u32 ecc_status_off = NFC_MAIN_AREA(0) + ECC_SRAM_ADDR + ECC_STATUS;
+ u8 ecc_status;
+ u8 ecc_count;
+ int flips;
+ int flips_threshold = nfc->chip.ecc.strength / 2;
+
+ ecc_status = vf610_nfc_read(nfc, ecc_status_off) & 0xff;
+ ecc_count = ecc_status & ECC_STATUS_ERR_COUNT;
+
+ if (!(ecc_status & ECC_STATUS_MASK))
+ return ecc_count;
+
+ /* Read OOB without ECC unit enabled */
+ vf610_nfc_command(mtd, NAND_CMD_READOOB, 0, page);
+ vf610_nfc_read_buf(mtd, oob, mtd->oobsize);
+
+ /*
+ * On an erased page, bit count (including OOB) should be zero or
+ * at least less then half of the ECC strength.
+ */
+ flips = count_written_bits(dat, nfc->chip.ecc.size, flips_threshold);
+ flips += count_written_bits(oob, mtd->oobsize, flips_threshold);
+
+ if (unlikely(flips > flips_threshold))
+ return -EINVAL;
+
+ /* Erased page. */
+ memset(dat, 0xff, nfc->chip.ecc.size);
+ memset(oob, 0xff, mtd->oobsize);
+ return flips;
+}
+
+static int vf610_nfc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
+ uint8_t *buf, int oob_required, int page)
+{
+ int eccsize = chip->ecc.size;
+ int stat;
+
+ vf610_nfc_read_buf(mtd, buf, eccsize);
+ if (oob_required)
+ vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+ stat = vf610_nfc_correct_data(mtd, buf, chip->oob_poi, page);
+
+ if (stat < 0) {
+ mtd->ecc_stats.failed++;
+ return 0;
+ } else {
+ mtd->ecc_stats.corrected += stat;
+ return stat;
+ }
+}
+
+static int vf610_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
+ const uint8_t *buf, int oob_required)
+{
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+
+ vf610_nfc_write_buf(mtd, buf, mtd->writesize);
+ if (oob_required)
+ vf610_nfc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+ /* Always write whole page including OOB due to HW ECC */
+ nfc->use_hw_ecc = true;
+ nfc->write_sz = mtd->writesize + mtd->oobsize;
+
+ return 0;
+}
+
+static const struct of_device_id vf610_nfc_dt_ids[] = {
+ { .compatible = "fsl,vf610-nfc", .data = (void *)NFC_VFC610 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, vf610_nfc_dt_ids);
+
+static void vf610_nfc_preinit_controller(struct vf610_nfc *nfc)
+{
+ vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT);
+ vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_ADDR_AUTO_INCR_BIT);
+ vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_BUFNO_AUTO_INCR_BIT);
+ vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_BOOT_MODE_BIT);
+ vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_DMA_REQ_BIT);
+ vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_FAST_FLASH_BIT);
+
+ /* Disable virtual pages, only one elementary transfer unit */
+ vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG, CONFIG_PAGE_CNT_MASK,
+ CONFIG_PAGE_CNT_SHIFT, 1);
+}
+
+static void vf610_nfc_init_controller(struct vf610_nfc *nfc)
+{
+ if (nfc->chip.options & NAND_BUSWIDTH_16)
+ vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT);
+ else
+ vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT);
+
+ if (nfc->chip.ecc.mode == NAND_ECC_HW) {
+ /* Set ECC status offset in SRAM */
+ vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG,
+ CONFIG_ECC_SRAM_ADDR_MASK,
+ CONFIG_ECC_SRAM_ADDR_SHIFT,
+ ECC_SRAM_ADDR >> 3);
+
+ /* Enable ECC status in SRAM */
+ vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_ECC_SRAM_REQ_BIT);
+ }
+}
+
+static int vf610_nfc_probe(struct platform_device *pdev)
+{
+ struct vf610_nfc *nfc;
+ struct resource *res;
+ struct mtd_info *mtd;
+ struct nand_chip *chip;
+ struct device_node *child;
+ const struct of_device_id *of_id;
+ int err = 0;
+ int irq;
+
+ nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL);
+ if (!nfc)
+ return -ENOMEM;
+
+ nfc->dev = &pdev->dev;
+ mtd = &nfc->mtd;
+ chip = &nfc->chip;
+
+ mtd->priv = chip;
+ mtd->owner = THIS_MODULE;
+ mtd->dev.parent = nfc->dev;
+ mtd->name = DRV_NAME;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0)
+ return -EINVAL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ nfc->regs = devm_ioremap_resource(nfc->dev, res);
+ if (IS_ERR(nfc->regs))
+ return PTR_ERR(nfc->regs);
+
+ nfc->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(nfc->clk))
+ return PTR_ERR(nfc->clk);
+
+ err = clk_prepare_enable(nfc->clk);
+ if (err) {
+ dev_err(nfc->dev, "Unable to enable clock!\n");
+ return err;
+ }
+
+ of_id = of_match_device(vf610_nfc_dt_ids, &pdev->dev);
+ nfc->variant = (enum vf610_nfc_variant)of_id->data;
+
+ for_each_available_child_of_node(nfc->dev->of_node, child) {
+ if (of_device_is_compatible(child, "fsl,vf610-nfc-nandcs")) {
+
+ if (chip->dn) {
+ dev_err(nfc->dev,
+ "Only one NAND chip supported!\n");
+ err = -EINVAL;
+ goto error;
+ }
+
+ chip->dn = child;
+ }
+ }
+
+ if (!chip->dn) {
+ dev_err(nfc->dev, "NAND chip sub-node missing!\n");
+ err = -ENODEV;
+ goto err_clk;
+ }
+
+ chip->dev_ready = vf610_nfc_dev_ready;
+ chip->cmdfunc = vf610_nfc_command;
+ chip->read_byte = vf610_nfc_read_byte;
+ chip->read_word = vf610_nfc_read_word;
+ chip->read_buf = vf610_nfc_read_buf;
+ chip->write_buf = vf610_nfc_write_buf;
+ chip->select_chip = vf610_nfc_select_chip;
+
+ chip->options |= NAND_NO_SUBPAGE_WRITE;
+
+ init_completion(&nfc->cmd_done);
+
+ err = devm_request_irq(nfc->dev, irq, vf610_nfc_irq, 0, DRV_NAME, mtd);
+ if (err) {
+ dev_err(nfc->dev, "Error requesting IRQ!\n");
+ goto error;
+ }
+
+ vf610_nfc_preinit_controller(nfc);
+
+ /* first scan to find the device and get the page size */
+ if (nand_scan_ident(mtd, 1, NULL)) {
+ err = -ENXIO;
+ goto error;
+ }
+
+ vf610_nfc_init_controller(nfc);
+
+ /* Bad block options. */
+ if (chip->bbt_options & NAND_BBT_USE_FLASH)
+ chip->bbt_options |= NAND_BBT_NO_OOB;
+
+ /* Single buffer only, max 256 OOB minus ECC status */
+ if (mtd->writesize + mtd->oobsize > PAGE_2K + OOB_MAX - 8) {
+ dev_err(nfc->dev, "Unsupported flash page size\n");
+ err = -ENXIO;
+ goto error;
+ }
+
+ if (chip->ecc.mode == NAND_ECC_HW) {
+ if (mtd->writesize != PAGE_2K && mtd->oobsize < 64) {
+ dev_err(nfc->dev, "Unsupported flash with hwecc\n");
+ err = -ENXIO;
+ goto error;
+ }
+
+ if (chip->ecc.size != mtd->writesize) {
+ dev_err(nfc->dev, "Step size needs to be page size\n");
+ err = -ENXIO;
+ goto error;
+ }
+
+ /* Only 64 byte ECC layouts known */
+ if (mtd->oobsize > 64)
+ mtd->oobsize = 64;
+
+ if (chip->ecc.strength == 32) {
+ nfc->ecc_mode = ECC_60_BYTE;
+ chip->ecc.bytes = 60;
+ chip->ecc.layout = &vf610_nfc_ecc60;
+ } else if (chip->ecc.strength == 24) {
+ nfc->ecc_mode = ECC_45_BYTE;
+ chip->ecc.bytes = 45;
+ chip->ecc.layout = &vf610_nfc_ecc45;
+ } else {
+ dev_err(nfc->dev, "Unsupported ECC strength\n");
+ err = -ENXIO;
+ goto error;
+ }
+
+ /* propagate ecc.layout to mtd_info */
+ mtd->ecclayout = chip->ecc.layout;
+ chip->ecc.read_page = vf610_nfc_read_page;
+ chip->ecc.write_page = vf610_nfc_write_page;
+
+ chip->ecc.size = PAGE_2K;
+ }
+
+ /* second phase scan */
+ if (nand_scan_tail(mtd)) {
+ err = -ENXIO;
+ goto error;
+ }
+
+ platform_set_drvdata(pdev, mtd);
+
+ /* Register device in MTD */
+ return mtd_device_parse_register(mtd, NULL,
+ &(struct mtd_part_parser_data){
+ .of_node = chip->dn,
+ },
+ NULL, 0);
+
+error:
+ of_node_put(chip->dn);
+err_clk:
+ clk_disable_unprepare(nfc->clk);
+ return err;
+}
+
+static int vf610_nfc_remove(struct platform_device *pdev)
+{
+ struct mtd_info *mtd = platform_get_drvdata(pdev);
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+
+ nand_release(mtd);
+ clk_disable_unprepare(nfc->clk);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int vf610_nfc_suspend(struct device *dev)
+{
+ struct mtd_info *mtd = dev_get_drvdata(dev);
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+
+ clk_disable_unprepare(nfc->clk);
+ return 0;
+}
+
+static int vf610_nfc_resume(struct device *dev)
+{
+ struct mtd_info *mtd = dev_get_drvdata(dev);
+ struct vf610_nfc *nfc = mtd_to_nfc(mtd);
+
+ pinctrl_pm_select_default_state(dev);
+
+ clk_prepare_enable(nfc->clk);
+
+ vf610_nfc_preinit_controller(nfc);
+ vf610_nfc_init_controller(nfc);
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(vf610_nfc_pm_ops, vf610_nfc_suspend, vf610_nfc_resume);
+
+static struct platform_driver vf610_nfc_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = vf610_nfc_dt_ids,
+ .pm = &vf610_nfc_pm_ops,
+ },
+ .probe = vf610_nfc_probe,
+ .remove = vf610_nfc_remove,
+};
+
+module_platform_driver(vf610_nfc_driver);
+
+MODULE_AUTHOR("Stefan Agner <stefan.agner@toradex.com>");
+MODULE_DESCRIPTION("Freescale VF610/MPC5125 NFC MTD NAND driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pinctrl/freescale/pinctrl-imx.c b/drivers/pinctrl/freescale/pinctrl-imx.c
index e261f1cf85c6..fa46971379cf 100644
--- a/drivers/pinctrl/freescale/pinctrl-imx.c
+++ b/drivers/pinctrl/freescale/pinctrl-imx.c
@@ -23,6 +23,7 @@
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/slab.h>
+#include <linux/syscore_ops.h>
#include "../core.h"
#include "pinctrl-imx.h"
@@ -40,6 +41,8 @@ struct imx_pinctrl {
struct pinctrl_dev *pctl;
void __iomem *base;
const struct imx_pinctrl_soc_info *info;
+ u32 *mux_regs;
+ u32 *input_regs;
};
static const inline struct imx_pin_group *imx_pinctrl_find_group_by_name(
@@ -643,6 +646,53 @@ static int imx_pinctrl_probe_dt(struct platform_device *pdev,
return 0;
}
+static int __maybe_unused imx_pinctrl_suspend(struct device *dev)
+{
+ struct imx_pinctrl *ipctl = dev_get_drvdata(dev);
+ const struct imx_pinctrl_soc_info *info = ipctl->info;
+ int i;
+
+ for (i = 0; i < info->npins; i++) {
+ const struct imx_pin_reg *pin_reg = &info->pin_regs[i];
+ if (pin_reg->mux_reg == -1)
+ continue;
+
+ ipctl->mux_regs[i] = readl(ipctl->base + pin_reg->mux_reg);
+ }
+
+ for (i = 0; i < info->ninput_regs; i++)
+ ipctl->input_regs[i] = readl(ipctl->base +
+ info->input_regs_offset + i * sizeof(u32 *));
+
+ return 0;
+}
+
+static int __maybe_unused imx_pinctrl_resume(struct device *dev)
+{
+ struct imx_pinctrl *ipctl = dev_get_drvdata(dev);
+ const struct imx_pinctrl_soc_info *info = ipctl->info;
+ const struct imx_pin_reg *pin_reg;
+ int i;
+
+ for (i = 0; i < info->npins; i++) {
+ pin_reg = &info->pin_regs[i];
+ if (pin_reg->mux_reg == -1)
+ continue;
+
+ writel(ipctl->mux_regs[i], ipctl->base + pin_reg->mux_reg);
+ }
+
+ for (i = 0; i < info->ninput_regs; i++)
+ writel(ipctl->input_regs[i], ipctl->base +
+ info->input_regs_offset + i * sizeof(u32 *));
+
+ return 0;
+}
+
+const struct dev_pm_ops imx_pinctrl_dev_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(imx_pinctrl_suspend, imx_pinctrl_resume)
+};
+
int imx_pinctrl_probe(struct platform_device *pdev,
struct imx_pinctrl_soc_info *info)
{
@@ -671,6 +721,18 @@ int imx_pinctrl_probe(struct platform_device *pdev,
info->pin_regs[i].conf_reg = -1;
}
+#ifdef CONFIG_PM_SLEEP
+ ipctl->mux_regs = devm_kzalloc(&pdev->dev, sizeof(u32 *) *
+ info->npins, GFP_KERNEL);
+ if (!ipctl->mux_regs)
+ return -ENOMEM;
+
+ ipctl->input_regs = devm_kzalloc(&pdev->dev, sizeof(u32 *) *
+ info->ninput_regs, GFP_KERNEL);
+ if (!ipctl->input_regs)
+ return -ENOMEM;
+#endif
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ipctl->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(ipctl->base))
diff --git a/drivers/pinctrl/freescale/pinctrl-imx.h b/drivers/pinctrl/freescale/pinctrl-imx.h
index 49e55d39f7c8..6da680675bd9 100644
--- a/drivers/pinctrl/freescale/pinctrl-imx.h
+++ b/drivers/pinctrl/freescale/pinctrl-imx.h
@@ -80,6 +80,8 @@ struct imx_pinctrl_soc_info {
unsigned int ngroups;
struct imx_pmx_func *functions;
unsigned int nfunctions;
+ unsigned int input_regs_offset;
+ unsigned int ninput_regs;
unsigned int flags;
};
@@ -97,4 +99,5 @@ struct imx_pinctrl_soc_info {
int imx_pinctrl_probe(struct platform_device *pdev,
struct imx_pinctrl_soc_info *info);
int imx_pinctrl_remove(struct platform_device *pdev);
+extern const struct dev_pm_ops imx_pinctrl_dev_pm_ops;
#endif /* __DRIVERS_PINCTRL_IMX_H */
diff --git a/drivers/pinctrl/freescale/pinctrl-vf610.c b/drivers/pinctrl/freescale/pinctrl-vf610.c
index 37a037543d29..3d5148e267fa 100644
--- a/drivers/pinctrl/freescale/pinctrl-vf610.c
+++ b/drivers/pinctrl/freescale/pinctrl-vf610.c
@@ -19,6 +19,9 @@
#include "pinctrl-imx.h"
+#define VF610_INPUT_REG_CNT 49
+#define VF610_INPUT_REG_BASE 0x2ec
+
enum vf610_pads {
VF610_PAD_PTA6 = 0,
VF610_PAD_PTA8 = 1,
@@ -299,6 +302,8 @@ static const struct pinctrl_pin_desc vf610_pinctrl_pads[] = {
static struct imx_pinctrl_soc_info vf610_pinctrl_info = {
.pins = vf610_pinctrl_pads,
.npins = ARRAY_SIZE(vf610_pinctrl_pads),
+ .input_regs_offset = VF610_INPUT_REG_BASE,
+ .ninput_regs = VF610_INPUT_REG_CNT,
.flags = SHARE_MUX_CONF_REG,
};
@@ -316,6 +321,7 @@ static struct platform_driver vf610_pinctrl_driver = {
.driver = {
.name = "vf610-pinctrl",
.of_match_table = vf610_pinctrl_of_match,
+ .pm = &imx_pinctrl_dev_pm_ops,
},
.probe = vf610_pinctrl_probe,
.remove = imx_pinctrl_remove,
diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c
index f9dfc8b6407a..5e1dd9605070 100644
--- a/drivers/pwm/pwm-fsl-ftm.c
+++ b/drivers/pwm/pwm-fsl-ftm.c
@@ -80,7 +80,6 @@ struct fsl_pwm_chip {
struct mutex lock;
- unsigned int use_count;
unsigned int cnt_select;
unsigned int clk_ps;
@@ -300,9 +299,6 @@ static int fsl_counter_clock_enable(struct fsl_pwm_chip *fpc)
{
int ret;
- if (fpc->use_count++ != 0)
- return 0;
-
/* select counter clock source */
regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK,
FTM_SC_CLK(fpc->cnt_select));
@@ -334,25 +330,6 @@ static int fsl_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
return ret;
}
-static void fsl_counter_clock_disable(struct fsl_pwm_chip *fpc)
-{
- /*
- * already disabled, do nothing
- */
- if (fpc->use_count == 0)
- return;
-
- /* there are still users, so can't disable yet */
- if (--fpc->use_count > 0)
- return;
-
- /* no users left, disable PWM counter clock */
- regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, 0);
-
- clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]);
- clk_disable_unprepare(fpc->clk[fpc->cnt_select]);
-}
-
static void fsl_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
@@ -362,7 +339,8 @@ static void fsl_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm),
BIT(pwm->hwpwm));
- fsl_counter_clock_disable(fpc);
+ clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]);
+ clk_disable_unprepare(fpc->clk[fpc->cnt_select]);
regmap_read(fpc->regmap, FTM_OUTMASK, &val);
if ((val & 0xFF) == 0xFF)
@@ -492,17 +470,24 @@ static int fsl_pwm_remove(struct platform_device *pdev)
static int fsl_pwm_suspend(struct device *dev)
{
struct fsl_pwm_chip *fpc = dev_get_drvdata(dev);
- u32 val;
+ int i;
regcache_cache_only(fpc->regmap, true);
regcache_mark_dirty(fpc->regmap);
- /* read from cache */
- regmap_read(fpc->regmap, FTM_OUTMASK, &val);
- if ((val & 0xFF) != 0xFF) {
+ for (i = 0; i < fpc->chip.npwm; i++) {
+ struct pwm_device *pwm = &fpc->chip.pwms[i];
+
+ if (!test_bit(PWMF_REQUESTED, &pwm->flags))
+ continue;
+
+ clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]);
+
+ if (!test_bit(PWMF_ENABLED, &pwm->flags))
+ continue;
+
clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]);
clk_disable_unprepare(fpc->clk[fpc->cnt_select]);
- clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]);
}
return 0;
@@ -511,12 +496,19 @@ static int fsl_pwm_suspend(struct device *dev)
static int fsl_pwm_resume(struct device *dev)
{
struct fsl_pwm_chip *fpc = dev_get_drvdata(dev);
- u32 val;
+ int i;
+
+ for (i = 0; i < fpc->chip.npwm; i++) {
+ struct pwm_device *pwm = &fpc->chip.pwms[i];
+
+ if (!test_bit(PWMF_REQUESTED, &pwm->flags))
+ continue;
- /* read from cache */
- regmap_read(fpc->regmap, FTM_OUTMASK, &val);
- if ((val & 0xFF) != 0xFF) {
clk_prepare_enable(fpc->clk[FSL_PWM_CLK_SYS]);
+
+ if (!test_bit(PWMF_ENABLED, &pwm->flags))
+ continue;
+
clk_prepare_enable(fpc->clk[fpc->cnt_select]);
clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]);
}
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index d8bde82f0370..8b4dd2be3bdb 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -1,5 +1,6 @@
menu "SOC (System On Chip) specific Drivers"
+source "drivers/soc/fsl/Kconfig"
source "drivers/soc/mediatek/Kconfig"
source "drivers/soc/qcom/Kconfig"
source "drivers/soc/ti/Kconfig"
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index 70042b259744..142676ea2996 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -2,6 +2,7 @@
# Makefile for the Linux Kernel SOC specific device drivers.
#
+obj-$(CONFIG_SOC_VF610) += fsl/
obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/
obj-$(CONFIG_ARCH_QCOM) += qcom/
obj-$(CONFIG_ARCH_TEGRA) += tegra/
diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig
new file mode 100644
index 000000000000..b1461aff4065
--- /dev/null
+++ b/drivers/soc/fsl/Kconfig
@@ -0,0 +1,9 @@
+#
+# Freescale SoC drivers
+
+config SOC_VF610
+ bool "SoC bus device for the Freescale Vybrid platform"
+ select SOC_BUS
+ help
+ Include support for the SoC bus on the Freescale Vybrid platform
+ providing some sysfs information about the module variant. \ No newline at end of file
diff --git a/drivers/soc/fsl/Makefile b/drivers/soc/fsl/Makefile
new file mode 100644
index 000000000000..5fccbbad7f58
--- /dev/null
+++ b/drivers/soc/fsl/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SOC_VF610) += soc-vf610.o
diff --git a/drivers/soc/fsl/soc-vf610.c b/drivers/soc/fsl/soc-vf610.c
new file mode 100644
index 000000000000..6425cfb16e1b
--- /dev/null
+++ b/drivers/soc/fsl/soc-vf610.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2015 Toradex AG
+ *
+ * Author: Sanchayan Maity <sanchayan.maity@toradex.com>
+ *
+ * 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 <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/random.h>
+#include <linux/slab.h>
+#include <linux/sys_soc.h>
+
+#define DRIVER_NAME "vf610-soc-bus"
+
+#define MSCM_CPxCOUNT_OFFSET 0x0000002C
+#define MSCM_CPxCFG1_OFFSET 0x00000014
+
+static struct soc_device_attribute *soc_dev_attr;
+static struct soc_device *soc_dev;
+
+static int vf610_soc_probe(struct platform_device *pdev)
+{
+ struct regmap *ocotp_regmap, *mscm_regmap, *rom_regmap;
+ struct device *dev = &pdev->dev;
+ struct device_node *node = pdev->dev.of_node;
+ struct device_node *soc_node;
+ struct of_phandle_args pargs;
+ char soc_type[] = "xx0";
+ u32 cfg0_offset, cfg1_offset, rom_rev_offset;
+ u32 soc_id1, soc_id2, rom_rev;
+ u32 cpxcount, cpxcfg1;
+ u64 soc_id;
+ int ret;
+
+ mscm_regmap = syscon_node_to_regmap(node);
+ if (IS_ERR(mscm_regmap)) {
+ dev_err(dev, "regmap lookup for mscm failed\n");
+ return PTR_ERR(mscm_regmap);
+ }
+
+ soc_node = of_find_node_by_path("/soc");
+
+ ret = of_parse_phandle_with_fixed_args(soc_node,
+ "ocotp-cfg", 2, 0, &pargs);
+ if (ret) {
+ dev_err(dev, "lookup failed for ocotp-cfg node %d\n", ret);
+ return ret;
+ }
+
+ ocotp_regmap = syscon_node_to_regmap(pargs.np);
+ if (IS_ERR(ocotp_regmap)) {
+ of_node_put(pargs.np);
+ dev_err(dev, "regmap lookup for ocotp failed\n");
+ return PTR_ERR(ocotp_regmap);
+ }
+
+ cfg0_offset = pargs.args[0];
+ cfg1_offset = pargs.args[1];
+ of_node_put(pargs.np);
+
+ ret = of_parse_phandle_with_fixed_args(soc_node,
+ "rom-revision", 1, 0, &pargs);
+ if (ret) {
+ dev_err(dev, "lookup failed for rom-revision node %d\n", ret);
+ return ret;
+ }
+
+ rom_regmap = syscon_node_to_regmap(pargs.np);
+ if (IS_ERR(rom_regmap)) {
+ of_node_put(pargs.np);
+ dev_err(dev, "regmap lookup for ocrom failed\n");
+ return PTR_ERR(rom_regmap);
+ }
+
+ rom_rev_offset = pargs.args[0];
+ of_node_put(pargs.np);
+
+ ret = regmap_read(ocotp_regmap, cfg0_offset, &soc_id1);
+ if (ret)
+ return -ENODEV;
+
+ ret = regmap_read(ocotp_regmap, cfg1_offset, &soc_id2);
+ if (ret)
+ return -ENODEV;
+
+ soc_id = (u64) soc_id1 << 32 | soc_id2;
+ add_device_randomness(&soc_id, sizeof(soc_id));
+
+ ret = regmap_read(mscm_regmap, MSCM_CPxCOUNT_OFFSET, &cpxcount);
+ if (ret)
+ return -ENODEV;
+
+ ret = regmap_read(mscm_regmap, MSCM_CPxCFG1_OFFSET, &cpxcfg1);
+ if (ret)
+ return -ENODEV;
+
+ soc_type[0] = cpxcount ? '6' : '5'; /* Dual Core => VF6x0 */
+ soc_type[1] = cpxcfg1 ? '1' : '0'; /* L2 Cache => VFx10 */
+
+ ret = regmap_read(rom_regmap, rom_rev_offset, &rom_rev);
+ if (ret)
+ return -ENODEV;
+
+ soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL);
+ if (!soc_dev_attr)
+ return -ENOMEM;
+
+ soc_dev_attr->machine = kasprintf(GFP_KERNEL, "Freescale Vybrid");
+ soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%016llx", soc_id);
+ soc_dev_attr->family = kasprintf(GFP_KERNEL, "Freescale Vybrid VF%s",
+ soc_type);
+ soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%08x", rom_rev);
+
+ soc_dev = soc_device_register(soc_dev_attr);
+ if (IS_ERR(soc_dev)) {
+ kfree(soc_dev_attr->revision);
+ kfree(soc_dev_attr->family);
+ kfree(soc_dev_attr->soc_id);
+ kfree(soc_dev_attr->machine);
+ kfree(soc_dev_attr);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int vf610_soc_remove(struct platform_device *pdev)
+{
+ if (soc_dev_attr) {
+ kfree(soc_dev_attr->revision);
+ kfree(soc_dev_attr->family);
+ kfree(soc_dev_attr->soc_id);
+ kfree(soc_dev_attr->machine);
+ kfree(soc_dev_attr);
+ }
+
+ if (soc_dev)
+ soc_device_unregister(soc_dev);
+
+ return 0;
+}
+
+static const struct of_device_id vf610_soc_bus_match[] = {
+ { .compatible = "fsl,vf610-mscm-cpucfg", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, vf610_soc_bus_match);
+
+static struct platform_driver vf610_soc_driver = {
+ .probe = vf610_soc_probe,
+ .remove = vf610_soc_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = vf610_soc_bus_match,
+ },
+};
+
+module_platform_driver(vf610_soc_driver);
+
+MODULE_DESCRIPTION("Freescale VF610 SoC bus driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c
index 5fe54cda309f..b21c3c01df6d 100644
--- a/drivers/spi/spi-fsl-dspi.c
+++ b/drivers/spi/spi-fsl-dspi.c
@@ -24,6 +24,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
@@ -137,7 +138,7 @@ static inline int is_double_byte_mode(struct fsl_dspi *dspi)
{
unsigned int val;
- regmap_read(dspi->regmap, SPI_CTAR(dspi->cs), &val);
+ regmap_read(dspi->regmap, SPI_CTAR(0), &val);
return ((val & SPI_FRAME_BITS_MASK) == SPI_FRAME_BITS(8)) ? 0 : 1;
}
@@ -230,7 +231,7 @@ static int dspi_transfer_write(struct fsl_dspi *dspi)
*/
if (tx_word && (dspi->len == 1)) {
dspi->dataflags |= TRAN_STATE_WORD_ODD_NUM;
- regmap_update_bits(dspi->regmap, SPI_CTAR(dspi->cs),
+ regmap_update_bits(dspi->regmap, SPI_CTAR(0),
SPI_FRAME_BITS_MASK, SPI_FRAME_BITS(8));
tx_word = 0;
}
@@ -249,7 +250,7 @@ static int dspi_transfer_write(struct fsl_dspi *dspi)
dspi_pushr = SPI_PUSHR_TXDATA(d16) |
SPI_PUSHR_PCS(dspi->cs) |
- SPI_PUSHR_CTAS(dspi->cs) |
+ SPI_PUSHR_CTAS(0) |
SPI_PUSHR_CONT;
dspi->len -= 2;
@@ -264,7 +265,7 @@ static int dspi_transfer_write(struct fsl_dspi *dspi)
dspi_pushr = SPI_PUSHR_TXDATA(d8) |
SPI_PUSHR_PCS(dspi->cs) |
- SPI_PUSHR_CTAS(dspi->cs) |
+ SPI_PUSHR_CTAS(0) |
SPI_PUSHR_CONT;
dspi->len--;
@@ -364,10 +365,10 @@ static int dspi_transfer_one_message(struct spi_master *master,
regmap_update_bits(dspi->regmap, SPI_MCR,
SPI_MCR_CLR_TXF | SPI_MCR_CLR_RXF,
SPI_MCR_CLR_TXF | SPI_MCR_CLR_RXF);
- regmap_write(dspi->regmap, SPI_CTAR(dspi->cs),
+ regmap_write(dspi->regmap, SPI_CTAR(0),
dspi->cur_chip->ctar_val);
if (transfer->speed_hz)
- regmap_write(dspi->regmap, SPI_CTAR(dspi->cs),
+ regmap_write(dspi->regmap, SPI_CTAR(0),
dspi->cur_chip->ctar_val);
regmap_write(dspi->regmap, SPI_RSER, SPI_RSER_EOQFE);
@@ -468,7 +469,7 @@ static irqreturn_t dspi_interrupt(int irq, void *dev_id)
if (!dspi->len) {
if (dspi->dataflags & TRAN_STATE_WORD_ODD_NUM)
- regmap_update_bits(dspi->regmap, SPI_CTAR(dspi->cs),
+ regmap_update_bits(dspi->regmap, SPI_CTAR(0),
SPI_FRAME_BITS_MASK, SPI_FRAME_BITS(16));
dspi->waitflags = 1;
@@ -494,6 +495,8 @@ static int dspi_suspend(struct device *dev)
spi_master_suspend(master);
clk_disable_unprepare(dspi->clk);
+ pinctrl_pm_select_sleep_state(dev);
+
return 0;
}
@@ -502,6 +505,8 @@ static int dspi_resume(struct device *dev)
struct spi_master *master = dev_get_drvdata(dev);
struct fsl_dspi *dspi = spi_master_get_devdata(master);
+ pinctrl_pm_select_default_state(dev);
+
clk_prepare_enable(dspi->clk);
spi_master_resume(master);
diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c
index 08ce76f4f261..be6c011b642d 100644
--- a/drivers/tty/serial/fsl_lpuart.c
+++ b/drivers/tty/serial/fsl_lpuart.c
@@ -230,6 +230,9 @@
#define DEV_NAME "ttyLP"
#define UART_NR 6
+static bool nodma = true;
+module_param(nodma, bool, S_IRUGO);
+
struct lpuart_port {
struct uart_port port;
struct clk *clk;
@@ -342,38 +345,19 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport,
FSL_UART_RX_DMA_BUFFER_SIZE, DMA_TO_DEVICE);
}
-static void lpuart_pio_tx(struct lpuart_port *sport)
-{
- struct circ_buf *xmit = &sport->port.state->xmit;
- unsigned long flags;
-
- spin_lock_irqsave(&sport->port.lock, flags);
-
- while (!uart_circ_empty(xmit) &&
- readb(sport->port.membase + UARTTCFIFO) < sport->txfifo_size) {
- writeb(xmit->buf[xmit->tail], sport->port.membase + UARTDR);
- xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
- sport->port.icount.tx++;
- }
-
- if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
- uart_write_wakeup(&sport->port);
-
- if (uart_circ_empty(xmit))
- writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS,
- sport->port.membase + UARTCR5);
-
- spin_unlock_irqrestore(&sport->port.lock, flags);
-}
-
-static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long count)
+static int lpuart_dma_tx(struct lpuart_port *sport)
{
struct circ_buf *xmit = &sport->port.state->xmit;
+ unsigned long count = CIRC_CNT_TO_END(xmit->head,
+ xmit->tail, UART_XMIT_SIZE);
dma_addr_t tx_bus_addr;
+ if (!count)
+ return 0;
+
dma_sync_single_for_device(sport->port.dev, sport->dma_tx_buf_bus,
UART_XMIT_SIZE, DMA_TO_DEVICE);
- sport->dma_tx_bytes = count & ~(sport->txfifo_size - 1);
+ sport->dma_tx_bytes = count;
tx_bus_addr = sport->dma_tx_buf_bus + xmit->tail;
sport->dma_tx_desc = dmaengine_prep_slave_single(sport->dma_tx_chan,
tx_bus_addr, sport->dma_tx_bytes,
@@ -393,25 +377,6 @@ static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long count)
return 0;
}
-static void lpuart_prepare_tx(struct lpuart_port *sport)
-{
- struct circ_buf *xmit = &sport->port.state->xmit;
- unsigned long count = CIRC_CNT_TO_END(xmit->head,
- xmit->tail, UART_XMIT_SIZE);
-
- if (!count)
- return;
-
- if (count < sport->txfifo_size)
- writeb(readb(sport->port.membase + UARTCR5) & ~UARTCR5_TDMAS,
- sport->port.membase + UARTCR5);
- else {
- writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS,
- sport->port.membase + UARTCR5);
- lpuart_dma_tx(sport, count);
- }
-}
-
static void lpuart_dma_tx_complete(void *arg)
{
struct lpuart_port *sport = arg;
@@ -428,7 +393,7 @@ static void lpuart_dma_tx_complete(void *arg)
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(&sport->port);
- lpuart_prepare_tx(sport);
+ lpuart_dma_tx(sport);
spin_unlock_irqrestore(&sport->port.lock, flags);
}
@@ -483,9 +448,8 @@ static void lpuart_dma_rx_complete(void *arg)
spin_unlock_irqrestore(&sport->port.lock, flags);
}
-static void lpuart_timer_func(unsigned long data)
+static void lpuart_dma_rx_terminate(struct lpuart_port *sport)
{
- struct lpuart_port *sport = (struct lpuart_port *)data;
struct tty_port *port = &sport->port.state->port;
struct dma_tx_state state;
unsigned long flags;
@@ -510,6 +474,11 @@ static void lpuart_timer_func(unsigned long data)
spin_unlock_irqrestore(&sport->port.lock, flags);
}
+static void lpuart_timer_func(unsigned long data)
+{
+ lpuart_dma_rx_terminate((struct lpuart_port *)data);
+}
+
static inline void lpuart_prepare_rx(struct lpuart_port *sport)
{
unsigned long flags;
@@ -581,7 +550,7 @@ static void lpuart_start_tx(struct uart_port *port)
if (sport->lpuart_dma_tx_use) {
if (!uart_circ_empty(xmit) && !sport->dma_tx_in_progress)
- lpuart_prepare_tx(sport);
+ lpuart_dma_tx(sport);
} else {
if (readb(port->membase + UARTSR1) & UARTSR1_TDRE)
lpuart_transmit_buffer(sport);
@@ -778,10 +747,8 @@ static irqreturn_t lpuart_int(int irq, void *dev_id)
lpuart_rxint(irq, dev_id);
}
if (sts & UARTSR1_TDRE && !(crdma & UARTCR5_TDMAS)) {
- if (sport->lpuart_dma_tx_use)
- lpuart_pio_tx(sport);
- else
- lpuart_txint(irq, dev_id);
+ BUG_ON(sport->lpuart_dma_tx_use);
+ lpuart_txint(irq, dev_id);
}
return IRQ_HANDLED;
@@ -810,8 +777,18 @@ static irqreturn_t lpuart32_int(int irq, void *dev_id)
/* return TIOCSER_TEMT when transmitter is not busy */
static unsigned int lpuart_tx_empty(struct uart_port *port)
{
- return (readb(port->membase + UARTSR1) & UARTSR1_TC) ?
- TIOCSER_TEMT : 0;
+ struct lpuart_port *sport = container_of(port,
+ struct lpuart_port, port);
+ unsigned char sr1 = readb(port->membase + UARTSR1);
+ unsigned char sfifo = readb(port->membase + UARTSFIFO);
+
+ if (sport->dma_tx_in_progress)
+ return 0;
+
+ if (sr1 & UARTSR1_TC && sfifo & UARTSFIFO_TXEMPT)
+ return TIOCSER_TEMT;
+
+ return 0;
}
static unsigned int lpuart32_tx_empty(struct uart_port *port)
@@ -820,6 +797,52 @@ static unsigned int lpuart32_tx_empty(struct uart_port *port)
TIOCSER_TEMT : 0;
}
+static int lpuart_config_rs485(struct uart_port *port,
+ struct serial_rs485 *rs485)
+{
+ struct lpuart_port *sport = container_of(port,
+ struct lpuart_port, port);
+ u8 modem = readb(sport->port.membase + UARTMODEM) &
+ ~(UARTMODEM_TXRTSPOL | UARTMODEM_TXRTSE);
+ writeb(modem, sport->port.membase + UARTMODEM);
+
+ if (rs485->flags & SER_RS485_ENABLED) {
+ /* Enable auto RS-485 RTS mode */
+ modem |= UARTMODEM_TXRTSE;
+
+ /*
+ * RTS needs to be logic HIGH either during transer _or_ after
+ * transfer, other variants are not supported by the hardware.
+ */
+
+ if (!(rs485->flags & (SER_RS485_RTS_ON_SEND |
+ SER_RS485_RTS_AFTER_SEND)))
+ rs485->flags |= SER_RS485_RTS_ON_SEND;
+
+ if (rs485->flags & SER_RS485_RTS_ON_SEND &&
+ rs485->flags & SER_RS485_RTS_AFTER_SEND)
+
+ rs485->flags &= ~SER_RS485_RTS_AFTER_SEND;
+
+ /*
+ * The hardware defaults to RTS logic HIGH while transfer.
+ * Switch polarity in case RTS shall be logic HIGH
+ * after transfer.
+ * Note: UART is assumed to be active high.
+ */
+ if (rs485->flags & SER_RS485_RTS_ON_SEND)
+ modem &= ~UARTMODEM_TXRTSPOL;
+ else if (rs485->flags & SER_RS485_RTS_AFTER_SEND)
+ modem |= UARTMODEM_TXRTSPOL;
+ }
+
+ /* Store the new configuration */
+ sport->port.rs485 = *rs485;
+
+ writeb(modem, sport->port.membase + UARTMODEM);
+ return 0;
+}
+
static unsigned int lpuart_get_mctrl(struct uart_port *port)
{
unsigned int temp = 0;
@@ -852,18 +875,23 @@ static unsigned int lpuart32_get_mctrl(struct uart_port *port)
static void lpuart_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
+ struct lpuart_port *sport = container_of(port,
+ struct lpuart_port, port);
unsigned char temp;
- temp = readb(port->membase + UARTMODEM) &
+ /* Make sure RXRTSE bit is not set when RS485 is enabled */
+ if (!(sport->port.rs485.flags & SER_RS485_ENABLED)) {
+ temp = readb(sport->port.membase + UARTMODEM) &
~(UARTMODEM_RXRTSE | UARTMODEM_TXCTSE);
- if (mctrl & TIOCM_RTS)
- temp |= UARTMODEM_RXRTSE;
+ if (mctrl & TIOCM_RTS)
+ temp |= UARTMODEM_RXRTSE;
- if (mctrl & TIOCM_CTS)
- temp |= UARTMODEM_TXCTSE;
+ if (mctrl & TIOCM_CTS)
+ temp |= UARTMODEM_TXCTSE;
- writeb(temp, port->membase + UARTMODEM);
+ writeb(temp, sport->port.membase + UARTMODEM);
+ }
}
static void lpuart32_set_mctrl(struct uart_port *port, unsigned int mctrl)
@@ -921,14 +949,17 @@ static void lpuart_setup_watermark(struct lpuart_port *sport)
writeb(val | UARTPFIFO_TXFE | UARTPFIFO_RXFE,
sport->port.membase + UARTPFIFO);
- /* explicitly clear RDRF */
- readb(sport->port.membase + UARTSR1);
-
/* flush Tx and Rx FIFO */
writeb(UARTCFIFO_TXFLUSH | UARTCFIFO_RXFLUSH,
sport->port.membase + UARTCFIFO);
- writeb(0, sport->port.membase + UARTTWFIFO);
+ /* explicitly clear RDRF */
+ if (readb(sport->port.membase + UARTSR1) & UARTSR1_RDRF) {
+ readb(sport->port.membase + UARTDR);
+ writeb(UARTSFIFO_RXUF, sport->port.membase + UARTSFIFO);
+ }
+
+ writeb(sport->txfifo_size / 2, sport->port.membase + UARTTWFIFO);
writeb(1, sport->port.membase + UARTRWFIFO);
/* Restore cr2 */
@@ -981,7 +1012,7 @@ static int lpuart_dma_tx_request(struct uart_port *port)
dma_buf = sport->port.state->xmit.buf;
dma_tx_sconfig.dst_addr = sport->port.mapbase + UARTDR;
dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
- dma_tx_sconfig.dst_maxburst = sport->txfifo_size;
+ dma_tx_sconfig.dst_maxburst = 1;
dma_tx_sconfig.direction = DMA_MEM_TO_DEV;
ret = dmaengine_slave_config(sport->dma_tx_chan, &dma_tx_sconfig);
@@ -1240,6 +1271,12 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios,
cr1 |= UARTCR1_M;
}
+ /* When auto RS-485 RTS mode is enabled,
+ * hardware flow control need to be disabled.
+ */
+ if (sport->port.rs485.flags & SER_RS485_ENABLED)
+ termios->c_cflag &= ~CRTSCTS;
+
if (termios->c_cflag & CRTSCTS) {
modem |= (UARTMODEM_RXRTSE | UARTMODEM_TXCTSE);
} else {
@@ -1761,6 +1798,18 @@ static struct uart_driver lpuart_reg = {
.cons = LPUART_CONSOLE,
};
+static struct dma_chan *lpuart_request_dma_chan(struct lpuart_port *sport,
+ const char *name)
+{
+ struct dma_chan *chan;
+
+ chan = dma_request_slave_channel(sport->port.dev, name);
+ if (!chan)
+ dev_info(sport->port.dev, "DMA %s channel request failed, "
+ "operating without %s DMA\n", name, name);
+ return chan;
+}
+
static int lpuart_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
@@ -1798,6 +1847,8 @@ static int lpuart_probe(struct platform_device *pdev)
sport->port.ops = &lpuart_pops;
sport->port.flags = UPF_BOOT_AUTOCONF;
+ sport->port.rs485_config = lpuart_config_rs485;
+
sport->clk = devm_clk_get(&pdev->dev, "ipg");
if (IS_ERR(sport->clk)) {
ret = PTR_ERR(sport->clk);
@@ -1828,15 +1879,16 @@ static int lpuart_probe(struct platform_device *pdev)
return ret;
}
- sport->dma_tx_chan = dma_request_slave_channel(sport->port.dev, "tx");
- if (!sport->dma_tx_chan)
- dev_info(sport->port.dev, "DMA tx channel request failed, "
- "operating without tx DMA\n");
+ if (!nodma) {
+ sport->dma_tx_chan = lpuart_request_dma_chan(sport, "tx");
+ sport->dma_rx_chan = lpuart_request_dma_chan(sport, "rx");
+ }
- sport->dma_rx_chan = dma_request_slave_channel(sport->port.dev, "rx");
- if (!sport->dma_rx_chan)
- dev_info(sport->port.dev, "DMA rx channel request failed, "
- "operating without rx DMA\n");
+ if (of_property_read_bool(np, "linux,rs485-enabled-at-boot-time")) {
+ sport->port.rs485.flags |= SER_RS485_ENABLED;
+ sport->port.rs485.flags |= SER_RS485_RTS_ON_SEND;
+ writeb(UARTMODEM_TXRTSE, sport->port.membase + UARTMODEM);
+ }
return 0;
}
@@ -1876,7 +1928,12 @@ static int lpuart_suspend(struct device *dev)
writeb(temp, sport->port.membase + UARTCR2);
}
+ if (sport->dma_rx_in_progress)
+ lpuart_dma_rx_terminate(sport);
+
uart_suspend_port(&lpuart_reg, &sport->port);
+ if (sport->port.suspended && !sport->port.irq_wake)
+ clk_disable_unprepare(sport->clk);
return 0;
}
@@ -1886,6 +1943,9 @@ static int lpuart_resume(struct device *dev)
struct lpuart_port *sport = dev_get_drvdata(dev);
unsigned long temp;
+ if (sport->port.suspended && !sport->port.irq_wake)
+ clk_prepare_enable(sport->clk);
+
if (sport->lpuart32) {
lpuart32_setup_watermark(sport);
temp = lpuart32_read(sport->port.membase + UARTCTRL);
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 6d6200e37b71..ba14c6c3b1aa 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -18,6 +18,7 @@
#include <linux/usb.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg-fsm.h>
+#include <linux/extcon.h>
/******************************************************************************
* DEFINE
@@ -246,6 +247,11 @@ struct ci_hdrc {
bool in_lpm;
bool wakeup_int;
enum ci_revision rev;
+
+ struct extcon_specific_cable_nb extcon_vbus_dev;
+ struct extcon_specific_cable_nb extcon_id_dev;
+ struct notifier_block vbus_nb;
+ struct notifier_block id_nb;
};
static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c
index 846ceb91ec14..df1ad5edbb5f 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.c
+++ b/drivers/usb/chipidea/ci_hdrc_imx.c
@@ -281,9 +281,9 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
&pdata);
if (IS_ERR(data->ci_pdev)) {
ret = PTR_ERR(data->ci_pdev);
- dev_err(&pdev->dev,
- "Can't register ci_hdrc platform device, err=%d\n",
- ret);
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev,
+ "ci_hdrc_add_device failed, err=%d\n", ret);
goto err_clk;
}
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 3ad48e1c0c57..86ef48e1ee7b 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -560,6 +560,17 @@ static irqreturn_t ci_irq(int irq, void *data)
static int ci_get_platdata(struct device *dev,
struct ci_hdrc_platform_data *platdata)
{
+ if (of_property_read_bool(dev->of_node, "extcon")) {
+ platdata->edev = extcon_get_edev_by_phandle(dev, 0);
+ if (IS_ERR(platdata->edev)) {
+ if (PTR_ERR(platdata->edev) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_err(dev, "couldn't get extcon device: %ld\n",
+ PTR_ERR(platdata->edev));
+ }
+ platdata->flags |= CI_HDRC_DUAL_ROLE_NOT_OTG;
+ }
+
if (!platdata->phy_mode)
platdata->phy_mode = of_usb_get_phy_mode(dev->of_node);
@@ -676,6 +687,42 @@ static void ci_get_otg_capable(struct ci_hdrc *ci)
}
}
+static int ci_id_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct ci_hdrc *ci = container_of(nb, struct ci_hdrc, id_nb);
+
+ pm_runtime_get_sync(ci->dev);
+
+ ci_role_stop(ci);
+
+ hw_wait_phy_stable();
+
+ if (ci_role_start(ci, event?CI_ROLE_HOST:CI_ROLE_GADGET))
+ dev_err(ci->dev, "can't start %s role\n", ci_role(ci)->name);
+
+ pm_runtime_put_sync(ci->dev);
+
+ return NOTIFY_DONE;
+}
+
+static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct ci_hdrc *ci = container_of(nb, struct ci_hdrc, vbus_nb);
+
+ pm_runtime_get_sync(ci->dev);
+
+ if (event)
+ usb_gadget_vbus_connect(&ci->gadget);
+ else
+ usb_gadget_vbus_disconnect(&ci->gadget);
+
+ pm_runtime_put_sync(ci->dev);
+
+ return NOTIFY_DONE;
+}
+
static int ci_hdrc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -712,6 +759,21 @@ static int ci_hdrc_probe(struct platform_device *pdev)
return -ENODEV;
}
+ if (ci->platdata->edev) {
+ ci->vbus_nb.notifier_call = ci_vbus_notifier;
+ ret = extcon_register_interest(&ci->extcon_vbus_dev,
+ ci->platdata->edev->name, "USB",
+ &ci->vbus_nb);
+ if (ret < 0)
+ dev_err(dev, "failed to register notifier for USB aka VBUS\n");
+ ci->id_nb.notifier_call = ci_id_notifier;
+ ret = extcon_register_interest(&ci->extcon_id_dev,
+ ci->platdata->edev->name, "USB-HOST",
+ &ci->id_nb);
+ if (ret < 0)
+ dev_err(dev, "failed to register notifier for USB-HOST aka ID\n");
+ }
+
if (ci->platdata->phy) {
ci->phy = ci->platdata->phy;
} else if (ci->platdata->usb_phy) {
@@ -737,7 +799,7 @@ static int ci_hdrc_probe(struct platform_device *pdev)
ret = ci_usb_phy_init(ci);
if (ret) {
dev_err(dev, "unable to init phy: %d\n", ret);
- return ret;
+ goto extcon_cleanup;
}
ci->hw_bank.phys = res->start;
@@ -790,7 +852,11 @@ static int ci_hdrc_probe(struct platform_device *pdev)
* role switch, the defalt role is gadget, and the
* user can switch it through debugfs.
*/
- ci->role = CI_ROLE_GADGET;
+ if ((ci->platdata->edev) && (extcon_get_cable_state(
+ ci->platdata->edev, "USB-HOST") == true))
+ ci->role = CI_ROLE_HOST;
+ else
+ ci->role = CI_ROLE_GADGET;
}
} else {
ci->role = ci->roles[CI_ROLE_HOST]
@@ -809,6 +875,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
ci_role(ci)->name);
goto stop;
}
+ if ((ci->role == CI_ROLE_GADGET) && (ci->platdata->edev) &&
+ (extcon_get_cable_state(ci->platdata->edev, "USB") == true))
+ usb_gadget_vbus_connect(&ci->gadget);
}
platform_set_drvdata(pdev, ci);
@@ -836,6 +905,11 @@ static int ci_hdrc_probe(struct platform_device *pdev)
stop:
ci_role_destroy(ci);
+extcon_cleanup:
+ if (ci->extcon_vbus_dev.edev)
+ extcon_unregister_interest(&ci->extcon_vbus_dev);
+ if (ci->extcon_id_dev.edev)
+ extcon_unregister_interest(&ci->extcon_id_dev);
deinit_phy:
ci_usb_phy_exit(ci);
@@ -846,6 +920,11 @@ static int ci_hdrc_remove(struct platform_device *pdev)
{
struct ci_hdrc *ci = platform_get_drvdata(pdev);
+ if (ci->extcon_vbus_dev.edev)
+ extcon_unregister_interest(&ci->extcon_vbus_dev);
+ if (ci->extcon_id_dev.edev)
+ extcon_unregister_interest(&ci->extcon_id_dev);
+
if (ci->supports_runtime_pm) {
pm_runtime_get_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
diff --git a/drivers/video/backlight/gpio_backlight.c b/drivers/video/backlight/gpio_backlight.c
index 439feb2389a8..575eee741bd3 100644
--- a/drivers/video/backlight/gpio_backlight.c
+++ b/drivers/video/backlight/gpio_backlight.c
@@ -89,6 +89,7 @@ static int gpio_backlight_probe(struct platform_device *pdev)
struct backlight_device *bl;
struct gpio_backlight *gbl;
struct device_node *np = pdev->dev.of_node;
+ unsigned long flags = GPIOF_DIR_OUT;
int ret;
if (!pdata && !np) {
@@ -114,9 +115,12 @@ static int gpio_backlight_probe(struct platform_device *pdev)
gbl->def_value = pdata->def_value;
}
- ret = devm_gpio_request_one(gbl->dev, gbl->gpio, GPIOF_DIR_OUT |
- (gbl->active ? GPIOF_INIT_LOW
- : GPIOF_INIT_HIGH),
+ if (gbl->active)
+ flags |= gbl->def_value ? GPIOF_INIT_HIGH : GPIOF_INIT_LOW;
+ else
+ flags |= gbl->def_value ? GPIOF_INIT_LOW : GPIOF_INIT_HIGH;
+
+ ret = devm_gpio_request_one(gbl->dev, gbl->gpio, flags,
pdata ? pdata->name : "backlight");
if (ret < 0) {
dev_err(&pdev->dev, "unable to request GPIO\n");
diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
index d1e1e1704da1..35da2b0a4fcf 100644
--- a/drivers/video/fbdev/Kconfig
+++ b/drivers/video/fbdev/Kconfig
@@ -1957,6 +1957,17 @@ config FB_FSL_DIU
---help---
Framebuffer driver for the Freescale SoC DIU
+config FB_FSL_DCU
+ tristate "Freescale DCU framebuffer support"
+ depends on FB
+ select FB_CFB_FILLRECT
+ select FB_CFB_COPYAREA
+ select FB_CFB_IMAGEBLIT
+ select FB_MODE_HELPERS
+ select VIDEOMODE_HELPERS
+ ---help---
+ Framebuffer driver for the Freescale SoC DCU
+
config FB_W100
tristate "W100 frame buffer support"
depends on FB && ARCH_PXA
diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile
index 1979afffccfe..471695d7b051 100644
--- a/drivers/video/fbdev/Makefile
+++ b/drivers/video/fbdev/Makefile
@@ -110,6 +110,7 @@ obj-$(CONFIG_FB_IMX) += imxfb.o
obj-$(CONFIG_FB_S3C) += s3c-fb.o
obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o
obj-$(CONFIG_FB_FSL_DIU) += fsl-diu-fb.o
+obj-$(CONFIG_FB_FSL_DCU) += fsl-dcu-fb.o
obj-$(CONFIG_FB_COBALT) += cobalt_lcdfb.o
obj-$(CONFIG_FB_IBM_GXT4500) += gxt4500.o
obj-$(CONFIG_FB_PS3) += ps3fb.o
diff --git a/drivers/video/fbdev/fsl-dcu-fb.c b/drivers/video/fbdev/fsl-dcu-fb.c
new file mode 100644
index 000000000000..a1da36f131f0
--- /dev/null
+++ b/drivers/video/fbdev/fsl-dcu-fb.c
@@ -0,0 +1,1275 @@
+/*
+ * Copyright 2012-2013 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU framebuffer device driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/fb.h>
+#include <linux/clk.h>
+#include <linux/of_platform.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+#include <linux/pm_runtime.h>
+#include <linux/console.h>
+
+#define DRIVER_NAME "fsl-dcu-fb"
+
+#define DCU_DCU_MODE 0x0010
+#define DCU_MODE_BLEND_ITER(x) ((x) << 20)
+#define DCU_MODE_RASTER_EN (1 << 14)
+#define DCU_MODE_DCU_MODE(x) (x)
+#define DCU_MODE_DCU_MODE_MASK 0x03
+#define DCU_MODE_OFF 0
+#define DCU_MODE_NORMAL 1
+#define DCU_MODE_TEST 2
+#define DCU_MODE_COLORBAR 3
+
+#define DCU_BGND 0x0014
+#define DCU_BGND_R(x) ((x) << 16)
+#define DCU_BGND_G(x) ((x) << 8)
+#define DCU_BGND_B(x) (x)
+
+#define DCU_DISP_SIZE 0x0018
+#define DCU_DISP_SIZE_DELTA_Y(x) ((x) << 16)
+#define DCU_DISP_SIZE_DELTA_X(x) (x)
+
+#define DCU_HSYN_PARA 0x001c
+#define DCU_HSYN_PARA_BP(x) ((x) << 22)
+#define DCU_HSYN_PARA_PW(x) ((x) << 11)
+#define DCU_HSYN_PARA_FP(x) (x)
+
+#define DCU_VSYN_PARA 0x0020
+#define DCU_VSYN_PARA_BP(x) ((x) << 22)
+#define DCU_VSYN_PARA_PW(x) ((x) << 11)
+#define DCU_VSYN_PARA_FP(x) (x)
+
+#define DCU_SYN_POL 0x0024
+#define DCU_SYN_POL_INV_PXCK_FALL (1 << 6)
+#define DCU_SYN_POL_NEG_REMAIN (1 << 5)
+#define DCU_SYN_POL_INV_VS_LOW (1 << 1)
+#define DCU_SYN_POL_INV_HS_LOW (1)
+
+#define DCU_THRESHOLD 0x0028
+#define DCU_THRESHOLD_LS_BF_VS(x) ((x) << 16)
+#define DCU_THRESHOLD_OUT_BUF_HIGH(x) ((x) << 8)
+#define DCU_THRESHOLD_OUT_BUF_LOW(x) (x)
+
+#define DCU_INT_STATUS 0x002C
+#define DCU_INT_STATUS_UNDRUN (1 << 1)
+#define DCU_INT_STATUS_VSYNC (1 << 0)
+
+#define DCU_INT_MASK 0x0030
+#define DCU_INT_MASK_UNDRUN (1 << 1)
+#define DCU_INT_MASK_VSYNC (1 << 0)
+
+#define DCU_DIV_RATIO 0x0054
+
+#define DCU_UPDATE_MODE 0x00cc
+#define DCU_UPDATE_MODE_MODE (1 << 31)
+#define DCU_UPDATE_MODE_READREG (1 << 30)
+
+#define DCU_CTRLDESCLN_1(x) (0x200 + (x) * 0x40)
+#define DCU_CTRLDESCLN_1_HEIGHT(x) ((x) << 16)
+#define DCU_CTRLDESCLN_1_WIDTH(x) (x)
+
+#define DCU_CTRLDESCLN_2(x) (0x204 + (x) * 0x40)
+#define DCU_CTRLDESCLN_2_POSY(x) ((x) << 16)
+#define DCU_CTRLDESCLN_2_POSX(x) (x)
+
+#define DCU_CTRLDESCLN_3(x) (0x208 + (x) * 0x40)
+
+#define DCU_CTRLDESCLN_4(x) (0x20c + (x) * 0x40)
+#define DCU_CTRLDESCLN_4_EN (1 << 31)
+#define DCU_CTRLDESCLN_4_TILE_EN (1 << 30)
+#define DCU_CTRLDESCLN_4_DATA_SEL_CLUT (1 << 29)
+#define DCU_CTRLDESCLN_4_SAFETY_EN (1 << 28)
+#define DCU_CTRLDESCLN_4_TRANS(x) ((x) << 20)
+#define DCU_CTRLDESCLN_4_BPP(x) ((x) << 16)
+#define DCU_CTRLDESCLN_4_RLE_EN (1 << 15)
+#define DCU_CTRLDESCLN_4_LUOFFS(x) ((x) << 4)
+#define DCU_CTRLDESCLN_4_BB_ON (1 << 2)
+#define DCU_CTRLDESCLN_4_AB(x) (x)
+
+#define DCU_CTRLDESCLN_5(x) (0x210 + (x) * 0x40)
+#define DCU_CTRLDESCLN_5_CKMAX_R(x) ((x) << 16)
+#define DCU_CTRLDESCLN_5_CKMAX_G(x) ((x) << 8)
+#define DCU_CTRLDESCLN_5_CKMAX_B(x) (x)
+
+#define DCU_CTRLDESCLN_6(x) (0x214 + (x) * 0x40)
+#define DCU_CTRLDESCLN_6_CKMIN_R(x) ((x) << 16)
+#define DCU_CTRLDESCLN_6_CKMIN_G(x) ((x) << 8)
+#define DCU_CTRLDESCLN_6_CKMIN_B(x) (x)
+
+#define DCU_CTRLDESCLN_7(x) (0x218 + (x) * 0x40)
+#define DCU_CTRLDESCLN_7_TILE_VER(x) ((x) << 16)
+#define DCU_CTRLDESCLN_7_TILE_HOR(x) (x)
+
+#define DCU_CTRLDESCLN_8(x) (0x21c + (x) * 0x40)
+#define DCU_CTRLDESCLN_8_FG_FCOLOR(x) (x)
+
+#define DCU_CTRLDESCLN_9(x) (0x220 + (x) * 0x40)
+#define DCU_CTRLDESCLN_9_BG_BCOLOR(x) (x)
+
+#define DCU_TOTAL_LAYER_NUM 64
+#define DCU_LAYER_NUM_MAX 6
+
+#define BPP_16_RGB565 4
+#define BPP_24_RGB888 5
+#define BPP_32_ARGB8888 6
+
+#define TCON_CTRL1 0x0000
+#define TCON_BYPASS_ENABLE (1 << 29)
+
+#define MFB_SET_ALPHA _IOW('M', 0, __u8)
+#define MFB_GET_ALPHA _IOR('M', 0, __u8)
+#define MFB_SET_LAYER _IOW('M', 4, struct layer_display_offset)
+#define MFB_GET_LAYER _IOR('M', 4, struct layer_display_offset)
+
+struct dcu_fb_data {
+ struct fb_info *fsl_dcu_info[DCU_LAYER_NUM_MAX];
+ struct device *dev;
+ void __iomem *reg_base;
+ unsigned int irq;
+ struct clk *clk;
+ struct fb_videomode *mode_db;
+ int modecnt;
+ struct fb_videomode native_mode;
+ u32 bits_per_pixel;
+ bool pixclockpol;
+ struct completion vsync_wait;
+};
+
+struct layer_display_offset {
+ int x_layer_d;
+ int y_layer_d;
+};
+
+struct mfb_info {
+ int index;
+ char *id;
+ unsigned long pseudo_palette[16];
+ unsigned char alpha;
+ unsigned char blend;
+ unsigned int count;
+ int x_layer_d; /* layer display x offset to physical screen */
+ int y_layer_d; /* layer display y offset to physical screen */
+ struct dcu_fb_data *parent;
+};
+
+enum mfb_index {
+ LAYER0 = 0,
+ LAYER1,
+ LAYER2,
+ LAYER3,
+ LAYER4,
+ LAYER5,
+};
+
+static struct mfb_info mfb_template[] = {
+ {
+ .index = LAYER0,
+ .id = "Layer0",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 0,
+ .y_layer_d = 0,
+ },
+ {
+ .index = LAYER1,
+ .id = "Layer1",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 50,
+ .y_layer_d = 50,
+ },
+ {
+ .index = LAYER2,
+ .id = "Layer2",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 100,
+ .y_layer_d = 100,
+ },
+ {
+ .index = LAYER3,
+ .id = "Layer3",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 150,
+ .y_layer_d = 150,
+ },
+ {
+ .index = LAYER4,
+ .id = "Layer4",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 200,
+ .y_layer_d = 200,
+ },
+ {
+ .index = LAYER5,
+ .id = "Layer5",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 250,
+ .y_layer_d = 250,
+ },
+};
+
+static inline unsigned int fsl_dcu_get_offset(struct fb_info *info)
+{
+ struct fb_var_screeninfo *var = &info->var;
+ int pixel_offset;
+
+ pixel_offset = (var->yoffset * var->xres_virtual) + var->xoffset;
+ return info->fix.smem_start +
+ (pixel_offset * (var->bits_per_pixel >> 3));
+}
+
+static int enable_panel(struct fb_info *info)
+{
+ struct fb_var_screeninfo *var = &info->var;
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int bpp;
+ unsigned int addr;
+
+ writel(DCU_CTRLDESCLN_1_HEIGHT(var->yres) |
+ DCU_CTRLDESCLN_1_WIDTH(var->xres),
+ dcufb->reg_base + DCU_CTRLDESCLN_1(mfbi->index));
+ writel(DCU_CTRLDESCLN_2_POSY(mfbi->y_layer_d) |
+ DCU_CTRLDESCLN_2_POSX(mfbi->x_layer_d),
+ dcufb->reg_base + DCU_CTRLDESCLN_2(mfbi->index));
+
+ addr = fsl_dcu_get_offset(info);
+ writel(addr, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index));
+
+ switch (var->bits_per_pixel) {
+ case 16:
+ bpp = BPP_16_RGB565;
+ break;
+ case 24:
+ bpp = BPP_24_RGB888;
+ break;
+ case 32:
+ bpp = BPP_32_ARGB8888;
+ break;
+ default:
+ dev_err(dcufb->dev, "unsupported color depth: %u\n",
+ var->bits_per_pixel);
+ return -EINVAL;
+ }
+
+ writel(DCU_CTRLDESCLN_4_EN |
+ DCU_CTRLDESCLN_4_TRANS(mfbi->alpha) |
+ DCU_CTRLDESCLN_4_BPP(bpp) |
+ DCU_CTRLDESCLN_4_AB(mfbi->blend),
+ dcufb->reg_base + DCU_CTRLDESCLN_4(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_5_CKMAX_R(0xff) |
+ DCU_CTRLDESCLN_5_CKMAX_G(0xff) |
+ DCU_CTRLDESCLN_5_CKMAX_B(0xff),
+ dcufb->reg_base + DCU_CTRLDESCLN_5(mfbi->index));
+ writel(DCU_CTRLDESCLN_6_CKMIN_R(0) |
+ DCU_CTRLDESCLN_6_CKMIN_G(0) |
+ DCU_CTRLDESCLN_6_CKMIN_B(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_6(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_7_TILE_VER(0) | DCU_CTRLDESCLN_7_TILE_HOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_7(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_8_FG_FCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_8(mfbi->index));
+ writel(DCU_CTRLDESCLN_9_BG_BCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_9(mfbi->index));
+
+ writel(DCU_UPDATE_MODE_READREG, dcufb->reg_base + DCU_UPDATE_MODE);
+
+ /* Wait until transfer is complete and switch to automatic updates */
+ while (readl(dcufb->reg_base + DCU_UPDATE_MODE) & DCU_UPDATE_MODE_READREG);
+ writel(DCU_UPDATE_MODE_MODE, dcufb->reg_base + DCU_UPDATE_MODE);
+
+ return 0;
+}
+
+static int disable_panel(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+
+ writel(DCU_CTRLDESCLN_1_HEIGHT(0) |
+ DCU_CTRLDESCLN_1_WIDTH(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_1(mfbi->index));
+ writel(DCU_CTRLDESCLN_2_POSY(0) | DCU_CTRLDESCLN_2_POSX(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_2(mfbi->index));
+
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_4(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_5_CKMAX_R(0) |
+ DCU_CTRLDESCLN_5_CKMAX_G(0) |
+ DCU_CTRLDESCLN_5_CKMAX_B(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_5(mfbi->index));
+ writel(DCU_CTRLDESCLN_6_CKMIN_R(0) |
+ DCU_CTRLDESCLN_6_CKMIN_G(0) |
+ DCU_CTRLDESCLN_6_CKMIN_B(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_6(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_7_TILE_VER(0) | DCU_CTRLDESCLN_7_TILE_HOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_7(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_8_FG_FCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_8(mfbi->index));
+ writel(DCU_CTRLDESCLN_9_BG_BCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_9(mfbi->index));
+
+ /* Clear Mode flag and schedule one transfer using READREG */
+ writel(DCU_UPDATE_MODE_READREG, dcufb->reg_base + DCU_UPDATE_MODE);
+ return 0;
+}
+
+static void enable_controller(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int dcu_mode;
+
+ dcu_mode = readl(dcufb->reg_base + DCU_DCU_MODE);
+ dcu_mode &= ~DCU_MODE_DCU_MODE_MASK;
+ writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_NORMAL),
+ dcufb->reg_base + DCU_DCU_MODE);
+}
+
+static void disable_controller(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int dcu_mode;
+
+ dcu_mode = readl(dcufb->reg_base + DCU_DCU_MODE);
+ dcu_mode &= ~DCU_MODE_DCU_MODE_MASK;
+ writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_OFF),
+ dcufb->reg_base + DCU_DCU_MODE);
+}
+
+static int fsl_dcu_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+
+ if (var->grayscale || var->rotate || var->nonstd)
+ return -EINVAL;
+
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres;
+
+ if (var->xoffset + info->var.xres > info->var.xres_virtual)
+ var->xoffset = info->var.xres_virtual - info->var.xres;
+
+ if (var->yoffset + info->var.yres > info->var.yres_virtual)
+ var->yoffset = info->var.yres_virtual - info->var.yres;
+
+ switch (var->bits_per_pixel) {
+ case 16:
+ var->red.length = 5;
+ var->red.offset = 11;
+ var->red.msb_right = 0;
+
+ var->green.length = 6;
+ var->green.offset = 5;
+ var->green.msb_right = 0;
+
+ var->blue.length = 5;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ var->transp.msb_right = 0;
+ break;
+ case 24:
+ var->red.length = 8;
+ var->red.offset = 16;
+ var->red.msb_right = 0;
+
+ var->green.length = 8;
+ var->green.offset = 8;
+ var->green.msb_right = 0;
+
+ var->blue.length = 8;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ var->transp.msb_right = 0;
+ break;
+ case 32:
+ var->red.length = 8;
+ var->red.offset = 16;
+ var->red.msb_right = 0;
+
+ var->green.length = 8;
+ var->green.offset = 8;
+ var->green.msb_right = 0;
+
+ var->blue.length = 8;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 8;
+ var->transp.offset = 24;
+ var->transp.msb_right = 0;
+ break;
+ default:
+ dev_err(dcufb->dev, "unsupported color depth: %u\n",
+ var->bits_per_pixel);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_dcu_calc_div(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned long long div;
+ div = (unsigned long long)(clk_get_rate(dcufb->clk) / 1000);
+ div *= info->var.pixclock;
+ do_div(div, 1000000000);
+
+ return div;
+}
+
+static void update_controller(struct fb_info *info)
+{
+ struct fb_var_screeninfo *var = &info->var;
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int div, pol = 0;
+
+ div = fsl_dcu_calc_div(info);
+ writel((div - 1), dcufb->reg_base + DCU_DIV_RATIO);
+
+ writel(DCU_DISP_SIZE_DELTA_Y(var->yres) |
+ DCU_DISP_SIZE_DELTA_X(var->xres / 16),
+ dcufb->reg_base + DCU_DISP_SIZE);
+
+ /* Horizontal and vertical sync parameters */
+ writel(DCU_HSYN_PARA_BP(var->left_margin) |
+ DCU_HSYN_PARA_PW(var->hsync_len) |
+ DCU_HSYN_PARA_FP(var->right_margin),
+ dcufb->reg_base + DCU_HSYN_PARA);
+
+ writel(DCU_VSYN_PARA_BP(var->upper_margin) |
+ DCU_VSYN_PARA_PW(var->vsync_len) |
+ DCU_VSYN_PARA_FP(var->lower_margin),
+ dcufb->reg_base + DCU_VSYN_PARA);
+
+ /*
+ * pixclockpol = 0 => display samples data on falling edge => 0
+ * pixclockpol = 1 => display samples data on rising edge => 1 (default)
+ */
+ if (dcufb->pixclockpol)
+ pol |= DCU_SYN_POL_INV_PXCK_FALL;
+
+ /* hsync:0 => active low => HS_LOW */
+ if (!(var->sync & FB_SYNC_HOR_HIGH_ACT))
+ pol |= DCU_SYN_POL_INV_HS_LOW;
+
+ /* vsync:0 => active low => VS_LOW */
+ if (!(var->sync & FB_SYNC_VERT_HIGH_ACT))
+ pol |= DCU_SYN_POL_INV_VS_LOW;
+
+ writel(pol, dcufb->reg_base + DCU_SYN_POL);
+
+ writel(DCU_BGND_R(0) | DCU_BGND_G(0) | DCU_BGND_B(0),
+ dcufb->reg_base + DCU_BGND);
+
+ writel(DCU_MODE_BLEND_ITER(DCU_LAYER_NUM_MAX) | DCU_MODE_RASTER_EN,
+ dcufb->reg_base + DCU_DCU_MODE);
+
+ writel(DCU_THRESHOLD_LS_BF_VS(0x3) | DCU_THRESHOLD_OUT_BUF_HIGH(0x78) |
+ DCU_THRESHOLD_OUT_BUF_LOW(0), dcufb->reg_base + DCU_THRESHOLD);
+}
+
+static int map_video_memory(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ u32 smem_len = info->fix.line_length * info->var.yres_virtual;
+
+ info->fix.smem_len = smem_len;
+
+ info->screen_base = dma_alloc_writecombine(info->device,
+ info->fix.smem_len, (dma_addr_t *)&info->fix.smem_start,
+ GFP_KERNEL);
+ if (!info->screen_base) {
+ dev_err(dcufb->dev, "unable to allocate fb memory\n");
+ return -ENOMEM;
+ }
+
+ memset(info->screen_base, 0, info->fix.smem_len);
+
+ return 0;
+}
+
+static void unmap_video_memory(struct fb_info *info)
+{
+ if (!info->screen_base)
+ return;
+
+ dma_free_writecombine(info->device, info->fix.smem_len,
+ info->screen_base, info->fix.smem_start);
+
+ info->screen_base = NULL;
+ info->fix.smem_start = 0;
+ info->fix.smem_len = 0;
+}
+
+static int fsl_dcu_set_par(struct fb_info *info)
+{
+ unsigned long len;
+ struct fb_var_screeninfo *var = &info->var;
+ struct fb_fix_screeninfo *fix = &info->fix;
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+
+ fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
+ fix->type = FB_TYPE_PACKED_PIXELS;
+ fix->accel = FB_ACCEL_NONE;
+ fix->visual = FB_VISUAL_TRUECOLOR;
+ fix->xpanstep = 1;
+ fix->ypanstep = 1;
+
+ len = info->var.yres_virtual * info->fix.line_length;
+ if (len != info->fix.smem_len) {
+ if (info->fix.smem_start)
+ unmap_video_memory(info);
+
+ if (map_video_memory(info)) {
+ dev_err(dcufb->dev, "unable to allocate fb memory\n");
+ return -ENOMEM;
+ }
+ }
+
+ /* Only layer 0 could update LCD controller */
+ if (mfbi->index == LAYER0) {
+ update_controller(info);
+ enable_controller(info);
+ }
+
+ enable_panel(info);
+ return 0;
+}
+
+static inline __u32 CNVT_TOHW(__u32 val, __u32 width)
+{
+ return ((val<<width) + 0x7FFF - val) >> 16;
+}
+
+static int fsl_dcu_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp, struct fb_info *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 (info->var.grayscale)
+ red = green = blue = (19595 * red + 38470 * green +
+ 7471 * blue) >> 16;
+ switch (info->fix.visual) {
+ case FB_VISUAL_TRUECOLOR:
+ /*
+ * 16-bit True Colour. We encode the RGB value
+ * according to the RGB bitfield information.
+ */
+ if (regno < 16) {
+ u32 *pal = info->pseudo_palette;
+
+ red = CNVT_TOHW(red, info->var.red.length);
+ green = CNVT_TOHW(green, info->var.green.length);
+ blue = CNVT_TOHW(blue, info->var.blue.length);
+ transp = CNVT_TOHW(transp, info->var.transp.length);
+
+ val = (red << info->var.red.offset) |
+ (green << info->var.green.offset) |
+ (blue << info->var.blue.offset) |
+ (transp << info->var.transp.offset);
+
+ pal[regno] = val;
+ ret = 0;
+ }
+ break;
+ case FB_VISUAL_STATIC_PSEUDOCOLOR:
+ case FB_VISUAL_PSEUDOCOLOR:
+ break;
+ }
+
+ return ret;
+}
+
+static int fsl_dcu_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int addr;
+
+ if ((info->var.xoffset == var->xoffset) &&
+ (info->var.yoffset == var->yoffset))
+ return 0;
+
+ if ((var->xoffset + info->var.xres) > info->var.xres_virtual
+ || (var->yoffset + info->var.yres) > info->var.yres_virtual)
+ return -EINVAL;
+
+ info->var.xoffset = var->xoffset;
+ info->var.yoffset = var->yoffset;
+
+ if (var->vmode & FB_VMODE_YWRAP)
+ info->var.vmode |= FB_VMODE_YWRAP;
+ else
+ info->var.vmode &= ~FB_VMODE_YWRAP;
+
+ addr = fsl_dcu_get_offset(info);
+ writel(addr, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index));
+
+ return 0;
+}
+
+static int fsl_dcu_blank(int blank_mode, struct fb_info *info)
+{
+ switch (blank_mode) {
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_NORMAL:
+ disable_panel(info);
+ break;
+ case FB_BLANK_POWERDOWN:
+ disable_controller(info);
+ disable_panel(info);
+ break;
+ case FB_BLANK_UNBLANK:
+ enable_controller(info);
+ enable_panel(info);
+ break;
+ }
+
+ return 0;
+}
+
+static int fsl_dcu_wait_for_vsync(struct dcu_fb_data *dcufb)
+{
+ unsigned long mask = readl(dcufb->reg_base + DCU_INT_MASK);
+ int ret;
+
+ /*
+ * Clear current VSYNC status since that still contains the flag from
+ * last VSYNC...
+ */
+ writel(DCU_INT_STATUS_VSYNC, dcufb->reg_base + DCU_INT_STATUS);
+ writel(mask & ~DCU_INT_MASK_VSYNC, dcufb->reg_base + DCU_INT_MASK);
+
+ ret = wait_for_completion_timeout(&dcufb->vsync_wait, HZ / 2);
+
+ if (ret < 0)
+ return ret;
+ if (ret == 0) {
+ dev_warn(dcufb->dev, "wait_for_vsync timed out!\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int fsl_dcu_ioctl(struct fb_info *info, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ struct layer_display_offset layer_d;
+ void __user *buf = (void __user *)arg;
+ unsigned char alpha;
+
+ switch (cmd) {
+ case MFB_SET_LAYER:
+ if (copy_from_user(&layer_d, buf, sizeof(layer_d)))
+ return -EFAULT;
+ mfbi->x_layer_d = layer_d.x_layer_d;
+ mfbi->y_layer_d = layer_d.y_layer_d;
+ fsl_dcu_set_par(info);
+ break;
+ case MFB_GET_LAYER:
+ layer_d.x_layer_d = mfbi->x_layer_d;
+ layer_d.y_layer_d = mfbi->y_layer_d;
+ if (copy_to_user(buf, &layer_d, sizeof(layer_d)))
+ return -EFAULT;
+ break;
+ case MFB_GET_ALPHA:
+ alpha = mfbi->alpha;
+ if (copy_to_user(buf, &alpha, sizeof(alpha)))
+ return -EFAULT;
+ break;
+ case MFB_SET_ALPHA:
+ if (copy_from_user(&alpha, buf, sizeof(alpha)))
+ return -EFAULT;
+ mfbi->blend = 1;
+ mfbi->alpha = alpha;
+ fsl_dcu_set_par(info);
+ break;
+ case FBIO_WAITFORVSYNC:
+ return fsl_dcu_wait_for_vsync(dcufb);
+ default:
+ dev_err(dcufb->dev, "unknown ioctl command (0x%08X)\n", cmd);
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static void reset_layers(struct dcu_fb_data *dcufb)
+{
+ int i;
+
+ for (i = 0; i < DCU_TOTAL_LAYER_NUM; i++) {
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_1(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_2(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_3(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_4(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_5(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_6(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_7(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_8(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_9(i));
+ }
+}
+
+static int fsl_dcu_open(struct fb_info *info, int user)
+{
+ struct mfb_info *mfbi = info->par;
+ int ret;
+
+ mfbi->index = info->node;
+
+ if (mfbi->count == 0) {
+ ret = fsl_dcu_check_var(&info->var, info);
+ if (ret < 0)
+ return ret;
+
+ ret = fsl_dcu_set_par(info);
+ if (ret < 0)
+ return ret;
+ }
+ mfbi->count++;
+
+ return 0;
+}
+
+static int fsl_dcu_release(struct fb_info *info, int user)
+{
+ struct mfb_info *mfbi = info->par;
+ int ret = 0;
+
+ mfbi->count--;
+ if (mfbi->count == 0)
+ ret = disable_panel(info);
+
+ return ret;
+}
+
+static struct fb_ops fsl_dcu_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = fsl_dcu_check_var,
+ .fb_set_par = fsl_dcu_set_par,
+ .fb_setcolreg = fsl_dcu_setcolreg,
+ .fb_blank = fsl_dcu_blank,
+ .fb_pan_display = fsl_dcu_pan_display,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_ioctl = fsl_dcu_ioctl,
+ .fb_open = fsl_dcu_open,
+ .fb_release = fsl_dcu_release,
+};
+
+static int fsl_dcu_init_modelist(struct dcu_fb_data *dcufb)
+{
+ struct device_node *np = dcufb->dev->of_node;
+ struct device_node *display_np;
+ struct display_timings *timings;
+ int i;
+ int ret = 0;
+
+ display_np = of_parse_phandle(np, "display", 0);
+ if (!display_np) {
+ dev_err(dcufb->dev, "failed to find display phandle\n");
+ return -ENOENT;
+ }
+
+ ret = of_property_read_u32(display_np, "bits-per-pixel",
+ &dcufb->bits_per_pixel);
+ if (ret < 0) {
+ dev_err(dcufb->dev, "failed to get property bits-per-pixel\n");
+ goto put_display_node;
+ }
+
+ timings = of_get_display_timings(display_np);
+ if (!timings) {
+ dev_err(dcufb->dev, "failed to get display timings\n");
+ ret = -ENOENT;
+ goto put_display_node;
+ }
+
+ dcufb->mode_db = devm_kzalloc(dcufb->dev, sizeof(struct fb_videomode) *
+ timings->num_timings, GFP_KERNEL);
+ if (!dcufb->mode_db) {
+ ret = -ENOMEM;
+ goto put_display_node;
+ }
+
+ for (i = 0; i < timings->num_timings; i++) {
+ struct videomode vm;
+
+ ret = videomode_from_timings(timings, &vm, i);
+ if (ret < 0)
+ goto free_dcu_mode_db;
+
+ ret = fb_videomode_from_videomode(&vm, &dcufb->mode_db[i]);
+ if (ret < 0)
+ goto free_dcu_mode_db;
+
+ if (i == timings->native_mode) {
+ fb_videomode_from_videomode(&vm, &dcufb->native_mode);
+
+ /*
+ * Kernel pixelclk settings are controller centric
+ * whereas DCU is display centric:
+ * PIXDATA_NEGEDGE (drive data on falling edge)
+ * => pixclockpol (display samples data on rising edge)
+ */
+ dcufb->pixclockpol = timings->timings[i]->flags &
+ DISPLAY_FLAGS_PIXDATA_NEGEDGE;
+ }
+
+ dcufb->modecnt++;
+ }
+
+ of_node_put(display_np);
+ return 0;
+
+free_dcu_mode_db:
+ kfree(dcufb->mode_db);
+ dcufb->mode_db = NULL;
+put_display_node:
+ of_node_put(display_np);
+ return ret;
+}
+
+static int parse_opt(struct dcu_fb_data *dcufb, char *this_opt, u32 *sync)
+{
+ if (!strncmp(this_opt, "hsync:", 6)) {
+ if (simple_strtoul(this_opt+6, NULL, 0))
+ *sync |= FB_SYNC_HOR_HIGH_ACT;
+ } else if (!strncmp(this_opt, "vsync:", 6)) {
+ if (simple_strtoul(this_opt+6, NULL, 0))
+ *sync |= FB_SYNC_VERT_HIGH_ACT;
+ } else if (!strncmp(this_opt, "pixclockpol:", 12))
+ dcufb->pixclockpol = !!simple_strtoul(this_opt+12, NULL, 0);
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int fsl_dcu_parse_options(struct dcu_fb_data *dcufb,
+ struct fb_info *info, char *option)
+{
+ char *this_opt;
+ int ret = 0;
+ u32 sync = 0;
+
+ while ((this_opt = strsep(&option, ",")) != NULL) {
+ /* Parse driver specific arguments */
+ if (parse_opt(dcufb, this_opt, &sync) == 0)
+ continue;
+
+ /* No valid driver specific argument, has to be mode */
+ ret = fb_find_mode(&info->var, info, this_opt, dcufb->mode_db,
+ dcufb->modecnt, &dcufb->native_mode,
+ dcufb->bits_per_pixel);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Overwrite from command line */
+ info->var.sync = sync;
+ return 0;
+}
+
+static int install_framebuffer(struct fb_info *info, char *option)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ struct fb_videomode *mode = &dcufb->native_mode;
+ int ret;
+
+ info->var.activate = FB_ACTIVATE_NOW;
+ info->fbops = &fsl_dcu_ops;
+ info->flags = FBINFO_FLAG_DEFAULT;
+ info->pseudo_palette = &mfbi->pseudo_palette;
+
+ fb_alloc_cmap(&info->cmap, 16, 0);
+
+ INIT_LIST_HEAD(&info->modelist);
+ fb_add_videomode(mode, &info->modelist);
+ fb_videomode_to_var(&info->var, mode);
+ info->var.bits_per_pixel = dcufb->bits_per_pixel;
+
+ /* Parse DCU option for every layer... */
+ ret = fsl_dcu_parse_options(dcufb, info, option);
+ if (ret < 0)
+ return ret;
+
+ fsl_dcu_check_var(&info->var, info);
+ ret = register_framebuffer(info);
+ if (ret < 0) {
+ dev_err(dcufb->dev, "failed to register framebuffer device\n");
+ return ret;
+ }
+
+ printk(KERN_INFO "fb%d: fb device registered successfully.\n",
+ info->node);
+ return 0;
+}
+
+static void uninstall_framebuffer(struct fb_info *info)
+{
+ unregister_framebuffer(info);
+ unmap_video_memory(info);
+
+ if (&info->cmap)
+ fb_dealloc_cmap(&info->cmap);
+}
+
+static irqreturn_t fsl_dcu_irq(int irq, void *dev_id)
+{
+ struct dcu_fb_data *dcufb = dev_id;
+ unsigned int mask = readl(dcufb->reg_base + DCU_INT_MASK);
+ unsigned int status = readl(dcufb->reg_base + DCU_INT_STATUS);
+
+ if (status & DCU_INT_STATUS_VSYNC) {
+ mask |= DCU_INT_MASK_VSYNC;
+ writel(mask, dcufb->reg_base + DCU_INT_MASK);
+ complete(&dcufb->vsync_wait);
+ }
+
+ writel(status, dcufb->reg_base + DCU_INT_STATUS);
+
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM_RUNTIME
+static int fsl_dcu_runtime_suspend(struct device *dev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(dcufb->clk);
+
+ return 0;
+}
+
+static int fsl_dcu_runtime_resume(struct device *dev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(dev);
+
+ clk_prepare_enable(dcufb->clk);
+
+ return 0;
+}
+#endif
+
+static int bypass_tcon(struct device_node *np)
+{
+ struct device_node *tcon_np;
+ struct platform_device *tcon_pdev;
+ struct clk *tcon_clk;
+ struct resource *res;
+ void __iomem *tcon_reg;
+
+ tcon_np = of_parse_phandle(np, "tcon-controller", 0);
+ if (!tcon_np)
+ return -EINVAL;
+
+ tcon_pdev = of_find_device_by_node(tcon_np);
+ if (!tcon_pdev)
+ return -EINVAL;
+
+ tcon_clk = devm_clk_get(&tcon_pdev->dev, "tcon");
+ if (IS_ERR(tcon_clk))
+ return PTR_ERR(tcon_clk);
+ clk_prepare_enable(tcon_clk);
+
+ res = platform_get_resource(tcon_pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ tcon_reg = ioremap(res->start, resource_size(res));
+ if (IS_ERR(tcon_reg))
+ return PTR_ERR(tcon_reg);
+
+ writel(TCON_BYPASS_ENABLE, tcon_reg + TCON_CTRL1);
+
+ iounmap(tcon_reg);
+ clk_disable_unprepare(tcon_clk);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_dcu_suspend(struct device *dev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(dev);
+ struct fb_info *fbi = dcufb->fsl_dcu_info[0];
+
+ console_lock();
+ fb_set_suspend(fbi, 1);
+ console_unlock();
+
+ disable_controller(dcufb->fsl_dcu_info[0]);
+ disable_panel(fbi);
+ clk_disable_unprepare(dcufb->clk);
+
+ return 0;
+}
+
+static int fsl_dcu_resume(struct device *dev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(dev);
+ struct fb_info *fbi = dcufb->fsl_dcu_info[0];
+ int ret;
+
+ clk_prepare_enable(dcufb->clk);
+
+ ret = bypass_tcon(dev->of_node);
+ if (ret) {
+ dev_err(dev, "could not bypass TCON\n");
+ goto failed_bypasstcon;
+ }
+
+ reset_layers(dcufb);
+
+ fsl_dcu_set_par(fbi);
+
+ console_lock();
+ fb_set_suspend(fbi, 0);
+ console_unlock();
+
+failed_bypasstcon:
+ return ret;
+}
+#endif
+
+static int fsl_dcu_probe(struct platform_device *pdev)
+{
+ struct dcu_fb_data *dcufb;
+ struct mfb_info *mfbi;
+ struct resource *res;
+ int ret = 0;
+ int i;
+ char *option = NULL;
+
+ fb_get_options("dcufb", &option);
+
+ if (option != NULL) {
+ dev_info(&pdev->dev, "using cmd options: %s\n", option);
+ if (!strcmp(option, "off"))
+ return -ENODEV;
+ }
+
+ dcufb = devm_kzalloc(&pdev->dev,
+ sizeof(struct dcu_fb_data), GFP_KERNEL);
+ if (!dcufb)
+ return -ENOMEM;
+
+ /* initialize the vsync wait queue */
+ init_completion(&dcufb->vsync_wait);
+
+ dcufb->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, dcufb);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "could not get memory IO resource\n");
+ return -ENODEV;
+ }
+
+ dcufb->reg_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dcufb->reg_base)) {
+ dev_err(&pdev->dev, "could not ioremap resource\n");
+ return PTR_ERR(dcufb->reg_base);
+ }
+
+ dcufb->irq = platform_get_irq(pdev, 0);
+ if (!dcufb->irq) {
+ dev_err(&pdev->dev, "could not get irq\n");
+ return -EINVAL;
+ }
+
+ ret = devm_request_irq(&pdev->dev, dcufb->irq, fsl_dcu_irq,
+ 0, DRIVER_NAME, dcufb);
+ if (ret) {
+ dev_err(&pdev->dev, "could not request irq\n");
+ goto failed_ioremap;
+ }
+
+ /* Put TCON in bypass mode, so the input signals from DCU are passed
+ * through TCON unchanged */
+ ret = bypass_tcon(pdev->dev.of_node);
+ if (ret) {
+ dev_err(&pdev->dev, "could not bypass TCON\n");
+ goto failed_bypasstcon;
+ }
+
+ dcufb->clk = devm_clk_get(&pdev->dev, "dcu");
+ if (IS_ERR(dcufb->clk)) {
+ ret = PTR_ERR(dcufb->clk);
+ dev_err(&pdev->dev, "could not get clock\n");
+ goto failed_getclock;
+ }
+ clk_prepare_enable(dcufb->clk);
+
+ pm_runtime_enable(dcufb->dev);
+ pm_runtime_get_sync(dcufb->dev);
+
+ ret = fsl_dcu_init_modelist(dcufb);
+ if (ret)
+ goto failed_alloc_framebuffer;
+
+ reset_layers(dcufb);
+
+ for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) {
+ dcufb->fsl_dcu_info[i] =
+ framebuffer_alloc(sizeof(struct mfb_info), &pdev->dev);
+ if (!dcufb->fsl_dcu_info[i]) {
+ ret = -ENOMEM;
+ goto failed_alloc_framebuffer;
+ }
+
+ dcufb->fsl_dcu_info[i]->fix.smem_start = 0;
+
+ mfbi = dcufb->fsl_dcu_info[i]->par;
+ memcpy(mfbi, &mfb_template[i], sizeof(struct mfb_info));
+ mfbi->parent = dcufb;
+
+ ret = install_framebuffer(dcufb->fsl_dcu_info[i], option);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "could not register framebuffer %d\n", i);
+ goto failed_register_framebuffer;
+ }
+ }
+
+ if (option != NULL) {
+ ret = fsl_dcu_parse_options(dcufb, dcufb->fsl_dcu_info[0],
+ option);
+ if (ret < 0)
+ goto failed_alloc_framebuffer;
+ }
+
+ return 0;
+
+failed_register_framebuffer:
+ for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) {
+ if (dcufb->fsl_dcu_info[i])
+ framebuffer_release(dcufb->fsl_dcu_info[i]);
+ }
+failed_alloc_framebuffer:
+ pm_runtime_put_sync(dcufb->dev);
+ pm_runtime_disable(dcufb->dev);
+failed_getclock:
+failed_bypasstcon:
+ free_irq(dcufb->irq, dcufb);
+failed_ioremap:
+ return ret;
+}
+
+static int fsl_dcu_remove(struct platform_device *pdev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(&pdev->dev);
+ int i;
+
+ pm_runtime_get_sync(dcufb->dev);
+
+ disable_controller(dcufb->fsl_dcu_info[0]);
+
+ clk_disable_unprepare(dcufb->clk);
+ free_irq(dcufb->irq, dcufb);
+
+ for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) {
+ uninstall_framebuffer(dcufb->fsl_dcu_info[i]);
+ framebuffer_release(dcufb->fsl_dcu_info[i]);
+ }
+
+ pm_runtime_put_sync(dcufb->dev);
+ pm_runtime_disable(dcufb->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops fsl_dcu_pm_ops = {
+#ifdef CONFIG_PM_RUNTIME
+ SET_RUNTIME_PM_OPS(fsl_dcu_runtime_suspend,
+ fsl_dcu_runtime_resume, NULL)
+#endif
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_dcu_suspend, fsl_dcu_resume)
+};
+
+static struct of_device_id fsl_dcu_dt_ids[] = {
+ {
+ .compatible = "fsl,vf610-dcu",
+ },
+ {}
+};
+
+static struct platform_driver fsl_dcu_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_dcu_dt_ids,
+ .pm = &fsl_dcu_pm_ops,
+ },
+ .probe = fsl_dcu_probe,
+ .remove = fsl_dcu_remove,
+};
+
+module_platform_driver(fsl_dcu_driver);
+
+MODULE_AUTHOR("Alison Wang");
+MODULE_DESCRIPTION("Freescale DCU framebuffer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/video/logo/Kconfig b/drivers/video/logo/Kconfig
index 0037104d66ac..d2ca63c03524 100644
--- a/drivers/video/logo/Kconfig
+++ b/drivers/video/logo/Kconfig
@@ -82,4 +82,8 @@ config LOGO_M32R_CLUT224
depends on M32R
default y
+config LOGO_CUSTOM_CLUT224
+ bool "Custom 224-color Linux logo"
+ default n
+
endif # LOGO
diff --git a/drivers/video/logo/Makefile b/drivers/video/logo/Makefile
index 3b437813584c..45d4b5346d07 100644
--- a/drivers/video/logo/Makefile
+++ b/drivers/video/logo/Makefile
@@ -18,6 +18,8 @@ obj-$(CONFIG_LOGO_M32R_CLUT224) += logo_m32r_clut224.o
obj-$(CONFIG_SPU_BASE) += logo_spe_clut224.o
+obj-$(CONFIG_LOGO_CUSTOM_CLUT224) += logo_custom_clut224.o
+
# How to generate logo's
# Use logo-cfiles to retrieve list of .c files to be built
diff --git a/drivers/video/logo/logo.c b/drivers/video/logo/logo.c
index 10fbfd8ab963..bef02e84a076 100644
--- a/drivers/video/logo/logo.c
+++ b/drivers/video/logo/logo.c
@@ -111,6 +111,10 @@ const struct linux_logo * __init_refok fb_find_logo(int depth)
/* M32R Linux logo */
logo = &logo_m32r_clut224;
#endif
+#ifdef CONFIG_LOGO_CUSTOM_CLUT224
+ /* Custom Linux logo */
+ logo = &logo_custom_clut224;
+#endif
}
return logo;
}