diff options
Diffstat (limited to 'drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c')
-rw-r--r-- | drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c | 291 |
1 files changed, 207 insertions, 84 deletions
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c b/drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c index 92c556ac22c7..15399a1a666b 100644 --- a/drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c +++ b/drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c @@ -1,7 +1,7 @@ /* * Generic DSI Command Mode panel driver * - * Copyright (C) 2013 Texas Instruments + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> * * This program is free software; you can redistribute it and/or modify it @@ -22,9 +22,10 @@ #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/of_device.h> -#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> #include <video/mipi_display.h> +#include <video/of_display_timing.h> #include "../dss/omapdss.h" @@ -49,6 +50,7 @@ struct panel_drv_data { struct mutex lock; struct backlight_device *bldev; + struct backlight_device *extbldev; unsigned long hw_guard_end; /* next value of jiffies when we can * issue the next sleep in/out command @@ -56,11 +58,17 @@ struct panel_drv_data { unsigned long hw_guard_wait; /* max guard time in jiffies */ /* panel HW configuration from DT or platform data */ - int reset_gpio; - int ext_te_gpio; + struct gpio_desc *reset_gpio; + struct gpio_desc *ext_te_gpio; + + struct regulator *vpnl; + struct regulator *vddi; bool use_dsi_backlight; + int width_mm; + int height_mm; + struct omap_dsi_pin_config pin_config; /* runtime variables */ @@ -92,6 +100,30 @@ static int dsicm_panel_reset(struct panel_drv_data *ddata); static void dsicm_ulps_work(struct work_struct *work); +static void dsicm_bl_power(struct panel_drv_data *ddata, bool enable) +{ + struct backlight_device *backlight; + + if (ddata->bldev) + backlight = ddata->bldev; + else if (ddata->extbldev) + backlight = ddata->extbldev; + else + return; + + if (enable) { + backlight->props.fb_blank = FB_BLANK_UNBLANK; + backlight->props.state = ~(BL_CORE_FBBLANK | BL_CORE_SUSPENDED); + backlight->props.power = FB_BLANK_UNBLANK; + } else { + backlight->props.fb_blank = FB_BLANK_NORMAL; + backlight->props.power = FB_BLANK_POWERDOWN; + backlight->props.state |= BL_CORE_FBBLANK | BL_CORE_SUSPENDED; + } + + backlight_update_status(backlight); +} + static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec) { ddata->hw_guard_wait = msecs_to_jiffies(guard_msec); @@ -255,8 +287,8 @@ static int dsicm_enter_ulps(struct panel_drv_data *ddata) if (r) goto err; - if (gpio_is_valid(ddata->ext_te_gpio)) - disable_irq(gpio_to_irq(ddata->ext_te_gpio)); + if (ddata->ext_te_gpio) + disable_irq(gpiod_to_irq(ddata->ext_te_gpio)); in->ops.dsi->disable(in, false, true); @@ -297,8 +329,8 @@ static int dsicm_exit_ulps(struct panel_drv_data *ddata) goto err2; } - if (gpio_is_valid(ddata->ext_te_gpio)) - enable_irq(gpio_to_irq(ddata->ext_te_gpio)); + if (ddata->ext_te_gpio) + enable_irq(gpiod_to_irq(ddata->ext_te_gpio)); dsicm_queue_ulps_work(ddata); @@ -311,8 +343,8 @@ err2: r = dsicm_panel_reset(ddata); if (!r) { - if (gpio_is_valid(ddata->ext_te_gpio)) - enable_irq(gpio_to_irq(ddata->ext_te_gpio)); + if (ddata->ext_te_gpio) + enable_irq(gpiod_to_irq(ddata->ext_te_gpio)); ddata->ulps_enabled = false; } err1: @@ -335,7 +367,7 @@ static int dsicm_bl_update_status(struct backlight_device *dev) { struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev); struct omap_dss_device *in = ddata->in; - int r; + int r = 0; int level; if (dev->props.fb_blank == FB_BLANK_UNBLANK && @@ -356,8 +388,6 @@ static int dsicm_bl_update_status(struct backlight_device *dev) r = dsicm_dcs_write_1(ddata, DCS_BRIGHTNESS, level); in->ops.dsi->bus_unlock(in); - } else { - r = 0; } mutex_unlock(&ddata->lock); @@ -560,16 +590,13 @@ static const struct attribute_group dsicm_attr_group = { static void dsicm_hw_reset(struct panel_drv_data *ddata) { - if (!gpio_is_valid(ddata->reset_gpio)) - return; - - gpio_set_value(ddata->reset_gpio, 1); + gpiod_set_value(ddata->reset_gpio, 1); udelay(10); /* reset the panel */ - gpio_set_value(ddata->reset_gpio, 0); + gpiod_set_value(ddata->reset_gpio, 0); /* assert reset */ udelay(10); - gpio_set_value(ddata->reset_gpio, 1); + gpiod_set_value(ddata->reset_gpio, 1); /* wait after releasing reset */ usleep_range(5000, 10000); } @@ -589,25 +616,43 @@ static int dsicm_power_on(struct panel_drv_data *ddata) .lp_clk_max = 10000000, }; + if (ddata->vpnl) { + r = regulator_enable(ddata->vpnl); + if (r) { + dev_err(&ddata->pdev->dev, + "failed to enable VPNL: %d\n", r); + return r; + } + } + + if (ddata->vddi) { + r = regulator_enable(ddata->vddi); + if (r) { + dev_err(&ddata->pdev->dev, + "failed to enable VDDI: %d\n", r); + goto err_vpnl; + } + } + if (ddata->pin_config.num_pins > 0) { r = in->ops.dsi->configure_pins(in, &ddata->pin_config); if (r) { dev_err(&ddata->pdev->dev, "failed to configure DSI pins\n"); - goto err0; + goto err_vddi; } } r = in->ops.dsi->set_config(in, &dsi_config); if (r) { dev_err(&ddata->pdev->dev, "failed to configure DSI\n"); - goto err0; + goto err_vddi; } r = in->ops.dsi->enable(in); if (r) { dev_err(&ddata->pdev->dev, "failed to enable DSI\n"); - goto err0; + goto err_vddi; } dsicm_hw_reset(ddata); @@ -665,7 +710,13 @@ err: dsicm_hw_reset(ddata); in->ops.dsi->disable(in, true, false); -err0: +err_vddi: + if (ddata->vddi) + regulator_disable(ddata->vddi); +err_vpnl: + if (ddata->vpnl) + regulator_disable(ddata->vpnl); + return r; } @@ -688,6 +739,11 @@ static void dsicm_power_off(struct panel_drv_data *ddata) in->ops.dsi->disable(in, true, false); + if (ddata->vddi) + regulator_disable(ddata->vddi); + if (ddata->vpnl) + regulator_disable(ddata->vpnl); + ddata->enabled = 0; } @@ -782,6 +838,8 @@ static int dsicm_enable(struct omap_dss_device *dssdev) mutex_unlock(&ddata->lock); + dsicm_bl_power(ddata, true); + return 0; err: dev_dbg(&ddata->pdev->dev, "enable failed\n"); @@ -797,6 +855,8 @@ static void dsicm_disable(struct omap_dss_device *dssdev) dev_dbg(&ddata->pdev->dev, "disable\n"); + dsicm_bl_power(ddata, false); + mutex_lock(&ddata->lock); dsicm_cancel_ulps_work(ddata); @@ -890,7 +950,7 @@ static int dsicm_update(struct omap_dss_device *dssdev, if (r) goto err; - if (ddata->te_enabled && gpio_is_valid(ddata->ext_te_gpio)) { + if (ddata->te_enabled && ddata->ext_te_gpio) { schedule_delayed_work(&ddata->te_timeout_work, msecs_to_jiffies(250)); atomic_set(&ddata->do_update, 1); @@ -937,7 +997,7 @@ static int _dsicm_enable_te(struct panel_drv_data *ddata, bool enable) else r = dsicm_dcs_write_0(ddata, MIPI_DCS_SET_TEAR_OFF); - if (!gpio_is_valid(ddata->ext_te_gpio)) + if (!ddata->ext_te_gpio) in->ops.dsi->enable_te(in, enable); /* possible panel bug */ @@ -1099,6 +1159,45 @@ static void dsicm_ulps_work(struct work_struct *work) mutex_unlock(&ddata->lock); } +static void dsicm_get_timings(struct omap_dss_device *dssdev, + struct videomode *vm) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *vm = ddata->vm; +} + +static int dsicm_check_timings(struct omap_dss_device *dssdev, + struct videomode *vm) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + int ret = 0; + + if (vm->hactive != ddata->vm.hactive) + ret = -EINVAL; + + if (vm->vactive != ddata->vm.vactive) + ret = -EINVAL; + + if (ret) { + dev_warn(dssdev->dev, "wrong resolution: %d x %d", + vm->hactive, vm->vactive); + dev_warn(dssdev->dev, "panel resolution: %d x %d", + ddata->vm.hactive, ddata->vm.vactive); + } + + return ret; +} + +static void dsicm_get_size(struct omap_dss_device *dssdev, + unsigned int *width, unsigned int *height) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + *width = ddata->width_mm; + *height = ddata->height_mm; +} + static struct omap_dss_driver dsicm_ops = { .connect = dsicm_connect, .disconnect = dsicm_disconnect, @@ -1109,6 +1208,10 @@ static struct omap_dss_driver dsicm_ops = { .update = dsicm_update, .sync = dsicm_sync, + .get_timings = dsicm_get_timings, + .check_timings = dsicm_check_timings, + .get_size = dsicm_get_size, + .enable_te = dsicm_enable_te, .get_te = dsicm_get_te, @@ -1118,41 +1221,87 @@ static struct omap_dss_driver dsicm_ops = { static int dsicm_probe_of(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; + struct device_node *backlight; struct panel_drv_data *ddata = platform_get_drvdata(pdev); struct omap_dss_device *in; - int gpio; + struct display_timing timing; + int err; + + ddata->reset_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ddata->reset_gpio)) { + err = PTR_ERR(ddata->reset_gpio); + dev_err(&pdev->dev, "reset gpio request failed: %d", err); + return err; + } - gpio = of_get_named_gpio(node, "reset-gpios", 0); - if (!gpio_is_valid(gpio)) { - dev_err(&pdev->dev, "failed to parse reset gpio\n"); - return gpio; + ddata->ext_te_gpio = devm_gpiod_get_optional(&pdev->dev, "te", + GPIOD_IN); + if (IS_ERR(ddata->ext_te_gpio)) { + err = PTR_ERR(ddata->ext_te_gpio); + dev_err(&pdev->dev, "TE gpio request failed: %d", err); + return err; } - ddata->reset_gpio = gpio; - gpio = of_get_named_gpio(node, "te-gpios", 0); - if (gpio_is_valid(gpio) || gpio == -ENOENT) { - ddata->ext_te_gpio = gpio; + err = of_get_display_timing(node, "panel-timing", &timing); + if (!err) { + videomode_from_timing(&timing, &ddata->vm); + if (!ddata->vm.pixelclock) + ddata->vm.pixelclock = + ddata->vm.hactive * ddata->vm.vactive * 60; } else { - dev_err(&pdev->dev, "failed to parse TE gpio\n"); - return gpio; + dev_warn(&pdev->dev, + "failed to get video timing, using defaults\n"); } + ddata->width_mm = 0; + of_property_read_u32(node, "width-mm", &ddata->width_mm); + + ddata->height_mm = 0; + of_property_read_u32(node, "height-mm", &ddata->height_mm); + in = omapdss_of_find_source_for_first_ep(node); if (IS_ERR(in)) { dev_err(&pdev->dev, "failed to find video source\n"); return PTR_ERR(in); } + ddata->vpnl = devm_regulator_get_optional(&pdev->dev, "vpnl"); + if (IS_ERR(ddata->vpnl)) { + err = PTR_ERR(ddata->vpnl); + if (err == -EPROBE_DEFER) + return err; + ddata->vpnl = NULL; + } + + ddata->vddi = devm_regulator_get_optional(&pdev->dev, "vddi"); + if (IS_ERR(ddata->vddi)) { + err = PTR_ERR(ddata->vddi); + if (err == -EPROBE_DEFER) + return err; + ddata->vddi = NULL; + } + ddata->in = in; - /* TODO: ulps, backlight */ + backlight = of_parse_phandle(node, "backlight", 0); + if (backlight) { + ddata->extbldev = of_find_backlight_by_node(backlight); + of_node_put(backlight); + + if (!ddata->extbldev) + return -EPROBE_DEFER; + } else { + /* assume native backlight support */ + ddata->use_dsi_backlight = true; + } + + /* TODO: ulps */ return 0; } static int dsicm_probe(struct platform_device *pdev) { - struct backlight_properties props; struct panel_drv_data *ddata; struct backlight_device *bldev = NULL; struct device *dev = &pdev->dev; @@ -1171,14 +1320,14 @@ static int dsicm_probe(struct platform_device *pdev) if (!pdev->dev.of_node) return -ENODEV; - r = dsicm_probe_of(pdev); - if (r) - return r; - ddata->vm.hactive = 864; ddata->vm.vactive = 480; ddata->vm.pixelclock = 864 * 480 * 60; + r = dsicm_probe_of(pdev); + if (r) + return r; + dssdev = &ddata->dssdev; dssdev->dev = dev; dssdev->driver = &dsicm_ops; @@ -1200,31 +1349,15 @@ static int dsicm_probe(struct platform_device *pdev) atomic_set(&ddata->do_update, 0); - if (gpio_is_valid(ddata->reset_gpio)) { - r = devm_gpio_request_one(dev, ddata->reset_gpio, - GPIOF_OUT_INIT_LOW, "taal rst"); - if (r) { - dev_err(dev, "failed to request reset gpio\n"); - return r; - } - } - - if (gpio_is_valid(ddata->ext_te_gpio)) { - r = devm_gpio_request_one(dev, ddata->ext_te_gpio, - GPIOF_IN, "taal irq"); - if (r) { - dev_err(dev, "GPIO request failed\n"); - return r; - } - - r = devm_request_irq(dev, gpio_to_irq(ddata->ext_te_gpio), + if (ddata->ext_te_gpio) { + r = devm_request_irq(dev, gpiod_to_irq(ddata->ext_te_gpio), dsicm_te_isr, IRQF_TRIGGER_RISING, "taal vsync", ddata); if (r) { dev_err(dev, "IRQ request failed\n"); - return r; + goto err_reg; } INIT_DEFERRABLE_WORK(&ddata->te_timeout_work, @@ -1234,48 +1367,43 @@ static int dsicm_probe(struct platform_device *pdev) } ddata->workqueue = create_singlethread_workqueue("dsicm_wq"); - if (ddata->workqueue == NULL) { - dev_err(dev, "can't create workqueue\n"); - return -ENOMEM; + if (!ddata->workqueue) { + r = -ENOMEM; + goto err_reg; } INIT_DELAYED_WORK(&ddata->ulps_work, dsicm_ulps_work); dsicm_hw_reset(ddata); if (ddata->use_dsi_backlight) { - memset(&props, 0, sizeof(props)); + struct backlight_properties props = { 0 }; props.max_brightness = 255; - props.type = BACKLIGHT_RAW; - bldev = backlight_device_register(dev_name(dev), - dev, ddata, &dsicm_bl_ops, &props); + + bldev = devm_backlight_device_register(dev, dev_name(dev), + dev, ddata, &dsicm_bl_ops, &props); if (IS_ERR(bldev)) { r = PTR_ERR(bldev); goto err_bl; } ddata->bldev = bldev; - - bldev->props.fb_blank = FB_BLANK_UNBLANK; - bldev->props.power = FB_BLANK_UNBLANK; - bldev->props.brightness = 255; - - dsicm_bl_update_status(bldev); } r = sysfs_create_group(&dev->kobj, &dsicm_attr_group); if (r) { dev_err(dev, "failed to create sysfs files\n"); - goto err_sysfs_create; + goto err_bl; } return 0; -err_sysfs_create: - backlight_device_unregister(bldev); err_bl: destroy_workqueue(ddata->workqueue); err_reg: + if (ddata->extbldev) + put_device(&ddata->extbldev->dev); + return r; } @@ -1283,7 +1411,6 @@ static int __exit dsicm_remove(struct platform_device *pdev) { struct panel_drv_data *ddata = platform_get_drvdata(pdev); struct omap_dss_device *dssdev = &ddata->dssdev; - struct backlight_device *bldev; dev_dbg(&pdev->dev, "remove\n"); @@ -1294,12 +1421,8 @@ static int __exit dsicm_remove(struct platform_device *pdev) sysfs_remove_group(&pdev->dev.kobj, &dsicm_attr_group); - bldev = ddata->bldev; - if (bldev != NULL) { - bldev->props.power = FB_BLANK_POWERDOWN; - dsicm_bl_update_status(bldev); - backlight_device_unregister(bldev); - } + if (ddata->extbldev) + put_device(&ddata->extbldev->dev); omap_dss_put_device(ddata->in); |