root/drivers/gpu/drm/panel/panel-samsung-s6e63m0-dsi.c
// SPDX-License-Identifier: GPL-2.0
/*
 * DSI interface to the Samsung S6E63M0 panel.
 * (C) 2019 Linus Walleij
 */

#include <linux/module.h>
#include <linux/delay.h>
#include <linux/mod_devicetable.h>

#include <drm/drm_mipi_dsi.h>
#include <drm/drm_print.h>

#include "panel-samsung-s6e63m0.h"

#define MCS_GLOBAL_PARAM        0xb0
#define S6E63M0_DSI_MAX_CHUNK   15 /* CMD + 15 bytes max */

static int s6e63m0_dsi_dcs_read(struct device *dev, void *trsp,
                                const u8 cmd, u8 *data)
{
        struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
        int ret;

        ret = mipi_dsi_dcs_read(dsi, cmd, data, 1);
        if (ret < 0) {
                dev_err(dev, "could not read DCS CMD %02x\n", cmd);
                return ret;
        }

        dev_dbg(dev, "DSI read CMD %02x = %02x\n", cmd, *data);

        return 0;
}

static int s6e63m0_dsi_dcs_write(struct device *dev, void *trsp,
                                 const u8 *data, size_t len)
{
        struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
        const u8 *seqp = data;
        u8 cmd;
        u8 cmdwritten;
        int remain;
        int chunk;
        int ret;

        dev_dbg(dev, "DSI writing dcs seq: %*ph\n", (int)len, data);

        /* Pick out and skip past the DCS command */
        cmd = *seqp;
        seqp++;
        cmdwritten = 0;
        remain = len - 1;
        chunk = remain;

        /* Send max S6E63M0_DSI_MAX_CHUNK bytes at a time */
        if (chunk > S6E63M0_DSI_MAX_CHUNK)
                chunk = S6E63M0_DSI_MAX_CHUNK;
        ret = mipi_dsi_dcs_write(dsi, cmd, seqp, chunk);
        if (ret < 0) {
                dev_err(dev, "error sending DCS command seq cmd %02x\n", cmd);
                return ret;
        }
        cmdwritten += chunk;
        seqp += chunk;

        while (cmdwritten < remain) {
                chunk = remain - cmdwritten;
                if (chunk > S6E63M0_DSI_MAX_CHUNK)
                        chunk = S6E63M0_DSI_MAX_CHUNK;
                ret = mipi_dsi_dcs_write(dsi, MCS_GLOBAL_PARAM, &cmdwritten, 1);
                if (ret < 0) {
                        dev_err(dev, "error sending CMD %02x global param %02x\n",
                                cmd, cmdwritten);
                        return ret;
                }
                ret = mipi_dsi_dcs_write(dsi, cmd, seqp, chunk);
                if (ret < 0) {
                        dev_err(dev, "error sending CMD %02x chunk\n", cmd);
                        return ret;
                }
                cmdwritten += chunk;
                seqp += chunk;
        }
        dev_dbg(dev, "sent command %02x %02x bytes\n", cmd, cmdwritten);

        usleep_range(8000, 9000);

        return 0;
}

static int s6e63m0_dsi_probe(struct mipi_dsi_device *dsi)
{
        struct device *dev = &dsi->dev;
        int ret;

        dsi->lanes = 2;
        dsi->format = MIPI_DSI_FMT_RGB888;
        dsi->hs_rate = 349440000;
        dsi->lp_rate = 9600000;
        dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
                MIPI_DSI_MODE_VIDEO_BURST;

        ret = s6e63m0_probe(dev, NULL, s6e63m0_dsi_dcs_read,
                            s6e63m0_dsi_dcs_write, true);
        if (ret)
                return ret;

        ret = mipi_dsi_attach(dsi);
        if (ret < 0)
                s6e63m0_remove(dev);

        return ret;
}

static void s6e63m0_dsi_remove(struct mipi_dsi_device *dsi)
{
        mipi_dsi_detach(dsi);
        s6e63m0_remove(&dsi->dev);
}

static const struct of_device_id s6e63m0_dsi_of_match[] = {
        { .compatible = "samsung,s6e63m0" },
        { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, s6e63m0_dsi_of_match);

static struct mipi_dsi_driver s6e63m0_dsi_driver = {
        .probe                  = s6e63m0_dsi_probe,
        .remove                 = s6e63m0_dsi_remove,
        .driver                 = {
                .name           = "panel-samsung-s6e63m0",
                .of_match_table = s6e63m0_dsi_of_match,
        },
};
module_mipi_dsi_driver(s6e63m0_dsi_driver);

MODULE_AUTHOR("Linus Walleij <linusw@kernel.org>");
MODULE_DESCRIPTION("s6e63m0 LCD DSI Driver");
MODULE_LICENSE("GPL v2");