alistair23-linux/arch/arm/mach-integrator/impd1.c
Linus Walleij 684284b64a ARM: integrator: add MMCI device to IM-PD1
The IM-PD1 logic module daughterboard holds an MMCI block, which
we can now provide using platform resources such as proper GPIO
lines etc. We add the GPIO table dynamically and using the new
GPIO descriptor mechanism. Tested and hey, it works:

root@integrator:/ mount /dev/mmcblk0p1 /mnt/
root@integrator:/ ls /mnt/
ARM          U-BOOT.EXE   u-boot.bin   u-boot.srec  u-pad.bin

Reviewed-by: Alexandre Courbot <gnurou@gmail.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
2014-08-19 12:14:48 -05:00

479 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/amba/mmci.h>
#include <linux/amba/pl061.h>
#include <linux/io.h>
#include <linux/platform_data/clk-integrator.h>
#include <linux/slab.h>
#include <linux/irqchip/arm-vic.h>
#include <linux/gpio/machine.h>
#include <asm/sizes.h>
#include "lm.h"
#include "impd1.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;
void __iomem *vic_base;
};
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);
/*
* MMC support
*/
static struct mmci_platform_data mmc_data = {
.ocr_mask = MMC_VDD_32_33|MMC_VDD_33_34,
};
/*
* 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 = 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,
.platform_data = &mmc_data,
}, {
.offset = 0x00800000,
.irq = { 9 },
.id = 0x00041041,
}, {
.offset = 0x01000000,
.irq = { 11 },
.id = 0x00041110,
.platform_data = &impd1_clcd_data,
}
};
/*
* Valid IRQs: 0 thru 9 and 11, 10 unused.
*/
#define IMPD1_VALID_IRQS 0x00000bffU
/*
* As this module is bool, it is OK to have this as __init_refok() - no
* probe calls will be done after the initial system bootup, as devices
* are discovered as part of the machine startup.
*/
static int __init_refok impd1_probe(struct lm_device *dev)
{
struct impd1_module *impd1;
int irq_base;
int i;
if (dev->id != module_id)
return -EINVAL;
if (!devm_request_mem_region(&dev->dev, dev->resource.start,
SZ_4K, "LM registers"))
return -EBUSY;
impd1 = devm_kzalloc(&dev->dev, sizeof(struct impd1_module),
GFP_KERNEL);
if (!impd1)
return -ENOMEM;
impd1->base = devm_ioremap(&dev->dev, dev->resource.start, SZ_4K);
if (!impd1->base)
return -ENOMEM;
integrator_impd1_clk_init(impd1->base, dev->id);
if (!devm_request_mem_region(&dev->dev,
dev->resource.start + 0x03000000,
SZ_4K, "VIC"))
return -EBUSY;
impd1->vic_base = devm_ioremap(&dev->dev,
dev->resource.start + 0x03000000,
SZ_4K);
if (!impd1->vic_base)
return -ENOMEM;
irq_base = vic_init_cascaded(impd1->vic_base, dev->irq,
IMPD1_VALID_IRQS, 0);
lm_set_drvdata(dev, impd1);
dev_info(&dev->dev, "IM-PD1 found at 0x%08lx\n",
(unsigned long)dev->resource.start);
for (i = 0; i < ARRAY_SIZE(impd1_devs); i++) {
struct impd1_device *idev = impd1_devs + i;
struct amba_device *d;
unsigned long pc_base;
char devname[32];
int irq1 = idev->irq[0];
int irq2 = idev->irq[1];
/* Translate IRQs to IM-PD1 local numberspace */
if (irq1)
irq1 += irq_base;
if (irq2)
irq2 += irq_base;
pc_base = dev->resource.start + idev->offset;
snprintf(devname, 32, "lm%x:%5.5lx", dev->id, idev->offset >> 12);
/* Add GPIO descriptor lookup table for the PL061 block */
if (idev->offset == 0x00400000) {
struct gpiod_lookup_table *lookup;
char *chipname;
char *mmciname;
lookup = devm_kzalloc(&dev->dev,
sizeof(*lookup) + 3 * sizeof(struct gpiod_lookup),
GFP_KERNEL);
chipname = devm_kstrdup(&dev->dev, devname, GFP_KERNEL);
mmciname = kasprintf(GFP_KERNEL, "lm%x:00700", dev->id);
lookup->dev_id = mmciname;
/*
* Offsets on GPIO block 1:
* 3 = MMC WP (write protect)
* 4 = MMC CD (card detect)
*
* Offsets on GPIO block 2:
* 0 = Up key
* 1 = Down key
* 2 = Left key
* 3 = Right key
* 4 = Key lower left
* 5 = Key lower right
*/
/* We need the two MMCI GPIO entries */
lookup->table[0].chip_label = chipname;
lookup->table[0].chip_hwnum = 3;
lookup->table[0].con_id = "wp";
lookup->table[1].chip_label = chipname;
lookup->table[1].chip_hwnum = 4;
lookup->table[1].con_id = "cd";
lookup->table[1].flags = GPIO_ACTIVE_LOW;
gpiod_add_lookup_table(lookup);
}
d = amba_ahb_device_add_res(&dev->dev, devname, pc_base, SZ_4K,
irq1, irq2,
idev->platform_data, idev->id,
&dev->resource);
if (IS_ERR(d)) {
dev_err(&dev->dev, "unable to register device: %ld\n", PTR_ERR(d));
continue;
}
}
return 0;
}
static int impd1_remove_one(struct device *dev, void *data)
{
device_unregister(dev);
return 0;
}
static void impd1_remove(struct lm_device *dev)
{
device_for_each_child(&dev->dev, NULL, impd1_remove_one);
integrator_impd1_clk_exit(dev->id);
lm_set_drvdata(dev, NULL);
}
static struct lm_driver impd1_driver = {
.drv = {
.name = "impd1",
/*
* As we're dropping the probe() function, suppress driver
* binding from sysfs.
*/
.suppress_bind_attrs = true,
},
.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");