root/drivers/gpu/drm/panel/panel-dsi-cm.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Generic DSI Command Mode panel driver
 *
 * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com/
 * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
 */

#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>

#include <drm/drm_connector.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>

#include <video/mipi_display.h>

#define DCS_GET_ID1             0xda
#define DCS_GET_ID2             0xdb
#define DCS_GET_ID3             0xdc

#define DCS_REGULATOR_SUPPLY_NUM 2

static const struct of_device_id dsicm_of_match[];

struct dsic_panel_data {
        u32 xres;
        u32 yres;
        u32 refresh;
        u32 width_mm;
        u32 height_mm;
        u32 max_hs_rate;
        u32 max_lp_rate;
        bool te_support;
};

struct panel_drv_data {
        struct mipi_dsi_device *dsi;
        struct drm_panel panel;
        struct drm_display_mode mode;

        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
                                         */
        unsigned long   hw_guard_wait;  /* max guard time in jiffies */

        const struct dsic_panel_data *panel_data;

        struct gpio_desc *reset_gpio;

        struct regulator_bulk_data supplies[DCS_REGULATOR_SUPPLY_NUM];

        bool use_dsi_backlight;

        /* runtime variables */
        bool enabled;

        bool intro_printed;
};

static inline struct panel_drv_data *panel_to_ddata(struct drm_panel *panel)
{
        return container_of(panel, struct panel_drv_data, panel);
}

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_enable(backlight);
        else
                backlight_disable(backlight);
}

static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec)
{
        ddata->hw_guard_wait = msecs_to_jiffies(guard_msec);
        ddata->hw_guard_end = jiffies + ddata->hw_guard_wait;
}

static void hw_guard_wait(struct panel_drv_data *ddata)
{
        unsigned long wait = ddata->hw_guard_end - jiffies;

        if ((long)wait > 0 && wait <= ddata->hw_guard_wait) {
                set_current_state(TASK_UNINTERRUPTIBLE);
                schedule_timeout(wait);
        }
}

static int dsicm_dcs_read_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 *data)
{
        return mipi_dsi_dcs_read(ddata->dsi, dcs_cmd, data, 1);
}

static int dsicm_dcs_write_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 param)
{
        return mipi_dsi_dcs_write(ddata->dsi, dcs_cmd, &param, 1);
}

static int dsicm_sleep_in(struct panel_drv_data *ddata)

{
        int r;

        hw_guard_wait(ddata);

        r = mipi_dsi_dcs_enter_sleep_mode(ddata->dsi);
        if (r)
                return r;

        hw_guard_start(ddata, 120);

        usleep_range(5000, 10000);

        return 0;
}

static int dsicm_sleep_out(struct panel_drv_data *ddata)
{
        int r;

        hw_guard_wait(ddata);

        r = mipi_dsi_dcs_exit_sleep_mode(ddata->dsi);
        if (r)
                return r;

        hw_guard_start(ddata, 120);

        usleep_range(5000, 10000);

        return 0;
}

static int dsicm_get_id(struct panel_drv_data *ddata, u8 *id1, u8 *id2, u8 *id3)
{
        int r;

        r = dsicm_dcs_read_1(ddata, DCS_GET_ID1, id1);
        if (r)
                return r;
        r = dsicm_dcs_read_1(ddata, DCS_GET_ID2, id2);
        if (r)
                return r;
        r = dsicm_dcs_read_1(ddata, DCS_GET_ID3, id3);
        if (r)
                return r;

        return 0;
}

static int dsicm_set_update_window(struct panel_drv_data *ddata)
{
        struct mipi_dsi_device *dsi = ddata->dsi;
        int r;

        r = mipi_dsi_dcs_set_column_address(dsi, 0, ddata->mode.hdisplay - 1);
        if (r < 0)
                return r;

        r = mipi_dsi_dcs_set_page_address(dsi, 0, ddata->mode.vdisplay - 1);
        if (r < 0)
                return r;

        return 0;
}

static int dsicm_bl_update_status(struct backlight_device *dev)
{
        struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
        int r = 0;
        int level = backlight_get_brightness(dev);

        dev_dbg(&ddata->dsi->dev, "update brightness to %d\n", level);

        mutex_lock(&ddata->lock);

        if (ddata->enabled)
                r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
                                      level);

        mutex_unlock(&ddata->lock);

        return r;
}

