blob: 5efa093180e340e8cf3135f55596fd277a3ba4a9 [file] [log] [blame]
/*
* Imagination Technologies Panel Display Interface (PDI).
*
* Copyright (C) 2012 Imagination Technologies
*
* Based on platform_lcd.c:
* Copyright 2008 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>
*
* 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/backlight.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/io.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <video/imgpdi_lcd.h>
/* Register offsets from base address */
#define PDI_SETUP 0x0000
#define PDI_TIMING0 0x0004
#define PDI_TIMING1 0x0008
#define PDI_COREID 0x0040
#define PDI_COREREVISION 0x0050
/* Register field descriptions */
#define PDI_SETUP_BLNKLVL (1 << 6) /* panel clock level during blanking */
#define PDI_SETUP_BLNK (1 << 5) /* clock blanking enable */
#define PDI_SETUP_PWR (1 << 4) /* panel power enable */
#define PDI_SETUP_EN (1 << 3) /* panel enable */
#define PDI_SETUP_GDEN (1 << 2) /* GD enable (active mode) */
#define PDI_SETUP_NFEN (1 << 1) /* NF enable (active mode) */
#define PDI_SETUP_CR (1 << 0) /* conversion mode active */
#define PDI_TIMING0_PWRSVGD (0xf << 24) /* delay in PCLKs-1 HSYNC to PWRSV,GD */
#define PDI_TIMING0_PWRSVGD_S 24
#define PDI_TIMING0_LSDEL (0x7f << 16) /* delay in PCLKs-1 HSYNC to LS */
#define PDI_TIMING0_LSDEL_S 16
#define PDI_TIMING0_PWRSV2GD2 (0x3ff << 0) /* delay in PCLKs-1 LS to end of PWRSV,GD */
#define PDI_TIMING0_PWRSV2GD2_S 0
#define PDI_TIMING1_NLDEL (0xf << 16) /* delay in PCLKs-1 HSYNC to NL */
#define PDI_TIMING1_NLDEL_S 16
#define PDI_TIMING1_ACBDEL (0x3ff << 0) /* delay in PCLKs-1 NL to end of ACB */
#define PDI_TIMING1_ACBDEL_S 0
#define PDI_COREID_GROUPID (0xff << 24) /* identifies IMG IP family group */
#define PDI_COREID_GROUPID_S 24
#define PDI_COREID_COREID (0xff << 16) /* identifies member of IP group */
#define PDI_COREID_COREID_S 16
#define PDI_COREID_CONFIG (0xffff << 0) /* configuration of core */
#define PDI_COREID_CONFIG_S 0
#define PDI_COREREV_MAJOR (0xff << 16) /* family major release revision */
#define PDI_COREREV_MAJOR_S 16
#define PDI_COREREV_MINOR (0xff << 8) /* core minor release revision */
#define PDI_COREREV_MINOR_S 8
#define PDI_COREREV_MAINT (0xff << 0) /* maintenance release revision */
#define PDI_COREREV_MAINT_S 0
struct imgpdi_lcd {
struct device *us;
struct lcd_device *lcd;
struct imgpdi_lcd_pdata *pdata;
void __iomem *base;
struct clk *clk;
unsigned int power;
unsigned int suspended:1;
};
static struct imgpdi_lcd *to_our_lcd(struct lcd_device *lcd)
{
return lcd_get_data(lcd);
}
static void imgpdi_write(struct imgpdi_lcd *plcd,
unsigned int reg_offs, unsigned int data)
{
iowrite32(data, plcd->base + reg_offs);
}
static unsigned int imgpdi_read(struct imgpdi_lcd *plcd,
unsigned int reg_offs)
{
return ioread32(plcd->base + reg_offs);
}
static void imgpdi_configure_en(struct imgpdi_lcd *plcd)
{
struct imgpdi_lcd_pdata *pdata = plcd->pdata;
struct imgpdi_lcd_timings *active = pdata->active;
unsigned int pdi_setup, pdi_timing0, pdi_timing1;
dev_dbg(plcd->us, "enable\n");
/* activate pdi */
if (active)
pdi_setup = PDI_SETUP_CR; /* active */
else
pdi_setup = 0; /* bypass */
imgpdi_write(plcd, PDI_SETUP, pdi_setup);
if (active) {
/* set up signal delays if in active mode */
pdi_timing0 = (active->pwrsvgd - 1) << PDI_TIMING0_PWRSVGD_S |
(active->ls - 1) << PDI_TIMING0_LSDEL_S |
(active->pwrsvgd2 - 1) << PDI_TIMING0_PWRSV2GD2_S;
pdi_timing1 = (active->nl - 1) << PDI_TIMING1_NLDEL_S |
(active->acb - 1) << PDI_TIMING1_ACBDEL_S;
imgpdi_write(plcd, PDI_TIMING0, pdi_timing0);
imgpdi_write(plcd, PDI_TIMING1, pdi_timing1);
if (active->gatedriver_en)
pdi_setup |= PDI_SETUP_GDEN;
if (active->newframe_en)
pdi_setup |= PDI_SETUP_NFEN;
if (active->blanking_en) {
pdi_setup |= PDI_SETUP_BLNK;
if (active->blanking_level)
pdi_setup |= PDI_SETUP_BLNKLVL;
}
imgpdi_write(plcd, PDI_SETUP, pdi_setup);
} else {
/* enable panel */
pdi_setup |= PDI_SETUP_EN;
imgpdi_write(plcd, PDI_SETUP, pdi_setup);
}
}
static void imgpdi_configure_dis(struct imgpdi_lcd *plcd)
{
dev_dbg(plcd->us, "disable\n");
/* disable panel */
imgpdi_write(plcd, PDI_SETUP, 0);
}
static void imgpdi_configure_pwr_en(struct imgpdi_lcd *plcd)
{
struct imgpdi_lcd_pdata *pdata = plcd->pdata;
unsigned int pdi_setup;
if (!pdata->active) {
dev_dbg(plcd->us, "power enable\n");
/* after 20ms enable power */
msleep(20);
pdi_setup = imgpdi_read(plcd, PDI_SETUP);
pdi_setup |= PDI_SETUP_PWR;
imgpdi_write(plcd, PDI_SETUP, pdi_setup);
}
}
static void imgpdi_configure_pwr_dis(struct imgpdi_lcd *plcd)
{
struct imgpdi_lcd_pdata *pdata = plcd->pdata;
unsigned int pdi_setup;
if (!pdata->active) {
dev_dbg(plcd->us, "power disable\n");
pdi_setup = imgpdi_read(plcd, PDI_SETUP);
pdi_setup &= ~PDI_SETUP_PWR;
imgpdi_write(plcd, PDI_SETUP, pdi_setup);
/* wait 20ms before disabling panel */
msleep(20);
}
}
static int imgpdi_lcd_get_power(struct lcd_device *lcd)
{
struct imgpdi_lcd *plcd = to_our_lcd(lcd);
return plcd->power;
}
static int imgpdi_lcd_set_power(struct lcd_device *lcd, int power)
{
struct imgpdi_lcd *plcd = to_our_lcd(lcd);
if (plcd->suspended)
power = FB_BLANK_POWERDOWN;
/*
* We use the following blank state mapping:
* 0: FB_BLANK_UNBLANK: en + power
* 1: FB_BLANK_NORMAL: en + power
* 2: FB_BLANK_VSYNC_SUSPEND: en
* 3: FB_BLANK_HSYNC_SUSPEND: en
* 4: FB_BLANK_POWERDOWN: (off)
*/
if (power < plcd->power) {
/* power up */
switch (plcd->power) {
case FB_BLANK_POWERDOWN:
if (power >= FB_BLANK_POWERDOWN)
break;
imgpdi_configure_en(plcd);
/* fall through */
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_VSYNC_SUSPEND:
if (power >= FB_BLANK_VSYNC_SUSPEND)
break;
imgpdi_configure_pwr_en(plcd);
default:
break;
};
} else {
/* power down */
switch (plcd->power) {
case FB_BLANK_UNBLANK:
case FB_BLANK_NORMAL:
if (power <= FB_BLANK_NORMAL)
break;
imgpdi_configure_pwr_dis(plcd);
/* fall through */
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
if (power <= FB_BLANK_HSYNC_SUSPEND)
break;
imgpdi_configure_dis(plcd);
default:
break;
};
}
plcd->power = power;
return 0;
}
static int imgpdi_lcd_match(struct lcd_device *lcd, struct fb_info *info)
{
struct imgpdi_lcd *plcd = to_our_lcd(lcd);
struct imgpdi_lcd_pdata *pdata = plcd->pdata;
if (pdata->match_fb)
return pdata->match_fb(pdata, info);
return plcd->us->parent == info->device;
}
static struct lcd_ops imgpdi_lcd_ops = {
.get_power = imgpdi_lcd_get_power,
.set_power = imgpdi_lcd_set_power,
.check_fb = imgpdi_lcd_match,
};
static void imgpdi_detect_state(struct imgpdi_lcd *plcd)
{
unsigned int pdi_setup;
pdi_setup = imgpdi_read(plcd, PDI_SETUP);
/* see imgpdi_lcd_set_power() for how blanking states map to PWR, EN */
if (pdi_setup & PDI_SETUP_PWR)
plcd->power = FB_BLANK_UNBLANK;
else if (pdi_setup & PDI_SETUP_EN)
plcd->power = FB_BLANK_HSYNC_SUSPEND;
else
plcd->power = FB_BLANK_POWERDOWN;
dev_dbg(plcd->us, "initial power = %d\n", plcd->power);
}
static void imgpdi_show_id(struct imgpdi_lcd *plcd)
{
unsigned int coreid, corerev;
coreid = imgpdi_read(plcd, PDI_COREID);
corerev = imgpdi_read(plcd, PDI_COREREVISION);
dev_info(plcd->us, "IMG PDI (id: %02x:%02x:%04x, revision: %u.%u.%u) probed successfully\n",
(coreid & PDI_COREID_GROUPID) >> PDI_COREID_GROUPID_S,
(coreid & PDI_COREID_COREID) >> PDI_COREID_COREID_S,
(coreid & PDI_COREID_CONFIG) >> PDI_COREID_CONFIG_S,
(corerev & PDI_COREREV_MAJOR) >> PDI_COREREV_MAJOR_S,
(corerev & PDI_COREREV_MINOR) >> PDI_COREREV_MINOR_S,
(corerev & PDI_COREREV_MAINT) >> PDI_COREREV_MAINT_S);
}
static int imgpdi_lcd_probe(struct platform_device *pdev)
{
struct imgpdi_lcd_pdata *pdata;
struct imgpdi_lcd *plcd;
struct device *dev = &pdev->dev;
struct resource *regs;
int err;
/* get register memory */
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!regs) {
dev_err(dev, "no register memory resource\n");
return -EINVAL;
}
/* get platform data */
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(dev, "no platform data supplied\n");
return -EINVAL;
}
/* create private data */
plcd = devm_kzalloc(&pdev->dev, sizeof(struct imgpdi_lcd),
GFP_KERNEL);
if (!plcd) {
dev_err(dev, "no memory for state\n");
return -ENOMEM;
}
plcd->us = dev;
plcd->pdata = pdata;
/* ioremap register memory */
plcd->base = devm_ioremap(dev, regs->start, regs->end - regs->start);
if (!plcd->base) {
dev_err(dev, "could not ioremap register memory\n");
return -ENOMEM;
}
/* get the clock */
plcd->clk = clk_get(dev, "pdi");
if (IS_ERR(plcd->clk)) {
dev_err(&pdev->dev, "could not get pdi clock resource\n");
return PTR_ERR(plcd->clk);
}
clk_prepare_enable(plcd->clk);
/* detect initial state */
imgpdi_detect_state(plcd);
/* register lcd device */
plcd->lcd = lcd_device_register(dev_name(dev), dev,
plcd, &imgpdi_lcd_ops);
if (IS_ERR(plcd->lcd)) {
dev_err(dev, "cannot register lcd device\n");
err = PTR_ERR(plcd->lcd);
goto err;
}
platform_set_drvdata(pdev, plcd);
imgpdi_show_id(plcd);
return 0;
err:
clk_disable_unprepare(plcd->clk);
clk_put(plcd->clk);
return err;
}
static int imgpdi_lcd_remove(struct platform_device *pdev)
{
struct imgpdi_lcd *plcd = platform_get_drvdata(pdev);
lcd_device_unregister(plcd->lcd);
clk_disable_unprepare(plcd->clk);
clk_put(plcd->clk);
return 0;
}
#ifdef CONFIG_PM
static int imgpdi_lcd_suspend(struct platform_device *pdev, pm_message_t st)
{
struct imgpdi_lcd *plcd = platform_get_drvdata(pdev);
plcd->suspended = 1;
imgpdi_lcd_set_power(plcd->lcd, plcd->power);
clk_disable_unprepare(plcd->clk);
return 0;
}
static int imgpdi_lcd_resume(struct platform_device *pdev)
{
struct imgpdi_lcd *plcd = platform_get_drvdata(pdev);
plcd->suspended = 0;
clk_prepare_enable(plcd->clk);
imgpdi_lcd_set_power(plcd->lcd, plcd->power);
return 0;
}
#else
#define imgpdi_lcd_suspend NULL
#define imgpdi_lcd_resume NULL
#endif
static struct platform_driver imgpdi_lcd_driver = {
.driver = {
.name = "imgpdi-lcd",
.owner = THIS_MODULE,
},
.probe = imgpdi_lcd_probe,
.remove = imgpdi_lcd_remove,
.suspend = imgpdi_lcd_suspend,
.resume = imgpdi_lcd_resume,
};
module_platform_driver(imgpdi_lcd_driver);
MODULE_AUTHOR("Imagination Technologies");
MODULE_DESCRIPTION("ImgTec Panel Display Interface (PDI) LCD Driver");
MODULE_LICENSE("GPL v2");