1/*
2 *  linux/arch/arm/mach-integrator/impd1.c
3 *
4 *  Copyright (C) 2003 Deep Blue Solutions Ltd, All Rights Reserved.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 *  This file provides the core support for the IM-PD1 module.
11 *
12 * Module / boot parameters.
13 *   lmid=n   impd1.lmid=n - set the logic module position in stack to 'n'
14 */
15#include <linux/module.h>
16#include <linux/moduleparam.h>
17#include <linux/init.h>
18#include <linux/device.h>
19#include <linux/errno.h>
20#include <linux/mm.h>
21#include <linux/amba/bus.h>
22#include <linux/amba/clcd.h>
23#include <linux/amba/mmci.h>
24#include <linux/amba/pl061.h>
25#include <linux/io.h>
26#include <linux/platform_data/clk-integrator.h>
27#include <linux/slab.h>
28#include <linux/irqchip/arm-vic.h>
29#include <linux/gpio/machine.h>
30
31#include <asm/sizes.h>
32#include "lm.h"
33#include "impd1.h"
34
35static int module_id;
36
37module_param_named(lmid, module_id, int, 0444);
38MODULE_PARM_DESC(lmid, "logic module stack position");
39
40struct impd1_module {
41	void __iomem	*base;
42	void __iomem	*vic_base;
43};
44
45void impd1_tweak_control(struct device *dev, u32 mask, u32 val)
46{
47	struct impd1_module *impd1 = dev_get_drvdata(dev);
48	u32 cur;
49
50	val &= mask;
51	cur = readl(impd1->base + IMPD1_CTRL) & ~mask;
52	writel(cur | val, impd1->base + IMPD1_CTRL);
53}
54
55EXPORT_SYMBOL(impd1_tweak_control);
56
57/*
58 * MMC support
59 */
60static struct mmci_platform_data mmc_data = {
61	.ocr_mask	= MMC_VDD_32_33|MMC_VDD_33_34,
62};
63
64/*
65 * CLCD support
66 */
67#define PANEL		PROSPECTOR
68
69#define LTM10C209		1
70#define PROSPECTOR		2
71#define SVGA			3
72#define VGA			4
73
74#if PANEL == VGA
75#define PANELTYPE	vga
76static struct clcd_panel vga = {
77	.mode		= {
78		.name		= "VGA",
79		.refresh	= 60,
80		.xres		= 640,
81		.yres		= 480,
82		.pixclock	= 39721,
83		.left_margin	= 40,
84		.right_margin	= 24,
85		.upper_margin	= 32,
86		.lower_margin	= 11,
87		.hsync_len	= 96,
88		.vsync_len	= 2,
89		.sync		= 0,
90		.vmode		= FB_VMODE_NONINTERLACED,
91	},
92	.width		= -1,
93	.height		= -1,
94	.tim2		= TIM2_BCD | TIM2_IPC,
95	.cntl		= CNTL_LCDTFT | CNTL_LCDVCOMP(1),
96	.caps		= CLCD_CAP_5551,
97	.connector	= IMPD1_CTRL_DISP_VGA,
98	.bpp		= 16,
99	.grayscale	= 0,
100};
101
102#elif PANEL == SVGA
103#define PANELTYPE	svga
104static struct clcd_panel svga = {
105	.mode		= {
106		.name		= "SVGA",
107		.refresh	= 0,
108		.xres		= 800,
109		.yres		= 600,
110		.pixclock	= 27778,
111		.left_margin	= 20,
112		.right_margin	= 20,
113		.upper_margin	= 5,
114		.lower_margin	= 5,
115		.hsync_len	= 164,
116		.vsync_len	= 62,
117		.sync		= 0,
118		.vmode		= FB_VMODE_NONINTERLACED,
119	},
120	.width		= -1,
121	.height		= -1,
122	.tim2		= TIM2_BCD,
123	.cntl		= CNTL_LCDTFT | CNTL_LCDVCOMP(1),
124	.connector	= IMPD1_CTRL_DISP_VGA,
125	.caps		= CLCD_CAP_5551,
126	.bpp		= 16,
127	.grayscale	= 0,
128};
129
130#elif PANEL == PROSPECTOR
131#define PANELTYPE	prospector
132static struct clcd_panel prospector = {
133	.mode		= {
134		.name		= "PROSPECTOR",
135		.refresh	= 0,
136		.xres		= 640,
137		.yres		= 480,
138		.pixclock	= 40000,
139		.left_margin	= 33,
140		.right_margin	= 64,
141		.upper_margin	= 36,
142		.lower_margin	= 7,
143		.hsync_len	= 64,
144		.vsync_len	= 25,
145		.sync		= FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
146		.vmode		= FB_VMODE_NONINTERLACED,
147	},
148	.width		= -1,
149	.height		= -1,
150	.tim2		= TIM2_BCD,
151	.cntl		= CNTL_LCDTFT | CNTL_LCDVCOMP(1),
152	.caps		= CLCD_CAP_5551,
153	.fixedtimings	= 1,
154	.connector	= IMPD1_CTRL_DISP_LCD,
155	.bpp		= 16,
156	.grayscale	= 0,
157};
158
159#elif PANEL == LTM10C209
160#define PANELTYPE	ltm10c209
161/*
162 * Untested.
163 */
164static struct clcd_panel ltm10c209 = {
165	.mode		= {
166		.name		= "LTM10C209",
167		.refresh	= 0,
168		.xres		= 640,
169		.yres		= 480,
170		.pixclock	= 40000,
171		.left_margin	= 20,
172		.right_margin	= 20,
173		.upper_margin	= 19,
174		.lower_margin	= 19,
175		.hsync_len	= 20,
176		.vsync_len	= 10,
177		.sync		= FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
178		.vmode		= FB_VMODE_NONINTERLACED,
179	},
180	.width		= -1,
181	.height		= -1,
182	.tim2		= TIM2_BCD,
183	.cntl		= CNTL_LCDTFT | CNTL_LCDVCOMP(1),
184	.caps		= CLCD_CAP_5551,
185	.fixedtimings	= 1,
186	.connector	= IMPD1_CTRL_DISP_LCD,
187	.bpp		= 16,
188	.grayscale	= 0,
189};
190#endif
191
192/*
193 * Disable all display connectors on the interface module.
194 */
195static void impd1fb_clcd_disable(struct clcd_fb *fb)
196{
197	impd1_tweak_control(fb->dev->dev.parent, IMPD1_CTRL_DISP_MASK, 0);
198}
199
200/*
201 * Enable the relevant connector on the interface module.
202 */
203static void impd1fb_clcd_enable(struct clcd_fb *fb)
204{
205	impd1_tweak_control(fb->dev->dev.parent, IMPD1_CTRL_DISP_MASK,
206			fb->panel->connector | IMPD1_CTRL_DISP_ENABLE);
207}
208
209static int impd1fb_clcd_setup(struct clcd_fb *fb)
210{
211	unsigned long framebase = fb->dev->res.start + 0x01000000;
212	unsigned long framesize = SZ_1M;
213	int ret = 0;
214
215	fb->panel = &PANELTYPE;
216
217	if (!request_mem_region(framebase, framesize, "clcd framebuffer")) {
218		printk(KERN_ERR "IM-PD1: unable to reserve framebuffer\n");
219		return -EBUSY;
220	}
221
222	fb->fb.screen_base = ioremap(framebase, framesize);
223	if (!fb->fb.screen_base) {
224		printk(KERN_ERR "IM-PD1: unable to map framebuffer\n");
225		ret = -ENOMEM;
226		goto free_buffer;
227	}
228
229	fb->fb.fix.smem_start	= framebase;
230	fb->fb.fix.smem_len	= framesize;
231
232	return 0;
233
234 free_buffer:
235	release_mem_region(framebase, framesize);
236	return ret;
237}
238
239static int impd1fb_clcd_mmap(struct clcd_fb *fb, struct vm_area_struct *vma)
240{
241	unsigned long start, size;
242
243	start = vma->vm_pgoff + (fb->fb.fix.smem_start >> PAGE_SHIFT);
244	size = vma->vm_end - vma->vm_start;
245
246	return remap_pfn_range(vma, vma->vm_start, start, size,
247			       vma->vm_page_prot);
248}
249
250static void impd1fb_clcd_remove(struct clcd_fb *fb)
251{
252	iounmap(fb->fb.screen_base);
253	release_mem_region(fb->fb.fix.smem_start, fb->fb.fix.smem_len);
254}
255
256static struct clcd_board impd1_clcd_data = {
257	.name		= "IM-PD/1",
258	.caps		= CLCD_CAP_5551 | CLCD_CAP_888,
259	.check		= clcdfb_check,
260	.decode		= clcdfb_decode,
261	.disable	= impd1fb_clcd_disable,
262	.enable		= impd1fb_clcd_enable,
263	.setup		= impd1fb_clcd_setup,
264	.mmap		= impd1fb_clcd_mmap,
265	.remove		= impd1fb_clcd_remove,
266};
267
268struct impd1_device {
269	unsigned long	offset;
270	unsigned int	irq[2];
271	unsigned int	id;
272	void		*platform_data;
273};
274
275static struct impd1_device impd1_devs[] = {
276	{
277		.offset	= 0x00100000,
278		.irq	= { 1 },
279		.id	= 0x00141011,
280	}, {
281		.offset	= 0x00200000,
282		.irq	= { 2 },
283		.id	= 0x00141011,
284	}, {
285		.offset	= 0x00300000,
286		.irq	= { 3 },
287		.id	= 0x00041022,
288	}, {
289		.offset	= 0x00400000,
290		.irq	= { 4 },
291		.id	= 0x00041061,
292	}, {
293		.offset	= 0x00500000,
294		.irq	= { 5 },
295		.id	= 0x00041061,
296	}, {
297		.offset	= 0x00600000,
298		.irq	= { 6 },
299		.id	= 0x00041130,
300	}, {
301		.offset	= 0x00700000,
302		.irq	= { 7, 8 },
303		.id	= 0x00041181,
304		.platform_data = &mmc_data,
305	}, {
306		.offset	= 0x00800000,
307		.irq	= { 9 },
308		.id	= 0x00041041,
309	}, {
310		.offset	= 0x01000000,
311		.irq	= { 11 },
312		.id	= 0x00041110,
313		.platform_data = &impd1_clcd_data,
314	}
315};
316
317/*
318 * Valid IRQs: 0 thru 9 and 11, 10 unused.
319 */
320#define IMPD1_VALID_IRQS 0x00000bffU
321
322/*
323 * As this module is bool, it is OK to have this as __init_refok() - no
324 * probe calls will be done after the initial system bootup, as devices
325 * are discovered as part of the machine startup.
326 */
327static int __init_refok impd1_probe(struct lm_device *dev)
328{
329	struct impd1_module *impd1;
330	int irq_base;
331	int i;
332
333	if (dev->id != module_id)
334		return -EINVAL;
335
336	if (!devm_request_mem_region(&dev->dev, dev->resource.start,
337				     SZ_4K, "LM registers"))
338		return -EBUSY;
339
340	impd1 = devm_kzalloc(&dev->dev, sizeof(struct impd1_module),
341			     GFP_KERNEL);
342	if (!impd1)
343		return -ENOMEM;
344
345	impd1->base = devm_ioremap(&dev->dev, dev->resource.start, SZ_4K);
346	if (!impd1->base)
347		return -ENOMEM;
348
349	integrator_impd1_clk_init(impd1->base, dev->id);
350
351	if (!devm_request_mem_region(&dev->dev,
352				     dev->resource.start + 0x03000000,
353				     SZ_4K, "VIC"))
354		return -EBUSY;
355
356	impd1->vic_base = devm_ioremap(&dev->dev,
357				       dev->resource.start + 0x03000000,
358				       SZ_4K);
359	if (!impd1->vic_base)
360		return -ENOMEM;
361
362	irq_base = vic_init_cascaded(impd1->vic_base, dev->irq,
363				     IMPD1_VALID_IRQS, 0);
364
365	lm_set_drvdata(dev, impd1);
366
367	dev_info(&dev->dev, "IM-PD1 found at 0x%08lx\n",
368		 (unsigned long)dev->resource.start);
369
370	for (i = 0; i < ARRAY_SIZE(impd1_devs); i++) {
371		struct impd1_device *idev = impd1_devs + i;
372		struct amba_device *d;
373		unsigned long pc_base;
374		char devname[32];
375		int irq1 = idev->irq[0];
376		int irq2 = idev->irq[1];
377
378		/* Translate IRQs to IM-PD1 local numberspace */
379		if (irq1)
380			irq1 += irq_base;
381		if (irq2)
382			irq2 += irq_base;
383
384		pc_base = dev->resource.start + idev->offset;
385		snprintf(devname, 32, "lm%x:%5.5lx", dev->id, idev->offset >> 12);
386
387		/* Add GPIO descriptor lookup table for the PL061 block */
388		if (idev->offset == 0x00400000) {
389			struct gpiod_lookup_table *lookup;
390			char *chipname;
391			char *mmciname;
392
393			lookup = devm_kzalloc(&dev->dev,
394					      sizeof(*lookup) + 3 * sizeof(struct gpiod_lookup),
395					      GFP_KERNEL);
396			chipname = devm_kstrdup(&dev->dev, devname, GFP_KERNEL);
397			mmciname = kasprintf(GFP_KERNEL, "lm%x:00700", dev->id);
398			lookup->dev_id = mmciname;
399			/*
400			 * Offsets on GPIO block 1:
401			 * 3 = MMC WP (write protect)
402			 * 4 = MMC CD (card detect)
403			 *
404			 * Offsets on GPIO block 2:
405			 * 0 = Up key
406			 * 1 = Down key
407			 * 2 = Left key
408			 * 3 = Right key
409			 * 4 = Key lower left
410			 * 5 = Key lower right
411			 */
412			/* We need the two MMCI GPIO entries */
413			lookup->table[0].chip_label = chipname;
414			lookup->table[0].chip_hwnum = 3;
415			lookup->table[0].con_id = "wp";
416			lookup->table[1].chip_label = chipname;
417			lookup->table[1].chip_hwnum = 4;
418			lookup->table[1].con_id = "cd";
419			lookup->table[1].flags = GPIO_ACTIVE_LOW;
420			gpiod_add_lookup_table(lookup);
421		}
422
423		d = amba_ahb_device_add_res(&dev->dev, devname, pc_base, SZ_4K,
424					    irq1, irq2,
425					    idev->platform_data, idev->id,
426					    &dev->resource);
427		if (IS_ERR(d)) {
428			dev_err(&dev->dev, "unable to register device: %ld\n", PTR_ERR(d));
429			continue;
430		}
431	}
432
433	return 0;
434}
435
436static int impd1_remove_one(struct device *dev, void *data)
437{
438	device_unregister(dev);
439	return 0;
440}
441
442static void impd1_remove(struct lm_device *dev)
443{
444	device_for_each_child(&dev->dev, NULL, impd1_remove_one);
445	integrator_impd1_clk_exit(dev->id);
446
447	lm_set_drvdata(dev, NULL);
448}
449
450static struct lm_driver impd1_driver = {
451	.drv = {
452		.name	= "impd1",
453		/*
454		 * As we're dropping the probe() function, suppress driver
455		 * binding from sysfs.
456		 */
457		.suppress_bind_attrs = true,
458	},
459	.probe		= impd1_probe,
460	.remove		= impd1_remove,
461};
462
463static int __init impd1_init(void)
464{
465	return lm_driver_register(&impd1_driver);
466}
467
468static void __exit impd1_exit(void)
469{
470	lm_driver_unregister(&impd1_driver);
471}
472
473module_init(impd1_init);
474module_exit(impd1_exit);
475
476MODULE_LICENSE("GPL");
477MODULE_DESCRIPTION("Integrator/IM-PD1 logic module core driver");
478MODULE_AUTHOR("Deep Blue Solutions Ltd");
479