diff options
Diffstat (limited to 'drivers/media/platform/seco-cec/seco-cec.c')
-rw-r--r-- | drivers/media/platform/seco-cec/seco-cec.c | 803 |
1 files changed, 0 insertions, 803 deletions
diff --git a/drivers/media/platform/seco-cec/seco-cec.c b/drivers/media/platform/seco-cec/seco-cec.c deleted file mode 100644 index 2ff62a488b27..000000000000 --- a/drivers/media/platform/seco-cec/seco-cec.c +++ /dev/null @@ -1,803 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause -/* - * CEC driver for SECO X86 Boards - * - * Author: Ettore Chimenti <ek5.chimenti@gmail.com> - * Copyright (C) 2018, SECO SpA. - * Copyright (C) 2018, Aidilab Srl. - */ - -#include <linux/module.h> -#include <linux/acpi.h> -#include <linux/delay.h> -#include <linux/dmi.h> -#include <linux/gpio/consumer.h> -#include <linux/gpio.h> -#include <linux/interrupt.h> -#include <linux/pci.h> -#include <linux/platform_device.h> - -/* CEC Framework */ -#include <media/cec-notifier.h> - -#include "seco-cec.h" - -struct secocec_data { - struct device *dev; - struct platform_device *pdev; - struct cec_adapter *cec_adap; - struct cec_notifier *notifier; - struct rc_dev *ir; - char ir_input_phys[32]; - int irq; -}; - -#define smb_wr16(cmd, data) smb_word_op(CMD_WORD_DATA, SECOCEC_MICRO_ADDRESS, \ - cmd, data, SMBUS_WRITE, NULL) -#define smb_rd16(cmd, res) smb_word_op(CMD_WORD_DATA, SECOCEC_MICRO_ADDRESS, \ - cmd, 0, SMBUS_READ, res) - -static int smb_word_op(short data_format, u16 slave_addr, u8 cmd, u16 data, - u8 operation, u16 *result) -{ - unsigned int count; - short _data_format; - int status = 0; - - switch (data_format) { - case CMD_BYTE_DATA: - _data_format = BRA_SMB_CMD_BYTE_DATA; - break; - case CMD_WORD_DATA: - _data_format = BRA_SMB_CMD_WORD_DATA; - break; - default: - return -EINVAL; - } - - /* Active wait until ready */ - for (count = 0; count <= SMBTIMEOUT; ++count) { - if (!(inb(HSTS) & BRA_INUSE_STS)) - break; - udelay(SMB_POLL_UDELAY); - } - - if (count > SMBTIMEOUT) - /* Reset the lock instead of failing */ - outb(0xff, HSTS); - - outb(0x00, HCNT); - outb((u8)(slave_addr & 0xfe) | operation, XMIT_SLVA); - outb(cmd, HCMD); - inb(HCNT); - - if (operation == SMBUS_WRITE) { - outb((u8)data, HDAT0); - outb((u8)(data >> 8), HDAT1); - } - - outb(BRA_START + _data_format, HCNT); - - for (count = 0; count <= SMBTIMEOUT; count++) { - if (!(inb(HSTS) & BRA_HOST_BUSY)) - break; - udelay(SMB_POLL_UDELAY); - } - - if (count > SMBTIMEOUT) { - status = -EBUSY; - goto err; - } - - if (inb(HSTS) & BRA_HSTS_ERR_MASK) { - status = -EIO; - goto err; - } - - if (operation == SMBUS_READ) - *result = ((inb(HDAT0) & 0xff) + ((inb(HDAT1) & 0xff) << 8)); - -err: - outb(0xff, HSTS); - return status; -} - -static int secocec_adap_enable(struct cec_adapter *adap, bool enable) -{ - struct secocec_data *cec = cec_get_drvdata(adap); - struct device *dev = cec->dev; - u16 val = 0; - int status; - - if (enable) { - /* Clear the status register */ - status = smb_rd16(SECOCEC_STATUS_REG_1, &val); - if (status) - goto err; - - status = smb_wr16(SECOCEC_STATUS_REG_1, val); - if (status) - goto err; - - /* Enable the interrupts */ - status = smb_rd16(SECOCEC_ENABLE_REG_1, &val); - if (status) - goto err; - - status = smb_wr16(SECOCEC_ENABLE_REG_1, - val | SECOCEC_ENABLE_REG_1_CEC); - if (status) - goto err; - - dev_dbg(dev, "Device enabled"); - } else { - /* Clear the status register */ - status = smb_rd16(SECOCEC_STATUS_REG_1, &val); - status = smb_wr16(SECOCEC_STATUS_REG_1, val); - - /* Disable the interrupts */ - status = smb_rd16(SECOCEC_ENABLE_REG_1, &val); - status = smb_wr16(SECOCEC_ENABLE_REG_1, val & - ~SECOCEC_ENABLE_REG_1_CEC & - ~SECOCEC_ENABLE_REG_1_IR); - - dev_dbg(dev, "Device disabled"); - } - - return 0; -err: - return status; -} - -static int secocec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr) -{ - u16 enable_val = 0; - int status; - - /* Disable device */ - status = smb_rd16(SECOCEC_ENABLE_REG_1, &enable_val); - if (status) - return status; - - status = smb_wr16(SECOCEC_ENABLE_REG_1, - enable_val & ~SECOCEC_ENABLE_REG_1_CEC); - if (status) - return status; - - /* Write logical address - * NOTE: CEC_LOG_ADDR_INVALID is mapped to the 'Unregistered' LA - */ - status = smb_wr16(SECOCEC_DEVICE_LA, logical_addr & 0xf); - if (status) - return status; - - /* Re-enable device */ - status = smb_wr16(SECOCEC_ENABLE_REG_1, - enable_val | SECOCEC_ENABLE_REG_1_CEC); - if (status) - return status; - - return 0; -} - -static int secocec_adap_transmit(struct cec_adapter *adap, u8 attempts, - u32 signal_free_time, struct cec_msg *msg) -{ - u16 payload_len, payload_id_len, destination, val = 0; - u8 *payload_msg; - int status; - u8 i; - - /* Device msg len already accounts for header */ - payload_id_len = msg->len - 1; - - /* Send data length */ - status = smb_wr16(SECOCEC_WRITE_DATA_LENGTH, payload_id_len); - if (status) - goto err; - - /* Send Operation ID if present */ - if (payload_id_len > 0) { - status = smb_wr16(SECOCEC_WRITE_OPERATION_ID, msg->msg[1]); - if (status) - goto err; - } - /* Send data if present */ - if (payload_id_len > 1) { - /* Only data; */ - payload_len = msg->len - 2; - payload_msg = &msg->msg[2]; - - /* Copy message into registers */ - for (i = 0; i < payload_len; i += 2) { - /* hi byte */ - val = payload_msg[i + 1] << 8; - - /* lo byte */ - val |= payload_msg[i]; - - status = smb_wr16(SECOCEC_WRITE_DATA_00 + i / 2, val); - if (status) - goto err; - } - } - /* Send msg source/destination and fire msg */ - destination = msg->msg[0]; - status = smb_wr16(SECOCEC_WRITE_BYTE0, destination); - if (status) - goto err; - - return 0; - -err: - return status; -} - -static void secocec_tx_done(struct cec_adapter *adap, u16 status_val) -{ - if (status_val & SECOCEC_STATUS_TX_ERROR_MASK) { - if (status_val & SECOCEC_STATUS_TX_NACK_ERROR) - cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK); - else - cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR); - } else { - cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK); - } - - /* Reset status reg */ - status_val = SECOCEC_STATUS_TX_ERROR_MASK | - SECOCEC_STATUS_MSG_SENT_MASK | - SECOCEC_STATUS_TX_NACK_ERROR; - smb_wr16(SECOCEC_STATUS, status_val); -} - -static void secocec_rx_done(struct cec_adapter *adap, u16 status_val) -{ - struct secocec_data *cec = cec_get_drvdata(adap); - struct device *dev = cec->dev; - struct cec_msg msg = { }; - bool flag_overflow = false; - u8 payload_len, i = 0; - u8 *payload_msg; - u16 val = 0; - int status; - - if (status_val & SECOCEC_STATUS_RX_OVERFLOW_MASK) { - /* NOTE: Untested, it also might not be necessary */ - dev_warn(dev, "Received more than 16 bytes. Discarding"); - flag_overflow = true; - } - - if (status_val & SECOCEC_STATUS_RX_ERROR_MASK) { - dev_warn(dev, "Message received with errors. Discarding"); - status = -EIO; - goto rxerr; - } - - /* Read message length */ - status = smb_rd16(SECOCEC_READ_DATA_LENGTH, &val); - if (status) - return; - - /* Device msg len already accounts for the header */ - msg.len = min(val + 1, CEC_MAX_MSG_SIZE); - - /* Read logical address */ - status = smb_rd16(SECOCEC_READ_BYTE0, &val); - if (status) - return; - - /* device stores source LA and destination */ - msg.msg[0] = val; - - /* Read operation ID */ - status = smb_rd16(SECOCEC_READ_OPERATION_ID, &val); - if (status) - return; - - msg.msg[1] = val; - - /* Read data if present */ - if (msg.len > 1) { - payload_len = msg.len - 2; - payload_msg = &msg.msg[2]; - - /* device stores 2 bytes in every 16-bit val */ - for (i = 0; i < payload_len; i += 2) { - status = smb_rd16(SECOCEC_READ_DATA_00 + i / 2, &val); - if (status) - return; - - /* low byte, skipping header */ - payload_msg[i] = val & 0x00ff; - - /* hi byte */ - payload_msg[i + 1] = (val & 0xff00) >> 8; - } - } - - cec_received_msg(cec->cec_adap, &msg); - - /* Reset status reg */ - status_val = SECOCEC_STATUS_MSG_RECEIVED_MASK; - if (flag_overflow) - status_val |= SECOCEC_STATUS_RX_OVERFLOW_MASK; - - status = smb_wr16(SECOCEC_STATUS, status_val); - - return; - -rxerr: - /* Reset error reg */ - status_val = SECOCEC_STATUS_MSG_RECEIVED_MASK | - SECOCEC_STATUS_RX_ERROR_MASK; - if (flag_overflow) - status_val |= SECOCEC_STATUS_RX_OVERFLOW_MASK; - smb_wr16(SECOCEC_STATUS, status_val); -} - -static const struct cec_adap_ops secocec_cec_adap_ops = { - /* Low-level callbacks */ - .adap_enable = secocec_adap_enable, - .adap_log_addr = secocec_adap_log_addr, - .adap_transmit = secocec_adap_transmit, -}; - -#ifdef CONFIG_VIDEO_SECO_RC -static int secocec_ir_probe(void *priv) -{ - struct secocec_data *cec = priv; - struct device *dev = cec->dev; - int status; - u16 val; - - /* Prepare the RC input device */ - cec->ir = devm_rc_allocate_device(dev, RC_DRIVER_SCANCODE); - if (!cec->ir) - return -ENOMEM; - - snprintf(cec->ir_input_phys, sizeof(cec->ir_input_phys), - "%s/input0", dev_name(dev)); - - cec->ir->device_name = dev_name(dev); - cec->ir->input_phys = cec->ir_input_phys; - cec->ir->input_id.bustype = BUS_HOST; - cec->ir->input_id.vendor = 0; - cec->ir->input_id.product = 0; - cec->ir->input_id.version = 1; - cec->ir->driver_name = SECOCEC_DEV_NAME; - cec->ir->allowed_protocols = RC_PROTO_BIT_RC5; - cec->ir->priv = cec; - cec->ir->map_name = RC_MAP_HAUPPAUGE; - cec->ir->timeout = MS_TO_NS(100); - - /* Clear the status register */ - status = smb_rd16(SECOCEC_STATUS_REG_1, &val); - if (status != 0) - goto err; - - status = smb_wr16(SECOCEC_STATUS_REG_1, val); - if (status != 0) - goto err; - - /* Enable the interrupts */ - status = smb_rd16(SECOCEC_ENABLE_REG_1, &val); - if (status != 0) - goto err; - - status = smb_wr16(SECOCEC_ENABLE_REG_1, - val | SECOCEC_ENABLE_REG_1_IR); - if (status != 0) - goto err; - - dev_dbg(dev, "IR enabled"); - - status = devm_rc_register_device(dev, cec->ir); - - if (status) { - dev_err(dev, "Failed to prepare input device"); - cec->ir = NULL; - goto err; - } - - return 0; - -err: - smb_rd16(SECOCEC_ENABLE_REG_1, &val); - - smb_wr16(SECOCEC_ENABLE_REG_1, - val & ~SECOCEC_ENABLE_REG_1_IR); - - dev_dbg(dev, "IR disabled"); - return status; -} - -static int secocec_ir_rx(struct secocec_data *priv) -{ - struct secocec_data *cec = priv; - struct device *dev = cec->dev; - u16 val, status, key, addr, toggle; - - if (!cec->ir) - return -ENODEV; - - status = smb_rd16(SECOCEC_IR_READ_DATA, &val); - if (status != 0) - goto err; - - key = val & SECOCEC_IR_COMMAND_MASK; - addr = (val & SECOCEC_IR_ADDRESS_MASK) >> SECOCEC_IR_ADDRESS_SHL; - toggle = (val & SECOCEC_IR_TOGGLE_MASK) >> SECOCEC_IR_TOGGLE_SHL; - - rc_keydown(cec->ir, RC_PROTO_RC5, RC_SCANCODE_RC5(addr, key), toggle); - - dev_dbg(dev, "IR key pressed: 0x%02x addr 0x%02x toggle 0x%02x", key, - addr, toggle); - - return 0; - -err: - dev_err(dev, "IR Receive message failed (%d)", status); - return -EIO; -} -#else -static void secocec_ir_rx(struct secocec_data *priv) -{ -} - -static int secocec_ir_probe(void *priv) -{ - return 0; -} -#endif - -static irqreturn_t secocec_irq_handler(int irq, void *priv) -{ - struct secocec_data *cec = priv; - struct device *dev = cec->dev; - u16 status_val, cec_val, val = 0; - int status; - - /* Read status register */ - status = smb_rd16(SECOCEC_STATUS_REG_1, &status_val); - if (status) - goto err; - - if (status_val & SECOCEC_STATUS_REG_1_CEC) { - /* Read CEC status register */ - status = smb_rd16(SECOCEC_STATUS, &cec_val); - if (status) - goto err; - - if (cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK) - secocec_rx_done(cec->cec_adap, cec_val); - - if (cec_val & SECOCEC_STATUS_MSG_SENT_MASK) - secocec_tx_done(cec->cec_adap, cec_val); - - if ((~cec_val & SECOCEC_STATUS_MSG_SENT_MASK) && - (~cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK)) - dev_warn_once(dev, - "Message not received or sent, but interrupt fired"); - - val = SECOCEC_STATUS_REG_1_CEC; - } - - if (status_val & SECOCEC_STATUS_REG_1_IR) { - val |= SECOCEC_STATUS_REG_1_IR; - - secocec_ir_rx(cec); - } - - /* Reset status register */ - status = smb_wr16(SECOCEC_STATUS_REG_1, val); - if (status) - goto err; - - return IRQ_HANDLED; - -err: - dev_err_once(dev, "IRQ: R/W SMBus operation failed (%d)", status); - - /* Reset status register */ - val = SECOCEC_STATUS_REG_1_CEC | SECOCEC_STATUS_REG_1_IR; - smb_wr16(SECOCEC_STATUS_REG_1, val); - - return IRQ_HANDLED; -} - -struct cec_dmi_match { - const char *sys_vendor; - const char *product_name; - const char *devname; - const char *conn; -}; - -static const struct cec_dmi_match secocec_dmi_match_table[] = { - /* UDOO X86 */ - { "SECO", "UDOO x86", "0000:00:02.0", "Port B" }, -}; - -static struct device *secocec_cec_find_hdmi_dev(struct device *dev, - const char **conn) -{ - int i; - - for (i = 0 ; i < ARRAY_SIZE(secocec_dmi_match_table) ; ++i) { - const struct cec_dmi_match *m = &secocec_dmi_match_table[i]; - - if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) && - dmi_match(DMI_PRODUCT_NAME, m->product_name)) { - struct device *d; - - /* Find the device, bail out if not yet registered */ - d = bus_find_device_by_name(&pci_bus_type, NULL, - m->devname); - if (!d) - return ERR_PTR(-EPROBE_DEFER); - - put_device(d); - *conn = m->conn; - return d; - } - } - - return ERR_PTR(-EINVAL); -} - -static int secocec_acpi_probe(struct secocec_data *sdev) -{ - struct device *dev = sdev->dev; - struct gpio_desc *gpio; - int irq = 0; - - gpio = devm_gpiod_get(dev, NULL, GPIOF_IN); - if (IS_ERR(gpio)) { - dev_err(dev, "Cannot request interrupt gpio"); - return PTR_ERR(gpio); - } - - irq = gpiod_to_irq(gpio); - if (irq < 0) { - dev_err(dev, "Cannot find valid irq"); - return -ENODEV; - } - dev_dbg(dev, "irq-gpio is bound to IRQ %d", irq); - - sdev->irq = irq; - - return 0; -} - -static int secocec_probe(struct platform_device *pdev) -{ - struct secocec_data *secocec; - struct device *dev = &pdev->dev; - struct device *hdmi_dev; - const char *conn = NULL; - int ret; - u16 val; - - hdmi_dev = secocec_cec_find_hdmi_dev(&pdev->dev, &conn); - if (IS_ERR(hdmi_dev)) - return PTR_ERR(hdmi_dev); - - secocec = devm_kzalloc(dev, sizeof(*secocec), GFP_KERNEL); - if (!secocec) - return -ENOMEM; - - dev_set_drvdata(dev, secocec); - - /* Request SMBus regions */ - if (!request_muxed_region(BRA_SMB_BASE_ADDR, 7, "CEC00001")) { - dev_err(dev, "Request memory region failed"); - return -ENXIO; - } - - secocec->pdev = pdev; - secocec->dev = dev; - - if (!has_acpi_companion(dev)) { - dev_dbg(dev, "Cannot find any ACPI companion"); - ret = -ENODEV; - goto err; - } - - ret = secocec_acpi_probe(secocec); - if (ret) { - dev_err(dev, "Cannot assign gpio to IRQ"); - ret = -ENODEV; - goto err; - } - - /* Firmware version check */ - ret = smb_rd16(SECOCEC_VERSION, &val); - if (ret) { - dev_err(dev, "Cannot check fw version"); - goto err; - } - if (val < SECOCEC_LATEST_FW) { - dev_err(dev, "CEC Firmware not supported (v.%04x). Use ver > v.%04x", - val, SECOCEC_LATEST_FW); - ret = -EINVAL; - goto err; - } - - ret = devm_request_threaded_irq(dev, - secocec->irq, - NULL, - secocec_irq_handler, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - dev_name(&pdev->dev), secocec); - - if (ret) { - dev_err(dev, "Cannot request IRQ %d", secocec->irq); - ret = -EIO; - goto err; - } - - /* Allocate CEC adapter */ - secocec->cec_adap = cec_allocate_adapter(&secocec_cec_adap_ops, - secocec, - dev_name(dev), - CEC_CAP_DEFAULTS | - CEC_CAP_CONNECTOR_INFO, - SECOCEC_MAX_ADDRS); - - if (IS_ERR(secocec->cec_adap)) { - ret = PTR_ERR(secocec->cec_adap); - goto err; - } - - secocec->notifier = cec_notifier_cec_adap_register(hdmi_dev, conn, - secocec->cec_adap); - if (!secocec->notifier) { - ret = -ENOMEM; - goto err_delete_adapter; - } - - ret = cec_register_adapter(secocec->cec_adap, dev); - if (ret) - goto err_notifier; - - ret = secocec_ir_probe(secocec); - if (ret) - goto err_notifier; - - platform_set_drvdata(pdev, secocec); - - dev_dbg(dev, "Device registered"); - - return ret; - -err_notifier: - cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap); -err_delete_adapter: - cec_delete_adapter(secocec->cec_adap); -err: - release_region(BRA_SMB_BASE_ADDR, 7); - dev_err(dev, "%s device probe failed\n", dev_name(dev)); - - return ret; -} - -static int secocec_remove(struct platform_device *pdev) -{ - struct secocec_data *secocec = platform_get_drvdata(pdev); - u16 val; - - if (secocec->ir) { - smb_rd16(SECOCEC_ENABLE_REG_1, &val); - - smb_wr16(SECOCEC_ENABLE_REG_1, val & ~SECOCEC_ENABLE_REG_1_IR); - - dev_dbg(&pdev->dev, "IR disabled"); - } - cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap); - cec_unregister_adapter(secocec->cec_adap); - - release_region(BRA_SMB_BASE_ADDR, 7); - - dev_dbg(&pdev->dev, "CEC device removed"); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int secocec_suspend(struct device *dev) -{ - int status; - u16 val; - - dev_dbg(dev, "Device going to suspend, disabling"); - - /* Clear the status register */ - status = smb_rd16(SECOCEC_STATUS_REG_1, &val); - if (status) - goto err; - - status = smb_wr16(SECOCEC_STATUS_REG_1, val); - if (status) - goto err; - - /* Disable the interrupts */ - status = smb_rd16(SECOCEC_ENABLE_REG_1, &val); - if (status) - goto err; - - status = smb_wr16(SECOCEC_ENABLE_REG_1, val & - ~SECOCEC_ENABLE_REG_1_CEC & ~SECOCEC_ENABLE_REG_1_IR); - if (status) - goto err; - - return 0; - -err: - dev_err(dev, "Suspend failed (err: %d)", status); - return status; -} - -static int secocec_resume(struct device *dev) -{ - int status; - u16 val; - - dev_dbg(dev, "Resuming device from suspend"); - - /* Clear the status register */ - status = smb_rd16(SECOCEC_STATUS_REG_1, &val); - if (status) - goto err; - - status = smb_wr16(SECOCEC_STATUS_REG_1, val); - if (status) - goto err; - - /* Enable the interrupts */ - status = smb_rd16(SECOCEC_ENABLE_REG_1, &val); - if (status) - goto err; - - status = smb_wr16(SECOCEC_ENABLE_REG_1, val | SECOCEC_ENABLE_REG_1_CEC); - if (status) - goto err; - - dev_dbg(dev, "Device resumed from suspend"); - - return 0; - -err: - dev_err(dev, "Resume failed (err: %d)", status); - return status; -} - -static SIMPLE_DEV_PM_OPS(secocec_pm_ops, secocec_suspend, secocec_resume); -#define SECOCEC_PM_OPS (&secocec_pm_ops) -#else -#define SECOCEC_PM_OPS NULL -#endif - -#ifdef CONFIG_ACPI -static const struct acpi_device_id secocec_acpi_match[] = { - {"CEC00001", 0}, - {}, -}; - -MODULE_DEVICE_TABLE(acpi, secocec_acpi_match); -#endif - -static struct platform_driver secocec_driver = { - .driver = { - .name = SECOCEC_DEV_NAME, - .acpi_match_table = ACPI_PTR(secocec_acpi_match), - .pm = SECOCEC_PM_OPS, - }, - .probe = secocec_probe, - .remove = secocec_remove, -}; - -module_platform_driver(secocec_driver); - -MODULE_DESCRIPTION("SECO CEC X86 Driver"); -MODULE_AUTHOR("Ettore Chimenti <ek5.chimenti@gmail.com>"); -MODULE_LICENSE("Dual BSD/GPL"); |