| /* |
| * RainShadow Tech HDMI CEC driver |
| * |
| * Copyright 2016 Hans Verkuil <hverkuil@xs4all.nl |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version of 2 of the License, or (at your |
| * option) any later version. See the file COPYING in the main directory of |
| * this archive for more details. |
| */ |
| |
| /* |
| * 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/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@xs4all.nl>"); |
| 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_done(rain->adap, CEC_TX_STATUS_OK, |
| 0, 0, 0, 0); |
| break; |
| case 2: |
| cec_transmit_done(rain->adap, CEC_TX_STATUS_NACK, |
| 0, 1, 0, 0); |
| break; |
| default: |
| cec_transmit_done(rain->adap, CEC_TX_STATUS_LOW_DRIVE, |
| 0, 0, 0, 1); |
| 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; |
| bool exit_loop = false; |
| char data; |
| |
| spin_lock_irqsave(&rain->buf_lock, flags); |
| exit_loop = rain->buf_len == 0; |
| if (rain->buf_len) { |
| 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 (exit_loop) |
| break; |
| |
| 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 { |
| strcpy(rain->cmd_reply, rain->cmd); |
| 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); |
| |
| if (rain->buf_len == DATA_SIZE) { |
| dev_warn_once(rain->dev, "buffer overflow\n"); |
| return IRQ_HANDLED; |
| } |
| spin_lock(&rain->buf_lock); |
| 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]); |
| strncat(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_TRANSMIT | CEC_CAP_LOG_ADDRS | CEC_CAP_PHYS_ADDR | |
| CEC_CAP_PASSTHROUGH | CEC_CAP_RC | CEC_CAP_MONITOR_ALL; |
| struct rain *rain; |
| int err = -ENOMEM; |
| struct cec_log_addrs log_addrs = {}; |
| u16 pa = CEC_PHYS_ADDR_INVALID; |
| |
| rain = kzalloc(sizeof(*rain), GFP_KERNEL); |
| |
| if (!rain) |
| return -ENOMEM; |
| |
| rain->serio = serio; |
| rain->adap = cec_allocate_adapter(&rain_cec_adap_ops, rain, |
| "HDMI CEC", 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); |
| |
| 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 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); |