Merge commit 'a54e105da36b4b00243c5c58dba3f59aff3d5665'

* commit 'a54e105da36b4b00243c5c58dba3f59aff3d5665':
  drm/bridge: synopsys: dw-hdmi-qp: Fix crash when accessing audio regs
  arm64: dts: rockchip: rk3588-evb1: add edp 8lanes local dimming board
  drm/rockchip: Add support for dimming panel driver

Change-Id: Ie60187c437294804196badfa2e850e59d13be384
This commit is contained in:
Tao Huang
2025-07-18 09:19:37 +08:00
6 changed files with 1194 additions and 3 deletions

View File

@@ -308,6 +308,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576s-tablet-v10.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-evb1-lp4-v10.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-evb1-lp4-v10-dsi-dsc-MV2100UZ1.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-evb1-lp4-v10-edp-8lanes-M280DCA.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-evb1-lp4-v10-edp-8lanes-TPM270WR1.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-evb1-lp4-v10-edp-NE160QAM-NX1.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-evb1-lp4-v10-edp-NV140QUM-N61.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-evb1-lp4-v10-hdmi2dp.dtb

View File

@@ -0,0 +1,308 @@
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2024 Rockchip Electronics Co., Ltd.
*
*/
/dts-v1/;
#include "rk3588-evb1-lp4.dtsi"
#include "rk3588-evb1-imx415.dtsi"
#include "rk3588-android.dtsi"
/ {
model = "Rockchip RK3588 EVB1 LP4 V10 Board + RK3588 EDP 8LANES V10 Ext Board";
compatible = "rockchip,rk3588-evb1-lp4-v10-edp-8lanes-TPM270WR1", "rockchip,rk3588";
vcc3v3_edp_bl: vcc3v3-edp-bl {
compatible = "regulator-fixed";
regulator-name = "vcc3v3_edp_bl";
regulator-boot-on;
regulator-always-on;
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
enable-active-high;
gpio = <&gpio4 RK_PC0 GPIO_ACTIVE_HIGH>;
vin-supply = <&vcc12v_dcin>;
};
vcc3v3_edp: vcc3v3-edp {
compatible = "regulator-fixed";
regulator-name = "vcc3v3_edp";
regulator-boot-on;
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
enable-active-high;
gpio = <&gpio3 RK_PC6 GPIO_ACTIVE_HIGH>;
vin-supply = <&vcc12v_dcin>;
};
};
&bt_sco {
status = "okay";
};
&bt_sound {
status = "okay";
};
&dsi0 {
status = "disabled";
};
&edp0 {
force-hpd;
status = "okay";
rockchip,dual-channel;
rockchip,data-swap;
ports {
port@1 {
reg = <1>;
edp0_out_panel: endpoint {
remote-endpoint = <&panel_in_edp0>;
};
};
};
};
&edp0_in_vp0 {
status = "okay";
};
&edp0_in_vp1 {
status = "disabled";
};
&edp0_in_vp2 {
status = "disabled";
};
&edp1 {
force-hpd;
status = "okay";
ports {
port@1 {
reg = <1>;
edp1_out_panel: endpoint {
remote-endpoint = <&panel_in_edp1>;
};
};
};
};
&edp1_in_vp0 {
status = "okay";
};
&edp1_in_vp1 {
status = "disabled";
};
&edp1_in_vp2 {
status = "disabled";
};
&hdmi0 {
status = "disabled";
};
&hdmi0_in_vp0 {
status = "disabled";
};
&hdmi0_sound {
status = "disabled";
};
&hdmi1 {
status = "disabled";
};
&hdmi1_in_vp1 {
status = "disabled";
};
&hdmi1_sound {
status = "disabled";
};
&hdptxphy0 {
status = "okay";
};
&hdptxphy1 {
status = "okay";
};
&hdptxphy_hdmi0 {
status = "disabled";
};
&hdptxphy_hdmi1 {
status = "disabled";
};
&i2s2_2ch {
status = "okay";
};
&pcie2x1l0 {
status = "disabled";
};
&pcie3x4 {
status = "disabled";
};
&route_dsi0 {
status = "disabled";
};
&route_edp0 {
status = "disabled";
connect = <&vp0_out_edp0>;
};
&route_edp1 {
status = "disabled";
connect = <&vp0_out_edp1>;
};
&route_hdmi0 {
status = "disabled";
};
&route_hdmi1 {
status = "disabled";
};
&spi0 {
pinctrl-names = "default";
pinctrl-0 = <&spi0m1_cs1 &spi0m1_pins>;
rockchip,poll-only;
status = "okay";
panel-edp@1 {
compatible = "rockchip,dimming-panel";
reg = <1>;
spi-max-frequency = <10000000>;
lden-gpios = <&gpio4 RK_PA5 GPIO_ACTIVE_HIGH>;
blen-gpios = <&gpio4 RK_PB4 GPIO_ACTIVE_HIGH>;
sync-gpios = <&gpio4 RK_PA3 GPIO_ACTIVE_HIGH>;
dbcl-gpios = <&gpio4 RK_PA4 GPIO_ACTIVE_HIGH>;
backlight = <&backlight>;
power-supply = <&vcc3v3_edp>;
enable-gpios = <&gpio4 RK_PC1 GPIO_ACTIVE_HIGH>;
prepare-delay-ms = <120>;
enable-delay-ms = <120>;
unprepare-delay-ms = <120>;
disable-delay-ms = <120>;
hzone-num = <48>;
vzone-num = <24>;
brightness-max = <255>;
brightness-min = <0>;
brightness-bpc = <8>;
command-header = [
aa // indicator
81 // command
00 // hdr on/off
0c // sdr current
20 // hdr current
00 // reserve
];
command-tail = [
00 // checknum
00 // end
];
/*
* The demo configs for 16bpc:
* brightness-bpc = <16>;
* command-header = [
* 00 aa // indicator
* 00 81 // command
* 00 00 // hdr on/off
* 00 0c // sdr current
* 00 20 // hdr current
* 00 00 // reserve
* ];
* command-tail = [
* 00 00 // checknum
* 00 00 // end
* ];
*/
status = "okay";
display-timings {
native-mode = <&timing_4kp60_dimming>;
timing_4kp60_dimming: timing2 {
clock-frequency = <528000000>;
hactive = <3840>;
vactive = <2160>;
hfront-porch = <60>;
hsync-len = <40>;
hback-porch = <60>;
vfront-porch = <15>;
vsync-len = <10>;
vback-porch = <15>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <0>;
};
timing_4kp144_dimming: timing3 {
clock-frequency = <1267200000>;
hactive = <3840>;
vactive = <2160>;
hfront-porch = <60>;
hsync-len = <40>;
hback-porch = <60>;
vfront-porch = <15>;
vsync-len = <10>;
vback-porch = <15>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <0>;
};
};
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
panel_in_edp0: endpoint {
remote-endpoint = <&edp0_out_panel>;
};
};
port@1 {
reg = <1>;
panel_in_edp1: endpoint {
remote-endpoint = <&edp1_out_panel>;
};
};
};
};
};
&vop {
assigned-clocks = <&cru ACLK_VOP>;
assigned-clock-rates = <800000000>;
};
&vp0 {
assigned-clocks = <&cru DCLK_VOP0_SRC>;
assigned-clock-parents = <&cru PLL_V0PLL>;
};
&vp2 {
/delete-property/ assigned-clocks;
/delete-property/ assigned-clock-parents;
};

