1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> |
4 | * Parts of this file were based on sources as follows: |
5 | * |
6 | * Copyright (C) 2006-2008 Intel Corporation |
7 | * Copyright (C) 2007 Amos Lee <amos_lee@storlinksemi.com> |
8 | * Copyright (C) 2007 Dave Airlie <airlied@linux.ie> |
9 | * Copyright (C) 2011 Texas Instruments |
10 | * Copyright (C) 2017 Eric Anholt |
11 | */ |
12 | |
13 | /** |
14 | * DOC: Faraday TV Encoder TVE200 DRM Driver |
15 | * |
16 | * The Faraday TV Encoder TVE200 is also known as the Gemini TV Interface |
17 | * Controller (TVC) and is found in the Gemini Chipset from Storlink |
18 | * Semiconductor (later Storm Semiconductor, later Cortina Systems) |
19 | * but also in the Grain Media GM8180 chipset. On the Gemini the module |
20 | * is connected to 8 data lines and a single clock line, comprising an |
21 | * 8-bit BT.656 interface. |
22 | * |
23 | * This is a very basic YUV display driver. The datasheet specifies that |
24 | * it supports the ITU BT.656 standard. It requires a 27 MHz clock which is |
25 | * the hallmark of any TV encoder supporting both PAL and NTSC. |
26 | * |
27 | * This driver exposes a standard KMS interface for this TV encoder. |
28 | */ |
29 | |
30 | #include <linux/clk.h> |
31 | #include <linux/dma-buf.h> |
32 | #include <linux/irq.h> |
33 | #include <linux/io.h> |
34 | #include <linux/module.h> |
35 | #include <linux/of.h> |
36 | #include <linux/platform_device.h> |
37 | #include <linux/shmem_fs.h> |
38 | #include <linux/slab.h> |
39 | |
40 | #include <drm/drm_atomic_helper.h> |
41 | #include <drm/drm_bridge.h> |
42 | #include <drm/drm_drv.h> |
43 | #include <drm/drm_fbdev_dma.h> |
44 | #include <drm/drm_gem_dma_helper.h> |
45 | #include <drm/drm_gem_framebuffer_helper.h> |
46 | #include <drm/drm_module.h> |
47 | #include <drm/drm_of.h> |
48 | #include <drm/drm_panel.h> |
49 | #include <drm/drm_probe_helper.h> |
50 | #include <drm/drm_vblank.h> |
51 | |
52 | #include "tve200_drm.h" |
53 | |
54 | #define DRIVER_DESC "DRM module for Faraday TVE200" |
55 | |
56 | static const struct drm_mode_config_funcs mode_config_funcs = { |
57 | .fb_create = drm_gem_fb_create, |
58 | .atomic_check = drm_atomic_helper_check, |
59 | .atomic_commit = drm_atomic_helper_commit, |
60 | }; |
61 | |
62 | static int tve200_modeset_init(struct drm_device *dev) |
63 | { |
64 | struct drm_mode_config *mode_config; |
65 | struct tve200_drm_dev_private *priv = dev->dev_private; |
66 | struct drm_panel *panel; |
67 | struct drm_bridge *bridge; |
68 | int ret; |
69 | |
70 | drm_mode_config_init(dev); |
71 | mode_config = &dev->mode_config; |
72 | mode_config->funcs = &mode_config_funcs; |
73 | mode_config->min_width = 352; |
74 | mode_config->max_width = 720; |
75 | mode_config->min_height = 240; |
76 | mode_config->max_height = 576; |
77 | |
78 | ret = drm_of_find_panel_or_bridge(np: dev->dev->of_node, |
79 | port: 0, endpoint: 0, panel: &panel, bridge: &bridge); |
80 | if (ret && ret != -ENODEV) |
81 | return ret; |
82 | if (panel) { |
83 | bridge = drm_panel_bridge_add_typed(panel, |
84 | DRM_MODE_CONNECTOR_Unknown); |
85 | if (IS_ERR(ptr: bridge)) { |
86 | ret = PTR_ERR(ptr: bridge); |
87 | goto out_bridge; |
88 | } |
89 | } else { |
90 | /* |
91 | * TODO: when we are using a different bridge than a panel |
92 | * (such as a dumb VGA connector) we need to devise a different |
93 | * method to get the connector out of the bridge. |
94 | */ |
95 | dev_err(dev->dev, "the bridge is not a panel\n" ); |
96 | ret = -EINVAL; |
97 | goto out_bridge; |
98 | } |
99 | |
100 | ret = tve200_display_init(dev); |
101 | if (ret) { |
102 | dev_err(dev->dev, "failed to init display\n" ); |
103 | goto out_bridge; |
104 | } |
105 | |
106 | ret = drm_simple_display_pipe_attach_bridge(pipe: &priv->pipe, |
107 | bridge); |
108 | if (ret) { |
109 | dev_err(dev->dev, "failed to attach bridge\n" ); |
110 | goto out_bridge; |
111 | } |
112 | |
113 | priv->panel = panel; |
114 | priv->connector = drm_panel_bridge_connector(bridge); |
115 | priv->bridge = bridge; |
116 | |
117 | dev_info(dev->dev, "attached to panel %s\n" , |
118 | dev_name(panel->dev)); |
119 | |
120 | ret = drm_vblank_init(dev, num_crtcs: 1); |
121 | if (ret) { |
122 | dev_err(dev->dev, "failed to init vblank\n" ); |
123 | goto out_bridge; |
124 | } |
125 | |
126 | drm_mode_config_reset(dev); |
127 | drm_kms_helper_poll_init(dev); |
128 | |
129 | goto finish; |
130 | |
131 | out_bridge: |
132 | if (panel) |
133 | drm_panel_bridge_remove(bridge); |
134 | drm_mode_config_cleanup(dev); |
135 | finish: |
136 | return ret; |
137 | } |
138 | |
139 | DEFINE_DRM_GEM_DMA_FOPS(drm_fops); |
140 | |
141 | static const struct drm_driver tve200_drm_driver = { |
142 | .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, |
143 | .ioctls = NULL, |
144 | .fops = &drm_fops, |
145 | .name = "tve200" , |
146 | .desc = DRIVER_DESC, |
147 | .date = "20170703" , |
148 | .major = 1, |
149 | .minor = 0, |
150 | .patchlevel = 0, |
151 | DRM_GEM_DMA_DRIVER_OPS, |
152 | }; |
153 | |
154 | static int tve200_probe(struct platform_device *pdev) |
155 | { |
156 | struct device *dev = &pdev->dev; |
157 | struct tve200_drm_dev_private *priv; |
158 | struct drm_device *drm; |
159 | int irq; |
160 | int ret; |
161 | |
162 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
163 | if (!priv) |
164 | return -ENOMEM; |
165 | |
166 | drm = drm_dev_alloc(driver: &tve200_drm_driver, parent: dev); |
167 | if (IS_ERR(ptr: drm)) |
168 | return PTR_ERR(ptr: drm); |
169 | platform_set_drvdata(pdev, data: drm); |
170 | priv->drm = drm; |
171 | drm->dev_private = priv; |
172 | |
173 | /* Clock the silicon so we can access the registers */ |
174 | priv->pclk = devm_clk_get(dev, id: "PCLK" ); |
175 | if (IS_ERR(ptr: priv->pclk)) { |
176 | dev_err(dev, "unable to get PCLK\n" ); |
177 | ret = PTR_ERR(ptr: priv->pclk); |
178 | goto dev_unref; |
179 | } |
180 | ret = clk_prepare_enable(clk: priv->pclk); |
181 | if (ret) { |
182 | dev_err(dev, "failed to enable PCLK\n" ); |
183 | goto dev_unref; |
184 | } |
185 | |
186 | /* This clock is for the pixels (27MHz) */ |
187 | priv->clk = devm_clk_get(dev, id: "TVE" ); |
188 | if (IS_ERR(ptr: priv->clk)) { |
189 | dev_err(dev, "unable to get TVE clock\n" ); |
190 | ret = PTR_ERR(ptr: priv->clk); |
191 | goto clk_disable; |
192 | } |
193 | |
194 | priv->regs = devm_platform_ioremap_resource(pdev, index: 0); |
195 | if (IS_ERR(ptr: priv->regs)) { |
196 | dev_err(dev, "%s failed mmio\n" , __func__); |
197 | ret = -EINVAL; |
198 | goto clk_disable; |
199 | } |
200 | |
201 | irq = platform_get_irq(pdev, 0); |
202 | if (irq < 0) { |
203 | ret = irq; |
204 | goto clk_disable; |
205 | } |
206 | |
207 | /* turn off interrupts before requesting the irq */ |
208 | writel(val: 0, addr: priv->regs + TVE200_INT_EN); |
209 | |
210 | ret = devm_request_irq(dev, irq, handler: tve200_irq, irqflags: 0, devname: "tve200" , dev_id: priv); |
211 | if (ret) { |
212 | dev_err(dev, "failed to request irq %d\n" , ret); |
213 | goto clk_disable; |
214 | } |
215 | |
216 | ret = tve200_modeset_init(dev: drm); |
217 | if (ret) |
218 | goto clk_disable; |
219 | |
220 | ret = drm_dev_register(dev: drm, flags: 0); |
221 | if (ret < 0) |
222 | goto clk_disable; |
223 | |
224 | /* |
225 | * Passing in 16 here will make the RGB565 mode the default |
226 | * Passing in 32 will use XRGB8888 mode |
227 | */ |
228 | drm_fbdev_dma_setup(dev: drm, preferred_bpp: 16); |
229 | |
230 | return 0; |
231 | |
232 | clk_disable: |
233 | clk_disable_unprepare(clk: priv->pclk); |
234 | dev_unref: |
235 | drm_dev_put(dev: drm); |
236 | return ret; |
237 | } |
238 | |
239 | static void tve200_remove(struct platform_device *pdev) |
240 | { |
241 | struct drm_device *drm = platform_get_drvdata(pdev); |
242 | struct tve200_drm_dev_private *priv = drm->dev_private; |
243 | |
244 | drm_dev_unregister(dev: drm); |
245 | drm_atomic_helper_shutdown(dev: drm); |
246 | if (priv->panel) |
247 | drm_panel_bridge_remove(bridge: priv->bridge); |
248 | drm_mode_config_cleanup(dev: drm); |
249 | clk_disable_unprepare(clk: priv->pclk); |
250 | drm_dev_put(dev: drm); |
251 | } |
252 | |
253 | static void tve200_shutdown(struct platform_device *pdev) |
254 | { |
255 | drm_atomic_helper_shutdown(dev: platform_get_drvdata(pdev)); |
256 | } |
257 | |
258 | static const struct of_device_id tve200_of_match[] = { |
259 | { |
260 | .compatible = "faraday,tve200" , |
261 | }, |
262 | {}, |
263 | }; |
264 | |
265 | static struct platform_driver tve200_driver = { |
266 | .driver = { |
267 | .name = "tve200" , |
268 | .of_match_table = tve200_of_match, |
269 | }, |
270 | .probe = tve200_probe, |
271 | .remove_new = tve200_remove, |
272 | .shutdown = tve200_shutdown, |
273 | }; |
274 | drm_module_platform_driver(tve200_driver); |
275 | |
276 | MODULE_DESCRIPTION(DRIVER_DESC); |
277 | MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>" ); |
278 | MODULE_LICENSE("GPL" ); |
279 | |