123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright 2021 Google Inc.
- *
- * Panel driver for the Samsung ATNA33XC20 panel. This panel can't be handled
- * by the DRM_PANEL_SIMPLE driver because its power sequencing is non-standard.
- */
- #include <linux/backlight.h>
- #include <linux/delay.h>
- #include <linux/gpio/consumer.h>
- #include <linux/iopoll.h>
- #include <linux/module.h>
- #include <linux/pm_runtime.h>
- #include <linux/regulator/consumer.h>
- #include <drm/display/drm_dp_aux_bus.h>
- #include <drm/display/drm_dp_helper.h>
- #include <drm/drm_edid.h>
- #include <drm/drm_panel.h>
- /* T3 VCC to HPD high is max 200 ms */
- #define HPD_MAX_MS 200
- #define HPD_MAX_US (HPD_MAX_MS * 1000)
- struct atana33xc20_panel {
- struct drm_panel base;
- bool prepared;
- bool enabled;
- bool el3_was_on;
- bool no_hpd;
- struct gpio_desc *hpd_gpio;
- struct regulator *supply;
- struct gpio_desc *el_on3_gpio;
- struct drm_dp_aux *aux;
- struct edid *edid;
- ktime_t powered_off_time;
- ktime_t powered_on_time;
- ktime_t el_on3_off_time;
- };
- static inline struct atana33xc20_panel *to_atana33xc20(struct drm_panel *panel)
- {
- return container_of(panel, struct atana33xc20_panel, base);
- }
- static void atana33xc20_wait(ktime_t start_ktime, unsigned int min_ms)
- {
- ktime_t now_ktime, min_ktime;
- min_ktime = ktime_add(start_ktime, ms_to_ktime(min_ms));
- now_ktime = ktime_get();
- if (ktime_before(now_ktime, min_ktime))
- msleep(ktime_to_ms(ktime_sub(min_ktime, now_ktime)) + 1);
- }
- static int atana33xc20_suspend(struct device *dev)
- {
- struct atana33xc20_panel *p = dev_get_drvdata(dev);
- int ret;
- /*
- * Note 3 (Example of power off sequence in detail) in spec
- * specifies to wait 150 ms after deasserting EL3_ON before
- * powering off.
- */
- if (p->el3_was_on)
- atana33xc20_wait(p->el_on3_off_time, 150);
- ret = regulator_disable(p->supply);
- if (ret)
- return ret;
- p->powered_off_time = ktime_get();
- p->el3_was_on = false;
- return 0;
- }
- static int atana33xc20_resume(struct device *dev)
- {
- struct atana33xc20_panel *p = dev_get_drvdata(dev);
- int hpd_asserted;
- int ret;
- /* T12 (Power off time) is min 500 ms */
- atana33xc20_wait(p->powered_off_time, 500);
- ret = regulator_enable(p->supply);
- if (ret)
- return ret;
- p->powered_on_time = ktime_get();
- if (p->no_hpd) {
- msleep(HPD_MAX_MS);
- return 0;
- }
- if (p->hpd_gpio) {
- ret = readx_poll_timeout(gpiod_get_value_cansleep, p->hpd_gpio,
- hpd_asserted, hpd_asserted,
- 1000, HPD_MAX_US);
- if (hpd_asserted < 0)
- ret = hpd_asserted;
- if (ret)
- dev_warn(dev, "Error waiting for HPD GPIO: %d\n", ret);
- return ret;
- }
- if (p->aux->wait_hpd_asserted) {
- ret = p->aux->wait_hpd_asserted(p->aux, HPD_MAX_US);
- if (ret)
- dev_warn(dev, "Controller error waiting for HPD: %d\n", ret);
- return ret;
- }
- /*
- * Note that it's possible that no_hpd is false, hpd_gpio is
- * NULL, and wait_hpd_asserted is NULL. This is because
- * wait_hpd_asserted() is optional even if HPD is hooked up to
- * a dedicated pin on the eDP controller. In this case we just
- * assume that the controller driver will wait for HPD at the
- * right times.
- */
- return 0;
- }
- static int atana33xc20_disable(struct drm_panel *panel)
- {
- struct atana33xc20_panel *p = to_atana33xc20(panel);
- /* Disabling when already disabled is a no-op */
- if (!p->enabled)
- return 0;
- gpiod_set_value_cansleep(p->el_on3_gpio, 0);
- p->el_on3_off_time = ktime_get();
- p->enabled = false;
- /*
- * Keep track of the fact that EL_ON3 was on but we haven't power
- * cycled yet. This lets us know that "el_on3_off_time" is recent (we
- * don't need to worry about ktime wraparounds) and also makes it
- * obvious if we try to enable again without a power cycle (see the
- * warning in atana33xc20_enable()).
- */
- p->el3_was_on = true;
- /*
- * Sleeping 20 ms here (after setting the GPIO) avoids a glitch when
- * powering off.
- */
- msleep(20);
- return 0;
- }
- static int atana33xc20_enable(struct drm_panel *panel)
- {
- struct atana33xc20_panel *p = to_atana33xc20(panel);
- /* Enabling when already enabled is a no-op */
- if (p->enabled)
- return 0;
- /*
- * Once EL_ON3 drops we absolutely need a power cycle before the next
- * enable or the backlight will never come on again. The code ensures
- * this because disable() is _always_ followed by unprepare() and
- * unprepare() forces a suspend with pm_runtime_put_sync_suspend(),
- * but let's track just to make sure since the requirement is so
- * non-obvious.
- */
- if (WARN_ON(p->el3_was_on))
- return -EIO;
- /*
- * Note 2 (Example of power on sequence in detail) in spec specifies
- * to wait 400 ms after powering on before asserting EL3_on.
- */
- atana33xc20_wait(p->powered_on_time, 400);
- gpiod_set_value_cansleep(p->el_on3_gpio, 1);
- p->enabled = true;
- return 0;
- }
- static int atana33xc20_unprepare(struct drm_panel *panel)
- {
- struct atana33xc20_panel *p = to_atana33xc20(panel);
- int ret;
- /* Unpreparing when already unprepared is a no-op */
- if (!p->prepared)
- return 0;
- /*
- * Purposely do a put_sync, don't use autosuspend. The panel's tcon
- * seems to sometimes crash when you stop giving it data and this is
- * the best way to ensure it will come back.
- *
- * NOTE: we still want autosuspend for cases where we only turn on
- * to get the EDID or otherwise send DP AUX commands to the panel.
- */
- ret = pm_runtime_put_sync_suspend(panel->dev);
- if (ret < 0)
- return ret;
- p->prepared = false;
- return 0;
- }
- static int atana33xc20_prepare(struct drm_panel *panel)
- {
- struct atana33xc20_panel *p = to_atana33xc20(panel);
- int ret;
- /* Preparing when already prepared is a no-op */
- if (p->prepared)
- return 0;
- ret = pm_runtime_get_sync(panel->dev);
- if (ret < 0) {
- pm_runtime_put_autosuspend(panel->dev);
- return ret;
- }
- p->prepared = true;
- return 0;
- }
- static int atana33xc20_get_modes(struct drm_panel *panel,
- struct drm_connector *connector)
- {
- struct atana33xc20_panel *p = to_atana33xc20(panel);
- struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(panel->dev);
- int num = 0;
- pm_runtime_get_sync(panel->dev);
- if (!p->edid)
- p->edid = drm_get_edid(connector, &aux_ep->aux->ddc);
- num = drm_add_edid_modes(connector, p->edid);
- pm_runtime_mark_last_busy(panel->dev);
- pm_runtime_put_autosuspend(panel->dev);
- return num;
- }
- static const struct drm_panel_funcs atana33xc20_funcs = {
- .disable = atana33xc20_disable,
- .enable = atana33xc20_enable,
- .unprepare = atana33xc20_unprepare,
- .prepare = atana33xc20_prepare,
- .get_modes = atana33xc20_get_modes,
- };
- static void atana33xc20_runtime_disable(void *data)
- {
- pm_runtime_disable(data);
- }
- static void atana33xc20_dont_use_autosuspend(void *data)
- {
- pm_runtime_dont_use_autosuspend(data);
- }
- static int atana33xc20_probe(struct dp_aux_ep_device *aux_ep)
- {
- struct atana33xc20_panel *panel;
- struct device *dev = &aux_ep->dev;
- int ret;
- panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
- if (!panel)
- return -ENOMEM;
- dev_set_drvdata(dev, panel);
- panel->aux = aux_ep->aux;
- panel->supply = devm_regulator_get(dev, "power");
- if (IS_ERR(panel->supply))
- return dev_err_probe(dev, PTR_ERR(panel->supply),
- "Failed to get power supply\n");
- panel->el_on3_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
- if (IS_ERR(panel->el_on3_gpio))
- return dev_err_probe(dev, PTR_ERR(panel->el_on3_gpio),
- "Failed to get enable GPIO\n");
- panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd");
- if (!panel->no_hpd) {
- panel->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
- if (IS_ERR(panel->hpd_gpio))
- return dev_err_probe(dev, PTR_ERR(panel->hpd_gpio),
- "Failed to get HPD GPIO\n");
- }
- pm_runtime_enable(dev);
- ret = devm_add_action_or_reset(dev, atana33xc20_runtime_disable, dev);
- if (ret)
- return ret;
- pm_runtime_set_autosuspend_delay(dev, 1000);
- pm_runtime_use_autosuspend(dev);
- ret = devm_add_action_or_reset(dev, atana33xc20_dont_use_autosuspend, dev);
- if (ret)
- return ret;
- drm_panel_init(&panel->base, dev, &atana33xc20_funcs, DRM_MODE_CONNECTOR_eDP);
- pm_runtime_get_sync(dev);
- ret = drm_panel_dp_aux_backlight(&panel->base, aux_ep->aux);
- pm_runtime_mark_last_busy(dev);
- pm_runtime_put_autosuspend(dev);
- if (ret)
- return dev_err_probe(dev, ret,
- "failed to register dp aux backlight\n");
- drm_panel_add(&panel->base);
- return 0;
- }
- static void atana33xc20_remove(struct dp_aux_ep_device *aux_ep)
- {
- struct device *dev = &aux_ep->dev;
- struct atana33xc20_panel *panel = dev_get_drvdata(dev);
- drm_panel_remove(&panel->base);
- drm_panel_disable(&panel->base);
- drm_panel_unprepare(&panel->base);
- kfree(panel->edid);
- }
- static void atana33xc20_shutdown(struct dp_aux_ep_device *aux_ep)
- {
- struct device *dev = &aux_ep->dev;
- struct atana33xc20_panel *panel = dev_get_drvdata(dev);
- drm_panel_disable(&panel->base);
- drm_panel_unprepare(&panel->base);
- }
- static const struct of_device_id atana33xc20_dt_match[] = {
- { .compatible = "samsung,atna33xc20", },
- { /* sentinal */ }
- };
- MODULE_DEVICE_TABLE(of, atana33xc20_dt_match);
- static const struct dev_pm_ops atana33xc20_pm_ops = {
- SET_RUNTIME_PM_OPS(atana33xc20_suspend, atana33xc20_resume, NULL)
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
- pm_runtime_force_resume)
- };
- static struct dp_aux_ep_driver atana33xc20_driver = {
- .driver = {
- .name = "samsung_atana33xc20",
- .of_match_table = atana33xc20_dt_match,
- .pm = &atana33xc20_pm_ops,
- },
- .probe = atana33xc20_probe,
- .remove = atana33xc20_remove,
- .shutdown = atana33xc20_shutdown,
- };
- static int __init atana33xc20_init(void)
- {
- return dp_aux_dp_driver_register(&atana33xc20_driver);
- }
- module_init(atana33xc20_init);
- static void __exit atana33xc20_exit(void)
- {
- dp_aux_dp_driver_unregister(&atana33xc20_driver);
- }
- module_exit(atana33xc20_exit);
- MODULE_DESCRIPTION("Samsung ATANA33XC20 Panel Driver");
- MODULE_LICENSE("GPL v2");
|