lpc_fb.c revision 259329
1/*-
2 * Copyright (c) 2011 Jakub Wojciech Klama <jceel@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: stable/10/sys/arm/lpc/lpc_fb.c 259329 2013-12-13 20:43:11Z ian $");
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/bio.h>
33#include <sys/bus.h>
34#include <sys/conf.h>
35#include <sys/endian.h>
36#include <sys/kernel.h>
37#include <sys/kthread.h>
38#include <sys/lock.h>
39#include <sys/malloc.h>
40#include <sys/module.h>
41#include <sys/mutex.h>
42#include <sys/queue.h>
43#include <sys/resource.h>
44#include <sys/rman.h>
45#include <sys/time.h>
46#include <sys/timetc.h>
47#include <sys/watchdog.h>
48
49#include <sys/kdb.h>
50
51#include <machine/bus.h>
52#include <machine/cpu.h>
53#include <machine/cpufunc.h>
54#include <machine/resource.h>
55#include <machine/intr.h>
56
57#include <dev/fdt/fdt_common.h>
58#include <dev/ofw/ofw_bus.h>
59#include <dev/ofw/ofw_bus_subr.h>
60
61#include <arm/lpc/lpcreg.h>
62#include <arm/lpc/lpcvar.h>
63
64
65struct lpc_fb_dmamap_arg {
66	bus_addr_t		lf_dma_busaddr;
67};
68
69struct lpc_lcd_config {
70	int			lc_xres;
71	int			lc_yres;
72	int			lc_bpp;
73	uint32_t		lc_pixelclock;
74	int			lc_left_margin;
75	int			lc_right_margin;
76	int			lc_upper_margin;
77	int			lc_lower_margin;
78	int			lc_hsync_len;
79	int			lc_vsync_len;
80};
81
82struct lpc_fb_softc {
83	device_t		lf_dev;
84	struct cdev *		lf_cdev;
85	struct mtx		lf_mtx;
86	struct resource *	lf_mem_res;
87	struct resource *	lf_irq_res;
88	bus_space_tag_t		lf_bst;
89	bus_space_handle_t	lf_bsh;
90	void *			lf_intrhand;
91	bus_dma_tag_t		lf_dma_tag;
92	bus_dmamap_t		lf_dma_map;
93	void *			lf_buffer;
94	bus_addr_t		lf_buffer_phys;
95	bus_size_t		lf_buffer_size;
96	struct lpc_lcd_config	lf_lcd_config;
97	int			lf_initialized;
98	int			lf_opened;
99};
100
101extern void ssd1289_configure(void);
102
103#define	lpc_fb_lock(_sc)	mtx_lock(&(_sc)->lf_mtx)
104#define	lpc_fb_unlock(_sc)	mtx_unlock(&(_sc)->lf_mtx)
105#define	lpc_fb_lock_assert(sc)	mtx_assert(&(_sc)->lf_mtx, MA_OWNED)
106
107#define	lpc_fb_read_4(_sc, _reg)		\
108    bus_space_read_4((_sc)->lf_bst, (_sc)->lf_bsh, (_reg))
109#define	lpc_fb_write_4(_sc, _reg, _val)		\
110    bus_space_write_4((_sc)->lf_bst, (_sc)->lf_bsh, (_reg), (_val))
111
112
113
114static int lpc_fb_probe(device_t);
115static int lpc_fb_attach(device_t);
116static void lpc_fb_intr(void *);
117static void lpc_fb_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err);
118
119static int lpc_fb_fdt_read(phandle_t, const char *, uint32_t *);
120static int lpc_fb_read_lcd_config(phandle_t, struct lpc_lcd_config *);
121
122static int lpc_fb_open(struct cdev *, int, int, struct thread *);
123static int lpc_fb_close(struct cdev *, int, int, struct thread *);
124static int lpc_fb_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
125static int lpc_fb_mmap(struct cdev *, vm_ooffset_t, vm_paddr_t *, int, vm_memattr_t *);
126
127static void lpc_fb_blank(struct lpc_fb_softc *);
128
129static struct cdevsw lpc_fb_cdevsw = {
130	.d_open		= lpc_fb_open,
131	.d_close	= lpc_fb_close,
132	.d_ioctl	= lpc_fb_ioctl,
133	.d_mmap		= lpc_fb_mmap,
134	.d_name		= "lpcfb",
135	.d_version	= D_VERSION,
136};
137
138static int
139lpc_fb_probe(device_t dev)
140{
141	if (!ofw_bus_is_compatible(dev, "lpc,fb"))
142		return (ENXIO);
143
144	device_set_desc(dev, "LPC32x0 framebuffer device");
145	return (BUS_PROBE_DEFAULT);
146}
147
148static int
149lpc_fb_attach(device_t dev)
150{
151	struct lpc_fb_softc *sc = device_get_softc(dev);
152	struct lpc_fb_dmamap_arg ctx;
153	phandle_t node;
154	int mode, rid, err = 0;
155
156	sc->lf_dev = dev;
157	mtx_init(&sc->lf_mtx, "lpcfb", "fb", MTX_DEF);
158
159	rid = 0;
160	sc->lf_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
161	    RF_ACTIVE);
162	if (!sc->lf_mem_res) {
163		device_printf(dev, "cannot allocate memory window\n");
164		return (ENXIO);
165	}
166
167	sc->lf_bst = rman_get_bustag(sc->lf_mem_res);
168	sc->lf_bsh = rman_get_bushandle(sc->lf_mem_res);
169
170	rid = 0;
171	sc->lf_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
172	    RF_ACTIVE);
173	if (!sc->lf_irq_res) {
174		device_printf(dev, "cannot allocate interrupt\n");
175		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->lf_mem_res);
176		return (ENXIO);
177	}
178
179	if (bus_setup_intr(dev, sc->lf_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
180	    NULL, lpc_fb_intr, sc, &sc->lf_intrhand))
181	{
182		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->lf_mem_res);
183		bus_release_resource(dev, SYS_RES_IRQ, 1, sc->lf_irq_res);
184		device_printf(dev, "cannot setup interrupt handler\n");
185		return (ENXIO);
186	}
187
188	node = ofw_bus_get_node(dev);
189
190	err = lpc_fb_read_lcd_config(node, &sc->lf_lcd_config);
191	if (err) {
192		device_printf(dev, "cannot read LCD configuration\n");
193		goto fail;
194	}
195
196	sc->lf_buffer_size = sc->lf_lcd_config.lc_xres *
197	    sc->lf_lcd_config.lc_yres *
198	    (sc->lf_lcd_config.lc_bpp == 24 ? 3 : 2);
199
200	device_printf(dev, "%dx%d LCD, %d bits per pixel, %dkHz pixel clock\n",
201	    sc->lf_lcd_config.lc_xres, sc->lf_lcd_config.lc_yres,
202	    sc->lf_lcd_config.lc_bpp, sc->lf_lcd_config.lc_pixelclock / 1000);
203
204	err = bus_dma_tag_create(
205	    bus_get_dma_tag(sc->lf_dev),
206	    4, 0,			/* alignment, boundary */
207	    BUS_SPACE_MAXADDR_32BIT,	/* lowaddr */
208	    BUS_SPACE_MAXADDR,		/* highaddr */
209	    NULL, NULL,			/* filter, filterarg */
210	    sc->lf_buffer_size, 1,	/* maxsize, nsegments */
211	    sc->lf_buffer_size, 0,	/* maxsegsize, flags */
212	    NULL, NULL,			/* lockfunc, lockarg */
213	    &sc->lf_dma_tag);
214
215	err = bus_dmamem_alloc(sc->lf_dma_tag, (void **)&sc->lf_buffer,
216	    0, &sc->lf_dma_map);
217	if (err) {
218		device_printf(dev, "cannot allocate framebuffer\n");
219		goto fail;
220	}
221
222	err = bus_dmamap_load(sc->lf_dma_tag, sc->lf_dma_map, sc->lf_buffer,
223	    sc->lf_buffer_size, lpc_fb_dmamap_cb, &ctx, BUS_DMA_NOWAIT);
224	if (err) {
225		device_printf(dev, "cannot load DMA map\n");
226		goto fail;
227	}
228
229	switch (sc->lf_lcd_config.lc_bpp) {
230	case 12:
231		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_12;
232		break;
233	case 15:
234		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_15;
235		break;
236	case 16:
237		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_16;
238		break;
239	case 24:
240		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_24;
241		break;
242	default:
243		panic("unsupported bpp");
244	}
245
246	lpc_pwr_write(sc->lf_dev, LPC_CLKPWR_LCDCLK_CTRL,
247	    LPC_CLKPWR_LCDCLK_CTRL_MODE(mode) |
248	    LPC_CLKPWR_LCDCLK_CTRL_HCLKEN);
249
250	sc->lf_buffer_phys = ctx.lf_dma_busaddr;
251	sc->lf_cdev = make_dev(&lpc_fb_cdevsw, 0, UID_ROOT, GID_WHEEL,
252	    0600, "lpcfb");
253
254	sc->lf_cdev->si_drv1 = sc;
255
256	return (0);
257fail:
258	return (ENXIO);
259}
260
261static void
262lpc_fb_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err)
263{
264	struct lpc_fb_dmamap_arg *ctx;
265
266	if (err)
267		return;
268
269	ctx = (struct lpc_fb_dmamap_arg *)arg;
270	ctx->lf_dma_busaddr = segs[0].ds_addr;
271}
272
273static void
274lpc_fb_intr(void *arg)
275{
276}
277
278static int
279lpc_fb_fdt_read(phandle_t node, const char *name, uint32_t *ret)
280{
281	if (OF_getprop(node, name, ret, sizeof(uint32_t)) <= 0)
282		return (ENOENT);
283
284	*ret = fdt32_to_cpu(*ret);
285	return (0);
286}
287
288static int
289lpc_fb_read_lcd_config(phandle_t node, struct lpc_lcd_config *cfg)
290{
291	if (lpc_fb_fdt_read(node, "horizontal-resolution", &cfg->lc_xres))
292		return (ENXIO);
293
294	if (lpc_fb_fdt_read(node, "vertical-resolution", &cfg->lc_yres))
295		return (ENXIO);
296
297	if (lpc_fb_fdt_read(node, "bits-per-pixel", &cfg->lc_bpp))
298		return (ENXIO);
299
300	if (lpc_fb_fdt_read(node, "pixel-clock", &cfg->lc_pixelclock))
301		return (ENXIO);
302
303	if (lpc_fb_fdt_read(node, "left-margin", &cfg->lc_left_margin))
304		return (ENXIO);
305
306	if (lpc_fb_fdt_read(node, "right-margin", &cfg->lc_right_margin))
307		return (ENXIO);
308
309	if (lpc_fb_fdt_read(node, "upper-margin", &cfg->lc_upper_margin))
310		return (ENXIO);
311
312	if (lpc_fb_fdt_read(node, "lower-margin", &cfg->lc_lower_margin))
313		return (ENXIO);
314
315	if (lpc_fb_fdt_read(node, "hsync-len", &cfg->lc_hsync_len))
316		return (ENXIO);
317
318	if (lpc_fb_fdt_read(node, "vsync-len", &cfg->lc_vsync_len))
319		return (ENXIO);
320
321	return (0);
322}
323
324static void
325lpc_fb_setup(struct lpc_fb_softc *sc)
326{
327	struct lpc_lcd_config *cfg = &sc->lf_lcd_config;
328	uint32_t bpp;
329
330	lpc_fb_write_4(sc, LPC_LCD_TIMH,
331	    LPC_LCD_TIMH_PPL(cfg->lc_xres) |
332	    LPC_LCD_TIMH_HSW(cfg->lc_hsync_len - 1) |
333	    LPC_LCD_TIMH_HFP(cfg->lc_right_margin - 1) |
334	    LPC_LCD_TIMH_HBP(cfg->lc_left_margin - 1));
335
336	lpc_fb_write_4(sc, LPC_LCD_TIMV,
337	    LPC_LCD_TIMV_LPP(cfg->lc_yres - 1) |
338	    LPC_LCD_TIMV_VSW(cfg->lc_vsync_len - 1) |
339	    LPC_LCD_TIMV_VFP(cfg->lc_lower_margin) |
340	    LPC_LCD_TIMV_VBP(cfg->lc_upper_margin));
341
342	/* XXX LPC_LCD_POL_PCD_LO */
343	lpc_fb_write_4(sc, LPC_LCD_POL,
344	    LPC_LCD_POL_IHS | LPC_LCD_POL_IVS |
345	    LPC_LCD_POL_CPL(cfg->lc_xres - 1) |
346	    LPC_LCD_POL_PCD_LO(4));
347
348	lpc_fb_write_4(sc, LPC_LCD_UPBASE, sc->lf_buffer_phys);
349
350	switch (cfg->lc_bpp) {
351	case 1:
352		bpp = LPC_LCD_CTRL_BPP1;
353		break;
354	case 2:
355		bpp = LPC_LCD_CTRL_BPP2;
356		break;
357	case 4:
358		bpp = LPC_LCD_CTRL_BPP4;
359		break;
360	case 8:
361		bpp = LPC_LCD_CTRL_BPP8;
362		break;
363	case 12:
364		bpp = LPC_LCD_CTRL_BPP12_444;
365		break;
366	case 15:
367		bpp = LPC_LCD_CTRL_BPP16;
368		break;
369	case 16:
370		bpp = LPC_LCD_CTRL_BPP16_565;
371		break;
372	case 24:
373		bpp = LPC_LCD_CTRL_BPP24;
374		break;
375	default:
376		panic("LCD unknown bpp: %d", cfg->lc_bpp);
377	}
378
379	lpc_fb_write_4(sc, LPC_LCD_CTRL,
380	    LPC_LCD_CTRL_LCDVCOMP(1) |
381	    LPC_LCD_CTRL_LCDPWR |
382	    LPC_LCD_CTRL_BGR |
383	    LPC_LCD_CTRL_LCDTFT |
384	    LPC_LCD_CTRL_LCDBPP(bpp) |
385	    LPC_LCD_CTRL_LCDEN);
386}
387
388
389static int
390lpc_fb_open(struct cdev *cdev, int oflags, int devtype, struct thread *td)
391{
392	struct lpc_fb_softc *sc = cdev->si_drv1;
393
394	lpc_fb_lock(sc);
395
396	if (sc->lf_opened)
397		return (EBUSY);
398
399	sc->lf_opened = 1;
400
401	lpc_fb_unlock(sc);
402
403	if (!sc->lf_initialized) {
404		ssd1289_configure();
405		lpc_fb_setup(sc);
406		lpc_fb_blank(sc);
407		sc->lf_initialized = 1;
408	}
409
410	return (0);
411}
412
413static int
414lpc_fb_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
415{
416	struct lpc_fb_softc *sc = cdev->si_drv1;
417
418	lpc_fb_lock(sc);
419	sc->lf_opened = 0;
420	lpc_fb_unlock(sc);
421
422	return (0);
423}
424
425static int
426lpc_fb_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int x,
427    struct thread *td)
428{
429
430	return (EINVAL);
431}
432
433static int
434lpc_fb_mmap(struct cdev *cdev, vm_ooffset_t offset, vm_paddr_t *paddr,
435    int nprot, vm_memattr_t *memattr)
436{
437	struct lpc_fb_softc *sc = cdev->si_drv1;
438
439	*paddr = (vm_paddr_t)(sc->lf_buffer_phys + offset);
440	return (0);
441}
442
443static void
444lpc_fb_blank(struct lpc_fb_softc *sc)
445{
446	memset(sc->lf_buffer, 0xffff, sc->lf_buffer_size);
447}
448
449static device_method_t lpc_fb_methods[] = {
450	/* Device interface */
451	DEVMETHOD(device_probe,		lpc_fb_probe),
452	DEVMETHOD(device_attach,	lpc_fb_attach),
453
454	{ 0, 0 }
455};
456
457static devclass_t lpc_fb_devclass;
458
459static driver_t lpc_fb_driver = {
460	"lpcfb",
461	lpc_fb_methods,
462	sizeof(struct lpc_fb_softc),
463};
464
465DRIVER_MODULE(lpcfb, simplebus, lpc_fb_driver, lpc_fb_devclass, 0, 0);
466