19960aa7cSTomi Valkeinen /* 29960aa7cSTomi Valkeinen * Copyright (C) 2009 Nokia Corporation 36505d75cSTomi Valkeinen * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> 49960aa7cSTomi Valkeinen * 59960aa7cSTomi Valkeinen * This program is free software; you can redistribute it and/or modify it 69960aa7cSTomi Valkeinen * under the terms of the GNU General Public License version 2 as published by 79960aa7cSTomi Valkeinen * the Free Software Foundation. 89960aa7cSTomi Valkeinen * 99960aa7cSTomi Valkeinen * This program is distributed in the hope that it will be useful, but WITHOUT 109960aa7cSTomi Valkeinen * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 119960aa7cSTomi Valkeinen * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 129960aa7cSTomi Valkeinen * more details. 139960aa7cSTomi Valkeinen * 149960aa7cSTomi Valkeinen * You should have received a copy of the GNU General Public License along with 159960aa7cSTomi Valkeinen * this program. If not, see <http://www.gnu.org/licenses/>. 169960aa7cSTomi Valkeinen */ 179960aa7cSTomi Valkeinen 189960aa7cSTomi Valkeinen #define DSS_SUBSYS_NAME "SDI" 199960aa7cSTomi Valkeinen 209960aa7cSTomi Valkeinen #include <linux/kernel.h> 219960aa7cSTomi Valkeinen #include <linux/delay.h> 229960aa7cSTomi Valkeinen #include <linux/err.h> 239960aa7cSTomi Valkeinen #include <linux/regulator/consumer.h> 249960aa7cSTomi Valkeinen #include <linux/export.h> 259960aa7cSTomi Valkeinen #include <linux/platform_device.h> 269960aa7cSTomi Valkeinen #include <linux/string.h> 279960aa7cSTomi Valkeinen #include <linux/of.h> 289960aa7cSTomi Valkeinen 2932043da7SPeter Ujfalusi #include "omapdss.h" 309960aa7cSTomi Valkeinen #include "dss.h" 319960aa7cSTomi Valkeinen 3224aac601SLaurent Pinchart struct sdi_device { 339960aa7cSTomi Valkeinen struct platform_device *pdev; 34d7157dfeSLaurent Pinchart struct dss_device *dss; 359960aa7cSTomi Valkeinen 369960aa7cSTomi Valkeinen bool update_enabled; 379960aa7cSTomi Valkeinen struct regulator *vdds_sdi_reg; 389960aa7cSTomi Valkeinen 399960aa7cSTomi Valkeinen struct dss_lcd_mgr_config mgr_config; 40e5906f76SLaurent Pinchart unsigned long pixelclock; 419960aa7cSTomi Valkeinen int datapairs; 429960aa7cSTomi Valkeinen 439960aa7cSTomi Valkeinen struct omap_dss_device output; 4424aac601SLaurent Pinchart }; 459960aa7cSTomi Valkeinen 4624aac601SLaurent Pinchart #define dssdev_to_sdi(dssdev) container_of(dssdev, struct sdi_device, output) 479960aa7cSTomi Valkeinen 489960aa7cSTomi Valkeinen struct sdi_clk_calc_ctx { 4924aac601SLaurent Pinchart struct sdi_device *sdi; 509960aa7cSTomi Valkeinen unsigned long pck_min, pck_max; 519960aa7cSTomi Valkeinen 529960aa7cSTomi Valkeinen unsigned long fck; 539960aa7cSTomi Valkeinen struct dispc_clock_info dispc_cinfo; 549960aa7cSTomi Valkeinen }; 559960aa7cSTomi Valkeinen 569960aa7cSTomi Valkeinen static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, 579960aa7cSTomi Valkeinen unsigned long pck, void *data) 589960aa7cSTomi Valkeinen { 599960aa7cSTomi Valkeinen struct sdi_clk_calc_ctx *ctx = data; 609960aa7cSTomi Valkeinen 619960aa7cSTomi Valkeinen ctx->dispc_cinfo.lck_div = lckd; 629960aa7cSTomi Valkeinen ctx->dispc_cinfo.pck_div = pckd; 639960aa7cSTomi Valkeinen ctx->dispc_cinfo.lck = lck; 649960aa7cSTomi Valkeinen ctx->dispc_cinfo.pck = pck; 659960aa7cSTomi Valkeinen 669960aa7cSTomi Valkeinen return true; 679960aa7cSTomi Valkeinen } 689960aa7cSTomi Valkeinen 699960aa7cSTomi Valkeinen static bool dpi_calc_dss_cb(unsigned long fck, void *data) 709960aa7cSTomi Valkeinen { 719960aa7cSTomi Valkeinen struct sdi_clk_calc_ctx *ctx = data; 729960aa7cSTomi Valkeinen 739960aa7cSTomi Valkeinen ctx->fck = fck; 749960aa7cSTomi Valkeinen 7524aac601SLaurent Pinchart return dispc_div_calc(ctx->sdi->dss->dispc, fck, 768a7eda76SLaurent Pinchart ctx->pck_min, ctx->pck_max, 779960aa7cSTomi Valkeinen dpi_calc_dispc_cb, ctx); 789960aa7cSTomi Valkeinen } 799960aa7cSTomi Valkeinen 8024aac601SLaurent Pinchart static int sdi_calc_clock_div(struct sdi_device *sdi, unsigned long pclk, 819960aa7cSTomi Valkeinen unsigned long *fck, 829960aa7cSTomi Valkeinen struct dispc_clock_info *dispc_cinfo) 839960aa7cSTomi Valkeinen { 849960aa7cSTomi Valkeinen int i; 852bc5ff0bSTomi Valkeinen struct sdi_clk_calc_ctx ctx; 869960aa7cSTomi Valkeinen 879960aa7cSTomi Valkeinen /* 889960aa7cSTomi Valkeinen * DSS fclk gives us very few possibilities, so finding a good pixel 899960aa7cSTomi Valkeinen * clock may not be possible. We try multiple times to find the clock, 909960aa7cSTomi Valkeinen * each time widening the pixel clock range we look for, up to 919960aa7cSTomi Valkeinen * +/- 1MHz. 929960aa7cSTomi Valkeinen */ 939960aa7cSTomi Valkeinen 949960aa7cSTomi Valkeinen for (i = 0; i < 10; ++i) { 959960aa7cSTomi Valkeinen bool ok; 969960aa7cSTomi Valkeinen 979960aa7cSTomi Valkeinen memset(&ctx, 0, sizeof(ctx)); 982bc5ff0bSTomi Valkeinen 992bc5ff0bSTomi Valkeinen ctx.sdi = sdi; 1002bc5ff0bSTomi Valkeinen 1019960aa7cSTomi Valkeinen if (pclk > 1000 * i * i * i) 1029960aa7cSTomi Valkeinen ctx.pck_min = max(pclk - 1000 * i * i * i, 0lu); 1039960aa7cSTomi Valkeinen else 1049960aa7cSTomi Valkeinen ctx.pck_min = 0; 1059960aa7cSTomi Valkeinen ctx.pck_max = pclk + 1000 * i * i * i; 1069960aa7cSTomi Valkeinen 10724aac601SLaurent Pinchart ok = dss_div_calc(sdi->dss, pclk, ctx.pck_min, 10860f9c59fSLaurent Pinchart dpi_calc_dss_cb, &ctx); 1099960aa7cSTomi Valkeinen if (ok) { 1109960aa7cSTomi Valkeinen *fck = ctx.fck; 1119960aa7cSTomi Valkeinen *dispc_cinfo = ctx.dispc_cinfo; 1129960aa7cSTomi Valkeinen return 0; 1139960aa7cSTomi Valkeinen } 1149960aa7cSTomi Valkeinen } 1159960aa7cSTomi Valkeinen 1169960aa7cSTomi Valkeinen return -EINVAL; 1179960aa7cSTomi Valkeinen } 1189960aa7cSTomi Valkeinen 11924aac601SLaurent Pinchart static void sdi_config_lcd_manager(struct sdi_device *sdi) 1209960aa7cSTomi Valkeinen { 12124aac601SLaurent Pinchart sdi->mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; 1229960aa7cSTomi Valkeinen 12324aac601SLaurent Pinchart sdi->mgr_config.stallmode = false; 12424aac601SLaurent Pinchart sdi->mgr_config.fifohandcheck = false; 1259960aa7cSTomi Valkeinen 12624aac601SLaurent Pinchart sdi->mgr_config.video_port_width = 24; 12724aac601SLaurent Pinchart sdi->mgr_config.lcden_sig_polarity = 1; 1289960aa7cSTomi Valkeinen 12924aac601SLaurent Pinchart dss_mgr_set_lcd_config(&sdi->output, &sdi->mgr_config); 1309960aa7cSTomi Valkeinen } 1319960aa7cSTomi Valkeinen 13219b4200dSLaurent Pinchart static void sdi_display_enable(struct omap_dss_device *dssdev) 1339960aa7cSTomi Valkeinen { 13424aac601SLaurent Pinchart struct sdi_device *sdi = dssdev_to_sdi(dssdev); 1359960aa7cSTomi Valkeinen struct dispc_clock_info dispc_cinfo; 13696fc64c7SLaurent Pinchart unsigned long fck; 1379960aa7cSTomi Valkeinen int r; 1389960aa7cSTomi Valkeinen 13924aac601SLaurent Pinchart r = regulator_enable(sdi->vdds_sdi_reg); 1409960aa7cSTomi Valkeinen if (r) 14119b4200dSLaurent Pinchart return; 1429960aa7cSTomi Valkeinen 14324aac601SLaurent Pinchart r = dispc_runtime_get(sdi->dss->dispc); 1449960aa7cSTomi Valkeinen if (r) 1459960aa7cSTomi Valkeinen goto err_get_dispc; 1469960aa7cSTomi Valkeinen 147e5906f76SLaurent Pinchart r = sdi_calc_clock_div(sdi, sdi->pixelclock, &fck, &dispc_cinfo); 1489960aa7cSTomi Valkeinen if (r) 1499960aa7cSTomi Valkeinen goto err_calc_clock_div; 1509960aa7cSTomi Valkeinen 15124aac601SLaurent Pinchart sdi->mgr_config.clock_info = dispc_cinfo; 1529960aa7cSTomi Valkeinen 15324aac601SLaurent Pinchart r = dss_set_fck_rate(sdi->dss, fck); 1549960aa7cSTomi Valkeinen if (r) 1559960aa7cSTomi Valkeinen goto err_set_dss_clock_div; 1569960aa7cSTomi Valkeinen 15724aac601SLaurent Pinchart sdi_config_lcd_manager(sdi); 1589960aa7cSTomi Valkeinen 1599960aa7cSTomi Valkeinen /* 1609960aa7cSTomi Valkeinen * LCLK and PCLK divisors are located in shadow registers, and we 1619960aa7cSTomi Valkeinen * normally write them to DISPC registers when enabling the output. 1629960aa7cSTomi Valkeinen * However, SDI uses pck-free as source clock for its PLL, and pck-free 1639960aa7cSTomi Valkeinen * is affected by the divisors. And as we need the PLL before enabling 1649960aa7cSTomi Valkeinen * the output, we need to write the divisors early. 1659960aa7cSTomi Valkeinen * 1669960aa7cSTomi Valkeinen * It seems just writing to the DISPC register is enough, and we don't 1679960aa7cSTomi Valkeinen * need to care about the shadow register mechanism for pck-free. The 1689960aa7cSTomi Valkeinen * exact reason for this is unknown. 1699960aa7cSTomi Valkeinen */ 17024aac601SLaurent Pinchart dispc_mgr_set_clock_div(sdi->dss->dispc, sdi->output.dispc_channel, 17124aac601SLaurent Pinchart &sdi->mgr_config.clock_info); 1729960aa7cSTomi Valkeinen 17324aac601SLaurent Pinchart dss_sdi_init(sdi->dss, sdi->datapairs); 17424aac601SLaurent Pinchart r = dss_sdi_enable(sdi->dss); 1759960aa7cSTomi Valkeinen if (r) 1769960aa7cSTomi Valkeinen goto err_sdi_enable; 1779960aa7cSTomi Valkeinen mdelay(2); 1789960aa7cSTomi Valkeinen 17924aac601SLaurent Pinchart r = dss_mgr_enable(&sdi->output); 1809960aa7cSTomi Valkeinen if (r) 1819960aa7cSTomi Valkeinen goto err_mgr_enable; 1829960aa7cSTomi Valkeinen 18319b4200dSLaurent Pinchart return; 1849960aa7cSTomi Valkeinen 1859960aa7cSTomi Valkeinen err_mgr_enable: 18624aac601SLaurent Pinchart dss_sdi_disable(sdi->dss); 1879960aa7cSTomi Valkeinen err_sdi_enable: 1889960aa7cSTomi Valkeinen err_set_dss_clock_div: 1899960aa7cSTomi Valkeinen err_calc_clock_div: 19024aac601SLaurent Pinchart dispc_runtime_put(sdi->dss->dispc); 1919960aa7cSTomi Valkeinen err_get_dispc: 19224aac601SLaurent Pinchart regulator_disable(sdi->vdds_sdi_reg); 1939960aa7cSTomi Valkeinen } 1949960aa7cSTomi Valkeinen 1959960aa7cSTomi Valkeinen static void sdi_display_disable(struct omap_dss_device *dssdev) 1969960aa7cSTomi Valkeinen { 19724aac601SLaurent Pinchart struct sdi_device *sdi = dssdev_to_sdi(dssdev); 1989960aa7cSTomi Valkeinen 19924aac601SLaurent Pinchart dss_mgr_disable(&sdi->output); 2009960aa7cSTomi Valkeinen 20124aac601SLaurent Pinchart dss_sdi_disable(sdi->dss); 2029960aa7cSTomi Valkeinen 20324aac601SLaurent Pinchart dispc_runtime_put(sdi->dss->dispc); 20424aac601SLaurent Pinchart 20524aac601SLaurent Pinchart regulator_disable(sdi->vdds_sdi_reg); 2069960aa7cSTomi Valkeinen } 2079960aa7cSTomi Valkeinen 2089960aa7cSTomi Valkeinen static void sdi_set_timings(struct omap_dss_device *dssdev, 20941322aa6SLaurent Pinchart const struct drm_display_mode *mode) 2109960aa7cSTomi Valkeinen { 21124aac601SLaurent Pinchart struct sdi_device *sdi = dssdev_to_sdi(dssdev); 21224aac601SLaurent Pinchart 213e5906f76SLaurent Pinchart sdi->pixelclock = mode->clock * 1000; 2149960aa7cSTomi Valkeinen } 2159960aa7cSTomi Valkeinen 2169960aa7cSTomi Valkeinen static int sdi_check_timings(struct omap_dss_device *dssdev, 21741322aa6SLaurent Pinchart struct drm_display_mode *mode) 2189960aa7cSTomi Valkeinen { 21996fc64c7SLaurent Pinchart struct sdi_device *sdi = dssdev_to_sdi(dssdev); 22096fc64c7SLaurent Pinchart struct dispc_clock_info dispc_cinfo; 22141322aa6SLaurent Pinchart unsigned long pixelclock = mode->clock * 1000; 22296fc64c7SLaurent Pinchart unsigned long fck; 22396fc64c7SLaurent Pinchart unsigned long pck; 22496fc64c7SLaurent Pinchart int r; 22596fc64c7SLaurent Pinchart 22641322aa6SLaurent Pinchart if (pixelclock == 0) 2279960aa7cSTomi Valkeinen return -EINVAL; 2289960aa7cSTomi Valkeinen 22941322aa6SLaurent Pinchart r = sdi_calc_clock_div(sdi, pixelclock, &fck, &dispc_cinfo); 23096fc64c7SLaurent Pinchart if (r) 23196fc64c7SLaurent Pinchart return r; 23296fc64c7SLaurent Pinchart 23396fc64c7SLaurent Pinchart pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div; 23496fc64c7SLaurent Pinchart 23541322aa6SLaurent Pinchart if (pck != pixelclock) { 23696fc64c7SLaurent Pinchart DSSWARN("Pixel clock adjusted from %lu Hz to %lu Hz\n", 23741322aa6SLaurent Pinchart pixelclock, pck); 23896fc64c7SLaurent Pinchart 23941322aa6SLaurent Pinchart mode->clock = pck / 1000; 24096fc64c7SLaurent Pinchart } 24196fc64c7SLaurent Pinchart 2429960aa7cSTomi Valkeinen return 0; 2439960aa7cSTomi Valkeinen } 2449960aa7cSTomi Valkeinen 245511afb44SLaurent Pinchart static int sdi_connect(struct omap_dss_device *src, 2469960aa7cSTomi Valkeinen struct omap_dss_device *dst) 2479960aa7cSTomi Valkeinen { 248f8a8eabbSLaurent Pinchart return omapdss_device_connect(dst->dss, dst, dst->next); 24971316556SLaurent Pinchart } 25071316556SLaurent Pinchart 251511afb44SLaurent Pinchart static void sdi_disconnect(struct omap_dss_device *src, 2529960aa7cSTomi Valkeinen struct omap_dss_device *dst) 2539960aa7cSTomi Valkeinen { 254511afb44SLaurent Pinchart omapdss_device_disconnect(dst, dst->next); 2559960aa7cSTomi Valkeinen } 2569960aa7cSTomi Valkeinen 257b93109d7SLaurent Pinchart static const struct omap_dss_device_ops sdi_ops = { 2589960aa7cSTomi Valkeinen .connect = sdi_connect, 2599960aa7cSTomi Valkeinen .disconnect = sdi_disconnect, 2609960aa7cSTomi Valkeinen 2619960aa7cSTomi Valkeinen .enable = sdi_display_enable, 2629960aa7cSTomi Valkeinen .disable = sdi_display_disable, 2639960aa7cSTomi Valkeinen 2649960aa7cSTomi Valkeinen .check_timings = sdi_check_timings, 2659960aa7cSTomi Valkeinen .set_timings = sdi_set_timings, 2669960aa7cSTomi Valkeinen }; 2679960aa7cSTomi Valkeinen 26827d62452SLaurent Pinchart static int sdi_init_output(struct sdi_device *sdi) 2699960aa7cSTomi Valkeinen { 27024aac601SLaurent Pinchart struct omap_dss_device *out = &sdi->output; 27171316556SLaurent Pinchart int r; 2729960aa7cSTomi Valkeinen 27324aac601SLaurent Pinchart out->dev = &sdi->pdev->dev; 2749960aa7cSTomi Valkeinen out->id = OMAP_DSS_OUTPUT_SDI; 2750dbfc396SLaurent Pinchart out->type = OMAP_DISPLAY_TYPE_SDI; 2769960aa7cSTomi Valkeinen out->name = "sdi.0"; 2779960aa7cSTomi Valkeinen out->dispc_channel = OMAP_DSS_CHANNEL_LCD; 2789960aa7cSTomi Valkeinen /* We have SDI only on OMAP3, where it's on port 1 */ 2794e20bda6SLaurent Pinchart out->of_ports = BIT(1); 280b93109d7SLaurent Pinchart out->ops = &sdi_ops; 2819960aa7cSTomi Valkeinen out->owner = THIS_MODULE; 282*88bc4178SLaurent Pinchart out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE /* 15.5.9.1.2 */ 283*88bc4178SLaurent Pinchart | DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE; 2849960aa7cSTomi Valkeinen 285d17eb453SLaurent Pinchart r = omapdss_device_init_output(out); 286d17eb453SLaurent Pinchart if (r < 0) 28771316556SLaurent Pinchart return r; 28871316556SLaurent Pinchart 289de57e9dbSLaurent Pinchart omapdss_device_register(out); 29027d62452SLaurent Pinchart 29127d62452SLaurent Pinchart return 0; 2929960aa7cSTomi Valkeinen } 2939960aa7cSTomi Valkeinen 29424aac601SLaurent Pinchart static void sdi_uninit_output(struct sdi_device *sdi) 2959960aa7cSTomi Valkeinen { 296de57e9dbSLaurent Pinchart omapdss_device_unregister(&sdi->output); 297d17eb453SLaurent Pinchart omapdss_device_cleanup_output(&sdi->output); 2989960aa7cSTomi Valkeinen } 2999960aa7cSTomi Valkeinen 300d7157dfeSLaurent Pinchart int sdi_init_port(struct dss_device *dss, struct platform_device *pdev, 301d7157dfeSLaurent Pinchart struct device_node *port) 3029960aa7cSTomi Valkeinen { 30324aac601SLaurent Pinchart struct sdi_device *sdi; 3049960aa7cSTomi Valkeinen struct device_node *ep; 3059960aa7cSTomi Valkeinen u32 datapairs; 3069960aa7cSTomi Valkeinen int r; 3079960aa7cSTomi Valkeinen 30824aac601SLaurent Pinchart sdi = kzalloc(sizeof(*sdi), GFP_KERNEL); 30924aac601SLaurent Pinchart if (!sdi) 31024aac601SLaurent Pinchart return -ENOMEM; 31124aac601SLaurent Pinchart 31209bffa6eSRob Herring ep = of_get_next_child(port, NULL); 31324aac601SLaurent Pinchart if (!ep) { 31424aac601SLaurent Pinchart r = 0; 31524aac601SLaurent Pinchart goto err_free; 31624aac601SLaurent Pinchart } 3179960aa7cSTomi Valkeinen 3189960aa7cSTomi Valkeinen r = of_property_read_u32(ep, "datapairs", &datapairs); 31966aacfe2SLaurent Pinchart of_node_put(ep); 3209960aa7cSTomi Valkeinen if (r) { 3219960aa7cSTomi Valkeinen DSSERR("failed to parse datapairs\n"); 32266aacfe2SLaurent Pinchart goto err_free; 3239960aa7cSTomi Valkeinen } 3249960aa7cSTomi Valkeinen 32524aac601SLaurent Pinchart sdi->datapairs = datapairs; 32624aac601SLaurent Pinchart sdi->dss = dss; 3279960aa7cSTomi Valkeinen 32824aac601SLaurent Pinchart sdi->pdev = pdev; 32924aac601SLaurent Pinchart port->data = sdi; 3309960aa7cSTomi Valkeinen 3318a36357aSLaurent Pinchart sdi->vdds_sdi_reg = devm_regulator_get(&pdev->dev, "vdds_sdi"); 3328a36357aSLaurent Pinchart if (IS_ERR(sdi->vdds_sdi_reg)) { 3338a36357aSLaurent Pinchart r = PTR_ERR(sdi->vdds_sdi_reg); 3348a36357aSLaurent Pinchart if (r != -EPROBE_DEFER) 3358a36357aSLaurent Pinchart DSSERR("can't get VDDS_SDI regulator\n"); 3368a36357aSLaurent Pinchart goto err_free; 3378a36357aSLaurent Pinchart } 3388a36357aSLaurent Pinchart 33927d62452SLaurent Pinchart r = sdi_init_output(sdi); 34027d62452SLaurent Pinchart if (r) 34127d62452SLaurent Pinchart goto err_free; 3429960aa7cSTomi Valkeinen 3439960aa7cSTomi Valkeinen return 0; 3449960aa7cSTomi Valkeinen 34524aac601SLaurent Pinchart err_free: 34624aac601SLaurent Pinchart kfree(sdi); 3479960aa7cSTomi Valkeinen 3489960aa7cSTomi Valkeinen return r; 3499960aa7cSTomi Valkeinen } 3509960aa7cSTomi Valkeinen 3519960aa7cSTomi Valkeinen void sdi_uninit_port(struct device_node *port) 3529960aa7cSTomi Valkeinen { 35324aac601SLaurent Pinchart struct sdi_device *sdi = port->data; 35424aac601SLaurent Pinchart 35524aac601SLaurent Pinchart if (!sdi) 3569960aa7cSTomi Valkeinen return; 3579960aa7cSTomi Valkeinen 35824aac601SLaurent Pinchart sdi_uninit_output(sdi); 35924aac601SLaurent Pinchart kfree(sdi); 3609960aa7cSTomi Valkeinen } 361