blob: d49d5f57b1eae6ae7849de84109a302fdc568a0d [file] [log] [blame]
/*
* IMG LCD controller driver.
*
* Copyright (C) 2006,2007,2008,2009,2010,2012 Imagination Technologies Ltd.
*
* 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; version 2 of the License.
*
* 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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/io.h>
#include <linux/completion.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/img_mdc_dma.h>
#include <linux/img_lcd.h>
#define LCD_REG0 0x0
#define LCD_REG1 0x4
#define LCD_REG2 0x8
#define LCD_REG3 0xC
#define LCD_ISTAT_REG 0x10
#define LCD_IENABLE_REG 0x14
#define LCD_ICLEAR_REG 0x18
#define LCD_DMA_STATUS_REG 0x1C
#define LCD_DMA_ENABLE_REG 0x20
#define LCD_DMA_CLEAR_REG 0x24
#define LCD_DMA_READ_REG 0x28
#define LCD_DMA_SIZE_REG 0x2C
#define REG0_L_H_DELAY_SHIFT 28
#define REG0_P_H_WIDTH_SHIFT 24
#define REG0_P_H_DELAY_SHIFT 20
#define REG0_D_DELAY_SHIFT 16
#define REG0_D_PERIOD_SHIFT 0
#define REG1_T_DIV_SHIFT 28
#define REG1_L_TRIGGER_SHIFT 16
#define REG1_F_H_WIDTH_SHIFT 8
#define REG1_F_H_DELAY_SHIFT 4
#define REG1_L_H_WIDTH_SHIFT 0
#define REG2_CMD 0x00000000
#define REG2_DATA 0x80000000
#define REG2_MODE_ALPH 0x00000000
#define REG2_MODE_GRAPH 0x40000000
#define REG2_MODE_BIT 0x20000000
#define REG2_MODE_BYTE 0x10000000
#define REG2_MODE_NIBBLE 0x00000000
#define REG2_DB_WIDTH_SHIFT 28
#define REG2_ORDER 0x08000000
#define REG2_L_PER_F_SHIFT 16
#define REG2_M_DELAY_SHIFT 12
#define REG2_D_PER_L_SHIFT 0
#define REG3_BUSY 4
#define DMA_INT_EMPTY 1
#define ALL_OP_FIN 0x4
#define MAX_BLOCK_SIZE PAGE_SIZE
#define IDLE_TIMEOUT 2
enum etimings {
data,
instruction,
};
static struct alpha_speed_struct cmd_speed = {
/* Default works for an 8bit KS0066U 2x16 display */
13517, /* d_period */
3, /* p_h_width */
0, /* p_h_delay */
2 /* t_div */
};
static struct alpha_speed_struct data_speed = {
/* Default works for an 8bit KS0066U 2x16 display */
375, /* d_period */
3, /* p_h_width */
0, /* p_h_delay */
2 /* t_div */
};
struct alpha_device {
struct mutex mutex;
int irq;
struct dma_chan *dma_channel;
void *buf;
dma_addr_t dmabuf;
struct completion dma_complete;
/* Default to sending LSB first */
unsigned int msb_choice;
/* Default to talking in bytes. Can be modified by ioctl */
unsigned int width_bits;
void __iomem *regs_base;
void (*enable_cs)(void);
void (*disable_cs)(void);
struct device *dev;
};
static struct alpha_device alpha_device;
static void program_timing(struct alpha_speed_struct *timing)
{
unsigned int v;
v = timing->t_div << REG1_T_DIV_SHIFT;
writel(v, alpha_device.regs_base + LCD_REG1);
v = timing->p_h_delay << REG0_P_H_DELAY_SHIFT;
v |= timing->p_h_width << REG0_P_H_WIDTH_SHIFT;
v |= timing->d_period << REG0_D_PERIOD_SHIFT;
writel(v, alpha_device.regs_base + LCD_REG0);
}
static void instr_timing(void)
{
program_timing(&cmd_speed);
}
static void data_timing(void)
{
program_timing(&data_speed);
}
static int set_width(struct alpha_width_struct __user *addr)
{
struct alpha_width_struct s;
if (copy_from_user(&s, addr, sizeof(s)))
return -EFAULT;
/* Set the mask here, which is then used in all 'chats' to reg2 */
switch (s.width) {
case 1:
alpha_device.width_bits = REG2_MODE_BIT;
break;
case 4:
alpha_device.width_bits = REG2_MODE_NIBBLE;
break;
case 8:
alpha_device.width_bits = REG2_MODE_BYTE;
break;
default:
return -ERANGE;
}
if (s.msb)
alpha_device.msb_choice = REG2_ORDER;
else
alpha_device.msb_choice = 0;
return 0;
}
static int validate_speed_struct(struct alpha_speed_struct __user *addr,
struct alpha_speed_struct *s)
{
if (copy_from_user(s, addr, sizeof(*s)))
return -EFAULT;
/* Do some verification according to the rules on the last page of
* the LCD C2 module TRM.
*/
if (s->p_h_width + s->p_h_delay >= s->d_period) {
dev_warn(alpha_device.dev,
"alpha data timing breaks d_period rule\n");
return -EINVAL;
}
dev_dbg(alpha_device.dev, "d_period %d\n", s->d_period);
dev_dbg(alpha_device.dev, "p_h_width %d\n", s->p_h_width);
dev_dbg(alpha_device.dev, "p_h_delay %d\n", s->p_h_delay);
dev_dbg(alpha_device.dev, "t_div %d\n", s->t_div);
return 0;
}
static int set_data_speed(struct alpha_speed_struct __user *addr)
{
int ret;
struct alpha_speed_struct s;
ret = validate_speed_struct(addr, &s);
if (!ret)
data_speed = s;
return ret;
}
static int set_cmd_speed(struct alpha_speed_struct __user *addr)
{
int ret;
struct alpha_speed_struct s;
ret = validate_speed_struct(addr, &s);
if (!ret)
cmd_speed = s;
return ret;
}
static int wait_for_idle(void __iomem *regs_base)
{
unsigned long start_time = jiffies;
while (!time_after_eq(jiffies, start_time + IDLE_TIMEOUT)) {
unsigned int status = readl(regs_base + LCD_REG3);
if (!(status & REG3_BUSY))
return 0;
}
return 1;
}
static int send_byte(unsigned char c, enum etimings type)
{
void __iomem *regs_base = alpha_device.regs_base;
unsigned int v;
/* Check for busy and tell the user */
if (wait_for_idle(regs_base))
return -EIO;
v = alpha_device.msb_choice | alpha_device.width_bits | REG2_MODE_ALPH;
init_completion(&alpha_device.dma_complete);
/* Put the byte in the FIFO */
writel(1, regs_base + LCD_DMA_SIZE_REG);
writel(c, regs_base + LCD_DMA_READ_REG);
/* Enable all operation complete interrupt*/
writel(ALL_OP_FIN, regs_base + LCD_ICLEAR_REG); /*clear first*/
writel(ALL_OP_FIN, regs_base + LCD_IENABLE_REG);
/* Setup the clocks and kick off the transfer */
if (type == data) {
data_timing();
v |= REG2_DATA;
} else {
instr_timing();
v |= REG2_CMD;
}
if (alpha_device.enable_cs)
alpha_device.enable_cs();
writel(v, regs_base + LCD_REG2);
if (wait_for_completion_interruptible(&alpha_device.dma_complete))
return -ERESTARTSYS;
return 0;
}
#define BURST_SIZE 4
static int
send_block(struct alpha_data_block __user *addr, enum etimings timings)
{
void __iomem *regs_base = alpha_device.regs_base;
struct dma_chan *dma_channel = alpha_device.dma_channel;
struct alpha_data_block block;
unsigned int v;
struct dma_async_tx_descriptor *desc;
dma_cookie_t txcookie;
struct dma_slave_config conf;
if (copy_from_user(&block, addr, sizeof(block)))
return -EFAULT;
if (!block.len)
return -EINVAL;
if (block.len > MAX_BLOCK_SIZE)
return -EINVAL;
/* Check for busy and tell the user */
if (wait_for_idle(regs_base))
return -EIO;
if (copy_from_user(alpha_device.buf, block.p, block.len))
return -EFAULT;
init_completion(&alpha_device.dma_complete);
conf.direction = DMA_MEM_TO_DEV;
conf.dst_addr = (unsigned int)regs_base + LCD_DMA_READ_REG;
conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
conf.dst_maxburst = BURST_SIZE;
dmaengine_slave_config(dma_channel, &conf);
desc = dmaengine_prep_slave_single(dma_channel, alpha_device.dmabuf,
block.len, DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT|DMA_CTRL_ACK);
if (!desc)
return -ENOMEM;
if (timings == data)
data_timing();
else
instr_timing();
writel(block.len, regs_base + LCD_DMA_SIZE_REG);
/*Enable all operation complete interrupt*/
writel(ALL_OP_FIN, regs_base + LCD_ICLEAR_REG); /*clear first*/
writel(ALL_OP_FIN, regs_base + LCD_IENABLE_REG);
txcookie = dmaengine_submit(desc);
dma_async_issue_pending(dma_channel);
/* Kick off the transfer */
if (timings == data)
v = alpha_device.msb_choice | REG2_DATA | REG2_MODE_ALPH;
else
v = alpha_device.msb_choice | REG2_CMD | REG2_MODE_ALPH;
v |= alpha_device.width_bits;
if (alpha_device.enable_cs)
alpha_device.enable_cs();
writel(v, regs_base + LCD_REG2);
if (wait_for_completion_interruptible(&alpha_device.dma_complete))
return -ERESTARTSYS;
return 0;
}
static irqreturn_t alpha_dma_irq(int irq, void *dev_id)
{
void __iomem *regs_base = dev_id;
writel(0, regs_base + LCD_IENABLE_REG);
writel(ALL_OP_FIN, regs_base + LCD_ICLEAR_REG);
#ifdef CONFIG_SOC_TZ1090
{
u32 rem = readl(regs_base + LCD_DMA_SIZE_REG);
/*
* Check that this is not a spurious interrupt - this can
* occur due to a h/w problem whereby FIFO underrun causes
* 'Alpha op finished' interrupt
*/
if (rem)
return IRQ_HANDLED;
}
#endif
if (alpha_device.disable_cs)
alpha_device.disable_cs();
complete(&alpha_device.dma_complete);
return IRQ_HANDLED;
}
static long alpha_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
long result;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != ALPHA_IOCTL)
return -ENOTTY;
/* Single ioctl at a time please!
*/
if (mutex_lock_interruptible(&alpha_device.mutex))
return -ERESTARTSYS;
switch (cmd) {
case ALPHA_INSTR:
pr_debug(" instr %#x\n", (char)arg);
result = send_byte((char)arg, instruction);
pr_debug(" instr done\n");
break;
case ALPHA_DATA:
pr_debug(" data %#x\n", (char)arg);
result = send_byte((char)arg, data);
pr_debug(" data done\n");
break;
case ALPHA_DATA_BLOCK:
pr_debug(" data block %#lx\n", arg);
result = send_block((struct alpha_data_block __user *)arg, data);
pr_debug(" data block done\n");
break;
case ALPHA_WIDTH:
pr_debug(" width\n");
result = set_width((struct alpha_width_struct __user *)arg);
pr_debug(" width done\n");
break;
case ALPHA_DATA_SPEED:
pr_debug(" data speed\n");
result = set_data_speed((struct alpha_speed_struct __user *)arg);
pr_debug(" data speed done\n");
break;
case ALPHA_COMMAND_SPEED:
pr_debug(" cmd speed\n");
result = set_cmd_speed((struct alpha_speed_struct __user *)arg);
pr_debug(" cmd speed done\n");
break;
case ALPHA_COMMAND_BLOCK:
pr_debug(" cmd block");
result = send_block((struct alpha_data_block __user *)arg, instruction);
pr_debug(" cmd block done\n");
break;
default:
result = -EINVAL;
}
mutex_unlock(&alpha_device.mutex);
return result;
}
static const struct file_operations alpha_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = alpha_ioctl,
};
static struct miscdevice alpha_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "alpha_lcd",
.fops = &alpha_fops,
};
static int __init img_lcd_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *irq_resource, *mem_resource, *dma_resource;
struct img_lcd_board *pdata;
struct mdc_dma_cookie *cookie;
dma_cap_mask_t mask;
int result;
mem_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_resource) {
dev_err(dev, "mem resource not defined\n");
result = -ENODEV;
goto err_resource;
}
irq_resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!irq_resource) {
dev_err(dev, "irq resource not defined\n");
result = -ENODEV;
goto err_resource;
}
dma_resource = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!dma_resource) {
dev_err(dev, "dma resource not defined\n");
result = -ENODEV;
goto err_resource;
}
mutex_init(&alpha_device.mutex);
alpha_device.buf = dma_alloc_coherent(dev, MAX_BLOCK_SIZE,
&alpha_device.dmabuf, GFP_KERNEL);
if (alpha_device.buf == NULL) {
dev_err(&pdev->dev, "failed to allocate DMA buffer\n");
result = -ENOMEM;
goto err_resource;
}
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
if (!cookie) {
result = -ENOMEM;
goto err_free_dma_buffer;
}
cookie->periph = dma_resource->start;
cookie->req_channel = -1;
alpha_device.dma_channel = dma_request_channel(mask,
&mdc_dma_filter_fn,
cookie);
if (!alpha_device.dma_channel) {
dev_err(&pdev->dev, "failed to get DMA channel\n");
result = -EBUSY;
goto err_free_cookie;
}
alpha_device.regs_base = (void __iomem *)mem_resource->start;
alpha_device.irq = irq_resource->start;
if (request_irq(alpha_device.irq, alpha_dma_irq, 0, "img-lcd",
alpha_device.regs_base)) {
dev_err(&pdev->dev, "failed to get irq\n");
result = -EBUSY;
goto err_free_channel;
}
alpha_device.width_bits = REG2_MODE_BYTE;
alpha_device.dev = dev;
pdata = (struct img_lcd_board *)pdev->dev.platform_data;
if (pdata) {
alpha_device.enable_cs = pdata->enable_cs;
alpha_device.disable_cs = pdata->disable_cs;
}
result = misc_register(&alpha_misc_device);
if (result) {
dev_err(dev, "misc_register failed\n");
goto err_free_irq;
}
dev_info(dev, "probed successfully\n");
kfree(cookie);
return 0;
err_free_irq:
free_irq(alpha_device.irq, NULL);
err_free_dma_buffer:
dma_free_coherent(dev, MAX_BLOCK_SIZE, alpha_device.buf,
alpha_device.dmabuf);
err_free_channel:
dma_release_channel(alpha_device.dma_channel);
err_free_cookie:
kfree(cookie);
err_resource:
return result;
}
static int __exit img_lcd_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
misc_deregister(&alpha_misc_device);
free_irq(alpha_device.irq, NULL);
dma_release_channel(alpha_device.dma_channel);
dma_free_coherent(dev, MAX_BLOCK_SIZE, alpha_device.buf,
alpha_device.dmabuf);
return 0;
}
#ifdef CONFIG_PM
static int img_lcd_suspend(struct platform_device *pdev,
pm_message_t state)
{
/* FIXME Can we do anything here to power down? */
return 0;
}
static int img_lcd_resume(struct platform_device *pdev)
{
return 0;
}
#else
#define img_lcd_suspend NULL
#define img_lcd_resume NULL
#endif /* CONFIG_PM */
MODULE_ALIAS("platform:img-lcd"); /* for platform bus hotplug */
static struct platform_driver img_lcd_driver = {
.driver = {
.name = "img-lcd",
.owner = THIS_MODULE,
},
.suspend = img_lcd_suspend,
.resume = img_lcd_resume,
.remove = __exit_p(img_lcd_remove),
};
static int __init img_lcd_init(void)
{
return platform_driver_probe(&img_lcd_driver, img_lcd_probe);
}
module_init(img_lcd_init);
static void __exit img_lcd_exit(void)
{
platform_driver_unregister(&img_lcd_driver);
}
module_exit(img_lcd_exit);
MODULE_AUTHOR("Imagination Technologies Ltd.");
MODULE_DESCRIPTION("IMG LCD controller");
MODULE_LICENSE("GPL");