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