diff options
author | Haojian Zhuang <haojian.zhuang@linaro.org> | 2013-04-04 16:14:57 +0800 |
---|---|---|
committer | Haojian Zhuang <haojian.zhuang@linaro.org> | 2013-04-04 16:14:57 +0800 |
commit | 59dbe89dc4ff20001ec8288b4706184a2a613fe4 (patch) | |
tree | 82bcd0fb4cc34210fb76a293b58d0668583f5cfb | |
parent | b1c56292a748d7f4748c480bf89a8d0941e92ac0 (diff) |
fb: append hi3620 driver
Add Hisilicon Hi3620 framebuffer driver.
Signed-off-by: Haojian Zhuang <haojian.zhuang@linaro.org>
-rw-r--r-- | drivers/video/Kconfig | 1 | ||||
-rw-r--r-- | drivers/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/hisilicon/Kconfig | 9 | ||||
-rw-r--r-- | drivers/video/hisilicon/Makefile | 2 | ||||
-rw-r--r-- | drivers/video/hisilicon/hi3620_dsi.c | 353 | ||||
-rw-r--r-- | drivers/video/hisilicon/hi3620_fb.c | 650 | ||||
-rw-r--r-- | drivers/video/hisilicon/hi3620_fb.h | 125 | ||||
-rw-r--r-- | include/linux/platform_data/hi3620-dsi.h | 32 |
8 files changed, 1173 insertions, 0 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 807c7fa689fa..2343912499d0 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2443,6 +2443,7 @@ config FB_PUV3_UNIGFX source "drivers/video/omap/Kconfig" source "drivers/video/omap2/Kconfig" source "drivers/video/exynos/Kconfig" +source "drivers/video/hisilicon/Kconfig" source "drivers/video/backlight/Kconfig" if VT diff --git a/drivers/video/Makefile b/drivers/video/Makefile index f592f3b32ec7..b322ebb7bc76 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -161,6 +161,7 @@ obj-$(CONFIG_FB_BFIN_7393) += bfin_adv7393fb.o obj-$(CONFIG_FB_MX3) += mx3fb.o obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o obj-$(CONFIG_FB_MXS) += mxsfb.o +obj-$(CONFIG_FB_HI3620) += hisilicon/ obj-$(CONFIG_FB_SSD1307) += ssd1307fb.o # the test framebuffer is last diff --git a/drivers/video/hisilicon/Kconfig b/drivers/video/hisilicon/Kconfig new file mode 100644 index 000000000000..a683760d2185 --- /dev/null +++ b/drivers/video/hisilicon/Kconfig @@ -0,0 +1,9 @@ +config FB_HI3620 + tristate "Hisilicon Hi3620 Frame Buffer support" + depends on FB && ARCH_HS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the Hisilicon Hi3620 LCD + controller. diff --git a/drivers/video/hisilicon/Makefile b/drivers/video/hisilicon/Makefile new file mode 100644 index 000000000000..1f4b79befe51 --- /dev/null +++ b/drivers/video/hisilicon/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_FB_HI3620) += hi3620_fb.o +obj-y += hi3620_dsi.o diff --git a/drivers/video/hisilicon/hi3620_dsi.c b/drivers/video/hisilicon/hi3620_dsi.c new file mode 100644 index 000000000000..af3dd68abd81 --- /dev/null +++ b/drivers/video/hisilicon/hi3620_dsi.c @@ -0,0 +1,353 @@ + +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/fb.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/platform_data/hi3620-dsi.h> +#include "hi3620_fb.h" + +/* DSI PHY internal register */ +#define DSI_PHY_CP_CURRENT 0x11 +#define DSI_PHY_LPF_CTRL 0x12 +#define DSI_PHY_PLL_UNLOCK_FILTER 0x16 +#define DSI_PHY_N_PLL 0x17 +#define DSI_PHY_M_PLL 0x18 +#define DSI_PHY_FACTOR 0x19 +#define DSI_PHY_HS_FREQ_RANGE 0x44 + +#define PHY_ADDR (1 << 16) + +#define DTYPE_GEN_WRITE_HEAD 0x03 +#define DTYPE_GEN_WRITE_PAYLOAD 0x29 + +struct phy_timing { + int dsi2x; /* MHz */ + int lp2hs; + int hs2lp; + int hsfreq; +}; + +static void hi3620_mipi_reset(void __iomem *reg_base) +{ + writel_relaxed(0, reg_base + DSI_PWR_UP); +} + +static void hi3620_mipi_unreset(void __iomem *reg_base) +{ + writel_relaxed(1, reg_base + DSI_PWR_UP); +} + +static void hi3620_mipi_set_highspeed(void __iomem *reg_base) +{ + unsigned int data; + + /* enable high speed clock for PHY */ + data = readl_relaxed(reg_base + DSI_PHY_IF_CTRL); + data |= (1 << 0); + writel_relaxed(data, reg_base + DSI_PHY_IF_CTRL); +} + +static void hi3620_mipi_unset_highspeed(void __iomem *reg_base) +{ + unsigned int data; + + /* disable high speed clock for PHY */ + data = readl_relaxed(reg_base + DSI_PHY_IF_CTRL); + data &= ~(1 << 0); + writel_relaxed(data, reg_base + DSI_PHY_IF_CTRL); +} + +/* Switch to CMD mode with Low Power mode */ +static void hi3620_mipi_switch_cmd_mode(void __iomem *reg_base) +{ + unsigned int data; + + /* disable VIDEO mode */ + data = readl_relaxed(reg_base + DSI_VID_MODE_CFG); + data &= ~(1 << 0); + writel_relaxed(data, reg_base + DSI_VID_MODE_CFG); + data = readl_relaxed(reg_base + DSI_CMD_MODE_CFG); + data |= ((1 << 13) - 1); + writel_relaxed(data, reg_base + DSI_CMD_MODE_CFG); +} + +/* Switch to VIDEO mode */ +static void hi3620_mipi_switch_video_mode(void __iomem *reg_base) +{ + unsigned int data; + + /* disable CMD mode */ + data = readl_relaxed(reg_base + DSI_CMD_MODE_CFG); + data &= ~(1 << 0); + writel_relaxed(data, reg_base + DSI_CMD_MODE_CFG); + /* enable VIDEO mode */ + data = readl_relaxed(reg_base + DSI_VID_MODE_CFG); + data |= (1 << 0); + writel_relaxed(data, reg_base + DSI_VID_MODE_CFG); +} + +static void hi3620_mipi_enable_phy(void __iomem *reg_base) +{ + writel_relaxed(0x7, reg_base + DSI_PHY_RSTZ); +} + +static void hi3620_mipi_disable_phy(void __iomem *reg_base) +{ + writel_relaxed(0, reg_base + DSI_PHY_RSTZ); +} + +#if 0 +static void hi3620_mipi_setup_cmd_mode(void __iomem *reg_base) +{ + /* enable CMD halt & disable VIDEO halt & discret WMS */ + writel_relaxed(1, reg_base + DSI_CMD_MOD_CTRL); + /* enable TE for CMD mode */ + writel_relaxed(0x4001, reg_base + DSI_TE_CTRL); + /* send data only when VSync is detected */ + writel_relaxed(0, reg_base + DSI_TE_HS_NUM); + /* HSync width */ + writel_relaxed(0x1001, reg_base + DSI_TE_HS_WD); + /* VSync width > Hsync width */ + writel_relaxed(0x8002, reg_base + DSI_TE_VS_WD); +} + +static void hi3620_mipi_setup_video_mode(struct hi3620fb_info *info) +{ + struct fb_info *fb = info->fb; + struct fb_var_screeninfo *var = &fb->var; + unsigned int data; + + /* enable low power mode & disable ACK */ + data = 0x1f8; + /* burst with Sync pulses */ + data |= 2 << 1; + writel_relaxed(data, info->reg_base + DSI_VID_MODE_CFG); + data = var->xres & 0x7ff; + writel_relaxed(data, info->reg_base + DSI_VID_PKT_CFG); + + /* only enable BTA, Bus Turn-Around */ + data = readl_relaxed(info->reg_base + DSI_PCKHDL_CFG); + data &= ~0x1f; + data |= 1 << 2; + writel_relaxed(data, info->reg_base + DSI_PCKHDL_CFG); +} +#endif + +void hi3620_mipi_dsi_set_video_packet_size(void __iomem *reg_base, + int null_pkt_size, int num_vid_pkt, + int vid_pkt_size) +{ + unsigned int data; + data = (null_pkt_size & 0x3ff) << 21; /* byte size */ + /* number of video packets for Each Multiple Packets */ + data |= (num_vid_pkt & 0x3ff) << 11; + /* pixels of each video packet */ + data |= vid_pkt_size & 0x7ff; + writel_relaxed(data, reg_base + DSI_VID_PKT_CFG); +} + +static void hi3620_mipi_setup_dpi(struct hi3620fb_info *info) +{ + unsigned int data; + + /* set lane numbers */ + /* + data = readl_relaxed(info->reg_base + DSI_PHY_IF_CFG) & ~0x3; + data |= (info->lane_cnt - 1) & 0x3; + writel_relaxed(data, info->reg_base + DSI_PHY_IF_CFG); + */ + data = 0; + //data = readl_relaxed(info->reg_base + DSI_DPI_CFG); + /* set virtual channel ID as 0 */ + data &= ~(3 << 0); + /* set color mode */ + data &= ~(7 << 2); + data |= info->color_mode << 2; + /* always set color mode & shutdown high active */ + writel_relaxed(data, info->reg_base + DSI_DPI_CFG); +} + +static void hi3620_mipi_setup_phy(struct hi3620fb_info *info) +{ + struct clk *parent; + unsigned char hs2lp = 0, lp2hs = 0, hsfreq = 0; + unsigned int data; + int rate, rate_mhz, i; + struct phy_timing timing[] = { + {90, 24, 14, 0}, {100, 25, 14, 0x20}, + {110, 25, 14, 0x40}, {125, 25, 14, 0x02}, + {140, 25, 14, 0x22}, {150, 25, 14, 0x42}, + {160, 25, 14, 0x04}, {180, 28, 16, 0x24}, + {200, 32, 16, 0x44}, {210, 31, 16, 0x06}, + {240, 35, 17, 0x26}, {250, 37, 18, 0x46}, + {270, 37, 18, 0x08}, {300, 39, 19, 0x28}, + {330, 44, 20, 0x08}, {360, 47, 21, 0x2a}, + {400, 48, 21, 0x4a}, {450, 54, 23, 0x0c}, + {500, 58, 25, 0x2c}, {550, 62, 26, 0x0e}, + {600, 67, 28, 0x2e}, {650, 72, 30, 0x10}, + {700, 76, 31, 0x30}, {750, 81, 32, 0x12}, + {800, 86, 34, 0x32}, {850, 89, 35, 0x14}, + {900, 95, 37, 0x34}, {950, 99, 38, 0x54}, + {1000, 104, 40, 0x74}, }; + parent = clk_get_parent(info->clk_dsi); + rate = clk_get_rate(parent); + rate_mhz = rate / 1000000; + for (i = 0; i < ARRAY_SIZE(timing); i++) { + if (rate_mhz <= timing[i].dsi2x) { + hs2lp = timing[i].hs2lp; + lp2hs = timing[i].lp2hs; + hsfreq = timing[i].hsfreq; + break; + } + } + /* setup PHY timing */ + data = 4095; /* bta time */ + data |= (lp2hs & 0xff) << 16; + data |= (hs2lp & 0xff) << 24; + writel_relaxed(data, info->reg_base + DSI_PHY_TMR_CFG); + /* set hsfreqrange */ + hi3620_dsi_phy_write(info->reg_base, 0x44, hsfreq); + writel_relaxed(0x7, info->reg_base + DSI_PHY_RSTZ); +} + +void hi3620_mipi_dsi_set_lane(void __iomem *reg_base, int id, int count) +{ + unsigned int data, cnt; + + cnt = count - 1; + if (cnt < 0 || id > cnt) + return; + data = readl_relaxed(reg_base + DSI_PHY_IF_CFG) & ~0x3; + data |= cnt & 0x3; + writel_relaxed(data, reg_base + DSI_PHY_IF_CFG); + data = readl_relaxed(reg_base + DSI_DPI_CFG) & ~0x3; + data |= id & 0x3; + writel_relaxed(data, reg_base + DSI_DPI_CFG); +} + +/* + * PHY_TST_CTRL0 & PHY_TST_CTRL1 registers are the interfaces of accessing + * PHY internal registers. + * PHY_TST_CTRL0 is used to produce clock, as I2C SCLK. + * PHY_TST_CTRL1 is used to store address or data, as I2C SDA. + */ +static void set_phy_testclk(void __iomem *reg_base, int level) +{ + unsigned int data; + + if (level) + data = 0x2; + else + data = 0; + writel_relaxed(data, reg_base + DSI_PHY_TST_CTRL0); +} + +/* write 8-bit data into 8-bit phy register */ +int hi3620_dsi_phy_write(void __iomem *reg_base, unsigned char addr, + unsigned char data) +{ + unsigned int value; + + set_phy_testclk(reg_base, 0); + value = (unsigned int)addr | PHY_ADDR; + writel_relaxed(value, reg_base + DSI_PHY_TST_CTRL1); + set_phy_testclk(reg_base, 1); + + set_phy_testclk(reg_base, 0); + value = (unsigned int)data; + writel_relaxed(value, reg_base + DSI_PHY_TST_CTRL1); + set_phy_testclk(reg_base, 1); + set_phy_testclk(reg_base, 0); + return 0; +} +EXPORT_SYMBOL(hi3620_dsi_phy_write); + +/* read 8-bit data from 8-bit phy register */ +unsigned char hi3620_dsi_phy_read(void __iomem *reg_base, unsigned char addr) +{ + unsigned int value; + + set_phy_testclk(reg_base, 0); + value = (unsigned int)addr | PHY_ADDR; + writel_relaxed(value, reg_base + DSI_PHY_TST_CTRL1); + set_phy_testclk(reg_base, 1); + set_phy_testclk(reg_base, 0); + value = readl_relaxed(reg_base + DSI_PHY_TST_CTRL1); + return (unsigned char)(value >> 8); +} + +static struct hi3620fb_info *hinfo = NULL; + +extern int sharp_display_on(void); + +int hi3620_mipi_enable(struct hi3620fb_info *info) +{ + if (!hinfo) + hinfo = info; + hi3620_mipi_enable_phy(info->reg_base); + hi3620_mipi_reset(info->reg_base); + //hi3620_mipi_switch_cmd_mode(info->reg_base); /* debug for keeping display on after boot */ + //hi3620_mipi_unset_highspeed(info->reg_base); /* debug for keeping display on after boot */ + hi3620_mipi_unreset(info->reg_base); + + clk_prepare_enable(info->clk_dsi); + clk_prepare_enable(info->clk_lane); + clk_set_rate(info->clk_dsi, info->dsi_rate); /* huawei logo is shifted to right & color may be changed??? */ + + hi3620_mipi_setup_phy(info); + + if (!strncmp(info->mipi_mode_name, "video", 5)) + hi3620_mipi_switch_video_mode(info->reg_base); + else if (!strncmp(info->mipi_mode_name, "command", 7)) + hi3620_mipi_switch_cmd_mode(info->reg_base); + else + return -EINVAL; + hi3620_mipi_set_highspeed(info->reg_base); + hi3620_mipi_setup_dpi(info); /* debug for keeping display on after boot */ + return 0; +} +EXPORT_SYMBOL(hi3620_mipi_enable); + +int hi3620_mipi_disable(struct hi3620fb_info *info) +{ + hi3620_mipi_reset(info->reg_base); + hi3620_mipi_switch_cmd_mode(info->reg_base); + hi3620_mipi_unset_highspeed(info->reg_base); + hi3620_mipi_unreset(info->reg_base); + hi3620_mipi_disable_phy(info->reg_base); + return 0; +} +EXPORT_SYMBOL(hi3620_mipi_disable); + +int send_generic_packet(u8 *cmd, int len) +{ + unsigned int head = 0, data = 0; + int i; + + if (len <= 0 || !hinfo || !cmd) + return -EINVAL; + switch (len) { + case 2: + head |= cmd[1] << 16; + /* fallthrough */ + case 1: + head |= cmd[0] << 8; + head |= (len & 0x3) << 4; + head |= DTYPE_GEN_WRITE_HEAD; + break; + default: + head |= DTYPE_GEN_WRITE_PAYLOAD; + head |= (len & 0xffff) << 8; + for (i = 0; i < len; i += 4) { + data = 0; + data |= *(u32 *)(cmd + i); + writel_relaxed(data, hinfo->reg_base + DSI_GEN_PLD_DATA); + } + break; + } + /* write head to trigger a packet transfer */ + writel_relaxed(data, hinfo->reg_base + DSI_GEN_HDR); + return len; +} +EXPORT_SYMBOL(send_generic_packet); diff --git a/drivers/video/hisilicon/hi3620_fb.c b/drivers/video/hisilicon/hi3620_fb.c new file mode 100644 index 000000000000..83cce2022743 --- /dev/null +++ b/drivers/video/hisilicon/hi3620_fb.c @@ -0,0 +1,650 @@ +/* + * Framebuffer driver of Hisilicon Hi3620 SoC + * + * Copyright (c) 2013 Linaro Ltd. + * Copyright (c) 2013 Hisilicon Ltd. + * + * Author: Haojian Zhuang <haojian.zhuang@linaro.org> + * + * 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/clk.h> +#include <linux/dma-mapping.h> +#include <linux/fb.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <video/of_display_timing.h> +#include <video/display_timing.h> +#include "hi3620_fb.h" + +static unsigned int hi3620fb_pseudo_palette[16] = { + 0, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, + ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, +}; + +static int match_fmt_555(struct fb_var_screeninfo *var) +{ + if (var->blue.offset == 0 && var->green.offset <= 5 && + var->red.offset <= 10 && var->transp.offset <= 15 && + var->blue.offset <= 5 && var->green.length <= 5 && + var->red.length <= 5) + return 1; + return 0; +} + +static int match_fmt_565(struct fb_var_screeninfo *var) +{ + if (var->blue.offset == 0 && var->green.offset <= 5 && + var->red.offset <= 11 && var->transp.offset == 0 && + var->blue.length <= 5 && var->green.length <= 6 && + var->red.length <= 5 && var->transp.length == 0) + return 1; + return 0; +} + +static int match_fmt_888(struct fb_var_screeninfo *var) +{ + if (var->blue.offset == 0 && var->green.offset <= 8 && + var->red.offset <= 16 && var->transp.offset <= 24 && + var->blue.length <= 8 && var->green.length <= 8 && + var->red.length <= 8) + return 1; + return 0; +} + +static int find_best_pix_fmt(struct fb_var_screeninfo *var) +{ + if (var->bits_per_pixel == 16) { + /* RGB565/RGBA5551/RGBX5551 */ + if (match_fmt_555(var)) { + if (var->transp.length == 1) + return IMG_PIXEL_FORMAT_ARGB1555; + else if (var->transp.length == 0) + return IMG_PIXEL_FORMAT_RGB555; + } else if (match_fmt_565(var)) + return IMG_PIXEL_FORMAT_RGB565; + } else if (var->bits_per_pixel == 32) { + if (match_fmt_888(var)) { + if (var->transp.length == 8) + return IMG_PIXEL_FORMAT_ARGB8888; + else if (var->transp.length == 0) + return IMG_PIXEL_FORMAT_RGB888; + } + } + return -EINVAL; +} + +static void set_pix_fmt(struct hi3620fb_info *info, int pix_fmt) +{ + struct fb_info *fb = info->fb; + struct fb_var_screeninfo *var = &fb->var; + + switch (pix_fmt) { + case IMG_PIXEL_FORMAT_RGB565: + var->blue.offset = 0; + var->blue.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->red.offset = 11; + var->red.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + var->bits_per_pixel = 16; + break; + case IMG_PIXEL_FORMAT_ARGB1555: + var->blue.offset = 0; + var->blue.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->red.offset = 10; + var->red.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + var->bits_per_pixel = 16; + break; + case IMG_PIXEL_FORMAT_RGB555: + var->blue.offset = 0; + var->blue.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->red.offset = 10; + var->red.length = 5; + var->transp.offset = 15; + var->transp.length = 0; + var->bits_per_pixel = 16; + break; + case IMG_PIXEL_FORMAT_ARGB8888: + var->blue.offset = 0; + var->blue.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + var->bits_per_pixel = 32; + break; + case IMG_PIXEL_FORMAT_RGB888: + var->blue.offset = 0; + var->blue.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->transp.offset = 24; + var->transp.length = 0; + var->bits_per_pixel = 32; + break; + default: + return; + } + info->pix_fmt = pix_fmt; +} + +static int hi3620fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *fb) +{ + struct hi3620fb_info *info = fb->par; + int pix_fmt; + + /* + * Determine which pixel format we're going to use. + */ + pix_fmt = find_best_pix_fmt(var); + if (pix_fmt < 0) + return pix_fmt; + set_pix_fmt(info, pix_fmt); + + /* + * Basic geometry sanity checks. + */ + if (var->xoffset + var->xres > var->xres_virtual) + return -EINVAL; + if (var->yoffset + var->yres > var->yres_virtual) + return -EINVAL; + if (var->xres + var->right_margin + + var->hsync_len + var->left_margin > 2048) + return -EINVAL; + if (var->yres + var->lower_margin + + var->vsync_len + var->upper_margin > 2048) + return -EINVAL; + + /* + * Check size of framebuffer. + */ + if (var->xres_virtual * var->yres_virtual * + (var->bits_per_pixel >> 3) > fb->fix.smem_len) + return -EINVAL; + return 0; +} + +/* It's used to make EDC configuration work. */ +static void update_edc(void __iomem *base) +{ + unsigned int data; + + data = readl_relaxed(base + EDC_DISP_CTL); + writel_relaxed(data | EDC_CFG_OK, base + EDC_DISP_CTL); + data &= ~EDC_CFG_OK; + writel_relaxed(data, base + EDC_DISP_CTL); +} + +static void set_panel_control(struct fb_info *fb) +{ + struct fb_videomode *fb_vm = fb->mode; + struct hi3620fb_info *info = fb->par; + void __iomem *base = info->reg_base; + u32 ldi, dpi; + + ldi = readl_relaxed(base + LDI_PLR_CTRL) & ~LDI_POLARITY_MASK; + dpi = readl_relaxed(base + DSI_DPI_CFG) & ~DSI_DPI_POLARITY_MASK; + if (fb_vm->sync & FB_SYNC_HOR_HIGH_ACT) { + ldi &= ~LDI_HSYNC_POLARITY; + dpi &= ~DSI_DPI_HSYNC_POLARITY; + } else { + ldi |= LDI_HSYNC_POLARITY; + dpi |= DSI_DPI_HSYNC_POLARITY; + } + if (fb_vm->sync & FB_SYNC_VERT_HIGH_ACT) { + ldi &= ~LDI_VSYNC_POLARITY; + dpi &= ~DSI_DPI_VSYNC_POLARITY; + } else { + ldi |= LDI_VSYNC_POLARITY; + dpi |= DSI_DPI_VSYNC_POLARITY; + } + if (fb_vm->flag & FB_FLAG_DE_HIGH) { + ldi &= ~LDI_DATAEN_POLARITY; + dpi &= ~DSI_DPI_DATAEN_POLARITY; + } + if (fb_vm->flag & FB_FLAG_DE_LOW) { + ldi |= LDI_DATAEN_POLARITY; + dpi |= DSI_DPI_DATAEN_POLARITY; + } + if (fb_vm->flag & FB_FLAG_PIXDATA_POSEDGE) + ldi |= LDI_PIXELCLK_POLARITY; + if (fb_vm->flag & FB_FLAG_PIXDATA_NEGEDGE) + ldi &= ~LDI_PIXELCLK_POLARITY; + writel_relaxed(ldi, base + LDI_PLR_CTRL); + /* always set color mode & shutdown high active */ + writel_relaxed(dpi, info->reg_base + DSI_DPI_CFG); +} + +static void set_screen_dimensions(struct fb_info *fb) +{ + struct fb_var_screeninfo *var = &fb->var; + struct fb_videomode *fb_vm = fb->mode; + struct hi3620fb_info *info = fb->par; + void __iomem *base = info->reg_base; + unsigned long long int tmp; + u32 data, lane_rate, timing; + + data = (var->left_margin & 0xfff) << 20; + data |= var->right_margin & 0xfff; + writel_relaxed(data, base + LDI_HRZ_CTRL0); + data = (var->hsync_len - 1) & 0xfff; + writel_relaxed(data, base + LDI_HRZ_CTRL1); + data = (var->upper_margin & 0xfff) << 20; + data |= var->lower_margin & 0xfff; + writel_relaxed(data, base + LDI_VRT_CTRL0); + data = (var->vsync_len - 1) & 0xfff; + writel_relaxed(data, base + LDI_VRT_CTRL1); + + data = (var->xres - 1) & 0xfff; + data |= ((var->yres - 1) & 0xfff) << 20; + writel_relaxed(data, base + LDI_DSP_SIZE); + + data = (fb->var.xres_virtual - 1) << 16 | (fb->var.yres_virtual - 1); + writel_relaxed(data, base + EDC_VIDEO_CHAN_SIZE); + + /* setup line timing */ + lane_rate = clk_get_rate(info->clk_lane); + data = fb_vm->hsync_len * lane_rate / fb_vm->pixclock; + timing = data & 0x1ff; + data = fb_vm->left_margin * lane_rate / fb_vm->pixclock; + timing |= (data & 0x1ff) << 9; + tmp = (unsigned long long int)(fb_vm->left_margin + fb_vm->xres + + fb_vm->right_margin + fb_vm->hsync_len); + tmp *= lane_rate; + do_div(tmp, fb_vm->pixclock); + data = (unsigned int)tmp; + timing |= data << 18; + writel_relaxed(timing, info->reg_base + DSI_TMR_LINE_CFG); + + /* setup frame timing */ + timing = fb_vm->vsync_len & 0xf; + timing |= (fb_vm->upper_margin & 0x3f) << 4; + timing |= (fb_vm->lower_margin & 0x3f) << 10; + timing |= (fb_vm->yres & 0x7ff) << 16; + writel_relaxed(timing, info->reg_base + DSI_VTIMING_CFG); +} + +static void set_graphics_start(struct fb_info *fb, int xoffset, int yoffset) +{ + struct hi3620fb_info *info = fb->par; + void __iomem *base = info->reg_base; + u32 data; + + data = yoffset & 0xfff; + data |= (xoffset & 0xfff) << 16; + writel_relaxed(data, base + EDC_VIDEO_CHAN_XY); + /* setup dma address */ + writel_relaxed(fb->fix.smem_start, base + EDC_VIDEO_CHAN_ADDR); +} + +static void set_dma_control(struct fb_info *fb) +{ + struct hi3620fb_info *info = fb->par; + void __iomem *base = info->reg_base; + + /* setup dma stride */ + writel_relaxed(fb->fix.line_length, base + EDC_VIDEO_CHAN_STRIDE); +} + +static int hi3620fb_set_par(struct fb_info *fb) +{ + struct fb_var_screeninfo *var = &fb->var; + struct hi3620fb_info *info = fb->par; + void __iomem *base = info->reg_base; + unsigned int ctrl = 0; + + fb->fix.ypanstep = var->yres; + + ctrl = readl_relaxed(base + EDC_VIDEO_CHAN_CTRL); + if (var->blue.offset) + ctrl |= EDC_CHAN_CTRL_BGR; /* BGR format */ + ctrl &= ~(7 << 16); + switch (info->pix_fmt) { + case IMG_PIXEL_FORMAT_ARGB1555: + case IMG_PIXEL_FORMAT_RGB555: + break; + case IMG_PIXEL_FORMAT_RGB565: + ctrl |= 1 << 16; + break; + case IMG_PIXEL_FORMAT_RGB888: + ctrl |= 2 << 16; + break; + case IMG_PIXEL_FORMAT_ARGB8888: + ctrl |= 3 << 16; + break; + } + ctrl |= EDC_VIDEO_CHAN_CTRL_EN; + /* color key & rotate is always disabled, linear format */ +// ctrl |= 1 << 24; /* enable channel */ +// ctrl &= ~0xfff; +// ctrl |= 0xa; +// ctrl |= var->yres - 1; /* debug. add for interrupt */ + writel_relaxed(ctrl, base + EDC_VIDEO_CHAN_CTRL); + + set_panel_control(fb); + set_screen_dimensions(fb); + set_dma_control(fb); + update_edc(base); + return 0; +} + +static int hi3620fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fb) +{ + return 0; +} + +static struct fb_ops hi3620fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = hi3620fb_check_var, + .fb_set_par = hi3620fb_set_par, + .fb_pan_display = hi3620fb_pan_display, +}; + +static int hi3620_parse_dt(struct device_node *np, struct hi3620fb_info *info) +{ + const char *name; + int ret; + + ret = of_property_read_u32(np, "hisilicon,dsi-clock-frequency", + &info->dsi_rate); + if (ret) + return ret; + ret = of_property_read_string(np, "hisilicon,mipi-mode", &name); + if (ret < 0) + return ret; + info->mipi_mode_name = kstrdup(name, GFP_KERNEL); + ret = of_property_read_u32(np, "hisilicon,mipi-lanes", &info->lane_cnt); + if (ret < 0) + return ret; + ret = of_property_read_u32(np, "hisilicon,color-mode", &info->color_mode); + if (ret < 0) + return ret; + return 0; +} + +static int hi3620_init_mode(struct device_node *np, struct fb_info *fb) +{ + struct fb_fix_screeninfo *fix = &fb->fix; + struct fb_var_screeninfo *var = &fb->var; + struct display_timings *disp; + struct fb_videomode *fb_vm; + struct hi3620fb_info *info = fb->par; + const char *pix_name; + int ret = 0, pix_fmt, i, length; + + fb_vm = kzalloc(sizeof(*fb_vm), GFP_KERNEL); + if (!fb_vm) + return -ENOMEM; + fb->mode = fb_vm; + disp = of_get_display_timings(np); + if (!disp) + return -ENOENT; + /* How to handle multiple display timings ???, + * add_videomode is implemented by register_framebuffer() */ + for (i = 0; i < disp->num_timings; i++) { + ret = of_get_fb_videomode(np, fb_vm, i); + if (ret) + goto out; + ret = of_property_read_string(np, "hisilicon,pixel-format", + &pix_name); + if (ret) + goto out; + if (!strncmp(pix_name, "RGBA8888", 8)) + pix_fmt = IMG_PIXEL_FORMAT_ARGB8888; + else if (!strncmp(pix_name, "RGBX8888", 8)) + pix_fmt = IMG_PIXEL_FORMAT_RGB888; + else if (!strncmp(pix_name, "RGBA5551", 8)) + pix_fmt = IMG_PIXEL_FORMAT_ARGB1555; + else if (!strncmp(pix_name, "RGBX5551", 8)) + pix_fmt = IMG_PIXEL_FORMAT_RGB555; + else if (!strncmp(pix_name, "RGB565", 6)) + pix_fmt = IMG_PIXEL_FORMAT_RGB565; + else { + ret = -EINVAL; + goto out; + } + + set_pix_fmt(info, pix_fmt); + fb_videomode_to_var(var, fb_vm); + var->xres_virtual = fb_vm->xres; + var->yres_virtual = fb_vm->yres; + var->grayscale = 0; + var->accel_flags = FB_ACCEL_NONE; + /* Now assume that video mode is only 1 in DTS. */ + //fb_add_videomode(&vm, &fb->modelist); + } + of_display_timings_exist(np); + + fix->type_aux = 0; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + fix->ywrapstep = 0; + fix->mmio_start = 0; /* No MMIO address */ + fix->mmio_len = 0; /* No MMIO address */ + fix->accel = FB_ACCEL_NONE; /* No hardware accelerator */ + + length = var->xres_virtual * var->bits_per_pixel / 8; + fb->fix.line_length = ALIGN(length, 64); + fb->fix.smem_len = ALIGN(fb->fix.line_length * fb->var.yres_virtual, + PAGE_SIZE); + hi3620_parse_dt(np, info); +out: + return ret; +} + +static irqreturn_t edc_irq_handler(int irq, void *data) +{ + struct hi3620fb_info *info = data; + + pr_err("#%s, %d, ints:0x%x, inte:0x%x\n", + __func__, __LINE__, readl_relaxed(info->reg_base + EDC_INTS), + readl_relaxed(info->reg_base + EDC_INTE)); + return IRQ_HANDLED; +} + +static irqreturn_t ldi_irq_handler(int irq, void *data) +{ + struct hi3620fb_info *info = data; + u32 value; + + value = readl_relaxed(info->reg_base + LDI_ORG_INT); + writel_relaxed(value, info->reg_base + LDI_INT_CLR); + return IRQ_HANDLED; +} + +static irqreturn_t dsi_irq_handler(int irq, void *data) +{ + return IRQ_HANDLED; +} + +static int hi3620_fb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct hi3620fb_info *info; + struct resource *res; + struct fb_info *fb; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no memory resource defined\n"); + return -ENODEV; + } + + fb = framebuffer_alloc(sizeof(*info), dev); + if (!fb) { + dev_err(dev, "failed to allocate framebuffer\n"); + return -ENOMEM; + } + fb->dev = &pdev->dev; + info = fb->par; + info->fb = fb; + info->dev = &pdev->dev; + info->irq_edc = platform_get_irq_byname(pdev, "edc"); + if (info->irq_edc < 0) { + ret = -ENOENT; + goto err_fb; + } + info->irq_ldi = platform_get_irq_byname(pdev, "ldi"); + if (info->irq_ldi < 0) { + ret = -ENOENT; + goto err_fb; + } + info->irq_dsi = platform_get_irq_byname(pdev, "dsi"); + if (info->irq_dsi < 0) { + ret = -ENOENT; + goto err_fb; + } + + info->reg_base = devm_request_and_ioremap(&pdev->dev, res); + if (!info->reg_base) { + ret = -EADDRNOTAVAIL; + goto err_fb; + } + info->vedc = devm_regulator_get(dev, "vedc"); + if (IS_ERR_OR_NULL(info->vedc)) { + if (IS_ERR(info->vedc)) { + dev_err(dev, "failed to get vedc regulator\n"); + info->vedc = NULL; + } + } + info->clk_ldi = of_clk_get_by_name(np, "ldi"); + if (IS_ERR(info->clk_ldi)) { + dev_err(dev, "failed to get ldi clock\n"); + ret = PTR_ERR(info->clk_ldi); + goto err_fb; + } + info->clk_edc = of_clk_get_by_name(np, "edc"); + if (IS_ERR(info->clk_edc)) { + dev_err(dev, "failed to get edc clock\n"); + ret = PTR_ERR(info->clk_edc); + goto err_fb; + } + info->clk_dsi = of_clk_get_by_name(np, "dsi"); + if (IS_ERR(info->clk_dsi)) { + dev_err(dev, "failed to get dsi clock\n"); + ret = PTR_ERR(info->clk_dsi); + goto err_clk; + } + info->clk_lane = of_clk_get_by_name(np, "lane"); + if (IS_ERR(info->clk_lane)) { + dev_err(dev, "failed to get lane clock\n"); + ret = PTR_ERR(info->clk_lane); + goto err_clk; + } + if (info->vedc) + regulator_enable(info->vedc); + clk_prepare_enable(info->clk_ldi); + //clk_prepare_enable(info->clk_edc); /* debug for keep display on after boot */ + + fb->fbops = &hi3620fb_ops; + fb->pseudo_palette = &hi3620fb_pseudo_palette; + + ret = hi3620_init_mode(np, fb); + if (ret) + goto err_clk; + +#if 1 + fb->screen_base = dma_alloc_coherent(fb->dev, fb->fix.smem_len, + &info->fb_start_dma, + GFP_KERNEL); + fb->screen_size = fb->fix.smem_len; + fb->fix.smem_start = info->fb_start_dma; +#else + /* debug for remapping the display region in bootloader */ + info->fb_start_dma = PAGE_ALIGN(0x35b00130 + 0x40000000); + fb->fix.smem_start = info->fb_start_dma; + fb->screen_size = fb->fix.smem_len; + fb->screen_base = __va(info->fb_start_dma); +#endif + hi3620_mipi_enable(info); + set_graphics_start(fb, 0, 0); + hi3620fb_set_par(fb); + + + /* clear IRQ status & enable IRQ */ + writel_relaxed(0, info->reg_base + EDC_INTS); + writel_relaxed(0x2c8, info->reg_base + EDC_INTE); + writel_relaxed(0x4, info->reg_base + LDI_INT_EN); /* disable front porch int for debugging */ + writel_relaxed(0x3fff, info->reg_base + LDI_INT_CLR); + + ret = devm_request_irq(dev, info->irq_edc, edc_irq_handler, + IRQF_DISABLED, "edc", info); + if (ret < 0) { + dev_err(dev, "failed to request edc irq\n"); + goto err_clk; + } + ret = devm_request_irq(dev, info->irq_ldi, ldi_irq_handler, + IRQF_DISABLED, "ldi", info); + if (ret < 0) { + dev_err(dev, "failed to request ldi irq\n"); + goto err_clk; + } + ret = devm_request_irq(dev, info->irq_dsi, dsi_irq_handler, + IRQF_DISABLED, "dsi", info); + if (ret < 0) { + dev_err(dev, "failed to request dsi irq\n"); + goto err_clk; + } + ret = register_framebuffer(fb); + if (ret < 0) { + dev_err(dev, "failed to register hi3620 framebuffer\n"); + goto err_clk; + } + platform_set_drvdata(pdev, info); + /* clock rate of ldi */ + return 0; +err_clk: + clk_disable_unprepare(info->clk_edc); + clk_disable_unprepare(info->clk_ldi); +err_fb: + framebuffer_release(fb); + return ret; +} + +static int hi3620_fb_remove(struct platform_device *pdev) +{ + struct hi3620fb_info *info = platform_get_drvdata(pdev); + hi3620_mipi_disable(info); + return 0; +} + +static const struct of_device_id hi3620_fb_of_match[] = { + { .compatible = "hisilicon,hi3620-fb", }, + {}, +}; +MODULE_DEVICE_TABLE(of, hi3620_fb_of_match); + +static struct platform_driver hi3620_fb_driver = { + .probe = hi3620_fb_probe, + .remove = hi3620_fb_remove, + .driver = { + .name = "hi3620-fb", + .owner = THIS_MODULE, + .of_match_table = hi3620_fb_of_match, + }, +}; +module_platform_driver(hi3620_fb_driver); diff --git a/drivers/video/hisilicon/hi3620_fb.h b/drivers/video/hisilicon/hi3620_fb.h new file mode 100644 index 000000000000..676ff8e2e8f7 --- /dev/null +++ b/drivers/video/hisilicon/hi3620_fb.h @@ -0,0 +1,125 @@ +#ifndef __HI3620FB_H +#define __HI3620FB_H + +#define EDC_ID 0x000 + +/* Channel 1 */ +#define EDC_GRAPH_CHAN_ADDR 0x004 +#define EDC_GRAPH_CHAN_STRIDE 0x00c +#define EDC_GRAPH_CHAN_XY 0x010 +#define EDC_GRAPH_CHAN_SIZE 0x014 +#define EDC_GRAPH_CHAN_CTRL 0x018 +#define EDC_GRAPH_CHAN_CKEY_MIN 0x01c +#define EDC_GRAPH_CHAN_CKEY_MAX 0x020 + +/* Channel 2 */ +#define EDC_VIDEO_CHAN_ADDR 0x024 +#define EDC_VIDEO_CHAN_STRIDE 0x02c +#define EDC_VIDEO_CHAN_XY 0x030 +#define EDC_VIDEO_CHAN_SIZE 0x034 +#define EDC_VIDEO_CHAN_CTRL 0x038 +#define EDC_VIDEO_CHAN_CKEY_MIN 0x03c +#define EDC_VIDEO_CHAN_CKEY_MAX 0x040 + +#define EDC_GRAPH_CHAN_CTRL_EN (1 << 24) +#define EDC_VIDEO_CHAN_CTRL_EN (1 << 22) +#define EDC_CHAN_CTRL_BGR (1 << 19) + +#define EDC_DISP_CTL 0x094 +#define EDC_DISP_DPD 0x098 +#define EDC_STS 0x09c +#define EDC_INTS 0x0a0 +#define EDC_INTE 0x0a4 + +#define LDI_HRZ_CTRL0 0x800 +#define LDI_HRZ_CTRL1 0x804 +#define LDI_VRT_CTRL0 0x808 +#define LDI_VRT_CTRL1 0x80c +#define LDI_PLR_CTRL 0x810 +#define LDI_DSP_SIZE 0x814 +#define LDI_INT_EN 0x81c +#define LDI_CTRL 0x820 +#define LDI_ORG_INT 0x824 +#define LDI_MSK_INT 0x828 +#define LDI_INT_CLR 0x82c +#define LDI_WORK_MODE 0x830 +#define LDI_HDMI_DSI_GT 0x834 + +#define DSI_CMD_MOD_CTRL 0x83c +#define DSI_TE_CTRL 0x840 +#define DSI_TE_HS_NUM 0x844 +#define DSI_TE_HS_WD 0x848 +#define DSI_TE_VS_WD 0x84c + +#define DSI_PWR_UP 0x904 +#define DSI_CLKMGR_CFG 0x908 +#define DSI_DPI_CFG 0x90c +#define DSI_PCKHDL_CFG 0x918 +#define DSI_VID_MODE_CFG 0x91c +#define DSI_VID_PKT_CFG 0x920 +#define DSI_CMD_MODE_CFG 0x924 +#define DSI_TMR_LINE_CFG 0x928 +#define DSI_VTIMING_CFG 0x92c +#define DSI_PHY_TMR_CFG 0x930 +#define DSI_GEN_HDR 0x934 +#define DSI_GEN_PLD_DATA 0x938 +#define DSI_ERROR_ST0 0x944 +#define DSI_ERROR_ST1 0x948 +#define DSI_ERROR_MSK0 0x94c +#define DSI_ERROR_MSK1 0x950 +#define DSI_PHY_RSTZ 0x954 +#define DSI_PHY_IF_CFG 0x958 +#define DSI_PHY_IF_CTRL 0x95c +#define DSI_PHY_TST_CTRL0 0x964 +#define DSI_PHY_TST_CTRL1 0x968 + +/* bits field of EDC_DISP_CTL register */ +#define EDC_CFG_OK (1 << 1) + +/* bits field of LDI_PLT_CTRL register */ +#define LDI_POLARITY_MASK 0xf +#define LDI_DATAEN_POLARITY (1 << 3) +#define LDI_PIXELCLK_POLARITY (1 << 2) +#define LDI_HSYNC_POLARITY (1 << 1) +#define LDI_VSYNC_POLARITY (1 << 0) + +/* bits field of DSI_DPI_CFG register */ +#define DSI_DPI_POLARITY_MASK (0x1f << 5) +#define DSI_DPI_COLORM_POLARITY (1 << 9) +#define DSI_DPI_SHUTD_POLARITY (1 << 8) +#define DSI_DPI_HSYNC_POLARITY (1 << 7) +#define DSI_DPI_VSYNC_POLARITY (1 << 6) +#define DSI_DPI_DATAEN_POLARITY (1 << 5) + +enum { + IMG_PIXEL_FORMAT_ARGB1555 = 0, + IMG_PIXEL_FORMAT_RGB555, + IMG_PIXEL_FORMAT_RGB565, + IMG_PIXEL_FORMAT_RGB888, + IMG_PIXEL_FORMAT_ARGB8888, +}; + +struct hi3620fb_info { + struct fb_info *fb; + struct device *dev; + void __iomem *reg_base; + struct clk *clk_ldi; + struct clk *clk_edc; + struct clk *clk_dsi; + struct clk *clk_lane; + int irq_edc; + int irq_ldi; + int irq_dsi; + struct regulator *vedc; + dma_addr_t fb_start_dma; + int pix_fmt; + int dsi_rate; /* dsi bit clock rate */ + const char *mipi_mode_name; + int lane_cnt; + int color_mode; +}; + +extern int hi3620_mipi_enable(struct hi3620fb_info *info); +extern int hi3620_mipi_disable(struct hi3620fb_info *info); +extern int send_generic_packet(u8 *cmd, int len); +#endif /* __HI3620FB_H */ diff --git a/include/linux/platform_data/hi3620-dsi.h b/include/linux/platform_data/hi3620-dsi.h new file mode 100644 index 000000000000..447366f1e5c1 --- /dev/null +++ b/include/linux/platform_data/hi3620-dsi.h @@ -0,0 +1,32 @@ +/* + * Hisilicon Hi3620 MIPI DSI head file + * + * Copyright (c) 2013 Hisilicon Limited. + * Copyright (c) 2013 Linaro Limited. + * + * Author: Haojian Zhuang <haojian.zhuang@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef __HI3620_DSI_H +#define __HI3620_DSI_H + +int hi3620_dsi_phy_write(void __iomem *reg_base, unsigned char addr, + unsigned char data); +unsigned char hi3620_dsi_phy_read(void __iomem *reg_base, unsigned char addr); + +#endif |