162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Generic DSI Command Mode panel driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com/
662306a36Sopenharmony_ci * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/backlight.h>
1062306a36Sopenharmony_ci#include <linux/delay.h>
1162306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1262306a36Sopenharmony_ci#include <linux/jiffies.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <drm/drm_connector.h>
1862306a36Sopenharmony_ci#include <drm/drm_mipi_dsi.h>
1962306a36Sopenharmony_ci#include <drm/drm_modes.h>
2062306a36Sopenharmony_ci#include <drm/drm_panel.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <video/mipi_display.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define DCS_GET_ID1		0xda
2562306a36Sopenharmony_ci#define DCS_GET_ID2		0xdb
2662306a36Sopenharmony_ci#define DCS_GET_ID3		0xdc
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define DCS_REGULATOR_SUPPLY_NUM 2
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic const struct of_device_id dsicm_of_match[];
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistruct dsic_panel_data {
3362306a36Sopenharmony_ci	u32 xres;
3462306a36Sopenharmony_ci	u32 yres;
3562306a36Sopenharmony_ci	u32 refresh;
3662306a36Sopenharmony_ci	u32 width_mm;
3762306a36Sopenharmony_ci	u32 height_mm;
3862306a36Sopenharmony_ci	u32 max_hs_rate;
3962306a36Sopenharmony_ci	u32 max_lp_rate;
4062306a36Sopenharmony_ci	bool te_support;
4162306a36Sopenharmony_ci};
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistruct panel_drv_data {
4462306a36Sopenharmony_ci	struct mipi_dsi_device *dsi;
4562306a36Sopenharmony_ci	struct drm_panel panel;
4662306a36Sopenharmony_ci	struct drm_display_mode mode;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	struct mutex lock;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	struct backlight_device *bldev;
5162306a36Sopenharmony_ci	struct backlight_device *extbldev;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	unsigned long	hw_guard_end;	/* next value of jiffies when we can
5462306a36Sopenharmony_ci					 * issue the next sleep in/out command
5562306a36Sopenharmony_ci					 */
5662306a36Sopenharmony_ci	unsigned long	hw_guard_wait;	/* max guard time in jiffies */
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	const struct dsic_panel_data *panel_data;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	struct gpio_desc *reset_gpio;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	struct regulator_bulk_data supplies[DCS_REGULATOR_SUPPLY_NUM];
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	bool use_dsi_backlight;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	/* runtime variables */
6762306a36Sopenharmony_ci	bool enabled;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	bool intro_printed;
7062306a36Sopenharmony_ci};
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic inline struct panel_drv_data *panel_to_ddata(struct drm_panel *panel)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	return container_of(panel, struct panel_drv_data, panel);
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic void dsicm_bl_power(struct panel_drv_data *ddata, bool enable)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	struct backlight_device *backlight;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	if (ddata->bldev)
8262306a36Sopenharmony_ci		backlight = ddata->bldev;
8362306a36Sopenharmony_ci	else if (ddata->extbldev)
8462306a36Sopenharmony_ci		backlight = ddata->extbldev;
8562306a36Sopenharmony_ci	else
8662306a36Sopenharmony_ci		return;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	if (enable)
8962306a36Sopenharmony_ci		backlight_enable(backlight);
9062306a36Sopenharmony_ci	else
9162306a36Sopenharmony_ci		backlight_disable(backlight);
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic void hw_guard_start(struct panel_drv_data *ddata, int guard_msec)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	ddata->hw_guard_wait = msecs_to_jiffies(guard_msec);
9762306a36Sopenharmony_ci	ddata->hw_guard_end = jiffies + ddata->hw_guard_wait;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic void hw_guard_wait(struct panel_drv_data *ddata)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	unsigned long wait = ddata->hw_guard_end - jiffies;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if ((long)wait > 0 && wait <= ddata->hw_guard_wait) {
10562306a36Sopenharmony_ci		set_current_state(TASK_UNINTERRUPTIBLE);
10662306a36Sopenharmony_ci		schedule_timeout(wait);
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic int dsicm_dcs_read_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 *data)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	return mipi_dsi_dcs_read(ddata->dsi, dcs_cmd, data, 1);
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic int dsicm_dcs_write_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 param)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	return mipi_dsi_dcs_write(ddata->dsi, dcs_cmd, &param, 1);
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic int dsicm_sleep_in(struct panel_drv_data *ddata)
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	int r;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	hw_guard_wait(ddata);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	r = mipi_dsi_dcs_enter_sleep_mode(ddata->dsi);
12862306a36Sopenharmony_ci	if (r)
12962306a36Sopenharmony_ci		return r;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	hw_guard_start(ddata, 120);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	usleep_range(5000, 10000);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	return 0;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic int dsicm_sleep_out(struct panel_drv_data *ddata)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	int r;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	hw_guard_wait(ddata);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	r = mipi_dsi_dcs_exit_sleep_mode(ddata->dsi);
14562306a36Sopenharmony_ci	if (r)
14662306a36Sopenharmony_ci		return r;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	hw_guard_start(ddata, 120);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	usleep_range(5000, 10000);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	return 0;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic int dsicm_get_id(struct panel_drv_data *ddata, u8 *id1, u8 *id2, u8 *id3)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	int r;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	r = dsicm_dcs_read_1(ddata, DCS_GET_ID1, id1);
16062306a36Sopenharmony_ci	if (r)
16162306a36Sopenharmony_ci		return r;
16262306a36Sopenharmony_ci	r = dsicm_dcs_read_1(ddata, DCS_GET_ID2, id2);
16362306a36Sopenharmony_ci	if (r)
16462306a36Sopenharmony_ci		return r;
16562306a36Sopenharmony_ci	r = dsicm_dcs_read_1(ddata, DCS_GET_ID3, id3);
16662306a36Sopenharmony_ci	if (r)
16762306a36Sopenharmony_ci		return r;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	return 0;
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cistatic int dsicm_set_update_window(struct panel_drv_data *ddata)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	struct mipi_dsi_device *dsi = ddata->dsi;
17562306a36Sopenharmony_ci	int r;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	r = mipi_dsi_dcs_set_column_address(dsi, 0, ddata->mode.hdisplay - 1);
17862306a36Sopenharmony_ci	if (r < 0)
17962306a36Sopenharmony_ci		return r;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	r = mipi_dsi_dcs_set_page_address(dsi, 0, ddata->mode.vdisplay - 1);
18262306a36Sopenharmony_ci	if (r < 0)
18362306a36Sopenharmony_ci		return r;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	return 0;
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cistatic int dsicm_bl_update_status(struct backlight_device *dev)
18962306a36Sopenharmony_ci{
19062306a36Sopenharmony_ci	struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
19162306a36Sopenharmony_ci	int r = 0;
19262306a36Sopenharmony_ci	int level = backlight_get_brightness(dev);
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	dev_dbg(&ddata->dsi->dev, "update brightness to %d\n", level);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	mutex_lock(&ddata->lock);
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if (ddata->enabled)
19962306a36Sopenharmony_ci		r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
20062306a36Sopenharmony_ci				      level);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	mutex_unlock(&ddata->lock);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	return r;
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_cistatic int dsicm_bl_get_intensity(struct backlight_device *dev)
20862306a36Sopenharmony_ci{
20962306a36Sopenharmony_ci	return backlight_get_brightness(dev);
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic const struct backlight_ops dsicm_bl_ops = {
21362306a36Sopenharmony_ci	.get_brightness = dsicm_bl_get_intensity,
21462306a36Sopenharmony_ci	.update_status  = dsicm_bl_update_status,
21562306a36Sopenharmony_ci};
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic ssize_t num_dsi_errors_show(struct device *dev,
21862306a36Sopenharmony_ci		struct device_attribute *attr, char *buf)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	struct panel_drv_data *ddata = dev_get_drvdata(dev);
22162306a36Sopenharmony_ci	u8 errors = 0;
22262306a36Sopenharmony_ci	int r = -ENODEV;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	mutex_lock(&ddata->lock);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	if (ddata->enabled)
22762306a36Sopenharmony_ci		r = dsicm_dcs_read_1(ddata, MIPI_DCS_GET_ERROR_COUNT_ON_DSI, &errors);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	mutex_unlock(&ddata->lock);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	if (r)
23262306a36Sopenharmony_ci		return r;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	return sysfs_emit(buf, "%d\n", errors);
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic ssize_t hw_revision_show(struct device *dev,
23862306a36Sopenharmony_ci		struct device_attribute *attr, char *buf)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	struct panel_drv_data *ddata = dev_get_drvdata(dev);
24162306a36Sopenharmony_ci	u8 id1, id2, id3;
24262306a36Sopenharmony_ci	int r = -ENODEV;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	mutex_lock(&ddata->lock);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	if (ddata->enabled)
24762306a36Sopenharmony_ci		r = dsicm_get_id(ddata, &id1, &id2, &id3);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	mutex_unlock(&ddata->lock);
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	if (r)
25262306a36Sopenharmony_ci		return r;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	return sysfs_emit(buf, "%02x.%02x.%02x\n", id1, id2, id3);
25562306a36Sopenharmony_ci}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistatic DEVICE_ATTR_RO(num_dsi_errors);
25862306a36Sopenharmony_cistatic DEVICE_ATTR_RO(hw_revision);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_cistatic struct attribute *dsicm_attrs[] = {
26162306a36Sopenharmony_ci	&dev_attr_num_dsi_errors.attr,
26262306a36Sopenharmony_ci	&dev_attr_hw_revision.attr,
26362306a36Sopenharmony_ci	NULL,
26462306a36Sopenharmony_ci};
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_cistatic const struct attribute_group dsicm_attr_group = {
26762306a36Sopenharmony_ci	.attrs = dsicm_attrs,
26862306a36Sopenharmony_ci};
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic void dsicm_hw_reset(struct panel_drv_data *ddata)
27162306a36Sopenharmony_ci{
27262306a36Sopenharmony_ci	gpiod_set_value(ddata->reset_gpio, 1);
27362306a36Sopenharmony_ci	udelay(10);
27462306a36Sopenharmony_ci	/* reset the panel */
27562306a36Sopenharmony_ci	gpiod_set_value(ddata->reset_gpio, 0);
27662306a36Sopenharmony_ci	/* assert reset */
27762306a36Sopenharmony_ci	udelay(10);
27862306a36Sopenharmony_ci	gpiod_set_value(ddata->reset_gpio, 1);
27962306a36Sopenharmony_ci	/* wait after releasing reset */
28062306a36Sopenharmony_ci	usleep_range(5000, 10000);
28162306a36Sopenharmony_ci}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_cistatic int dsicm_power_on(struct panel_drv_data *ddata)
28462306a36Sopenharmony_ci{
28562306a36Sopenharmony_ci	u8 id1, id2, id3;
28662306a36Sopenharmony_ci	int r;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	dsicm_hw_reset(ddata);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	ddata->dsi->mode_flags |= MIPI_DSI_MODE_LPM;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	r = dsicm_sleep_out(ddata);
29362306a36Sopenharmony_ci	if (r)
29462306a36Sopenharmony_ci		goto err;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	r = dsicm_get_id(ddata, &id1, &id2, &id3);
29762306a36Sopenharmony_ci	if (r)
29862306a36Sopenharmony_ci		goto err;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0xff);
30162306a36Sopenharmony_ci	if (r)
30262306a36Sopenharmony_ci		goto err;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	r = dsicm_dcs_write_1(ddata, MIPI_DCS_WRITE_CONTROL_DISPLAY,
30562306a36Sopenharmony_ci			(1<<2) | (1<<5));	/* BL | BCTRL */
30662306a36Sopenharmony_ci	if (r)
30762306a36Sopenharmony_ci		goto err;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	r = mipi_dsi_dcs_set_pixel_format(ddata->dsi, MIPI_DCS_PIXEL_FMT_24BIT);
31062306a36Sopenharmony_ci	if (r)
31162306a36Sopenharmony_ci		goto err;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	r = dsicm_set_update_window(ddata);
31462306a36Sopenharmony_ci	if (r)
31562306a36Sopenharmony_ci		goto err;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	r = mipi_dsi_dcs_set_display_on(ddata->dsi);
31862306a36Sopenharmony_ci	if (r)
31962306a36Sopenharmony_ci		goto err;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	if (ddata->panel_data->te_support) {
32262306a36Sopenharmony_ci		r = mipi_dsi_dcs_set_tear_on(ddata->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
32362306a36Sopenharmony_ci		if (r)
32462306a36Sopenharmony_ci			goto err;
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	/* possible panel bug */
32862306a36Sopenharmony_ci	msleep(100);
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	ddata->enabled = true;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	if (!ddata->intro_printed) {
33362306a36Sopenharmony_ci		dev_info(&ddata->dsi->dev, "panel revision %02x.%02x.%02x\n",
33462306a36Sopenharmony_ci			id1, id2, id3);
33562306a36Sopenharmony_ci		ddata->intro_printed = true;
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	ddata->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	return 0;
34162306a36Sopenharmony_cierr:
34262306a36Sopenharmony_ci	dev_err(&ddata->dsi->dev, "error while enabling panel, issuing HW reset\n");
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	dsicm_hw_reset(ddata);
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	return r;
34762306a36Sopenharmony_ci}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_cistatic int dsicm_power_off(struct panel_drv_data *ddata)
35062306a36Sopenharmony_ci{
35162306a36Sopenharmony_ci	int r;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	ddata->enabled = false;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	r = mipi_dsi_dcs_set_display_off(ddata->dsi);
35662306a36Sopenharmony_ci	if (!r)
35762306a36Sopenharmony_ci		r = dsicm_sleep_in(ddata);
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	if (r) {
36062306a36Sopenharmony_ci		dev_err(&ddata->dsi->dev,
36162306a36Sopenharmony_ci				"error disabling panel, issuing HW reset\n");
36262306a36Sopenharmony_ci		dsicm_hw_reset(ddata);
36362306a36Sopenharmony_ci	}
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	return r;
36662306a36Sopenharmony_ci}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_cistatic int dsicm_prepare(struct drm_panel *panel)
36962306a36Sopenharmony_ci{
37062306a36Sopenharmony_ci	struct panel_drv_data *ddata = panel_to_ddata(panel);
37162306a36Sopenharmony_ci	int r;
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	r = regulator_bulk_enable(ARRAY_SIZE(ddata->supplies), ddata->supplies);
37462306a36Sopenharmony_ci	if (r)
37562306a36Sopenharmony_ci		dev_err(&ddata->dsi->dev, "failed to enable supplies: %d\n", r);
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	return r;
37862306a36Sopenharmony_ci}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_cistatic int dsicm_enable(struct drm_panel *panel)
38162306a36Sopenharmony_ci{
38262306a36Sopenharmony_ci	struct panel_drv_data *ddata = panel_to_ddata(panel);
38362306a36Sopenharmony_ci	int r;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	mutex_lock(&ddata->lock);
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	r = dsicm_power_on(ddata);
38862306a36Sopenharmony_ci	if (r)
38962306a36Sopenharmony_ci		goto err;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	mutex_unlock(&ddata->lock);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	dsicm_bl_power(ddata, true);
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	return 0;
39662306a36Sopenharmony_cierr:
39762306a36Sopenharmony_ci	dev_err(&ddata->dsi->dev, "enable failed (%d)\n", r);
39862306a36Sopenharmony_ci	mutex_unlock(&ddata->lock);
39962306a36Sopenharmony_ci	return r;
40062306a36Sopenharmony_ci}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_cistatic int dsicm_unprepare(struct drm_panel *panel)
40362306a36Sopenharmony_ci{
40462306a36Sopenharmony_ci	struct panel_drv_data *ddata = panel_to_ddata(panel);
40562306a36Sopenharmony_ci	int r;
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	r = regulator_bulk_disable(ARRAY_SIZE(ddata->supplies), ddata->supplies);
40862306a36Sopenharmony_ci	if (r)
40962306a36Sopenharmony_ci		dev_err(&ddata->dsi->dev, "failed to disable supplies: %d\n", r);
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	return r;
41262306a36Sopenharmony_ci}
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_cistatic int dsicm_disable(struct drm_panel *panel)
41562306a36Sopenharmony_ci{
41662306a36Sopenharmony_ci	struct panel_drv_data *ddata = panel_to_ddata(panel);
41762306a36Sopenharmony_ci	int r;
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	dsicm_bl_power(ddata, false);
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	mutex_lock(&ddata->lock);
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	r = dsicm_power_off(ddata);
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	mutex_unlock(&ddata->lock);
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	return r;
42862306a36Sopenharmony_ci}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_cistatic int dsicm_get_modes(struct drm_panel *panel,
43162306a36Sopenharmony_ci			   struct drm_connector *connector)
43262306a36Sopenharmony_ci{
43362306a36Sopenharmony_ci	struct panel_drv_data *ddata = panel_to_ddata(panel);
43462306a36Sopenharmony_ci	struct drm_display_mode *mode;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	mode = drm_mode_duplicate(connector->dev, &ddata->mode);
43762306a36Sopenharmony_ci	if (!mode) {
43862306a36Sopenharmony_ci		dev_err(&ddata->dsi->dev, "failed to add mode %ux%ux@%u kHz\n",
43962306a36Sopenharmony_ci			ddata->mode.hdisplay, ddata->mode.vdisplay,
44062306a36Sopenharmony_ci			ddata->mode.clock);
44162306a36Sopenharmony_ci		return -ENOMEM;
44262306a36Sopenharmony_ci	}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	connector->display_info.width_mm = ddata->panel_data->width_mm;
44562306a36Sopenharmony_ci	connector->display_info.height_mm = ddata->panel_data->height_mm;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	drm_mode_probed_add(connector, mode);
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	return 1;
45062306a36Sopenharmony_ci}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_cistatic const struct drm_panel_funcs dsicm_panel_funcs = {
45362306a36Sopenharmony_ci	.unprepare = dsicm_unprepare,
45462306a36Sopenharmony_ci	.disable = dsicm_disable,
45562306a36Sopenharmony_ci	.prepare = dsicm_prepare,
45662306a36Sopenharmony_ci	.enable = dsicm_enable,
45762306a36Sopenharmony_ci	.get_modes = dsicm_get_modes,
45862306a36Sopenharmony_ci};
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_cistatic int dsicm_probe_of(struct mipi_dsi_device *dsi)
46162306a36Sopenharmony_ci{
46262306a36Sopenharmony_ci	struct backlight_device *backlight;
46362306a36Sopenharmony_ci	struct panel_drv_data *ddata = mipi_dsi_get_drvdata(dsi);
46462306a36Sopenharmony_ci	int err;
46562306a36Sopenharmony_ci	struct drm_display_mode *mode = &ddata->mode;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	ddata->reset_gpio = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW);
46862306a36Sopenharmony_ci	if (IS_ERR(ddata->reset_gpio)) {
46962306a36Sopenharmony_ci		err = PTR_ERR(ddata->reset_gpio);
47062306a36Sopenharmony_ci		dev_err(&dsi->dev, "reset gpio request failed: %d", err);
47162306a36Sopenharmony_ci		return err;
47262306a36Sopenharmony_ci	}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	mode->hdisplay = mode->hsync_start = mode->hsync_end = mode->htotal =
47562306a36Sopenharmony_ci		ddata->panel_data->xres;
47662306a36Sopenharmony_ci	mode->vdisplay = mode->vsync_start = mode->vsync_end = mode->vtotal =
47762306a36Sopenharmony_ci		ddata->panel_data->yres;
47862306a36Sopenharmony_ci	mode->clock = ddata->panel_data->xres * ddata->panel_data->yres *
47962306a36Sopenharmony_ci		ddata->panel_data->refresh / 1000;
48062306a36Sopenharmony_ci	mode->width_mm = ddata->panel_data->width_mm;
48162306a36Sopenharmony_ci	mode->height_mm = ddata->panel_data->height_mm;
48262306a36Sopenharmony_ci	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
48362306a36Sopenharmony_ci	drm_mode_set_name(mode);
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	ddata->supplies[0].supply = "vpnl";
48662306a36Sopenharmony_ci	ddata->supplies[1].supply = "vddi";
48762306a36Sopenharmony_ci	err = devm_regulator_bulk_get(&dsi->dev, ARRAY_SIZE(ddata->supplies),
48862306a36Sopenharmony_ci				      ddata->supplies);
48962306a36Sopenharmony_ci	if (err)
49062306a36Sopenharmony_ci		return err;
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	backlight = devm_of_find_backlight(&dsi->dev);
49362306a36Sopenharmony_ci	if (IS_ERR(backlight))
49462306a36Sopenharmony_ci		return PTR_ERR(backlight);
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	/* If no backlight device is found assume native backlight support */
49762306a36Sopenharmony_ci	if (backlight)
49862306a36Sopenharmony_ci		ddata->extbldev = backlight;
49962306a36Sopenharmony_ci	else
50062306a36Sopenharmony_ci		ddata->use_dsi_backlight = true;
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	return 0;
50362306a36Sopenharmony_ci}
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_cistatic int dsicm_probe(struct mipi_dsi_device *dsi)
50662306a36Sopenharmony_ci{
50762306a36Sopenharmony_ci	struct panel_drv_data *ddata;
50862306a36Sopenharmony_ci	struct backlight_device *bldev = NULL;
50962306a36Sopenharmony_ci	struct device *dev = &dsi->dev;
51062306a36Sopenharmony_ci	int r;
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	dev_dbg(dev, "probe\n");
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
51562306a36Sopenharmony_ci	if (!ddata)
51662306a36Sopenharmony_ci		return -ENOMEM;
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	mipi_dsi_set_drvdata(dsi, ddata);
51962306a36Sopenharmony_ci	ddata->dsi = dsi;
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	ddata->panel_data = of_device_get_match_data(dev);
52262306a36Sopenharmony_ci	if (!ddata->panel_data)
52362306a36Sopenharmony_ci		return -ENODEV;
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	r = dsicm_probe_of(dsi);
52662306a36Sopenharmony_ci	if (r)
52762306a36Sopenharmony_ci		return r;
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	mutex_init(&ddata->lock);
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	dsicm_hw_reset(ddata);
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	drm_panel_init(&ddata->panel, dev, &dsicm_panel_funcs,
53462306a36Sopenharmony_ci		       DRM_MODE_CONNECTOR_DSI);
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	if (ddata->use_dsi_backlight) {
53762306a36Sopenharmony_ci		struct backlight_properties props = { 0 };
53862306a36Sopenharmony_ci		props.max_brightness = 255;
53962306a36Sopenharmony_ci		props.type = BACKLIGHT_RAW;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci		bldev = devm_backlight_device_register(dev, dev_name(dev),
54262306a36Sopenharmony_ci			dev, ddata, &dsicm_bl_ops, &props);
54362306a36Sopenharmony_ci		if (IS_ERR(bldev)) {
54462306a36Sopenharmony_ci			r = PTR_ERR(bldev);
54562306a36Sopenharmony_ci			goto err_bl;
54662306a36Sopenharmony_ci		}
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci		ddata->bldev = bldev;
54962306a36Sopenharmony_ci	}
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci	r = sysfs_create_group(&dev->kobj, &dsicm_attr_group);
55262306a36Sopenharmony_ci	if (r) {
55362306a36Sopenharmony_ci		dev_err(dev, "failed to create sysfs files\n");
55462306a36Sopenharmony_ci		goto err_bl;
55562306a36Sopenharmony_ci	}
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci	dsi->lanes = 2;
55862306a36Sopenharmony_ci	dsi->format = MIPI_DSI_FMT_RGB888;
55962306a36Sopenharmony_ci	dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS |
56062306a36Sopenharmony_ci			  MIPI_DSI_MODE_NO_EOT_PACKET;
56162306a36Sopenharmony_ci	dsi->hs_rate = ddata->panel_data->max_hs_rate;
56262306a36Sopenharmony_ci	dsi->lp_rate = ddata->panel_data->max_lp_rate;
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	drm_panel_add(&ddata->panel);
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	r = mipi_dsi_attach(dsi);
56762306a36Sopenharmony_ci	if (r < 0)
56862306a36Sopenharmony_ci		goto err_dsi_attach;
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	return 0;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_cierr_dsi_attach:
57362306a36Sopenharmony_ci	drm_panel_remove(&ddata->panel);
57462306a36Sopenharmony_ci	sysfs_remove_group(&dsi->dev.kobj, &dsicm_attr_group);
57562306a36Sopenharmony_cierr_bl:
57662306a36Sopenharmony_ci	if (ddata->extbldev)
57762306a36Sopenharmony_ci		put_device(&ddata->extbldev->dev);
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci	return r;
58062306a36Sopenharmony_ci}
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_cistatic void dsicm_remove(struct mipi_dsi_device *dsi)
58362306a36Sopenharmony_ci{
58462306a36Sopenharmony_ci	struct panel_drv_data *ddata = mipi_dsi_get_drvdata(dsi);
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	dev_dbg(&dsi->dev, "remove\n");
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci	mipi_dsi_detach(dsi);
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	drm_panel_remove(&ddata->panel);
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	sysfs_remove_group(&dsi->dev.kobj, &dsicm_attr_group);
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	if (ddata->extbldev)
59562306a36Sopenharmony_ci		put_device(&ddata->extbldev->dev);
59662306a36Sopenharmony_ci}
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_cistatic const struct dsic_panel_data taal_data = {
59962306a36Sopenharmony_ci	.xres = 864,
60062306a36Sopenharmony_ci	.yres = 480,
60162306a36Sopenharmony_ci	.refresh = 60,
60262306a36Sopenharmony_ci	.width_mm = 0,
60362306a36Sopenharmony_ci	.height_mm = 0,
60462306a36Sopenharmony_ci	.max_hs_rate = 300000000,
60562306a36Sopenharmony_ci	.max_lp_rate = 10000000,
60662306a36Sopenharmony_ci	.te_support = true,
60762306a36Sopenharmony_ci};
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_cistatic const struct dsic_panel_data himalaya_data = {
61062306a36Sopenharmony_ci	.xres = 480,
61162306a36Sopenharmony_ci	.yres = 864,
61262306a36Sopenharmony_ci	.refresh = 60,
61362306a36Sopenharmony_ci	.width_mm = 49,
61462306a36Sopenharmony_ci	.height_mm = 88,
61562306a36Sopenharmony_ci	.max_hs_rate = 300000000,
61662306a36Sopenharmony_ci	.max_lp_rate = 10000000,
61762306a36Sopenharmony_ci	.te_support = false,
61862306a36Sopenharmony_ci};
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_cistatic const struct dsic_panel_data droid4_data = {
62162306a36Sopenharmony_ci	.xres = 540,
62262306a36Sopenharmony_ci	.yres = 960,
62362306a36Sopenharmony_ci	.refresh = 60,
62462306a36Sopenharmony_ci	.width_mm = 50,
62562306a36Sopenharmony_ci	.height_mm = 89,
62662306a36Sopenharmony_ci	.max_hs_rate = 300000000,
62762306a36Sopenharmony_ci	.max_lp_rate = 10000000,
62862306a36Sopenharmony_ci	.te_support = false,
62962306a36Sopenharmony_ci};
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_cistatic const struct of_device_id dsicm_of_match[] = {
63262306a36Sopenharmony_ci	{ .compatible = "tpo,taal", .data = &taal_data },
63362306a36Sopenharmony_ci	{ .compatible = "nokia,himalaya", &himalaya_data },
63462306a36Sopenharmony_ci	{ .compatible = "motorola,droid4-panel", &droid4_data },
63562306a36Sopenharmony_ci	{},
63662306a36Sopenharmony_ci};
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, dsicm_of_match);
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_cistatic struct mipi_dsi_driver dsicm_driver = {
64162306a36Sopenharmony_ci	.probe = dsicm_probe,
64262306a36Sopenharmony_ci	.remove = dsicm_remove,
64362306a36Sopenharmony_ci	.driver = {
64462306a36Sopenharmony_ci		.name = "panel-dsi-cm",
64562306a36Sopenharmony_ci		.of_match_table = dsicm_of_match,
64662306a36Sopenharmony_ci	},
64762306a36Sopenharmony_ci};
64862306a36Sopenharmony_cimodule_mipi_dsi_driver(dsicm_driver);
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ciMODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
65162306a36Sopenharmony_ciMODULE_DESCRIPTION("Generic DSI Command Mode Panel Driver");
65262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
653