Last active
August 11, 2022 22:02
-
-
Save macromorgan/0f39ef720642ea09d1db2890a6d6e3d1 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
gpio_spi: spi { | |
compatible = "spi-gpio"; | |
pinctrl-names = "default"; | |
pinctrl-0 = <&spi_gpios>; | |
#address-cells = <1>; | |
#size-cells = <0>; | |
sck-gpios = <&gpio4 RK_PB3 GPIO_ACTIVE_HIGH>; | |
mosi-gpios = <&gpio4 RK_PB0 GPIO_ACTIVE_HIGH>; | |
cs-gpios = <&gpio4 RK_PA7 GPIO_ACTIVE_HIGH>; | |
num-chipselects = <0>; | |
panel@0 { | |
compatible = "samsung,ams495qa01"; | |
reg = <0>; | |
pinctrl-names = "default"; | |
pinctrl-0 = <&lcd_enable>, <&lcd_reset>; | |
enable-gpios = <&gpio4 RK_PB7 GPIO_ACTIVE_HIGH>; | |
reset-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>; | |
vci-supply = <&vcc3v3_lcd0_n>; | |
vccio-supply = <&vcc3v3_lcd0_n>; | |
port { | |
mipi_in_panel: endpoint@0 { | |
remote-endpoint = <&mipi_out_panel>; | |
}; | |
}; | |
}; | |
}; |
This file contains hidden or 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
// SPDX-License-Identifier: GPL-2.0 | |
#include <drm/drm_mipi_dbi.h> | |
#include <drm/drm_mipi_dsi.h> | |
#include <drm/drm_modes.h> | |
#include <drm/drm_panel.h> | |
#include <linux/delay.h> | |
#include <linux/gpio/consumer.h> | |
#include <linux/init.h> | |
#include <linux/kernel.h> | |
#include <linux/media-bus-format.h> | |
#include <linux/module.h> | |
#include <linux/of.h> | |
#include <linux/of_graph.h> | |
#include <linux/regulator/consumer.h> | |
#include <linux/spi/spi.h> | |
#include <video/mipi_display.h> | |
struct ams495qa01 { | |
/** @dev: the container device */ | |
struct device *dev; | |
/** @dbi: the DBI bus abstraction handle */ | |
struct mipi_dbi dbi; | |
/** @panel: the DRM panel instance for this device */ | |
struct drm_panel panel; | |
/** @reset: reset GPIO line */ | |
struct gpio_desc *reset; | |
/** @enable: enable GPIO line */ | |
struct gpio_desc *enable; | |
/** @regulators: VCCIO and VIO supply regulators */ | |
struct regulator_bulk_data regulators[2]; | |
/** @dsi_dev: DSI child device (panel) */ | |
struct mipi_dsi_device *dsi_dev; | |
}; | |
static const struct drm_display_mode ams495qa01_mode = { | |
.clock = 33500, | |
.hdisplay = 960, | |
.hsync_start = 960 + 10, | |
.hsync_end = 960 + 10 + 2, | |
.htotal = 960 + 10 + 2 + 10, | |
.vdisplay = 544, | |
.vsync_start = 544 + 10, | |
.vsync_end = 544 + 10 + 2, | |
.vtotal = 544 + 10 + 2 + 10, | |
.width_mm = 111, | |
.height_mm = 63, | |
}; | |
static inline struct ams495qa01 *to_ams495qa01(struct drm_panel *panel) | |
{ | |
return container_of(panel, struct ams495qa01, panel); | |
} | |
static int ams495qa01_prepare(struct drm_panel *panel) | |
{ | |
struct ams495qa01 *db = to_ams495qa01(panel); | |
struct mipi_dbi *dbi = &db->dbi; | |
int ret; | |
/* Power up */ | |
ret = regulator_bulk_enable(ARRAY_SIZE(db->regulators), | |
db->regulators); | |
if (ret) { | |
dev_err(db->dev, "failed to enable regulators: %d\n", ret); | |
return ret; | |
} | |
/* Enable */ | |
if (db->enable) | |
gpiod_set_value_cansleep(db->enable, 1); | |
usleep_range(2000, 3000); | |
/* Reset */ | |
gpiod_set_value_cansleep(db->reset, 1); | |
usleep_range(1000, 5000); | |
gpiod_set_value_cansleep(db->reset, 0); | |
msleep(20); | |
/* Panel Init Sequence */ | |
mipi_dbi_command(dbi, 0xf0, 0x5a, 0x5a); | |
mipi_dbi_command(dbi, 0xf1, 0x5a, 0x5a); | |
mipi_dbi_command(dbi, 0xb0, 0x02); | |
mipi_dbi_command(dbi, 0xf3, 0x3b); | |
mipi_dbi_command(dbi, 0xf4, 0x33, 0x42, 0x00, 0x08); | |
mipi_dbi_command(dbi, 0xf5, 0x00, 0x06, 0x26, 0x35, 0x03); | |
mipi_dbi_command(dbi, 0xf6, 0x02); | |
mipi_dbi_command(dbi, 0xc6, 0x0B, 0x00, 0x00, 0x3C, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00); | |
mipi_dbi_command(dbi, 0xf7, 0x20); | |
mipi_dbi_command(dbi, 0xf5, 0x00, 0x06, 0x27, 0x35, 0x03); | |
mipi_dbi_command(dbi, 0xb2, 0x06, 0x06, 0x06, 0x06); | |
mipi_dbi_command(dbi, 0xb1, 0x07, 0x00, 0x10); | |
mipi_dbi_command(dbi, 0xf8, 0x7f, 0x7a, 0x89, 0x67, 0x26, 0x38, 0x00, 0x00, 0x09, 0x67, 0x70, 0x88, 0x7a, 0x76, 0x05, 0x09, 0x23, 0x23, 0x23); | |
mipi_dbi_command(dbi, 0x11); | |
msleep(200); | |
mipi_dbi_command(dbi, 0x29); | |
msleep(10); | |
mipi_dbi_command(dbi, 0xb5, 0xff, 0xef, 0x35, 0x42, 0x0d, 0xd7, 0xff, 0x07, 0xff, 0xff, 0xfd, 0x00, 0x01, 0xff, 0x05, 0x12, 0x0f, 0xff, 0xff, 0xff, 0xff); | |
mipi_dbi_command(dbi, 0xb4, 0x15); | |
mipi_dbi_command(dbi, 0xb3, 0x00); | |
mipi_dbi_command(dbi, 0xf9, 0x01, 0x9f, 0x9f, 0xbe, 0xcf, 0xd7, 0xc9, 0xc2, 0xcb, 0xbb, 0xe1, 0xe3, 0xde, 0xd6, 0xd0, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, 0x00, 0x2f); | |
mipi_dbi_command(dbi, 0xf9, 0x00); | |
mipi_dbi_command(dbi, 0x26, 0x00); | |
mipi_dbi_command(dbi, 0xb2, 0x12); | |
msleep(200); | |
mipi_dbi_command(dbi, 0x11); | |
msleep(200); | |
mipi_dbi_command(dbi, 0x29); | |
msleep(10); | |
return 0; | |
} | |
static int ams495qa01_unprepare(struct drm_panel *panel) | |
{ | |
struct ams495qa01 *db = to_ams495qa01(panel); | |
struct mipi_dbi *dbi = &db->dbi; | |
/* Panel Exit Sequence */ | |
mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF); | |
msleep(20); | |
mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE); | |
msleep(10); | |
gpiod_set_value_cansleep(db->reset, 0); | |
if (db->enable) | |
gpiod_set_value_cansleep(db->enable, 0); | |
regulator_bulk_disable(ARRAY_SIZE(db->regulators), | |
db->regulators); | |
msleep(20); | |
return 0; | |
} | |
static int ams495qa01_get_modes(struct drm_panel *panel, | |
struct drm_connector *connector) | |
{ | |
struct ams495qa01 *db = to_ams495qa01(panel); | |
struct drm_display_mode *mode; | |
static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; | |
mode = drm_mode_duplicate(connector->dev, &ams495qa01_mode); | |
if (!mode) { | |
dev_err(db->dev, "failed to add mode\n"); | |
return -ENOMEM; | |
} | |
connector->display_info.bpc = 8; | |
connector->display_info.width_mm = mode->width_mm; | |
connector->display_info.height_mm = mode->height_mm; | |
connector->display_info.bus_flags = | |
DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; | |
drm_display_info_set_bus_formats(&connector->display_info, | |
&bus_format, 1); | |
drm_mode_set_name(mode); | |
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; | |
drm_mode_probed_add(connector, mode); | |
return 1; | |
} | |
static const struct drm_panel_funcs ams495qa01_drm_funcs = { | |
.unprepare = ams495qa01_unprepare, | |
.prepare = ams495qa01_prepare, | |
.get_modes = ams495qa01_get_modes, | |
}; | |
static int ams495qa01_probe(struct spi_device *spi) | |
{ | |
struct device *dev = &spi->dev; | |
struct device_node *endpoint, *dsi_host_node; | |
struct mipi_dsi_host *dsi_host; | |
struct ams495qa01 *db; | |
int ret; | |
struct mipi_dsi_device_info info = { | |
.type = "dupa", | |
.channel = 0, | |
.node = NULL, | |
}; | |
db = devm_kzalloc(dev, sizeof(*db), GFP_KERNEL); | |
if (!db) | |
return -ENOMEM; | |
spi_set_drvdata(spi, db); | |
db->dev = dev; | |
/* | |
* VCI is the analog voltage supply | |
* VCCIO is the digital I/O voltage supply | |
*/ | |
db->regulators[0].supply = "vci"; | |
db->regulators[1].supply = "vccio"; | |
ret = devm_regulator_bulk_get(dev, | |
ARRAY_SIZE(db->regulators), | |
db->regulators); | |
if (ret) | |
return dev_err_probe(dev, ret, "failed to get regulators\n"); | |
db->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); | |
if (IS_ERR(db->reset)) { | |
ret = PTR_ERR(db->reset); | |
return dev_err_probe(dev, ret, "no RESET GPIO\n"); | |
} | |
db->enable = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH); | |
if (IS_ERR(db->enable)) { | |
ret = PTR_ERR(db->enable); | |
return dev_err_probe(dev, ret, "cannot get ENABLE GPIO\n"); | |
} | |
ret = mipi_dbi_spi_init(spi, &db->dbi, NULL); | |
if (ret) | |
return dev_err_probe(dev, ret, "MIPI DBI init failed\n"); | |
endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); | |
if (!endpoint) { | |
dev_err(dev, "failed to get endpoint\n"); | |
return -ENODEV; | |
} | |
dsi_host_node = of_graph_get_remote_port_parent(endpoint); | |
if (!dsi_host_node) { | |
dev_err(dev, "failed to get remote port parent\n"); | |
goto put_endpoint; | |
} | |
dsi_host = of_find_mipi_dsi_host_by_node(dsi_host_node); | |
if (!dsi_host) { | |
dev_err(dev, "failed to find dsi host\n"); | |
goto put_host; | |
} | |
info.node = of_graph_get_remote_port(endpoint); | |
if (!info.node) { | |
dev_err(dev, "failed to get remote port node\n"); | |
ret = -ENODEV; | |
goto put_host; | |
} | |
db->dsi_dev = devm_mipi_dsi_device_register_full(dev, dsi_host, &info); | |
if (IS_ERR(db->dsi_dev)) { | |
dev_err(dev, "failed to register dsi device: %ld\n", | |
PTR_ERR(db->dsi_dev)); | |
ret = PTR_ERR(db->dsi_dev); | |
goto put_host; | |
} | |
db->dsi_dev->lanes = 2; | |
db->dsi_dev->format = MIPI_DSI_FMT_RGB888; | |
db->dsi_dev->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | | |
MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET | | |
MIPI_DSI_CLOCK_NON_CONTINUOUS; | |
drm_panel_init(&db->panel, dev, &ams495qa01_drm_funcs, | |
DRM_MODE_CONNECTOR_DSI); | |
drm_panel_add(&db->panel); | |
ret = devm_mipi_dsi_attach(dev, db->dsi_dev); | |
if (ret < 0) { | |
dev_err(dev, "mipi_dsi_attach failed: %d\n", ret); | |
drm_panel_remove(&db->panel); | |
return ret; | |
} | |
printk("Finished probing DBI Samsung panel\n"); | |
of_node_put(dsi_host_node); | |
of_node_put(endpoint); | |
return 0; | |
put_host: | |
of_node_put(dsi_host_node); | |
put_endpoint: | |
of_node_put(endpoint); | |
return -ENODEV; | |
} | |
static void ams495qa01_remove(struct spi_device *spi) | |
{ | |
struct ams495qa01 *db = spi_get_drvdata(spi); | |
drm_panel_remove(&db->panel); | |
} | |
static const struct of_device_id ams495qa01_match[] = { | |
{ .compatible = "samsung,ams495qa01", }, | |
{}, | |
}; | |
MODULE_DEVICE_TABLE(of, ams495qa01_match); | |
static const struct spi_device_id ams495qa01_ids[] = { | |
{ "ams495qa01", 0 }, | |
{}, | |
}; | |
MODULE_DEVICE_TABLE(spi, ams495qa01_ids); | |
static struct spi_driver ams495qa01_driver = { | |
.probe = ams495qa01_probe, | |
.remove = ams495qa01_remove, | |
.id_table = ams495qa01_ids, | |
.driver = { | |
.name = "ams495qa01-panel", | |
.of_match_table = ams495qa01_match, | |
}, | |
}; | |
module_spi_driver(ams495qa01_driver); | |
MODULE_AUTHOR("Chris Morgan <[email protected]>"); | |
MODULE_DESCRIPTION("Samsung ams495qa01 panel driver"); | |
MODULE_LICENSE("GPL v2"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment