// SPDX-License-Identifier: GPL-2.0+ /* * CEC driver for ChromeOS Embedded Controller * * Copyright (c) 2018 BayLibre, SAS * Author: Neil Armstrong */ #include #include #include #include #include #include #include #include #include #include #include #include #define DRV_NAME "cros-ec-cec" /** * struct cros_ec_cec_port - Driver data for a single EC CEC port * * @port_num: port number * @adap: CEC adapter * @notify: CEC notifier pointer * @rx_msg: storage for a received message * @cros_ec_cec: pointer to the parent struct */ struct cros_ec_cec_port { int port_num; struct cec_adapter *adap; struct cec_notifier *notify; struct cec_msg rx_msg; struct cros_ec_cec *cros_ec_cec; }; /** * struct cros_ec_cec - Driver data for EC CEC * * @cros_ec: Pointer to EC device * @notifier: Notifier info for responding to EC events * @write_cmd_version: Highest supported version of EC_CMD_CEC_WRITE_MSG. * @num_ports: Number of CEC ports * @ports: Array of ports */ struct cros_ec_cec { struct cros_ec_device *cros_ec; struct notifier_block notifier; int write_cmd_version; int num_ports; struct cros_ec_cec_port *ports[EC_CEC_MAX_PORTS]; }; static void cros_ec_cec_received_message(struct cros_ec_cec_port *port, uint8_t *msg, uint8_t len) { if (len > CEC_MAX_MSG_SIZE) len = CEC_MAX_MSG_SIZE; port->rx_msg.len = len; memcpy(port->rx_msg.msg, msg, len); cec_received_msg(port->adap, &port->rx_msg); } static void handle_cec_message(struct cros_ec_cec *cros_ec_cec) { struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; uint8_t *cec_message = cros_ec->event_data.data.cec_message; unsigned int len = cros_ec->event_size; struct cros_ec_cec_port *port; /* * There are two ways of receiving CEC messages: * 1. Old EC firmware which only supports one port sends the data in a * cec_message MKBP event. * 2. New EC firmware which supports multiple ports uses * EC_MKBP_CEC_HAVE_DATA to notify that data is ready and * EC_CMD_CEC_READ_MSG to read it. * Check that the EC only has one CEC port, and then we can assume the * message is from port 0. */ if (cros_ec_cec->num_ports != 1) { dev_err(cros_ec->dev, "received cec_message on device with %d ports\n", cros_ec_cec->num_ports); return; } port = cros_ec_cec->ports[0]; cros_ec_cec_received_message(port, cec_message, len); } static void cros_ec_cec_read_message(struct cros_ec_cec_port *port) { struct cros_ec_device *cros_ec = port->cros_ec_cec->cros_ec; struct ec_params_cec_read params = { .port = port->port_num, }; struct ec_response_cec_read response; int ret; ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_READ_MSG, ¶ms, sizeof(params), &response, sizeof(response)); if (ret < 0) { dev_err(cros_ec->dev, "error reading CEC message on EC: %d\n", ret); return; } cros_ec_cec_received_message(port, response.msg, response.msg_len); } static void handle_cec_event(struct cros_ec_cec *cros_ec_cec) { struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; uint32_t cec_events = cros_ec->event_data.data.cec_events; uint32_t port_num = EC_MKBP_EVENT_CEC_GET_PORT(cec_events); uint32_t events = EC_MKBP_EVENT_CEC_GET_EVENTS(cec_events); struct cros_ec_cec_port *port; if (port_num >= cros_ec_cec->num_ports) { dev_err(cros_ec->dev, "received CEC event for invalid port %d\n", port_num); return; } port = cros_ec_cec->ports[port_num]; if (events & EC_MKBP_CEC_SEND_OK) cec_transmit_attempt_done(port->adap, CEC_TX_STATUS_OK); /* FW takes care of all retries, tell core to avoid more retries */ if (events & EC_MKBP_CEC_SEND_FAILED) cec_transmit_attempt_done(port->adap, CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_NACK); if (events & EC_MKBP_CEC_HAVE_DATA) cros_ec_cec_read_message(port); } static int cros_ec_cec_event(struct notifier_block *nb, unsigned long queued_during_suspend, void *_notify) { struct cros_ec_cec *cros_ec_cec; struct cros_ec_device *cros_ec; cros_ec_cec = container_of(nb, struct cros_ec_cec, notifier); cros_ec = cros_ec_cec->cros_ec; if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_EVENT) { handle_cec_event(cros_ec_cec); return NOTIFY_OK; } if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_MESSAGE) { handle_cec_message(cros_ec_cec); return NOTIFY_OK; } return NOTIFY_DONE; } static int cros_ec_cec_set_log_addr(struct cec_adapter *adap, u8 logical_addr) { struct cros_ec_cec_port *port = adap->priv; struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec; struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; struct ec_params_cec_set params = { .cmd = CEC_CMD_LOGICAL_ADDRESS, .port = port->port_num, .val = logical_addr, }; int ret; ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_SET, ¶ms, sizeof(params), NULL, 0); if (ret < 0) { dev_err(cros_ec->dev, "error setting CEC logical address on EC: %d\n", ret); return ret; } return 0; } static int cros_ec_cec_transmit(struct cec_adapter *adap, u8 attempts, u32 signal_free_time, struct cec_msg *cec_msg) { struct cros_ec_cec_port *port = adap->priv; struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec; struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; struct ec_params_cec_write params; struct ec_params_cec_write_v1 params_v1; int ret; if (cros_ec_cec->write_cmd_version == 0) { memcpy(params.msg, cec_msg->msg, cec_msg->len); ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_WRITE_MSG, ¶ms, cec_msg->len, NULL, 0); } else { params_v1.port = port->port_num; params_v1.msg_len = cec_msg->len; memcpy(params_v1.msg, cec_msg->msg, cec_msg->len); ret = cros_ec_cmd(cros_ec, cros_ec_cec->write_cmd_version, EC_CMD_CEC_WRITE_MSG, ¶ms_v1, sizeof(params_v1), NULL, 0); } if (ret < 0) { dev_err(cros_ec->dev, "error writing CEC msg on EC: %d\n", ret); return ret; } return 0; } static int cros_ec_cec_adap_enable(struct cec_adapter *adap, bool enable) { struct cros_ec_cec_port *port = adap->priv; struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec; struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; struct ec_params_cec_set params = { .cmd = CEC_CMD_ENABLE, .port = port->port_num, .val = enable, }; int ret; ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_SET, ¶ms, sizeof(params), NULL, 0); if (ret < 0) { dev_err(cros_ec->dev, "error %sabling CEC on EC: %d\n", (enable ? "en" : "dis"), ret); return ret; } return 0; } static const struct cec_adap_ops cros_ec_cec_ops = { .adap_enable = cros_ec_cec_adap_enable, .adap_log_addr = cros_ec_cec_set_log_addr, .adap_transmit = cros_ec_cec_transmit, }; #ifdef CONFIG_PM_SLEEP static int cros_ec_cec_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev); if (device_may_wakeup(dev)) enable_irq_wake(cros_ec_cec->cros_ec->irq); return 0; } static int cros_ec_cec_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev); if (device_may_wakeup(dev)) disable_irq_wake(cros_ec_cec->cros_ec->irq); return 0; } #endif static SIMPLE_DEV_PM_OPS(cros_ec_cec_pm_ops, cros_ec_cec_suspend, cros_ec_cec_resume); #if IS_ENABLED(CONFIG_PCI) && IS_ENABLED(CONFIG_DMI) /* * Specify the DRM device name handling the HDMI output and the HDMI connector * corresponding to each CEC port. The order of connectors must match the order * in the EC (first connector is EC port 0, ...), and the number of connectors * must match the number of ports in the EC (which can be queried using the * EC_CMD_CEC_PORT_COUNT host command). */ struct cec_dmi_match { const char *sys_vendor; const char *product_name; const char *devname; const char *const *conns; }; static const char *const port_b_conns[] = { "Port B", NULL }; static const char *const port_db_conns[] = { "Port D", "Port B", NULL }; static const char *const port_ba_conns[] = { "Port B", "Port A", NULL }; static const char *const port_d_conns[] = { "Port D", NULL }; static const struct cec_dmi_match cec_dmi_match_table[] = { /* Google Fizz */ { "Google", "Fizz", "0000:00:02.0", port_b_conns }, /* Google Brask */ { "Google", "Brask", "0000:00:02.0", port_b_conns }, /* Google Moli */ { "Google", "Moli", "0000:00:02.0", port_b_conns }, /* Google Kinox */ { "Google", "Kinox", "0000:00:02.0", port_b_conns }, /* Google Kuldax */ { "Google", "Kuldax", "0000:00:02.0", port_b_conns }, /* Google Aurash */ { "Google", "Aurash", "0000:00:02.0", port_b_conns }, /* Google Gladios */ { "Google", "Gladios", "0000:00:02.0", port_b_conns }, /* Google Lisbon */ { "Google", "Lisbon", "0000:00:02.0", port_b_conns }, /* Google Dibbi */ { "Google", "Dibbi", "0000:00:02.0", port_db_conns }, /* Google Constitution */ { "Google", "Constitution", "0000:00:02.0", port_ba_conns }, /* Google Boxy */ { "Google", "Boxy", "0000:00:02.0", port_d_conns }, /* Google Taranza */ { "Google", "Taranza", "0000:00:02.0", port_db_conns }, }; static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev, const char * const **conns) { int i; for (i = 0 ; i < ARRAY_SIZE(cec_dmi_match_table) ; ++i) { const struct cec_dmi_match *m = &cec_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); *conns = m->conns; return d; } } /* Hardware support must be added in the cec_dmi_match_table */ dev_warn(dev, "CEC notifier not configured for this hardware\n"); return ERR_PTR(-ENODEV); } #else static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev, const char * const **conns) { return ERR_PTR(-ENODEV); } #endif static int cros_ec_cec_get_num_ports(struct cros_ec_cec *cros_ec_cec) { struct ec_response_cec_port_count response; int ret; ret = cros_ec_cmd(cros_ec_cec->cros_ec, 0, EC_CMD_CEC_PORT_COUNT, NULL, 0, &response, sizeof(response)); if (ret < 0) { /* * Old EC firmware only supports one port and does not support * the port count command, so fall back to assuming one port. */ cros_ec_cec->num_ports = 1; return 0; } if (response.port_count == 0) { dev_err(cros_ec_cec->cros_ec->dev, "EC reports 0 CEC ports\n"); return -ENODEV; } if (response.port_count > EC_CEC_MAX_PORTS) { dev_err(cros_ec_cec->cros_ec->dev, "EC reports too many ports: %d\n", response.port_count); return -EINVAL; } cros_ec_cec->num_ports = response.port_count; return 0; } static int cros_ec_cec_get_write_cmd_version(struct cros_ec_cec *cros_ec_cec) { struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; struct ec_params_get_cmd_versions_v1 params = { .cmd = EC_CMD_CEC_WRITE_MSG, }; struct ec_response_get_cmd_versions response; int ret; ret = cros_ec_cmd(cros_ec, 1, EC_CMD_GET_CMD_VERSIONS, ¶ms, sizeof(params), &response, sizeof(response)); if (ret < 0) { dev_err(cros_ec->dev, "error getting CEC write command version: %d\n", ret); return ret; } if (response.version_mask & EC_VER_MASK(1)) { cros_ec_cec->write_cmd_version = 1; } else { if (cros_ec_cec->num_ports != 1) { dev_err(cros_ec->dev, "v0 write command only supports 1 port, %d reported\n", cros_ec_cec->num_ports); return -EINVAL; } cros_ec_cec->write_cmd_version = 0; } return 0; } static int cros_ec_cec_init_port(struct device *dev, struct cros_ec_cec *cros_ec_cec, int port_num, struct device *hdmi_dev, const char * const *conns) { struct cros_ec_cec_port *port; int ret; port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); if (!port) return -ENOMEM; port->cros_ec_cec = cros_ec_cec; port->port_num = port_num; port->adap = cec_allocate_adapter(&cros_ec_cec_ops, port, DRV_NAME, CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO, 1); if (IS_ERR(port->adap)) return PTR_ERR(port->adap); if (!conns[port_num]) { dev_err(dev, "no conn for port %d\n", port_num); ret = -ENODEV; goto out_probe_adapter; } port->notify = cec_notifier_cec_adap_register(hdmi_dev, conns[port_num], port->adap); if (!port->notify) { ret = -ENOMEM; goto out_probe_adapter; } ret = cec_register_adapter(port->adap, dev); if (ret < 0) goto out_probe_notify; cros_ec_cec->ports[port_num] = port; return 0; out_probe_notify: cec_notifier_cec_adap_unregister(port->notify, port->adap); out_probe_adapter: cec_delete_adapter(port->adap); return ret; } static int cros_ec_cec_probe(struct platform_device *pdev) { struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent); struct cros_ec_device *cros_ec = ec_dev->ec_dev; struct cros_ec_cec *cros_ec_cec; struct cros_ec_cec_port *port; struct device *hdmi_dev; const char * const *conns = NULL; int ret; hdmi_dev = cros_ec_cec_find_hdmi_dev(&pdev->dev, &conns); if (IS_ERR(hdmi_dev)) return PTR_ERR(hdmi_dev); cros_ec_cec = devm_kzalloc(&pdev->dev, sizeof(*cros_ec_cec), GFP_KERNEL); if (!cros_ec_cec) return -ENOMEM; platform_set_drvdata(pdev, cros_ec_cec); cros_ec_cec->cros_ec = cros_ec; device_init_wakeup(&pdev->dev, 1); ret = cros_ec_cec_get_num_ports(cros_ec_cec); if (ret) return ret; ret = cros_ec_cec_get_write_cmd_version(cros_ec_cec); if (ret) return ret; for (int i = 0; i < cros_ec_cec->num_ports; i++) { ret = cros_ec_cec_init_port(&pdev->dev, cros_ec_cec, i, hdmi_dev, conns); if (ret) goto unregister_ports; } /* Get CEC events from the EC. */ cros_ec_cec->notifier.notifier_call = cros_ec_cec_event; ret = blocking_notifier_chain_register(&cros_ec->event_notifier, &cros_ec_cec->notifier); if (ret) { dev_err(&pdev->dev, "failed to register notifier\n"); goto unregister_ports; } return 0; unregister_ports: /* * Unregister any adapters which have been registered. We don't add the * port to the array until the adapter has been registered successfully, * so any non-NULL ports must have been registered. */ for (int i = 0; i < cros_ec_cec->num_ports; i++) { port = cros_ec_cec->ports[i]; if (!port) break; cec_notifier_cec_adap_unregister(port->notify, port->adap); cec_unregister_adapter(port->adap); } return ret; } static void cros_ec_cec_remove(struct platform_device *pdev) { struct cros_ec_cec *cros_ec_cec = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; struct cros_ec_cec_port *port; int ret; /* * blocking_notifier_chain_unregister() only fails if the notifier isn't * in the list. We know it was added to it by .probe(), so there should * be no need for error checking. Be cautious and still check. */ ret = blocking_notifier_chain_unregister( &cros_ec_cec->cros_ec->event_notifier, &cros_ec_cec->notifier); if (ret) dev_err(dev, "failed to unregister notifier\n"); for (int i = 0; i < cros_ec_cec->num_ports; i++) { port = cros_ec_cec->ports[i]; cec_notifier_cec_adap_unregister(port->notify, port->adap); cec_unregister_adapter(port->adap); } } static struct platform_driver cros_ec_cec_driver = { .probe = cros_ec_cec_probe, .remove_new = cros_ec_cec_remove, .driver = { .name = DRV_NAME, .pm = &cros_ec_cec_pm_ops, }, }; module_platform_driver(cros_ec_cec_driver); MODULE_DESCRIPTION("CEC driver for ChromeOS ECs"); MODULE_AUTHOR("Neil Armstrong "); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:" DRV_NAME);