789 lines · c
1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause2/*3 * CEC driver for SECO X86 Boards4 *5 * Author: Ettore Chimenti <ek5.chimenti@gmail.com>6 * Copyright (C) 2018, SECO SpA.7 * Copyright (C) 2018, Aidilab Srl.8 */9 10#include <linux/module.h>11#include <linux/acpi.h>12#include <linux/delay.h>13#include <linux/dmi.h>14#include <linux/gpio/consumer.h>15#include <linux/interrupt.h>16#include <linux/pci.h>17#include <linux/platform_device.h>18 19/* CEC Framework */20#include <media/cec-notifier.h>21 22#include "seco-cec.h"23 24struct secocec_data {25 struct device *dev;26 struct platform_device *pdev;27 struct cec_adapter *cec_adap;28 struct cec_notifier *notifier;29 struct rc_dev *ir;30 char ir_input_phys[32];31 int irq;32};33 34#define smb_wr16(cmd, data) smb_word_op(SECOCEC_MICRO_ADDRESS, \35 cmd, data, SMBUS_WRITE, NULL)36#define smb_rd16(cmd, res) smb_word_op(SECOCEC_MICRO_ADDRESS, \37 cmd, 0, SMBUS_READ, res)38 39static int smb_word_op(u16 slave_addr, u8 cmd, u16 data,40 u8 operation, u16 *result)41{42 unsigned int count;43 int status = 0;44 45 /* Active wait until ready */46 for (count = 0; count <= SMBTIMEOUT; ++count) {47 if (!(inb(HSTS) & BRA_INUSE_STS))48 break;49 udelay(SMB_POLL_UDELAY);50 }51 52 if (count > SMBTIMEOUT)53 /* Reset the lock instead of failing */54 outb(0xff, HSTS);55 56 outb(0x00, HCNT);57 outb((u8)(slave_addr & 0xfe) | operation, XMIT_SLVA);58 outb(cmd, HCMD);59 inb(HCNT);60 61 if (operation == SMBUS_WRITE) {62 outb((u8)data, HDAT0);63 outb((u8)(data >> 8), HDAT1);64 }65 66 outb(BRA_START + BRA_SMB_CMD_WORD_DATA, HCNT);67 68 for (count = 0; count <= SMBTIMEOUT; count++) {69 if (!(inb(HSTS) & BRA_HOST_BUSY))70 break;71 udelay(SMB_POLL_UDELAY);72 }73 74 if (count > SMBTIMEOUT) {75 status = -EBUSY;76 goto err;77 }78 79 if (inb(HSTS) & BRA_HSTS_ERR_MASK) {80 status = -EIO;81 goto err;82 }83 84 if (operation == SMBUS_READ)85 *result = ((inb(HDAT0) & 0xff) + ((inb(HDAT1) & 0xff) << 8));86 87err:88 outb(0xff, HSTS);89 return status;90}91 92static int secocec_adap_enable(struct cec_adapter *adap, bool enable)93{94 struct secocec_data *cec = cec_get_drvdata(adap);95 struct device *dev = cec->dev;96 u16 val = 0;97 int status;98 99 if (enable) {100 /* Clear the status register */101 status = smb_rd16(SECOCEC_STATUS_REG_1, &val);102 if (status)103 goto err;104 105 status = smb_wr16(SECOCEC_STATUS_REG_1, val);106 if (status)107 goto err;108 109 /* Enable the interrupts */110 status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);111 if (status)112 goto err;113 114 status = smb_wr16(SECOCEC_ENABLE_REG_1,115 val | SECOCEC_ENABLE_REG_1_CEC);116 if (status)117 goto err;118 119 dev_dbg(dev, "Device enabled\n");120 } else {121 /* Clear the status register */122 status = smb_rd16(SECOCEC_STATUS_REG_1, &val);123 status = smb_wr16(SECOCEC_STATUS_REG_1, val);124 125 /* Disable the interrupts */126 status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);127 status = smb_wr16(SECOCEC_ENABLE_REG_1, val &128 ~SECOCEC_ENABLE_REG_1_CEC &129 ~SECOCEC_ENABLE_REG_1_IR);130 131 dev_dbg(dev, "Device disabled\n");132 }133 134 return 0;135err:136 return status;137}138 139static int secocec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)140{141 u16 enable_val = 0;142 int status;143 144 /* Disable device */145 status = smb_rd16(SECOCEC_ENABLE_REG_1, &enable_val);146 if (status)147 return status;148 149 status = smb_wr16(SECOCEC_ENABLE_REG_1,150 enable_val & ~SECOCEC_ENABLE_REG_1_CEC);151 if (status)152 return status;153 154 /* Write logical address155 * NOTE: CEC_LOG_ADDR_INVALID is mapped to the 'Unregistered' LA156 */157 status = smb_wr16(SECOCEC_DEVICE_LA, logical_addr & 0xf);158 if (status)159 return status;160 161 /* Re-enable device */162 status = smb_wr16(SECOCEC_ENABLE_REG_1,163 enable_val | SECOCEC_ENABLE_REG_1_CEC);164 if (status)165 return status;166 167 return 0;168}169 170static int secocec_adap_transmit(struct cec_adapter *adap, u8 attempts,171 u32 signal_free_time, struct cec_msg *msg)172{173 u16 payload_len, payload_id_len, destination, val = 0;174 u8 *payload_msg;175 int status;176 u8 i;177 178 /* Device msg len already accounts for header */179 payload_id_len = msg->len - 1;180 181 /* Send data length */182 status = smb_wr16(SECOCEC_WRITE_DATA_LENGTH, payload_id_len);183 if (status)184 goto err;185 186 /* Send Operation ID if present */187 if (payload_id_len > 0) {188 status = smb_wr16(SECOCEC_WRITE_OPERATION_ID, msg->msg[1]);189 if (status)190 goto err;191 }192 /* Send data if present */193 if (payload_id_len > 1) {194 /* Only data; */195 payload_len = msg->len - 2;196 payload_msg = &msg->msg[2];197 198 /* Copy message into registers */199 for (i = 0; i < payload_len; i += 2) {200 /* hi byte */201 val = payload_msg[i + 1] << 8;202 203 /* lo byte */204 val |= payload_msg[i];205 206 status = smb_wr16(SECOCEC_WRITE_DATA_00 + i / 2, val);207 if (status)208 goto err;209 }210 }211 /* Send msg source/destination and fire msg */212 destination = msg->msg[0];213 status = smb_wr16(SECOCEC_WRITE_BYTE0, destination);214 if (status)215 goto err;216 217 return 0;218 219err:220 return status;221}222 223static void secocec_tx_done(struct cec_adapter *adap, u16 status_val)224{225 if (status_val & SECOCEC_STATUS_TX_ERROR_MASK) {226 if (status_val & SECOCEC_STATUS_TX_NACK_ERROR)227 cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK);228 else229 cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR);230 } else {231 cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK);232 }233 234 /* Reset status reg */235 status_val = SECOCEC_STATUS_TX_ERROR_MASK |236 SECOCEC_STATUS_MSG_SENT_MASK |237 SECOCEC_STATUS_TX_NACK_ERROR;238 smb_wr16(SECOCEC_STATUS, status_val);239}240 241static void secocec_rx_done(struct cec_adapter *adap, u16 status_val)242{243 struct secocec_data *cec = cec_get_drvdata(adap);244 struct device *dev = cec->dev;245 struct cec_msg msg = { };246 bool flag_overflow = false;247 u8 payload_len, i = 0;248 u8 *payload_msg;249 u16 val = 0;250 int status;251 252 if (status_val & SECOCEC_STATUS_RX_OVERFLOW_MASK) {253 /* NOTE: Untested, it also might not be necessary */254 dev_warn(dev, "Received more than 16 bytes. Discarding\n");255 flag_overflow = true;256 }257 258 if (status_val & SECOCEC_STATUS_RX_ERROR_MASK) {259 dev_warn(dev, "Message received with errors. Discarding\n");260 status = -EIO;261 goto rxerr;262 }263 264 /* Read message length */265 status = smb_rd16(SECOCEC_READ_DATA_LENGTH, &val);266 if (status)267 return;268 269 /* Device msg len already accounts for the header */270 msg.len = min(val + 1, CEC_MAX_MSG_SIZE);271 272 /* Read logical address */273 status = smb_rd16(SECOCEC_READ_BYTE0, &val);274 if (status)275 return;276 277 /* device stores source LA and destination */278 msg.msg[0] = val;279 280 /* Read operation ID */281 status = smb_rd16(SECOCEC_READ_OPERATION_ID, &val);282 if (status)283 return;284 285 msg.msg[1] = val;286 287 /* Read data if present */288 if (msg.len > 1) {289 payload_len = msg.len - 2;290 payload_msg = &msg.msg[2];291 292 /* device stores 2 bytes in every 16-bit val */293 for (i = 0; i < payload_len; i += 2) {294 status = smb_rd16(SECOCEC_READ_DATA_00 + i / 2, &val);295 if (status)296 return;297 298 /* low byte, skipping header */299 payload_msg[i] = val & 0x00ff;300 301 /* hi byte */302 payload_msg[i + 1] = (val & 0xff00) >> 8;303 }304 }305 306 cec_received_msg(cec->cec_adap, &msg);307 308 /* Reset status reg */309 status_val = SECOCEC_STATUS_MSG_RECEIVED_MASK;310 if (flag_overflow)311 status_val |= SECOCEC_STATUS_RX_OVERFLOW_MASK;312 313 status = smb_wr16(SECOCEC_STATUS, status_val);314 315 return;316 317rxerr:318 /* Reset error reg */319 status_val = SECOCEC_STATUS_MSG_RECEIVED_MASK |320 SECOCEC_STATUS_RX_ERROR_MASK;321 if (flag_overflow)322 status_val |= SECOCEC_STATUS_RX_OVERFLOW_MASK;323 smb_wr16(SECOCEC_STATUS, status_val);324}325 326static const struct cec_adap_ops secocec_cec_adap_ops = {327 /* Low-level callbacks */328 .adap_enable = secocec_adap_enable,329 .adap_log_addr = secocec_adap_log_addr,330 .adap_transmit = secocec_adap_transmit,331};332 333#ifdef CONFIG_CEC_SECO_RC334static int secocec_ir_probe(void *priv)335{336 struct secocec_data *cec = priv;337 struct device *dev = cec->dev;338 int status;339 u16 val;340 341 /* Prepare the RC input device */342 cec->ir = devm_rc_allocate_device(dev, RC_DRIVER_SCANCODE);343 if (!cec->ir)344 return -ENOMEM;345 346 snprintf(cec->ir_input_phys, sizeof(cec->ir_input_phys),347 "%s/input0", dev_name(dev));348 349 cec->ir->device_name = dev_name(dev);350 cec->ir->input_phys = cec->ir_input_phys;351 cec->ir->input_id.bustype = BUS_HOST;352 cec->ir->input_id.vendor = 0;353 cec->ir->input_id.product = 0;354 cec->ir->input_id.version = 1;355 cec->ir->driver_name = SECOCEC_DEV_NAME;356 cec->ir->allowed_protocols = RC_PROTO_BIT_RC5;357 cec->ir->priv = cec;358 cec->ir->map_name = RC_MAP_HAUPPAUGE;359 cec->ir->timeout = MS_TO_US(100);360 361 /* Clear the status register */362 status = smb_rd16(SECOCEC_STATUS_REG_1, &val);363 if (status != 0)364 goto err;365 366 status = smb_wr16(SECOCEC_STATUS_REG_1, val);367 if (status != 0)368 goto err;369 370 /* Enable the interrupts */371 status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);372 if (status != 0)373 goto err;374 375 status = smb_wr16(SECOCEC_ENABLE_REG_1,376 val | SECOCEC_ENABLE_REG_1_IR);377 if (status != 0)378 goto err;379 380 dev_dbg(dev, "IR enabled\n");381 382 status = devm_rc_register_device(dev, cec->ir);383 384 if (status) {385 dev_err(dev, "Failed to prepare input device\n");386 cec->ir = NULL;387 goto err;388 }389 390 return 0;391 392err:393 smb_rd16(SECOCEC_ENABLE_REG_1, &val);394 395 smb_wr16(SECOCEC_ENABLE_REG_1,396 val & ~SECOCEC_ENABLE_REG_1_IR);397 398 dev_dbg(dev, "IR disabled\n");399 return status;400}401 402static int secocec_ir_rx(struct secocec_data *priv)403{404 struct secocec_data *cec = priv;405 struct device *dev = cec->dev;406 u16 val, status, key, addr, toggle;407 408 if (!cec->ir)409 return -ENODEV;410 411 status = smb_rd16(SECOCEC_IR_READ_DATA, &val);412 if (status != 0)413 goto err;414 415 key = val & SECOCEC_IR_COMMAND_MASK;416 addr = (val & SECOCEC_IR_ADDRESS_MASK) >> SECOCEC_IR_ADDRESS_SHL;417 toggle = (val & SECOCEC_IR_TOGGLE_MASK) >> SECOCEC_IR_TOGGLE_SHL;418 419 rc_keydown(cec->ir, RC_PROTO_RC5, RC_SCANCODE_RC5(addr, key), toggle);420 421 dev_dbg(dev, "IR key pressed: 0x%02x addr 0x%02x toggle 0x%02x\n", key,422 addr, toggle);423 424 return 0;425 426err:427 dev_err(dev, "IR Receive message failed (%d)\n", status);428 return -EIO;429}430#else431static void secocec_ir_rx(struct secocec_data *priv)432{433}434 435static int secocec_ir_probe(void *priv)436{437 return 0;438}439#endif440 441static irqreturn_t secocec_irq_handler(int irq, void *priv)442{443 struct secocec_data *cec = priv;444 struct device *dev = cec->dev;445 u16 status_val, cec_val, val = 0;446 int status;447 448 /* Read status register */449 status = smb_rd16(SECOCEC_STATUS_REG_1, &status_val);450 if (status)451 goto err;452 453 if (status_val & SECOCEC_STATUS_REG_1_CEC) {454 /* Read CEC status register */455 status = smb_rd16(SECOCEC_STATUS, &cec_val);456 if (status)457 goto err;458 459 if (cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK)460 secocec_rx_done(cec->cec_adap, cec_val);461 462 if (cec_val & SECOCEC_STATUS_MSG_SENT_MASK)463 secocec_tx_done(cec->cec_adap, cec_val);464 465 if ((~cec_val & SECOCEC_STATUS_MSG_SENT_MASK) &&466 (~cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK))467 dev_warn_once(dev,468 "Message not received or sent, but interrupt fired");469 470 val = SECOCEC_STATUS_REG_1_CEC;471 }472 473 if (status_val & SECOCEC_STATUS_REG_1_IR) {474 val |= SECOCEC_STATUS_REG_1_IR;475 476 secocec_ir_rx(cec);477 }478 479 /* Reset status register */480 status = smb_wr16(SECOCEC_STATUS_REG_1, val);481 if (status)482 goto err;483 484 return IRQ_HANDLED;485 486err:487 dev_err_once(dev, "IRQ: R/W SMBus operation failed %d\n", status);488 489 /* Reset status register */490 val = SECOCEC_STATUS_REG_1_CEC | SECOCEC_STATUS_REG_1_IR;491 smb_wr16(SECOCEC_STATUS_REG_1, val);492 493 return IRQ_HANDLED;494}495 496struct cec_dmi_match {497 const char *sys_vendor;498 const char *product_name;499 const char *devname;500 const char *conn;501};502 503static const struct cec_dmi_match secocec_dmi_match_table[] = {504 /* UDOO X86 */505 { "SECO", "UDOO x86", "0000:00:02.0", "Port B" },506};507 508static struct device *secocec_cec_find_hdmi_dev(struct device *dev,509 const char **conn)510{511 int i;512 513 for (i = 0 ; i < ARRAY_SIZE(secocec_dmi_match_table) ; ++i) {514 const struct cec_dmi_match *m = &secocec_dmi_match_table[i];515 516 if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) &&517 dmi_match(DMI_PRODUCT_NAME, m->product_name)) {518 struct device *d;519 520 /* Find the device, bail out if not yet registered */521 d = bus_find_device_by_name(&pci_bus_type, NULL,522 m->devname);523 if (!d)524 return ERR_PTR(-EPROBE_DEFER);525 526 put_device(d);527 *conn = m->conn;528 return d;529 }530 }531 532 return ERR_PTR(-EINVAL);533}534 535static int secocec_acpi_probe(struct secocec_data *sdev)536{537 struct device *dev = sdev->dev;538 struct gpio_desc *gpio;539 int irq = 0;540 541 gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);542 if (IS_ERR(gpio)) {543 dev_err(dev, "Cannot request interrupt gpio\n");544 return PTR_ERR(gpio);545 }546 547 irq = gpiod_to_irq(gpio);548 if (irq < 0) {549 dev_err(dev, "Cannot find valid irq\n");550 return -ENODEV;551 }552 dev_dbg(dev, "irq-gpio is bound to IRQ %d\n", irq);553 554 sdev->irq = irq;555 556 return 0;557}558 559static int secocec_probe(struct platform_device *pdev)560{561 struct secocec_data *secocec;562 struct device *dev = &pdev->dev;563 struct device *hdmi_dev;564 const char *conn = NULL;565 int ret;566 u16 val;567 568 hdmi_dev = secocec_cec_find_hdmi_dev(&pdev->dev, &conn);569 if (IS_ERR(hdmi_dev))570 return PTR_ERR(hdmi_dev);571 572 secocec = devm_kzalloc(dev, sizeof(*secocec), GFP_KERNEL);573 if (!secocec)574 return -ENOMEM;575 576 dev_set_drvdata(dev, secocec);577 578 /* Request SMBus regions */579 if (!request_muxed_region(BRA_SMB_BASE_ADDR, 7, "CEC00001")) {580 dev_err(dev, "Request memory region failed\n");581 return -ENXIO;582 }583 584 secocec->pdev = pdev;585 secocec->dev = dev;586 587 if (!has_acpi_companion(dev)) {588 dev_dbg(dev, "Cannot find any ACPI companion\n");589 ret = -ENODEV;590 goto err;591 }592 593 ret = secocec_acpi_probe(secocec);594 if (ret) {595 dev_err(dev, "Cannot assign gpio to IRQ\n");596 ret = -ENODEV;597 goto err;598 }599 600 /* Firmware version check */601 ret = smb_rd16(SECOCEC_VERSION, &val);602 if (ret) {603 dev_err(dev, "Cannot check fw version\n");604 goto err;605 }606 if (val < SECOCEC_LATEST_FW) {607 dev_err(dev, "CEC Firmware not supported (v.%04x). Use ver > v.%04x\n",608 val, SECOCEC_LATEST_FW);609 ret = -EINVAL;610 goto err;611 }612 613 ret = devm_request_threaded_irq(dev,614 secocec->irq,615 NULL,616 secocec_irq_handler,617 IRQF_TRIGGER_RISING | IRQF_ONESHOT,618 dev_name(&pdev->dev), secocec);619 620 if (ret) {621 dev_err(dev, "Cannot request IRQ %d\n", secocec->irq);622 ret = -EIO;623 goto err;624 }625 626 /* Allocate CEC adapter */627 secocec->cec_adap = cec_allocate_adapter(&secocec_cec_adap_ops,628 secocec,629 dev_name(dev),630 CEC_CAP_DEFAULTS |631 CEC_CAP_CONNECTOR_INFO,632 SECOCEC_MAX_ADDRS);633 634 if (IS_ERR(secocec->cec_adap)) {635 ret = PTR_ERR(secocec->cec_adap);636 goto err;637 }638 639 secocec->notifier = cec_notifier_cec_adap_register(hdmi_dev, conn,640 secocec->cec_adap);641 if (!secocec->notifier) {642 ret = -ENOMEM;643 goto err_delete_adapter;644 }645 646 ret = cec_register_adapter(secocec->cec_adap, dev);647 if (ret)648 goto err_notifier;649 650 ret = secocec_ir_probe(secocec);651 if (ret)652 goto err_notifier;653 654 platform_set_drvdata(pdev, secocec);655 656 dev_dbg(dev, "Device registered\n");657 658 return ret;659 660err_notifier:661 cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap);662err_delete_adapter:663 cec_delete_adapter(secocec->cec_adap);664err:665 release_region(BRA_SMB_BASE_ADDR, 7);666 dev_err(dev, "%s device probe failed\n", dev_name(dev));667 668 return ret;669}670 671static void secocec_remove(struct platform_device *pdev)672{673 struct secocec_data *secocec = platform_get_drvdata(pdev);674 u16 val;675 676 if (secocec->ir) {677 smb_rd16(SECOCEC_ENABLE_REG_1, &val);678 679 smb_wr16(SECOCEC_ENABLE_REG_1, val & ~SECOCEC_ENABLE_REG_1_IR);680 681 dev_dbg(&pdev->dev, "IR disabled\n");682 }683 cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap);684 cec_unregister_adapter(secocec->cec_adap);685 686 release_region(BRA_SMB_BASE_ADDR, 7);687 688 dev_dbg(&pdev->dev, "CEC device removed\n");689}690 691#ifdef CONFIG_PM_SLEEP692static int secocec_suspend(struct device *dev)693{694 int status;695 u16 val;696 697 dev_dbg(dev, "Device going to suspend, disabling\n");698 699 /* Clear the status register */700 status = smb_rd16(SECOCEC_STATUS_REG_1, &val);701 if (status)702 goto err;703 704 status = smb_wr16(SECOCEC_STATUS_REG_1, val);705 if (status)706 goto err;707 708 /* Disable the interrupts */709 status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);710 if (status)711 goto err;712 713 status = smb_wr16(SECOCEC_ENABLE_REG_1, val &714 ~SECOCEC_ENABLE_REG_1_CEC & ~SECOCEC_ENABLE_REG_1_IR);715 if (status)716 goto err;717 718 return 0;719 720err:721 dev_err(dev, "Suspend failed: %d\n", status);722 return status;723}724 725static int secocec_resume(struct device *dev)726{727 int status;728 u16 val;729 730 dev_dbg(dev, "Resuming device from suspend\n");731 732 /* Clear the status register */733 status = smb_rd16(SECOCEC_STATUS_REG_1, &val);734 if (status)735 goto err;736 737 status = smb_wr16(SECOCEC_STATUS_REG_1, val);738 if (status)739 goto err;740 741 /* Enable the interrupts */742 status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);743 if (status)744 goto err;745 746 status = smb_wr16(SECOCEC_ENABLE_REG_1, val | SECOCEC_ENABLE_REG_1_CEC);747 if (status)748 goto err;749 750 dev_dbg(dev, "Device resumed from suspend\n");751 752 return 0;753 754err:755 dev_err(dev, "Resume failed: %d\n", status);756 return status;757}758 759static SIMPLE_DEV_PM_OPS(secocec_pm_ops, secocec_suspend, secocec_resume);760#define SECOCEC_PM_OPS (&secocec_pm_ops)761#else762#define SECOCEC_PM_OPS NULL763#endif764 765#ifdef CONFIG_ACPI766static const struct acpi_device_id secocec_acpi_match[] = {767 {"CEC00001", 0},768 {},769};770 771MODULE_DEVICE_TABLE(acpi, secocec_acpi_match);772#endif773 774static struct platform_driver secocec_driver = {775 .driver = {776 .name = SECOCEC_DEV_NAME,777 .acpi_match_table = ACPI_PTR(secocec_acpi_match),778 .pm = SECOCEC_PM_OPS,779 },780 .probe = secocec_probe,781 .remove_new = secocec_remove,782};783 784module_platform_driver(secocec_driver);785 786MODULE_DESCRIPTION("SECO CEC X86 Driver");787MODULE_AUTHOR("Ettore Chimenti <ek5.chimenti@gmail.com>");788MODULE_LICENSE("Dual BSD/GPL");789