static int dsicm_bl_get_intensity(struct backlight_device *dev)
{
        return backlight_get_brightness(dev);
}

static const struct backlight_ops dsicm_bl_ops = {
        .get_brightness = dsicm_bl_get_intensity,
        .update_status  = dsicm_bl_update_status,
};

static ssize_t num_dsi_errors_show(struct device *dev,
                struct device_attribute *attr, char *buf)
{
        struct panel_drv_data *ddata = dev_get_drvdata(dev);
        u8 errors = 0;
        int r = -ENODEV;

        mutex_lock(&ddata->lock);

        if (ddata->enabled)
                r = dsicm_dcs_read_1(ddata, MIPI_DCS_GET_ERROR_COUNT_ON_DSI, &errors);

        mutex_unlock(&ddata->lock);

        if (r)
                return r;

        return sysfs_emit(buf, "%d\n", errors);
}

static ssize_t hw_revision_show(struct device *dev,
                struct device_attribute *attr, char *buf)
{
        struct panel_drv_data *ddata = dev_get_drvdata(dev);
        u8 id1, id2, id3;
        int r = -ENODEV;

        mutex_lock(&ddata->lock);

        if (ddata->enabled)
                r = dsicm_get_id(ddata, &id1, &id2, &id3);

        mutex_unlock(&ddata->lock);

        if (r)
                return r;

        return sysfs_emit(buf, "%02x.%02x.%02x\n", id1, id2, id3);
}

static DEVICE_ATTR_RO(num_dsi_errors);
static DEVICE_ATTR_RO(hw_revision);

static struct attribute *dsicm_attrs[] = {
        &dev_attr_num_dsi_errors.attr,
        &dev_attr_hw_revision.attr,
        NULL,
};

static const struct attribute_group dsicm_attr_group = {
        .attrs = dsicm_attrs,
};

static void dsicm_hw_reset(struct panel_drv_data *ddata)
{
        gpiod_set_value(ddata->reset_gpio, 1);
        udelay(10);
        /* reset the panel */
        gpiod_set_value(ddata->reset_gpio, 0);
        /* assert reset */
        udelay(10);
        gpiod_set_value(ddata->reset_gpio, 1);
        /* wait after releasing reset */
        usleep_range(5000, 10000);
}

static int dsicm_power_on(struct panel_drv_data *ddata)
{
        u8 id1, id2, id3;
        int r;

        dsicm_hw_reset(ddata);

        ddata->dsi->mode_flags |= MIPI_DSI_MODE_LPM;

        r = dsicm_sleep_out(ddata);
        if (r)
                goto err;

        r = dsicm_get_id(ddata, &id1, &id2, &id3);
        if (r)
                goto err;

        r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0xff);
        if (r)
                goto err;

        r = dsicm_dcs_write_1(ddata, MIPI_DCS_WRITE_CONTROL_DISPLAY,
                        (1<<2) | (1<<5));       /* BL | BCTRL */
        if (r)
                goto err;

        r = mipi_dsi_dcs_set_pixel_format(ddata->dsi, MIPI_DCS_PIXEL_FMT_24BIT);
        if (r)
                goto err;

        r = dsicm_set_update_window(ddata);
        if (r)
                goto err;

        r = mipi_dsi_dcs_set_display_on(ddata->dsi);
        if (r)
                goto err;

        if (ddata->panel_data->te_support) {
                r = mipi_dsi_dcs_set_tear_on(ddata->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
                if (r)
                        goto err;
        }

        /* possible panel bug */
        msleep(100);

        ddata->enabled = true;

        if (!ddata->intro_printed) {
                dev_info(&ddata->dsi->dev, "panel revision %02x.%02x.%02x\n",
                        id1, id2, id3);
                ddata->intro_printed = true;
        }

        ddata->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;

        return 0;
err:
        dev_err(&ddata->dsi->dev, "error while enabling panel, issuing HW reset\n");

        dsicm_hw_reset(ddata);

        return r;
}

static int dsicm_power_off(struct panel_drv_data *ddata)
{
        int r;

        ddata->enabled = false;

        r = mipi_dsi_dcs_set_display_off(ddata->dsi);
        if (!r)
                r = dsicm_sleep_in(ddata);

        if (r) {
                dev_err(&ddata->dsi->dev,
                                "error disabling panel, issuing HW reset\n");
                dsicm_hw_reset(ddata);
        }

        return r;
}

static int dsicm_prepare(struct drm_panel *panel)
{
        struct panel_drv_data *ddata = panel_to_ddata(panel);
        int r;

        r = regulator_bulk_enable(ARRAY_SIZE(ddata->supplies), ddata->supplies);
        if (r)
                dev_err(&ddata->dsi->dev, "failed to enable supplies: %d\n", r);

        return r;
}

static int dsicm_enable(struct drm_panel *panel)
{
        struct panel_drv_data *ddata = panel_to_ddata(panel);
        int r;

        mutex_lock(&ddata->lock);

        r = dsicm_power_on(ddata);
        if (r)
                goto err;

        mutex_unlock(&ddata->lock);

        dsicm_bl_power(ddata, true);

        return 0;
err:
        dev_err(&ddata->dsi->dev, "enable failed (%d)\n", r);
        mutex_unlock(&ddata->lock);
        return r;
}

static int dsicm_unprepare(struct drm_panel *panel)
{
        struct panel_drv_data *ddata = panel_to_ddata(panel);
        int r;

        r = regulator_bulk_disable(ARRAY_SIZE(ddata->supplies), ddata->supplies);
        if (r)
                dev_err(&ddata->dsi->dev, "failed to disable supplies: %d\n", r);

        return r;
}

static int dsicm_disable(struct drm_panel *panel)
{
        struct panel_drv_data *ddata = panel_to_ddata(panel);
        int r;

        dsicm_bl_power(ddata, false);

        mutex_lock(&ddata->lock);

        r = dsicm_power_off(ddata);

        mutex_unlock(&ddata->lock);

        return r;
}

static int dsicm_get_modes(struct drm_panel *panel,
                           struct drm_connector *connector)
{
        struct panel_drv_data *ddata = panel_to_ddata(panel);
        struct drm_display_mode *mode;

        mode = drm_mode_duplicate(connector->dev, &ddata->mode);
        if (!mode) {
                dev_err(&ddata->dsi->dev, "failed to add mode %ux%ux@%u kHz\n",
                        ddata->mode.hdisplay, ddata->mode.vdisplay,
                        ddata->mode.clock);
                return -ENOMEM;
        }

        connector->display_info.width_mm = ddata->panel_data->width_mm;
        connector->display_info.height_mm = ddata->panel_data->height_mm;

        drm_mode_probed_add(connector, mode);

        return 1;
}

static const struct drm_panel_funcs dsicm_panel_funcs = {
        .unprepare = dsicm_unprepare,
        .disable = dsicm_disable,
        .prepare = dsicm_prepare,
        .enable = dsicm_enable,
        .get_modes = dsicm_get_modes,
};

static int dsicm_probe_of(struct mipi_dsi_device *dsi)
{
        struct backlight_device *backlight;
        struct panel_drv_data *ddata = mipi_dsi_get_drvdata(dsi);
        int err;
        struct drm_display_mode *mode = &ddata->mode;

        ddata->reset_gpio = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW);
        if (IS_ERR(ddata->reset_gpio)) {
                err = PTR_ERR(ddata->reset_gpio);
                dev_err(&dsi->dev, "reset gpio request failed: %d", err);
                return err;
        }

        mode->hdisplay = mode->hsync_start = mode->hsync_end = mode->htotal =
                ddata->panel_data->xres;
        mode->vdisplay = mode->vsync_start = mode->vsync_end = mode->vtotal =
                ddata->panel_data->yres;
        mode->clock = ddata->panel_data->xres * ddata->panel_data->yres *
                ddata->panel_data->refresh / 1000;
        mode->width_mm = ddata->panel_data->width_mm;
        mode->height_mm = ddata->panel_data->height_mm;
        mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
        drm_mode_set_name(mode);

        ddata->supplies[0].supply = "vpnl";
        ddata->supplies[1].supply = "vddi";
        err = devm_regulator_bulk_get(&dsi->dev, ARRAY_SIZE(ddata->supplies),
                                      ddata->supplies);
        if (err)
                return err;

        backlight = devm_of_find_backlight(&dsi->dev);
        if (IS_ERR(backlight))
                return PTR_ERR(backlight);

        /* If no backlight device is found assume native backlight support */
        if (backlight)
                ddata->extbldev = backlight;
        else
                ddata->use_dsi_backlight = true;

        return 0;
}

