forked from Minki/linux
9dfec4fe6d
This switches integrator platforms to use the consolidated CLCD panel support, including the display capabilities. These capabilities prevent the unsupported BGR565 mode being selected, while still allowing RGB5551, BGR5551 and RGB565 modes. Acked-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
483 lines
10 KiB
C
483 lines
10 KiB
C
/*
|
|
* linux/arch/arm/mach-integrator/impd1.c
|
|
*
|
|
* Copyright (C) 2003 Deep Blue Solutions Ltd, All Rights Reserved.
|
|
*
|
|
* 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.
|
|
*
|
|
* This file provides the core support for the IM-PD1 module.
|
|
*
|
|
* Module / boot parameters.
|
|
* lmid=n impd1.lmid=n - set the logic module position in stack to 'n'
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/init.h>
|
|
#include <linux/device.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/amba/bus.h>
|
|
#include <linux/amba/clcd.h>
|
|
#include <linux/io.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/clkdev.h>
|
|
|
|
#include <asm/hardware/icst.h>
|
|
#include <mach/lm.h>
|
|
#include <mach/impd1.h>
|
|
#include <asm/sizes.h>
|
|
|
|
static int module_id;
|
|
|
|
module_param_named(lmid, module_id, int, 0444);
|
|
MODULE_PARM_DESC(lmid, "logic module stack position");
|
|
|
|
struct impd1_module {
|
|
void __iomem *base;
|
|
struct clk vcos[2];
|
|
struct clk_lookup *clks[3];
|
|
};
|
|
|
|
static const struct icst_params impd1_vco_params = {
|
|
.ref = 24000000, /* 24 MHz */
|
|
.vco_max = ICST525_VCO_MAX_3V,
|
|
.vco_min = ICST525_VCO_MIN,
|
|
.vd_min = 12,
|
|
.vd_max = 519,
|
|
.rd_min = 3,
|
|
.rd_max = 120,
|
|
.s2div = icst525_s2div,
|
|
.idx2s = icst525_idx2s,
|
|
};
|
|
|
|
static void impd1_setvco(struct clk *clk, struct icst_vco vco)
|
|
{
|
|
struct impd1_module *impd1 = clk->data;
|
|
u32 val = vco.v | (vco.r << 9) | (vco.s << 16);
|
|
|
|
writel(0xa05f, impd1->base + IMPD1_LOCK);
|
|
writel(val, clk->vcoreg);
|
|
writel(0, impd1->base + IMPD1_LOCK);
|
|
|
|
#ifdef DEBUG
|
|
vco.v = val & 0x1ff;
|
|
vco.r = (val >> 9) & 0x7f;
|
|
vco.s = (val >> 16) & 7;
|
|
|
|
pr_debug("IM-PD1: VCO%d clock is %ld Hz\n",
|
|
vconr, icst525_hz(&impd1_vco_params, vco));
|
|
#endif
|
|
}
|
|
|
|
static const struct clk_ops impd1_clk_ops = {
|
|
.round = icst_clk_round,
|
|
.set = icst_clk_set,
|
|
.setvco = impd1_setvco,
|
|
};
|
|
|
|
void impd1_tweak_control(struct device *dev, u32 mask, u32 val)
|
|
{
|
|
struct impd1_module *impd1 = dev_get_drvdata(dev);
|
|
u32 cur;
|
|
|
|
val &= mask;
|
|
cur = readl(impd1->base + IMPD1_CTRL) & ~mask;
|
|
writel(cur | val, impd1->base + IMPD1_CTRL);
|
|
}
|
|
|
|
EXPORT_SYMBOL(impd1_tweak_control);
|
|
|
|
/*
|
|
* CLCD support
|
|
*/
|
|
#define PANEL PROSPECTOR
|
|
|
|
#define LTM10C209 1
|
|
#define PROSPECTOR 2
|
|
#define SVGA 3
|
|
#define VGA 4
|
|
|
|
#if PANEL == VGA
|
|
#define PANELTYPE vga
|
|
static struct clcd_panel vga = {
|
|
.mode = {
|
|
.name = "VGA",
|
|
.refresh = 60,
|
|
.xres = 640,
|
|
.yres = 480,
|
|
.pixclock = 39721,
|
|
.left_margin = 40,
|
|
.right_margin = 24,
|
|
.upper_margin = 32,
|
|
.lower_margin = 11,
|
|
.hsync_len = 96,
|
|
.vsync_len = 2,
|
|
.sync = 0,
|
|
.vmode = FB_VMODE_NONINTERLACED,
|
|
},
|
|
.width = -1,
|
|
.height = -1,
|
|
.tim2 = TIM2_BCD | TIM2_IPC,
|
|
.cntl = CNTL_LCDTFT | CNTL_LCDVCOMP(1),
|
|
.caps = CLCD_CAP_5551,
|
|
.connector = IMPD1_CTRL_DISP_VGA,
|
|
.bpp = 16,
|
|
.grayscale = 0,
|
|
};
|
|
|
|
#elif PANEL == SVGA
|
|
#define PANELTYPE svga
|
|
static struct clcd_panel svga = {
|
|
.mode = {
|
|
.name = "SVGA",
|
|
.refresh = 0,
|
|
.xres = 800,
|
|
.yres = 600,
|
|
.pixclock = 27778,
|
|
.left_margin = 20,
|
|
.right_margin = 20,
|
|
.upper_margin = 5,
|
|
.lower_margin = 5,
|
|
.hsync_len = 164,
|
|
.vsync_len = 62,
|
|
.sync = 0,
|
|
.vmode = FB_VMODE_NONINTERLACED,
|
|
},
|
|
.width = -1,
|
|
.height = -1,
|
|
.tim2 = TIM2_BCD,
|
|
.cntl = CNTL_LCDTFT | CNTL_LCDVCOMP(1),
|
|
.connector = IMPD1_CTRL_DISP_VGA,
|
|
.caps = CLCD_CAP_5551,
|
|
.bpp = 16,
|
|
.grayscale = 0,
|
|
};
|
|
|
|
#elif PANEL == PROSPECTOR
|
|
#define PANELTYPE prospector
|
|
static struct clcd_panel prospector = {
|
|
.mode = {
|
|
.name = "PROSPECTOR",
|
|
.refresh = 0,
|
|
.xres = 640,
|
|
.yres = 480,
|
|
.pixclock = 40000,
|
|
.left_margin = 33,
|
|
.right_margin = 64,
|
|
.upper_margin = 36,
|
|
.lower_margin = 7,
|
|
.hsync_len = 64,
|
|
.vsync_len = 25,
|
|
.sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
|
|
.vmode = FB_VMODE_NONINTERLACED,
|
|
},
|
|
.width = -1,
|
|
.height = -1,
|
|
.tim2 = TIM2_BCD,
|
|
.cntl = CNTL_LCDTFT | CNTL_LCDVCOMP(1),
|
|
.caps = CLCD_CAP_5551,
|
|
.fixedtimings = 1,
|
|
.connector = IMPD1_CTRL_DISP_LCD,
|
|
.bpp = 16,
|
|
.grayscale = 0,
|
|
};
|
|
|
|
#elif PANEL == LTM10C209
|
|
#define PANELTYPE ltm10c209
|
|
/*
|
|
* Untested.
|
|
*/
|
|
static struct clcd_panel ltm10c209 = {
|
|
.mode = {
|
|
.name = "LTM10C209",
|
|
.refresh = 0,
|
|
.xres = 640,
|
|
.yres = 480,
|
|
.pixclock = 40000,
|
|
.left_margin = 20,
|
|
.right_margin = 20,
|
|
.upper_margin = 19,
|
|
.lower_margin = 19,
|
|
.hsync_len = 20,
|
|
.vsync_len = 10,
|
|
.sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
|
|
.vmode = FB_VMODE_NONINTERLACED,
|
|
},
|
|
.width = -1,
|
|
.height = -1,
|
|
.tim2 = TIM2_BCD,
|
|
.cntl = CNTL_LCDTFT | CNTL_LCDVCOMP(1),
|
|
.caps = CLCD_CAP_5551,
|
|
.fixedtimings = 1,
|
|
.connector = IMPD1_CTRL_DISP_LCD,
|
|
.bpp = 16,
|
|
.grayscale = 0,
|
|
};
|
|
#endif
|
|
|
|
/*
|
|
* Disable all display connectors on the interface module.
|
|
*/
|
|
static void impd1fb_clcd_disable(struct clcd_fb *fb)
|
|
{
|
|
impd1_tweak_control(fb->dev->dev.parent, IMPD1_CTRL_DISP_MASK, 0);
|
|
}
|
|
|
|
/*
|
|
* Enable the relevant connector on the interface module.
|
|
*/
|
|
static void impd1fb_clcd_enable(struct clcd_fb *fb)
|
|
{
|
|
impd1_tweak_control(fb->dev->dev.parent, IMPD1_CTRL_DISP_MASK,
|
|
fb->panel->connector | IMPD1_CTRL_DISP_ENABLE);
|
|
}
|
|
|
|
static int impd1fb_clcd_setup(struct clcd_fb *fb)
|
|
{
|
|
unsigned long framebase = fb->dev->res.start + 0x01000000;
|
|
unsigned long framesize = SZ_1M;
|
|
int ret = 0;
|
|
|
|
fb->panel = &PANELTYPE;
|
|
|
|
if (!request_mem_region(framebase, framesize, "clcd framebuffer")) {
|
|
printk(KERN_ERR "IM-PD1: unable to reserve framebuffer\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
fb->fb.screen_base = ioremap(framebase, framesize);
|
|
if (!fb->fb.screen_base) {
|
|
printk(KERN_ERR "IM-PD1: unable to map framebuffer\n");
|
|
ret = -ENOMEM;
|
|
goto free_buffer;
|
|
}
|
|
|
|
fb->fb.fix.smem_start = framebase;
|
|
fb->fb.fix.smem_len = framesize;
|
|
|
|
return 0;
|
|
|
|
free_buffer:
|
|
release_mem_region(framebase, framesize);
|
|
return ret;
|
|
}
|
|
|
|
static int impd1fb_clcd_mmap(struct clcd_fb *fb, struct vm_area_struct *vma)
|
|
{
|
|
unsigned long start, size;
|
|
|
|
start = vma->vm_pgoff + (fb->fb.fix.smem_start >> PAGE_SHIFT);
|
|
size = vma->vm_end - vma->vm_start;
|
|
|
|
return remap_pfn_range(vma, vma->vm_start, start, size,
|
|
vma->vm_page_prot);
|
|
}
|
|
|
|
static void impd1fb_clcd_remove(struct clcd_fb *fb)
|
|
{
|
|
iounmap(fb->fb.screen_base);
|
|
release_mem_region(fb->fb.fix.smem_start, fb->fb.fix.smem_len);
|
|
}
|
|
|
|
static struct clcd_board impd1_clcd_data = {
|
|
.name = "IM-PD/1",
|
|
.caps = CLCD_CAP_5551 | CLCD_CAP_888,
|
|
.check = clcdfb_check,
|
|
.decode = clcdfb_decode,
|
|
.disable = impd1fb_clcd_disable,
|
|
.enable = impd1fb_clcd_enable,
|
|
.setup = impd1fb_clcd_setup,
|
|
.mmap = impd1fb_clcd_mmap,
|
|
.remove = impd1fb_clcd_remove,
|
|
};
|
|
|
|
struct impd1_device {
|
|
unsigned long offset;
|
|
unsigned int irq[2];
|
|
unsigned int id;
|
|
void *platform_data;
|
|
};
|
|
|
|
static struct impd1_device impd1_devs[] = {
|
|
{
|
|
.offset = 0x03000000,
|
|
.id = 0x00041190,
|
|
}, {
|
|
.offset = 0x00100000,
|
|
.irq = { 1 },
|
|
.id = 0x00141011,
|
|
}, {
|
|
.offset = 0x00200000,
|
|
.irq = { 2 },
|
|
.id = 0x00141011,
|
|
}, {
|
|
.offset = 0x00300000,
|
|
.irq = { 3 },
|
|
.id = 0x00041022,
|
|
}, {
|
|
.offset = 0x00400000,
|
|
.irq = { 4 },
|
|
.id = 0x00041061,
|
|
}, {
|
|
.offset = 0x00500000,
|
|
.irq = { 5 },
|
|
.id = 0x00041061,
|
|
}, {
|
|
.offset = 0x00600000,
|
|
.irq = { 6 },
|
|
.id = 0x00041130,
|
|
}, {
|
|
.offset = 0x00700000,
|
|
.irq = { 7, 8 },
|
|
.id = 0x00041181,
|
|
}, {
|
|
.offset = 0x00800000,
|
|
.irq = { 9 },
|
|
.id = 0x00041041,
|
|
}, {
|
|
.offset = 0x01000000,
|
|
.irq = { 11 },
|
|
.id = 0x00041110,
|
|
.platform_data = &impd1_clcd_data,
|
|
}
|
|
};
|
|
|
|
static struct clk fixed_14745600 = {
|
|
.rate = 14745600,
|
|
};
|
|
|
|
static int impd1_probe(struct lm_device *dev)
|
|
{
|
|
struct impd1_module *impd1;
|
|
int i, ret;
|
|
|
|
if (dev->id != module_id)
|
|
return -EINVAL;
|
|
|
|
if (!request_mem_region(dev->resource.start, SZ_4K, "LM registers"))
|
|
return -EBUSY;
|
|
|
|
impd1 = kzalloc(sizeof(struct impd1_module), GFP_KERNEL);
|
|
if (!impd1) {
|
|
ret = -ENOMEM;
|
|
goto release_lm;
|
|
}
|
|
|
|
impd1->base = ioremap(dev->resource.start, SZ_4K);
|
|
if (!impd1->base) {
|
|
ret = -ENOMEM;
|
|
goto free_impd1;
|
|
}
|
|
|
|
lm_set_drvdata(dev, impd1);
|
|
|
|
printk("IM-PD1 found at 0x%08lx\n",
|
|
(unsigned long)dev->resource.start);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(impd1->vcos); i++) {
|
|
impd1->vcos[i].ops = &impd1_clk_ops,
|
|
impd1->vcos[i].owner = THIS_MODULE,
|
|
impd1->vcos[i].params = &impd1_vco_params,
|
|
impd1->vcos[i].data = impd1;
|
|
}
|
|
impd1->vcos[0].vcoreg = impd1->base + IMPD1_OSC1;
|
|
impd1->vcos[1].vcoreg = impd1->base + IMPD1_OSC2;
|
|
|
|
impd1->clks[0] = clkdev_alloc(&impd1->vcos[0], NULL, "lm%x:01000",
|
|
dev->id);
|
|
impd1->clks[1] = clkdev_alloc(&fixed_14745600, NULL, "lm%x:00100",
|
|
dev->id);
|
|
impd1->clks[2] = clkdev_alloc(&fixed_14745600, NULL, "lm%x:00200",
|
|
dev->id);
|
|
for (i = 0; i < ARRAY_SIZE(impd1->clks); i++)
|
|
clkdev_add(impd1->clks[i]);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(impd1_devs); i++) {
|
|
struct impd1_device *idev = impd1_devs + i;
|
|
struct amba_device *d;
|
|
unsigned long pc_base;
|
|
|
|
pc_base = dev->resource.start + idev->offset;
|
|
|
|
d = kzalloc(sizeof(struct amba_device), GFP_KERNEL);
|
|
if (!d)
|
|
continue;
|
|
|
|
dev_set_name(&d->dev, "lm%x:%5.5lx", dev->id, idev->offset >> 12);
|
|
d->dev.parent = &dev->dev;
|
|
d->res.start = dev->resource.start + idev->offset;
|
|
d->res.end = d->res.start + SZ_4K - 1;
|
|
d->res.flags = IORESOURCE_MEM;
|
|
d->irq[0] = dev->irq;
|
|
d->irq[1] = dev->irq;
|
|
d->periphid = idev->id;
|
|
d->dev.platform_data = idev->platform_data;
|
|
|
|
ret = amba_device_register(d, &dev->resource);
|
|
if (ret) {
|
|
dev_err(&d->dev, "unable to register device: %d\n", ret);
|
|
kfree(d);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
free_impd1:
|
|
if (impd1 && impd1->base)
|
|
iounmap(impd1->base);
|
|
kfree(impd1);
|
|
release_lm:
|
|
release_mem_region(dev->resource.start, SZ_4K);
|
|
return ret;
|
|
}
|
|
|
|
static int impd1_remove_one(struct device *dev, void *data)
|
|
{
|
|
device_unregister(dev);
|
|
return 0;
|
|
}
|
|
|
|
static void impd1_remove(struct lm_device *dev)
|
|
{
|
|
struct impd1_module *impd1 = lm_get_drvdata(dev);
|
|
int i;
|
|
|
|
device_for_each_child(&dev->dev, NULL, impd1_remove_one);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(impd1->clks); i++)
|
|
clkdev_drop(impd1->clks[i]);
|
|
|
|
lm_set_drvdata(dev, NULL);
|
|
|
|
iounmap(impd1->base);
|
|
kfree(impd1);
|
|
release_mem_region(dev->resource.start, SZ_4K);
|
|
}
|
|
|
|
static struct lm_driver impd1_driver = {
|
|
.drv = {
|
|
.name = "impd1",
|
|
},
|
|
.probe = impd1_probe,
|
|
.remove = impd1_remove,
|
|
};
|
|
|
|
static int __init impd1_init(void)
|
|
{
|
|
return lm_driver_register(&impd1_driver);
|
|
}
|
|
|
|
static void __exit impd1_exit(void)
|
|
{
|
|
lm_driver_unregister(&impd1_driver);
|
|
}
|
|
|
|
module_init(impd1_init);
|
|
module_exit(impd1_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Integrator/IM-PD1 logic module core driver");
|
|
MODULE_AUTHOR("Deep Blue Solutions Ltd");
|