1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * OPA362 analog video amplifier with output/power control 4 * 5 * Copyright (C) 2014 Golden Delicious Computers 6 * Author: H. Nikolaus Schaller <hns@goldelico.com> 7 * 8 * based on encoder-tfp410 9 * 10 * Copyright (C) 2013 Texas Instruments 11 * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> 12 */ 13 14 #include <linux/gpio/consumer.h> 15 #include <linux/module.h> 16 #include <linux/mod_devicetable.h> 17 #include <linux/platform_device.h> 18 #include <linux/slab.h> 19 20 #include <video/omapfb_dss.h> 21 22 struct panel_drv_data { 23 struct omap_dss_device dssdev; 24 struct omap_dss_device *in; 25 26 struct gpio_desc *enable_gpio; 27 28 struct omap_video_timings timings; 29 }; 30 31 #define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) 32 33 static int opa362_connect(struct omap_dss_device *dssdev, 34 struct omap_dss_device *dst) 35 { 36 struct panel_drv_data *ddata = to_panel_data(dssdev); 37 struct omap_dss_device *in = ddata->in; 38 int r; 39 40 dev_dbg(dssdev->dev, "connect\n"); 41 42 if (omapdss_device_is_connected(dssdev)) 43 return -EBUSY; 44 45 r = in->ops.atv->connect(in, dssdev); 46 if (r) 47 return r; 48 49 dst->src = dssdev; 50 dssdev->dst = dst; 51 52 return 0; 53 } 54 55 static void opa362_disconnect(struct omap_dss_device *dssdev, 56 struct omap_dss_device *dst) 57 { 58 struct panel_drv_data *ddata = to_panel_data(dssdev); 59 struct omap_dss_device *in = ddata->in; 60 61 dev_dbg(dssdev->dev, "disconnect\n"); 62 63 WARN_ON(!omapdss_device_is_connected(dssdev)); 64 if (!omapdss_device_is_connected(dssdev)) 65 return; 66 67 WARN_ON(dst != dssdev->dst); 68 if (dst != dssdev->dst) 69 return; 70 71 dst->src = NULL; 72 dssdev->dst = NULL; 73 74 in->ops.atv->disconnect(in, &ddata->dssdev); 75 } 76 77 static int opa362_enable(struct omap_dss_device *dssdev) 78 { 79 struct panel_drv_data *ddata = to_panel_data(dssdev); 80 struct omap_dss_device *in = ddata->in; 81 int r; 82 83 dev_dbg(dssdev->dev, "enable\n"); 84 85 if (!omapdss_device_is_connected(dssdev)) 86 return -ENODEV; 87 88 if (omapdss_device_is_enabled(dssdev)) 89 return 0; 90 91 in->ops.atv->set_timings(in, &ddata->timings); 92 93 r = in->ops.atv->enable(in); 94 if (r) 95 return r; 96 97 if (ddata->enable_gpio) 98 gpiod_set_value_cansleep(ddata->enable_gpio, 1); 99 100 dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; 101 102 return 0; 103 } 104 105 static void opa362_disable(struct omap_dss_device *dssdev) 106 { 107 struct panel_drv_data *ddata = to_panel_data(dssdev); 108 struct omap_dss_device *in = ddata->in; 109 110 dev_dbg(dssdev->dev, "disable\n"); 111 112 if (!omapdss_device_is_enabled(dssdev)) 113 return; 114 115 if (ddata->enable_gpio) 116 gpiod_set_value_cansleep(ddata->enable_gpio, 0); 117 118 in->ops.atv->disable(in); 119 120 dssdev->state = OMAP_DSS_DISPLAY_DISABLED; 121 } 122 123 static void opa362_set_timings(struct omap_dss_device *dssdev, 124 struct omap_video_timings *timings) 125 { 126 struct panel_drv_data *ddata = to_panel_data(dssdev); 127 struct omap_dss_device *in = ddata->in; 128 129 dev_dbg(dssdev->dev, "set_timings\n"); 130 131 ddata->timings = *timings; 132 dssdev->panel.timings = *timings; 133 134 in->ops.atv->set_timings(in, timings); 135 } 136 137 static void opa362_get_timings(struct omap_dss_device *dssdev, 138 struct omap_video_timings *timings) 139 { 140 struct panel_drv_data *ddata = to_panel_data(dssdev); 141 142 dev_dbg(dssdev->dev, "get_timings\n"); 143 144 *timings = ddata->timings; 145 } 146 147 static int opa362_check_timings(struct omap_dss_device *dssdev, 148 struct omap_video_timings *timings) 149 { 150 struct panel_drv_data *ddata = to_panel_data(dssdev); 151 struct omap_dss_device *in = ddata->in; 152 153 dev_dbg(dssdev->dev, "check_timings\n"); 154 155 return in->ops.atv->check_timings(in, timings); 156 } 157 158 static void opa362_set_type(struct omap_dss_device *dssdev, 159 enum omap_dss_venc_type type) 160 { 161 /* we can only drive a COMPOSITE output */ 162 WARN_ON(type != OMAP_DSS_VENC_TYPE_COMPOSITE); 163 164 } 165 166 static const struct omapdss_atv_ops opa362_atv_ops = { 167 .connect = opa362_connect, 168 .disconnect = opa362_disconnect, 169 170 .enable = opa362_enable, 171 .disable = opa362_disable, 172 173 .check_timings = opa362_check_timings, 174 .set_timings = opa362_set_timings, 175 .get_timings = opa362_get_timings, 176 177 .set_type = opa362_set_type, 178 }; 179 180 static int opa362_probe(struct platform_device *pdev) 181 { 182 struct device_node *node = pdev->dev.of_node; 183 struct panel_drv_data *ddata; 184 struct omap_dss_device *dssdev, *in; 185 struct gpio_desc *gpio; 186 int r; 187 188 dev_dbg(&pdev->dev, "probe\n"); 189 190 if (node == NULL) { 191 dev_err(&pdev->dev, "Unable to find device tree\n"); 192 return -EINVAL; 193 } 194 195 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); 196 if (!ddata) 197 return -ENOMEM; 198 199 platform_set_drvdata(pdev, ddata); 200 201 gpio = devm_gpiod_get_optional(&pdev->dev, "enable", GPIOD_OUT_LOW); 202 if (IS_ERR(gpio)) 203 return PTR_ERR(gpio); 204 205 ddata->enable_gpio = gpio; 206 207 in = omapdss_of_find_source_for_first_ep(node); 208 if (IS_ERR(in)) { 209 dev_err(&pdev->dev, "failed to find video source\n"); 210 return PTR_ERR(in); 211 } 212 213 ddata->in = in; 214 215 dssdev = &ddata->dssdev; 216 dssdev->ops.atv = &opa362_atv_ops; 217 dssdev->dev = &pdev->dev; 218 dssdev->type = OMAP_DISPLAY_TYPE_VENC; 219 dssdev->output_type = OMAP_DISPLAY_TYPE_VENC; 220 dssdev->owner = THIS_MODULE; 221 222 r = omapdss_register_output(dssdev); 223 if (r) { 224 dev_err(&pdev->dev, "Failed to register output\n"); 225 goto err_reg; 226 } 227 228 return 0; 229 err_reg: 230 omap_dss_put_device(ddata->in); 231 return r; 232 } 233 234 static void opa362_remove(struct platform_device *pdev) 235 { 236 struct panel_drv_data *ddata = platform_get_drvdata(pdev); 237 struct omap_dss_device *dssdev = &ddata->dssdev; 238 struct omap_dss_device *in = ddata->in; 239 240 omapdss_unregister_output(&ddata->dssdev); 241 242 WARN_ON(omapdss_device_is_enabled(dssdev)); 243 if (omapdss_device_is_enabled(dssdev)) 244 opa362_disable(dssdev); 245 246 WARN_ON(omapdss_device_is_connected(dssdev)); 247 if (omapdss_device_is_connected(dssdev)) 248 opa362_disconnect(dssdev, dssdev->dst); 249 250 omap_dss_put_device(in); 251 } 252 253 static const struct of_device_id opa362_of_match[] = { 254 { .compatible = "omapdss,ti,opa362", }, 255 {}, 256 }; 257 MODULE_DEVICE_TABLE(of, opa362_of_match); 258 259 static struct platform_driver opa362_driver = { 260 .probe = opa362_probe, 261 .remove_new = opa362_remove, 262 .driver = { 263 .name = "amplifier-opa362", 264 .of_match_table = opa362_of_match, 265 }, 266 }; 267 268 module_platform_driver(opa362_driver); 269 270 MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>"); 271 MODULE_DESCRIPTION("OPA362 analog video amplifier with output/power control"); 272 MODULE_LICENSE("GPL v2"); 273