root/drivers/media/cec/usb/rainshadow/rainshadow-cec.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * RainShadow Tech HDMI CEC driver
 *
 * Copyright 2016 Hans Verkuil <hverkuil@kernel.org>
 */

/*
 * Notes:
 *
 * The higher level protocols are currently disabled. This can be added
 * later, similar to how this is done for the Pulse Eight CEC driver.
 *
 * Documentation of the protocol is available here:
 *
 * http://rainshadowtech.com/doc/HDMICECtoUSBandRS232v2.0.pdf
 */

#include <linux/completion.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/hex.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/serio.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>
#include <linux/workqueue.h>

#include <media/cec.h>

MODULE_AUTHOR("Hans Verkuil <hverkuil@kernel.org>");
MODULE_DESCRIPTION("RainShadow Tech HDMI CEC driver");
MODULE_LICENSE("GPL");

#define DATA_SIZE 256

struct rain {
        struct device *dev;
        struct serio *serio;
        struct cec_adapter *adap;
        struct completion cmd_done;
        struct work_struct work;

        /* Low-level ringbuffer, collecting incoming characters */
        char buf[DATA_SIZE];
        unsigned int buf_rd_idx;
        unsigned int buf_wr_idx;
        unsigned int buf_len;
        spinlock_t buf_lock;

        /* command buffer */
        char cmd[DATA_SIZE];
        unsigned int cmd_idx;
        bool cmd_started;

        /* reply to a command, only used to store the firmware version */
        char cmd_reply[DATA_SIZE];

        struct mutex write_lock;
};

static void rain_process_msg(struct rain *rain)
{
        struct cec_msg msg = {};
        const char *cmd = rain->cmd + 3;
        int stat = -1;

        for (; *cmd; cmd++) {
                if (!isxdigit(*cmd))
                        continue;
                if (isxdigit(cmd[0]) && isxdigit(cmd[1])) {
                        if (msg.len == CEC_MAX_MSG_SIZE)
                                break;
                        if (hex2bin(msg.msg + msg.len, cmd, 1))
                                continue;
                        msg.len++;
                        cmd++;
                        continue;
                }
                if (!cmd[1])
                        stat = hex_to_bin(cmd[0]);
                break;
        }

        if (rain->cmd[0] == 'R') {
                if (stat == 1 || stat == 2)
                        cec_received_msg(rain->adap, &msg);
                return;
        }

        switch (stat) {
        case 1:
                cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_OK);
                break;
        case 2:
                cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_NACK);
                break;
        default:
                cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_LOW_DRIVE);
                break;
        }
}

static void rain_irq_work_handler(struct work_struct *work)
{
        struct rain *rain =
                container_of(work, struct rain, work);

        while (true) {
                unsigned long flags;
                char data;

                spin_lock_irqsave(&rain->buf_lock, flags);
                if (!rain->buf_len) {
                        spin_unlock_irqrestore(&rain->buf_lock, flags);
                        break;
                }

                data = rain->buf[rain->buf_rd_idx];
                rain->buf_len--;
                rain->buf_rd_idx = (rain->buf_rd_idx + 1) & 0xff;

                spin_unlock_irqrestore(&rain->buf_lock, flags);

                if (!rain->cmd_started && data != '?')
                        continue;

                switch (data) {
                case '\r':
                        rain->cmd[rain->cmd_idx] = '\0';
                        dev_dbg(rain->dev, "received: %s\n", rain->cmd);
                        if (!memcmp(rain->cmd, "REC", 3) ||
                            !memcmp(rain->cmd, "STA", 3)) {
                                rain_process_msg(rain);
                        } else {
                                strscpy(rain->cmd_reply, rain->cmd,
                                        sizeof(rain->cmd_reply));
                                complete(&rain->cmd_done);
                        }
                        rain->cmd_idx = 0;
                        rain->cmd_started = false;
                        break;

                case '\n':
                        rain->cmd_idx = 0;
                        rain->cmd_started = false;
                        break;

                case '?':
                        rain->cmd_idx = 0;
                        rain->cmd_started = true;
                        break;

                default:
                        if (rain->cmd_idx >= DATA_SIZE - 1) {
                                dev_dbg(rain->dev,
                                        "throwing away %d bytes of garbage\n", rain->cmd_idx);
                                rain->cmd_idx = 0;
                        }
                        rain->cmd[rain->cmd_idx++] = data;
                        break;
                }
        }
}

static irqreturn_t rain_interrupt(struct serio *serio, unsigned char data,
                                    unsigned int flags)
{
        struct rain *rain = serio_get_drvdata(serio);

        spin_lock(&rain->buf_lock);
        if (rain->buf_len == DATA_SIZE) {
                spin_unlock(&rain->buf_lock);
                dev_warn_once(rain->dev, "buffer overflow\n");
                return IRQ_HANDLED;
        }
        rain->buf_len++;
        rain->buf[rain->buf_wr_idx] = data;
        rain->buf_wr_idx = (rain->buf_wr_idx + 1) & 0xff;
        spin_unlock(&rain->buf_lock);
        schedule_work(&rain->work);
        return IRQ_HANDLED;
}

static void rain_disconnect(struct serio *serio)
{
        struct rain *rain = serio_get_drvdata(serio);

        cancel_work_sync(&rain->work);
        cec_unregister_adapter(rain->adap);
        dev_info(&serio->dev, "disconnected\n");
        serio_close(serio);
        serio_set_drvdata(serio, NULL);
        kfree(rain);
}

static int rain_send(struct rain *rain, const char *command)
{
        int err = serio_write(rain->serio, '!');

        dev_dbg(rain->dev, "send: %s\n", command);
        while (!err && *command)
                err = serio_write(rain->serio, *command++);
        if (!err)
                err = serio_write(rain->serio, '~');

        return err;
}

static int rain_send_and_wait(struct rain *rain,
                              const char *cmd, const char *reply)
{
        int err;

        init_completion(&rain->cmd_done);

        mutex_lock(&rain->write_lock);
        err = rain_send(rain, cmd);
        if (err)
                goto err;

        if (!wait_for_completion_timeout(&rain->cmd_done, HZ)) {
                err = -ETIMEDOUT;
                goto err;
        }
        if (reply && strncmp(rain->cmd_reply, reply, strlen(reply))) {
                dev_dbg(rain->dev,
                         "transmit of '%s': received '%s' instead of '%s'\n",
                         cmd, rain->cmd_reply, reply);
                err = -EIO;
        }
err:
        mutex_unlock(&rain->write_lock);
        return err;
}

static int rain_setup(struct rain *rain, struct serio *serio,
                        struct cec_log_addrs *log_addrs, u16 *pa)
{
        int err;

        err = rain_send_and_wait(rain, "R", "REV");
        if (err)
                return err;
        dev_info(rain->dev, "Firmware version %s\n", rain->cmd_reply + 4);

        err = rain_send_and_wait(rain, "Q 1", "QTY");
        if (err)
                return err;
        err = rain_send_and_wait(rain, "c0000", "CFG");
        if (err)
                return err;
        return rain_send_and_wait(rain, "A F 0000", "ADR");
}

static int rain_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
        return 0;
}

static int rain_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
{
        struct rain *rain = cec_get_drvdata(adap);
        u8 cmd[16];

        if (log_addr == CEC_LOG_ADDR_INVALID)
                log_addr = CEC_LOG_ADDR_UNREGISTERED;
        snprintf(cmd, sizeof(cmd), "A %x", log_addr);
        return rain_send_and_wait(rain, cmd, "ADR");
}

static int rain_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
                                    u32 signal_free_time, struct cec_msg *msg)
{
        struct rain *rain = cec_get_drvdata(adap);
        char cmd[2 * CEC_MAX_MSG_SIZE + 16];
        unsigned int i;
        int err;

        if (msg->len == 1) {
                snprintf(cmd, sizeof(cmd), "x%x", cec_msg_destination(msg));
        } else {
                char hex[3];

                snprintf(cmd, sizeof(cmd), "x%x %02x ",
                         cec_msg_destination(msg), msg->msg[1]);
                for (i = 2; i < msg->len; i++) {
                        snprintf(hex, sizeof(hex), "%02x", msg->msg[i]);
                        strlcat(cmd, hex, sizeof(cmd));
                }
        }
        mutex_lock(&rain->write_lock);
        err = rain_send(rain, cmd);
        mutex_unlock(&rain->write_lock);
        return err;
}

static const struct cec_adap_ops rain_cec_adap_ops = {
        .adap_enable = rain_cec_adap_enable,
        .adap_log_addr = rain_cec_adap_log_addr,
        .adap_transmit = rain_cec_adap_transmit,
};

static int rain_connect(struct serio *serio, struct serio_driver *drv)
{
        u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_PHYS_ADDR | CEC_CAP_MONITOR_ALL;
        struct rain *rain;
        int err = -ENOMEM;
        struct cec_log_addrs log_addrs = {};
        u16 pa = CEC_PHYS_ADDR_INVALID;

        rain = kzalloc_obj(*rain);

        if (!rain)
                return -ENOMEM;

        rain->serio = serio;
        rain->adap = cec_allocate_adapter(&rain_cec_adap_ops, rain,
                                          dev_name(&serio->dev), caps, 1);
        err = PTR_ERR_OR_ZERO(rain->adap);
        if (err < 0)
                goto free_device;

        rain->dev = &serio->dev;
        serio_set_drvdata(serio, rain);
        INIT_WORK(&rain->work, rain_irq_work_handler);
        mutex_init(&rain->write_lock);
        spin_lock_init(&rain->buf_lock);

        err = serio_open(serio, drv);
        if (err)
                goto delete_adap;

        err = rain_setup(rain, serio, &log_addrs, &pa);
        if (err)
                goto close_serio;

        err = cec_register_adapter(rain->adap, &serio->dev);
        if (err < 0)
                goto close_serio;

        rain->dev = &rain->adap->devnode.dev;
        return 0;

close_serio:
        serio_close(serio);
delete_adap:
        cec_delete_adapter(rain->adap);
        serio_set_drvdata(serio, NULL);
free_device:
        kfree(rain);
        return err;
}

static const struct serio_device_id rain_serio_ids[] = {
        {
                .type   = SERIO_RS232,
                .proto  = SERIO_RAINSHADOW_CEC,
                .id     = SERIO_ANY,
                .extra  = SERIO_ANY,
        },
        { 0 }
};

MODULE_DEVICE_TABLE(serio, rain_serio_ids);

static struct serio_driver rain_drv = {
        .driver         = {
                .name   = "rainshadow-cec",
        },
        .description    = "RainShadow Tech HDMI CEC driver",
        .id_table       = rain_serio_ids,
        .interrupt      = rain_interrupt,
        .connect        = rain_connect,
        .disconnect     = rain_disconnect,
};

module_serio_driver(rain_drv);