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        /* Google Brask */
 219        { "Google", "Brask", "0000:00:02.0", "Port B" },
 220};
 221
 222static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev,
 223                                                const char **conn)
 224{
 225        int i;
 226
 227        for (i = 0 ; i < ARRAY_SIZE(cec_dmi_match_table) ; ++i) {
 228                const struct cec_dmi_match *m = &cec_dmi_match_table[i];
 229
 230                if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) &&
 231                    dmi_match(DMI_PRODUCT_NAME, m->product_name)) {
 232                        struct device *d;
 233
 234                        /* Find the device, bail out if not yet registered */
 235                        d = bus_find_device_by_name(&pci_bus_type, NULL,
 236                                                    m->devname);
 237                        if (!d)
 238                                return ERR_PTR(-EPROBE_DEFER);
 239                        put_device(d);
 240                        *conn = m->conn;
 241                        return d;
 242                }
 243        }
 244
 245        /* Hardware support must be added in the cec_dmi_match_table */
 246        dev_warn(dev, "CEC notifier not configured for this hardware\n");
 247
 248        return ERR_PTR(-ENODEV);
 249}
 250
 251#else
 252
 253static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev,
 254                                                const char **conn)
 255{
 256        return ERR_PTR(-ENODEV);
 257}
 258
 259#endif
 260
 261static int cros_ec_cec_probe(struct platform_device *pdev)
 262{
 263        struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent);
 264        struct cros_ec_device *cros_ec = ec_dev->ec_dev;
 265        struct cros_ec_cec *cros_ec_cec;
 266        struct device *hdmi_dev;
 267        const char *conn = NULL;
 268        int ret;
 269
 270        hdmi_dev = cros_ec_cec_find_hdmi_dev(&pdev->dev, &conn);
 271        if (IS_ERR(hdmi_dev))
 272                return PTR_ERR(hdmi_dev);
 273
 274        cros_ec_cec = devm_kzalloc(&pdev->dev, sizeof(*cros_ec_cec),
 275                                   GFP_KERNEL);
 276        if (!cros_ec_cec)
 277                return -ENOMEM;
 278
 279        platform_set_drvdata(pdev, cros_ec_cec);
 280        cros_ec_cec->cros_ec = cros_ec;
 281
 282        device_init_wakeup(&pdev->dev, 1);
 283
 284        cros_ec_cec->adap = cec_allocate_adapter(&cros_ec_cec_ops, cros_ec_cec,
 285                                                 DRV_NAME,
 286                                                 CEC_CAP_DEFAULTS |
 287                                                 CEC_CAP_CONNECTOR_INFO, 1);
 288        if (IS_ERR(cros_ec_cec->adap))
 289                return PTR_ERR(cros_ec_cec->adap);
 290
 291        cros_ec_cec->notify = cec_notifier_cec_adap_register(hdmi_dev, conn,
 292                                                             cros_ec_cec->adap);
 293        if (!cros_ec_cec->notify) {
 294                ret = -ENOMEM;
 295                goto out_probe_adapter;
 296        }
 297
 298        /* Get CEC events from the EC. */
 299        cros_ec_cec->notifier.notifier_call = cros_ec_cec_event;
 300        ret = blocking_notifier_chain_register(&cros_ec->event_notifier,
 301                                               &cros_ec_cec->notifier);
 302        if (ret) {
 303                dev_err(&pdev->dev, "failed to register notifier\n");
 304                goto out_probe_notify;
 305        }
 306
 307        ret = cec_register_adapter(cros_ec_cec->adap, &pdev->dev);
 308        if (ret < 0)
 309                goto out_probe_notify;
 310
 311        return 0;
 312
 313out_probe_notify:
 314        cec_notifier_cec_adap_unregister(cros_ec_cec->notify,
 315                                         cros_ec_cec->adap);
 316out_probe_adapter:
 317        cec_delete_adapter(cros_ec_cec->adap);
 318        return ret;
 319}
 320
 321static int cros_ec_cec_remove(struct platform_device *pdev)
 322{
 323        struct cros_ec_cec *cros_ec_cec = platform_get_drvdata(pdev);
 324        struct device *dev = &pdev->dev;
 325        int ret;
 326
 327        ret = blocking_notifier_chain_unregister(
 328                        &cros_ec_cec->cros_ec->event_notifier,
 329                        &cros_ec_cec->notifier);
 330
 331        if (ret) {
 332                dev_err(dev, "failed to unregister notifier\n");
 333                return ret;
 334        }
 335
 336        cec_notifier_cec_adap_unregister(cros_ec_cec->notify,
 337                                         cros_ec_cec->adap);
 338        cec_unregister_adapter(cros_ec_cec->adap);
 339
 340        return 0;
 341}
 342
 343static struct platform_driver cros_ec_cec_driver = {
 344        .probe = cros_ec_cec_probe,
 345        .remove  = cros_ec_cec_remove,
 346        .driver = {
 347                .name = DRV_NAME,
 348                .pm = &cros_ec_cec_pm_ops,
 349        },
 350};
 351
 352module_platform_driver(cros_ec_cec_driver);
 353
 354MODULE_DESCRIPTION("CEC driver for ChromeOS ECs");
 355MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
 356MODULE_LICENSE("GPL");
 357MODULE_ALIAS("platform:" DRV_NAME);
 358