381 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * RainShadow Tech HDMI CEC driver4 *5 * Copyright 2016 Hans Verkuil <hverkuil@xs4all.nl6 */7 8/*9 * Notes:10 *11 * The higher level protocols are currently disabled. This can be added12 * later, similar to how this is done for the Pulse Eight CEC driver.13 *14 * Documentation of the protocol is available here:15 *16 * http://rainshadowtech.com/doc/HDMICECtoUSBandRS232v2.0.pdf17 */18 19#include <linux/completion.h>20#include <linux/ctype.h>21#include <linux/delay.h>22#include <linux/init.h>23#include <linux/interrupt.h>24#include <linux/kernel.h>25#include <linux/module.h>26#include <linux/serio.h>27#include <linux/slab.h>28#include <linux/spinlock.h>29#include <linux/time.h>30#include <linux/workqueue.h>31 32#include <media/cec.h>33 34MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");35MODULE_DESCRIPTION("RainShadow Tech HDMI CEC driver");36MODULE_LICENSE("GPL");37 38#define DATA_SIZE 25639 40struct rain {41 struct device *dev;42 struct serio *serio;43 struct cec_adapter *adap;44 struct completion cmd_done;45 struct work_struct work;46 47 /* Low-level ringbuffer, collecting incoming characters */48 char buf[DATA_SIZE];49 unsigned int buf_rd_idx;50 unsigned int buf_wr_idx;51 unsigned int buf_len;52 spinlock_t buf_lock;53 54 /* command buffer */55 char cmd[DATA_SIZE];56 unsigned int cmd_idx;57 bool cmd_started;58 59 /* reply to a command, only used to store the firmware version */60 char cmd_reply[DATA_SIZE];61 62 struct mutex write_lock;63};64 65static void rain_process_msg(struct rain *rain)66{67 struct cec_msg msg = {};68 const char *cmd = rain->cmd + 3;69 int stat = -1;70 71 for (; *cmd; cmd++) {72 if (!isxdigit(*cmd))73 continue;74 if (isxdigit(cmd[0]) && isxdigit(cmd[1])) {75 if (msg.len == CEC_MAX_MSG_SIZE)76 break;77 if (hex2bin(msg.msg + msg.len, cmd, 1))78 continue;79 msg.len++;80 cmd++;81 continue;82 }83 if (!cmd[1])84 stat = hex_to_bin(cmd[0]);85 break;86 }87 88 if (rain->cmd[0] == 'R') {89 if (stat == 1 || stat == 2)90 cec_received_msg(rain->adap, &msg);91 return;92 }93 94 switch (stat) {95 case 1:96 cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_OK);97 break;98 case 2:99 cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_NACK);100 break;101 default:102 cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_LOW_DRIVE);103 break;104 }105}106 107static void rain_irq_work_handler(struct work_struct *work)108{109 struct rain *rain =110 container_of(work, struct rain, work);111 112 while (true) {113 unsigned long flags;114 char data;115 116 spin_lock_irqsave(&rain->buf_lock, flags);117 if (!rain->buf_len) {118 spin_unlock_irqrestore(&rain->buf_lock, flags);119 break;120 }121 122 data = rain->buf[rain->buf_rd_idx];123 rain->buf_len--;124 rain->buf_rd_idx = (rain->buf_rd_idx + 1) & 0xff;125 126 spin_unlock_irqrestore(&rain->buf_lock, flags);127 128 if (!rain->cmd_started && data != '?')129 continue;130 131 switch (data) {132 case '\r':133 rain->cmd[rain->cmd_idx] = '\0';134 dev_dbg(rain->dev, "received: %s\n", rain->cmd);135 if (!memcmp(rain->cmd, "REC", 3) ||136 !memcmp(rain->cmd, "STA", 3)) {137 rain_process_msg(rain);138 } else {139 strscpy(rain->cmd_reply, rain->cmd,140 sizeof(rain->cmd_reply));141 complete(&rain->cmd_done);142 }143 rain->cmd_idx = 0;144 rain->cmd_started = false;145 break;146 147 case '\n':148 rain->cmd_idx = 0;149 rain->cmd_started = false;150 break;151 152 case '?':153 rain->cmd_idx = 0;154 rain->cmd_started = true;155 break;156 157 default:158 if (rain->cmd_idx >= DATA_SIZE - 1) {159 dev_dbg(rain->dev,160 "throwing away %d bytes of garbage\n", rain->cmd_idx);161 rain->cmd_idx = 0;162 }163 rain->cmd[rain->cmd_idx++] = data;164 break;165 }166 }167}168 169static irqreturn_t rain_interrupt(struct serio *serio, unsigned char data,170 unsigned int flags)171{172 struct rain *rain = serio_get_drvdata(serio);173 174 if (rain->buf_len == DATA_SIZE) {175 dev_warn_once(rain->dev, "buffer overflow\n");176 return IRQ_HANDLED;177 }178 spin_lock(&rain->buf_lock);179 rain->buf_len++;180 rain->buf[rain->buf_wr_idx] = data;181 rain->buf_wr_idx = (rain->buf_wr_idx + 1) & 0xff;182 spin_unlock(&rain->buf_lock);183 schedule_work(&rain->work);184 return IRQ_HANDLED;185}186 187static void rain_disconnect(struct serio *serio)188{189 struct rain *rain = serio_get_drvdata(serio);190 191 cancel_work_sync(&rain->work);192 cec_unregister_adapter(rain->adap);193 dev_info(&serio->dev, "disconnected\n");194 serio_close(serio);195 serio_set_drvdata(serio, NULL);196 kfree(rain);197}198 199static int rain_send(struct rain *rain, const char *command)200{201 int err = serio_write(rain->serio, '!');202 203 dev_dbg(rain->dev, "send: %s\n", command);204 while (!err && *command)205 err = serio_write(rain->serio, *command++);206 if (!err)207 err = serio_write(rain->serio, '~');208 209 return err;210}211 212static int rain_send_and_wait(struct rain *rain,213 const char *cmd, const char *reply)214{215 int err;216 217 init_completion(&rain->cmd_done);218 219 mutex_lock(&rain->write_lock);220 err = rain_send(rain, cmd);221 if (err)222 goto err;223 224 if (!wait_for_completion_timeout(&rain->cmd_done, HZ)) {225 err = -ETIMEDOUT;226 goto err;227 }228 if (reply && strncmp(rain->cmd_reply, reply, strlen(reply))) {229 dev_dbg(rain->dev,230 "transmit of '%s': received '%s' instead of '%s'\n",231 cmd, rain->cmd_reply, reply);232 err = -EIO;233 }234err:235 mutex_unlock(&rain->write_lock);236 return err;237}238 239static int rain_setup(struct rain *rain, struct serio *serio,240 struct cec_log_addrs *log_addrs, u16 *pa)241{242 int err;243 244 err = rain_send_and_wait(rain, "R", "REV");245 if (err)246 return err;247 dev_info(rain->dev, "Firmware version %s\n", rain->cmd_reply + 4);248 249 err = rain_send_and_wait(rain, "Q 1", "QTY");250 if (err)251 return err;252 err = rain_send_and_wait(rain, "c0000", "CFG");253 if (err)254 return err;255 return rain_send_and_wait(rain, "A F 0000", "ADR");256}257 258static int rain_cec_adap_enable(struct cec_adapter *adap, bool enable)259{260 return 0;261}262 263static int rain_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)264{265 struct rain *rain = cec_get_drvdata(adap);266 u8 cmd[16];267 268 if (log_addr == CEC_LOG_ADDR_INVALID)269 log_addr = CEC_LOG_ADDR_UNREGISTERED;270 snprintf(cmd, sizeof(cmd), "A %x", log_addr);271 return rain_send_and_wait(rain, cmd, "ADR");272}273 274static int rain_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,275 u32 signal_free_time, struct cec_msg *msg)276{277 struct rain *rain = cec_get_drvdata(adap);278 char cmd[2 * CEC_MAX_MSG_SIZE + 16];279 unsigned int i;280 int err;281 282 if (msg->len == 1) {283 snprintf(cmd, sizeof(cmd), "x%x", cec_msg_destination(msg));284 } else {285 char hex[3];286 287 snprintf(cmd, sizeof(cmd), "x%x %02x ",288 cec_msg_destination(msg), msg->msg[1]);289 for (i = 2; i < msg->len; i++) {290 snprintf(hex, sizeof(hex), "%02x", msg->msg[i]);291 strlcat(cmd, hex, sizeof(cmd));292 }293 }294 mutex_lock(&rain->write_lock);295 err = rain_send(rain, cmd);296 mutex_unlock(&rain->write_lock);297 return err;298}299 300static const struct cec_adap_ops rain_cec_adap_ops = {301 .adap_enable = rain_cec_adap_enable,302 .adap_log_addr = rain_cec_adap_log_addr,303 .adap_transmit = rain_cec_adap_transmit,304};305 306static int rain_connect(struct serio *serio, struct serio_driver *drv)307{308 u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_PHYS_ADDR | CEC_CAP_MONITOR_ALL;309 struct rain *rain;310 int err = -ENOMEM;311 struct cec_log_addrs log_addrs = {};312 u16 pa = CEC_PHYS_ADDR_INVALID;313 314 rain = kzalloc(sizeof(*rain), GFP_KERNEL);315 316 if (!rain)317 return -ENOMEM;318 319 rain->serio = serio;320 rain->adap = cec_allocate_adapter(&rain_cec_adap_ops, rain,321 dev_name(&serio->dev), caps, 1);322 err = PTR_ERR_OR_ZERO(rain->adap);323 if (err < 0)324 goto free_device;325 326 rain->dev = &serio->dev;327 serio_set_drvdata(serio, rain);328 INIT_WORK(&rain->work, rain_irq_work_handler);329 mutex_init(&rain->write_lock);330 spin_lock_init(&rain->buf_lock);331 332 err = serio_open(serio, drv);333 if (err)334 goto delete_adap;335 336 err = rain_setup(rain, serio, &log_addrs, &pa);337 if (err)338 goto close_serio;339 340 err = cec_register_adapter(rain->adap, &serio->dev);341 if (err < 0)342 goto close_serio;343 344 rain->dev = &rain->adap->devnode.dev;345 return 0;346 347close_serio:348 serio_close(serio);349delete_adap:350 cec_delete_adapter(rain->adap);351 serio_set_drvdata(serio, NULL);352free_device:353 kfree(rain);354 return err;355}356 357static const struct serio_device_id rain_serio_ids[] = {358 {359 .type = SERIO_RS232,360 .proto = SERIO_RAINSHADOW_CEC,361 .id = SERIO_ANY,362 .extra = SERIO_ANY,363 },364 { 0 }365};366 367MODULE_DEVICE_TABLE(serio, rain_serio_ids);368 369static struct serio_driver rain_drv = {370 .driver = {371 .name = "rainshadow-cec",372 },373 .description = "RainShadow Tech HDMI CEC driver",374 .id_table = rain_serio_ids,375 .interrupt = rain_interrupt,376 .connect = rain_connect,377 .disconnect = rain_disconnect,378};379 380module_serio_driver(rain_drv);381