diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/ata/Kconfig | 11 | ||||
-rw-r--r-- | drivers/ata/Makefile | 2 | ||||
-rw-r--r-- | drivers/ata/pata_pxa.c | 411 | ||||
-rw-r--r-- | drivers/dma/Kconfig | 2 | ||||
-rw-r--r-- | drivers/dma/shdma.c | 8 | ||||
-rw-r--r-- | drivers/i2c/busses/Kconfig | 13 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-sh_mobile.c | 121 | ||||
-rw-r--r-- | drivers/net/irda/sh_irda.c | 6 | ||||
-rw-r--r-- | drivers/pcmcia/Kconfig | 2 | ||||
-rw-r--r-- | drivers/pcmcia/Makefile | 1 | ||||
-rw-r--r-- | drivers/pcmcia/pxa2xx_balloon3.c | 158 | ||||
-rw-r--r-- | drivers/power/wm97xx_battery.c | 16 | ||||
-rw-r--r-- | drivers/rtc/Kconfig | 9 | ||||
-rw-r--r-- | drivers/rtc/rtc-s3c.c | 44 | ||||
-rw-r--r-- | drivers/serial/Kconfig | 4 | ||||
-rw-r--r-- | drivers/serial/s5pv210.c | 8 | ||||
-rw-r--r-- | drivers/serial/samsung.c | 9 | ||||
-rw-r--r-- | drivers/serial/sh-sci.c | 42 | ||||
-rw-r--r-- | drivers/serial/sh-sci.h | 29 | ||||
-rw-r--r-- | drivers/sh/Makefile | 5 | ||||
-rw-r--r-- | drivers/sh/clk-cpg.c | 58 | ||||
-rw-r--r-- | drivers/video/Kconfig | 15 | ||||
-rw-r--r-- | drivers/video/Makefile | 2 | ||||
-rw-r--r-- | drivers/video/sh_mipi_dsi.c | 505 | ||||
-rw-r--r-- | drivers/video/sh_mobile_hdmi.c | 1028 | ||||
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 196 |
26 files changed, 2562 insertions, 143 deletions
diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig index 8fae6afd6a3d..65e3e2708371 100644 --- a/drivers/ata/Kconfig +++ b/drivers/ata/Kconfig @@ -651,6 +651,17 @@ config PATA_VIA If unsure, say N. +config PATA_PXA + tristate "PXA DMA-capable PATA support" + depends on ARCH_PXA + help + This option enables support for harddrive attached to PXA CPU's bus. + + NOTE: This driver utilizes PXA DMA controller, in case your hardware + is not capable of doing MWDMA, use pata_platform instead. + + If unsure, say N. + config PATA_WINBOND tristate "Winbond SL82C105 PATA support" depends on PCI diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile index 6540632bda08..158eaa961b1e 100644 --- a/drivers/ata/Makefile +++ b/drivers/ata/Makefile @@ -91,6 +91,8 @@ obj-$(CONFIG_PATA_RZ1000) += pata_rz1000.o obj-$(CONFIG_PATA_SAMSUNG_CF) += pata_samsung_cf.o obj-$(CONFIG_PATA_WINBOND_VLB) += pata_winbond.o +obj-$(CONFIG_PATA_PXA) += pata_pxa.o + # Should be last but two libata driver obj-$(CONFIG_PATA_ACPI) += pata_acpi.o # Should be last but one libata driver diff --git a/drivers/ata/pata_pxa.c b/drivers/ata/pata_pxa.c new file mode 100644 index 000000000000..1898c6ed4b4e --- /dev/null +++ b/drivers/ata/pata_pxa.c @@ -0,0 +1,411 @@ +/* + * Generic PXA PATA driver + * + * Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.com> + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/blkdev.h> +#include <linux/ata.h> +#include <linux/libata.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/completion.h> + +#include <scsi/scsi_host.h> + +#include <mach/pxa2xx-regs.h> +#include <mach/pata_pxa.h> +#include <mach/dma.h> + +#define DRV_NAME "pata_pxa" +#define DRV_VERSION "0.1" + +struct pata_pxa_data { + uint32_t dma_channel; + struct pxa_dma_desc *dma_desc; + dma_addr_t dma_desc_addr; + uint32_t dma_desc_id; + + /* DMA IO physical address */ + uint32_t dma_io_addr; + /* PXA DREQ<0:2> pin selector */ + uint32_t dma_dreq; + /* DMA DCSR register value */ + uint32_t dma_dcsr; + + struct completion dma_done; +}; + +/* + * Setup the DMA descriptors. The size is transfer capped at 4k per descriptor, + * if the transfer is longer, it is split into multiple chained descriptors. + */ +static void pxa_load_dmac(struct scatterlist *sg, struct ata_queued_cmd *qc) +{ + struct pata_pxa_data *pd = qc->ap->private_data; + + uint32_t cpu_len, seg_len; + dma_addr_t cpu_addr; + + cpu_addr = sg_dma_address(sg); + cpu_len = sg_dma_len(sg); + + do { + seg_len = (cpu_len > 0x1000) ? 0x1000 : cpu_len; + + pd->dma_desc[pd->dma_desc_id].ddadr = pd->dma_desc_addr + + ((pd->dma_desc_id + 1) * sizeof(struct pxa_dma_desc)); + + pd->dma_desc[pd->dma_desc_id].dcmd = DCMD_BURST32 | + DCMD_WIDTH2 | (DCMD_LENGTH & seg_len); + + if (qc->tf.flags & ATA_TFLAG_WRITE) { + pd->dma_desc[pd->dma_desc_id].dsadr = cpu_addr; + pd->dma_desc[pd->dma_desc_id].dtadr = pd->dma_io_addr; + pd->dma_desc[pd->dma_desc_id].dcmd |= DCMD_INCSRCADDR | + DCMD_FLOWTRG; + } else { + pd->dma_desc[pd->dma_desc_id].dsadr = pd->dma_io_addr; + pd->dma_desc[pd->dma_desc_id].dtadr = cpu_addr; + pd->dma_desc[pd->dma_desc_id].dcmd |= DCMD_INCTRGADDR | + DCMD_FLOWSRC; + } + + cpu_len -= seg_len; + cpu_addr += seg_len; + pd->dma_desc_id++; + + } while (cpu_len); + + /* Should not happen */ + if (seg_len & 0x1f) + DALGN |= (1 << pd->dma_dreq); +} + +/* + * Prepare taskfile for submission. + */ +static void pxa_qc_prep(struct ata_queued_cmd *qc) +{ + struct pata_pxa_data *pd = qc->ap->private_data; + int si = 0; + struct scatterlist *sg; + + if (!(qc->flags & ATA_QCFLAG_DMAMAP)) + return; + + pd->dma_desc_id = 0; + + DCSR(pd->dma_channel) = 0; + DALGN &= ~(1 << pd->dma_dreq); + + for_each_sg(qc->sg, sg, qc->n_elem, si) + pxa_load_dmac(sg, qc); + + pd->dma_desc[pd->dma_desc_id - 1].ddadr = DDADR_STOP; + + /* Fire IRQ only at the end of last block */ + pd->dma_desc[pd->dma_desc_id - 1].dcmd |= DCMD_ENDIRQEN; + + DDADR(pd->dma_channel) = pd->dma_desc_addr; + DRCMR(pd->dma_dreq) = DRCMR_MAPVLD | pd->dma_channel; + +} + +/* + * Configure the DMA controller, load the DMA descriptors, but don't start the + * DMA controller yet. Only issue the ATA command. + */ +static void pxa_bmdma_setup(struct ata_queued_cmd *qc) +{ + qc->ap->ops->sff_exec_command(qc->ap, &qc->tf); +} + +/* + * Execute the DMA transfer. + */ +static void pxa_bmdma_start(struct ata_queued_cmd *qc) +{ + struct pata_pxa_data *pd = qc->ap->private_data; + init_completion(&pd->dma_done); + DCSR(pd->dma_channel) = DCSR_RUN; +} + +/* + * Wait until the DMA transfer completes, then stop the DMA controller. + */ +static void pxa_bmdma_stop(struct ata_queued_cmd *qc) +{ + struct pata_pxa_data *pd = qc->ap->private_data; + + if ((DCSR(pd->dma_channel) & DCSR_RUN) && + wait_for_completion_timeout(&pd->dma_done, HZ)) + dev_err(qc->ap->dev, "Timeout waiting for DMA completion!"); + + DCSR(pd->dma_channel) = 0; +} + +/* + * Read DMA status. The bmdma_stop() will take care of properly finishing the + * DMA transfer so we always have DMA-complete interrupt here. + */ +static unsigned char pxa_bmdma_status(struct ata_port *ap) +{ + struct pata_pxa_data *pd = ap->private_data; + unsigned char ret = ATA_DMA_INTR; + + if (pd->dma_dcsr & DCSR_BUSERR) + ret |= ATA_DMA_ERR; + + return ret; +} + +/* + * No IRQ register present so we do nothing. + */ +static void pxa_irq_clear(struct ata_port *ap) +{ +} + +/* + * Check for ATAPI DMA. ATAPI DMA is unsupported by this driver. It's still + * unclear why ATAPI has DMA issues. + */ +static int pxa_check_atapi_dma(struct ata_queued_cmd *qc) +{ + return -EOPNOTSUPP; +} + +static struct scsi_host_template pxa_ata_sht = { + ATA_BMDMA_SHT(DRV_NAME), +}; + +static struct ata_port_operations pxa_ata_port_ops = { + .inherits = &ata_bmdma_port_ops, + .cable_detect = ata_cable_40wire, + + .bmdma_setup = pxa_bmdma_setup, + .bmdma_start = pxa_bmdma_start, + .bmdma_stop = pxa_bmdma_stop, + .bmdma_status = pxa_bmdma_status, + + .check_atapi_dma = pxa_check_atapi_dma, + + .sff_irq_clear = pxa_irq_clear, + + .qc_prep = pxa_qc_prep, +}; + +/* + * DMA interrupt handler. + */ +static void pxa_ata_dma_irq(int dma, void *port) +{ + struct ata_port *ap = port; + struct pata_pxa_data *pd = ap->private_data; + + pd->dma_dcsr = DCSR(dma); + DCSR(dma) = pd->dma_dcsr; + + if (pd->dma_dcsr & DCSR_STOPSTATE) + complete(&pd->dma_done); +} + +static int __devinit pxa_ata_probe(struct platform_device *pdev) +{ + struct ata_host *host; + struct ata_port *ap; + struct pata_pxa_data *data; + struct resource *cmd_res; + struct resource *ctl_res; + struct resource *dma_res; + struct resource *irq_res; + struct pata_pxa_pdata *pdata = pdev->dev.platform_data; + int ret = 0; + + /* + * Resource validation, three resources are needed: + * - CMD port base address + * - CTL port base address + * - DMA port base address + * - IRQ pin + */ + if (pdev->num_resources != 4) { + dev_err(&pdev->dev, "invalid number of resources\n"); + return -EINVAL; + } + + /* + * CMD port base address + */ + cmd_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(cmd_res == NULL)) + return -EINVAL; + + /* + * CTL port base address + */ + ctl_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (unlikely(ctl_res == NULL)) + return -EINVAL; + + /* + * DMA port base address + */ + dma_res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (unlikely(dma_res == NULL)) + return -EINVAL; + + /* + * IRQ pin + */ + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (unlikely(irq_res == NULL)) + return -EINVAL; + + /* + * Allocate the host + */ + host = ata_host_alloc(&pdev->dev, 1); + if (!host) + return -ENOMEM; + + ap = host->ports[0]; + ap->ops = &pxa_ata_port_ops; + ap->pio_mask = ATA_PIO4; + ap->mwdma_mask = ATA_MWDMA2; + ap->flags = ATA_FLAG_MMIO; + + ap->ioaddr.cmd_addr = devm_ioremap(&pdev->dev, cmd_res->start, + resource_size(cmd_res)); + ap->ioaddr.ctl_addr = devm_ioremap(&pdev->dev, ctl_res->start, + resource_size(ctl_res)); + ap->ioaddr.bmdma_addr = devm_ioremap(&pdev->dev, dma_res->start, + resource_size(dma_res)); + + /* + * Adjust register offsets + */ + ap->ioaddr.altstatus_addr = ap->ioaddr.ctl_addr; + ap->ioaddr.data_addr = ap->ioaddr.cmd_addr + + (ATA_REG_DATA << pdata->reg_shift); + ap->ioaddr.error_addr = ap->ioaddr.cmd_addr + + (ATA_REG_ERR << pdata->reg_shift); + ap->ioaddr.feature_addr = ap->ioaddr.cmd_addr + + (ATA_REG_FEATURE << pdata->reg_shift); + ap->ioaddr.nsect_addr = ap->ioaddr.cmd_addr + + (ATA_REG_NSECT << pdata->reg_shift); + ap->ioaddr.lbal_addr = ap->ioaddr.cmd_addr + + (ATA_REG_LBAL << pdata->reg_shift); + ap->ioaddr.lbam_addr = ap->ioaddr.cmd_addr + + (ATA_REG_LBAM << pdata->reg_shift); + ap->ioaddr.lbah_addr = ap->ioaddr.cmd_addr + + (ATA_REG_LBAH << pdata->reg_shift); + ap->ioaddr.device_addr = ap->ioaddr.cmd_addr + + (ATA_REG_DEVICE << pdata->reg_shift); + ap->ioaddr.status_addr = ap->ioaddr.cmd_addr + + (ATA_REG_STATUS << pdata->reg_shift); + ap->ioaddr.command_addr = ap->ioaddr.cmd_addr + + (ATA_REG_CMD << pdata->reg_shift); + + /* + * Allocate and load driver's internal data structure + */ + data = devm_kzalloc(&pdev->dev, sizeof(struct pata_pxa_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + ap->private_data = data; + data->dma_dreq = pdata->dma_dreq; + data->dma_io_addr = dma_res->start; + + /* + * Allocate space for the DMA descriptors + */ + data->dma_desc = dmam_alloc_coherent(&pdev->dev, PAGE_SIZE, + &data->dma_desc_addr, GFP_KERNEL); + if (!data->dma_desc) + return -EINVAL; + + /* + * Request the DMA channel + */ + data->dma_channel = pxa_request_dma(DRV_NAME, DMA_PRIO_LOW, + pxa_ata_dma_irq, ap); + if (data->dma_channel < 0) + return -EBUSY; + + /* + * Stop and clear the DMA channel + */ + DCSR(data->dma_channel) = 0; + + /* + * Activate the ATA host + */ + ret = ata_host_activate(host, irq_res->start, ata_sff_interrupt, + pdata->irq_flags, &pxa_ata_sht); + if (ret) + pxa_free_dma(data->dma_channel); + + return ret; +} + +static int __devexit pxa_ata_remove(struct platform_device *pdev) +{ + struct ata_host *host = dev_get_drvdata(&pdev->dev); + struct pata_pxa_data *data = host->ports[0]->private_data; + + pxa_free_dma(data->dma_channel); + + ata_host_detach(host); + + return 0; +} + +static struct platform_driver pxa_ata_driver = { + .probe = pxa_ata_probe, + .remove = __devexit_p(pxa_ata_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pxa_ata_init(void) +{ + return platform_driver_register(&pxa_ata_driver); +} + +static void __exit pxa_ata_exit(void) +{ + platform_driver_unregister(&pxa_ata_driver); +} + +module_init(pxa_ata_init); +module_exit(pxa_ata_exit); + +MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); +MODULE_DESCRIPTION("DMA-capable driver for PATA on PXA CPU"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index fed57634b6c1..9520cf02edc8 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -141,7 +141,7 @@ config TXX9_DMAC config SH_DMAE tristate "Renesas SuperH DMAC support" - depends on SUPERH && SH_DMA + depends on (SUPERH && SH_DMA) || (ARM && ARCH_SHMOBILE) depends on !SH_DMA_API select DMA_ENGINE help diff --git a/drivers/dma/shdma.c b/drivers/dma/shdma.c index a2a519fd2a24..fb64cf36ba61 100644 --- a/drivers/dma/shdma.c +++ b/drivers/dma/shdma.c @@ -816,7 +816,7 @@ static irqreturn_t sh_dmae_interrupt(int irq, void *data) return ret; } -#if defined(CONFIG_CPU_SH4) +#if defined(CONFIG_CPU_SH4) || defined(CONFIG_ARCH_SHMOBILE) static irqreturn_t sh_dmae_err(int irq, void *data) { struct sh_dmae_device *shdev = (struct sh_dmae_device *)data; @@ -1057,7 +1057,7 @@ static int __init sh_dmae_probe(struct platform_device *pdev) /* Default transfer size of 32 bytes requires 32-byte alignment */ shdev->common.copy_align = LOG2_DEFAULT_XFER_SIZE; -#if defined(CONFIG_CPU_SH4) +#if defined(CONFIG_CPU_SH4) || defined(CONFIG_ARCH_SHMOBILE) chanirq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 1); if (!chanirq_res) @@ -1082,7 +1082,7 @@ static int __init sh_dmae_probe(struct platform_device *pdev) #else chanirq_res = errirq_res; -#endif /* CONFIG_CPU_SH4 */ +#endif /* CONFIG_CPU_SH4 || CONFIG_ARCH_SHMOBILE */ if (chanirq_res->start == chanirq_res->end && !platform_get_resource(pdev, IORESOURCE_IRQ, 1)) { @@ -1129,7 +1129,7 @@ static int __init sh_dmae_probe(struct platform_device *pdev) chan_probe_err: sh_dmae_chan_remove(shdev); eirqres: -#if defined(CONFIG_CPU_SH4) +#if defined(CONFIG_CPU_SH4) || defined(CONFIG_ARCH_SHMOBILE) free_irq(errirq, shdev); eirq_err: #endif diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index bceafbfa7268..15a9702e2941 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -521,12 +521,19 @@ config I2C_PXA_SLAVE is necessary for systems where the PXA may be a target on the I2C bus. +config HAVE_S3C2410_I2C + bool + help + This will include I2C support for Samsung SoCs. If you want to + include I2C support for any machine, kindly select this in the + respective Kconfig file. + config I2C_S3C2410 tristate "S3C2410 I2C Driver" - depends on ARCH_S3C2410 || ARCH_S3C64XX + depends on HAVE_S3C2410_I2C help Say Y here to include support for I2C controller in the - Samsung S3C2410 based System-on-Chip devices. + Samsung SoCs. config I2C_S6000 tristate "S6000 I2C support" @@ -549,7 +556,7 @@ config I2C_SH7760 config I2C_SH_MOBILE tristate "SuperH Mobile I2C Controller" - depends on SUPERH + depends on SUPERH || ARCH_SHMOBILE help If you say yes to this option, support will be included for the built-in I2C interface on the Renesas SH-Mobile processor. diff --git a/drivers/i2c/busses/i2c-sh_mobile.c b/drivers/i2c/busses/i2c-sh_mobile.c index ffb405d7c6f2..598c49acaeb5 100644 --- a/drivers/i2c/busses/i2c-sh_mobile.c +++ b/drivers/i2c/busses/i2c-sh_mobile.c @@ -119,8 +119,10 @@ struct sh_mobile_i2c_data { struct i2c_adapter adap; struct clk *clk; + u_int8_t icic; u_int8_t iccl; u_int8_t icch; + u_int8_t flags; spinlock_t lock; wait_queue_head_t wait; @@ -129,15 +131,17 @@ struct sh_mobile_i2c_data { int sr; }; +#define IIC_FLAG_HAS_ICIC67 (1 << 0) + #define NORMAL_SPEED 100000 /* FAST_SPEED 400000 */ /* Register offsets */ -#define ICDR(pd) (pd->reg + 0x00) -#define ICCR(pd) (pd->reg + 0x04) -#define ICSR(pd) (pd->reg + 0x08) -#define ICIC(pd) (pd->reg + 0x0c) -#define ICCL(pd) (pd->reg + 0x10) -#define ICCH(pd) (pd->reg + 0x14) +#define ICDR 0x00 +#define ICCR 0x04 +#define ICSR 0x08 +#define ICIC 0x0c +#define ICCL 0x10 +#define ICCH 0x14 /* Register bits */ #define ICCR_ICE 0x80 @@ -155,11 +159,32 @@ struct sh_mobile_i2c_data { #define ICSR_WAIT 0x02 #define ICSR_DTE 0x01 +#define ICIC_ICCLB8 0x80 +#define ICIC_ICCHB8 0x40 #define ICIC_ALE 0x08 #define ICIC_TACKE 0x04 #define ICIC_WAITE 0x02 #define ICIC_DTEE 0x01 +static void iic_wr(struct sh_mobile_i2c_data *pd, int offs, unsigned char data) +{ + if (offs == ICIC) + data |= pd->icic; + + iowrite8(data, pd->reg + offs); +} + +static unsigned char iic_rd(struct sh_mobile_i2c_data *pd, int offs) +{ + return ioread8(pd->reg + offs); +} + +static void iic_set_clr(struct sh_mobile_i2c_data *pd, int offs, + unsigned char set, unsigned char clr) +{ + iic_wr(pd, offs, (iic_rd(pd, offs) | set) & ~clr); +} + static void activate_ch(struct sh_mobile_i2c_data *pd) { unsigned long i2c_clk; @@ -187,6 +212,14 @@ static void activate_ch(struct sh_mobile_i2c_data *pd) else pd->iccl = (u_int8_t)(num/denom); + /* one more bit of ICCL in ICIC */ + if (pd->flags & IIC_FLAG_HAS_ICIC67) { + if ((num/denom) > 0xff) + pd->icic |= ICIC_ICCLB8; + else + pd->icic &= ~ICIC_ICCLB8; + } + /* Calculate the value for icch. From the data sheet: icch = (p clock / transfer rate) * (H / (L + H)) */ num = i2c_clk * 4; @@ -196,25 +229,33 @@ static void activate_ch(struct sh_mobile_i2c_data *pd) else pd->icch = (u_int8_t)(num/denom); + /* one more bit of ICCH in ICIC */ + if (pd->flags & IIC_FLAG_HAS_ICIC67) { + if ((num/denom) > 0xff) + pd->icic |= ICIC_ICCHB8; + else + pd->icic &= ~ICIC_ICCHB8; + } + /* Enable channel and configure rx ack */ - iowrite8(ioread8(ICCR(pd)) | ICCR_ICE, ICCR(pd)); + iic_set_clr(pd, ICCR, ICCR_ICE, 0); /* Mask all interrupts */ - iowrite8(0, ICIC(pd)); + iic_wr(pd, ICIC, 0); /* Set the clock */ - iowrite8(pd->iccl, ICCL(pd)); - iowrite8(pd->icch, ICCH(pd)); + iic_wr(pd, ICCL, pd->iccl); + iic_wr(pd, ICCH, pd->icch); } static void deactivate_ch(struct sh_mobile_i2c_data *pd) { /* Clear/disable interrupts */ - iowrite8(0, ICSR(pd)); - iowrite8(0, ICIC(pd)); + iic_wr(pd, ICSR, 0); + iic_wr(pd, ICIC, 0); /* Disable channel */ - iowrite8(ioread8(ICCR(pd)) & ~ICCR_ICE, ICCR(pd)); + iic_set_clr(pd, ICCR, 0, ICCR_ICE); /* Disable clock and mark device as idle */ clk_disable(pd->clk); @@ -233,35 +274,35 @@ static unsigned char i2c_op(struct sh_mobile_i2c_data *pd, switch (op) { case OP_START: /* issue start and trigger DTE interrupt */ - iowrite8(0x94, ICCR(pd)); + iic_wr(pd, ICCR, 0x94); break; case OP_TX_FIRST: /* disable DTE interrupt and write data */ - iowrite8(ICIC_WAITE | ICIC_ALE | ICIC_TACKE, ICIC(pd)); - iowrite8(data, ICDR(pd)); + iic_wr(pd, ICIC, ICIC_WAITE | ICIC_ALE | ICIC_TACKE); + iic_wr(pd, ICDR, data); break; case OP_TX: /* write data */ - iowrite8(data, ICDR(pd)); + iic_wr(pd, ICDR, data); break; case OP_TX_STOP: /* write data and issue a stop afterwards */ - iowrite8(data, ICDR(pd)); - iowrite8(0x90, ICCR(pd)); + iic_wr(pd, ICDR, data); + iic_wr(pd, ICCR, 0x90); break; case OP_TX_TO_RX: /* select read mode */ - iowrite8(0x81, ICCR(pd)); + iic_wr(pd, ICCR, 0x81); break; case OP_RX: /* just read data */ - ret = ioread8(ICDR(pd)); + ret = iic_rd(pd, ICDR); break; case OP_RX_STOP: /* enable DTE interrupt, issue stop */ - iowrite8(ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE, - ICIC(pd)); - iowrite8(0xc0, ICCR(pd)); + iic_wr(pd, ICIC, + ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE); + iic_wr(pd, ICCR, 0xc0); break; case OP_RX_STOP_DATA: /* enable DTE interrupt, read data, issue stop */ - iowrite8(ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE, - ICIC(pd)); - ret = ioread8(ICDR(pd)); - iowrite8(0xc0, ICCR(pd)); + iic_wr(pd, ICIC, + ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE); + ret = iic_rd(pd, ICDR); + iic_wr(pd, ICCR, 0xc0); break; } @@ -367,7 +408,7 @@ static irqreturn_t sh_mobile_i2c_isr(int irq, void *dev_id) unsigned char sr; int wakeup; - sr = ioread8(ICSR(pd)); + sr = iic_rd(pd, ICSR); pd->sr |= sr; /* remember state */ dev_dbg(pd->dev, "i2c_isr 0x%02x 0x%02x %s %d %d!\n", sr, pd->sr, @@ -376,7 +417,7 @@ static irqreturn_t sh_mobile_i2c_isr(int irq, void *dev_id) if (sr & (ICSR_AL | ICSR_TACK)) { /* don't interrupt transaction - continue to issue stop */ - iowrite8(sr & ~(ICSR_AL | ICSR_TACK), ICSR(pd)); + iic_wr(pd, ICSR, sr & ~(ICSR_AL | ICSR_TACK)); wakeup = 0; } else if (pd->msg->flags & I2C_M_RD) wakeup = sh_mobile_i2c_isr_rx(pd); @@ -384,7 +425,7 @@ static irqreturn_t sh_mobile_i2c_isr(int irq, void *dev_id) wakeup = sh_mobile_i2c_isr_tx(pd); if (sr & ICSR_WAIT) /* TODO: add delay here to support slow acks */ - iowrite8(sr & ~ICSR_WAIT, ICSR(pd)); + iic_wr(pd, ICSR, sr & ~ICSR_WAIT); if (wakeup) { pd->sr |= SW_DONE; @@ -402,21 +443,21 @@ static int start_ch(struct sh_mobile_i2c_data *pd, struct i2c_msg *usr_msg) } /* Initialize channel registers */ - iowrite8(ioread8(ICCR(pd)) & ~ICCR_ICE, ICCR(pd)); + iic_set_clr(pd, ICCR, 0, ICCR_ICE); /* Enable channel and configure rx ack */ - iowrite8(ioread8(ICCR(pd)) | ICCR_ICE, ICCR(pd)); + iic_set_clr(pd, ICCR, ICCR_ICE, 0); /* Set the clock */ - iowrite8(pd->iccl, ICCL(pd)); - iowrite8(pd->icch, ICCH(pd)); + iic_wr(pd, ICCL, pd->iccl); + iic_wr(pd, ICCH, pd->icch); pd->msg = usr_msg; pd->pos = -1; pd->sr = 0; /* Enable all interrupts to begin with */ - iowrite8(ICIC_WAITE | ICIC_ALE | ICIC_TACKE | ICIC_DTEE, ICIC(pd)); + iic_wr(pd, ICIC, ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE); return 0; } @@ -451,7 +492,7 @@ static int sh_mobile_i2c_xfer(struct i2c_adapter *adapter, retry_count = 1000; again: - val = ioread8(ICSR(pd)); + val = iic_rd(pd, ICSR); dev_dbg(pd->dev, "val 0x%02x pd->sr 0x%02x\n", val, pd->sr); @@ -576,6 +617,12 @@ static int sh_mobile_i2c_probe(struct platform_device *dev) goto err_irq; } + /* The IIC blocks on SH-Mobile ARM processors + * come with two new bits in ICIC. + */ + if (size > 0x17) + pd->flags |= IIC_FLAG_HAS_ICIC67; + /* Enable Runtime PM for this device. * * Also tell the Runtime PM core to ignore children diff --git a/drivers/net/irda/sh_irda.c b/drivers/net/irda/sh_irda.c index edd5666f0ffb..9e3f4f54281d 100644 --- a/drivers/net/irda/sh_irda.c +++ b/drivers/net/irda/sh_irda.c @@ -748,7 +748,6 @@ static int __devinit sh_irda_probe(struct platform_device *pdev) struct net_device *ndev; struct sh_irda_self *self; struct resource *res; - char clk_name[8]; int irq; int err = -ENOMEM; @@ -775,10 +774,9 @@ static int __devinit sh_irda_probe(struct platform_device *pdev) if (err) goto err_mem_2; - snprintf(clk_name, sizeof(clk_name), "irda%d", pdev->id); - self->clk = clk_get(&pdev->dev, clk_name); + self->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(self->clk)) { - dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name); + dev_err(&pdev->dev, "cannot get irda clock\n"); goto err_mem_3; } diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig index c988514eb551..c80a7a6e7698 100644 --- a/drivers/pcmcia/Kconfig +++ b/drivers/pcmcia/Kconfig @@ -215,7 +215,7 @@ config PCMCIA_PXA2XX depends on (ARCH_LUBBOCK || MACH_MAINSTONE || PXA_SHARPSL \ || MACH_ARMCORE || ARCH_PXA_PALM || TRIZEPS_PCMCIA \ || ARCOM_PCMCIA || ARCH_PXA_ESERIES || MACH_STARGATE2 \ - || MACH_VPAC270) + || MACH_VPAC270 || MACH_BALLOON3) select PCMCIA_SOC_COMMON help Say Y here to include support for the PXA2xx PCMCIA controller diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile index 7a2b1604bf1c..8d9386a22eb3 100644 --- a/drivers/pcmcia/Makefile +++ b/drivers/pcmcia/Makefile @@ -69,6 +69,7 @@ pxa2xx-obj-$(CONFIG_MACH_PALMLD) += pxa2xx_palmld.o pxa2xx-obj-$(CONFIG_MACH_E740) += pxa2xx_e740.o pxa2xx-obj-$(CONFIG_MACH_STARGATE2) += pxa2xx_stargate2.o pxa2xx-obj-$(CONFIG_MACH_VPAC270) += pxa2xx_vpac270.o +pxa2xx-obj-$(CONFIG_MACH_BALLOON3) += pxa2xx_balloon3.o obj-$(CONFIG_PCMCIA_PXA2XX) += pxa2xx_base.o $(pxa2xx-obj-y) diff --git a/drivers/pcmcia/pxa2xx_balloon3.c b/drivers/pcmcia/pxa2xx_balloon3.c new file mode 100644 index 000000000000..dbbdd0063202 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_balloon3.c @@ -0,0 +1,158 @@ +/* + * linux/drivers/pcmcia/pxa2xx_balloon3.c + * + * Balloon3 PCMCIA specific routines. + * + * Author: Nick Bane + * Created: June, 2006 + * Copyright: Toby Churchill Ltd + * Derived from pxa2xx_mainstone.c, by Nico Pitre + * + * Various modification by Marek Vasut <marek.vasut@gmail.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/module.h> +#include <linux/gpio.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <linux/io.h> + +#include <mach/balloon3.h> + +#include "soc_common.h" + +/* + * These are a list of interrupt sources that provokes a polled + * check of status + */ +static struct pcmcia_irqs irqs[] = { + { 0, BALLOON3_S0_CD_IRQ, "PCMCIA0 CD" }, + { 0, BALLOON3_BP_NSTSCHG_IRQ, "PCMCIA0 STSCHG" }, +}; + +static int balloon3_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + uint16_t ver; + int ret; + static void __iomem *fpga_ver; + + ver = __raw_readw(BALLOON3_FPGA_VER); + if (ver > 0x0201) + pr_warn("The FPGA code, version 0x%04x, is newer than rel-0.3. " + "PCMCIA/CF support might be broken in this version!", + ver); + + skt->socket.pci_irq = BALLOON3_BP_CF_NRDY_IRQ; + return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void balloon3_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static unsigned long balloon3_pcmcia_status[2] = { + BALLOON3_CF_nSTSCHG_BVD1, + BALLOON3_CF_nSTSCHG_BVD1 +}; + +static void balloon3_pcmcia_socket_state(struct soc_pcmcia_socket *skt, + struct pcmcia_state *state) +{ + uint16_t status; + int flip; + + /* This actually reads the STATUS register */ + status = __raw_readw(BALLOON3_CF_STATUS_REG); + flip = (status ^ balloon3_pcmcia_status[skt->nr]) + & BALLOON3_CF_nSTSCHG_BVD1; + /* + * Workaround for STSCHG which can't be deasserted: + * We therefore disable/enable corresponding IRQs + * as needed to avoid IRQ locks. + */ + if (flip) { + balloon3_pcmcia_status[skt->nr] = status; + if (status & BALLOON3_CF_nSTSCHG_BVD1) + enable_irq(BALLOON3_BP_NSTSCHG_IRQ); + else + disable_irq(BALLOON3_BP_NSTSCHG_IRQ); + } + + state->detect = !gpio_get_value(BALLOON3_GPIO_S0_CD); + state->ready = !!(status & BALLOON3_CF_nIRQ); + state->bvd1 = !!(status & BALLOON3_CF_nSTSCHG_BVD1); + state->bvd2 = 0; /* not available */ + state->vs_3v = 1; /* Always true its a CF card */ + state->vs_Xv = 0; /* not available */ + state->wrprot = 0; /* not available */ +} + +static int balloon3_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, + const socket_state_t *state) +{ + __raw_writew((state->flags & SS_RESET) ? BALLOON3_CF_RESET : 0, + BALLOON3_CF_CONTROL_REG); + return 0; +} + +static void balloon3_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ +} + +static void balloon3_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) +{ +} + +static struct pcmcia_low_level balloon3_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = balloon3_pcmcia_hw_init, + .hw_shutdown = balloon3_pcmcia_hw_shutdown, + .socket_state = balloon3_pcmcia_socket_state, + .configure_socket = balloon3_pcmcia_configure_socket, + .socket_init = balloon3_pcmcia_socket_init, + .socket_suspend = balloon3_pcmcia_socket_suspend, + .first = 0, + .nr = 1, +}; + +static struct platform_device *balloon3_pcmcia_device; + +static int __init balloon3_pcmcia_init(void) +{ + int ret; + + balloon3_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1); + if (!balloon3_pcmcia_device) + return -ENOMEM; + + ret = platform_device_add_data(balloon3_pcmcia_device, + &balloon3_pcmcia_ops, sizeof(balloon3_pcmcia_ops)); + + if (!ret) + ret = platform_device_add(balloon3_pcmcia_device); + + if (ret) + platform_device_put(balloon3_pcmcia_device); + + return ret; +} + +static void __exit balloon3_pcmcia_exit(void) +{ + platform_device_unregister(balloon3_pcmcia_device); +} + +module_init(balloon3_pcmcia_init); +module_exit(balloon3_pcmcia_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nick Bane <nick@cecomputing.co.uk>"); +MODULE_ALIAS("platform:pxa2xx-pcmcia"); +MODULE_DESCRIPTION("Balloon3 board CF/PCMCIA driver"); diff --git a/drivers/power/wm97xx_battery.c b/drivers/power/wm97xx_battery.c index 4e8afce0c818..5071d85ec12d 100644 --- a/drivers/power/wm97xx_battery.c +++ b/drivers/power/wm97xx_battery.c @@ -29,7 +29,6 @@ static DEFINE_MUTEX(bat_lock); static struct work_struct bat_work; static struct mutex work_lock; static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; -static struct wm97xx_batt_info *gpdata; static enum power_supply_property *prop; static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) @@ -172,12 +171,6 @@ static int __devinit wm97xx_bat_probe(struct platform_device *dev) struct wm97xx_pdata *wmdata = dev->dev.platform_data; struct wm97xx_batt_pdata *pdata; - if (gpdata) { - dev_err(&dev->dev, "Do not pass platform_data through " - "wm97xx_bat_set_pdata!\n"); - return -EINVAL; - } - if (!wmdata) { dev_err(&dev->dev, "No platform data supplied\n"); return -EINVAL; @@ -308,15 +301,6 @@ static void __exit wm97xx_bat_exit(void) platform_driver_unregister(&wm97xx_bat_driver); } -/* The interface is deprecated, as well as linux/wm97xx_batt.h */ -void wm97xx_bat_set_pdata(struct wm97xx_batt_info *data); - -void wm97xx_bat_set_pdata(struct wm97xx_batt_info *data) -{ - gpdata = data; -} -EXPORT_SYMBOL_GPL(wm97xx_bat_set_pdata); - module_init(wm97xx_bat_init); module_exit(wm97xx_bat_exit); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 4301a6c7ed3b..9238c8f40f03 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -645,9 +645,16 @@ config RTC_DRV_OMAP DA8xx/OMAP-L13x chips. This driver can also be built as a module called rtc-omap. +config HAVE_S3C_RTC + bool + help + This will include RTC support for Samsung SoCs. If + you want to include RTC support for any machine, kindly + select this in the respective mach-XXXX/Kconfig file. + config RTC_DRV_S3C tristate "Samsung S3C series SoC RTC" - depends on ARCH_S3C2410 || ARCH_S3C64XX + depends on ARCH_S3C2410 || ARCH_S3C64XX || HAVE_S3C_RTC help RTC (Realtime Clock) driver for the clock inbuilt into the Samsung S3C24XX series of SoCs. This can provide periodic diff --git a/drivers/rtc/rtc-s3c.c b/drivers/rtc/rtc-s3c.c index 70b68d35f969..a0d3ec89d412 100644 --- a/drivers/rtc/rtc-s3c.c +++ b/drivers/rtc/rtc-s3c.c @@ -1,5 +1,8 @@ /* drivers/rtc/rtc-s3c.c * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * * Copyright (c) 2004,2006 Simtec Electronics * Ben Dooks, <ben@simtec.co.uk> * http://armlinux.simtec.co.uk/ @@ -39,6 +42,7 @@ enum s3c_cpu_type { static struct resource *s3c_rtc_mem; +static struct clk *rtc_clk; static void __iomem *s3c_rtc_base; static int s3c_rtc_alarmno = NO_IRQ; static int s3c_rtc_tickno = NO_IRQ; @@ -53,6 +57,10 @@ static irqreturn_t s3c_rtc_alarmirq(int irq, void *id) struct rtc_device *rdev = id; rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF); + + if (s3c_rtc_cpu_type == TYPE_S3C64XX) + writeb(S3C2410_INTP_ALM, s3c_rtc_base + S3C2410_INTP); + return IRQ_HANDLED; } @@ -61,6 +69,10 @@ static irqreturn_t s3c_rtc_tickirq(int irq, void *id) struct rtc_device *rdev = id; rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF); + + if (s3c_rtc_cpu_type == TYPE_S3C64XX) + writeb(S3C2410_INTP_TIC, s3c_rtc_base + S3C2410_INTP); + return IRQ_HANDLED; } @@ -94,7 +106,7 @@ static int s3c_rtc_setpie(struct device *dev, int enabled) if (enabled) tmp |= S3C64XX_RTCCON_TICEN; - writeb(tmp, s3c_rtc_base + S3C2410_RTCCON); + writew(tmp, s3c_rtc_base + S3C2410_RTCCON); } else { tmp = readb(s3c_rtc_base + S3C2410_TICNT); tmp &= ~S3C2410_TICNT_ENABLE; @@ -128,7 +140,7 @@ static int s3c_rtc_setfreq(struct device *dev, int freq) tmp |= (rtc_dev->max_user_freq / freq)-1; - writeb(tmp, s3c_rtc_base + S3C2410_TICNT); + writel(tmp, s3c_rtc_base + S3C2410_TICNT); spin_unlock_irq(&s3c_rtc_pie_lock); return 0; @@ -431,6 +443,10 @@ static int __devexit s3c_rtc_remove(struct platform_device *dev) s3c_rtc_setpie(&dev->dev, 0); s3c_rtc_setaie(0); + clk_disable(rtc_clk); + clk_put(rtc_clk); + rtc_clk = NULL; + iounmap(s3c_rtc_base); release_resource(s3c_rtc_mem); kfree(s3c_rtc_mem); @@ -442,6 +458,7 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev) { struct rtc_device *rtc; struct resource *res; + unsigned int tmp, i; int ret; pr_debug("%s: probe=%p\n", __func__, pdev); @@ -488,6 +505,16 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev) goto err_nomap; } + rtc_clk = clk_get(&pdev->dev, "rtc"); + if (IS_ERR(rtc_clk)) { + dev_err(&pdev->dev, "failed to find rtc clock source\n"); + ret = PTR_ERR(rtc_clk); + rtc_clk = NULL; + goto err_clk; + } + + clk_enable(rtc_clk); + /* check to see if everything is setup correctly */ s3c_rtc_enable(pdev, 1); @@ -510,6 +537,15 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev) s3c_rtc_cpu_type = platform_get_device_id(pdev)->driver_data; + /* Check RTC Time */ + + for (i = S3C2410_RTCSEC; i <= S3C2410_RTCYEAR; i += 0x4) { + tmp = readb(s3c_rtc_base + i); + + if ((tmp & 0xf) > 0x9 || ((tmp >> 4) & 0xf) > 0x9) + writeb(0, s3c_rtc_base + i); + } + if (s3c_rtc_cpu_type == TYPE_S3C64XX) rtc->max_user_freq = 32768; else @@ -523,6 +559,10 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev) err_nortc: s3c_rtc_enable(pdev, 0); + clk_disable(rtc_clk); + clk_put(rtc_clk); + + err_clk: iounmap(s3c_rtc_base); err_nomap: diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index e437ce8c1748..8f23eb54f498 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -536,8 +536,8 @@ config SERIAL_S3C6400 config SERIAL_S5PV210 tristate "Samsung S5PV210 Serial port support" - depends on SERIAL_SAMSUNG && (CPU_S5PV210 || CPU_S5P6442) - select SERIAL_SAMSUNG_UARTS_4 if CPU_S5PV210 + depends on SERIAL_SAMSUNG && (CPU_S5PV210 || CPU_S5P6442 || CPU_S5PV310) + select SERIAL_SAMSUNG_UARTS_4 if (CPU_S5PV210 || CPU_S5PV310) default y help Serial port support for Samsung's S5P Family of SoC's diff --git a/drivers/serial/s5pv210.c b/drivers/serial/s5pv210.c index 4a789e5361a4..6ebccd70a707 100644 --- a/drivers/serial/s5pv210.c +++ b/drivers/serial/s5pv210.c @@ -28,8 +28,12 @@ static int s5pv210_serial_setsource(struct uart_port *port, struct s3c24xx_uart_clksrc *clk) { + struct s3c2410_uartcfg *cfg = port->dev->platform_data; unsigned long ucon = rd_regl(port, S3C2410_UCON); + if ((cfg->clocks_size) == 1) + return 0; + if (strcmp(clk->name, "pclk") == 0) ucon &= ~S5PV210_UCON_CLKMASK; else if (strcmp(clk->name, "uclk1") == 0) @@ -47,10 +51,14 @@ static int s5pv210_serial_setsource(struct uart_port *port, static int s5pv210_serial_getsource(struct uart_port *port, struct s3c24xx_uart_clksrc *clk) { + struct s3c2410_uartcfg *cfg = port->dev->platform_data; u32 ucon = rd_regl(port, S3C2410_UCON); clk->divisor = 1; + if ((cfg->clocks_size) == 1) + return 0; + switch (ucon & S5PV210_UCON_CLKMASK) { case S5PV210_UCON_PCLK: clk->name = "pclk"; diff --git a/drivers/serial/samsung.c b/drivers/serial/samsung.c index a9d6c5626a0a..b1156ba8ad14 100644 --- a/drivers/serial/samsung.c +++ b/drivers/serial/samsung.c @@ -705,8 +705,13 @@ static void s3c24xx_serial_set_termios(struct uart_port *port, if (ourport->info->has_divslot) { unsigned int div = ourport->baudclk_rate / baud; - udivslot = udivslot_table[div & 15]; - dbg("udivslot = %04x (div %d)\n", udivslot, div & 15); + if (cfg->has_fracval) { + udivslot = (div & 15); + dbg("fracval = %04x\n", udivslot); + } else { + udivslot = udivslot_table[div & 15]; + dbg("udivslot = %04x (div %d)\n", udivslot, div & 15); + } } switch (termios->c_cflag & CSIZE) { diff --git a/drivers/serial/sh-sci.c b/drivers/serial/sh-sci.c index 5f90fcd7d107..c291b3add1d2 100644 --- a/drivers/serial/sh-sci.c +++ b/drivers/serial/sh-sci.c @@ -346,6 +346,27 @@ static int scif_rxfill(struct uart_port *port) return sci_in(port, SCFDR) & SCIF2_RFDC_MASK; } } +#elif defined(CONFIG_ARCH_SH7372) +static int scif_txfill(struct uart_port *port) +{ + if (port->type == PORT_SCIFA) + return sci_in(port, SCFDR) >> 8; + else + return sci_in(port, SCTFDR); +} + +static int scif_txroom(struct uart_port *port) +{ + return port->fifosize - scif_txfill(port); +} + +static int scif_rxfill(struct uart_port *port) +{ + if (port->type == PORT_SCIFA) + return sci_in(port, SCFDR) & SCIF_RFDC_MASK; + else + return sci_in(port, SCRFDR); +} #else static int scif_txfill(struct uart_port *port) { @@ -683,7 +704,7 @@ static irqreturn_t sci_rx_interrupt(int irq, void *ptr) u16 ssr = sci_in(port, SCxSR); /* Disable future Rx interrupts */ - if (port->type == PORT_SCIFA) { + if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { disable_irq_nosync(irq); scr |= 0x4000; } else { @@ -928,7 +949,7 @@ static void sci_dma_tx_complete(void *arg) if (!uart_circ_empty(xmit)) { schedule_work(&s->work_tx); - } else if (port->type == PORT_SCIFA) { + } else if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { u16 ctrl = sci_in(port, SCSCR); sci_out(port, SCSCR, ctrl & ~SCI_CTRL_FLAGS_TIE); } @@ -1184,7 +1205,7 @@ static void sci_start_tx(struct uart_port *port) unsigned short ctrl; #ifdef CONFIG_SERIAL_SH_SCI_DMA - if (port->type == PORT_SCIFA) { + if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { u16 new, scr = sci_in(port, SCSCR); if (s->chan_tx) new = scr | 0x8000; @@ -1197,7 +1218,7 @@ static void sci_start_tx(struct uart_port *port) s->cookie_tx < 0) schedule_work(&s->work_tx); #endif - if (!s->chan_tx || port->type == PORT_SCIFA) { + if (!s->chan_tx || port->type == PORT_SCIFA || port->type == PORT_SCIFB) { /* Set TIE (Transmit Interrupt Enable) bit in SCSCR */ ctrl = sci_in(port, SCSCR); sci_out(port, SCSCR, ctrl | SCI_CTRL_FLAGS_TIE); @@ -1210,7 +1231,7 @@ static void sci_stop_tx(struct uart_port *port) /* Clear TIE (Transmit Interrupt Enable) bit in SCSCR */ ctrl = sci_in(port, SCSCR); - if (port->type == PORT_SCIFA) + if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) ctrl &= ~0x8000; ctrl &= ~SCI_CTRL_FLAGS_TIE; sci_out(port, SCSCR, ctrl); @@ -1222,7 +1243,7 @@ static void sci_start_rx(struct uart_port *port) /* Set RIE (Receive Interrupt Enable) bit in SCSCR */ ctrl |= sci_in(port, SCSCR); - if (port->type == PORT_SCIFA) + if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) ctrl &= ~0x4000; sci_out(port, SCSCR, ctrl); } @@ -1233,7 +1254,7 @@ static void sci_stop_rx(struct uart_port *port) /* Clear RIE (Receive Interrupt Enable) bit in SCSCR */ ctrl = sci_in(port, SCSCR); - if (port->type == PORT_SCIFA) + if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) ctrl &= ~0x4000; ctrl &= ~(SCI_CTRL_FLAGS_RIE | SCI_CTRL_FLAGS_REIE); sci_out(port, SCSCR, ctrl); @@ -1271,7 +1292,7 @@ static void rx_timer_fn(unsigned long arg) struct uart_port *port = &s->port; u16 scr = sci_in(port, SCSCR); - if (port->type == PORT_SCIFA) { + if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { scr &= ~0x4000; enable_irq(s->irqs[1]); } @@ -1524,6 +1545,8 @@ static const char *sci_type(struct uart_port *port) return "scif"; case PORT_SCIFA: return "scifa"; + case PORT_SCIFB: + return "scifb"; } return NULL; @@ -1612,6 +1635,9 @@ static int __devinit sci_init_single(struct platform_device *dev, port->line = index; switch (p->type) { + case PORT_SCIFB: + port->fifosize = 256; + break; case PORT_SCIFA: port->fifosize = 64; break; diff --git a/drivers/serial/sh-sci.h b/drivers/serial/sh-sci.h index f70c49f915fa..9b52f77a9305 100644 --- a/drivers/serial/sh-sci.h +++ b/drivers/serial/sh-sci.h @@ -322,7 +322,7 @@ #define CPU_SCIx_FNS(name, sci_offset, sci_size, scif_offset, scif_size)\ static inline unsigned int sci_##name##_in(struct uart_port *port) \ { \ - if (port->type == PORT_SCIF) { \ + if (port->type == PORT_SCIF || port->type == PORT_SCIFB) { \ SCI_IN(scif_size, scif_offset) \ } else { /* PORT_SCI or PORT_SCIFA */ \ SCI_IN(sci_size, sci_offset); \ @@ -330,7 +330,7 @@ } \ static inline void sci_##name##_out(struct uart_port *port, unsigned int value) \ { \ - if (port->type == PORT_SCIF) { \ + if (port->type == PORT_SCIF || port->type == PORT_SCIFB) { \ SCI_OUT(scif_size, scif_offset, value) \ } else { /* PORT_SCI or PORT_SCIFA */ \ SCI_OUT(sci_size, sci_offset, value); \ @@ -384,8 +384,12 @@ defined(CONFIG_CPU_SUBTYPE_SH7720) || \ defined(CONFIG_CPU_SUBTYPE_SH7721) || \ defined(CONFIG_ARCH_SH7367) || \ - defined(CONFIG_ARCH_SH7377) || \ - defined(CONFIG_ARCH_SH7372) + defined(CONFIG_ARCH_SH7377) +#define SCIF_FNS(name, scif_offset, scif_size) \ + CPU_SCIF_FNS(name, scif_offset, scif_size) +#elif defined(CONFIG_ARCH_SH7372) +#define SCIx_FNS(name, sh4_scifa_offset, sh4_scifa_size, sh4_scifb_offset, sh4_scifb_size) \ + CPU_SCIx_FNS(name, sh4_scifa_offset, sh4_scifa_size, sh4_scifb_offset, sh4_scifb_size) #define SCIF_FNS(name, scif_offset, scif_size) \ CPU_SCIF_FNS(name, scif_offset, scif_size) #else @@ -422,8 +426,7 @@ defined(CONFIG_CPU_SUBTYPE_SH7720) || \ defined(CONFIG_CPU_SUBTYPE_SH7721) || \ defined(CONFIG_ARCH_SH7367) || \ - defined(CONFIG_ARCH_SH7377) || \ - defined(CONFIG_ARCH_SH7372) + defined(CONFIG_ARCH_SH7377) SCIF_FNS(SCSMR, 0x00, 16) SCIF_FNS(SCBRR, 0x04, 8) @@ -436,6 +439,20 @@ SCIF_FNS(SCFDR, 0x1c, 16) SCIF_FNS(SCxTDR, 0x20, 8) SCIF_FNS(SCxRDR, 0x24, 8) SCIF_FNS(SCLSR, 0x00, 0) +#elif defined(CONFIG_ARCH_SH7372) +SCIF_FNS(SCSMR, 0x00, 16) +SCIF_FNS(SCBRR, 0x04, 8) +SCIF_FNS(SCSCR, 0x08, 16) +SCIF_FNS(SCTDSR, 0x0c, 16) +SCIF_FNS(SCFER, 0x10, 16) +SCIF_FNS(SCxSR, 0x14, 16) +SCIF_FNS(SCFCR, 0x18, 16) +SCIF_FNS(SCFDR, 0x1c, 16) +SCIF_FNS(SCTFDR, 0x38, 16) +SCIF_FNS(SCRFDR, 0x3c, 16) +SCIx_FNS(SCxTDR, 0x20, 8, 0x40, 8) +SCIx_FNS(SCxRDR, 0x24, 8, 0x60, 8) +SCIF_FNS(SCLSR, 0x00, 0) #elif defined(CONFIG_CPU_SUBTYPE_SH7723) ||\ defined(CONFIG_CPU_SUBTYPE_SH7724) SCIx_FNS(SCSMR, 0x00, 16, 0x00, 16) diff --git a/drivers/sh/Makefile b/drivers/sh/Makefile index 78bb5127abd0..08fc653a825c 100644 --- a/drivers/sh/Makefile +++ b/drivers/sh/Makefile @@ -1,9 +1,10 @@ # # Makefile for the SuperH specific drivers. # +obj-y := clk.o intc.o + obj-$(CONFIG_SUPERHYWAY) += superhyway/ obj-$(CONFIG_MAPLE) += maple/ + obj-$(CONFIG_GENERIC_GPIO) += pfc.o -obj-$(CONFIG_SUPERH) += clk.o obj-$(CONFIG_SH_CLK_CPG) += clk-cpg.o -obj-y += intc.o diff --git a/drivers/sh/clk-cpg.c b/drivers/sh/clk-cpg.c index f5c80ba9ab1c..8c024b984ed8 100644 --- a/drivers/sh/clk-cpg.c +++ b/drivers/sh/clk-cpg.c @@ -68,6 +68,39 @@ static unsigned long sh_clk_div6_recalc(struct clk *clk) return clk->freq_table[idx].frequency; } +static int sh_clk_div6_set_parent(struct clk *clk, struct clk *parent) +{ + struct clk_div_mult_table *table = &sh_clk_div6_table; + u32 value; + int ret, i; + + if (!clk->parent_table || !clk->parent_num) + return -EINVAL; + + /* Search the parent */ + for (i = 0; i < clk->parent_num; i++) + if (clk->parent_table[i] == parent) + break; + + if (i == clk->parent_num) + return -ENODEV; + + ret = clk_reparent(clk, parent); + if (ret < 0) + return ret; + + value = __raw_readl(clk->enable_reg) & + ~(((1 << clk->src_width) - 1) << clk->src_shift); + + __raw_writel(value | (i << clk->src_shift), clk->enable_reg); + + /* Rebuild the frequency table */ + clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, + table, &clk->arch_flags); + + return 0; +} + static int sh_clk_div6_set_rate(struct clk *clk, unsigned long rate, int algo_id) { @@ -117,7 +150,17 @@ static struct clk_ops sh_clk_div6_clk_ops = { .disable = sh_clk_div6_disable, }; -int __init sh_clk_div6_register(struct clk *clks, int nr) +static struct clk_ops sh_clk_div6_reparent_clk_ops = { + .recalc = sh_clk_div6_recalc, + .round_rate = sh_clk_div_round_rate, + .set_rate = sh_clk_div6_set_rate, + .enable = sh_clk_div6_enable, + .disable = sh_clk_div6_disable, + .set_parent = sh_clk_div6_set_parent, +}; + +static int __init sh_clk_div6_register_ops(struct clk *clks, int nr, + struct clk_ops *ops) { struct clk *clkp; void *freq_table; @@ -136,7 +179,7 @@ int __init sh_clk_div6_register(struct clk *clks, int nr) for (k = 0; !ret && (k < nr); k++) { clkp = clks + k; - clkp->ops = &sh_clk_div6_clk_ops; + clkp->ops = ops; clkp->id = -1; clkp->freq_table = freq_table + (k * freq_table_size); clkp->freq_table[nr_divs].frequency = CPUFREQ_TABLE_END; @@ -147,6 +190,17 @@ int __init sh_clk_div6_register(struct clk *clks, int nr) return ret; } +int __init sh_clk_div6_register(struct clk *clks, int nr) +{ + return sh_clk_div6_register_ops(clks, nr, &sh_clk_div6_clk_ops); +} + +int __init sh_clk_div6_reparent_register(struct clk *clks, int nr) +{ + return sh_clk_div6_register_ops(clks, nr, + &sh_clk_div6_reparent_clk_ops); +} + static unsigned long sh_clk_div4_recalc(struct clk *clk) { struct clk_div4_table *d4t = clk->priv; diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index a9ca72f301bf..a1e9406b5afa 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -1896,6 +1896,13 @@ config FB_W100 If unsure, say N. +config SH_MIPI_DSI + tristate + depends on (SUPERH || ARCH_SHMOBILE) && HAVE_CLK + +config SH_LCD_MIPI_DSI + bool + config FB_SH_MOBILE_LCDC tristate "SuperH Mobile LCDC framebuffer support" depends on FB && (SUPERH || ARCH_SHMOBILE) && HAVE_CLK @@ -1904,9 +1911,17 @@ config FB_SH_MOBILE_LCDC select FB_SYS_IMAGEBLIT select FB_SYS_FOPS select FB_DEFERRED_IO + select SH_MIPI_DSI if SH_LCD_MIPI_DSI ---help--- Frame buffer driver for the on-chip SH-Mobile LCD controller. +config FB_SH_MOBILE_HDMI + tristate "SuperH Mobile HDMI controller support" + depends on FB_SH_MOBILE_LCDC + select FB_MODE_HELPERS + ---help--- + Driver for the on-chip SH-Mobile HDMI controller. + config FB_TMIO tristate "Toshiba Mobile IO FrameBuffer support" depends on FB && MFD_CORE diff --git a/drivers/video/Makefile b/drivers/video/Makefile index f56a9cae2157..485e8ed1318c 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -123,6 +123,8 @@ obj-$(CONFIG_FB_IBM_GXT4500) += gxt4500.o obj-$(CONFIG_FB_PS3) += ps3fb.o obj-$(CONFIG_FB_SM501) += sm501fb.o obj-$(CONFIG_FB_XILINX) += xilinxfb.o +obj-$(CONFIG_SH_MIPI_DSI) += sh_mipi_dsi.o +obj-$(CONFIG_FB_SH_MOBILE_HDMI) += sh_mobile_hdmi.o obj-$(CONFIG_FB_SH_MOBILE_LCDC) += sh_mobile_lcdcfb.o obj-$(CONFIG_FB_OMAP) += omap/ obj-y += omap2/ diff --git a/drivers/video/sh_mipi_dsi.c b/drivers/video/sh_mipi_dsi.c new file mode 100644 index 000000000000..5699ce0c1780 --- /dev/null +++ b/drivers/video/sh_mipi_dsi.c @@ -0,0 +1,505 @@ +/* + * Renesas SH-mobile MIPI DSI support + * + * Copyright (C) 2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * This is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <video/mipi_display.h> +#include <video/sh_mipi_dsi.h> +#include <video/sh_mobile_lcdc.h> + +#define CMTSRTCTR 0x80d0 +#define CMTSRTREQ 0x8070 + +#define DSIINTE 0x0060 + +/* E.g., sh7372 has 2 MIPI-DSIs - one for each LCDC */ +#define MAX_SH_MIPI_DSI 2 + +struct sh_mipi { + void __iomem *base; + struct clk *dsit_clk; + struct clk *dsip_clk; +}; + +static struct sh_mipi *mipi_dsi[MAX_SH_MIPI_DSI]; + +/* Protect the above array */ +static DEFINE_MUTEX(array_lock); + +static struct sh_mipi *sh_mipi_by_handle(int handle) +{ + if (handle >= ARRAY_SIZE(mipi_dsi) || handle < 0) + return NULL; + + return mipi_dsi[handle]; +} + +static int sh_mipi_send_short(struct sh_mipi *mipi, u8 dsi_cmd, + u8 cmd, u8 param) +{ + u32 data = (dsi_cmd << 24) | (cmd << 16) | (param << 8); + int cnt = 100; + + /* transmit a short packet to LCD panel */ + iowrite32(1 | data, mipi->base + 0x80d0); /* CMTSRTCTR */ + iowrite32(1, mipi->base + 0x8070); /* CMTSRTREQ */ + + while ((ioread32(mipi->base + 0x8070) & 1) && --cnt) + udelay(1); + + return cnt ? 0 : -ETIMEDOUT; +} + +#define LCD_CHAN2MIPI(c) ((c) < LCDC_CHAN_MAINLCD || (c) > LCDC_CHAN_SUBLCD ? \ + -EINVAL : (c) - 1) + +static int sh_mipi_dcs(int handle, u8 cmd) +{ + struct sh_mipi *mipi = sh_mipi_by_handle(LCD_CHAN2MIPI(handle)); + if (!mipi) + return -ENODEV; + return sh_mipi_send_short(mipi, MIPI_DSI_DCS_SHORT_WRITE, cmd, 0); +} + +static int sh_mipi_dcs_param(int handle, u8 cmd, u8 param) +{ + struct sh_mipi *mipi = sh_mipi_by_handle(LCD_CHAN2MIPI(handle)); + if (!mipi) + return -ENODEV; + return sh_mipi_send_short(mipi, MIPI_DSI_DCS_SHORT_WRITE_PARAM, cmd, + param); +} + +static void sh_mipi_dsi_enable(struct sh_mipi *mipi, bool enable) +{ + /* + * enable LCDC data tx, transition to LPS after completion of each HS + * packet + */ + iowrite32(0x00000002 | enable, mipi->base + 0x8000); /* DTCTR */ +} + +static void sh_mipi_shutdown(struct platform_device *pdev) +{ + struct sh_mipi *mipi = platform_get_drvdata(pdev); + + sh_mipi_dsi_enable(mipi, false); +} + +static void mipi_display_on(void *arg, struct fb_info *info) +{ + struct sh_mipi *mipi = arg; + + sh_mipi_dsi_enable(mipi, true); +} + +static void mipi_display_off(void *arg) +{ + struct sh_mipi *mipi = arg; + + sh_mipi_dsi_enable(mipi, false); +} + +static int __init sh_mipi_setup(struct sh_mipi *mipi, + struct sh_mipi_dsi_info *pdata) +{ + void __iomem *base = mipi->base; + struct sh_mobile_lcdc_chan_cfg *ch = pdata->lcd_chan; + u32 pctype, datatype, pixfmt; + u32 linelength; + bool yuv; + + /* Select data format */ + switch (pdata->data_format) { + case MIPI_RGB888: + pctype = 0; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_24; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = ch->lcd_cfg.xres * 3; + yuv = false; + break; + case MIPI_RGB565: + pctype = 1; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = ch->lcd_cfg.xres * 2; + yuv = false; + break; + case MIPI_RGB666_LP: + pctype = 2; + datatype = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = ch->lcd_cfg.xres * 3; + yuv = false; + break; + case MIPI_RGB666: + pctype = 3; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_18; + pixfmt = MIPI_DCS_PIXEL_FMT_18BIT; + linelength = (ch->lcd_cfg.xres * 18 + 7) / 8; + yuv = false; + break; + case MIPI_BGR888: + pctype = 8; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_24; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = ch->lcd_cfg.xres * 3; + yuv = false; + break; + case MIPI_BGR565: + pctype = 9; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = ch->lcd_cfg.xres * 2; + yuv = false; + break; + case MIPI_BGR666_LP: + pctype = 0xa; + datatype = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = ch->lcd_cfg.xres * 3; + yuv = false; + break; + case MIPI_BGR666: + pctype = 0xb; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_18; + pixfmt = MIPI_DCS_PIXEL_FMT_18BIT; + linelength = (ch->lcd_cfg.xres * 18 + 7) / 8; + yuv = false; + break; + case MIPI_YUYV: + pctype = 4; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = ch->lcd_cfg.xres * 2; + yuv = true; + break; + case MIPI_UYVY: + pctype = 5; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = ch->lcd_cfg.xres * 2; + yuv = true; + break; + case MIPI_YUV420_L: + pctype = 6; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR12; + pixfmt = MIPI_DCS_PIXEL_FMT_12BIT; + linelength = (ch->lcd_cfg.xres * 12 + 7) / 8; + yuv = true; + break; + case MIPI_YUV420: + pctype = 7; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR12; + pixfmt = MIPI_DCS_PIXEL_FMT_12BIT; + /* Length of U/V line */ + linelength = (ch->lcd_cfg.xres + 1) / 2; + yuv = true; + break; + default: + return -EINVAL; + } + + if ((yuv && ch->interface_type != YUV422) || + (!yuv && ch->interface_type != RGB24)) + return -EINVAL; + + /* reset DSI link */ + iowrite32(0x00000001, base); /* SYSCTRL */ + /* Hold reset for 100 cycles of the slowest of bus, HS byte and LP clock */ + udelay(50); + iowrite32(0x00000000, base); /* SYSCTRL */ + + /* setup DSI link */ + + /* + * Default = ULPS enable | + * Contention detection enabled | + * EoT packet transmission enable | + * CRC check enable | + * ECC check enable + * additionally enable first two lanes + */ + iowrite32(0x00003703, base + 0x04); /* SYSCONF */ + /* + * T_wakeup = 0x7000 + * T_hs-trail = 3 + * T_hs-prepare = 3 + * T_clk-trail = 3 + * T_clk-prepare = 2 + */ + iowrite32(0x70003332, base + 0x08); /* TIMSET */ + /* no responses requested */ + iowrite32(0x00000000, base + 0x18); /* RESREQSET0 */ + /* request response to packets of type 0x28 */ + iowrite32(0x00000100, base + 0x1c); /* RESREQSET1 */ + /* High-speed transmission timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + 0x20); /* HSTTOVSET */ + /* LP reception timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + 0x24); /* LPRTOVSET */ + /* Turn-around timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + 0x28); /* TATOVSET */ + /* Peripheral reset timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + 0x2c); /* PRTOVSET */ + /* Enable timeout counters */ + iowrite32(0x00000f00, base + 0x30); /* DSICTRL */ + /* Interrupts not used, disable all */ + iowrite32(0, base + DSIINTE); + /* DSI-Tx bias on */ + iowrite32(0x00000001, base + 0x70); /* PHYCTRL */ + udelay(200); + /* Deassert resets, power on, set multiplier */ + iowrite32(0x03070b01, base + 0x70); /* PHYCTRL */ + + /* setup l-bridge */ + + /* + * Enable transmission of all packets, + * transmit LPS after each HS packet completion + */ + iowrite32(0x00000006, base + 0x8000); /* DTCTR */ + /* VSYNC width = 2 (<< 17) */ + iowrite32(0x00040000 | (pctype << 12) | datatype, base + 0x8020); /* VMCTR1 */ + /* + * Non-burst mode with sync pulses: VSE and HSE are output, + * HSA period allowed, no commands in LP + */ + iowrite32(0x00e00000, base + 0x8024); /* VMCTR2 */ + /* + * 0x660 = 1632 bytes per line (RGB24, 544 pixels: see + * sh_mobile_lcdc_info.ch[0].lcd_cfg.xres), HSALEN = 1 - default + * (unused, since VMCTR2[HSABM] = 0) + */ + iowrite32(1 | (linelength << 16), base + 0x8028); /* VMLEN1 */ + + msleep(5); + + /* setup LCD panel */ + + /* cf. drivers/video/omap/lcd_mipid.c */ + sh_mipi_dcs(ch->chan, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(120); + /* + * [7] - Page Address Mode + * [6] - Column Address Mode + * [5] - Page / Column Address Mode + * [4] - Display Device Line Refresh Order + * [3] - RGB/BGR Order + * [2] - Display Data Latch Data Order + * [1] - Flip Horizontal + * [0] - Flip Vertical + */ + sh_mipi_dcs_param(ch->chan, MIPI_DCS_SET_ADDRESS_MODE, 0x00); + /* cf. set_data_lines() */ + sh_mipi_dcs_param(ch->chan, MIPI_DCS_SET_PIXEL_FORMAT, + pixfmt << 4); + sh_mipi_dcs(ch->chan, MIPI_DCS_SET_DISPLAY_ON); + + return 0; +} + +static int __init sh_mipi_probe(struct platform_device *pdev) +{ + struct sh_mipi *mipi; + struct sh_mipi_dsi_info *pdata = pdev->dev.platform_data; + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + unsigned long rate, f_current; + int idx = pdev->id, ret; + char dsip_clk[] = "dsi.p_clk"; + + if (!res || idx >= ARRAY_SIZE(mipi_dsi) || !pdata) + return -ENODEV; + + mutex_lock(&array_lock); + if (idx < 0) + for (idx = 0; idx < ARRAY_SIZE(mipi_dsi) && mipi_dsi[idx]; idx++) + ; + + if (idx == ARRAY_SIZE(mipi_dsi)) { + ret = -EBUSY; + goto efindslot; + } + + mipi = kzalloc(sizeof(*mipi), GFP_KERNEL); + if (!mipi) { + ret = -ENOMEM; + goto ealloc; + } + + if (!request_mem_region(res->start, resource_size(res), pdev->name)) { + dev_err(&pdev->dev, "MIPI register region already claimed\n"); + ret = -EBUSY; + goto ereqreg; + } + + mipi->base = ioremap(res->start, resource_size(res)); + if (!mipi->base) { + ret = -ENOMEM; + goto emap; + } + + mipi->dsit_clk = clk_get(&pdev->dev, "dsit_clk"); + if (IS_ERR(mipi->dsit_clk)) { + ret = PTR_ERR(mipi->dsit_clk); + goto eclktget; + } + + f_current = clk_get_rate(mipi->dsit_clk); + /* 80MHz required by the datasheet */ + rate = clk_round_rate(mipi->dsit_clk, 80000000); + if (rate > 0 && rate != f_current) + ret = clk_set_rate(mipi->dsit_clk, rate); + else + ret = rate; + if (ret < 0) + goto esettrate; + + dev_dbg(&pdev->dev, "DSI-T clk %lu -> %lu\n", f_current, rate); + + sprintf(dsip_clk, "dsi%1.1dp_clk", idx); + mipi->dsip_clk = clk_get(&pdev->dev, dsip_clk); + if (IS_ERR(mipi->dsip_clk)) { + ret = PTR_ERR(mipi->dsip_clk); + goto eclkpget; + } + + f_current = clk_get_rate(mipi->dsip_clk); + /* Between 10 and 50MHz */ + rate = clk_round_rate(mipi->dsip_clk, 24000000); + if (rate > 0 && rate != f_current) + ret = clk_set_rate(mipi->dsip_clk, rate); + else + ret = rate; + if (ret < 0) + goto esetprate; + + dev_dbg(&pdev->dev, "DSI-P clk %lu -> %lu\n", f_current, rate); + + msleep(10); + + ret = clk_enable(mipi->dsit_clk); + if (ret < 0) + goto eclkton; + + ret = clk_enable(mipi->dsip_clk); + if (ret < 0) + goto eclkpon; + + mipi_dsi[idx] = mipi; + + ret = sh_mipi_setup(mipi, pdata); + if (ret < 0) + goto emipisetup; + + mutex_unlock(&array_lock); + platform_set_drvdata(pdev, mipi); + + /* Set up LCDC callbacks */ + pdata->lcd_chan->board_cfg.board_data = mipi; + pdata->lcd_chan->board_cfg.display_on = mipi_display_on; + pdata->lcd_chan->board_cfg.display_off = mipi_display_off; + + return 0; + +emipisetup: + mipi_dsi[idx] = NULL; + clk_disable(mipi->dsip_clk); +eclkpon: + clk_disable(mipi->dsit_clk); +eclkton: +esetprate: + clk_put(mipi->dsip_clk); +eclkpget: +esettrate: + clk_put(mipi->dsit_clk); +eclktget: + iounmap(mipi->base); +emap: + release_mem_region(res->start, resource_size(res)); +ereqreg: + kfree(mipi); +ealloc: +efindslot: + mutex_unlock(&array_lock); + + return ret; +} + +static int __exit sh_mipi_remove(struct platform_device *pdev) +{ + struct sh_mipi_dsi_info *pdata = pdev->dev.platform_data; + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct sh_mipi *mipi = platform_get_drvdata(pdev); + int i, ret; + + mutex_lock(&array_lock); + + for (i = 0; i < ARRAY_SIZE(mipi_dsi) && mipi_dsi[i] != mipi; i++) + ; + + if (i == ARRAY_SIZE(mipi_dsi)) { + ret = -EINVAL; + } else { + ret = 0; + mipi_dsi[i] = NULL; + } + + mutex_unlock(&array_lock); + + if (ret < 0) + return ret; + + pdata->lcd_chan->board_cfg.display_on = NULL; + pdata->lcd_chan->board_cfg.display_off = NULL; + pdata->lcd_chan->board_cfg.board_data = NULL; + + clk_disable(mipi->dsip_clk); + clk_disable(mipi->dsit_clk); + clk_put(mipi->dsit_clk); + clk_put(mipi->dsip_clk); + iounmap(mipi->base); + if (res) + release_mem_region(res->start, resource_size(res)); + platform_set_drvdata(pdev, NULL); + kfree(mipi); + + return 0; +} + +static struct platform_driver sh_mipi_driver = { + .remove = __exit_p(sh_mipi_remove), + .shutdown = sh_mipi_shutdown, + .driver = { + .name = "sh-mipi-dsi", + }, +}; + +static int __init sh_mipi_init(void) +{ + return platform_driver_probe(&sh_mipi_driver, sh_mipi_probe); +} +module_init(sh_mipi_init); + +static void __exit sh_mipi_exit(void) +{ + platform_driver_unregister(&sh_mipi_driver); +} +module_exit(sh_mipi_exit); + +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_DESCRIPTION("SuperH / ARM-shmobile MIPI DSI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/sh_mobile_hdmi.c b/drivers/video/sh_mobile_hdmi.c new file mode 100644 index 000000000000..2fde08cc66bf --- /dev/null +++ b/drivers/video/sh_mobile_hdmi.c @@ -0,0 +1,1028 @@ +/* + * SH-Mobile High-Definition Multimedia Interface (HDMI) driver + * for SLISHDMI13T and SLIPHDMIT IP cores + * + * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * 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/clk.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include <video/sh_mobile_hdmi.h> +#include <video/sh_mobile_lcdc.h> + +#define HDMI_SYSTEM_CTRL 0x00 /* System control */ +#define HDMI_L_R_DATA_SWAP_CTRL_RPKT 0x01 /* L/R data swap control, + bits 19..16 of 20-bit N for Audio Clock Regeneration packet */ +#define HDMI_20_BIT_N_FOR_AUDIO_RPKT_15_8 0x02 /* bits 15..8 of 20-bit N for Audio Clock Regeneration packet */ +#define HDMI_20_BIT_N_FOR_AUDIO_RPKT_7_0 0x03 /* bits 7..0 of 20-bit N for Audio Clock Regeneration packet */ +#define HDMI_SPDIF_AUDIO_SAMP_FREQ_CTS 0x04 /* SPDIF audio sampling frequency, + bits 19..16 of Internal CTS */ +#define HDMI_INTERNAL_CTS_15_8 0x05 /* bits 15..8 of Internal CTS */ +#define HDMI_INTERNAL_CTS_7_0 0x06 /* bits 7..0 of Internal CTS */ +#define HDMI_EXTERNAL_CTS_19_16 0x07 /* External CTS */ +#define HDMI_EXTERNAL_CTS_15_8 0x08 /* External CTS */ +#define HDMI_EXTERNAL_CTS_7_0 0x09 /* External CTS */ +#define HDMI_AUDIO_SETTING_1 0x0A /* Audio setting.1 */ +#define HDMI_AUDIO_SETTING_2 0x0B /* Audio setting.2 */ +#define HDMI_I2S_AUDIO_SET 0x0C /* I2S audio setting */ +#define HDMI_DSD_AUDIO_SET 0x0D /* DSD audio setting */ +#define HDMI_DEBUG_MONITOR_1 0x0E /* Debug monitor.1 */ +#define HDMI_DEBUG_MONITOR_2 0x0F /* Debug monitor.2 */ +#define HDMI_I2S_INPUT_PIN_SWAP 0x10 /* I2S input pin swap */ +#define HDMI_AUDIO_STATUS_BITS_SETTING_1 0x11 /* Audio status bits setting.1 */ +#define HDMI_AUDIO_STATUS_BITS_SETTING_2 0x12 /* Audio status bits setting.2 */ +#define HDMI_CATEGORY_CODE 0x13 /* Category code */ +#define HDMI_SOURCE_NUM_AUDIO_WORD_LEN 0x14 /* Source number/Audio word length */ +#define HDMI_AUDIO_VIDEO_SETTING_1 0x15 /* Audio/Video setting.1 */ +#define HDMI_VIDEO_SETTING_1 0x16 /* Video setting.1 */ +#define HDMI_DEEP_COLOR_MODES 0x17 /* Deep Color Modes */ + +/* 12 16- and 10-bit Color space conversion parameters: 0x18..0x2f */ +#define HDMI_COLOR_SPACE_CONVERSION_PARAMETERS 0x18 + +#define HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS 0x30 /* External video parameter settings */ +#define HDMI_EXTERNAL_H_TOTAL_7_0 0x31 /* External horizontal total (LSB) */ +#define HDMI_EXTERNAL_H_TOTAL_11_8 0x32 /* External horizontal total (MSB) */ +#define HDMI_EXTERNAL_H_BLANK_7_0 0x33 /* External horizontal blank (LSB) */ +#define HDMI_EXTERNAL_H_BLANK_9_8 0x34 /* External horizontal blank (MSB) */ +#define HDMI_EXTERNAL_H_DELAY_7_0 0x35 /* External horizontal delay (LSB) */ +#define HDMI_EXTERNAL_H_DELAY_9_8 0x36 /* External horizontal delay (MSB) */ +#define HDMI_EXTERNAL_H_DURATION_7_0 0x37 /* External horizontal duration (LSB) */ +#define HDMI_EXTERNAL_H_DURATION_9_8 0x38 /* External horizontal duration (MSB) */ +#define HDMI_EXTERNAL_V_TOTAL_7_0 0x39 /* External vertical total (LSB) */ +#define HDMI_EXTERNAL_V_TOTAL_9_8 0x3A /* External vertical total (MSB) */ +#define HDMI_AUDIO_VIDEO_SETTING_2 0x3B /* Audio/Video setting.2 */ +#define HDMI_EXTERNAL_V_BLANK 0x3D /* External vertical blank */ +#define HDMI_EXTERNAL_V_DELAY 0x3E /* External vertical delay */ +#define HDMI_EXTERNAL_V_DURATION 0x3F /* External vertical duration */ +#define HDMI_CTRL_PKT_MANUAL_SEND_CONTROL 0x40 /* Control packet manual send control */ +#define HDMI_CTRL_PKT_AUTO_SEND 0x41 /* Control packet auto send with VSYNC control */ +#define HDMI_AUTO_CHECKSUM_OPTION 0x42 /* Auto checksum option */ +#define HDMI_VIDEO_SETTING_2 0x45 /* Video setting.2 */ +#define HDMI_OUTPUT_OPTION 0x46 /* Output option */ +#define HDMI_SLIPHDMIT_PARAM_OPTION 0x51 /* SLIPHDMIT parameter option */ +#define HDMI_HSYNC_PMENT_AT_EMB_7_0 0x52 /* HSYNC placement at embedded sync (LSB) */ +#define HDMI_HSYNC_PMENT_AT_EMB_15_8 0x53 /* HSYNC placement at embedded sync (MSB) */ +#define HDMI_VSYNC_PMENT_AT_EMB_7_0 0x54 /* VSYNC placement at embedded sync (LSB) */ +#define HDMI_VSYNC_PMENT_AT_EMB_14_8 0x55 /* VSYNC placement at embedded sync (MSB) */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_1 0x56 /* SLIPHDMIT parameter settings.1 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_2 0x57 /* SLIPHDMIT parameter settings.2 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_3 0x58 /* SLIPHDMIT parameter settings.3 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_5 0x59 /* SLIPHDMIT parameter settings.5 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_6 0x5A /* SLIPHDMIT parameter settings.6 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_7 0x5B /* SLIPHDMIT parameter settings.7 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_8 0x5C /* SLIPHDMIT parameter settings.8 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_9 0x5D /* SLIPHDMIT parameter settings.9 */ +#define HDMI_SLIPHDMIT_PARAM_SETTINGS_10 0x5E /* SLIPHDMIT parameter settings.10 */ +#define HDMI_CTRL_PKT_BUF_INDEX 0x5F /* Control packet buffer index */ +#define HDMI_CTRL_PKT_BUF_ACCESS_HB0 0x60 /* Control packet data buffer access window - HB0 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_HB1 0x61 /* Control packet data buffer access window - HB1 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_HB2 0x62 /* Control packet data buffer access window - HB2 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB0 0x63 /* Control packet data buffer access window - PB0 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB1 0x64 /* Control packet data buffer access window - PB1 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB2 0x65 /* Control packet data buffer access window - PB2 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB3 0x66 /* Control packet data buffer access window - PB3 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB4 0x67 /* Control packet data buffer access window - PB4 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB5 0x68 /* Control packet data buffer access window - PB5 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB6 0x69 /* Control packet data buffer access window - PB6 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB7 0x6A /* Control packet data buffer access window - PB7 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB8 0x6B /* Control packet data buffer access window - PB8 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB9 0x6C /* Control packet data buffer access window - PB9 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB10 0x6D /* Control packet data buffer access window - PB10 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB11 0x6E /* Control packet data buffer access window - PB11 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB12 0x6F /* Control packet data buffer access window - PB12 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB13 0x70 /* Control packet data buffer access window - PB13 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB14 0x71 /* Control packet data buffer access window - PB14 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB15 0x72 /* Control packet data buffer access window - PB15 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB16 0x73 /* Control packet data buffer access window - PB16 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB17 0x74 /* Control packet data buffer access window - PB17 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB18 0x75 /* Control packet data buffer access window - PB18 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB19 0x76 /* Control packet data buffer access window - PB19 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB20 0x77 /* Control packet data buffer access window - PB20 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB21 0x78 /* Control packet data buffer access window - PB21 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB22 0x79 /* Control packet data buffer access window - PB22 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB23 0x7A /* Control packet data buffer access window - PB23 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB24 0x7B /* Control packet data buffer access window - PB24 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB25 0x7C /* Control packet data buffer access window - PB25 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB26 0x7D /* Control packet data buffer access window - PB26 */ +#define HDMI_CTRL_PKT_BUF_ACCESS_PB27 0x7E /* Control packet data buffer access window - PB27 */ +#define HDMI_EDID_KSV_FIFO_ACCESS_WINDOW 0x80 /* EDID/KSV FIFO access window */ +#define HDMI_DDC_BUS_ACCESS_FREQ_CTRL_7_0 0x81 /* DDC bus access frequency control (LSB) */ +#define HDMI_DDC_BUS_ACCESS_FREQ_CTRL_15_8 0x82 /* DDC bus access frequency control (MSB) */ +#define HDMI_INTERRUPT_MASK_1 0x92 /* Interrupt mask.1 */ +#define HDMI_INTERRUPT_MASK_2 0x93 /* Interrupt mask.2 */ +#define HDMI_INTERRUPT_STATUS_1 0x94 /* Interrupt status.1 */ +#define HDMI_INTERRUPT_STATUS_2 0x95 /* Interrupt status.2 */ +#define HDMI_INTERRUPT_MASK_3 0x96 /* Interrupt mask.3 */ +#define HDMI_INTERRUPT_MASK_4 0x97 /* Interrupt mask.4 */ +#define HDMI_INTERRUPT_STATUS_3 0x98 /* Interrupt status.3 */ +#define HDMI_INTERRUPT_STATUS_4 0x99 /* Interrupt status.4 */ +#define HDMI_SOFTWARE_HDCP_CONTROL_1 0x9A /* Software HDCP control.1 */ +#define HDMI_FRAME_COUNTER 0x9C /* Frame counter */ +#define HDMI_FRAME_COUNTER_FOR_RI_CHECK 0x9D /* Frame counter for Ri check */ +#define HDMI_HDCP_CONTROL 0xAF /* HDCP control */ +#define HDMI_RI_FRAME_COUNT_REGISTER 0xB2 /* Ri frame count register */ +#define HDMI_DDC_BUS_CONTROL 0xB7 /* DDC bus control */ +#define HDMI_HDCP_STATUS 0xB8 /* HDCP status */ +#define HDMI_SHA0 0xB9 /* sha0 */ +#define HDMI_SHA1 0xBA /* sha1 */ +#define HDMI_SHA2 0xBB /* sha2 */ +#define HDMI_SHA3 0xBC /* sha3 */ +#define HDMI_SHA4 0xBD /* sha4 */ +#define HDMI_BCAPS_READ 0xBE /* BCAPS read / debug */ +#define HDMI_AKSV_BKSV_7_0_MONITOR 0xBF /* AKSV/BKSV[7:0] monitor */ +#define HDMI_AKSV_BKSV_15_8_MONITOR 0xC0 /* AKSV/BKSV[15:8] monitor */ +#define HDMI_AKSV_BKSV_23_16_MONITOR 0xC1 /* AKSV/BKSV[23:16] monitor */ +#define HDMI_AKSV_BKSV_31_24_MONITOR 0xC2 /* AKSV/BKSV[31:24] monitor */ +#define HDMI_AKSV_BKSV_39_32_MONITOR 0xC3 /* AKSV/BKSV[39:32] monitor */ +#define HDMI_EDID_SEGMENT_POINTER 0xC4 /* EDID segment pointer */ +#define HDMI_EDID_WORD_ADDRESS 0xC5 /* EDID word address */ +#define HDMI_EDID_DATA_FIFO_ADDRESS 0xC6 /* EDID data FIFO address */ +#define HDMI_NUM_OF_HDMI_DEVICES 0xC7 /* Number of HDMI devices */ +#define HDMI_HDCP_ERROR_CODE 0xC8 /* HDCP error code */ +#define HDMI_100MS_TIMER_SET 0xC9 /* 100ms timer setting */ +#define HDMI_5SEC_TIMER_SET 0xCA /* 5sec timer setting */ +#define HDMI_RI_READ_COUNT 0xCB /* Ri read count */ +#define HDMI_AN_SEED 0xCC /* An seed */ +#define HDMI_MAX_NUM_OF_RCIVRS_ALLOWED 0xCD /* Maximum number of receivers allowed */ +#define HDMI_HDCP_MEMORY_ACCESS_CONTROL_1 0xCE /* HDCP memory access control.1 */ +#define HDMI_HDCP_MEMORY_ACCESS_CONTROL_2 0xCF /* HDCP memory access control.2 */ +#define HDMI_HDCP_CONTROL_2 0xD0 /* HDCP Control 2 */ +#define HDMI_HDCP_KEY_MEMORY_CONTROL 0xD2 /* HDCP Key Memory Control */ +#define HDMI_COLOR_SPACE_CONV_CONFIG_1 0xD3 /* Color space conversion configuration.1 */ +#define HDMI_VIDEO_SETTING_3 0xD4 /* Video setting.3 */ +#define HDMI_RI_7_0 0xD5 /* Ri[7:0] */ +#define HDMI_RI_15_8 0xD6 /* Ri[15:8] */ +#define HDMI_PJ 0xD7 /* Pj */ +#define HDMI_SHA_RD 0xD8 /* sha_rd */ +#define HDMI_RI_7_0_SAVED 0xD9 /* Ri[7:0] saved */ +#define HDMI_RI_15_8_SAVED 0xDA /* Ri[15:8] saved */ +#define HDMI_PJ_SAVED 0xDB /* Pj saved */ +#define HDMI_NUM_OF_DEVICES 0xDC /* Number of devices */ +#define HDMI_HOT_PLUG_MSENS_STATUS 0xDF /* Hot plug/MSENS status */ +#define HDMI_BCAPS_WRITE 0xE0 /* bcaps */ +#define HDMI_BSTAT_7_0 0xE1 /* bstat[7:0] */ +#define HDMI_BSTAT_15_8 0xE2 /* bstat[15:8] */ +#define HDMI_BKSV_7_0 0xE3 /* bksv[7:0] */ +#define HDMI_BKSV_15_8 0xE4 /* bksv[15:8] */ +#define HDMI_BKSV_23_16 0xE5 /* bksv[23:16] */ +#define HDMI_BKSV_31_24 0xE6 /* bksv[31:24] */ +#define HDMI_BKSV_39_32 0xE7 /* bksv[39:32] */ +#define HDMI_AN_7_0 0xE8 /* An[7:0] */ +#define HDMI_AN_15_8 0xE9 /* An [15:8] */ +#define HDMI_AN_23_16 0xEA /* An [23:16] */ +#define HDMI_AN_31_24 0xEB /* An [31:24] */ +#define HDMI_AN_39_32 0xEC /* An [39:32] */ +#define HDMI_AN_47_40 0xED /* An [47:40] */ +#define HDMI_AN_55_48 0xEE /* An [55:48] */ +#define HDMI_AN_63_56 0xEF /* An [63:56] */ +#define HDMI_PRODUCT_ID 0xF0 /* Product ID */ +#define HDMI_REVISION_ID 0xF1 /* Revision ID */ +#define HDMI_TEST_MODE 0xFE /* Test mode */ + +enum hotplug_state { + HDMI_HOTPLUG_DISCONNECTED, + HDMI_HOTPLUG_CONNECTED, + HDMI_HOTPLUG_EDID_DONE, +}; + +struct sh_hdmi { + void __iomem *base; + enum hotplug_state hp_state; + struct clk *hdmi_clk; + struct device *dev; + struct fb_info *info; + struct delayed_work edid_work; + struct fb_var_screeninfo var; +}; + +static void hdmi_write(struct sh_hdmi *hdmi, u8 data, u8 reg) +{ + iowrite8(data, hdmi->base + reg); +} + +static u8 hdmi_read(struct sh_hdmi *hdmi, u8 reg) +{ + return ioread8(hdmi->base + reg); +} + +/* External video parameter settings */ +static void hdmi_external_video_param(struct sh_hdmi *hdmi) +{ + struct fb_var_screeninfo *var = &hdmi->var; + u16 htotal, hblank, hdelay, vtotal, vblank, vdelay, voffset; + u8 sync = 0; + + htotal = var->xres + var->right_margin + var->left_margin + var->hsync_len; + + hdelay = var->hsync_len + var->left_margin; + hblank = var->right_margin + hdelay; + + /* + * Vertical timing looks a bit different in Figure 18, + * but let's try the same first by setting offset = 0 + */ + vtotal = var->yres + var->upper_margin + var->lower_margin + var->vsync_len; + + vdelay = var->vsync_len + var->upper_margin; + vblank = var->lower_margin + vdelay; + voffset = min(var->upper_margin / 2, 6U); + + /* + * [3]: VSYNC polarity: Positive + * [2]: HSYNC polarity: Positive + * [1]: Interlace/Progressive: Progressive + * [0]: External video settings enable: used. + */ + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + sync |= 4; + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + sync |= 8; + + pr_debug("H: %u, %u, %u, %u; V: %u, %u, %u, %u; sync 0x%x\n", + htotal, hblank, hdelay, var->hsync_len, + vtotal, vblank, vdelay, var->vsync_len, sync); + + hdmi_write(hdmi, sync | (voffset << 4), HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS); + + hdmi_write(hdmi, htotal, HDMI_EXTERNAL_H_TOTAL_7_0); + hdmi_write(hdmi, htotal >> 8, HDMI_EXTERNAL_H_TOTAL_11_8); + + hdmi_write(hdmi, hblank, HDMI_EXTERNAL_H_BLANK_7_0); + hdmi_write(hdmi, hblank >> 8, HDMI_EXTERNAL_H_BLANK_9_8); + + hdmi_write(hdmi, hdelay, HDMI_EXTERNAL_H_DELAY_7_0); + hdmi_write(hdmi, hdelay >> 8, HDMI_EXTERNAL_H_DELAY_9_8); + + hdmi_write(hdmi, var->hsync_len, HDMI_EXTERNAL_H_DURATION_7_0); + hdmi_write(hdmi, var->hsync_len >> 8, HDMI_EXTERNAL_H_DURATION_9_8); + + hdmi_write(hdmi, vtotal, HDMI_EXTERNAL_V_TOTAL_7_0); + hdmi_write(hdmi, vtotal >> 8, HDMI_EXTERNAL_V_TOTAL_9_8); + + hdmi_write(hdmi, vblank, HDMI_EXTERNAL_V_BLANK); + + hdmi_write(hdmi, vdelay, HDMI_EXTERNAL_V_DELAY); + + hdmi_write(hdmi, var->vsync_len, HDMI_EXTERNAL_V_DURATION); + + /* Set bit 0 of HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS here for manual mode */ +} + +/** + * sh_hdmi_video_config() + */ +static void sh_hdmi_video_config(struct sh_hdmi *hdmi) +{ + /* + * [7:4]: Audio sampling frequency: 48kHz + * [3:1]: Input video format: RGB and YCbCr 4:4:4 (Y on Green) + * [0]: Internal/External DE select: internal + */ + hdmi_write(hdmi, 0x20, HDMI_AUDIO_VIDEO_SETTING_1); + + /* + * [7:6]: Video output format: RGB 4:4:4 + * [5:4]: Input video data width: 8 bit + * [3:1]: EAV/SAV location: channel 1 + * [0]: Video input color space: RGB + */ + hdmi_write(hdmi, 0x34, HDMI_VIDEO_SETTING_1); + + /* + * [7:6]: Together with bit [6] of HDMI_AUDIO_VIDEO_SETTING_2, which is + * left at 0 by default, this configures 24bpp and sets the Color Depth + * (CD) field in the General Control Packet + */ + hdmi_write(hdmi, 0x20, HDMI_DEEP_COLOR_MODES); +} + +/** + * sh_hdmi_audio_config() + */ +static void sh_hdmi_audio_config(struct sh_hdmi *hdmi) +{ + /* + * [7:4] L/R data swap control + * [3:0] appropriate N[19:16] + */ + hdmi_write(hdmi, 0x00, HDMI_L_R_DATA_SWAP_CTRL_RPKT); + /* appropriate N[15:8] */ + hdmi_write(hdmi, 0x18, HDMI_20_BIT_N_FOR_AUDIO_RPKT_15_8); + /* appropriate N[7:0] */ + hdmi_write(hdmi, 0x00, HDMI_20_BIT_N_FOR_AUDIO_RPKT_7_0); + + /* [7:4] 48 kHz SPDIF not used */ + hdmi_write(hdmi, 0x20, HDMI_SPDIF_AUDIO_SAMP_FREQ_CTS); + + /* + * [6:5] set required down sampling rate if required + * [4:3] set required audio source + */ + hdmi_write(hdmi, 0x00, HDMI_AUDIO_SETTING_1); + + /* [3:0] set sending channel number for channel status */ + hdmi_write(hdmi, 0x40, HDMI_AUDIO_SETTING_2); + + /* + * [5:2] set valid I2S source input pin + * [1:0] set input I2S source mode + */ + hdmi_write(hdmi, 0x04, HDMI_I2S_AUDIO_SET); + + /* [7:4] set valid DSD source input pin */ + hdmi_write(hdmi, 0x00, HDMI_DSD_AUDIO_SET); + + /* [7:0] set appropriate I2S input pin swap settings if required */ + hdmi_write(hdmi, 0x00, HDMI_I2S_INPUT_PIN_SWAP); + + /* + * [7] set validity bit for channel status + * [3:0] set original sample frequency for channel status + */ + hdmi_write(hdmi, 0x00, HDMI_AUDIO_STATUS_BITS_SETTING_1); + + /* + * [7] set value for channel status + * [6] set value for channel status + * [5] set copyright bit for channel status + * [4:2] set additional information for channel status + * [1:0] set clock accuracy for channel status + */ + hdmi_write(hdmi, 0x00, HDMI_AUDIO_STATUS_BITS_SETTING_2); + + /* [7:0] set category code for channel status */ + hdmi_write(hdmi, 0x00, HDMI_CATEGORY_CODE); + + /* + * [7:4] set source number for channel status + * [3:0] set word length for channel status + */ + hdmi_write(hdmi, 0x00, HDMI_SOURCE_NUM_AUDIO_WORD_LEN); + + /* [7:4] set sample frequency for channel status */ + hdmi_write(hdmi, 0x20, HDMI_AUDIO_VIDEO_SETTING_1); +} + +/** + * sh_hdmi_phy_config() + */ +static void sh_hdmi_phy_config(struct sh_hdmi *hdmi) +{ + /* 720p, 8bit, 74.25MHz. Might need to be adjusted for other formats */ + hdmi_write(hdmi, 0x19, HDMI_SLIPHDMIT_PARAM_SETTINGS_1); + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2); + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3); + /* PLLA_CONFIG[7:0]: VCO gain, VCO offset, LPF resistance[0] */ + hdmi_write(hdmi, 0x44, HDMI_SLIPHDMIT_PARAM_SETTINGS_5); + hdmi_write(hdmi, 0x32, HDMI_SLIPHDMIT_PARAM_SETTINGS_6); + hdmi_write(hdmi, 0x4A, HDMI_SLIPHDMIT_PARAM_SETTINGS_7); + hdmi_write(hdmi, 0x0E, HDMI_SLIPHDMIT_PARAM_SETTINGS_8); + hdmi_write(hdmi, 0x25, HDMI_SLIPHDMIT_PARAM_SETTINGS_9); + hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10); +} + +/** + * sh_hdmi_avi_infoframe_setup() - Auxiliary Video Information InfoFrame CONTROL PACKET + */ +static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi) +{ + /* AVI InfoFrame */ + hdmi_write(hdmi, 0x06, HDMI_CTRL_PKT_BUF_INDEX); + + /* Packet Type = 0x82 */ + hdmi_write(hdmi, 0x82, HDMI_CTRL_PKT_BUF_ACCESS_HB0); + + /* Version = 0x02 */ + hdmi_write(hdmi, 0x02, HDMI_CTRL_PKT_BUF_ACCESS_HB1); + + /* Length = 13 (0x0D) */ + hdmi_write(hdmi, 0x0D, HDMI_CTRL_PKT_BUF_ACCESS_HB2); + + /* N. A. Checksum */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0); + + /* + * Y = RGB + * A0 = No Data + * B = Bar Data not valid + * S = No Data + */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB1); + + /* + * C = No Data + * M = 16:9 Picture Aspect Ratio + * R = Same as picture aspect ratio + */ + hdmi_write(hdmi, 0x28, HDMI_CTRL_PKT_BUF_ACCESS_PB2); + + /* + * ITC = No Data + * EC = xvYCC601 + * Q = Default (depends on video format) + * SC = No Known non_uniform Scaling + */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB3); + + /* + * VIC = 1280 x 720p: ignored if external config is used + * Send 2 for 720 x 480p, 16 for 1080p + */ + hdmi_write(hdmi, 4, HDMI_CTRL_PKT_BUF_ACCESS_PB4); + + /* PR = No Repetition */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB5); + + /* Line Number of End of Top Bar (lower 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB6); + + /* Line Number of End of Top Bar (upper 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB7); + + /* Line Number of Start of Bottom Bar (lower 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB8); + + /* Line Number of Start of Bottom Bar (upper 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB9); + + /* Pixel Number of End of Left Bar (lower 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB10); + + /* Pixel Number of End of Left Bar (upper 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB11); + + /* Pixel Number of Start of Right Bar (lower 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB12); + + /* Pixel Number of Start of Right Bar (upper 8 bits) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB13); +} + +/** + * sh_hdmi_audio_infoframe_setup() - Audio InfoFrame of CONTROL PACKET + */ +static void sh_hdmi_audio_infoframe_setup(struct sh_hdmi *hdmi) +{ + /* Audio InfoFrame */ + hdmi_write(hdmi, 0x08, HDMI_CTRL_PKT_BUF_INDEX); + + /* Packet Type = 0x84 */ + hdmi_write(hdmi, 0x84, HDMI_CTRL_PKT_BUF_ACCESS_HB0); + + /* Version Number = 0x01 */ + hdmi_write(hdmi, 0x01, HDMI_CTRL_PKT_BUF_ACCESS_HB1); + + /* 0 Length = 10 (0x0A) */ + hdmi_write(hdmi, 0x0A, HDMI_CTRL_PKT_BUF_ACCESS_HB2); + + /* n. a. Checksum */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0); + + /* Audio Channel Count = Refer to Stream Header */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB1); + + /* Refer to Stream Header */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB2); + + /* Format depends on coding type (i.e. CT0...CT3) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB3); + + /* Speaker Channel Allocation = Front Right + Front Left */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB4); + + /* Level Shift Value = 0 dB, Down - mix is permitted or no information */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB5); + + /* Reserved (0) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB6); + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB7); + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB8); + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB9); + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB10); +} + +/** + * sh_hdmi_gamut_metadata_setup() - Gamut Metadata Packet of CONTROL PACKET + */ +static void sh_hdmi_gamut_metadata_setup(struct sh_hdmi *hdmi) +{ + int i; + + /* Gamut Metadata Packet */ + hdmi_write(hdmi, 0x04, HDMI_CTRL_PKT_BUF_INDEX); + + /* Packet Type = 0x0A */ + hdmi_write(hdmi, 0x0A, HDMI_CTRL_PKT_BUF_ACCESS_HB0); + /* Gamut Packet is not used, so default value */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1); + /* Gamut Packet is not used, so default value */ + hdmi_write(hdmi, 0x10, HDMI_CTRL_PKT_BUF_ACCESS_HB2); + + /* GBD bytes 0 through 27 */ + for (i = 0; i <= 27; i++) + /* HDMI_CTRL_PKT_BUF_ACCESS_PB0_63H - PB27_7EH */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i); +} + +/** + * sh_hdmi_acp_setup() - Audio Content Protection Packet (ACP) + */ +static void sh_hdmi_acp_setup(struct sh_hdmi *hdmi) +{ + int i; + + /* Audio Content Protection Packet (ACP) */ + hdmi_write(hdmi, 0x01, HDMI_CTRL_PKT_BUF_INDEX); + + /* Packet Type = 0x04 */ + hdmi_write(hdmi, 0x04, HDMI_CTRL_PKT_BUF_ACCESS_HB0); + /* ACP_Type */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1); + /* Reserved (0) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB2); + + /* GBD bytes 0 through 27 */ + for (i = 0; i <= 27; i++) + /* HDMI_CTRL_PKT_BUF_ACCESS_PB0 - PB27 */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i); +} + +/** + * sh_hdmi_isrc1_setup() - ISRC1 Packet + */ +static void sh_hdmi_isrc1_setup(struct sh_hdmi *hdmi) +{ + int i; + + /* ISRC1 Packet */ + hdmi_write(hdmi, 0x02, HDMI_CTRL_PKT_BUF_INDEX); + + /* Packet Type = 0x05 */ + hdmi_write(hdmi, 0x05, HDMI_CTRL_PKT_BUF_ACCESS_HB0); + /* ISRC_Cont, ISRC_Valid, Reserved (0), ISRC_Status */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1); + /* Reserved (0) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB2); + + /* PB0 UPC_EAN_ISRC_0-15 */ + /* Bytes PB16-PB27 shall be set to a value of 0. */ + for (i = 0; i <= 27; i++) + /* HDMI_CTRL_PKT_BUF_ACCESS_PB0 - PB27 */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i); +} + +/** + * sh_hdmi_isrc2_setup() - ISRC2 Packet + */ +static void sh_hdmi_isrc2_setup(struct sh_hdmi *hdmi) +{ + int i; + + /* ISRC2 Packet */ + hdmi_write(hdmi, 0x03, HDMI_CTRL_PKT_BUF_INDEX); + + /* HB0 Packet Type = 0x06 */ + hdmi_write(hdmi, 0x06, HDMI_CTRL_PKT_BUF_ACCESS_HB0); + /* Reserved (0) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1); + /* Reserved (0) */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB2); + + /* PB0 UPC_EAN_ISRC_16-31 */ + /* Bytes PB16-PB27 shall be set to a value of 0. */ + for (i = 0; i <= 27; i++) + /* HDMI_CTRL_PKT_BUF_ACCESS_PB0 - PB27 */ + hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i); +} + +/** + * sh_hdmi_configure() - Initialise HDMI for output + */ +static void sh_hdmi_configure(struct sh_hdmi *hdmi) +{ + /* Configure video format */ + sh_hdmi_video_config(hdmi); + + /* Configure audio format */ + sh_hdmi_audio_config(hdmi); + + /* Configure PHY */ + sh_hdmi_phy_config(hdmi); + + /* Auxiliary Video Information (AVI) InfoFrame */ + sh_hdmi_avi_infoframe_setup(hdmi); + + /* Audio InfoFrame */ + sh_hdmi_audio_infoframe_setup(hdmi); + + /* Gamut Metadata packet */ + sh_hdmi_gamut_metadata_setup(hdmi); + + /* Audio Content Protection (ACP) Packet */ + sh_hdmi_acp_setup(hdmi); + + /* ISRC1 Packet */ + sh_hdmi_isrc1_setup(hdmi); + + /* ISRC2 Packet */ + sh_hdmi_isrc2_setup(hdmi); + + /* + * Control packet auto send with VSYNC control: auto send + * General control, Gamut metadata, ISRC, and ACP packets + */ + hdmi_write(hdmi, 0x8E, HDMI_CTRL_PKT_AUTO_SEND); + + /* FIXME */ + msleep(10); + + /* PS mode b->d, reset PLLA and PLLB */ + hdmi_write(hdmi, 0x4C, HDMI_SYSTEM_CTRL); + + udelay(10); + + hdmi_write(hdmi, 0x40, HDMI_SYSTEM_CTRL); +} + +static void sh_hdmi_read_edid(struct sh_hdmi *hdmi) +{ + struct fb_var_screeninfo *var = &hdmi->var; + struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data; + struct fb_videomode *lcd_cfg = &pdata->lcd_chan->lcd_cfg; + unsigned long height = var->height, width = var->width; + int i; + u8 edid[128]; + + /* Read EDID */ + pr_debug("Read back EDID code:"); + for (i = 0; i < 128; i++) { + edid[i] = hdmi_read(hdmi, HDMI_EDID_KSV_FIFO_ACCESS_WINDOW); +#ifdef DEBUG + if ((i % 16) == 0) { + printk(KERN_CONT "\n"); + printk(KERN_DEBUG "%02X | %02X", i, edid[i]); + } else { + printk(KERN_CONT " %02X", edid[i]); + } +#endif + } +#ifdef DEBUG + printk(KERN_CONT "\n"); +#endif + fb_parse_edid(edid, var); + pr_debug("%u-%u-%u-%u x %u-%u-%u-%u @ %lu kHz monitor detected\n", + var->left_margin, var->xres, var->right_margin, var->hsync_len, + var->upper_margin, var->yres, var->lower_margin, var->vsync_len, + PICOS2KHZ(var->pixclock)); + + /* FIXME: Use user-provided configuration instead of EDID */ + var->width = width; + var->xres = lcd_cfg->xres; + var->xres_virtual = lcd_cfg->xres; + var->left_margin = lcd_cfg->left_margin; + var->right_margin = lcd_cfg->right_margin; + var->hsync_len = lcd_cfg->hsync_len; + var->height = height; + var->yres = lcd_cfg->yres; + var->yres_virtual = lcd_cfg->yres * 2; + var->upper_margin = lcd_cfg->upper_margin; + var->lower_margin = lcd_cfg->lower_margin; + var->vsync_len = lcd_cfg->vsync_len; + var->sync = lcd_cfg->sync; + var->pixclock = lcd_cfg->pixclock; + + hdmi_external_video_param(hdmi); +} + +static irqreturn_t sh_hdmi_hotplug(int irq, void *dev_id) +{ + struct sh_hdmi *hdmi = dev_id; + u8 status1, status2, mask1, mask2; + + /* mode_b and PLLA and PLLB reset */ + hdmi_write(hdmi, 0x2C, HDMI_SYSTEM_CTRL); + + /* How long shall reset be held? */ + udelay(10); + + /* mode_b and PLLA and PLLB reset release */ + hdmi_write(hdmi, 0x20, HDMI_SYSTEM_CTRL); + + status1 = hdmi_read(hdmi, HDMI_INTERRUPT_STATUS_1); + status2 = hdmi_read(hdmi, HDMI_INTERRUPT_STATUS_2); + + mask1 = hdmi_read(hdmi, HDMI_INTERRUPT_MASK_1); + mask2 = hdmi_read(hdmi, HDMI_INTERRUPT_MASK_2); + + /* Correct would be to ack only set bits, but the datasheet requires 0xff */ + hdmi_write(hdmi, 0xFF, HDMI_INTERRUPT_STATUS_1); + hdmi_write(hdmi, 0xFF, HDMI_INTERRUPT_STATUS_2); + + if (printk_ratelimit()) + pr_debug("IRQ #%d: Status #1: 0x%x & 0x%x, #2: 0x%x & 0x%x\n", + irq, status1, mask1, status2, mask2); + + if (!((status1 & mask1) | (status2 & mask2))) { + return IRQ_NONE; + } else if (status1 & 0xc0) { + u8 msens; + + /* Datasheet specifies 10ms... */ + udelay(500); + + msens = hdmi_read(hdmi, HDMI_HOT_PLUG_MSENS_STATUS); + pr_debug("MSENS 0x%x\n", msens); + /* Check, if hot plug & MSENS pin status are both high */ + if ((msens & 0xC0) == 0xC0) { + /* Display plug in */ + hdmi->hp_state = HDMI_HOTPLUG_CONNECTED; + + /* Set EDID word address */ + hdmi_write(hdmi, 0x00, HDMI_EDID_WORD_ADDRESS); + /* Set EDID segment pointer */ + hdmi_write(hdmi, 0x00, HDMI_EDID_SEGMENT_POINTER); + /* Enable EDID interrupt */ + hdmi_write(hdmi, 0xC6, HDMI_INTERRUPT_MASK_1); + } else if (!(status1 & 0x80)) { + /* Display unplug, beware multiple interrupts */ + if (hdmi->hp_state != HDMI_HOTPLUG_DISCONNECTED) + schedule_delayed_work(&hdmi->edid_work, 0); + + hdmi->hp_state = HDMI_HOTPLUG_DISCONNECTED; + /* display_off will switch back to mode_a */ + } + } else if (status1 & 2) { + /* EDID error interrupt: retry */ + /* Set EDID word address */ + hdmi_write(hdmi, 0x00, HDMI_EDID_WORD_ADDRESS); + /* Set EDID segment pointer */ + hdmi_write(hdmi, 0x00, HDMI_EDID_SEGMENT_POINTER); + } else if (status1 & 4) { + /* Disable EDID interrupt */ + hdmi_write(hdmi, 0xC0, HDMI_INTERRUPT_MASK_1); + hdmi->hp_state = HDMI_HOTPLUG_EDID_DONE; + schedule_delayed_work(&hdmi->edid_work, msecs_to_jiffies(10)); + } + + return IRQ_HANDLED; +} + +static void hdmi_display_on(void *arg, struct fb_info *info) +{ + struct sh_hdmi *hdmi = arg; + struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data; + + if (info->var.xres != 1280 || info->var.yres != 720) { + dev_warn(info->device, "Unsupported framebuffer geometry %ux%u\n", + info->var.xres, info->var.yres); + return; + } + + pr_debug("%s(%p): state %x\n", __func__, pdata->lcd_dev, info->state); + /* + * FIXME: not a good place to store fb_info. And we cannot nullify it + * even on monitor disconnect. What should the lifecycle be? + */ + hdmi->info = info; + switch (hdmi->hp_state) { + case HDMI_HOTPLUG_EDID_DONE: + /* PS mode d->e. All functions are active */ + hdmi_write(hdmi, 0x80, HDMI_SYSTEM_CTRL); + pr_debug("HDMI running\n"); + break; + case HDMI_HOTPLUG_DISCONNECTED: + info->state = FBINFO_STATE_SUSPENDED; + default: + hdmi->var = info->var; + } +} + +static void hdmi_display_off(void *arg) +{ + struct sh_hdmi *hdmi = arg; + struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data; + + pr_debug("%s(%p)\n", __func__, pdata->lcd_dev); + /* PS mode e->a */ + hdmi_write(hdmi, 0x10, HDMI_SYSTEM_CTRL); +} + +/* Hotplug interrupt occurred, read EDID */ +static void edid_work_fn(struct work_struct *work) +{ + struct sh_hdmi *hdmi = container_of(work, struct sh_hdmi, edid_work.work); + struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data; + + pr_debug("%s(%p): begin, hotplug status %d\n", __func__, + pdata->lcd_dev, hdmi->hp_state); + + if (!pdata->lcd_dev) + return; + + if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) { + pm_runtime_get_sync(hdmi->dev); + /* A device has been plugged in */ + sh_hdmi_read_edid(hdmi); + msleep(10); + sh_hdmi_configure(hdmi); + /* Switched to another (d) power-save mode */ + msleep(10); + + if (!hdmi->info) + return; + + acquire_console_sem(); + + /* HDMI plug in */ + hdmi->info->var = hdmi->var; + if (hdmi->info->state != FBINFO_STATE_RUNNING) + fb_set_suspend(hdmi->info, 0); + else + hdmi_display_on(hdmi, hdmi->info); + + release_console_sem(); + } else { + if (!hdmi->info) + return; + + acquire_console_sem(); + + /* HDMI disconnect */ + fb_set_suspend(hdmi->info, 1); + + release_console_sem(); + pm_runtime_put(hdmi->dev); + } + + pr_debug("%s(%p): end\n", __func__, pdata->lcd_dev); +} + +static int __init sh_hdmi_probe(struct platform_device *pdev) +{ + struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data; + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int irq = platform_get_irq(pdev, 0), ret; + struct sh_hdmi *hdmi; + long rate; + + if (!res || !pdata || irq < 0) + return -ENODEV; + + hdmi = kzalloc(sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) { + dev_err(&pdev->dev, "Cannot allocate device data\n"); + return -ENOMEM; + } + + hdmi->dev = &pdev->dev; + + hdmi->hdmi_clk = clk_get(&pdev->dev, "ick"); + if (IS_ERR(hdmi->hdmi_clk)) { + ret = PTR_ERR(hdmi->hdmi_clk); + dev_err(&pdev->dev, "Unable to get clock: %d\n", ret); + goto egetclk; + } + + rate = PICOS2KHZ(pdata->lcd_chan->lcd_cfg.pixclock) * 1000; + + rate = clk_round_rate(hdmi->hdmi_clk, rate); + if (rate < 0) { + ret = rate; + dev_err(&pdev->dev, "Cannot get suitable rate: %ld\n", rate); + goto erate; + } + + ret = clk_set_rate(hdmi->hdmi_clk, rate); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot set rate %ld: %d\n", rate, ret); + goto erate; + } + + pr_debug("HDMI set frequency %lu\n", rate); + + ret = clk_enable(hdmi->hdmi_clk); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot enable clock: %d\n", ret); + goto eclkenable; + } + + dev_info(&pdev->dev, "Enabled HDMI clock at %luHz\n", rate); + + if (!request_mem_region(res->start, resource_size(res), dev_name(&pdev->dev))) { + dev_err(&pdev->dev, "HDMI register region already claimed\n"); + ret = -EBUSY; + goto ereqreg; + } + + hdmi->base = ioremap(res->start, resource_size(res)); + if (!hdmi->base) { + dev_err(&pdev->dev, "HDMI register region already claimed\n"); + ret = -ENOMEM; + goto emap; + } + + platform_set_drvdata(pdev, hdmi); + +#if 1 + /* Product and revision IDs are 0 in sh-mobile version */ + dev_info(&pdev->dev, "Detected HDMI controller 0x%x:0x%x\n", + hdmi_read(hdmi, HDMI_PRODUCT_ID), hdmi_read(hdmi, HDMI_REVISION_ID)); +#endif + + /* Set up LCDC callbacks */ + pdata->lcd_chan->board_cfg.board_data = hdmi; + pdata->lcd_chan->board_cfg.display_on = hdmi_display_on; + pdata->lcd_chan->board_cfg.display_off = hdmi_display_off; + + INIT_DELAYED_WORK(&hdmi->edid_work, edid_work_fn); + + pm_runtime_enable(&pdev->dev); + pm_runtime_resume(&pdev->dev); + + ret = request_irq(irq, sh_hdmi_hotplug, 0, + dev_name(&pdev->dev), hdmi); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to request irq: %d\n", ret); + goto ereqirq; + } + + return 0; + +ereqirq: + pm_runtime_disable(&pdev->dev); + iounmap(hdmi->base); +emap: + release_mem_region(res->start, resource_size(res)); +ereqreg: + clk_disable(hdmi->hdmi_clk); +eclkenable: +erate: + clk_put(hdmi->hdmi_clk); +egetclk: + kfree(hdmi); + + return ret; +} + +static int __exit sh_hdmi_remove(struct platform_device *pdev) +{ + struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data; + struct sh_hdmi *hdmi = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int irq = platform_get_irq(pdev, 0); + + pdata->lcd_chan->board_cfg.display_on = NULL; + pdata->lcd_chan->board_cfg.display_off = NULL; + pdata->lcd_chan->board_cfg.board_data = NULL; + + free_irq(irq, hdmi); + pm_runtime_disable(&pdev->dev); + cancel_delayed_work_sync(&hdmi->edid_work); + clk_disable(hdmi->hdmi_clk); + clk_put(hdmi->hdmi_clk); + iounmap(hdmi->base); + release_mem_region(res->start, resource_size(res)); + kfree(hdmi); + + return 0; +} + +static struct platform_driver sh_hdmi_driver = { + .remove = __exit_p(sh_hdmi_remove), + .driver = { + .name = "sh-mobile-hdmi", + }, +}; + +static int __init sh_hdmi_init(void) +{ + return platform_driver_probe(&sh_hdmi_driver, sh_hdmi_probe); +} +module_init(sh_hdmi_init); + +static void __exit sh_hdmi_exit(void) +{ + platform_driver_unregister(&sh_hdmi_driver); +} +module_exit(sh_hdmi_exit); + +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_DESCRIPTION("SuperH / ARM-shmobile HDMI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index 12c451a711e9..d72075a9f01c 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c @@ -56,6 +56,7 @@ static int lcdc_shared_regs[] = { /* per-channel registers */ enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, + LDHAJR, NR_CH_REGS }; static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { @@ -74,6 +75,7 @@ static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { [LDVLNR] = 0x450, [LDVSYNR] = 0x454, [LDPMR] = 0x460, + [LDHAJR] = 0x4a0, }; static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { @@ -137,6 +139,7 @@ struct sh_mobile_lcdc_priv { struct clk *dot_clk; unsigned long lddckr; struct sh_mobile_lcdc_chan ch[2]; + struct notifier_block notifier; unsigned long saved_shared_regs[NR_SHARED_REGS]; int started; }; @@ -404,6 +407,56 @@ static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, lcdc_write(priv, _LDDCKSTPR, 1); /* stop dotclock */ } +static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) +{ + struct fb_var_screeninfo *var = &ch->info->var; + unsigned long h_total, hsync_pos; + u32 tmp; + + tmp = ch->ldmt1r_value; + tmp |= (var->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : 1 << 28; + tmp |= (var->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : 1 << 27; + tmp |= (ch->cfg.flags & LCDC_FLAGS_DWPOL) ? 1 << 26 : 0; + tmp |= (ch->cfg.flags & LCDC_FLAGS_DIPOL) ? 1 << 25 : 0; + tmp |= (ch->cfg.flags & LCDC_FLAGS_DAPOL) ? 1 << 24 : 0; + tmp |= (ch->cfg.flags & LCDC_FLAGS_HSCNT) ? 1 << 17 : 0; + tmp |= (ch->cfg.flags & LCDC_FLAGS_DWCNT) ? 1 << 16 : 0; + lcdc_write_chan(ch, LDMT1R, tmp); + + /* setup SYS bus */ + lcdc_write_chan(ch, LDMT2R, ch->cfg.sys_bus_cfg.ldmt2r); + lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r); + + /* horizontal configuration */ + h_total = var->xres + var->hsync_len + + var->left_margin + var->right_margin; + tmp = h_total / 8; /* HTCN */ + tmp |= (var->xres / 8) << 16; /* HDCN */ + lcdc_write_chan(ch, LDHCNR, tmp); + + hsync_pos = var->xres + var->right_margin; + tmp = hsync_pos / 8; /* HSYNP */ + tmp |= (var->hsync_len / 8) << 16; /* HSYNW */ + lcdc_write_chan(ch, LDHSYNR, tmp); + + /* vertical configuration */ + tmp = var->yres + var->vsync_len + + var->upper_margin + var->lower_margin; /* VTLN */ + tmp |= var->yres << 16; /* VDLN */ + lcdc_write_chan(ch, LDVLNR, tmp); + + tmp = var->yres + var->lower_margin; /* VSYNP */ + tmp |= var->vsync_len << 16; /* VSYNW */ + lcdc_write_chan(ch, LDVSYNR, tmp); + + /* Adjust horizontal synchronisation for HDMI */ + tmp = ((var->xres & 7) << 24) | + ((h_total & 7) << 16) | + ((var->hsync_len & 7) << 8) | + hsync_pos; + lcdc_write_chan(ch, LDHAJR, tmp); +} + static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) { struct sh_mobile_lcdc_chan *ch; @@ -470,49 +523,11 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) if (!ch->enabled) continue; - tmp = ch->ldmt1r_value; - tmp |= (lcd_cfg->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : 1 << 28; - tmp |= (lcd_cfg->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : 1 << 27; - tmp |= (ch->cfg.flags & LCDC_FLAGS_DWPOL) ? 1 << 26 : 0; - tmp |= (ch->cfg.flags & LCDC_FLAGS_DIPOL) ? 1 << 25 : 0; - tmp |= (ch->cfg.flags & LCDC_FLAGS_DAPOL) ? 1 << 24 : 0; - tmp |= (ch->cfg.flags & LCDC_FLAGS_HSCNT) ? 1 << 17 : 0; - tmp |= (ch->cfg.flags & LCDC_FLAGS_DWCNT) ? 1 << 16 : 0; - lcdc_write_chan(ch, LDMT1R, tmp); - - /* setup SYS bus */ - lcdc_write_chan(ch, LDMT2R, ch->cfg.sys_bus_cfg.ldmt2r); - lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r); - - /* horizontal configuration */ - tmp = lcd_cfg->xres + lcd_cfg->hsync_len; - tmp += lcd_cfg->left_margin; - tmp += lcd_cfg->right_margin; - tmp /= 8; /* HTCN */ - tmp |= (lcd_cfg->xres / 8) << 16; /* HDCN */ - lcdc_write_chan(ch, LDHCNR, tmp); - - tmp = lcd_cfg->xres; - tmp += lcd_cfg->right_margin; - tmp /= 8; /* HSYNP */ - tmp |= (lcd_cfg->hsync_len / 8) << 16; /* HSYNW */ - lcdc_write_chan(ch, LDHSYNR, tmp); + sh_mobile_lcdc_geometry(ch); /* power supply */ lcdc_write_chan(ch, LDPMR, 0); - /* vertical configuration */ - tmp = lcd_cfg->yres + lcd_cfg->vsync_len; - tmp += lcd_cfg->upper_margin; - tmp += lcd_cfg->lower_margin; /* VTLN */ - tmp |= lcd_cfg->yres << 16; /* VDLN */ - lcdc_write_chan(ch, LDVLNR, tmp); - - tmp = lcd_cfg->yres; - tmp += lcd_cfg->lower_margin; /* VSYNP */ - tmp |= lcd_cfg->vsync_len << 16; /* VSYNW */ - lcdc_write_chan(ch, LDVSYNR, tmp); - board_cfg = &ch->cfg.board_cfg; if (board_cfg->setup_sys) ret = board_cfg->setup_sys(board_cfg->board_data, ch, @@ -577,7 +592,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) board_cfg = &ch->cfg.board_cfg; if (board_cfg->display_on) - board_cfg->display_on(board_cfg->board_data); + board_cfg->display_on(board_cfg->board_data, ch->info); } return 0; @@ -943,6 +958,62 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { .runtime_resume = sh_mobile_lcdc_runtime_resume, }; +static int sh_mobile_lcdc_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct fb_event *event = data; + struct fb_info *info = event->info; + struct sh_mobile_lcdc_chan *ch = info->par; + struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg; + struct fb_var_screeninfo *var; + + if (&ch->lcdc->notifier != nb) + return 0; + + dev_dbg(info->dev, "%s(): action = %lu, data = %p\n", + __func__, action, event->data); + + switch(action) { + case FB_EVENT_SUSPEND: + if (board_cfg->display_off) + board_cfg->display_off(board_cfg->board_data); + pm_runtime_put(info->device); + break; + case FB_EVENT_RESUME: + var = &info->var; + + /* HDMI must be enabled before LCDC configuration */ + if (board_cfg->display_on) + board_cfg->display_on(board_cfg->board_data, ch->info); + + /* Check if the new display is not in our modelist */ + if (ch->info->modelist.next && + !fb_match_mode(var, &ch->info->modelist)) { + struct fb_videomode mode; + int ret; + + /* Can we handle this display? */ + if (var->xres > ch->cfg.lcd_cfg.xres || + var->yres > ch->cfg.lcd_cfg.yres) + return -ENOMEM; + + /* Add to the modelist */ + fb_var_to_videomode(&mode, var); + ret = fb_add_videomode(&mode, &ch->info->modelist); + if (ret < 0) + return ret; + } + + pm_runtime_get_sync(info->device); + + sh_mobile_lcdc_geometry(ch); + + break; + } + + return 0; +} + static int sh_mobile_lcdc_remove(struct platform_device *pdev); static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) @@ -1020,15 +1091,19 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) goto err1; } + priv->base = ioremap_nocache(res->start, resource_size(res)); + if (!priv->base) + goto err1; + error = sh_mobile_lcdc_setup_clocks(pdev, pdata->clock_source, priv); if (error) { dev_err(&pdev->dev, "unable to setup clocks\n"); goto err1; } - priv->base = ioremap_nocache(res->start, (res->end - res->start) + 1); - for (i = 0; i < j; i++) { + struct fb_var_screeninfo *var; + struct fb_videomode *lcd_cfg; cfg = &priv->ch[i].cfg; priv->ch[i].info = framebuffer_alloc(0, &pdev->dev); @@ -1039,22 +1114,33 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) } info = priv->ch[i].info; + var = &info->var; + lcd_cfg = &cfg->lcd_cfg; info->fbops = &sh_mobile_lcdc_ops; - info->var.xres = info->var.xres_virtual = cfg->lcd_cfg.xres; - info->var.yres = cfg->lcd_cfg.yres; + var->xres = var->xres_virtual = lcd_cfg->xres; + var->yres = lcd_cfg->yres; /* Default Y virtual resolution is 2x panel size */ - info->var.yres_virtual = info->var.yres * 2; - info->var.width = cfg->lcd_size_cfg.width; - info->var.height = cfg->lcd_size_cfg.height; - info->var.activate = FB_ACTIVATE_NOW; - error = sh_mobile_lcdc_set_bpp(&info->var, cfg->bpp); + var->yres_virtual = var->yres * 2; + var->width = cfg->lcd_size_cfg.width; + var->height = cfg->lcd_size_cfg.height; + var->activate = FB_ACTIVATE_NOW; + var->left_margin = lcd_cfg->left_margin; + var->right_margin = lcd_cfg->right_margin; + var->upper_margin = lcd_cfg->upper_margin; + var->lower_margin = lcd_cfg->lower_margin; + var->hsync_len = lcd_cfg->hsync_len; + var->vsync_len = lcd_cfg->vsync_len; + var->sync = lcd_cfg->sync; + var->pixclock = lcd_cfg->pixclock; + + error = sh_mobile_lcdc_set_bpp(var, cfg->bpp); if (error) break; info->fix = sh_mobile_lcdc_fix; - info->fix.line_length = cfg->lcd_cfg.xres * (cfg->bpp / 8); + info->fix.line_length = lcd_cfg->xres * (cfg->bpp / 8); info->fix.smem_len = info->fix.line_length * - info->var.yres_virtual; + var->yres_virtual; buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len, &priv->ch[i].dma_handle, GFP_KERNEL); @@ -1119,10 +1205,14 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) ch->cfg.bpp); /* deferred io mode: disable clock to save power */ - if (info->fbdefio) + if (info->fbdefio || info->state == FBINFO_STATE_SUSPENDED) sh_mobile_lcdc_clk_off(priv); } + /* Failure ignored */ + priv->notifier.notifier_call = sh_mobile_lcdc_notify; + fb_register_client(&priv->notifier); + return 0; err1: sh_mobile_lcdc_remove(pdev); @@ -1136,6 +1226,8 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) struct fb_info *info; int i; + fb_unregister_client(&priv->notifier); + for (i = 0; i < ARRAY_SIZE(priv->ch); i++) if (priv->ch[i].info && priv->ch[i].info->dev) unregister_framebuffer(priv->ch[i].info); |