static int dsicm_probe(struct mipi_dsi_device *dsi)
{
        struct panel_drv_data *ddata;
        struct backlight_device *bldev = NULL;
        struct device *dev = &dsi->dev;
        int r;

        dev_dbg(dev, "probe\n");

        ddata = devm_drm_panel_alloc(dev, struct panel_drv_data, panel,
                                     &dsicm_panel_funcs, DRM_MODE_CONNECTOR_DSI);
        if (IS_ERR(ddata))
                return PTR_ERR(ddata);

        mipi_dsi_set_drvdata(dsi, ddata);
        ddata->dsi = dsi;

        ddata->panel_data = of_device_get_match_data(dev);
        if (!ddata->panel_data)
                return -ENODEV;

        r = dsicm_probe_of(dsi);
        if (r)
                return r;

        mutex_init(&ddata->lock);

        dsicm_hw_reset(ddata);

        if (ddata->use_dsi_backlight) {
                struct backlight_properties props = { 0 };
                props.max_brightness = 255;
                props.type = BACKLIGHT_RAW;

                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;
        }

        r = sysfs_create_group(&dev->kobj, &dsicm_attr_group);
        if (r) {
                dev_err(dev, "failed to create sysfs files\n");
                goto err_bl;
        }

        dsi->lanes = 2;
        dsi->format = MIPI_DSI_FMT_RGB888;
        dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS |
                          MIPI_DSI_MODE_NO_EOT_PACKET;
        dsi->hs_rate = ddata->panel_data->max_hs_rate;
        dsi->lp_rate = ddata->panel_data->max_lp_rate;

        drm_panel_add(&ddata->panel);

        r = mipi_dsi_attach(dsi);
        if (r < 0)
                goto err_dsi_attach;

        return 0;

err_dsi_attach:
        drm_panel_remove(&ddata->panel);
        sysfs_remove_group(&dsi->dev.kobj, &dsicm_attr_group);
err_bl:
        if (ddata->extbldev)
                put_device(&ddata->extbldev->dev);

        return r;
}

static void dsicm_remove(struct mipi_dsi_device *dsi)
{
        struct panel_drv_data *ddata = mipi_dsi_get_drvdata(dsi);

        dev_dbg(&dsi->dev, "remove\n");

        mipi_dsi_detach(dsi);

        drm_panel_remove(&ddata->panel);

        sysfs_remove_group(&dsi->dev.kobj, &dsicm_attr_group);

        if (ddata->extbldev)
                put_device(&ddata->extbldev->dev);
}

static const struct dsic_panel_data taal_data = {
        .xres = 864,
        .yres = 480,
        .refresh = 60,
        .width_mm = 0,
        .height_mm = 0,
        .max_hs_rate = 300000000,
        .max_lp_rate = 10000000,
        .te_support = true,
};

static const struct dsic_panel_data himalaya_data = {
        .xres = 480,
        .yres = 864,
        .refresh = 60,
        .width_mm = 49,
        .height_mm = 88,
        .max_hs_rate = 300000000,
        .max_lp_rate = 10000000,
        .te_support = false,
};

static const struct dsic_panel_data droid4_data = {
        .xres = 540,
        .yres = 960,
        .refresh = 60,
        .width_mm = 50,
        .height_mm = 89,
        .max_hs_rate = 300000000,
        .max_lp_rate = 10000000,
        .te_support = false,
};

static const struct of_device_id dsicm_of_match[] = {
        { .compatible = "tpo,taal", .data = &taal_data },
        { .compatible = "nokia,himalaya", &himalaya_data },
        { .compatible = "motorola,droid4-panel", &droid4_data },
        {},
};

MODULE_DEVICE_TABLE(of, dsicm_of_match);

static struct mipi_dsi_driver dsicm_driver = {
        .probe = dsicm_probe,
        .remove = dsicm_remove,
        .driver = {
                .name = "panel-dsi-cm",
                .of_match_table = dsicm_of_match,
        },
};
module_mipi_dsi_driver(dsicm_driver);

MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("Generic DSI Command Mode Panel Driver");
MODULE_LICENSE("GPL");