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/io.h>
24#include <linux/slab.h>
25#include <linux/clkdev.h>
26
27#include <asm/hardware/icst.h>
28#include <mach/lm.h>
29#include <mach/impd1.h>
30#include <asm/sizes.h>
31
32static int module_id;
33
34module_param_named(lmid, module_id, int, 0444);
35MODULE_PARM_DESC(lmid, "logic module stack position");
36
37struct impd1_module {
38	void __iomem	*base;
39	struct clk	vcos[2];
40	struct clk_lookup *clks[3];
41};
42
43static const struct icst_params impd1_vco_params = {
44	.ref		= 24000000,	/* 24 MHz */
45	.vco_max	= ICST525_VCO_MAX_3V,
46	.vco_min	= ICST525_VCO_MIN,
47	.vd_min		= 12,
48	.vd_max		= 519,
49	.rd_min		= 3,
50	.rd_max		= 120,
51	.s2div		= icst525_s2div,
52	.idx2s		= icst525_idx2s,
53};
54
55static void impd1_setvco(struct clk *clk, struct icst_vco vco)
56{
57	struct impd1_module *impd1 = clk->data;
58	u32 val = vco.v | (vco.r << 9) | (vco.s << 16);
59
60	writel(0xa05f, impd1->base + IMPD1_LOCK);
61	writel(val, clk->vcoreg);
62	writel(0, impd1->base + IMPD1_LOCK);
63
64#ifdef DEBUG
65	vco.v = val & 0x1ff;
66	vco.r = (val >> 9) & 0x7f;
67	vco.s = (val >> 16) & 7;
68
69	pr_debug("IM-PD1: VCO%d clock is %ld Hz\n",
70		 vconr, icst525_hz(&impd1_vco_params, vco));
71#endif
72}
73
74static const struct clk_ops impd1_clk_ops = {
75	.round	= icst_clk_round,
76	.set	= icst_clk_set,
77	.setvco	= impd1_setvco,
78};
79
80void impd1_tweak_control(struct device *dev, u32 mask, u32 val)
81{
82	struct impd1_module *impd1 = dev_get_drvdata(dev);
83	u32 cur;
84
85	val &= mask;
86	cur = readl(impd1->base + IMPD1_CTRL) & ~mask;
87	writel(cur | val, impd1->base + IMPD1_CTRL);
88}
89
90EXPORT_SYMBOL(impd1_tweak_control);
91
92/*
93 * CLCD support
94 */
95#define PANEL		PROSPECTOR
96
97#define LTM10C209		1
98#define PROSPECTOR		2
99#define SVGA			3
100#define VGA			4
101
102#if PANEL == VGA
103#define PANELTYPE	vga
104static struct clcd_panel vga = {
105	.mode		= {
106		.name		= "VGA",
107		.refresh	= 60,
108		.xres		= 640,
109		.yres		= 480,
110		.pixclock	= 39721,
111		.left_margin	= 40,
112		.right_margin	= 24,
113		.upper_margin	= 32,
114		.lower_margin	= 11,
115		.hsync_len	= 96,
116		.vsync_len	= 2,
117		.sync		= 0,
118		.vmode		= FB_VMODE_NONINTERLACED,
119	},
120	.width		= -1,
121	.height		= -1,
122	.tim2		= TIM2_BCD | TIM2_IPC,
123	.cntl		= CNTL_LCDTFT | CNTL_LCDVCOMP(1),
124	.caps		= CLCD_CAP_5551,
125	.connector	= IMPD1_CTRL_DISP_VGA,
126	.bpp		= 16,
127	.grayscale	= 0,
128};
129
130#elif PANEL == SVGA
131#define PANELTYPE	svga
132static struct clcd_panel svga = {
133	.mode		= {
134		.name		= "SVGA",
135		.refresh	= 0,
136		.xres		= 800,
137		.yres		= 600,
138		.pixclock	= 27778,
139		.left_margin	= 20,
140		.right_margin	= 20,
141		.upper_margin	= 5,
142		.lower_margin	= 5,
143		.hsync_len	= 164,
144		.vsync_len	= 62,
145		.sync		= 0,
146		.vmode		= FB_VMODE_NONINTERLACED,
147	},
148	.width		= -1,
149	.height		= -1,
150	.tim2		= TIM2_BCD,
151	.cntl		= CNTL_LCDTFT | CNTL_LCDVCOMP(1),
152	.connector	= IMPD1_CTRL_DISP_VGA,
153	.caps		= CLCD_CAP_5551,
154	.bpp		= 16,
155	.grayscale	= 0,
156};
157
158#elif PANEL == PROSPECTOR
159#define PANELTYPE	prospector
160static struct clcd_panel prospector = {
161	.mode		= {
162		.name		= "PROSPECTOR",
163		.refresh	= 0,
164		.xres		= 640,
165		.yres		= 480,
166		.pixclock	= 40000,
167		.left_margin	= 33,
168		.right_margin	= 64,
169		.upper_margin	= 36,
170		.lower_margin	= 7,
171		.hsync_len	= 64,
172		.vsync_len	= 25,
173		.sync		= FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
174		.vmode		= FB_VMODE_NONINTERLACED,
175	},
176	.width		= -1,
177	.height		= -1,
178	.tim2		= TIM2_BCD,
179	.cntl		= CNTL_LCDTFT | CNTL_LCDVCOMP(1),
180	.caps		= CLCD_CAP_5551,
181	.fixedtimings	= 1,
182	.connector	= IMPD1_CTRL_DISP_LCD,
183	.bpp		= 16,
184	.grayscale	= 0,
185};
186
187#elif PANEL == LTM10C209
188#define PANELTYPE	ltm10c209
189/*
190 * Untested.
191 */
192static struct clcd_panel ltm10c209 = {
193	.mode		= {
194		.name		= "LTM10C209",
195		.refresh	= 0,
196		.xres		= 640,
197		.yres		= 480,
198		.pixclock	= 40000,
199		.left_margin	= 20,
200		.right_margin	= 20,
201		.upper_margin	= 19,
202		.lower_margin	= 19,
203		.hsync_len	= 20,
204		.vsync_len	= 10,
205		.sync		= FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
206		.vmode		= FB_VMODE_NONINTERLACED,
207	},
208	.width		= -1,
209	.height		= -1,
210	.tim2		= TIM2_BCD,
211	.cntl		= CNTL_LCDTFT | CNTL_LCDVCOMP(1),
212	.caps		= CLCD_CAP_5551,
213	.fixedtimings	= 1,
214	.connector	= IMPD1_CTRL_DISP_LCD,
215	.bpp		= 16,
216	.grayscale	= 0,
217};
218#endif
219
220/*
221 * Disable all display connectors on the interface module.
222 */
223static void impd1fb_clcd_disable(struct clcd_fb *fb)
224{
225	impd1_tweak_control(fb->dev->dev.parent, IMPD1_CTRL_DISP_MASK, 0);
226}
227
228/*
229 * Enable the relevant connector on the interface module.
230 */
231static void impd1fb_clcd_enable(struct clcd_fb *fb)
232{
233	impd1_tweak_control(fb->dev->dev.parent, IMPD1_CTRL_DISP_MASK,
234			fb->panel->connector | IMPD1_CTRL_DISP_ENABLE);
235}
236
237static int impd1fb_clcd_setup(struct clcd_fb *fb)
238{
239	unsigned long framebase = fb->dev->res.start + 0x01000000;
240	unsigned long framesize = SZ_1M;
241	int ret = 0;
242
243	fb->panel = &PANELTYPE;
244
245	if (!request_mem_region(framebase, framesize, "clcd framebuffer")) {
246		printk(KERN_ERR "IM-PD1: unable to reserve framebuffer\n");
247		return -EBUSY;
248	}
249
250	fb->fb.screen_base = ioremap(framebase, framesize);
251	if (!fb->fb.screen_base) {
252		printk(KERN_ERR "IM-PD1: unable to map framebuffer\n");
253		ret = -ENOMEM;
254		goto free_buffer;
255	}
256
257	fb->fb.fix.smem_start	= framebase;
258	fb->fb.fix.smem_len	= framesize;
259
260	return 0;
261
262 free_buffer:
263	release_mem_region(framebase, framesize);
264	return ret;
265}
266
267static int impd1fb_clcd_mmap(struct clcd_fb *fb, struct vm_area_struct *vma)
268{
269	unsigned long start, size;
270
271	start = vma->vm_pgoff + (fb->fb.fix.smem_start >> PAGE_SHIFT);
272	size = vma->vm_end - vma->vm_start;
273
274	return remap_pfn_range(vma, vma->vm_start, start, size,
275			       vma->vm_page_prot);
276}
277
278static void impd1fb_clcd_remove(struct clcd_fb *fb)
279{
280	iounmap(fb->fb.screen_base);
281	release_mem_region(fb->fb.fix.smem_start, fb->fb.fix.smem_len);
282}
283
284static struct clcd_board impd1_clcd_data = {
285	.name		= "IM-PD/1",
286	.caps		= CLCD_CAP_5551 | CLCD_CAP_888,
287	.check		= clcdfb_check,
288	.decode		= clcdfb_decode,
289	.disable	= impd1fb_clcd_disable,
290	.enable		= impd1fb_clcd_enable,
291	.setup		= impd1fb_clcd_setup,
292	.mmap		= impd1fb_clcd_mmap,
293	.remove		= impd1fb_clcd_remove,
294};
295
296struct impd1_device {
297	unsigned long	offset;
298	unsigned int	irq[2];
299	unsigned int	id;
300	void		*platform_data;
301};
302
303static struct impd1_device impd1_devs[] = {
304	{
305		.offset	= 0x03000000,
306		.id	= 0x00041190,
307	}, {
308		.offset	= 0x00100000,
309		.irq	= { 1 },
310		.id	= 0x00141011,
311	}, {
312		.offset	= 0x00200000,
313		.irq	= { 2 },
314		.id	= 0x00141011,
315	}, {
316		.offset	= 0x00300000,
317		.irq	= { 3 },
318		.id	= 0x00041022,
319	}, {
320		.offset	= 0x00400000,
321		.irq	= { 4 },
322		.id	= 0x00041061,
323	}, {
324		.offset	= 0x00500000,
325		.irq	= { 5 },
326		.id	= 0x00041061,
327	}, {
328		.offset	= 0x00600000,
329		.irq	= { 6 },
330		.id	= 0x00041130,
331	}, {
332		.offset	= 0x00700000,
333		.irq	= { 7, 8 },
334		.id	= 0x00041181,
335	}, {
336		.offset	= 0x00800000,
337		.irq	= { 9 },
338		.id	= 0x00041041,
339	}, {
340		.offset	= 0x01000000,
341		.irq	= { 11 },
342		.id	= 0x00041110,
343		.platform_data = &impd1_clcd_data,
344	}
345};
346
347static struct clk fixed_14745600 = {
348	.rate = 14745600,
349};
350
351static int impd1_probe(struct lm_device *dev)
352{
353	struct impd1_module *impd1;
354	int i, ret;
355
356	if (dev->id != module_id)
357		return -EINVAL;
358
359	if (!request_mem_region(dev->resource.start, SZ_4K, "LM registers"))
360		return -EBUSY;
361
362	impd1 = kzalloc(sizeof(struct impd1_module), GFP_KERNEL);
363	if (!impd1) {
364		ret = -ENOMEM;
365		goto release_lm;
366	}
367
368	impd1->base = ioremap(dev->resource.start, SZ_4K);
369	if (!impd1->base) {
370		ret = -ENOMEM;
371		goto free_impd1;
372	}
373
374	lm_set_drvdata(dev, impd1);
375
376	printk("IM-PD1 found at 0x%08lx\n",
377		(unsigned long)dev->resource.start);
378
379	for (i = 0; i < ARRAY_SIZE(impd1->vcos); i++) {
380		impd1->vcos[i].ops = &impd1_clk_ops,
381		impd1->vcos[i].owner = THIS_MODULE,
382		impd1->vcos[i].params = &impd1_vco_params,
383		impd1->vcos[i].data = impd1;
384	}
385	impd1->vcos[0].vcoreg = impd1->base + IMPD1_OSC1;
386	impd1->vcos[1].vcoreg = impd1->base + IMPD1_OSC2;
387
388	impd1->clks[0] = clkdev_alloc(&impd1->vcos[0], NULL, "lm%x:01000",
389					dev->id);
390	impd1->clks[1] = clkdev_alloc(&fixed_14745600, NULL, "lm%x:00100",
391					dev->id);
392	impd1->clks[2] = clkdev_alloc(&fixed_14745600, NULL, "lm%x:00200",
393					dev->id);
394	for (i = 0; i < ARRAY_SIZE(impd1->clks); i++)
395		clkdev_add(impd1->clks[i]);
396
397	for (i = 0; i < ARRAY_SIZE(impd1_devs); i++) {
398		struct impd1_device *idev = impd1_devs + i;
399		struct amba_device *d;
400		unsigned long pc_base;
401
402		pc_base = dev->resource.start + idev->offset;
403
404		d = amba_device_alloc(NULL, pc_base, SZ_4K);
405		if (!d)
406			continue;
407
408		dev_set_name(&d->dev, "lm%x:%5.5lx", dev->id, idev->offset >> 12);
409		d->dev.parent	= &dev->dev;
410		d->irq[0]	= dev->irq;
411		d->irq[1]	= dev->irq;
412		d->periphid	= idev->id;
413		d->dev.platform_data = idev->platform_data;
414
415		ret = amba_device_add(d, &dev->resource);
416		if (ret) {
417			dev_err(&d->dev, "unable to register device: %d\n", ret);
418			amba_device_put(d);
419		}
420	}
421
422	return 0;
423
424 free_impd1:
425	if (impd1 && impd1->base)
426		iounmap(impd1->base);
427	kfree(impd1);
428 release_lm:
429	release_mem_region(dev->resource.start, SZ_4K);
430	return ret;
431}
432
433static int impd1_remove_one(struct device *dev, void *data)
434{
435	device_unregister(dev);
436	return 0;
437}
438
439static void impd1_remove(struct lm_device *dev)
440{
441	struct impd1_module *impd1 = lm_get_drvdata(dev);
442	int i;
443
444	device_for_each_child(&dev->dev, NULL, impd1_remove_one);
445
446	for (i = 0; i < ARRAY_SIZE(impd1->clks); i++)
447		clkdev_drop(impd1->clks[i]);
448
449	lm_set_drvdata(dev, NULL);
450
451	iounmap(impd1->base);
452	kfree(impd1);
453	release_mem_region(dev->resource.start, SZ_4K);
454}
455
456static struct lm_driver impd1_driver = {
457	.drv = {
458		.name	= "impd1",
459	},
460	.probe		= impd1_probe,
461	.remove		= impd1_remove,
462};
463
464static int __init impd1_init(void)
465{
466	return lm_driver_register(&impd1_driver);
467}
468
469static void __exit impd1_exit(void)
470{
471	lm_driver_unregister(&impd1_driver);
472}
473
474module_init(impd1_init);
475module_exit(impd1_exit);
476
477MODULE_LICENSE("GPL");
478MODULE_DESCRIPTION("Integrator/IM-PD1 logic module core driver");
479MODULE_AUTHOR("Deep Blue Solutions Ltd");
480