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, ¶m, 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