The MTL017 is found on the Efika Smartbook. Not much is known about this chip, so this driver only programs a register dump which is suitable for some panels. Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> --- drivers/video/Kconfig | 9 ++ drivers/video/Makefile | 1 + drivers/video/mtl017.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 drivers/video/mtl017.c diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 6d540a5..6637877 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -108,4 +108,13 @@ config DRIVER_VIDEO_BACKLIGHT_PWM help Enable this to get support for backlight devices driven by a PWM. +comment "Video encoder chips" + +config DRIVER_VIDEO_MTL017 + bool "MTL017 LVDS encoder" + select VIDEO_VPL + help + The MTL017 is a parallel to lvds video encoder chip found on the + Efika MX Smartbook. + endif diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 3aa544a..1e2109e 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_OFDEVICE) += of_display_timing.o obj-$(CONFIG_DRIVER_VIDEO_BACKLIGHT) += backlight.o obj-$(CONFIG_DRIVER_VIDEO_BACKLIGHT_PWM) += backlight-pwm.o obj-$(CONFIG_VIDEO_VPL) += vpl.o +obj-$(CONFIG_DRIVER_VIDEO_MTL017) += mtl017.o obj-$(CONFIG_DRIVER_VIDEO_ATMEL) += atmel_lcdfb.o atmel_lcdfb_core.o obj-$(CONFIG_DRIVER_VIDEO_ATMEL_HLCD) += atmel_hlcdfb.o atmel_lcdfb_core.o diff --git a/drivers/video/mtl017.c b/drivers/video/mtl017.c new file mode 100644 index 0000000..1a1f686 --- /dev/null +++ b/drivers/video/mtl017.c @@ -0,0 +1,280 @@ +/* + * (C) Copyright 2014 Sascha Hauer, Pengutronix + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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. + * + */ +#include <common.h> +#include <init.h> +#include <driver.h> +#include <xfuncs.h> +#include <errno.h> +#include <regulator.h> +#include <of_gpio.h> +#include <gpio.h> +#include <fb.h> +#include <video/vpl.h> + +#include <i2c/i2c.h> + +struct mtl017 { + struct vpl vpl; + struct device_d *dev; + struct i2c_client *client; + u8 *regs; + int enable_gpio; + int enable_active_high; + int reset_gpio; + int reset_active_high; + struct regulator *regulator; +}; + +/* + * Unfortunately we know nothing about the mtl017 except the following + * register tables which are derived from the Efika kernel. The displays + * provide EDID data, but this does not work with the mtl017 or at least + * not with the below register settings, so we have to provide hardcoded + * modelines and use the EDID data only to match against the vendor/display. + */ +static u8 mtl017_44_54_tbl[] = { + /* 44M to 53.9M */ + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x51, 0x00, + 0x00, 0x04, 0x17, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x00, 0x3B, 0x01, 0x08, 0x00, 0x1E, 0x01, 0x05, + 0x00, 0x01, 0x72, 0x05, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x20, 0xA8, 0x02, 0x12, 0x00, 0x58, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x01, 0x10, 0x01, 0x00, 0x00, 0x10, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x04, 0x12, 0x00, 0x58, 0x02, + 0x02, 0x7C, 0x04, 0x98, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +#define REGMAP_LENGTH (sizeof(mtl017_44_54_tbl) / sizeof(u8)) +#define BLOCK_TX_SIZE 32 + +struct mtl017_panel_info { + char *manufacturer; + char *product_name; + struct fb_videomode *mode; + u8 *regs; +}; + + +static struct fb_videomode auo_bw101aw02_mode = { + .name = "AUO B101AW02 1024x600", + .refresh = 60, + .xres = 1024, + .yres = 600, + .pixclock = 22800, + .left_margin = 80, + .right_margin = 40, + .upper_margin = 21, + .lower_margin = 21, + .hsync_len = 4, + .vsync_len = 4, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct mtl017_panel_info panels[] = { + { + .manufacturer = "AUO", + .product_name = "B101AW02 V0", + .mode = &auo_bw101aw02_mode, + .regs = mtl017_44_54_tbl, + }, + { + .manufacturer = "CMO", + .product_name = "N101L6-L0D", + .mode = &auo_bw101aw02_mode, + .regs = mtl017_44_54_tbl, + }, +}; + +static int mtl017_get_videomodes(struct mtl017 *mtl017, struct display_timings *timings) +{ + int ret, i; + + ret = vpl_ioctl(&mtl017->vpl, 1, VPL_GET_VIDEOMODES, timings); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(panels); i ++) { + if (memcmp(timings->edid + 0x71, panels[i].product_name, + strlen(panels[i].product_name))) + continue; + + dev_dbg(mtl017->dev, "found LCD Panel: %s %s\n", + panels[i].manufacturer, + panels[i].product_name); + mtl017->regs = panels[i].regs; + + timings->modes[0].name = panels[i].mode->name; + timings->modes[0].refresh = panels[i].mode->refresh; + timings->modes[0].xres = panels[i].mode->xres; + timings->modes[0].yres = panels[i].mode->yres; + timings->modes[0].pixclock = panels[i].mode->pixclock; + timings->modes[0].left_margin = panels[i].mode->left_margin; + timings->modes[0].right_margin = panels[i].mode->right_margin; + timings->modes[0].upper_margin = panels[i].mode->upper_margin; + timings->modes[0].lower_margin = panels[i].mode->lower_margin; + timings->modes[0].hsync_len = panels[i].mode->hsync_len; + timings->modes[0].vsync_len = panels[i].mode->vsync_len; + timings->num_modes = 1; + } + + return 0; +} + +static int mtl017_enable(struct mtl017 *mtl017) +{ + int i; + int ret; + int retry = 5; + u8 *reg_tbl = mtl017->regs; + + dev_dbg(mtl017->dev, "Initializing MTL017 LVDS Controller\n"); + + ret = regulator_enable(mtl017->regulator); + if (ret) + return ret; + + gpio_direction_output(mtl017->reset_gpio, mtl017->reset_active_high); + mdelay(5); + gpio_direction_output(mtl017->reset_gpio, !mtl017->reset_active_high); + mdelay(5); + gpio_direction_output(mtl017->enable_gpio, mtl017->enable_active_high); + + /* Write configuration table */ + for (i = 0; i < REGMAP_LENGTH; i+=BLOCK_TX_SIZE) { +retry: + mdelay(1); + ret = i2c_smbus_write_i2c_block_data(mtl017->client, i, BLOCK_TX_SIZE, &(reg_tbl[i])); + if (ret < 0) { + dev_warn(mtl017->dev, "failed to initialize: %d\n", ret); + if (retry-- > 0) + goto retry; + return -EIO; + } + } + + return 0; +} + +static int mtl017_ioctl(struct vpl *vpl, unsigned int port, + unsigned int cmd, void *ptr) +{ + struct mtl017 *mtl017 = container_of(vpl, + struct mtl017, vpl); + int ret = 0; + + switch (cmd) { + case VPL_PREPARE: + dev_dbg(mtl017->dev, "VPL_PREPARE\n"); + goto forward; + case VPL_ENABLE: + dev_dbg(mtl017->dev, "VPL_ENABLE\n"); + + ret = mtl017_enable(mtl017); + if (ret < 0) + break; + + goto forward; + case VPL_DISABLE: + dev_dbg(mtl017->dev, "VPL_DISABLE\n"); + + goto forward; + case VPL_GET_VIDEOMODES: + dev_dbg(mtl017->dev, "VPL_GET_VIDEOMODES\n"); + + ret = mtl017_get_videomodes(mtl017, ptr); + break; + default: + break; + } + + return ret; + +forward: + return vpl_ioctl(&mtl017->vpl, 1, cmd, ptr); +} + +static int mtl017_probe(struct device_d *dev) +{ + struct mtl017 *mtl017; + int ret; + enum of_gpio_flags flags; + + mtl017 = xzalloc(sizeof(struct mtl017)); + mtl017->vpl.node = dev->device_node; + mtl017->vpl.ioctl = mtl017_ioctl; + mtl017->dev = dev; + mtl017->client = to_i2c_client(dev); + mtl017->regulator = regulator_get(dev, "vdd"); + + mtl017->enable_gpio = of_get_named_gpio_flags(dev->device_node, + "enable-gpios", 0, &flags); + if (gpio_is_valid(mtl017->enable_gpio)) { + if (!(flags & OF_GPIO_ACTIVE_LOW)) + mtl017->enable_active_high = 1; + } + + mtl017->reset_gpio = of_get_named_gpio_flags(dev->device_node, + "reset-gpios", 0, &flags); + if (gpio_is_valid(mtl017->reset_gpio)) { + if (!(flags & OF_GPIO_ACTIVE_LOW)) + mtl017->reset_active_high = 1; + } + + ret = vpl_register(&mtl017->vpl); + if (ret) + return ret; + + return 0; +} + +static struct driver_d twl_driver = { + .name = "mtl017", + .probe = mtl017_probe, +}; + +static int mtl017_init(void) +{ + i2c_driver_register(&twl_driver); + return 0; +} + +device_initcall(mtl017_init); -- 2.1.4 _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox