sun4i_tcon.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. /*
  2. * Copyright (C) 2015 Free Electrons
  3. * Copyright (C) 2015 NextThing Co
  4. *
  5. * Maxime Ripard <maxime.ripard@free-electrons.com>
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License as
  9. * published by the Free Software Foundation; either version 2 of
  10. * the License, or (at your option) any later version.
  11. */
  12. #include <drm/drmP.h>
  13. #include <drm/drm_atomic_helper.h>
  14. #include <drm/drm_crtc.h>
  15. #include <drm/drm_crtc_helper.h>
  16. #include <drm/drm_modes.h>
  17. #include <drm/drm_of.h>
  18. #include <linux/component.h>
  19. #include <linux/ioport.h>
  20. #include <linux/of_address.h>
  21. #include <linux/of_device.h>
  22. #include <linux/of_irq.h>
  23. #include <linux/regmap.h>
  24. #include <linux/reset.h>
  25. #include "sun4i_crtc.h"
  26. #include "sun4i_dotclock.h"
  27. #include "sun4i_drv.h"
  28. #include "sun4i_rgb.h"
  29. #include "sun4i_tcon.h"
  30. void sun4i_tcon_disable(struct sun4i_tcon *tcon)
  31. {
  32. DRM_DEBUG_DRIVER("Disabling TCON\n");
  33. /* Disable the TCON */
  34. regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
  35. SUN4I_TCON_GCTL_TCON_ENABLE, 0);
  36. }
  37. EXPORT_SYMBOL(sun4i_tcon_disable);
  38. void sun4i_tcon_enable(struct sun4i_tcon *tcon)
  39. {
  40. DRM_DEBUG_DRIVER("Enabling TCON\n");
  41. /* Enable the TCON */
  42. regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
  43. SUN4I_TCON_GCTL_TCON_ENABLE,
  44. SUN4I_TCON_GCTL_TCON_ENABLE);
  45. }
  46. EXPORT_SYMBOL(sun4i_tcon_enable);
  47. void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel)
  48. {
  49. /* Disable the TCON's channel */
  50. if (channel == 0) {
  51. regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
  52. SUN4I_TCON0_CTL_TCON_ENABLE, 0);
  53. clk_disable_unprepare(tcon->dclk);
  54. return;
  55. }
  56. WARN_ON(!tcon->quirks->has_channel_1);
  57. regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
  58. SUN4I_TCON1_CTL_TCON_ENABLE, 0);
  59. clk_disable_unprepare(tcon->sclk1);
  60. }
  61. EXPORT_SYMBOL(sun4i_tcon_channel_disable);
  62. void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel)
  63. {
  64. /* Enable the TCON's channel */
  65. if (channel == 0) {
  66. regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
  67. SUN4I_TCON0_CTL_TCON_ENABLE,
  68. SUN4I_TCON0_CTL_TCON_ENABLE);
  69. clk_prepare_enable(tcon->dclk);
  70. return;
  71. }
  72. WARN_ON(!tcon->quirks->has_channel_1);
  73. regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
  74. SUN4I_TCON1_CTL_TCON_ENABLE,
  75. SUN4I_TCON1_CTL_TCON_ENABLE);
  76. clk_prepare_enable(tcon->sclk1);
  77. }
  78. EXPORT_SYMBOL(sun4i_tcon_channel_enable);
  79. void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
  80. {
  81. u32 mask, val = 0;
  82. DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n", enable ? "En" : "Dis");
  83. mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) |
  84. SUN4I_TCON_GINT0_VBLANK_ENABLE(1);
  85. if (enable)
  86. val = mask;
  87. regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, mask, val);
  88. }
  89. EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
  90. static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
  91. int channel)
  92. {
  93. int delay = mode->vtotal - mode->vdisplay;
  94. if (mode->flags & DRM_MODE_FLAG_INTERLACE)
  95. delay /= 2;
  96. if (channel == 1)
  97. delay -= 2;
  98. delay = min(delay, 30);
  99. DRM_DEBUG_DRIVER("TCON %d clock delay %u\n", channel, delay);
  100. return delay;
  101. }
  102. void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
  103. struct drm_display_mode *mode)
  104. {
  105. unsigned int bp, hsync, vsync;
  106. u8 clk_delay;
  107. u32 val = 0;
  108. /* Adjust clock delay */
  109. clk_delay = sun4i_tcon_get_clk_delay(mode, 0);
  110. regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
  111. SUN4I_TCON0_CTL_CLK_DELAY_MASK,
  112. SUN4I_TCON0_CTL_CLK_DELAY(clk_delay));
  113. /* Set the resolution */
  114. regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG,
  115. SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) |
  116. SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay));
  117. /*
  118. * This is called a backporch in the register documentation,
  119. * but it really is the back porch + hsync
  120. */
  121. bp = mode->crtc_htotal - mode->crtc_hsync_start;
  122. DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
  123. mode->crtc_htotal, bp);
  124. /* Set horizontal display timings */
  125. regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG,
  126. SUN4I_TCON0_BASIC1_H_TOTAL(mode->crtc_htotal) |
  127. SUN4I_TCON0_BASIC1_H_BACKPORCH(bp));
  128. /*
  129. * This is called a backporch in the register documentation,
  130. * but it really is the back porch + hsync
  131. */
  132. bp = mode->crtc_vtotal - mode->crtc_vsync_start;
  133. DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
  134. mode->crtc_vtotal, bp);
  135. /* Set vertical display timings */
  136. regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG,
  137. SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) |
  138. SUN4I_TCON0_BASIC2_V_BACKPORCH(bp));
  139. /* Set Hsync and Vsync length */
  140. hsync = mode->crtc_hsync_end - mode->crtc_hsync_start;
  141. vsync = mode->crtc_vsync_end - mode->crtc_vsync_start;
  142. DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync);
  143. regmap_write(tcon->regs, SUN4I_TCON0_BASIC3_REG,
  144. SUN4I_TCON0_BASIC3_V_SYNC(vsync) |
  145. SUN4I_TCON0_BASIC3_H_SYNC(hsync));
  146. /* Setup the polarity of the various signals */
  147. if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
  148. val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE;
  149. if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
  150. val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE;
  151. regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG,
  152. SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE,
  153. val);
  154. /* Map output pins to channel 0 */
  155. regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
  156. SUN4I_TCON_GCTL_IOMAP_MASK,
  157. SUN4I_TCON_GCTL_IOMAP_TCON0);
  158. /* Enable the output on the pins */
  159. regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0);
  160. }
  161. EXPORT_SYMBOL(sun4i_tcon0_mode_set);
  162. void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
  163. struct drm_display_mode *mode)
  164. {
  165. unsigned int bp, hsync, vsync;
  166. u8 clk_delay;
  167. u32 val;
  168. WARN_ON(!tcon->quirks->has_channel_1);
  169. /* Adjust clock delay */
  170. clk_delay = sun4i_tcon_get_clk_delay(mode, 1);
  171. regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
  172. SUN4I_TCON1_CTL_CLK_DELAY_MASK,
  173. SUN4I_TCON1_CTL_CLK_DELAY(clk_delay));
  174. /* Set interlaced mode */
  175. if (mode->flags & DRM_MODE_FLAG_INTERLACE)
  176. val = SUN4I_TCON1_CTL_INTERLACE_ENABLE;
  177. else
  178. val = 0;
  179. regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
  180. SUN4I_TCON1_CTL_INTERLACE_ENABLE,
  181. val);
  182. /* Set the input resolution */
  183. regmap_write(tcon->regs, SUN4I_TCON1_BASIC0_REG,
  184. SUN4I_TCON1_BASIC0_X(mode->crtc_hdisplay) |
  185. SUN4I_TCON1_BASIC0_Y(mode->crtc_vdisplay));
  186. /* Set the upscaling resolution */
  187. regmap_write(tcon->regs, SUN4I_TCON1_BASIC1_REG,
  188. SUN4I_TCON1_BASIC1_X(mode->crtc_hdisplay) |
  189. SUN4I_TCON1_BASIC1_Y(mode->crtc_vdisplay));
  190. /* Set the output resolution */
  191. regmap_write(tcon->regs, SUN4I_TCON1_BASIC2_REG,
  192. SUN4I_TCON1_BASIC2_X(mode->crtc_hdisplay) |
  193. SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay));
  194. /* Set horizontal display timings */
  195. bp = mode->crtc_htotal - mode->crtc_hsync_end;
  196. DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
  197. mode->htotal, bp);
  198. regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG,
  199. SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) |
  200. SUN4I_TCON1_BASIC3_H_BACKPORCH(bp));
  201. /* Set vertical display timings */
  202. bp = mode->crtc_vtotal - mode->crtc_vsync_end;
  203. DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
  204. mode->vtotal, bp);
  205. regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
  206. SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
  207. SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
  208. /* Set Hsync and Vsync length */
  209. hsync = mode->crtc_hsync_end - mode->crtc_hsync_start;
  210. vsync = mode->crtc_vsync_end - mode->crtc_vsync_start;
  211. DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync);
  212. regmap_write(tcon->regs, SUN4I_TCON1_BASIC5_REG,
  213. SUN4I_TCON1_BASIC5_V_SYNC(vsync) |
  214. SUN4I_TCON1_BASIC5_H_SYNC(hsync));
  215. /* Map output pins to channel 1 */
  216. regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
  217. SUN4I_TCON_GCTL_IOMAP_MASK,
  218. SUN4I_TCON_GCTL_IOMAP_TCON1);
  219. /*
  220. * FIXME: Undocumented bits
  221. */
  222. if (tcon->quirks->has_unknown_mux)
  223. regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
  224. }
  225. EXPORT_SYMBOL(sun4i_tcon1_mode_set);
  226. static void sun4i_tcon_finish_page_flip(struct drm_device *dev,
  227. struct sun4i_crtc *scrtc)
  228. {
  229. unsigned long flags;
  230. spin_lock_irqsave(&dev->event_lock, flags);
  231. if (scrtc->event) {
  232. drm_crtc_send_vblank_event(&scrtc->crtc, scrtc->event);
  233. drm_crtc_vblank_put(&scrtc->crtc);
  234. scrtc->event = NULL;
  235. }
  236. spin_unlock_irqrestore(&dev->event_lock, flags);
  237. }
  238. static irqreturn_t sun4i_tcon_handler(int irq, void *private)
  239. {
  240. struct sun4i_tcon *tcon = private;
  241. struct drm_device *drm = tcon->drm;
  242. struct sun4i_crtc *scrtc = tcon->crtc;
  243. unsigned int status;
  244. regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status);
  245. if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) |
  246. SUN4I_TCON_GINT0_VBLANK_INT(1))))
  247. return IRQ_NONE;
  248. drm_crtc_handle_vblank(&scrtc->crtc);
  249. sun4i_tcon_finish_page_flip(drm, scrtc);
  250. /* Acknowledge the interrupt */
  251. regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG,
  252. SUN4I_TCON_GINT0_VBLANK_INT(0) |
  253. SUN4I_TCON_GINT0_VBLANK_INT(1),
  254. 0);
  255. return IRQ_HANDLED;
  256. }
  257. static int sun4i_tcon_init_clocks(struct device *dev,
  258. struct sun4i_tcon *tcon)
  259. {
  260. tcon->clk = devm_clk_get(dev, "ahb");
  261. if (IS_ERR(tcon->clk)) {
  262. dev_err(dev, "Couldn't get the TCON bus clock\n");
  263. return PTR_ERR(tcon->clk);
  264. }
  265. clk_prepare_enable(tcon->clk);
  266. tcon->sclk0 = devm_clk_get(dev, "tcon-ch0");
  267. if (IS_ERR(tcon->sclk0)) {
  268. dev_err(dev, "Couldn't get the TCON channel 0 clock\n");
  269. return PTR_ERR(tcon->sclk0);
  270. }
  271. if (tcon->quirks->has_channel_1) {
  272. tcon->sclk1 = devm_clk_get(dev, "tcon-ch1");
  273. if (IS_ERR(tcon->sclk1)) {
  274. dev_err(dev, "Couldn't get the TCON channel 1 clock\n");
  275. return PTR_ERR(tcon->sclk1);
  276. }
  277. }
  278. return 0;
  279. }
  280. static void sun4i_tcon_free_clocks(struct sun4i_tcon *tcon)
  281. {
  282. clk_disable_unprepare(tcon->clk);
  283. }
  284. static int sun4i_tcon_init_irq(struct device *dev,
  285. struct sun4i_tcon *tcon)
  286. {
  287. struct platform_device *pdev = to_platform_device(dev);
  288. int irq, ret;
  289. irq = platform_get_irq(pdev, 0);
  290. if (irq < 0) {
  291. dev_err(dev, "Couldn't retrieve the TCON interrupt\n");
  292. return irq;
  293. }
  294. ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0,
  295. dev_name(dev), tcon);
  296. if (ret) {
  297. dev_err(dev, "Couldn't request the IRQ\n");
  298. return ret;
  299. }
  300. return 0;
  301. }
  302. static struct regmap_config sun4i_tcon_regmap_config = {
  303. .reg_bits = 32,
  304. .val_bits = 32,
  305. .reg_stride = 4,
  306. .max_register = 0x800,
  307. };
  308. static int sun4i_tcon_init_regmap(struct device *dev,
  309. struct sun4i_tcon *tcon)
  310. {
  311. struct platform_device *pdev = to_platform_device(dev);
  312. struct resource *res;
  313. void __iomem *regs;
  314. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  315. regs = devm_ioremap_resource(dev, res);
  316. if (IS_ERR(regs))
  317. return PTR_ERR(regs);
  318. tcon->regs = devm_regmap_init_mmio(dev, regs,
  319. &sun4i_tcon_regmap_config);
  320. if (IS_ERR(tcon->regs)) {
  321. dev_err(dev, "Couldn't create the TCON regmap\n");
  322. return PTR_ERR(tcon->regs);
  323. }
  324. /* Make sure the TCON is disabled and all IRQs are off */
  325. regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, 0);
  326. regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0);
  327. regmap_write(tcon->regs, SUN4I_TCON_GINT1_REG, 0);
  328. /* Disable IO lines and set them to tristate */
  329. regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, ~0);
  330. regmap_write(tcon->regs, SUN4I_TCON1_IO_TRI_REG, ~0);
  331. return 0;
  332. }
  333. static int sun4i_tcon_bind(struct device *dev, struct device *master,
  334. void *data)
  335. {
  336. struct drm_device *drm = data;
  337. struct sun4i_drv *drv = drm->dev_private;
  338. struct sun4i_tcon *tcon;
  339. int ret;
  340. tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL);
  341. if (!tcon)
  342. return -ENOMEM;
  343. dev_set_drvdata(dev, tcon);
  344. drv->tcon = tcon;
  345. tcon->drm = drm;
  346. tcon->dev = dev;
  347. tcon->quirks = of_device_get_match_data(dev);
  348. tcon->lcd_rst = devm_reset_control_get(dev, "lcd");
  349. if (IS_ERR(tcon->lcd_rst)) {
  350. dev_err(dev, "Couldn't get our reset line\n");
  351. return PTR_ERR(tcon->lcd_rst);
  352. }
  353. /* Make sure our TCON is reset */
  354. if (!reset_control_status(tcon->lcd_rst))
  355. reset_control_assert(tcon->lcd_rst);
  356. ret = reset_control_deassert(tcon->lcd_rst);
  357. if (ret) {
  358. dev_err(dev, "Couldn't deassert our reset line\n");
  359. return ret;
  360. }
  361. ret = sun4i_tcon_init_clocks(dev, tcon);
  362. if (ret) {
  363. dev_err(dev, "Couldn't init our TCON clocks\n");
  364. goto err_assert_reset;
  365. }
  366. ret = sun4i_tcon_init_regmap(dev, tcon);
  367. if (ret) {
  368. dev_err(dev, "Couldn't init our TCON regmap\n");
  369. goto err_free_clocks;
  370. }
  371. ret = sun4i_dclk_create(dev, tcon);
  372. if (ret) {
  373. dev_err(dev, "Couldn't create our TCON dot clock\n");
  374. goto err_free_clocks;
  375. }
  376. ret = sun4i_tcon_init_irq(dev, tcon);
  377. if (ret) {
  378. dev_err(dev, "Couldn't init our TCON interrupts\n");
  379. goto err_free_dotclock;
  380. }
  381. tcon->crtc = sun4i_crtc_init(drm, drv->backend, tcon);
  382. if (IS_ERR(tcon->crtc)) {
  383. dev_err(dev, "Couldn't create our CRTC\n");
  384. ret = PTR_ERR(tcon->crtc);
  385. goto err_free_clocks;
  386. }
  387. ret = sun4i_rgb_init(drm, tcon);
  388. if (ret < 0)
  389. goto err_free_clocks;
  390. return 0;
  391. err_free_dotclock:
  392. sun4i_dclk_free(tcon);
  393. err_free_clocks:
  394. sun4i_tcon_free_clocks(tcon);
  395. err_assert_reset:
  396. reset_control_assert(tcon->lcd_rst);
  397. return ret;
  398. }
  399. static void sun4i_tcon_unbind(struct device *dev, struct device *master,
  400. void *data)
  401. {
  402. struct sun4i_tcon *tcon = dev_get_drvdata(dev);
  403. sun4i_dclk_free(tcon);
  404. sun4i_tcon_free_clocks(tcon);
  405. }
  406. static const struct component_ops sun4i_tcon_ops = {
  407. .bind = sun4i_tcon_bind,
  408. .unbind = sun4i_tcon_unbind,
  409. };
  410. static int sun4i_tcon_probe(struct platform_device *pdev)
  411. {
  412. struct device_node *node = pdev->dev.of_node;
  413. struct drm_bridge *bridge;
  414. struct drm_panel *panel;
  415. int ret;
  416. ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge);
  417. if (ret == -EPROBE_DEFER)
  418. return ret;
  419. return component_add(&pdev->dev, &sun4i_tcon_ops);
  420. }
  421. static int sun4i_tcon_remove(struct platform_device *pdev)
  422. {
  423. component_del(&pdev->dev, &sun4i_tcon_ops);
  424. return 0;
  425. }
  426. static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
  427. .has_unknown_mux = true,
  428. .has_channel_1 = true,
  429. };
  430. static const struct sun4i_tcon_quirks sun6i_a31_quirks = {
  431. .has_channel_1 = true,
  432. };
  433. static const struct sun4i_tcon_quirks sun6i_a31s_quirks = {
  434. .has_channel_1 = true,
  435. };
  436. static const struct sun4i_tcon_quirks sun8i_a33_quirks = {
  437. /* nothing is supported */
  438. };
  439. static const struct of_device_id sun4i_tcon_of_table[] = {
  440. { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks },
  441. { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks },
  442. { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks },
  443. { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks },
  444. { }
  445. };
  446. MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table);
  447. static struct platform_driver sun4i_tcon_platform_driver = {
  448. .probe = sun4i_tcon_probe,
  449. .remove = sun4i_tcon_remove,
  450. .driver = {
  451. .name = "sun4i-tcon",
  452. .of_match_table = sun4i_tcon_of_table,
  453. },
  454. };
  455. module_platform_driver(sun4i_tcon_platform_driver);
  456. MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
  457. MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver");
  458. MODULE_LICENSE("GPL");