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: releng/11.0/sys/arm/lpc/lpc_fb.c 261410 2014-02-02 19:17:28Z 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
142	if (!ofw_bus_status_okay(dev))
143		return (ENXIO);
144
145	if (!ofw_bus_is_compatible(dev, "lpc,fb"))
146		return (ENXIO);
147
148	device_set_desc(dev, "LPC32x0 framebuffer device");
149	return (BUS_PROBE_DEFAULT);
150}
151
152static int
153lpc_fb_attach(device_t dev)
154{
155	struct lpc_fb_softc *sc = device_get_softc(dev);
156	struct lpc_fb_dmamap_arg ctx;
157	phandle_t node;
158	int mode, rid, err = 0;
159
160	sc->lf_dev = dev;
161	mtx_init(&sc->lf_mtx, "lpcfb", "fb", MTX_DEF);
162
163	rid = 0;
164	sc->lf_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
165	    RF_ACTIVE);
166	if (!sc->lf_mem_res) {
167		device_printf(dev, "cannot allocate memory window\n");
168		return (ENXIO);
169	}
170
171	sc->lf_bst = rman_get_bustag(sc->lf_mem_res);
172	sc->lf_bsh = rman_get_bushandle(sc->lf_mem_res);
173
174	rid = 0;
175	sc->lf_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
176	    RF_ACTIVE);
177	if (!sc->lf_irq_res) {
178		device_printf(dev, "cannot allocate interrupt\n");
179		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->lf_mem_res);
180		return (ENXIO);
181	}
182
183	if (bus_setup_intr(dev, sc->lf_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
184	    NULL, lpc_fb_intr, sc, &sc->lf_intrhand))
185	{
186		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->lf_mem_res);
187		bus_release_resource(dev, SYS_RES_IRQ, 1, sc->lf_irq_res);
188		device_printf(dev, "cannot setup interrupt handler\n");
189		return (ENXIO);
190	}
191
192	node = ofw_bus_get_node(dev);
193
194	err = lpc_fb_read_lcd_config(node, &sc->lf_lcd_config);
195	if (err) {
196		device_printf(dev, "cannot read LCD configuration\n");
197		goto fail;
198	}
199
200	sc->lf_buffer_size = sc->lf_lcd_config.lc_xres *
201	    sc->lf_lcd_config.lc_yres *
202	    (sc->lf_lcd_config.lc_bpp == 24 ? 3 : 2);
203
204	device_printf(dev, "%dx%d LCD, %d bits per pixel, %dkHz pixel clock\n",
205	    sc->lf_lcd_config.lc_xres, sc->lf_lcd_config.lc_yres,
206	    sc->lf_lcd_config.lc_bpp, sc->lf_lcd_config.lc_pixelclock / 1000);
207
208	err = bus_dma_tag_create(
209	    bus_get_dma_tag(sc->lf_dev),
210	    4, 0,			/* alignment, boundary */
211	    BUS_SPACE_MAXADDR_32BIT,	/* lowaddr */
212	    BUS_SPACE_MAXADDR,		/* highaddr */
213	    NULL, NULL,			/* filter, filterarg */
214	    sc->lf_buffer_size, 1,	/* maxsize, nsegments */
215	    sc->lf_buffer_size, 0,	/* maxsegsize, flags */
216	    NULL, NULL,			/* lockfunc, lockarg */
217	    &sc->lf_dma_tag);
218
219	err = bus_dmamem_alloc(sc->lf_dma_tag, (void **)&sc->lf_buffer,
220	    0, &sc->lf_dma_map);
221	if (err) {
222		device_printf(dev, "cannot allocate framebuffer\n");
223		goto fail;
224	}
225
226	err = bus_dmamap_load(sc->lf_dma_tag, sc->lf_dma_map, sc->lf_buffer,
227	    sc->lf_buffer_size, lpc_fb_dmamap_cb, &ctx, BUS_DMA_NOWAIT);
228	if (err) {
229		device_printf(dev, "cannot load DMA map\n");
230		goto fail;
231	}
232
233	switch (sc->lf_lcd_config.lc_bpp) {
234	case 12:
235		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_12;
236		break;
237	case 15:
238		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_15;
239		break;
240	case 16:
241		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_16;
242		break;
243	case 24:
244		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_24;
245		break;
246	default:
247		panic("unsupported bpp");
248	}
249
250	lpc_pwr_write(sc->lf_dev, LPC_CLKPWR_LCDCLK_CTRL,
251	    LPC_CLKPWR_LCDCLK_CTRL_MODE(mode) |
252	    LPC_CLKPWR_LCDCLK_CTRL_HCLKEN);
253
254	sc->lf_buffer_phys = ctx.lf_dma_busaddr;
255	sc->lf_cdev = make_dev(&lpc_fb_cdevsw, 0, UID_ROOT, GID_WHEEL,
256	    0600, "lpcfb");
257
258	sc->lf_cdev->si_drv1 = sc;
259
260	return (0);
261fail:
262	return (ENXIO);
263}
264
265static void
266lpc_fb_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err)
267{
268	struct lpc_fb_dmamap_arg *ctx;
269
270	if (err)
271		return;
272
273	ctx = (struct lpc_fb_dmamap_arg *)arg;
274	ctx->lf_dma_busaddr = segs[0].ds_addr;
275}
276
277static void
278lpc_fb_intr(void *arg)
279{
280}
281
282static int
283lpc_fb_fdt_read(phandle_t node, const char *name, uint32_t *ret)
284{
285	if (OF_getprop(node, name, ret, sizeof(uint32_t)) <= 0)
286		return (ENOENT);
287
288	*ret = fdt32_to_cpu(*ret);
289	return (0);
290}
291
292static int
293lpc_fb_read_lcd_config(phandle_t node, struct lpc_lcd_config *cfg)
294{
295	if (lpc_fb_fdt_read(node, "horizontal-resolution", &cfg->lc_xres))
296		return (ENXIO);
297
298	if (lpc_fb_fdt_read(node, "vertical-resolution", &cfg->lc_yres))
299		return (ENXIO);
300
301	if (lpc_fb_fdt_read(node, "bits-per-pixel", &cfg->lc_bpp))
302		return (ENXIO);
303
304	if (lpc_fb_fdt_read(node, "pixel-clock", &cfg->lc_pixelclock))
305		return (ENXIO);
306
307	if (lpc_fb_fdt_read(node, "left-margin", &cfg->lc_left_margin))
308		return (ENXIO);
309
310	if (lpc_fb_fdt_read(node, "right-margin", &cfg->lc_right_margin))
311		return (ENXIO);
312
313	if (lpc_fb_fdt_read(node, "upper-margin", &cfg->lc_upper_margin))
314		return (ENXIO);
315
316	if (lpc_fb_fdt_read(node, "lower-margin", &cfg->lc_lower_margin))
317		return (ENXIO);
318
319	if (lpc_fb_fdt_read(node, "hsync-len", &cfg->lc_hsync_len))
320		return (ENXIO);
321
322	if (lpc_fb_fdt_read(node, "vsync-len", &cfg->lc_vsync_len))
323		return (ENXIO);
324
325	return (0);
326}
327
328static void
329lpc_fb_setup(struct lpc_fb_softc *sc)
330{
331	struct lpc_lcd_config *cfg = &sc->lf_lcd_config;
332	uint32_t bpp;
333
334	lpc_fb_write_4(sc, LPC_LCD_TIMH,
335	    LPC_LCD_TIMH_PPL(cfg->lc_xres) |
336	    LPC_LCD_TIMH_HSW(cfg->lc_hsync_len - 1) |
337	    LPC_LCD_TIMH_HFP(cfg->lc_right_margin - 1) |
338	    LPC_LCD_TIMH_HBP(cfg->lc_left_margin - 1));
339
340	lpc_fb_write_4(sc, LPC_LCD_TIMV,
341	    LPC_LCD_TIMV_LPP(cfg->lc_yres - 1) |
342	    LPC_LCD_TIMV_VSW(cfg->lc_vsync_len - 1) |
343	    LPC_LCD_TIMV_VFP(cfg->lc_lower_margin) |
344	    LPC_LCD_TIMV_VBP(cfg->lc_upper_margin));
345
346	/* XXX LPC_LCD_POL_PCD_LO */
347	lpc_fb_write_4(sc, LPC_LCD_POL,
348	    LPC_LCD_POL_IHS | LPC_LCD_POL_IVS |
349	    LPC_LCD_POL_CPL(cfg->lc_xres - 1) |
350	    LPC_LCD_POL_PCD_LO(4));
351
352	lpc_fb_write_4(sc, LPC_LCD_UPBASE, sc->lf_buffer_phys);
353
354	switch (cfg->lc_bpp) {
355	case 1:
356		bpp = LPC_LCD_CTRL_BPP1;
357		break;
358	case 2:
359		bpp = LPC_LCD_CTRL_BPP2;
360		break;
361	case 4:
362		bpp = LPC_LCD_CTRL_BPP4;
363		break;
364	case 8:
365		bpp = LPC_LCD_CTRL_BPP8;
366		break;
367	case 12:
368		bpp = LPC_LCD_CTRL_BPP12_444;
369		break;
370	case 15:
371		bpp = LPC_LCD_CTRL_BPP16;
372		break;
373	case 16:
374		bpp = LPC_LCD_CTRL_BPP16_565;
375		break;
376	case 24:
377		bpp = LPC_LCD_CTRL_BPP24;
378		break;
379	default:
380		panic("LCD unknown bpp: %d", cfg->lc_bpp);
381	}
382
383	lpc_fb_write_4(sc, LPC_LCD_CTRL,
384	    LPC_LCD_CTRL_LCDVCOMP(1) |
385	    LPC_LCD_CTRL_LCDPWR |
386	    LPC_LCD_CTRL_BGR |
387	    LPC_LCD_CTRL_LCDTFT |
388	    LPC_LCD_CTRL_LCDBPP(bpp) |
389	    LPC_LCD_CTRL_LCDEN);
390}
391
392
393static int
394lpc_fb_open(struct cdev *cdev, int oflags, int devtype, struct thread *td)
395{
396	struct lpc_fb_softc *sc = cdev->si_drv1;
397
398	lpc_fb_lock(sc);
399
400	if (sc->lf_opened)
401		return (EBUSY);
402
403	sc->lf_opened = 1;
404
405	lpc_fb_unlock(sc);
406
407	if (!sc->lf_initialized) {
408		ssd1289_configure();
409		lpc_fb_setup(sc);
410		lpc_fb_blank(sc);
411		sc->lf_initialized = 1;
412	}
413
414	return (0);
415}
416
417static int
418lpc_fb_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
419{
420	struct lpc_fb_softc *sc = cdev->si_drv1;
421
422	lpc_fb_lock(sc);
423	sc->lf_opened = 0;
424	lpc_fb_unlock(sc);
425
426	return (0);
427}
428
429static int
430lpc_fb_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int x,
431    struct thread *td)
432{
433
434	return (EINVAL);
435}
436
437static int
438lpc_fb_mmap(struct cdev *cdev, vm_ooffset_t offset, vm_paddr_t *paddr,
439    int nprot, vm_memattr_t *memattr)
440{
441	struct lpc_fb_softc *sc = cdev->si_drv1;
442
443	*paddr = (vm_paddr_t)(sc->lf_buffer_phys + offset);
444	return (0);
445}
446
447static void
448lpc_fb_blank(struct lpc_fb_softc *sc)
449{
450	memset(sc->lf_buffer, 0xffff, sc->lf_buffer_size);
451}
452
453static device_method_t lpc_fb_methods[] = {
454	/* Device interface */
455	DEVMETHOD(device_probe,		lpc_fb_probe),
456	DEVMETHOD(device_attach,	lpc_fb_attach),
457
458	{ 0, 0 }
459};
460
461static devclass_t lpc_fb_devclass;
462
463static driver_t lpc_fb_driver = {
464	"lpcfb",
465	lpc_fb_methods,
466	sizeof(struct lpc_fb_softc),
467};
468
469DRIVER_MODULE(lpcfb, simplebus, lpc_fb_driver, lpc_fb_devclass, 0, 0);
470