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 const struct cec_pin_ops cec_gpio_pin_ops = { 163 .read = cec_gpio_read, 164 .low = cec_gpio_low, 165 .high = cec_gpio_high, 166 .enable_irq = cec_gpio_enable_irq, 167 .disable_irq = cec_gpio_disable_irq, 168 .status = cec_gpio_status, 169 .read_hpd = cec_gpio_read_hpd, 170 .read_5v = cec_gpio_read_5v, 171 }; 172 173 static int cec_gpio_probe(struct platform_device *pdev) 174 { 175 struct device *dev = &pdev->dev; 176 struct device *hdmi_dev; 177 struct cec_gpio *cec; 178 u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN; 179 int ret; 180 181 hdmi_dev = cec_notifier_parse_hdmi_phandle(dev); 182 if (PTR_ERR(hdmi_dev) == -EPROBE_DEFER) 183 return PTR_ERR(hdmi_dev); 184 if (IS_ERR(hdmi_dev)) 185 caps |= CEC_CAP_PHYS_ADDR; 186 187 cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL); 188 if (!cec) 189 return -ENOMEM; 190 191 cec->dev = dev; 192 193 cec->cec_gpio = devm_gpiod_get(dev, "cec", GPIOD_OUT_HIGH_OPEN_DRAIN); 194 if (IS_ERR(cec->cec_gpio)) 195 return PTR_ERR(cec->cec_gpio); 196 cec->cec_irq = gpiod_to_irq(cec->cec_gpio); 197 198 cec->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN); 199 if (IS_ERR(cec->hpd_gpio)) 200 return PTR_ERR(cec->hpd_gpio); 201 202 cec->v5_gpio = devm_gpiod_get_optional(dev, "v5", GPIOD_IN); 203 if (IS_ERR(cec->v5_gpio)) 204 return PTR_ERR(cec->v5_gpio); 205 206 cec->adap = cec_pin_allocate_adapter(&cec_gpio_pin_ops, 207 cec, pdev->name, caps); 208 if (IS_ERR(cec->adap)) 209 return PTR_ERR(cec->adap); 210 211 ret = devm_request_irq(dev, cec->cec_irq, cec_gpio_irq_handler, 212 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN, 213 cec->adap->name, cec); 214 if (ret) 215 goto del_adap; 216 217 if (cec->hpd_gpio) { 218 cec->hpd_irq = gpiod_to_irq(cec->hpd_gpio); 219 ret = devm_request_threaded_irq(dev, cec->hpd_irq, 220 cec_hpd_gpio_irq_handler, 221 cec_hpd_gpio_irq_handler_thread, 222 IRQF_ONESHOT | 223 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 224 "hpd-gpio", cec); 225 if (ret) 226 goto del_adap; 227 } 228 229 if (cec->v5_gpio) { 230 cec->v5_irq = gpiod_to_irq(cec->v5_gpio); 231 ret = devm_request_threaded_irq(dev, cec->v5_irq, 232 cec_5v_gpio_irq_handler, 233 cec_5v_gpio_irq_handler_thread, 234 IRQF_ONESHOT | 235 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 236 "v5-gpio", cec); 237 if (ret) 238 goto del_adap; 239 } 240 241 if (!IS_ERR(hdmi_dev)) { 242 cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL, 243 cec->adap); 244 if (!cec->notifier) { 245 ret = -ENOMEM; 246 goto del_adap; 247 } 248 } 249 250 ret = cec_register_adapter(cec->adap, &pdev->dev); 251 if (ret) 252 goto unreg_notifier; 253 254 platform_set_drvdata(pdev, cec); 255 return 0; 256 257 unreg_notifier: 258 cec_notifier_cec_adap_unregister(cec->notifier, cec->adap); 259 del_adap: 260 cec_delete_adapter(cec->adap); 261 return ret; 262 } 263 264 static void cec_gpio_remove(struct platform_device *pdev) 265 { 266 struct cec_gpio *cec = platform_get_drvdata(pdev); 267 268 cec_notifier_cec_adap_unregister(cec->notifier, cec->adap); 269 cec_unregister_adapter(cec->adap); 270 } 271 272 static const struct of_device_id cec_gpio_match[] = { 273 { 274 .compatible = "cec-gpio", 275 }, 276 {}, 277 }; 278 MODULE_DEVICE_TABLE(of, cec_gpio_match); 279 280 static struct platform_driver cec_gpio_pdrv = { 281 .probe = cec_gpio_probe, 282 .remove_new = cec_gpio_remove, 283 .driver = { 284 .name = "cec-gpio", 285 .of_match_table = cec_gpio_match, 286 }, 287 }; 288 289 module_platform_driver(cec_gpio_pdrv); 290 291 MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>"); 292 MODULE_LICENSE("GPL v2"); 293 MODULE_DESCRIPTION("CEC GPIO driver"); 294