cec-gpio.c (7036B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 4 */ 5 6#include <linux/module.h> 7#include <linux/interrupt.h> 8#include <linux/delay.h> 9#include <linux/platform_device.h> 10#include <linux/gpio/consumer.h> 11#include <media/cec-notifier.h> 12#include <media/cec-pin.h> 13 14struct cec_gpio { 15 struct cec_adapter *adap; 16 struct cec_notifier *notifier; 17 struct device *dev; 18 19 struct gpio_desc *cec_gpio; 20 int cec_irq; 21 bool cec_is_low; 22 23 struct gpio_desc *hpd_gpio; 24 int hpd_irq; 25 bool hpd_is_high; 26 ktime_t hpd_ts; 27 28 struct gpio_desc *v5_gpio; 29 int v5_irq; 30 bool v5_is_high; 31 ktime_t v5_ts; 32}; 33 34static int cec_gpio_read(struct cec_adapter *adap) 35{ 36 struct cec_gpio *cec = cec_get_drvdata(adap); 37 38 if (cec->cec_is_low) 39 return 0; 40 return gpiod_get_value(cec->cec_gpio); 41} 42 43static void cec_gpio_high(struct cec_adapter *adap) 44{ 45 struct cec_gpio *cec = cec_get_drvdata(adap); 46 47 if (!cec->cec_is_low) 48 return; 49 cec->cec_is_low = false; 50 gpiod_set_value(cec->cec_gpio, 1); 51} 52 53static void cec_gpio_low(struct cec_adapter *adap) 54{ 55 struct cec_gpio *cec = cec_get_drvdata(adap); 56 57 if (cec->cec_is_low) 58 return; 59 cec->cec_is_low = true; 60 gpiod_set_value(cec->cec_gpio, 0); 61} 62 63static irqreturn_t cec_hpd_gpio_irq_handler_thread(int irq, void *priv) 64{ 65 struct cec_gpio *cec = priv; 66 67 cec_queue_pin_hpd_event(cec->adap, cec->hpd_is_high, cec->hpd_ts); 68 return IRQ_HANDLED; 69} 70 71static irqreturn_t cec_5v_gpio_irq_handler(int irq, void *priv) 72{ 73 struct cec_gpio *cec = priv; 74 int val = gpiod_get_value(cec->v5_gpio); 75 bool is_high = val > 0; 76 77 if (val < 0 || is_high == cec->v5_is_high) 78 return IRQ_HANDLED; 79 cec->v5_ts = ktime_get(); 80 cec->v5_is_high = is_high; 81 return IRQ_WAKE_THREAD; 82} 83 84static irqreturn_t cec_5v_gpio_irq_handler_thread(int irq, void *priv) 85{ 86 struct cec_gpio *cec = priv; 87 88 cec_queue_pin_5v_event(cec->adap, cec->v5_is_high, cec->v5_ts); 89 return IRQ_HANDLED; 90} 91 92static irqreturn_t cec_hpd_gpio_irq_handler(int irq, void *priv) 93{ 94 struct cec_gpio *cec = priv; 95 int val = gpiod_get_value(cec->hpd_gpio); 96 bool is_high = val > 0; 97 98 if (val < 0 || is_high == cec->hpd_is_high) 99 return IRQ_HANDLED; 100 cec->hpd_ts = ktime_get(); 101 cec->hpd_is_high = is_high; 102 return IRQ_WAKE_THREAD; 103} 104 105static irqreturn_t cec_gpio_irq_handler(int irq, void *priv) 106{ 107 struct cec_gpio *cec = priv; 108 int val = gpiod_get_value(cec->cec_gpio); 109 110 if (val >= 0) 111 cec_pin_changed(cec->adap, val > 0); 112 return IRQ_HANDLED; 113} 114 115static bool cec_gpio_enable_irq(struct cec_adapter *adap) 116{ 117 struct cec_gpio *cec = cec_get_drvdata(adap); 118 119 enable_irq(cec->cec_irq); 120 return true; 121} 122 123static void cec_gpio_disable_irq(struct cec_adapter *adap) 124{ 125 struct cec_gpio *cec = cec_get_drvdata(adap); 126 127 disable_irq(cec->cec_irq); 128} 129 130static void cec_gpio_status(struct cec_adapter *adap, struct seq_file *file) 131{ 132 struct cec_gpio *cec = cec_get_drvdata(adap); 133 134 seq_printf(file, "mode: %s\n", cec->cec_is_low ? "low-drive" : "read"); 135 seq_printf(file, "using irq: %d\n", cec->cec_irq); 136 if (cec->hpd_gpio) 137 seq_printf(file, "hpd: %s\n", 138 cec->hpd_is_high ? "high" : "low"); 139 if (cec->v5_gpio) 140 seq_printf(file, "5V: %s\n", 141 cec->v5_is_high ? "high" : "low"); 142} 143 144static int cec_gpio_read_hpd(struct cec_adapter *adap) 145{ 146 struct cec_gpio *cec = cec_get_drvdata(adap); 147 148 if (!cec->hpd_gpio) 149 return -ENOTTY; 150 return gpiod_get_value(cec->hpd_gpio); 151} 152 153static int cec_gpio_read_5v(struct cec_adapter *adap) 154{ 155 struct cec_gpio *cec = cec_get_drvdata(adap); 156 157 if (!cec->v5_gpio) 158 return -ENOTTY; 159 return gpiod_get_value(cec->v5_gpio); 160} 161 162static void cec_gpio_free(struct cec_adapter *adap) 163{ 164 cec_gpio_disable_irq(adap); 165} 166 167static const struct cec_pin_ops cec_gpio_pin_ops = { 168 .read = cec_gpio_read, 169 .low = cec_gpio_low, 170 .high = cec_gpio_high, 171 .enable_irq = cec_gpio_enable_irq, 172 .disable_irq = cec_gpio_disable_irq, 173 .status = cec_gpio_status, 174 .free = cec_gpio_free, 175 .read_hpd = cec_gpio_read_hpd, 176 .read_5v = cec_gpio_read_5v, 177}; 178 179static int cec_gpio_probe(struct platform_device *pdev) 180{ 181 struct device *dev = &pdev->dev; 182 struct device *hdmi_dev; 183 struct cec_gpio *cec; 184 u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN; 185 int ret; 186 187 hdmi_dev = cec_notifier_parse_hdmi_phandle(dev); 188 if (PTR_ERR(hdmi_dev) == -EPROBE_DEFER) 189 return PTR_ERR(hdmi_dev); 190 if (IS_ERR(hdmi_dev)) 191 caps |= CEC_CAP_PHYS_ADDR; 192 193 cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL); 194 if (!cec) 195 return -ENOMEM; 196 197 cec->dev = dev; 198 199 cec->cec_gpio = devm_gpiod_get(dev, "cec", GPIOD_OUT_HIGH_OPEN_DRAIN); 200 if (IS_ERR(cec->cec_gpio)) 201 return PTR_ERR(cec->cec_gpio); 202 cec->cec_irq = gpiod_to_irq(cec->cec_gpio); 203 204 cec->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN); 205 if (IS_ERR(cec->hpd_gpio)) 206 return PTR_ERR(cec->hpd_gpio); 207 208 cec->v5_gpio = devm_gpiod_get_optional(dev, "v5", GPIOD_IN); 209 if (IS_ERR(cec->v5_gpio)) 210 return PTR_ERR(cec->v5_gpio); 211 212 cec->adap = cec_pin_allocate_adapter(&cec_gpio_pin_ops, 213 cec, pdev->name, caps); 214 if (IS_ERR(cec->adap)) 215 return PTR_ERR(cec->adap); 216 217 ret = devm_request_irq(dev, cec->cec_irq, cec_gpio_irq_handler, 218 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 219 cec->adap->name, cec); 220 if (ret) 221 goto del_adap; 222 223 cec_gpio_disable_irq(cec->adap); 224 225 if (cec->hpd_gpio) { 226 cec->hpd_irq = gpiod_to_irq(cec->hpd_gpio); 227 ret = devm_request_threaded_irq(dev, cec->hpd_irq, 228 cec_hpd_gpio_irq_handler, 229 cec_hpd_gpio_irq_handler_thread, 230 IRQF_ONESHOT | 231 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 232 "hpd-gpio", cec); 233 if (ret) 234 goto del_adap; 235 } 236 237 if (cec->v5_gpio) { 238 cec->v5_irq = gpiod_to_irq(cec->v5_gpio); 239 ret = devm_request_threaded_irq(dev, cec->v5_irq, 240 cec_5v_gpio_irq_handler, 241 cec_5v_gpio_irq_handler_thread, 242 IRQF_ONESHOT | 243 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 244 "v5-gpio", cec); 245 if (ret) 246 goto del_adap; 247 } 248 249 if (!IS_ERR(hdmi_dev)) { 250 cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL, 251 cec->adap); 252 if (!cec->notifier) { 253 ret = -ENOMEM; 254 goto del_adap; 255 } 256 } 257 258 ret = cec_register_adapter(cec->adap, &pdev->dev); 259 if (ret) 260 goto unreg_notifier; 261 262 platform_set_drvdata(pdev, cec); 263 return 0; 264 265unreg_notifier: 266 cec_notifier_cec_adap_unregister(cec->notifier, cec->adap); 267del_adap: 268 cec_delete_adapter(cec->adap); 269 return ret; 270} 271 272static int cec_gpio_remove(struct platform_device *pdev) 273{ 274 struct cec_gpio *cec = platform_get_drvdata(pdev); 275 276 cec_notifier_cec_adap_unregister(cec->notifier, cec->adap); 277 cec_unregister_adapter(cec->adap); 278 return 0; 279} 280 281static const struct of_device_id cec_gpio_match[] = { 282 { 283 .compatible = "cec-gpio", 284 }, 285 {}, 286}; 287MODULE_DEVICE_TABLE(of, cec_gpio_match); 288 289static struct platform_driver cec_gpio_pdrv = { 290 .probe = cec_gpio_probe, 291 .remove = cec_gpio_remove, 292 .driver = { 293 .name = "cec-gpio", 294 .of_match_table = cec_gpio_match, 295 }, 296}; 297 298module_platform_driver(cec_gpio_pdrv); 299 300MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>"); 301MODULE_LICENSE("GPL v2"); 302MODULE_DESCRIPTION("CEC GPIO driver");