Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Copyright (C) 2019 Renesas Electronics Corporation
  4 * Copyright (C) 2016 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
  5 */
  6
  7#include <linux/gpio/consumer.h>
  8#include <linux/module.h>
  9#include <linux/of.h>
 10#include <linux/of_device.h>
 11#include <linux/of_graph.h>
 12#include <linux/platform_device.h>
 13#include <linux/regulator/consumer.h>
 14
 15#include <drm/drm_bridge.h>
 16#include <drm/drm_panel.h>
 17
 18struct lvds_codec {
 19	struct device *dev;
 20	struct drm_bridge bridge;
 21	struct drm_bridge *panel_bridge;
 22	struct regulator *vcc;
 23	struct gpio_desc *powerdown_gpio;
 24	u32 connector_type;
 25};
 26
 27static inline struct lvds_codec *to_lvds_codec(struct drm_bridge *bridge)
 28{
 29	return container_of(bridge, struct lvds_codec, bridge);
 30}
 31
 32static int lvds_codec_attach(struct drm_bridge *bridge,
 33			     enum drm_bridge_attach_flags flags)
 34{
 35	struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
 36
 37	return drm_bridge_attach(bridge->encoder, lvds_codec->panel_bridge,
 38				 bridge, flags);
 39}
 40
 41static void lvds_codec_enable(struct drm_bridge *bridge)
 42{
 43	struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
 44	int ret;
 45
 46	ret = regulator_enable(lvds_codec->vcc);
 47	if (ret) {
 48		dev_err(lvds_codec->dev,
 49			"Failed to enable regulator \"vcc\": %d\n", ret);
 50		return;
 51	}
 52
 53	if (lvds_codec->powerdown_gpio)
 54		gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 0);
 55}
 56
 57static void lvds_codec_disable(struct drm_bridge *bridge)
 58{
 59	struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
 60	int ret;
 61
 62	if (lvds_codec->powerdown_gpio)
 63		gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 1);
 64
 65	ret = regulator_disable(lvds_codec->vcc);
 66	if (ret)
 67		dev_err(lvds_codec->dev,
 68			"Failed to disable regulator \"vcc\": %d\n", ret);
 69}
 70
 71static const struct drm_bridge_funcs funcs = {
 72	.attach = lvds_codec_attach,
 73	.enable = lvds_codec_enable,
 74	.disable = lvds_codec_disable,
 75};
 76
 77static int lvds_codec_probe(struct platform_device *pdev)
 78{
 79	struct device *dev = &pdev->dev;
 80	struct device_node *panel_node;
 81	struct drm_panel *panel;
 82	struct lvds_codec *lvds_codec;
 83
 84	lvds_codec = devm_kzalloc(dev, sizeof(*lvds_codec), GFP_KERNEL);
 85	if (!lvds_codec)
 86		return -ENOMEM;
 87
 88	lvds_codec->dev = &pdev->dev;
 89	lvds_codec->connector_type = (uintptr_t)of_device_get_match_data(dev);
 90
 91	lvds_codec->vcc = devm_regulator_get(lvds_codec->dev, "power");
 92	if (IS_ERR(lvds_codec->vcc))
 93		return dev_err_probe(dev, PTR_ERR(lvds_codec->vcc),
 94				     "Unable to get \"vcc\" supply\n");
 95
 96	lvds_codec->powerdown_gpio = devm_gpiod_get_optional(dev, "powerdown",
 97							     GPIOD_OUT_HIGH);
 98	if (IS_ERR(lvds_codec->powerdown_gpio))
 99		return dev_err_probe(dev, PTR_ERR(lvds_codec->powerdown_gpio),
100				     "powerdown GPIO failure\n");
101
102	/* Locate the panel DT node. */
103	panel_node = of_graph_get_remote_node(dev->of_node, 1, 0);
104	if (!panel_node) {
105		dev_dbg(dev, "panel DT node not found\n");
106		return -ENXIO;
107	}
108
109	panel = of_drm_find_panel(panel_node);
110	of_node_put(panel_node);
111	if (IS_ERR(panel)) {
112		dev_dbg(dev, "panel not found, deferring probe\n");
113		return PTR_ERR(panel);
114	}
115
116	lvds_codec->panel_bridge =
117		devm_drm_panel_bridge_add_typed(dev, panel,
118						lvds_codec->connector_type);
119	if (IS_ERR(lvds_codec->panel_bridge))
120		return PTR_ERR(lvds_codec->panel_bridge);
121
122	/*
123	 * The panel_bridge bridge is attached to the panel's of_node,
124	 * but we need a bridge attached to our of_node for our user
125	 * to look up.
126	 */
127	lvds_codec->bridge.of_node = dev->of_node;
128	lvds_codec->bridge.funcs = &funcs;
129	drm_bridge_add(&lvds_codec->bridge);
130
131	platform_set_drvdata(pdev, lvds_codec);
132
133	return 0;
134}
135
136static int lvds_codec_remove(struct platform_device *pdev)
137{
138	struct lvds_codec *lvds_codec = platform_get_drvdata(pdev);
139
140	drm_bridge_remove(&lvds_codec->bridge);
141
142	return 0;
143}
144
145static const struct of_device_id lvds_codec_match[] = {
146	{
147		.compatible = "lvds-decoder",
148		.data = (void *)DRM_MODE_CONNECTOR_DPI,
149	},
150	{
151		.compatible = "lvds-encoder",
152		.data = (void *)DRM_MODE_CONNECTOR_LVDS,
153	},
154	{
155		.compatible = "thine,thc63lvdm83d",
156		.data = (void *)DRM_MODE_CONNECTOR_LVDS,
157	},
158	{},
159};
160MODULE_DEVICE_TABLE(of, lvds_codec_match);
161
162static struct platform_driver lvds_codec_driver = {
163	.probe	= lvds_codec_probe,
164	.remove	= lvds_codec_remove,
165	.driver		= {
166		.name		= "lvds-codec",
167		.of_match_table	= lvds_codec_match,
168	},
169};
170module_platform_driver(lvds_codec_driver);
171
172MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
173MODULE_DESCRIPTION("LVDS encoders and decoders");
174MODULE_LICENSE("GPL");