162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2014 Traphandler
462306a36Sopenharmony_ci * Copyright (C) 2014 Free Electrons
562306a36Sopenharmony_ci * Copyright (C) 2014 Atmel
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com>
862306a36Sopenharmony_ci * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/media-bus-format.h>
1262306a36Sopenharmony_ci#include <linux/of.h>
1362306a36Sopenharmony_ci#include <linux/of_graph.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <drm/drm_bridge.h>
1662306a36Sopenharmony_ci#include <drm/drm_encoder.h>
1762306a36Sopenharmony_ci#include <drm/drm_of.h>
1862306a36Sopenharmony_ci#include <drm/drm_simple_kms_helper.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "atmel_hlcdc_dc.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistruct atmel_hlcdc_rgb_output {
2362306a36Sopenharmony_ci	struct drm_encoder encoder;
2462306a36Sopenharmony_ci	int bus_fmt;
2562306a36Sopenharmony_ci};
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic struct atmel_hlcdc_rgb_output *
2862306a36Sopenharmony_ciatmel_hlcdc_encoder_to_rgb_output(struct drm_encoder *encoder)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder);
3162306a36Sopenharmony_ci}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ciint atmel_hlcdc_encoder_get_bus_fmt(struct drm_encoder *encoder)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	struct atmel_hlcdc_rgb_output *output;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	output = atmel_hlcdc_encoder_to_rgb_output(encoder);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	return output->bus_fmt;
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int atmel_hlcdc_of_bus_fmt(const struct device_node *ep)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	u32 bus_width;
4562306a36Sopenharmony_ci	int ret;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	ret = of_property_read_u32(ep, "bus-width", &bus_width);
4862306a36Sopenharmony_ci	if (ret == -EINVAL)
4962306a36Sopenharmony_ci		return 0;
5062306a36Sopenharmony_ci	if (ret)
5162306a36Sopenharmony_ci		return ret;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	switch (bus_width) {
5462306a36Sopenharmony_ci	case 12:
5562306a36Sopenharmony_ci		return MEDIA_BUS_FMT_RGB444_1X12;
5662306a36Sopenharmony_ci	case 16:
5762306a36Sopenharmony_ci		return MEDIA_BUS_FMT_RGB565_1X16;
5862306a36Sopenharmony_ci	case 18:
5962306a36Sopenharmony_ci		return MEDIA_BUS_FMT_RGB666_1X18;
6062306a36Sopenharmony_ci	case 24:
6162306a36Sopenharmony_ci		return MEDIA_BUS_FMT_RGB888_1X24;
6262306a36Sopenharmony_ci	default:
6362306a36Sopenharmony_ci		return -EINVAL;
6462306a36Sopenharmony_ci	}
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	struct atmel_hlcdc_rgb_output *output;
7062306a36Sopenharmony_ci	struct device_node *ep;
7162306a36Sopenharmony_ci	struct drm_panel *panel;
7262306a36Sopenharmony_ci	struct drm_bridge *bridge;
7362306a36Sopenharmony_ci	int ret;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	ep = of_graph_get_endpoint_by_regs(dev->dev->of_node, 0, endpoint);
7662306a36Sopenharmony_ci	if (!ep)
7762306a36Sopenharmony_ci		return -ENODEV;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	ret = drm_of_find_panel_or_bridge(dev->dev->of_node, 0, endpoint,
8062306a36Sopenharmony_ci					  &panel, &bridge);
8162306a36Sopenharmony_ci	if (ret) {
8262306a36Sopenharmony_ci		of_node_put(ep);
8362306a36Sopenharmony_ci		return ret;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	output = devm_kzalloc(dev->dev, sizeof(*output), GFP_KERNEL);
8762306a36Sopenharmony_ci	if (!output) {
8862306a36Sopenharmony_ci		of_node_put(ep);
8962306a36Sopenharmony_ci		return -ENOMEM;
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	output->bus_fmt = atmel_hlcdc_of_bus_fmt(ep);
9362306a36Sopenharmony_ci	of_node_put(ep);
9462306a36Sopenharmony_ci	if (output->bus_fmt < 0) {
9562306a36Sopenharmony_ci		dev_err(dev->dev, "endpoint %d: invalid bus width\n", endpoint);
9662306a36Sopenharmony_ci		return -EINVAL;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	ret = drm_simple_encoder_init(dev, &output->encoder,
10062306a36Sopenharmony_ci				      DRM_MODE_ENCODER_NONE);
10162306a36Sopenharmony_ci	if (ret)
10262306a36Sopenharmony_ci		return ret;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	output->encoder.possible_crtcs = 0x1;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	if (panel) {
10762306a36Sopenharmony_ci		bridge = drm_panel_bridge_add_typed(panel,
10862306a36Sopenharmony_ci						    DRM_MODE_CONNECTOR_Unknown);
10962306a36Sopenharmony_ci		if (IS_ERR(bridge))
11062306a36Sopenharmony_ci			return PTR_ERR(bridge);
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	if (bridge) {
11462306a36Sopenharmony_ci		ret = drm_bridge_attach(&output->encoder, bridge, NULL, 0);
11562306a36Sopenharmony_ci		if (!ret)
11662306a36Sopenharmony_ci			return 0;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci		if (panel)
11962306a36Sopenharmony_ci			drm_panel_bridge_remove(bridge);
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	drm_encoder_cleanup(&output->encoder);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	return ret;
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ciint atmel_hlcdc_create_outputs(struct drm_device *dev)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	int endpoint, ret = 0;
13062306a36Sopenharmony_ci	int attached = 0;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/*
13362306a36Sopenharmony_ci	 * Always scan the first few endpoints even if we get -ENODEV,
13462306a36Sopenharmony_ci	 * but keep going after that as long as we keep getting hits.
13562306a36Sopenharmony_ci	 */
13662306a36Sopenharmony_ci	for (endpoint = 0; !ret || endpoint < 4; endpoint++) {
13762306a36Sopenharmony_ci		ret = atmel_hlcdc_attach_endpoint(dev, endpoint);
13862306a36Sopenharmony_ci		if (ret == -ENODEV)
13962306a36Sopenharmony_ci			continue;
14062306a36Sopenharmony_ci		if (ret)
14162306a36Sopenharmony_ci			break;
14262306a36Sopenharmony_ci		attached++;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	/* At least one device was successfully attached.*/
14662306a36Sopenharmony_ci	if (ret == -ENODEV && attached)
14762306a36Sopenharmony_ci		return 0;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	return ret;
15062306a36Sopenharmony_ci}
151