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 14 struct 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 34 static 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 43 static 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 53 static 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 63 static 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 71 static 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 84 static 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 92 static 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 105 static 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 115 static 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 123 static 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 130 static 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 144 static 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 153 static 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 162 static void cec_gpio_free(struct cec_adapter *adap) 163 { 164 cec_gpio_disable_irq(adap); 165 } 166 167 static 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 179 static 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 265 unreg_notifier: 266 cec_notifier_cec_adap_unregister(cec->notifier, cec->adap); 267 del_adap: 268 cec_delete_adapter(cec->adap); 269 return ret; 270 } 271 272 static 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 281 static const struct of_device_id cec_gpio_match[] = { 282 { 283 .compatible = "cec-gpio", 284 }, 285 {}, 286 }; 287 MODULE_DEVICE_TABLE(of, cec_gpio_match); 288 289 static 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 298 module_platform_driver(cec_gpio_pdrv); 299 300 MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>"); 301 MODULE_LICENSE("GPL v2"); 302 MODULE_DESCRIPTION("CEC GPIO driver"); 303