linux/drivers/media/cec/platform/cros-ec/cros-ec-cec.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * CEC driver for ChromeOS Embedded Controller
   4 *
   5 * Copyright (c) 2018 BayLibre, SAS
   6 * Author: Neil Armstrong <narmstrong@baylibre.com>
   7 */
   8
   9#include <linux/kernel.h>
  10#include <linux/module.h>
  11#include <linux/platform_device.h>
  12#include <linux/dmi.h>
  13#include <linux/pci.h>
  14#include <linux/cec.h>
  15#include <linux/slab.h>
  16#include <linux/interrupt.h>
  17#include <linux/platform_data/cros_ec_commands.h>
  18#include <linux/platform_data/cros_ec_proto.h>
  19#include <media/cec.h>
  20#include <media/cec-notifier.h>
  21
  22#define DRV_NAME        "cros-ec-cec"
  23
  24/**
  25 * struct cros_ec_cec - Driver data for EC CEC
  26 *
  27 * @cros_ec: Pointer to EC device
  28 * @notifier: Notifier info for responding to EC events
  29 * @adap: CEC adapter
  30 * @notify: CEC notifier pointer
  31 * @rx_msg: storage for a received message
  32 */
  33struct cros_ec_cec {
  34        struct cros_ec_device *cros_ec;
  35        struct notifier_block notifier;
  36        struct cec_adapter *adap;
  37        struct cec_notifier *notify;
  38        struct cec_msg rx_msg;
  39};
  40
  41static void handle_cec_message(struct cros_ec_cec *cros_ec_cec)
  42{
  43        struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
  44        uint8_t *cec_message = cros_ec->event_data.data.cec_message;
  45        unsigned int len = cros_ec->event_size;
  46
  47        cros_ec_cec->rx_msg.len = len;
  48        memcpy(cros_ec_cec->rx_msg.msg, cec_message, len);
  49
  50        cec_received_msg(cros_ec_cec->adap, &cros_ec_cec->rx_msg);
  51}
  52
  53static void handle_cec_event(struct cros_ec_cec *cros_ec_cec)
  54{
  55        struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
  56        uint32_t events = cros_ec->event_data.data.cec_events;
  57
  58        if (events & EC_MKBP_CEC_SEND_OK)
  59                cec_transmit_attempt_done(cros_ec_cec->adap,
  60                                          CEC_TX_STATUS_OK);
  61
  62        /* FW takes care of all retries, tell core to avoid more retries */
  63        if (events & EC_MKBP_CEC_SEND_FAILED)
  64                cec_transmit_attempt_done(cros_ec_cec->adap,
  65                                          CEC_TX_STATUS_MAX_RETRIES |
  66                                          CEC_TX_STATUS_NACK);
  67}
  68
  69static int cros_ec_cec_event(struct notifier_block *nb,
  70                             unsigned long queued_during_suspend,
  71                             void *_notify)
  72{
  73        struct cros_ec_cec *cros_ec_cec;
  74        struct cros_ec_device *cros_ec;
  75
  76        cros_ec_cec = container_of(nb, struct cros_ec_cec, notifier);
  77        cros_ec = cros_ec_cec->cros_ec;
  78
  79        if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_EVENT) {
  80                handle_cec_event(cros_ec_cec);
  81                return NOTIFY_OK;
  82        }
  83
  84        if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_MESSAGE) {
  85                handle_cec_message(cros_ec_cec);
  86                return NOTIFY_OK;
  87        }
  88
  89        return NOTIFY_DONE;
  90}
  91
  92static int cros_ec_cec_set_log_addr(struct cec_adapter *adap, u8 logical_addr)
  93{
  94        struct cros_ec_cec *cros_ec_cec = adap->priv;
  95        struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
  96        struct {
  97                struct cros_ec_command msg;
  98                struct ec_params_cec_set data;
  99        } __packed msg = {};
 100        int ret;
 101
 102        msg.msg.command = EC_CMD_CEC_SET;
 103        msg.msg.outsize = sizeof(msg.data);
 104        msg.data.cmd = CEC_CMD_LOGICAL_ADDRESS;
 105        msg.data.val = logical_addr;
 106
 107        ret = cros_ec_cmd_xfer_status(cros_ec, &msg.msg);
 108        if (ret < 0) {
 109                dev_err(cros_ec->dev,
 110                        "error setting CEC logical address on EC: %d\n", ret);
 111                return ret;
 112        }
 113
 114        return 0;
 115}
 116
 117static int cros_ec_cec_transmit(struct cec_adapter *adap, u8 attempts,
 118                                u32 signal_free_time, struct cec_msg *cec_msg)
 119{
 120        struct cros_ec_cec *cros_ec_cec = adap->priv;
 121        struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
 122        struct {
 123                struct cros_ec_command msg;
 124                struct ec_params_cec_write data;
 125        } __packed msg = {};
 126        int ret;
 127
 128        msg.msg.command = EC_CMD_CEC_WRITE_MSG;
 129        msg.msg.outsize = cec_msg->len;
 130        memcpy(msg.data.msg, cec_msg->msg, cec_msg->len);
 131
 132        ret = cros_ec_cmd_xfer_status(cros_ec, &msg.msg);
 133        if (ret < 0) {
 134                dev_err(cros_ec->dev,
 135                        "error writing CEC msg on EC: %d\n", ret);
 136                return ret;
 137        }
 138
 139        return 0;
 140}
 141
 142static int cros_ec_cec_adap_enable(struct cec_adapter *adap, bool enable)
 143{
 144        struct cros_ec_cec *cros_ec_cec = adap->priv;
 145        struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
 146        struct {
 147                struct cros_ec_command msg;
 148                struct ec_params_cec_set data;
 149        } __packed msg = {};
 150        int ret;
 151
 152        msg.msg.command = EC_CMD_CEC_SET;
 153        msg.msg.outsize = sizeof(msg.data);
 154        msg.data.cmd = CEC_CMD_ENABLE;
 155        msg.data.val = enable;
 156
 157        ret = cros_ec_cmd_xfer_status(cros_ec, &msg.msg);
 158        if (ret < 0) {
 159                dev_err(cros_ec->dev,
 160                        "error %sabling CEC on EC: %d\n",
 161                        (enable ? "en" : "dis"), ret);
 162                return ret;
 163        }
 164
 165        return 0;
 166}
 167
 168static const struct cec_adap_ops cros_ec_cec_ops = {
 169        .adap_enable = cros_ec_cec_adap_enable,
 170        .adap_log_addr = cros_ec_cec_set_log_addr,
 171        .adap_transmit = cros_ec_cec_transmit,
 172};
 173
 174#ifdef CONFIG_PM_SLEEP
 175static int cros_ec_cec_suspend(struct device *dev)
 176{
 177        struct platform_device *pdev = to_platform_device(dev);
 178        struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev);
 179
 180        if (device_may_wakeup(dev))
 181                enable_irq_wake(cros_ec_cec->cros_ec->irq);
 182
 183        return 0;
 184}
 185
 186static int cros_ec_cec_resume(struct device *dev)
 187{
 188        struct platform_device *pdev = to_platform_device(dev);
 189        struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev);
 190
 191        if (device_may_wakeup(dev))
 192                disable_irq_wake(cros_ec_cec->cros_ec->irq);
 193
 194        return 0;
 195}
 196#endif
 197
 198static SIMPLE_DEV_PM_OPS(cros_ec_cec_pm_ops,
 199        cros_ec_cec_suspend, cros_ec_cec_resume);
 200
 201#if IS_ENABLED(CONFIG_PCI) && IS_ENABLED(CONFIG_DMI)
 202
 203/*
 204 * The Firmware only handles a single CEC interface tied to a single HDMI
 205 * connector we specify along with the DRM device name handling the HDMI output
 206 */
 207
 208struct cec_dmi_match {
 209        const char *sys_vendor;
 210        const char *product_name;
 211        const char *devname;
 212        const char *conn;
 213};
 214
 215static const struct cec_dmi_match cec_dmi_match_table[] = {
 216        /* Google Fizz */
 217        { "Google", "Fizz", "0000:00:02.0", "Port B" },
 218};
 219
 220static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev,
 221                                                const char **conn)
 222{
 223        int i;
 224
 225        for (i = 0 ; i < ARRAY_SIZE(cec_dmi_match_table) ; ++i) {
 226                const struct cec_dmi_match *m = &cec_dmi_match_table[i];
 227
 228                if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) &&
 229                    dmi_match(DMI_PRODUCT_NAME, m->product_name)) {
 230                        struct device *d;
 231
 232                        /* Find the device, bail out if not yet registered */
 233                        d = bus_find_device_by_name(&pci_bus_type, NULL,
 234                                                    m->devname);
 235                        if (!d)
 236                                return ERR_PTR(-EPROBE_DEFER);
 237                        put_device(d);
 238                        *conn = m->conn;
 239                        return d;
 240                }
 241        }
 242
 243        /* Hardware support must be added in the cec_dmi_match_table */
 244        dev_warn(dev, "CEC notifier not configured for this hardware\n");
 245
 246        return ERR_PTR(-ENODEV);
 247}
 248
 249#else
 250
 251static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev,
 252                                                const char **conn)
 253{
 254        return ERR_PTR(-ENODEV);
 255}
 256
 257#endif
 258
 259static int cros_ec_cec_probe(struct platform_device *pdev)
 260{
 261        struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent);
 262        struct cros_ec_device *cros_ec = ec_dev->ec_dev;
 263        struct cros_ec_cec *cros_ec_cec;
 264        struct device *hdmi_dev;
 265        const char *conn = NULL;
 266        int ret;
 267
 268        hdmi_dev = cros_ec_cec_find_hdmi_dev(&pdev->dev, &conn);
 269        if (IS_ERR(hdmi_dev))
 270                return PTR_ERR(hdmi_dev);
 271
 272        cros_ec_cec = devm_kzalloc(&pdev->dev, sizeof(*cros_ec_cec),
 273                                   GFP_KERNEL);
 274        if (!cros_ec_cec)
 275                return -ENOMEM;
 276
 277        platform_set_drvdata(pdev, cros_ec_cec);
 278        cros_ec_cec->cros_ec = cros_ec;
 279
 280        device_init_wakeup(&pdev->dev, 1);
 281
 282        cros_ec_cec->adap = cec_allocate_adapter(&cros_ec_cec_ops, cros_ec_cec,
 283                                                 DRV_NAME,
 284                                                 CEC_CAP_DEFAULTS |
 285                                                 CEC_CAP_CONNECTOR_INFO, 1);
 286        if (IS_ERR(cros_ec_cec->adap))
 287                return PTR_ERR(cros_ec_cec->adap);
 288
 289        cros_ec_cec->notify = cec_notifier_cec_adap_register(hdmi_dev, conn,
 290                                                             cros_ec_cec->adap);
 291        if (!cros_ec_cec->notify) {
 292                ret = -ENOMEM;
 293                goto out_probe_adapter;
 294        }
 295
 296        /* Get CEC events from the EC. */
 297        cros_ec_cec->notifier.notifier_call = cros_ec_cec_event;
 298        ret = blocking_notifier_chain_register(&cros_ec->event_notifier,
 299                                               &cros_ec_cec->notifier);
 300        if (ret) {
 301                dev_err(&pdev->dev, "failed to register notifier\n");
 302                goto out_probe_notify;
 303        }
 304
 305        ret = cec_register_adapter(cros_ec_cec->adap, &pdev->dev);
 306        if (ret < 0)
 307                goto out_probe_notify;
 308
 309        return 0;
 310
 311out_probe_notify:
 312        cec_notifier_cec_adap_unregister(cros_ec_cec->notify,
 313                                         cros_ec_cec->adap);
 314out_probe_adapter:
 315        cec_delete_adapter(cros_ec_cec->adap);
 316        return ret;
 317}
 318
 319static int cros_ec_cec_remove(struct platform_device *pdev)
 320{
 321        struct cros_ec_cec *cros_ec_cec = platform_get_drvdata(pdev);
 322        struct device *dev = &pdev->dev;
 323        int ret;
 324
 325        ret = blocking_notifier_chain_unregister(
 326                        &cros_ec_cec->cros_ec->event_notifier,
 327                        &cros_ec_cec->notifier);
 328
 329        if (ret) {
 330                dev_err(dev, "failed to unregister notifier\n");
 331                return ret;
 332        }
 333
 334        cec_notifier_cec_adap_unregister(cros_ec_cec->notify,
 335                                         cros_ec_cec->adap);
 336        cec_unregister_adapter(cros_ec_cec->adap);
 337
 338        return 0;
 339}
 340
 341static struct platform_driver cros_ec_cec_driver = {
 342        .probe = cros_ec_cec_probe,
 343        .remove  = cros_ec_cec_remove,
 344        .driver = {
 345                .name = DRV_NAME,
 346                .pm = &cros_ec_cec_pm_ops,
 347        },
 348};
 349
 350module_platform_driver(cros_ec_cec_driver);
 351
 352MODULE_DESCRIPTION("CEC driver for ChromeOS ECs");
 353MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
 354MODULE_LICENSE("GPL");
 355MODULE_ALIAS("platform:" DRV_NAME);
 356