View File

@@ -3765,6 +3765,8 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge,
mutex_lock(&hdmi->audio_mutex);
if (hdmi->plat_data->dclk_set)
hdmi->plat_data->dclk_set(data, true, hdmi->vp_id);
if (hdmi->plat_data->crtc_post_enable)
hdmi->plat_data->crtc_post_enable(data, bridge->encoder->crtc);
hdmi->dclk_en = true;
mutex_unlock(&hdmi->audio_mutex);
}
@@ -3772,9 +3774,6 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge,
if (link_cfg && link_cfg->frl_mode)
queue_work(hdmi->workqueue, &hdmi->flt_work);
if (hdmi->plat_data->crtc_post_enable)
hdmi->plat_data->crtc_post_enable(data, bridge->encoder->crtc);
dw_hdmi_qp_init_audio_infoframe(hdmi);
dw_hdmi_qp_audio_enable(hdmi);
hdmi_clk_regenerator_update_pixel_clock(hdmi);

View File

@@ -82,6 +82,15 @@ config ROCKCHIP_CDN_DP
RK3399 based SoC, you should select this
option.
config ROCKCHIP_DIMMING_PANEL
bool "Rockchip dimming panel support"
depends on SPI
default n
help
Choose this option to enable support for generic dimming panel
which supports to adjust the backlight brightness of different
zones.
config ROCKCHIP_DRM_TVE
bool "Rockchip TVE support"
depends on DRM_ROCKCHIP

View File

@@ -34,6 +34,7 @@ obj-$(CONFIG_ROCKCHIP_DW_HDCP2) += dw_hdcp2.o
obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o
obj-$(CONFIG_DRM_ROCKCHIP_RK618) += rk618/
obj-$(CONFIG_ROCKCHIP_EBC_DEV) += ebc-dev/
obj-$(CONFIG_ROCKCHIP_DIMMING_PANEL) += rockchip_dimming_panel.o
rockchip_aux_client-y := rockchip_dp_mst_aux_client.o rockchip_dp_mst_aux_client_helper.o
obj-$(CONFIG_ROCKCHIP_DP_MST_AUX_CLIENT) += rockchip_aux_client.o

View File

