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@pengutronix.de>
281 lines
7.4 KiB
C
281 lines
7.4 KiB
C
/*
|
|
* (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);
|