diff options
Diffstat (limited to 'drivers/video/fbdev/sh_mipi_dsi.c')
-rw-r--r-- | drivers/video/fbdev/sh_mipi_dsi.c | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/drivers/video/fbdev/sh_mipi_dsi.c b/drivers/video/fbdev/sh_mipi_dsi.c new file mode 100644 index 00000000000..8f6e8ff620d --- /dev/null +++ b/drivers/video/fbdev/sh_mipi_dsi.c @@ -0,0 +1,587 @@ +/* + * 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/bitmap.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/module.h> + +#include <video/mipi_display.h> +#include <video/sh_mipi_dsi.h> +#include <video/sh_mobile_lcdc.h> + +#include "sh_mobile_lcdcfb.h" + +#define SYSCTRL 0x0000 +#define SYSCONF 0x0004 +#define TIMSET 0x0008 +#define RESREQSET0 0x0018 +#define RESREQSET1 0x001c +#define HSTTOVSET 0x0020 +#define LPRTOVSET 0x0024 +#define TATOVSET 0x0028 +#define PRTOVSET 0x002c +#define DSICTRL 0x0030 +#define DSIINTE 0x0060 +#define PHYCTRL 0x0070 + +/* relative to linkbase */ +#define DTCTR 0x0000 +#define VMCTR1 0x0020 +#define VMCTR2 0x0024 +#define VMLEN1 0x0028 +#define VMLEN2 0x002c +#define CMTSRTREQ 0x0070 +#define CMTSRTCTR 0x00d0 + +/* E.g., sh7372 has 2 MIPI-DSIs - one for each LCDC */ +#define MAX_SH_MIPI_DSI 2 + +struct sh_mipi { + struct sh_mobile_lcdc_entity entity; + + void __iomem *base; + void __iomem *linkbase; + struct clk *dsit_clk; + struct platform_device *pdev; +}; + +#define to_sh_mipi(e) container_of(e, struct sh_mipi, entity) + +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->linkbase + CMTSRTCTR); + iowrite32(1, mipi->linkbase + CMTSRTREQ); + + while ((ioread32(mipi->linkbase + CMTSRTREQ) & 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->linkbase + DTCTR); +} + +static void sh_mipi_shutdown(struct platform_device *pdev) +{ + struct sh_mipi *mipi = to_sh_mipi(platform_get_drvdata(pdev)); + + sh_mipi_dsi_enable(mipi, false); +} + +static int sh_mipi_setup(struct sh_mipi *mipi, const struct fb_videomode *mode) +{ + void __iomem *base = mipi->base; + struct sh_mipi_dsi_info *pdata = mipi->pdev->dev.platform_data; + u32 pctype, datatype, pixfmt, linelength, vmctr2; + u32 tmp, top, bottom, delay, div; + int bpp; + + /* + * Select data format. MIPI DSI is not hot-pluggable, so, we just use + * the default videomode. If this ever becomes a problem, We'll have to + * move this to mipi_display_on() above and use info->var.xres + */ + switch (pdata->data_format) { + case MIPI_RGB888: + pctype = 0; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_24; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = mode->xres * 3; + break; + case MIPI_RGB565: + pctype = 1; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = mode->xres * 2; + break; + case MIPI_RGB666_LP: + pctype = 2; + datatype = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = mode->xres * 3; + break; + case MIPI_RGB666: + pctype = 3; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_18; + pixfmt = MIPI_DCS_PIXEL_FMT_18BIT; + linelength = (mode->xres * 18 + 7) / 8; + break; + case MIPI_BGR888: + pctype = 8; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_24; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = mode->xres * 3; + break; + case MIPI_BGR565: + pctype = 9; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = mode->xres * 2; + break; + case MIPI_BGR666_LP: + pctype = 0xa; + datatype = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; + linelength = mode->xres * 3; + break; + case MIPI_BGR666: + pctype = 0xb; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_18; + pixfmt = MIPI_DCS_PIXEL_FMT_18BIT; + linelength = (mode->xres * 18 + 7) / 8; + break; + case MIPI_YUYV: + pctype = 4; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = mode->xres * 2; + break; + case MIPI_UYVY: + pctype = 5; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16; + pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; + linelength = mode->xres * 2; + break; + case MIPI_YUV420_L: + pctype = 6; + datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR12; + pixfmt = MIPI_DCS_PIXEL_FMT_12BIT; + linelength = (mode->xres * 12 + 7) / 8; + 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 = (mode->xres + 1) / 2; + break; + default: + return -EINVAL; + } + + if (!pdata->lane) + 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 */ + + /* + * T_wakeup = 0x7000 + * T_hs-trail = 3 + * T_hs-prepare = 3 + * T_clk-trail = 3 + * T_clk-prepare = 2 + */ + iowrite32(0x70003332, base + TIMSET); + /* no responses requested */ + iowrite32(0x00000000, base + RESREQSET0); + /* request response to packets of type 0x28 */ + iowrite32(0x00000100, base + RESREQSET1); + /* High-speed transmission timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + HSTTOVSET); + /* LP reception timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + LPRTOVSET); + /* Turn-around timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + TATOVSET); + /* Peripheral reset timeout, default 0xffffffff */ + iowrite32(0x0fffffff, base + PRTOVSET); + /* Interrupts not used, disable all */ + iowrite32(0, base + DSIINTE); + /* DSI-Tx bias on */ + iowrite32(0x00000001, base + PHYCTRL); + udelay(200); + /* Deassert resets, power on */ + iowrite32(0x03070001 | pdata->phyctrl, base + PHYCTRL); + + /* + * Default = ULPS enable | + * Contention detection enabled | + * EoT packet transmission enable | + * CRC check enable | + * ECC check enable + */ + bitmap_fill((unsigned long *)&tmp, pdata->lane); + tmp |= 0x00003700; + iowrite32(tmp, base + SYSCONF); + + /* setup l-bridge */ + + /* + * Enable transmission of all packets, + * transmit LPS after each HS packet completion + */ + iowrite32(0x00000006, mipi->linkbase + DTCTR); + /* VSYNC width = 2 (<< 17) */ + iowrite32((mode->vsync_len << pdata->vsynw_offset) | + (pdata->clksrc << 16) | (pctype << 12) | datatype, + mipi->linkbase + VMCTR1); + + /* + * Non-burst mode with sync pulses: VSE and HSE are output, + * HSA period allowed, no commands in LP + */ + vmctr2 = 0; + if (pdata->flags & SH_MIPI_DSI_VSEE) + vmctr2 |= 1 << 23; + if (pdata->flags & SH_MIPI_DSI_HSEE) + vmctr2 |= 1 << 22; + if (pdata->flags & SH_MIPI_DSI_HSAE) + vmctr2 |= 1 << 21; + if (pdata->flags & SH_MIPI_DSI_BL2E) + vmctr2 |= 1 << 17; + if (pdata->flags & SH_MIPI_DSI_HSABM) + vmctr2 |= 1 << 5; + if (pdata->flags & SH_MIPI_DSI_HBPBM) + vmctr2 |= 1 << 4; + if (pdata->flags & SH_MIPI_DSI_HFPBM) + vmctr2 |= 1 << 3; + iowrite32(vmctr2, mipi->linkbase + VMCTR2); + + /* + * VMLEN1 = RGBLEN | HSALEN + * + * see + * Video mode - Blanking Packet setting + */ + top = linelength << 16; /* RGBLEN */ + bottom = 0x00000001; + if (pdata->flags & SH_MIPI_DSI_HSABM) /* HSALEN */ + bottom = (pdata->lane * mode->hsync_len) - 10; + iowrite32(top | bottom , mipi->linkbase + VMLEN1); + + /* + * VMLEN2 = HBPLEN | HFPLEN + * + * see + * Video mode - Blanking Packet setting + */ + top = 0x00010000; + bottom = 0x00000001; + delay = 0; + + div = 1; /* HSbyteCLK is calculation base + * HS4divCLK = HSbyteCLK/2 + * HS6divCLK is not supported for now */ + if (pdata->flags & SH_MIPI_DSI_HS4divCLK) + div = 2; + + if (pdata->flags & SH_MIPI_DSI_HFPBM) { /* HBPLEN */ + top = mode->hsync_len + mode->left_margin; + top = ((pdata->lane * top / div) - 10) << 16; + } + if (pdata->flags & SH_MIPI_DSI_HBPBM) { /* HFPLEN */ + bottom = mode->right_margin; + bottom = (pdata->lane * bottom / div) - 12; + } + + bpp = linelength / mode->xres; /* byte / pixel */ + if ((pdata->lane / div) > bpp) { + tmp = mode->xres / bpp; /* output cycle */ + tmp = mode->xres - tmp; /* (input - output) cycle */ + delay = (pdata->lane * tmp); + } + + iowrite32(top | (bottom + delay) , mipi->linkbase + VMLEN2); + + msleep(5); + + /* setup LCD panel */ + + /* cf. drivers/video/omap/lcd_mipid.c */ + sh_mipi_dcs(pdata->channel, 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(pdata->channel, MIPI_DCS_SET_ADDRESS_MODE, 0x00); + /* cf. set_data_lines() */ + sh_mipi_dcs_param(pdata->channel, MIPI_DCS_SET_PIXEL_FORMAT, + pixfmt << 4); + sh_mipi_dcs(pdata->channel, MIPI_DCS_SET_DISPLAY_ON); + + /* Enable timeout counters */ + iowrite32(0x00000f00, base + DSICTRL); + + return 0; +} + +static int mipi_display_on(struct sh_mobile_lcdc_entity *entity) +{ + struct sh_mipi *mipi = to_sh_mipi(entity); + struct sh_mipi_dsi_info *pdata = mipi->pdev->dev.platform_data; + int ret; + + pm_runtime_get_sync(&mipi->pdev->dev); + + ret = pdata->set_dot_clock(mipi->pdev, mipi->base, 1); + if (ret < 0) + goto mipi_display_on_fail1; + + ret = sh_mipi_setup(mipi, &entity->def_mode); + if (ret < 0) + goto mipi_display_on_fail2; + + sh_mipi_dsi_enable(mipi, true); + + return SH_MOBILE_LCDC_DISPLAY_CONNECTED; + +mipi_display_on_fail1: + pm_runtime_put_sync(&mipi->pdev->dev); +mipi_display_on_fail2: + pdata->set_dot_clock(mipi->pdev, mipi->base, 0); + + return ret; +} + +static void mipi_display_off(struct sh_mobile_lcdc_entity *entity) +{ + struct sh_mipi *mipi = to_sh_mipi(entity); + struct sh_mipi_dsi_info *pdata = mipi->pdev->dev.platform_data; + + sh_mipi_dsi_enable(mipi, false); + + pdata->set_dot_clock(mipi->pdev, mipi->base, 0); + + pm_runtime_put_sync(&mipi->pdev->dev); +} + +static const struct sh_mobile_lcdc_entity_ops mipi_ops = { + .display_on = mipi_display_on, + .display_off = mipi_display_off, +}; + +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); + struct resource *res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + unsigned long rate, f_current; + int idx = pdev->id, ret; + + if (!res || !res2 || idx >= ARRAY_SIZE(mipi_dsi) || !pdata) + return -ENODEV; + + if (!pdata->set_dot_clock) + return -EINVAL; + + 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; + } + + mipi->entity.owner = THIS_MODULE; + mipi->entity.ops = &mipi_ops; + + 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; + } + + if (!request_mem_region(res2->start, resource_size(res2), pdev->name)) { + dev_err(&pdev->dev, "MIPI register region 2 already claimed\n"); + ret = -EBUSY; + goto ereqreg2; + } + + mipi->linkbase = ioremap(res2->start, resource_size(res2)); + if (!mipi->linkbase) { + ret = -ENOMEM; + goto emap2; + } + + mipi->pdev = pdev; + + 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); + + ret = clk_enable(mipi->dsit_clk); + if (ret < 0) + goto eclkton; + + mipi_dsi[idx] = mipi; + + pm_runtime_enable(&pdev->dev); + pm_runtime_resume(&pdev->dev); + + mutex_unlock(&array_lock); + platform_set_drvdata(pdev, &mipi->entity); + + return 0; + +eclkton: +esettrate: + clk_put(mipi->dsit_clk); +eclktget: + iounmap(mipi->linkbase); +emap2: + release_mem_region(res2->start, resource_size(res2)); +ereqreg2: + 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 sh_mipi_remove(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct resource *res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + struct sh_mipi *mipi = to_sh_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; + + pm_runtime_disable(&pdev->dev); + clk_disable(mipi->dsit_clk); + clk_put(mipi->dsit_clk); + + iounmap(mipi->linkbase); + if (res2) + release_mem_region(res2->start, resource_size(res2)); + iounmap(mipi->base); + if (res) + release_mem_region(res->start, resource_size(res)); + kfree(mipi); + + return 0; +} + +static struct platform_driver sh_mipi_driver = { + .remove = sh_mipi_remove, + .shutdown = sh_mipi_shutdown, + .driver = { + .name = "sh-mipi-dsi", + }, +}; + +module_platform_driver_probe(sh_mipi_driver, sh_mipi_probe); + +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_DESCRIPTION("SuperH / ARM-shmobile MIPI DSI driver"); +MODULE_LICENSE("GPL v2"); |