@@ -0,0 +1,873 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2025 Rockchip Electronics Co., Ltd.
* Author: Damon Ding <damon.ding@rock-chips.com>
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/media-bus-format.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/rockchip-panel-notifier.h>
#include <linux/spi/spi.h>
#include <video/display_timing.h>
#include <video/of_display_timing.h>
#include <video/videomode.h>
#include <drm/drm_crtc.h>
#include <drm/drm_vblank.h>
#include "rockchip_drm_drv.h"
#define MAX_DIMMING_PANELS 8
static DECLARE_BITMAP(allocated_dimming_panels, MAX_DIMMING_PANELS);
static struct class *rockchip_dimming_class;
struct rockchip_dimming_panel {
struct device *dev;
struct device *dimming_dev;
struct drm_crtc *crtc;
struct drm_panel base;
uint8_t id;
const struct drm_display_mode *modes;
uint32_t num_modes;
/** @delay: Structure containing various delay values for this panel. */
/**
* @prepare: the time (in milliseconds) that it takes for the panel to
* become ready and start receiving video data
* @hpd_absent_delay: Add this to the prepare delay if we know Hot
* Plug Detect isn't used.
* @enable: the time (in milliseconds) that it takes for the panel to
* display the first valid frame after starting to receive
* video data
* @disable: the time (in milliseconds) that it takes for the panel to
* turn the display off (no content is visible)
* @unprepare: the time (in milliseconds) that it takes for the panel
* to power itself down completely
* @reset: the time (in milliseconds) that it takes for the panel
* to reset itself completely
* @init: the time (in milliseconds) that it takes for the panel to
* send init command sequence after reset deassert
* @vsync_hold: the time (in microseconds) that it takes for the panel to
* hold the vsync signal high
* @vysnc_back: the time (in microseconds) that it takes for the panel to
* delay the vsync signal
*/
struct {
uint32_t prepare;
uint32_t enable;
uint32_t disable;
uint32_t unprepare;
uint32_t reset;
uint32_t init;
uint32_t vsync_hold;
uint32_t vsync_back;
} delay;
struct regulator *supply;
struct gpio_desc *enable_gpio;
struct gpio_desc *reset_gpio;
struct gpio_desc *lden_gpio;
struct gpio_desc *blen_gpio;
struct gpio_desc *sync_gpio;
struct gpio_desc *dbcl_gpio;
struct kthread_worker *dimming_worker;
struct kthread_delayed_work dimming_delayed_work;
struct rockchip_panel_notifier panel_notifier;
struct rockchip_drm_sub_dev sub_dev;
/** @bus_format: See MEDIA_BUS_FMT_... defines. */
uint32_t bus_format;
/** @bus_flags: See DRM_BUS_FLAG_... defines. */
uint32_t bus_flags;
void *data;
bool enabled;
bool prepared;
uint32_t checksum;
uint32_t hzone_num;
uint32_t vzone_num;
uint32_t zone_max;
uint32_t brightness_max;
uint32_t brightness_min;
uint32_t brightness_bpc;
uint32_t cmd_element_size;
uint32_t cmd_header_len;
uint32_t cmd_tail_len;
uint8_t *cmd_header;
uint8_t *cmd_tail;
};
static ssize_t crtc_id_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct spi_device *spi = dev_get_drvdata(dev);
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
return sprintf(buf, "%d\n", dimming_panel->crtc ? dimming_panel->crtc->base.id : 0);
}
static ssize_t checksum_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct spi_device *spi = dev_get_drvdata(dev);
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
return sprintf(buf, "%d\n", dimming_panel->checksum);
}
static ssize_t hzone_num_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct spi_device *spi = dev_get_drvdata(dev);
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
return sprintf(buf, "%d\n", dimming_panel->hzone_num);
}
static ssize_t vzone_num_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct spi_device *spi = dev_get_drvdata(dev);
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
return sprintf(buf, "%d\n", dimming_panel->vzone_num);
}
static ssize_t zone_max_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct spi_device *spi = dev_get_drvdata(dev);
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
return sprintf(buf, "%d\n", dimming_panel->zone_max);
}
static ssize_t brightness_max_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct spi_device *spi = dev_get_drvdata(dev);
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
return sprintf(buf, "%d\n", dimming_panel->brightness_max);
}
static ssize_t brightness_min_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct spi_device *spi = dev_get_drvdata(dev);
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
return sprintf(buf, "%d\n", dimming_panel->brightness_min);
}
static ssize_t brightness_bpc_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct spi_device *spi = dev_get_drvdata(dev);
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
return sprintf(buf, "%d\n", dimming_panel->brightness_bpc);
}
static DEVICE_ATTR_RO(crtc_id);
static DEVICE_ATTR_RO(checksum);
static DEVICE_ATTR_RO(hzone_num);
static DEVICE_ATTR_RO(vzone_num);
static DEVICE_ATTR_RO(zone_max);
static DEVICE_ATTR_RO(brightness_max);
static DEVICE_ATTR_RO(brightness_min);
static DEVICE_ATTR_RO(brightness_bpc);
/*
* The above attributes can be read via the following paths:
* (X means the index of dimming panel device)
* /sys/class/dimming/dimming_X/crtc_id
* /sys/class/dimming/dimming_X/checksum
* /sys/class/dimming/dimming_X/hzone_num
* /sys/class/dimming/dimming_X/vzone_num
* /sys/class/dimming/dimming_X/zone_max
* /sys/class/dimming/dimming_X/brightness_max
* /sys/class/dimming/dimming_X/brightness_min
* /sys/class/dimming/dimming_X/brightness_bpc
*/
static struct attribute *rockchip_dimming_attrs[] = {
&dev_attr_crtc_id.attr,
&dev_attr_checksum.attr,
&dev_attr_hzone_num.attr,
&dev_attr_vzone_num.attr,
&dev_attr_zone_max.attr,
&dev_attr_brightness_max.attr,
&dev_attr_brightness_min.attr,
&dev_attr_brightness_bpc.attr,
NULL,
};
static struct attribute_group rockchip_dimming_attr_group = {
.attrs = rockchip_dimming_attrs,
};
static inline void rockchip_dimming_panel_msleep(uint32_t msecs)
{
usleep_range(msecs * 1000, msecs * 1000 + 100);
}
static inline struct rockchip_dimming_panel *to_rockchip_dimming_panel(struct drm_panel *panel)
{
return container_of(panel, struct rockchip_dimming_panel, base);
}
static bool of_child_node_is_present(const struct device_node *node, const char *name)
{
struct device_node *child;
child = of_get_child_by_name(node, name);
of_node_put(child);
return !!child;
}
static int rockchip_dimming_panel_spi_write_data(struct device *dev, const void *data)
{
struct spi_device *spi = to_spi_device(dev);
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
struct spi_transfer t;
struct spi_message m;
uint32_t offset = 0;
uint32_t element_bytes = dimming_panel->brightness_bpc / 8;
uint16_t checksum = 0;
uint8_t *txbuf = NULL;
int len;
int i = 0, ret;
t.len = dimming_panel->cmd_element_size * element_bytes;
t.bits_per_word = dimming_panel->brightness_bpc;
txbuf = kzalloc(t.len * element_bytes, GFP_KERNEL);
if (!txbuf)
return -ENOMEM;
/* set sequence header */
memcpy(txbuf, dimming_panel->cmd_header, dimming_panel->cmd_header_len);
/* set brightness data */
offset += dimming_panel->cmd_header_len;
memcpy(txbuf + offset, data, dimming_panel->zone_max * element_bytes);
/* calculate magic code */
len = dimming_panel->cmd_header_len + dimming_panel->zone_max * element_bytes;
for (i = 0; i < len; i++) {
if (dimming_panel->brightness_bpc == 16) {
checksum ^= (uint16_t)(txbuf[i] << 8 | txbuf[i + 1]);
i++;
} else {
checksum ^= txbuf[i];
}
}
/* set magic code */
offset += dimming_panel->zone_max * element_bytes;
memcpy(txbuf + offset, &checksum, element_bytes);
dimming_panel->checksum = checksum;
/* set sequence tail */
offset += element_bytes;
memcpy(txbuf + offset, dimming_panel->cmd_tail,
dimming_panel->cmd_tail_len - element_bytes);
t.tx_buf = txbuf;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m);
kfree(txbuf);
return ret;
}
static void dimming_delayed_work_func(struct kthread_work *work)
{
struct rockchip_dimming_panel *dimming_panel =
container_of(work, struct rockchip_dimming_panel, dimming_delayed_work.work);
struct device *dev = dimming_panel->base.dev;
struct drm_crtc *crtc = dimming_panel->crtc;
struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
const struct drm_display_mode *mode = &dimming_panel->modes[0];
struct drm_vblank_crtc *vblank = &crtc->dev->vblank[drm_crtc_index(crtc)];
unsigned int delay_ms = 0;
uint32_t timeout = DIV_ROUND_CLOSEST_ULL(1000, drm_mode_vrefresh(mode));
uint32_t element_bytes = dimming_panel->brightness_bpc / 8;
uint64_t last;
int pipe = 0;
int ret = 0;
if (!vcstate->dimming_changed || !vcstate->dimming_data || !vcstate->dimming_data->data) {
dev_dbg(dev, "dimming data may be unprepared\n");
delay_ms = timeout;
goto out;
}
vcstate->dimming_changed = false;
memcpy(dimming_panel->data, vcstate->dimming_data->data,
dimming_panel->zone_max * element_bytes);
ret = drm_crtc_vblank_get(crtc);
if (ret) {
dev_err(dev, "failed to get vblank on crtc-%d\n", pipe);
delay_ms = timeout;
goto out;
}
delay_ms = 0;
last = drm_crtc_vblank_count(crtc);
ret = wait_event_timeout(vblank->queue, last != drm_crtc_vblank_count(crtc),
msecs_to_jiffies(timeout));
drm_crtc_vblank_put(crtc);
if (!ret) {
dev_err(dev, "failed to wait for vblank on crtc-%d\n", pipe);
goto out;
}
gpiod_direction_output(dimming_panel->sync_gpio, 1);
udelay(dimming_panel->delay.vsync_hold);
gpiod_direction_output(dimming_panel->sync_gpio, 0);
udelay(dimming_panel->delay.vsync_back);
ret = rockchip_dimming_panel_spi_write_data(dimming_panel->base.dev,
dimming_panel->data);
if (ret)
dev_err(dev, "failed to write dimming data on crtc-%d\n", pipe);
out:
kthread_queue_delayed_work(dimming_panel->dimming_worker,
&dimming_panel->dimming_delayed_work,
msecs_to_jiffies(delay_ms));
};
static int rockchip_dimming_panel_regulator_enable(struct rockchip_dimming_panel *dimming_panel)
{
int ret;
ret = regulator_enable(dimming_panel->supply);
if (ret < 0)
return ret;
return 0;
}
static int rockchip_dimming_panel_regulator_disable(struct rockchip_dimming_panel *dimming_panel)
{
regulator_disable(dimming_panel->supply);
return 0;
}
static int rockchip_dimming_panel_prepare(struct drm_panel *panel)
{
struct rockchip_dimming_panel *dimming_panel = to_rockchip_dimming_panel(panel);
int ret;
if (dimming_panel->prepared)
return 0;
ret = rockchip_dimming_panel_regulator_enable(dimming_panel);
if (ret < 0) {
dev_err(panel->dev, "failed to enable regulator: %d\n", ret);
return ret;
}
gpiod_direction_output(dimming_panel->lden_gpio, 0);
gpiod_direction_output(dimming_panel->blen_gpio, 1);
gpiod_direction_output(dimming_panel->dbcl_gpio, 1);
gpiod_direction_output(dimming_panel->sync_gpio, 0);
gpiod_direction_output(dimming_panel->enable_gpio, 1);
if (dimming_panel->delay.prepare)
rockchip_dimming_panel_msleep(dimming_panel->delay.prepare);
gpiod_direction_output(dimming_panel->reset_gpio, 1);
if (dimming_panel->delay.reset)
rockchip_dimming_panel_msleep(dimming_panel->delay.reset);
gpiod_direction_output(dimming_panel->reset_gpio, 0);
if (dimming_panel->delay.init)
rockchip_dimming_panel_msleep(dimming_panel->delay.init);
dimming_panel->prepared = true;
return 0;
}
static int rockchip_dimming_panel_enable(struct drm_panel *panel)
{
struct rockchip_dimming_panel *dimming_panel = to_rockchip_dimming_panel(panel);
if (dimming_panel->enabled)
return 0;
if (dimming_panel->delay.enable)
rockchip_dimming_panel_msleep(dimming_panel->delay.enable);
gpiod_direction_output(dimming_panel->lden_gpio, 1);
usleep_range(10 * 1000, 10 * 1000 + 500);
kthread_queue_delayed_work(dimming_panel->dimming_worker,
&dimming_panel->dimming_delayed_work, 0);
dimming_panel->enabled = true;
/*
* notify other devices (such as TP) to perform the action after the
* panel is enabled.
*/
rockchip_panel_notifier_call_chain(&dimming_panel->panel_notifier,
PANEL_ENABLED, NULL);
return 0;
}
static int rockchip_dimming_panel_disable(struct drm_panel *panel)
{
struct rockchip_dimming_panel *dimming_panel = to_rockchip_dimming_panel(panel);
/*
* notify other devices (such as TP) to perform the action before the
* panel is disabled.
*/
rockchip_panel_notifier_call_chain(&dimming_panel->panel_notifier,
PANEL_PRE_DISABLE, NULL);
if (!dimming_panel->enabled)
return 0;
if (dimming_panel->delay.disable)
rockchip_dimming_panel_msleep(dimming_panel->delay.disable);
kthread_cancel_delayed_work_sync(&dimming_panel->dimming_delayed_work);
gpiod_direction_output(dimming_panel->lden_gpio, 0);
dimming_panel->enabled = false;
return 0;
}
static int rockchip_dimming_panel_unprepare(struct drm_panel *panel)
{
struct rockchip_dimming_panel *dimming_panel = to_rockchip_dimming_panel(panel);
/* Unpreparing when already unprepared is a no-op */
if (!dimming_panel->prepared)
return 0;
gpiod_direction_output(dimming_panel->reset_gpio, 1);
gpiod_direction_output(dimming_panel->enable_gpio, 0);
rockchip_dimming_panel_regulator_disable(dimming_panel);
if (dimming_panel->delay.unprepare)
rockchip_dimming_panel_msleep(dimming_panel->delay.unprepare);
dimming_panel->prepared = false;
return 0;
}
static int rockchip_dimming_panel_find_possible_crtc(struct drm_panel *panel,
struct drm_connector *connector)
{
struct rockchip_dimming_panel *dimming_panel = to_rockchip_dimming_panel(panel);
struct drm_encoder *encoder;
struct drm_crtc *crtc;
drm_connector_for_each_possible_encoder(connector, encoder)
break;
if (!encoder)
return -EINVAL;
drm_for_each_crtc(crtc, encoder->dev) {
if (crtc->index == ffs(encoder->possible_crtcs) - 1) {
dimming_panel->crtc = crtc;
return 0;
}
}
return -EINVAL;
}
static int rockchip_dimming_panel_get_modes(struct drm_panel *panel,
struct drm_connector *connector)
{
struct rockchip_dimming_panel *dimming_panel = to_rockchip_dimming_panel(panel);
struct drm_display_mode *mode;
uint32_t i, num = 0;
if (rockchip_dimming_panel_find_possible_crtc(panel, connector))
return 0;
for (i = 0; i < dimming_panel->num_modes; i++) {
const struct drm_display_mode *m = &dimming_panel->modes[i];
mode = drm_mode_duplicate(connector->dev, m);
if (!mode) {
dev_err(dimming_panel->base.dev, "failed to add mode %ux%u@%u\n",
m->hdisplay, m->vdisplay,
drm_mode_vrefresh(m));
continue;
}
mode->type |= DRM_MODE_TYPE_DRIVER;
if (dimming_panel->num_modes == 1)
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_set_name(mode);
drm_mode_probed_add(connector, mode);
num++;
}
if (dimming_panel->bus_format)
drm_display_info_set_bus_formats(&connector->display_info,
&dimming_panel->bus_format, 1);
if (dimming_panel->bus_flags)
connector->display_info.bus_flags = dimming_panel->bus_flags;
return num;
}
static const struct drm_panel_funcs rockchip_dimming_panel_funcs = {
.prepare = rockchip_dimming_panel_prepare,
.enable = rockchip_dimming_panel_enable,
.disable = rockchip_dimming_panel_disable,
.unprepare = rockchip_dimming_panel_unprepare,
.get_modes = rockchip_dimming_panel_get_modes,
};
static int rockchip_dimming_panel_loader_protect(struct rockchip_drm_sub_dev *sub_dev, bool on)
{
struct rockchip_dimming_panel *dimming_panel = container_of(sub_dev,
struct rockchip_dimming_panel,
sub_dev);
int ret;
if (on) {
ret = rockchip_dimming_panel_regulator_enable(dimming_panel);
if (ret < 0) {
dev_err(dimming_panel->base.dev, "failed to enable regulator: %d\n", ret);
return ret;
}
kthread_queue_delayed_work(dimming_panel->dimming_worker,
&dimming_panel->dimming_delayed_work, 0);
dimming_panel->enabled = true;
dimming_panel->prepared = true;
} else {
dimming_panel->enabled = false;
dimming_panel->prepared = false;
kthread_cancel_delayed_work_sync(&dimming_panel->dimming_delayed_work);
rockchip_dimming_panel_regulator_disable(dimming_panel);
}
return 0;
}
static int rockchip_dimming_panel_of_get_data(struct rockchip_dimming_panel *dimming_panel)
{
struct device *dev = dimming_panel->dev;
struct device_node *np;
struct drm_display_mode *mode;
const void *data;
uint32_t bus_flags;
uint32_t element_bytes;
int len;
int ret;
dimming_panel->supply = devm_regulator_get(dev, "power");
if (IS_ERR(dimming_panel->supply))
return dev_err_probe(dev, PTR_ERR(dimming_panel->supply),
"failed to get power regulator\n");
dimming_panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_ASIS);
if (IS_ERR(dimming_panel->enable_gpio))
return dev_err_probe(dev, PTR_ERR(dimming_panel->enable_gpio),
"failed to get enable GPIO\n");
dimming_panel->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS);
if (IS_ERR(dimming_panel->reset_gpio))
return dev_err_probe(dev, PTR_ERR(dimming_panel->reset_gpio),
"failed to get reset GPIO\n");
dimming_panel->lden_gpio = devm_gpiod_get_optional(dev, "lden", GPIOD_ASIS);
if (IS_ERR(dimming_panel->lden_gpio))
return dev_err_probe(dev, PTR_ERR(dimming_panel->lden_gpio),
"failed to get lden GPIO\n");
dimming_panel->blen_gpio = devm_gpiod_get_optional(dev, "blen", GPIOD_ASIS);
if (IS_ERR(dimming_panel->blen_gpio)) {
return dev_err_probe(dev, PTR_ERR(dimming_panel->blen_gpio),
"failed to get blen GPIO\n");
}
dimming_panel->dbcl_gpio = devm_gpiod_get_optional(dev, "dbcl", GPIOD_ASIS);
if (IS_ERR(dimming_panel->dbcl_gpio))
return dev_err_probe(dev, PTR_ERR(dimming_panel->dbcl_gpio),
"failed to get dbcl GPIO\n");
dimming_panel->sync_gpio = devm_gpiod_get_optional(dev, "sync", GPIOD_ASIS);
if (IS_ERR(dimming_panel->sync_gpio))
return dev_err_probe(dev, PTR_ERR(dimming_panel->sync_gpio),
"failed to get sync GPIO\n");
np = dimming_panel->dev->of_node;
if (of_child_node_is_present(np, "display-timings")) {
mode = devm_kzalloc(dev, sizeof(*mode), GFP_KERNEL);
if (!mode)
return -ENOMEM;
if (!of_get_drm_display_mode(np, mode, &bus_flags, OF_USE_NATIVE_MODE)) {
dimming_panel->modes = mode;
dimming_panel->num_modes = 1;
dimming_panel->bus_flags = bus_flags;
of_property_read_u32(np, "bus-format", &dimming_panel->bus_format);
of_property_read_u32(np, "prepare-delay-ms", &dimming_panel->delay.prepare);
of_property_read_u32(np, "enable-delay-ms", &dimming_panel->delay.enable);
of_property_read_u32(np, "disable-delay-ms", &dimming_panel->delay.disable);
of_property_read_u32(np, "unprepare-delay-ms",
&dimming_panel->delay.unprepare);
of_property_read_u32(np, "reset-delay-ms", &dimming_panel->delay.reset);
of_property_read_u32(np, "init-delay-ms", &dimming_panel->delay.init);
of_property_read_u32(np, "vsync-hold-us", &dimming_panel->delay.vsync_hold);
of_property_read_u32(np, "vsync-back-us", &dimming_panel->delay.vsync_back);
} else {
devm_kfree(dev, mode);
}
}
/* Parameters to report */
ret = of_property_read_u32(np, "hzone-num", &dimming_panel->hzone_num);
if (ret)
return dev_err_probe(dev, ret, "failed to get horizontal zone number\n");
ret = of_property_read_u32(np, "vzone-num", &dimming_panel->vzone_num);
if (ret)
return dev_err_probe(dev, ret, "failed to get vertical zone number\n");
dimming_panel->zone_max = dimming_panel->hzone_num * dimming_panel->vzone_num;
ret = of_property_read_u32(np, "brightness-max", &dimming_panel->brightness_max);
if (ret)
return dev_err_probe(dev, ret, "failed to get brightness max value\n");
ret = of_property_read_u32(np, "brightness-min", &dimming_panel->brightness_min);
if (ret)
return dev_err_probe(dev, ret, "failed to get brightness min value\n");
ret = of_property_read_u32(np, "brightness-bpc", &dimming_panel->brightness_bpc);
if (ret)
return dev_err_probe(dev, ret, "failed to get brightness bpc value\n");
if (dimming_panel->brightness_bpc != 8 && dimming_panel->brightness_bpc != 16)
return dev_err_probe(dev, -EINVAL, "brightness bpc value should be 8 or 16\n");
element_bytes = dimming_panel->brightness_bpc / 8;
dimming_panel->data = devm_kzalloc(dev, element_bytes * dimming_panel->zone_max,
GFP_KERNEL);
if (!dimming_panel->data)
return -ENOMEM;
dimming_panel->cmd_element_size = dimming_panel->zone_max;
data = of_get_property(np, "command-header", &len);
if (data) {
dimming_panel->cmd_header = devm_kzalloc(dev, len, GFP_KERNEL);
if (!dimming_panel->cmd_header)
return -ENOMEM;
memcpy(dimming_panel->cmd_header, data, len);
dimming_panel->cmd_header_len = len;
}
dimming_panel->cmd_element_size += dimming_panel->cmd_header_len / element_bytes;
data = of_get_property(np, "command-tail", &len);
if (data) {
dimming_panel->cmd_tail = devm_kzalloc(dev, len, GFP_KERNEL);
if (!dimming_panel->cmd_tail)
return -ENOMEM;
memcpy(dimming_panel->cmd_tail, data, len);
dimming_panel->cmd_tail_len = len;
}
dimming_panel->cmd_element_size += dimming_panel->cmd_tail_len / element_bytes;
return 0;
}
static int rockchip_dimming_panel_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct device *dimming_dev;
struct rockchip_dimming_panel *dimming_panel;
int ret;
spi->bits_per_word = 8;
ret = spi_setup(spi);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to setup spi\n");
dimming_panel = devm_kzalloc(dev, sizeof(*dimming_panel), GFP_KERNEL);
if (!dimming_panel)
return -ENOMEM;
dimming_panel->dev = dev;
ret = rockchip_dimming_panel_of_get_data(dimming_panel);
if (ret)
return dev_err_probe(dev, ret, "failed to get dimming panel configs\n");
dev_set_drvdata(dev, dimming_panel);
drm_panel_init(&dimming_panel->base, dev, &rockchip_dimming_panel_funcs,
DRM_MODE_CONNECTOR_Unknown);
ret = drm_panel_of_backlight(&dimming_panel->base);
if (ret)
return dev_err_probe(dev, ret, "failed to find backlight\n");
drm_panel_add(&dimming_panel->base);
dimming_panel->id = bitmap_find_next_zero_area(allocated_dimming_panels,
MAX_DIMMING_PANELS, 0, 1, 0);
dimming_dev = device_create(rockchip_dimming_class, dev, MKDEV(0, 0), spi,
"dimming_%d", dimming_panel->id);
if (IS_ERR(dimming_dev)) {
dev_err(dev, "Failed to create rockchip dimming device\n");
ret = PTR_ERR(dimming_dev);
goto remove_panel;
}
dimming_panel->dimming_dev = dimming_dev;
bitmap_set(allocated_dimming_panels, dimming_panel->id, 1);
dev_set_drvdata(dimming_dev, spi);
ret = sysfs_create_group(&dimming_dev->kobj, &rockchip_dimming_attr_group);
if (ret)
goto destroy_device;
devm_rockchip_panel_notifier_register(dev, &dimming_panel->base,
&dimming_panel->panel_notifier);
dimming_panel->dimming_worker = kthread_create_worker(0, dev_name(dimming_dev));
if (IS_ERR(dimming_panel->dimming_worker)) {
dev_err(dimming_dev, "Failed to create rockchip dimming worker\n");
ret = PTR_ERR(dimming_panel->dimming_worker);
goto remove_group;
}
kthread_init_delayed_work(&dimming_panel->dimming_delayed_work, dimming_delayed_work_func);
dimming_panel->sub_dev.of_node = dev->of_node;
dimming_panel->sub_dev.loader_protect = rockchip_dimming_panel_loader_protect;
rockchip_drm_register_sub_dev(&dimming_panel->sub_dev);
return 0;
remove_group:
sysfs_remove_group(&dimming_dev->kobj, &rockchip_dimming_attr_group);
destroy_device:
bitmap_clear(allocated_dimming_panels, dimming_panel->id, 1);
device_destroy(rockchip_dimming_class, dimming_dev->devt);
remove_panel:
drm_panel_remove(&dimming_panel->base);
return ret;
}
static void rockchip_dimming_panel_remove(struct spi_device *spi)
{
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
struct device *dimming_dev = dimming_panel->dimming_dev;
rockchip_drm_unregister_sub_dev(&dimming_panel->sub_dev);
kthread_destroy_worker(dimming_panel->dimming_worker);
sysfs_remove_group(&dimming_dev->kobj, &rockchip_dimming_attr_group);
bitmap_clear(allocated_dimming_panels, dimming_panel->id, 1);
device_destroy(rockchip_dimming_class, dimming_dev->devt);
drm_panel_remove(&dimming_panel->base);
drm_panel_disable(&dimming_panel->base);
drm_panel_unprepare(&dimming_panel->base);
}
static void rockchip_dimming_panel_shutdown(struct spi_device *spi)
{
struct rockchip_dimming_panel *dimming_panel = dev_get_drvdata(&spi->dev);
drm_panel_disable(&dimming_panel->base);
drm_panel_unprepare(&dimming_panel->base);
}
static const struct spi_device_id rockchip_dimming_panel_ids[] = {
{ .name = "rockchip,dimming-panel" },
{},
};
MODULE_DEVICE_TABLE(spi, rockchip_dimming_panel_ids);
static const struct of_device_id rockchip_dimming_panel_of_match[] = {
{ .compatible = "rockchip,dimming-panel" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_dimming_panel_of_match);
static struct spi_driver rockchip_dimming_panel_driver = {
.probe = rockchip_dimming_panel_probe,
.remove = rockchip_dimming_panel_remove,
.shutdown = rockchip_dimming_panel_shutdown,
.id_table = rockchip_dimming_panel_ids,
.driver = {
.name = "rockchip-dimming-panel",
.of_match_table = rockchip_dimming_panel_of_match,
},
};
static int __init rockchip_dimming_panel_init(void)
{
rockchip_dimming_class = class_create(THIS_MODULE, "dimming");
if (IS_ERR(rockchip_dimming_class)) {
pr_err("Failed to create rockchip dimming class\n");
return PTR_ERR(rockchip_dimming_class);
}
return spi_register_driver(&rockchip_dimming_panel_driver);
}
module_init(rockchip_dimming_panel_init);
static void __exit rockchip_dimming_panel_exit(void)
{
spi_unregister_driver(&rockchip_dimming_panel_driver);
class_destroy(rockchip_dimming_class);
}
module_exit(rockchip_dimming_panel_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Damon Ding <damon.ding@rock-chips.com>");
MODULE_DESCRIPTION("rockchip dimming panel");
MODULE_SOFTDEP("pre: rockchipdrm");