Hans Verkuil | 0f314f6 | 2016-11-05 08:39:58 -0200 | [diff] [blame] | 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; |
Colin Ian King | ca33784 | 2017-05-19 14:45:15 -0300 | [diff] [blame] | 122 | bool exit_loop = false; |
Hans Verkuil | 0f314f6 | 2016-11-05 08:39:58 -0200 | [diff] [blame] | 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); |
Wei Yongjun | e0f2e5e | 2017-04-25 00:12:46 -0300 | [diff] [blame] | 339 | spin_lock_init(&rain->buf_lock); |
Hans Verkuil | 0f314f6 | 2016-11-05 08:39:58 -0200 | [diff] [blame] | 340 | |
| 341 | err = serio_open(serio, drv); |
| 342 | if (err) |
| 343 | goto delete_adap; |
| 344 | |
| 345 | err = rain_setup(rain, serio, &log_addrs, &pa); |
| 346 | if (err) |
| 347 | goto close_serio; |
| 348 | |
| 349 | err = cec_register_adapter(rain->adap, &serio->dev); |
| 350 | if (err < 0) |
| 351 | goto close_serio; |
| 352 | |
| 353 | rain->dev = &rain->adap->devnode.dev; |
| 354 | return 0; |
| 355 | |
| 356 | close_serio: |
| 357 | serio_close(serio); |
| 358 | delete_adap: |
| 359 | cec_delete_adapter(rain->adap); |
| 360 | serio_set_drvdata(serio, NULL); |
| 361 | free_device: |
| 362 | kfree(rain); |
| 363 | return err; |
| 364 | } |
| 365 | |
| 366 | static struct serio_device_id rain_serio_ids[] = { |
| 367 | { |
| 368 | .type = SERIO_RS232, |
| 369 | .proto = SERIO_RAINSHADOW_CEC, |
| 370 | .id = SERIO_ANY, |
| 371 | .extra = SERIO_ANY, |
| 372 | }, |
| 373 | { 0 } |
| 374 | }; |
| 375 | |
| 376 | MODULE_DEVICE_TABLE(serio, rain_serio_ids); |
| 377 | |
| 378 | static struct serio_driver rain_drv = { |
| 379 | .driver = { |
| 380 | .name = "rainshadow-cec", |
| 381 | }, |
| 382 | .description = "RainShadow Tech HDMI CEC driver", |
| 383 | .id_table = rain_serio_ids, |
| 384 | .interrupt = rain_interrupt, |
| 385 | .connect = rain_connect, |
| 386 | .disconnect = rain_disconnect, |
| 387 | }; |
| 388 | |
| 389 | module_serio_driver(rain_drv); |