forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 229
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LF-811-1: drm/bridge: Add driver for legacy Freescale Seiko 43WVFIG a…
…dapter This is an adapter card made for the 4.3", 800x480, LCD panel Seiko 43WVFIG. The LCD panel is a 24bit DPI bus, while the adapter card has two ports: 18-bit and 24-bit data input. For the 18-bit data input, the adapter card is demuxing some of the data lines, in order to feed all of the 24 lines needed by the LCD. This driver handles both this use-cases. Signed-off-by: Robert Chiras <robert.chiras@nxp.com>
- Loading branch information
Robert Chiras
committed
May 22, 2020
1 parent
5fd9412
commit c40c90f
Showing
4 changed files
with
291 additions
and
0 deletions.
There are no files selected for viewing
40 changes: 40 additions & 0 deletions
40
Documentation/devicetree/bindings/display/bridge/nxp,seiko-43wvfig.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
Legacy Freescale RA169Z20 adapter card for Seiko 43WVFIG panel, driver bindings | ||
|
||
This is an adapter card made for the 4.3", 800x480, LCD panel Seiko 43WVFIG. | ||
The LCD panel is a 24bit DPI bus, while the adapter card has two ports: | ||
18-bit and 24-bit data input. For the 18-bit data input, the adapter card | ||
is demuxing some of the data lines, in order to feed all of the 24 lines | ||
needed by the LCD. | ||
|
||
Required properties: | ||
- compatible: "nxp,seiko-43wvfig" | ||
- bus_mode: must be one of <18> or <24>, depending on the input port | ||
used (18-bit or 24-bit) | ||
- port: input and output port nodes with endpoint definitions as | ||
defined in Documentation/devicetree/bindings/graph.txt; | ||
the input port should be connected to an lcd controller | ||
while the output port should be connected to the Seiko | ||
43wvfig LCD panel | ||
|
||
Example: | ||
seiko_adapter: seiko-adapter { | ||
#address-cells = <1>; | ||
#size-cells = <0>; | ||
compatible = "nxp,seiko-43wvfig"; | ||
bus_mode = <18>; | ||
|
||
port@0 { | ||
reg = <0>; | ||
adapter_in: endpoint { | ||
remote-endpoint = <&lcdif_out>; | ||
}; | ||
}; | ||
port@1 { | ||
reg = <1>; | ||
adapter_out: endpoint { | ||
remote-endpoint = <&panel_in>; | ||
}; | ||
}; | ||
}; | ||
|
||
- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
/* | ||
* DRM driver for the legacy Freescale adapter card holding | ||
* Seiko RA169Z20 43WVFIG LCD panel | ||
* | ||
* Copyright (C) 2018 NXP | ||
* | ||
* This program is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU General Public License | ||
* as published by the Free Software Foundation; either version 2 | ||
* of the License, or (at your option) any later version. | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
*/ | ||
|
||
#include <drm/drmP.h> | ||
#include <drm/drm_atomic_helper.h> | ||
#include <drm/drm_crtc_helper.h> | ||
#include <drm/drm_of.h> | ||
#include <drm/drm_panel.h> | ||
#include <drm/drm_probe_helper.h> | ||
#include <linux/of.h> | ||
#include <linux/of_graph.h> | ||
#include <linux/of_platform.h> | ||
|
||
struct seiko_adapter { | ||
struct device *dev; | ||
struct drm_panel *panel; | ||
struct drm_bridge bridge; | ||
struct drm_connector connector; | ||
|
||
u32 bpc; | ||
u32 bus_format; | ||
}; | ||
|
||
static enum drm_connector_status seiko_adapter_connector_detect( | ||
struct drm_connector *connector, bool force) | ||
{ | ||
return connector_status_connected; | ||
} | ||
|
||
static int seiko_adapter_connector_get_modes(struct drm_connector *connector) | ||
{ | ||
int num_modes; | ||
|
||
struct seiko_adapter *adap = container_of(connector, | ||
struct seiko_adapter, | ||
connector); | ||
|
||
num_modes = drm_panel_get_modes(adap->panel); | ||
|
||
/* | ||
* The panel will populate the connector display_info properties with | ||
* fixed numbers, but we need to change them according to our | ||
* configuration. | ||
*/ | ||
connector->display_info.bpc = adap->bpc; | ||
drm_display_info_set_bus_formats(&connector->display_info, | ||
&adap->bus_format, 1); | ||
|
||
return num_modes; | ||
} | ||
|
||
static const struct drm_connector_funcs seiko_adapter_connector_funcs = { | ||
.detect = seiko_adapter_connector_detect, | ||
.fill_modes = drm_helper_probe_single_connector_modes, | ||
.destroy = drm_connector_cleanup, | ||
.reset = drm_atomic_helper_connector_reset, | ||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | ||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | ||
}; | ||
|
||
static const struct drm_connector_helper_funcs | ||
seiko_adapter_connector_helper_funcs = { | ||
.get_modes = seiko_adapter_connector_get_modes, | ||
}; | ||
|
||
static int seiko_adapter_bridge_attach(struct drm_bridge *bridge) | ||
{ | ||
struct seiko_adapter *adap = bridge->driver_private; | ||
struct device *dev = adap->dev; | ||
struct drm_encoder *encoder = bridge->encoder; | ||
struct drm_device *drm; | ||
int ret = 0; | ||
|
||
if (!encoder) { | ||
DRM_DEV_ERROR(dev, "Parent encoder object not found\n"); | ||
return -ENODEV; | ||
} | ||
|
||
drm = encoder->dev; | ||
|
||
/* | ||
* Create the connector for our panel | ||
*/ | ||
|
||
ret = drm_connector_init(drm, &adap->connector, | ||
&seiko_adapter_connector_funcs, | ||
DRM_MODE_CONNECTOR_DPI); | ||
if (ret) { | ||
DRM_DEV_ERROR(dev, "Failed to init drm connector: %d\n", ret); | ||
return ret; | ||
} | ||
|
||
drm_connector_helper_add(&adap->connector, | ||
&seiko_adapter_connector_helper_funcs); | ||
|
||
adap->connector.dpms = DRM_MODE_DPMS_OFF; | ||
drm_connector_attach_encoder(&adap->connector, encoder); | ||
|
||
ret = drm_panel_attach(adap->panel, &adap->connector); | ||
if (ret) { | ||
DRM_DEV_ERROR(dev, "Failed to attach panel: %d\n", ret); | ||
drm_connector_cleanup(&adap->connector); | ||
return ret; | ||
} | ||
|
||
return ret; | ||
} | ||
|
||
static void seiko_adapter_bridge_detach(struct drm_bridge *bridge) | ||
{ | ||
struct seiko_adapter *adap = bridge->driver_private; | ||
|
||
drm_panel_detach(adap->panel); | ||
drm_connector_cleanup(&adap->connector); | ||
} | ||
|
||
static void seiko_adapter_bridge_enable(struct drm_bridge *bridge) | ||
{ | ||
struct seiko_adapter *adap = bridge->driver_private; | ||
struct device *dev = adap->dev; | ||
|
||
if (drm_panel_prepare(adap->panel)) { | ||
DRM_DEV_ERROR(dev, "Failed to prepare panel\n"); | ||
return; | ||
} | ||
|
||
if (drm_panel_enable(adap->panel)) { | ||
DRM_DEV_ERROR(dev, "Failed to enable panel\n"); | ||
drm_panel_unprepare(adap->panel); | ||
} | ||
} | ||
|
||
static void seiko_adapter_bridge_disable(struct drm_bridge *bridge) | ||
{ | ||
struct seiko_adapter *adap = bridge->driver_private; | ||
struct device *dev = adap->dev; | ||
|
||
if (drm_panel_disable(adap->panel)) { | ||
DRM_DEV_ERROR(dev, "failed to disable panel\n"); | ||
return; | ||
} | ||
|
||
if (drm_panel_unprepare(adap->panel)) | ||
DRM_DEV_ERROR(dev, "failed to unprepare panel\n"); | ||
} | ||
|
||
static const struct drm_bridge_funcs seiko_adapter_bridge_funcs = { | ||
.enable = seiko_adapter_bridge_enable, | ||
.disable = seiko_adapter_bridge_disable, | ||
.attach = seiko_adapter_bridge_attach, | ||
.detach = seiko_adapter_bridge_detach, | ||
}; | ||
|
||
static int seiko_adapter_probe(struct platform_device *pdev) | ||
{ | ||
struct device *dev = &pdev->dev; | ||
struct seiko_adapter *adap; | ||
struct device_node *remote; | ||
u32 bus_mode; | ||
int port; | ||
|
||
adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); | ||
if (!adap) | ||
return -ENOMEM; | ||
|
||
of_property_read_u32(dev->of_node, "bus_mode", &bus_mode); | ||
if (bus_mode != 18 && bus_mode != 24) { | ||
dev_err(dev, "Invalid bus_mode: %d\n", bus_mode); | ||
return -EINVAL; | ||
} | ||
|
||
switch (bus_mode) { | ||
case 18: | ||
adap->bpc = 6; | ||
adap->bus_format = MEDIA_BUS_FMT_RGB666_1X18; | ||
break; | ||
case 24: | ||
adap->bpc = 8; | ||
adap->bus_format = MEDIA_BUS_FMT_RGB888_1X24; | ||
break; | ||
} | ||
|
||
for (port = 0; port < 2; port++) { | ||
remote = of_graph_get_remote_node(dev->of_node, port, -1); | ||
if (!remote) { | ||
dev_err(dev, "No remote for port %d\n", port); | ||
return -ENODEV; | ||
} | ||
adap->panel = of_drm_find_panel(remote); | ||
if (!IS_ERR(adap->panel)) | ||
break; | ||
} | ||
if (IS_ERR(adap->panel)) { | ||
dev_err(dev, "No panel found: %ld\n", PTR_ERR(adap->panel)); | ||
return PTR_ERR(adap->panel); | ||
} | ||
|
||
adap->dev = dev; | ||
adap->bridge.driver_private = adap; | ||
adap->bridge.funcs = &seiko_adapter_bridge_funcs; | ||
adap->bridge.of_node = dev->of_node; | ||
|
||
drm_bridge_add(&adap->bridge); | ||
|
||
return 0; | ||
} | ||
|
||
static int seiko_adapter_remove(struct platform_device *pdev) | ||
{ | ||
return 0; | ||
} | ||
|
||
static const struct of_device_id seiko_adapter_dt_ids[] = { | ||
{ .compatible = "nxp,seiko-43wvfig" }, | ||
{ /* sentinel */ } | ||
}; | ||
MODULE_DEVICE_TABLE(of, seiko_adapter_dt_ids); | ||
|
||
static struct platform_driver seiko_adapter_driver = { | ||
.probe = seiko_adapter_probe, | ||
.remove = seiko_adapter_remove, | ||
.driver = { | ||
.of_match_table = seiko_adapter_dt_ids, | ||
.name = "nxp-seiko-adapter", | ||
}, | ||
}; | ||
|
||
module_platform_driver(seiko_adapter_driver); | ||
|
||
MODULE_AUTHOR("NXP Semiconductor"); | ||
MODULE_DESCRIPTION("Seiko 43WVFIG adapter card driver"); | ||
MODULE_LICENSE("GPL"); |