Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[media] rainshadow-cec: new RainShadow Tech HDMI CEC driver

This driver supports the RainShadow Tech USB HDMI CEC adapter.

See: http://rainshadowtech.com/HdmiCecUsb.html

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>

authored by

Hans Verkuil and committed by
Mauro Carvalho Chehab
0f314f6c 04dffe11

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