atmel_hlcdc_output.c (3185B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Copyright (C) 2014 Traphandler 4 * Copyright (C) 2014 Free Electrons 5 * Copyright (C) 2014 Atmel 6 * 7 * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com> 8 * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> 9 */ 10 11#include <linux/media-bus-format.h> 12#include <linux/of_graph.h> 13 14#include <drm/drm_bridge.h> 15#include <drm/drm_encoder.h> 16#include <drm/drm_of.h> 17#include <drm/drm_simple_kms_helper.h> 18 19#include "atmel_hlcdc_dc.h" 20 21struct atmel_hlcdc_rgb_output { 22 struct drm_encoder encoder; 23 int bus_fmt; 24}; 25 26static struct atmel_hlcdc_rgb_output * 27atmel_hlcdc_encoder_to_rgb_output(struct drm_encoder *encoder) 28{ 29 return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder); 30} 31 32int atmel_hlcdc_encoder_get_bus_fmt(struct drm_encoder *encoder) 33{ 34 struct atmel_hlcdc_rgb_output *output; 35 36 output = atmel_hlcdc_encoder_to_rgb_output(encoder); 37 38 return output->bus_fmt; 39} 40 41static int atmel_hlcdc_of_bus_fmt(const struct device_node *ep) 42{ 43 u32 bus_width; 44 int ret; 45 46 ret = of_property_read_u32(ep, "bus-width", &bus_width); 47 if (ret == -EINVAL) 48 return 0; 49 if (ret) 50 return ret; 51 52 switch (bus_width) { 53 case 12: 54 return MEDIA_BUS_FMT_RGB444_1X12; 55 case 16: 56 return MEDIA_BUS_FMT_RGB565_1X16; 57 case 18: 58 return MEDIA_BUS_FMT_RGB666_1X18; 59 case 24: 60 return MEDIA_BUS_FMT_RGB888_1X24; 61 default: 62 return -EINVAL; 63 } 64} 65 66static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint) 67{ 68 struct atmel_hlcdc_rgb_output *output; 69 struct device_node *ep; 70 struct drm_panel *panel; 71 struct drm_bridge *bridge; 72 int ret; 73 74 ep = of_graph_get_endpoint_by_regs(dev->dev->of_node, 0, endpoint); 75 if (!ep) 76 return -ENODEV; 77 78 ret = drm_of_find_panel_or_bridge(dev->dev->of_node, 0, endpoint, 79 &panel, &bridge); 80 if (ret) { 81 of_node_put(ep); 82 return ret; 83 } 84 85 output = devm_kzalloc(dev->dev, sizeof(*output), GFP_KERNEL); 86 if (!output) { 87 of_node_put(ep); 88 return -ENOMEM; 89 } 90 91 output->bus_fmt = atmel_hlcdc_of_bus_fmt(ep); 92 of_node_put(ep); 93 if (output->bus_fmt < 0) { 94 dev_err(dev->dev, "endpoint %d: invalid bus width\n", endpoint); 95 return -EINVAL; 96 } 97 98 ret = drm_simple_encoder_init(dev, &output->encoder, 99 DRM_MODE_ENCODER_NONE); 100 if (ret) 101 return ret; 102 103 output->encoder.possible_crtcs = 0x1; 104 105 if (panel) { 106 bridge = drm_panel_bridge_add_typed(panel, 107 DRM_MODE_CONNECTOR_Unknown); 108 if (IS_ERR(bridge)) 109 return PTR_ERR(bridge); 110 } 111 112 if (bridge) { 113 ret = drm_bridge_attach(&output->encoder, bridge, NULL, 0); 114 if (!ret) 115 return 0; 116 117 if (panel) 118 drm_panel_bridge_remove(bridge); 119 } 120 121 drm_encoder_cleanup(&output->encoder); 122 123 return ret; 124} 125 126int atmel_hlcdc_create_outputs(struct drm_device *dev) 127{ 128 int endpoint, ret = 0; 129 int attached = 0; 130 131 /* 132 * Always scan the first few endpoints even if we get -ENODEV, 133 * but keep going after that as long as we keep getting hits. 134 */ 135 for (endpoint = 0; !ret || endpoint < 4; endpoint++) { 136 ret = atmel_hlcdc_attach_endpoint(dev, endpoint); 137 if (ret == -ENODEV) 138 continue; 139 if (ret) 140 break; 141 attached++; 142 } 143 144 /* At least one device was successfully attached.*/ 145 if (ret == -ENODEV && attached) 146 return 0; 147 148 return ret; 149}