Devicetree
 help / color / mirror / Atom feed
* [PATCH v2 0/3] arm64: dts: qcom: agatti: Fix IOMMU DT properties
From: Sumit Garg @ 2026-01-16  6:20 UTC (permalink / raw)
  To: linux-arm-msm, devicetree
  Cc: andersson, konradybcio, robh, krzk+dt, conor+dt, akhilpo,
	vikash.garodia, dikshita.agarwal, robin.clark, lumag,
	loic.poulain, jorge.ramirez, linux-kernel, Sumit Garg

From: Sumit Garg <sumit.garg@oss.qualcomm.com>

Fix IOMMU DT propeties for GPU, display and video peripherals via
dropping SMMU stream IDs which relates to secure context bank.

This problem only surfaced when the Gunyah based firmware stack is
ported on Agatti replacing the legacy QHEE based firmware stack. Assigning
Linux kernel (HLOS) VMID to secure context bank stream IDs is treated
as a fault by Gunyah hypervisor which were previously ignored by QHEE
hypervisor.

The DT changes should be backwards compatible with legacy QHEE based
firmware stack too.

Changes in v2:
---
- Dropped Gunyah related reserved memory requirement changes
- Added DT bindings changes related to IOMMU property fixups
- Collected review tags for the DT changes

Sumit Garg (3):
  dt-bindings: display: msm: qcm2290-mdss: Fix iommus property
  dt-bindings: media: venus: Fix iommus property
  arm64: dts: qcom: agatti: Fix IOMMU DT properties

 .../bindings/display/msm/qcom,qcm2290-mdss.yaml       |  5 ++---
 .../devicetree/bindings/media/qcom,qcm2290-venus.yaml |  6 ++----
 arch/arm64/boot/dts/qcom/agatti.dtsi                  | 11 +++--------
 3 files changed, 7 insertions(+), 15 deletions(-)

-- 
2.51.0


^ permalink raw reply

* Re: [PATCH] arm64: dts: qcom: ipq9574: Enable eMMC variant
From: Varadarajan Narayanan @ 2026-01-16  6:17 UTC (permalink / raw)
  To: Sumit Garg
  Cc: Bjorn Andersson, Krzysztof Kozlowski, konradybcio, robh, krzk+dt,
	conor+dt, linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <aVtAYqwmjPPRmWzV@sumit-xelite>

[ . . . ]
> > > > +/*
> > > > + * IPQ9574 RDP433 eMMC board variant device tree source
> > > > + *
> > > > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > > > + */
> > > > +
> > > > +/dts-v1/;
> > > > +
> > > > +#include "ipq9574-rdp433.dts"
> > >
> > > I find it discouraged practice. Although if Bjorn is fine with it, you
> > > still need proper compatibles.
> > >
> >
> > Nah, I don't fancy including .dts files. But I do see that I've let a
> > few of those slip by over the years.
> >
> > The general approach for these cases has been to rename dts->dtsi and
> > create the two dts files.
>
> That sounds better to me.
>
> >
> > I wonder though if this would be better served by an overlay, as it's
> > the same board, but with modifications applied?
>
> I am not sure how overlay is a better fit here. AFAIK, the DT overlay is
> generally used for plug and play hardware mezzanines. But here we are
> dealing with the default boot medium which can either be eMMC or NAND.
> Can a developer easily reverse the board modifications to boot from a
> different medium?
>
> And this DT is not only going to be consumed by the kernel but rather
> the bootloader (U-Boot) in this case where there isn't a concept of DT
> overlays which the prior stage can apply.

Bjorn/Dmitry/Sumit,

Will proceed with separate dts approach. Please let me know if that is
not ok.

Thanks
Varada

^ permalink raw reply

* Re: [PATCH v3 2/3] hwmon: (tmp108) Add support for P3T1035 and P3T2030
From: Guenter Roeck @ 2026-01-16  6:11 UTC (permalink / raw)
  To: Mayank Mahajan, corbet, robh, krzk+dt, conor+dt, linux-hwmon,
	devicetree, linux-doc, linux-kernel
  Cc: priyanka.jain, vikash.bansal
In-Reply-To: <20260115111418.1851-2-mayankmahajan.x@nxp.com>

On 1/15/26 03:14, Mayank Mahajan wrote:
> Add support for the P3T1035 & P3T2030 temperature sensor. While mostly
> compatible with the TMP108, P3T1035 uses an 8-bit configuration register
> instead of the 16-bit layout used by TMP108. Updated driver to handle
> this difference during configuration read/write.
> 
> Signed-off-by: Mayank Mahajan <mayankmahajan.x@nxp.com>
> ---
> V1 -> V2:
> - Disabled hysteresis in is_visible function for P3T1035.
> - Added tables for conversion rate similar to the LM75 driver.
> - Implemented different bus access depending on the chip being used.
>    - Removed regmap for 8 bits; now we are using one regmap as before.
>    - Added read and write functions for i2c and i3c for use with regmap.
>    - Mapped the 8-bit configuration register to a 16 bit value for P3T1035.
> V2 -> V3:
> - Remove changes not relevant to adding a new device in the driver.
> - Address warnings due to incorrect usage of casting operations.
> - Remove the usage of P3T2030 as it's functionally identical to P3T1035.
> 
>   drivers/hwmon/Kconfig  |   2 +-
>   drivers/hwmon/tmp108.c | 227 +++++++++++++++++++++++++++++++++--------
>   2 files changed, 186 insertions(+), 43 deletions(-)
> 
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 157678b821fc..31969bddc812 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -2398,7 +2398,7 @@ config SENSORS_TMP108
>   	select REGMAP_I3C if I3C
>   	help
>   	  If you say yes here you get support for Texas Instruments TMP108
> -	  sensor chips and NXP P3T1085.
> +	  sensor chips, NXP temperature sensors P3T1035, P3T1085 and P3T2030.
> 
>   	  This driver can also be built as a module. If so, the module
>   	  will be called tmp108.
> diff --git a/drivers/hwmon/tmp108.c b/drivers/hwmon/tmp108.c
> index 60a237cbedbc..38a2203c3bd9 100644
> --- a/drivers/hwmon/tmp108.c
> +++ b/drivers/hwmon/tmp108.c
> @@ -17,9 +17,16 @@
>   #include <linux/regmap.h>
>   #include <linux/regulator/consumer.h>
>   #include <linux/slab.h>
> +#include <linux/util_macros.h>
> 
>   #define	DRIVER_NAME "tmp108"
> 
> +enum tmp108_hw_id {
> +	P3T1035_ID,		/* For sensors p3t1035 and p3t2030 */
> +	P3T1085_ID,
> +	TMP108_ID,
> +};
> +
>   #define	TMP108_REG_TEMP		0x00
>   #define	TMP108_REG_CONF		0x01
>   #define	TMP108_REG_TLOW		0x02
> @@ -61,6 +68,7 @@
>   #define TMP108_CONVRATE_1HZ		TMP108_CONF_CR0		/* Default */
>   #define TMP108_CONVRATE_4HZ		TMP108_CONF_CR1
>   #define TMP108_CONVRATE_16HZ		(TMP108_CONF_CR0|TMP108_CONF_CR1)
> +#define TMP108_CONVRATE_SHIFT		13
> 
>   #define TMP108_CONF_HYSTERESIS_MASK	(TMP108_CONF_HYS0|TMP108_CONF_HYS1)
>   #define TMP108_HYSTERESIS_0C		0x0000
> @@ -71,11 +79,21 @@
>   #define TMP108_CONVERSION_TIME_MS	30	/* in milli-seconds */
> 
>   struct tmp108 {
> -	struct regmap *regmap;
> -	u16 orig_config;
> -	unsigned long ready_time;
> +	struct regmap		*regmap;
> +	u16			orig_config;
> +	unsigned long		ready_time;
> +	enum tmp108_hw_id	hw_id;
> +	bool			config_reg_16bits;
> +	u8			reg_buf[1];
> +	u8			val_buf[3];
> +	unsigned int		sample_times[4];
>   };
> 
> +static const u16 tmp108_sample_set_masks[] = { 3 << TMP108_CONVRATE_SHIFT,
> +					       2 << TMP108_CONVRATE_SHIFT,
> +					       1 << TMP108_CONVRATE_SHIFT,
> +					       0 << TMP108_CONVRATE_SHIFT };
> +

Unnecessary. See below.

>   /* convert 12-bit TMP108 register value to milliCelsius */
>   static inline int tmp108_temp_reg_to_mC(s16 val)
>   {
> @@ -94,6 +112,8 @@ static int tmp108_read(struct device *dev, enum hwmon_sensor_types type,
>   	struct tmp108 *tmp108 = dev_get_drvdata(dev);
>   	unsigned int regval;
>   	int err, hyst;
> +	u16 conv_rate;
> +	u8 index;
> 
>   	if (type == hwmon_chip) {
>   		if (attr == hwmon_chip_update_interval) {
> @@ -101,21 +121,10 @@ static int tmp108_read(struct device *dev, enum hwmon_sensor_types type,
>   					  &regval);
>   			if (err < 0)
>   				return err;
> -			switch (regval & TMP108_CONF_CONVRATE_MASK) {
> -			case TMP108_CONVRATE_0P25HZ:
> -			default:
> -				*temp = 4000;
> -				break;
> -			case TMP108_CONVRATE_1HZ:
> -				*temp = 1000;
> -				break;
> -			case TMP108_CONVRATE_4HZ:
> -				*temp = 250;
> -				break;
> -			case TMP108_CONVRATE_16HZ:
> -				*temp = 63;
> -				break;
> -			}
> +			conv_rate = regval & TMP108_CONF_CONVRATE_MASK;
> +			index = find_closest_descending(conv_rate, tmp108_sample_set_masks,
> +							(int)ARRAY_SIZE(tmp108_sample_set_masks));
> +			*temp = tmp108->sample_times[index];

(regval & TMP108_CONF_CONVRATE_MASK) >> TMP108_CONVRATE_SHIFT, or alternatively
FIELD_GET(TMP108_CONF_CONVRATE_MASK, regval), yields 0..3. With a sample_times
array of { 4000, 1000, 250, 63 } or { 4000, 1000, 250, 125 }, the code above
could simply be
			*temp = tmp108->sample_times[FIELD_GET(TMP108_CONF_CONVRATE_MASK, regval)];
which would both be easier to understand and much simpler.

>   			return 0;
>   		}
>   		return -EOPNOTSUPP;
> @@ -192,22 +201,17 @@ static int tmp108_write(struct device *dev, enum hwmon_sensor_types type,
>   {
>   	struct tmp108 *tmp108 = dev_get_drvdata(dev);
>   	u32 regval, mask;
> +	u8 index;
>   	int err;
> 
>   	if (type == hwmon_chip) {
>   		if (attr == hwmon_chip_update_interval) {
> -			if (temp < 156)
> -				mask = TMP108_CONVRATE_16HZ;
> -			else if (temp < 625)
> -				mask = TMP108_CONVRATE_4HZ;
> -			else if (temp < 2500)
> -				mask = TMP108_CONVRATE_1HZ;
> -			else
> -				mask = TMP108_CONVRATE_0P25HZ;
> +			index = find_closest(temp, tmp108->sample_times,
> +					     (int)ARRAY_SIZE(tmp108->sample_times));

I don't see why the type cast would be needed. Other users of find_closest()
don't need it either.

>   			return regmap_update_bits(tmp108->regmap,
>   						  TMP108_REG_CONF,
>   						  TMP108_CONF_CONVRATE_MASK,
> -						  mask);
> +						  tmp108_sample_set_masks[index]);

Use GENMASK().

>   		}
>   		return -EOPNOTSUPP;
>   	}
> @@ -251,6 +255,8 @@ static int tmp108_write(struct device *dev, enum hwmon_sensor_types type,
>   static umode_t tmp108_is_visible(const void *data, enum hwmon_sensor_types type,
>   				 u32 attr, int channel)
>   {
> +	const struct tmp108 *tmp108 = data;
> +
>   	if (type == hwmon_chip && attr == hwmon_chip_update_interval)
>   		return 0644;
> 
> @@ -264,8 +270,11 @@ static umode_t tmp108_is_visible(const void *data, enum hwmon_sensor_types type,
>   		return 0444;
>   	case hwmon_temp_min:
>   	case hwmon_temp_max:
> +		return 0644;
>   	case hwmon_temp_min_hyst:
>   	case hwmon_temp_max_hyst:
> +		if (tmp108->hw_id == P3T1035_ID)
> +			return 0;
>   		return 0644;
>   	default:
>   		return 0;
> @@ -311,6 +320,105 @@ static bool tmp108_is_volatile_reg(struct device *dev, unsigned int reg)
>   	return reg == TMP108_REG_TEMP || reg == TMP108_REG_CONF;
>   }
> 
> +static int tmp108_i2c_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> +	struct i2c_client *client = context;
> +	struct tmp108 *tmp108 = i2c_get_clientdata(client);
> +	int ret;
> +
> +	if (reg == TMP108_REG_CONF && !tmp108->config_reg_16bits)
> +		ret = i2c_smbus_read_byte_data(client, TMP108_REG_CONF);
> +	else
> +		ret = i2c_smbus_read_word_swapped(client, reg);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	if (reg == TMP108_REG_CONF && !tmp108->config_reg_16bits)
> +		*val = ret << 8;
> +	else
> +		*val = ret;

This evaluates reg and tmp108->config_reg_16bits twice. Try

	if (reg == TMP108_REG_CONF && !tmp108->config_reg_16bits) {
		ret = i2c_smbus_read_byte_data(client, TMP108_REG_CONF);
		if (ret < 0)
			return ret;
		*val = ret << 8;
		return 0;
	}
	ret = i2c_smbus_read_word_swapped(client, reg);
	if (ret < 0)
		return ret;
	*val = ret;
	return 0;

instead to reduce runtime overhead.

> +
> +	return 0;
> +}
> +
> +static int tmp108_i2c_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> +	struct i2c_client *client = context;
> +	struct tmp108 *tmp108 = i2c_get_clientdata(client);
> +
> +	if (reg == TMP108_REG_CONF && !tmp108->config_reg_16bits)
> +		return i2c_smbus_write_byte_data(client, reg, val >> 8);
> +	return i2c_smbus_write_word_swapped(client, reg, val);
> +}
> +
> +static const struct regmap_bus tmp108_i2c_regmap_bus = {
> +	.reg_read = tmp108_i2c_reg_read,
> +	.reg_write = tmp108_i2c_reg_write,
> +};
> +
> +static int tmp108_i3c_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> +	struct i3c_device *i3cdev = context;
> +	struct tmp108 *tmp108 = i3cdev_get_drvdata(i3cdev);
> +	struct i3c_xfer xfers[] = {
> +		{
> +			.rnw = false,
> +			.len = 1,
> +			.data.out = tmp108->reg_buf,
> +		},
> +		{
> +			.rnw = true,
> +			.len = 2,
> +			.data.in = tmp108->val_buf,

What is the point of having reg_buf and val_buf allocated instead
of just using local variables/arrays ?

> +		},
> +	};
> +	int ret;
> +
> +	tmp108->reg_buf[0] = reg;
> +
> +	if (reg == TMP108_REG_CONF && !tmp108->config_reg_16bits)
> +		xfers[1].len--;
> +
> +	ret = i3c_device_do_xfers(i3cdev, xfers, 2, I3C_SDR);
> +	if (ret < 0)
> +		return ret;
> +
> +	*val = tmp108->val_buf[0] << 8;
> +	if (!(reg == TMP108_REG_CONF && !tmp108->config_reg_16bits))

Please refrain from using double negations.
	if (reg != TMP108_REG_CONF || tmp108->config_reg_16bits)
is much easier to understand.

> +		*val |= tmp108->val_buf[1];
> +
> +	return 0;
> +}
> +
> +static int tmp108_i3c_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> +	struct i3c_device *i3cdev = context;
> +	struct tmp108 *tmp108 = i3cdev_get_drvdata(i3cdev);
> +	struct i3c_xfer xfers[] = {
> +		{
> +			.rnw = false,
> +			.len = 3,
> +			.data.out = tmp108->val_buf,
> +		},
> +	};
> +
> +	tmp108->val_buf[0] = reg;
> +	tmp108->val_buf[1] = (val >> 8) & 0xff;
> +
> +	if (reg == TMP108_REG_CONF && !tmp108->config_reg_16bits)
> +		xfers[0].len--;
> +	else
> +		tmp108->val_buf[2] = val & 0xff;
> +
> +	return i3c_device_do_xfers(i3cdev, xfers, 1, I3C_SDR);
> +}
> +
> +static const struct regmap_bus tmp108_i3c_regmap_bus = {
> +	.reg_read = tmp108_i3c_reg_read,
> +	.reg_write = tmp108_i3c_reg_write,
> +};
> +
>   static const struct regmap_config tmp108_regmap_config = {
>   	.reg_bits = 8,
>   	.val_bits = 16,
> @@ -323,7 +431,8 @@ static const struct regmap_config tmp108_regmap_config = {
>   	.use_single_write = true,
>   };
> 
> -static int tmp108_common_probe(struct device *dev, struct regmap *regmap, char *name)
> +static int tmp108_common_probe(struct device *dev, struct regmap *regmap, char *name,
> +			       enum tmp108_hw_id hw_id)
>   {
>   	struct device *hwmon_dev;
>   	struct tmp108 *tmp108;
> @@ -340,6 +449,14 @@ static int tmp108_common_probe(struct device *dev, struct regmap *regmap, char *
> 
>   	dev_set_drvdata(dev, tmp108);
>   	tmp108->regmap = regmap;
> +	tmp108->hw_id = hw_id;
> +	tmp108->config_reg_16bits = (hw_id == P3T1035_ID) ? false : true;
> +	if (hw_id == P3T1035_ID)
> +		memcpy(tmp108->sample_times, (unsigned int[]){ 125, 250, 1000, 4000 },
> +		       sizeof(tmp108->sample_times));
> +	else
> +		memcpy(tmp108->sample_times, (unsigned int[]){ 63, 250, 1000, 4000 },
> +		       sizeof(tmp108->sample_times));

You'd think that the repeated 0-day complaints have an effect.
Just make tmp108->sample_times a pointer and create two ushort arrays where the values
match the index values.

	struct tmp108 {
		ushort *sample_times;
	};

	ushort p3t_1035_sample_times[] = {4000, 1000, 250, 125};
	ushort tmp108_sample_times[] = {4000, 1000, 250, 125};

	if (hw_id == P3T1035_ID)
		tmp108->sample_times = p3t_1035_sample_times;
	else
		tmp108->sample_times = tmp108_sample_times;

Something like
	tmp108->sample_times = (ushort []) {4000, 1000, 250, 125};
might work as well, but I did not test it.
		
The memcpy is really unnecessary here.

> 
>   	err = regmap_read(tmp108->regmap, TMP108_REG_CONF, &config);
>   	if (err < 0) {
> @@ -351,7 +468,6 @@ static int tmp108_common_probe(struct device *dev, struct regmap *regmap, char *
>   	/* Only continuous mode is supported. */
>   	config &= ~TMP108_CONF_MODE_MASK;
>   	config |= TMP108_MODE_CONTINUOUS;
> -
>   	/* Only comparator mode is supported. */
>   	config &= ~TMP108_CONF_TM;
> 
> @@ -384,17 +500,33 @@ static int tmp108_probe(struct i2c_client *client)
>   {
>   	struct device *dev = &client->dev;
>   	struct regmap *regmap;
> +	enum tmp108_hw_id hw_id;
> +	const void *of_data;
> 
>   	if (!i2c_check_functionality(client->adapter,
> -				     I2C_FUNC_SMBUS_WORD_DATA))
> +				     I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
>   		return dev_err_probe(dev, -ENODEV,
>   				     "adapter doesn't support SMBus word transactions\n");
> 
> -	regmap = devm_regmap_init_i2c(client, &tmp108_regmap_config);
> +	regmap = devm_regmap_init(dev, &tmp108_i2c_regmap_bus, client, &tmp108_regmap_config);
>   	if (IS_ERR(regmap))
>   		return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed");
> 
> -	return tmp108_common_probe(dev, regmap, client->name);
> +	/* Prefer OF match data (DT-first systems) */
> +	of_data = device_get_match_data(&client->dev);
> +	if (of_data) {
> +		hw_id = (unsigned long)of_data;
> +	} else {
> +		/* Fall back to legacy I2C ID table */
> +		const struct i2c_device_id *id = i2c_client_get_device_id(client);
> +
> +		if (!id) {
> +			return dev_err_probe(dev, -ENODEV, "No matching device ID for i2c device\n");
> +		}
> +		hw_id = (unsigned long)id->driver_data;
> +	}

That complexity is unnecessary. Just use i2c_get_match_data().

> +
> +	return tmp108_common_probe(dev, regmap, client->name, hw_id);
>   }
> 
>   static int tmp108_suspend(struct device *dev)
> @@ -420,16 +552,18 @@ static int tmp108_resume(struct device *dev)
>   static DEFINE_SIMPLE_DEV_PM_OPS(tmp108_dev_pm_ops, tmp108_suspend, tmp108_resume);
> 
>   static const struct i2c_device_id tmp108_i2c_ids[] = {
> -	{ "p3t1085" },
> -	{ "tmp108" },
> -	{ }
> +	{ "p3t1035", P3T1035_ID },
> +	{ "p3t1085", P3T1085_ID },
> +	{ "tmp108", TMP108_ID },
> +	{ /* sentinel */ },
>   };
>   MODULE_DEVICE_TABLE(i2c, tmp108_i2c_ids);
> 
>   static const struct of_device_id tmp108_of_ids[] = {
> -	{ .compatible = "nxp,p3t1085", },
> -	{ .compatible = "ti,tmp108", },
> -	{}
> +	{ .compatible = "nxp,p3t1035", .data = (void *)(uintptr_t)P3T1035_ID },
> +	{ .compatible = "nxp,p3t1085", .data = (void *)(uintptr_t)P3T1085_ID },
> +	{ .compatible = "ti,tmp108", .data = (void *)(uintptr_t)TMP108_ID },
> +	{ /* sentinel */ },
>   };
>   MODULE_DEVICE_TABLE(of, tmp108_of_ids);
> 
> @@ -444,8 +578,9 @@ static struct i2c_driver tmp108_driver = {
>   };
> 
>   static const struct i3c_device_id p3t1085_i3c_ids[] = {
> -	I3C_DEVICE(0x011b, 0x1529, NULL),
> -	{}
> +	I3C_DEVICE(0x011B, 0x1529, (void *)P3T1085_ID),
> +	I3C_DEVICE(0x011B, 0x152B, (void *)P3T1035_ID),
> +	{ /* sentinel */ },

I know that some people like that comment, and I accept it for new drivers.
I do _not_ accept it being changed in existing drivers.

>   };
>   MODULE_DEVICE_TABLE(i3c, p3t1085_i3c_ids);
> 
> @@ -453,13 +588,21 @@ static int p3t1085_i3c_probe(struct i3c_device *i3cdev)
>   {
>   	struct device *dev = i3cdev_to_dev(i3cdev);
>   	struct regmap *regmap;
> +	const struct i3c_device_id *id;
> +	enum tmp108_hw_id hw_id;
> 
> -	regmap = devm_regmap_init_i3c(i3cdev, &tmp108_regmap_config);
> +	regmap = devm_regmap_init(dev, &tmp108_i3c_regmap_bus, i3cdev, &tmp108_regmap_config);
>   	if (IS_ERR(regmap))
>   		return dev_err_probe(dev, PTR_ERR(regmap),
>   				     "Failed to register i3c regmap\n");
> 
> -	return tmp108_common_probe(dev, regmap, "p3t1085_i3c");
> +	id = i3c_device_match_id(i3cdev, p3t1085_i3c_ids);
> +	if (!id) {
> +		return dev_err_probe(dev, -ENODEV, "No matching device ID for i3c device\n");
> +	}

Unnecessary error check since the id already matches or the function would not
have been called.

> +	hw_id = (enum tmp108_hw_id)(uintptr_t)id->data;
> +
> +	return tmp108_common_probe(dev, regmap, "p3t1085_i3c", hw_id);
>   }
> 
>   static struct i3c_driver p3t1085_driver = {
> --
> 2.34.1
> 


^ permalink raw reply

* Re: [PATCH v3 7/7] soc: qcom: ubwc: Add configuration Glymur platform
From: Dmitry Baryshkov @ 2026-01-16  6:10 UTC (permalink / raw)
  To: Abel Vesa
  Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
	Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Kuogee Hsieh,
	Bjorn Andersson, Konrad Dybcio, linux-arm-msm, dri-devel,
	freedreno, devicetree, linux-kernel
In-Reply-To: <20251027-glymur-display-v3-7-aa13055818ac@linaro.org>

On Mon, Oct 27, 2025 at 04:59:24PM +0200, Abel Vesa wrote:
> Describe the Universal Bandwidth Compression (UBWC) configuration
> for the new Glymur platform.
> 
> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> Signed-off-by: Abel Vesa <abel.vesa@linaro.org>
> ---
>  drivers/soc/qcom/ubwc_config.c | 12 ++++++++++++
>  1 file changed, 12 insertions(+)
> 
> diff --git a/drivers/soc/qcom/ubwc_config.c b/drivers/soc/qcom/ubwc_config.c
> index 15d373bff231d770e00fe0aee1b5a95c7b8a6305..7cca2afb68e3e9d33f3066f1deb3b9fcc01641a1 100644
> --- a/drivers/soc/qcom/ubwc_config.c
> +++ b/drivers/soc/qcom/ubwc_config.c
> @@ -218,11 +218,23 @@ static const struct qcom_ubwc_cfg_data x1e80100_data = {
>  	.macrotile_mode = true,
>  };
>  
> +static const struct qcom_ubwc_cfg_data glymur_data = {
> +	.ubwc_enc_version = UBWC_5_0,
> +	.ubwc_dec_version = UBWC_5_0,
> +	.ubwc_swizzle = UBWC_SWIZZLE_ENABLE_LVL2 |
> +			UBWC_SWIZZLE_ENABLE_LVL3,
> +	.ubwc_bank_spread = true,
> +	/* TODO: highest_bank_bit = 15 for LP_DDR4 */
> +	.highest_bank_bit = 16,

As I started reviewing UBWC bits and pieces... Could you please check,
according to the document I'm looking at this configuration is not
correct.

> +	.macrotile_mode = true,
> +};
> +

-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH v2 2/2] arm64: dts: qcom: kodiak: enable the inline crypto engine for SDHC
From: Neeraj Soni @ 2026-01-16  5:59 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: ulf.hansson, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-mmc, devicetree, linux-kernel
In-Reply-To: <20260115-versatile-bustard-of-bliss-059e5a@quoll>

Hi,

On 1/15/2026 2:35 PM, Krzysztof Kozlowski wrote:
> On Wed, Jan 14, 2026 at 03:18:48PM +0530, Neeraj Soni wrote:
>> Add an ICE node to kodiak SoC description and enable it by adding a
>> phandle to the SDHC node.
>>
>> Signed-off-by: Neeraj Soni <neeraj.soni@oss.qualcomm.com>
>> ---
>>  arch/arm64/boot/dts/qcom/kodiak.dtsi | 9 +++++++++
>>  1 file changed, 9 insertions(+)
>>
>> diff --git a/arch/arm64/boot/dts/qcom/kodiak.dtsi b/arch/arm64/boot/dts/qcom/kodiak.dtsi
>> index c2ccbb67f800..fb2a9c0ea0f5 100644
>> --- a/arch/arm64/boot/dts/qcom/kodiak.dtsi
>> +++ b/arch/arm64/boot/dts/qcom/kodiak.dtsi
>> @@ -1045,6 +1045,8 @@ sdhc_1: mmc@7c4000 {
>>  			qcom,dll-config = <0x0007642c>;
>>  			qcom,ddr-config = <0x80040868>;
>>  
>> +			qcom,ice = <&sdhc_ice>;
>> +
>>  			mmc-ddr-1_8v;
>>  			mmc-hs200-1_8v;
>>  			mmc-hs400-1_8v;
>> @@ -1071,6 +1073,13 @@ opp-384000000 {
>>  			};
>>  		};
>>  
>> +		sdhc_ice: crypto@7C8000 {
> 
> Why this became uppercase?
Thnaks for pointing out. I will fix this next patch.
> 
>> +			compatible = "qcom,sc7280-inline-crypto-engine",
>> +				     "qcom,inline-crypto-engine";
>> +			reg = <0x0 0x007C8000 0x0 0x18000>;
> 
> And this? there is no uppercase at all, so maybye you copied it from
> downstream, but that's not right approach - do not use downstream code.
> 
Yes it was copied but i missed to align with upstream. Thanks for pointing out. This will be fixed in next patch.
> Best regards,
> Krzysztof
> 
Regards
Neeraj

^ permalink raw reply

* Re: [PATCH v5 3/3] counter: Add STM based counter
From: William Breathitt Gray @ 2026-01-16  5:57 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: William Breathitt Gray, Frank.li, robh, conor+dt, krzk+dt, s32,
	devicetree, linux-kernel, linux-iio
In-Reply-To: <20260113165220.1599038-4-daniel.lezcano@linaro.org>

On Tue, Jan 13, 2026 at 05:52:20PM +0100, Daniel Lezcano wrote:
> The NXP S32G2 automotive platform integrates four Cortex-A53 cores and
> three Cortex-M7 cores, along with a large number of timers and
> counters. These hardware blocks can be used as clocksources or
> clockevents, or as timestamp counters shared across the various
> subsystems running alongside the Linux kernel, such as firmware
> components. Their actual usage depends on the overall platform
> software design.
> 
> In a Linux-based system, the kernel controls the counter, which is a
> read-only shared resource for the other subsystems. One of its primary
> purposes is to act as a common timestamp source for messages or
> traces, allowing correlation of events occurring in different
> operating system contexts.
> 
> These changes introduce a basic counter driver that can start, stop,
> and reset the counter. It also handles overflow accounting and
> configures the prescaler value.
> 
> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>

Hi Daniel,

A few of the features in this driver are implemented using somewhat of
anti-patterns in the Generic Counter paradigm. In particular, I believe
the overflow and accumulation features would be best implemented using
the Counter events and watches idioms rather than in the Counter
attributes. I'll explain further inline below.

> +static void nxp_stm_cnt_cfg_overflow(struct nxp_stm_cnt *stm_cnt)
> +{
> +	/*
> +	 * The STM does not have a dedicated interrupt when the
> +	 * counter wraps. We need to use the comparator to check if it
> +	 * wrapped or not.
> +	 */
> +	writel(0, STM_CMP0(stm_cnt->base));
> +}

So to implement overflows, you're currently setting up a compare against
0 to fire off interrupts.

The problem, besides the added complexity in your driver, is that users
now lose control over the comparator for other threshold checks. A
better approach is to define a COUNTER_COMP_COMPARE() attribute for this
comparator which users can set to the value they desire, and push
COUNTER_EVENT_THRESHOLD on the interrupts. Userspace can then watch for
these COUNTER_EVENT_THRESHOLD events and use them to detect overflows,
or for any other general purpose now that they can control the
particular threshold value.

> +static irqreturn_t nxp_stm_cnt_irq(int irq, void *dev_id)
> +{
> +	struct counter_device *counter = dev_id;
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(counter);
> +
> +	nxp_stm_cnt_irq_ack(stm_cnt);
> +
> +	atomic_inc(&stm_cnt->nr_wraps);
> +
> +	counter_push_event(counter, COUNTER_EVENT_OVERFLOW, 0);

Remove nr_wraps and push COUNTER_EVENT_THRESHOLD here instead if you
implement the comparator design I suggested above.

> +static struct counter_comp stm_cnt_count_ext[] = {
> +	COUNTER_COMP_COUNT_U8("prescaler", nxp_stm_cnt_prescaler_read, nxp_stm_cnt_prescaler_write),
> +	COUNTER_COMP_ENABLE(nxp_stm_cnt_count_enable_read, nxp_stm_cnt_count_enable_write),

Add COUNTER_COMP_COMPARE() here for your Counter's comparator.

> +static int nxp_stm_cnt_count_read(struct counter_device *dev,
> +				  struct counter_count *count, u64 *val)
> +{
> +	struct nxp_stm_cnt *stm_cnt = counter_priv(dev);
> +	u32 w1, w2, cnt;
> +
> +	do {
> +		w1 = atomic_read(&stm_cnt->nr_wraps);
> +		cnt = nxp_stm_cnt_get_counter(stm_cnt);
> +		w2 = atomic_read(&stm_cnt->nr_wraps);
> +	} while (w1 != w2);
> +
> +	*val = ((u64)w1 << 32) | cnt;

Just report the raw counter value back directly. If a user wants to
count wraparounds and accumulate the counter, they can do so by setting
a Counter watch in userspace for the COUNTER_EVENT_THRESHOLD event.
Userspace can then keep track of the nr_wraps and perform the
accumulation calculation with the current count value.

> +static int nxp_stm_cnt_watch_validate(struct counter_device *counter,
> +				      const struct counter_watch *watch)
> +{
> +	switch (watch->event) {
> +	case COUNTER_EVENT_OVERFLOW:

This becomes COUNTER_EVENT_THRESHOLD.

> +static const struct counter_ops nxp_stm_cnt_counter_ops = {
> +	.action_read = nxp_stm_cnt_action_read,
> +	.count_read  = nxp_stm_cnt_count_read,
> +	.count_write = nxp_stm_cnt_count_write,
> +	.function_read = nxp_stm_cnt_function_read,
> +	.watch_validate = nxp_stm_cnt_watch_validate,
> +};
> +
> +static struct counter_signal nxp_stm_cnt_signals[] = {
> +	{
> +		.id = 0,
> +		.name = "Counter wrap signal",

Surely this can't be how the signal is described in the datasheet (is
it?). Although you do not have to match the datasheet exactly, the
description should make it obvious which Signal is the source trigger
for the Count state changes. I haven't read the manual for this device,
but I suspect to this signal is named after some sort of clock
oscillator if it's for a timer module; this could be named "System Timer
Module Clock" if the signal is identified simply as that in the manual.

William Breathitt Gray

^ permalink raw reply

* Re: [PATCH v12 3/3] arm64: dts: qcom: talos-evk: Add support for QCS615 talos evk board
From: tessolveupstream @ 2026-01-16  5:55 UTC (permalink / raw)
  To: Jie Gan, andersson, konradybcio
  Cc: robh, krzk+dt, conor+dt, linux-arm-msm, devicetree, linux-kernel,
	Dmitry Baryshkov
In-Reply-To: <5102252a-0f50-4ee9-97b4-a90859a33b2b@oss.qualcomm.com>



On 15-01-2026 07:54, Jie Gan wrote:
> 
> 
> On 1/14/2026 6:00 PM, Sudarshan Shetty wrote:
>> Add the device tree for the QCS615-based Talos EVK platform. The
>> platform is composed of a System-on-Module following the SMARC
>> standard, and a Carrier Board.
>>
>> The Carrier Board supports several display configurations, HDMI and
>> LVDS. Both configurations use the same base hardware, with the display
>> selection controlled by a DIP switch.
>>
>> Use a DTBO file, talos-evk-lvds-auo,g133han01.dtso, which defines an
>> overlay that disables HDMI and adds LVDS. The DTs file talos-evk
>> can describe the HDMI display configurations.
>>
>> The initial device tree includes support for:
>> - CPU and memory
>> - UART
>> - GPIOs
>> - Regulators
>> - PMIC
>> - Early console
>> - AT24MAC602 EEPROM
>> - MCP2515 SPI to CAN
>> - ADV7535 DSI-to-HDMI bridge
>> - DisplayPort interface
>> - SN65DSI84ZXHR DSI-to-LVDS bridge
>> - Wi-Fi/BT
>>
>> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
>> Signed-off-by: Sudarshan Shetty <tessolveupstream@gmail.com>
>> ---
>>   arch/arm64/boot/dts/qcom/Makefile             |   4 +
>>   .../qcom/talos-evk-lvds-auo,g133han01.dtso    | 131 ++++
>>   arch/arm64/boot/dts/qcom/talos-evk-som.dtsi   | 616 ++++++++++++++++++
>>   arch/arm64/boot/dts/qcom/talos-evk.dts        | 139 ++++
>>   4 files changed, 890 insertions(+)
>>   create mode 100644 arch/arm64/boot/dts/qcom/talos-evk-lvds-auo,g133han01.dtso
>>   create mode 100644 arch/arm64/boot/dts/qcom/talos-evk-som.dtsi
>>   create mode 100644 arch/arm64/boot/dts/qcom/talos-evk.dts
>>
>> diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile
>> index 76cf0115a00a..289b651ef0c5 100644
>> --- a/arch/arm64/boot/dts/qcom/Makefile
>> +++ b/arch/arm64/boot/dts/qcom/Makefile
>> @@ -324,6 +324,10 @@ dtb-$(CONFIG_ARCH_QCOM)    += sm8650-mtp.dtb
>>   dtb-$(CONFIG_ARCH_QCOM)    += sm8650-qrd.dtb
>>   dtb-$(CONFIG_ARCH_QCOM)    += sm8750-mtp.dtb
>>   dtb-$(CONFIG_ARCH_QCOM)    += sm8750-qrd.dtb
>> +dtb-$(CONFIG_ARCH_QCOM)    += talos-evk.dtb
>> +talos-evk-lvds-auo,g133han01-dtbs    := \
>> +    talos-evk.dtb talos-evk-lvds-auo,g133han01.dtbo
>> +dtb-$(CONFIG_ARCH_QCOM)    += talos-evk-lvds-auo,g133han01.dtb
>>   x1e001de-devkit-el2-dtbs    := x1e001de-devkit.dtb x1-el2.dtbo
>>   dtb-$(CONFIG_ARCH_QCOM)    += x1e001de-devkit.dtb x1e001de-devkit-el2.dtb
>>   x1e78100-lenovo-thinkpad-t14s-el2-dtbs    := x1e78100-lenovo-thinkpad-t14s.dtb x1-el2.dtbo
>> diff --git a/arch/arm64/boot/dts/qcom/talos-evk-lvds-auo,g133han01.dtso b/arch/arm64/boot/dts/qcom/talos-evk-lvds-auo,g133han01.dtso
>> new file mode 100644
>> index 000000000000..884df2d4f4e1
>> --- /dev/null
>> +++ b/arch/arm64/boot/dts/qcom/talos-evk-lvds-auo,g133han01.dtso
>> @@ -0,0 +1,131 @@
>> +// SPDX-License-Identifier: BSD-3-Clause
>> +/*
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> + */
>> +/dts-v1/;
>> +/plugin/;
>> +
>> +#include <dt-bindings/gpio/gpio.h>
>> +
>> +&{/} {
>> +    backlight: backlight {
>> +        compatible = "gpio-backlight";
>> +        gpios = <&tlmm 59 GPIO_ACTIVE_HIGH>,
>> +            <&tlmm 115 GPIO_ACTIVE_HIGH>;
>> +        default-on;
>> +    };
>> +
>> +    panel-lvds {
>> +        compatible = "auo,g133han01";
>> +        power-supply = <&vreg_v3p3>;
>> +
>> +        ports {
>> +            #address-cells = <1>;
>> +            #size-cells = <0>;
>> +
>> +            /* LVDS A (Odd pixels) */
>> +            port@0 {
>> +                reg = <0>;
>> +                dual-lvds-odd-pixels;
>> +
>> +                lvds_panel_out_a: endpoint {
>> +                    remote-endpoint = <&sn65dsi84_out_a>;
>> +                };
>> +            };
>> +
>> +            /* LVDS B (Even pixels) */
>> +            port@1 {
>> +                reg = <1>;
>> +                dual-lvds-even-pixels;
>> +
>> +                lvds_panel_out_b: endpoint {
>> +                    remote-endpoint = <&sn65dsi84_out_b>;
>> +                };
>> +            };
>> +        };
>> +    };
>> +
>> +    vreg_v3p3: regulator-v3p3 {
>> +        compatible = "regulator-fixed";
>> +        regulator-name = "vdd-3v3";
>> +        regulator-min-microvolt = <3300000>;
>> +        regulator-max-microvolt = <3300000>;
>> +    };
>> +};
>> +
>> +&hdmi_connector {
>> +    status = "disabled";
>> +};
>> +
>> +&i2c1 {
>> +    clock-frequency = <400000>;
>> +
>> +    status = "okay";
>> +
>> +    hdmi_bridge: bridge@3d {
>> +        status = "disabled";
>> +    };
>> +
>> +    lvds_bridge: bridge@2c {
>> +        compatible = "ti,sn65dsi84";
>> +        reg = <0x2c>;
>> +        enable-gpios = <&tlmm 42 GPIO_ACTIVE_HIGH>;
>> +
>> +        ports {
>> +            #address-cells = <1>;
>> +            #size-cells = <0>;
>> +
>> +            port@0 {
>> +                reg = <0>;
>> +
>> +                sn65dsi84_in: endpoint {
>> +                    data-lanes = <1 2 3 4>;
>> +                    remote-endpoint = <&mdss_dsi0_out>;
>> +                };
>> +            };
>> +
>> +            port@2 {
>> +                reg = <2>;
>> +
>> +                sn65dsi84_out_a: endpoint {
>> +                    data-lanes = <1 2 3 4>;
>> +                    remote-endpoint = <&lvds_panel_out_a>;
>> +                };
>> +            };
>> +
>> +            port@3 {
>> +                reg = <3>;
>> +
>> +                sn65dsi84_out_b: endpoint {
>> +                    data-lanes = <1 2 3 4>;
>> +                    remote-endpoint = <&lvds_panel_out_b>;
>> +                };
>> +            };
>> +        };
>> +    };
>> +};
>> +
>> +&mdss_dsi0 {
>> +    vdda-supply = <&vreg_l11a>;
>> +
>> +    status = "okay";
>> +};
>> +
>> +&mdss_dsi0_out {
>> +    remote-endpoint = <&sn65dsi84_in>;
>> +    data-lanes = <0 1 2 3>;
>> +};
>> +
>> +&tlmm {
>> +    lcd_bklt_en: lcd-bklt-en-state {
>> +        pins = "gpio115";
>> +        function = "gpio";
>> +        bias-disable;
>> +    };
>> +
>> +    lcd_bklt_pwm: lcd-bklt-pwm-state {
>> +        pins = "gpio59";
>> +        function = "gpio";
>> +        bias-disable;
>> +    };
>> +};
>> diff --git a/arch/arm64/boot/dts/qcom/talos-evk-som.dtsi b/arch/arm64/boot/dts/qcom/talos-evk-som.dtsi
>> new file mode 100644
>> index 000000000000..95ed335bcb08
>> --- /dev/null
>> +++ b/arch/arm64/boot/dts/qcom/talos-evk-som.dtsi
>> @@ -0,0 +1,616 @@
>> +// SPDX-License-Identifier: BSD-3-Clause
>> +/*
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> + */
>> +/dts-v1/;
>> +
>> +#include <dt-bindings/gpio/gpio.h>
>> +#include <dt-bindings/regulator/qcom,rpmh-regulator.h>
>> +#include "talos.dtsi"
>> +#include "pm8150.dtsi"
> 
> <...>
> 
>> +
>> +&usb_1 {
>> +    status = "okay";
>> +};
>> +
>> +&usb_1_dwc3 {
>> +    dr_mode = "host";
>> +};
>> +
>> +&usb_hsphy_1 {
>> +    vdd-supply = <&vreg_l5a>;
>> +    vdda-pll-supply = <&vreg_l12a>;
>> +    vdda-phy-dpdm-supply = <&vreg_l13a>;
>> +
>> +    status = "okay";
>> +};
>> +
>> +&usb_2 {
>> +    status = "okay";
>> +};
>> +
>> +&usb_2_dwc3 {
>> +    dr_mode = "host";
>> +};
> 
> Both usb devices have been configured to host mode, do we need adb?
> The adb only work with usb peripheral mode.
> 

This topic was discussed previously, and the fix was implemented 
based on that discussion.
For reference, I’m sharing the earlier communication in the 
links below.

https://lore.kernel.org/all/qq4aak33bn3mqxd2edu6zgkkshby63mmitg7zqkly2rj4c2lh7@4s7sndb7e2jr/T/#meaa464a4e6992b36b5d8d41ddc691ee4ea36b1ce

https://lore.kernel.org/all/20251014120223.1914790-1-tessolveupstream@gmail.com/T/#t

> Thanks,
> Jie
> 
>> +
>> +&usb_hsphy_2 {
>> +    vdd-supply = <&vreg_l5a>;
>> +    vdda-pll-supply = <&vreg_l12a>;
>> +    vdda-phy-dpdm-supply = <&vreg_l13a>;
>> +
>> +    status = "okay";
>> +};
>> +
>> +&usb_qmpphy {
>> +    vdda-phy-supply = <&vreg_l5a>;
>> +    vdda-pll-supply = <&vreg_l12a>;
>> +
>> +    status = "okay";
>> +};
>> +
>> +&usb_qmpphy_2 {
>> +    vdda-phy-supply = <&vreg_l11a>;
>> +    vdda-pll-supply = <&vreg_l5a>;
>> +
>> +    status = "okay";
>> +};
>> +
>> +&ufs_mem_hc {
>> +    reset-gpios = <&tlmm 123 GPIO_ACTIVE_LOW>;
>> +    vcc-supply = <&vreg_l17a>;
>> +    vcc-max-microamp = <600000>;
>> +    vccq2-supply = <&vreg_s4a>;
>> +    vccq2-max-microamp = <600000>;
>> +
>> +    status = "okay";
>> +};
>> +
>> +&ufs_mem_phy {
>> +    vdda-phy-supply = <&vreg_l5a>;
>> +    vdda-pll-supply = <&vreg_l12a>;
>> +
>> +    status = "okay";
>> +};
>> +
>> +&venus {
>> +    status = "okay";
>> +};
>> diff --git a/arch/arm64/boot/dts/qcom/talos-evk.dts b/arch/arm64/boot/dts/qcom/talos-evk.dts
>> new file mode 100644
>> index 000000000000..af100e22beee
>> --- /dev/null
>> +++ b/arch/arm64/boot/dts/qcom/talos-evk.dts
>> @@ -0,0 +1,139 @@
>> +// SPDX-License-Identifier: BSD-3-Clause
>> +/*
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> + */
>> +/dts-v1/;
>> +
>> +#include "talos-evk-som.dtsi"
>> +
>> +/ {
>> +    model = "Qualcomm QCS615 IQ 615 EVK";
>> +    compatible = "qcom,talos-evk", "qcom,qcs615", "qcom,sm6150";
>> +    chassis-type = "embedded";
>> +
>> +    aliases {
>> +        mmc1 = &sdhc_2;
>> +    };
>> +
>> +    dp0-connector {
>> +        compatible = "dp-connector";
>> +        label = "DP0";
>> +        type = "full-size";
>> +
>> +        hpd-gpios = <&tlmm 104 GPIO_ACTIVE_HIGH>;
>> +
>> +        port {
>> +            dp0_connector_in: endpoint {
>> +                remote-endpoint = <&mdss_dp0_out>;
>> +            };
>> +        };
>> +    };
>> +
>> +    hdmi_connector: hdmi-out {
>> +        compatible = "hdmi-connector";
>> +        type = "d";
>> +
>> +        port {
>> +            hdmi_con_out: endpoint {
>> +            remote-endpoint = <&adv7535_out>;
>> +            };
>> +        };
>> +    };
>> +
>> +    vreg_v1p8_out: regulator-v1p8-out {
>> +        compatible = "regulator-fixed";
>> +        regulator-name = "vreg-v1p8-out";
>> +        regulator-min-microvolt = <1800000>;
>> +        regulator-max-microvolt = <1800000>;
>> +        vin-supply = <&vreg_v5p0_out>;
>> +        regulator-boot-on;
>> +        regulator-always-on;
>> +    };
>> +
>> +    vreg_v3p3_out: regulator-v3p3-out {
>> +        compatible = "regulator-fixed";
>> +        regulator-name = "vreg-v3p3-out";
>> +        regulator-min-microvolt = <3300000>;
>> +        regulator-max-microvolt = <3300000>;
>> +        vin-supply = <&vreg_v5p0_out>;
>> +        regulator-boot-on;
>> +        regulator-always-on;
>> +    };
>> +
>> +    vreg_v5p0_out: regulator-v5p0-out {
>> +        compatible = "regulator-fixed";
>> +        regulator-name = "vreg-v5p0-out";
>> +        regulator-min-microvolt = <5000000>;
>> +        regulator-max-microvolt = <5000000>;
>> +        regulator-boot-on;
>> +        regulator-always-on;
>> +        /* Powered by system 20V rail (USBC_VBUS_IN) */
>> +    };
>> +};
>> +
>> +&i2c1 {
>> +    clock-frequency = <400000>;
>> +    status = "okay";
>> +
>> +    hdmi_bridge: bridge@3d {
>> +        compatible = "adi,adv7535";
>> +        reg = <0x3d>;
>> +        avdd-supply = <&vreg_v1p8_out>;
>> +        dvdd-supply = <&vreg_v1p8_out>;
>> +        pvdd-supply = <&vreg_v1p8_out>;
>> +        a2vdd-supply = <&vreg_v1p8_out>;
>> +        v3p3-supply = <&vreg_v3p3_out>;
>> +        interrupts-extended = <&tlmm 26 IRQ_TYPE_LEVEL_LOW>;
>> +        adi,dsi-lanes = <4>;
>> +
>> +        ports {
>> +            #address-cells = <1>;
>> +            #size-cells = <0>;
>> +
>> +            port@0 {
>> +                reg = <0>;
>> +
>> +                adv7535_in: endpoint {
>> +                    remote-endpoint = <&mdss_dsi0_out>;
>> +                };
>> +            };
>> +
>> +            port@1 {
>> +                reg = <1>;
>> +
>> +                adv7535_out: endpoint {
>> +                    remote-endpoint = <&hdmi_con_out>;
>> +                };
>> +            };
>> +        };
>> +    };
>> +};
>> +
>> +&mdss_dsi0_out {
>> +    remote-endpoint = <&adv7535_in>;
>> +    data-lanes = <0 1 2 3>;
>> +};
>> +
>> +&pon_pwrkey {
>> +    status = "okay";
>> +};
>> +
>> +&pon_resin {
>> +    linux,code = <KEY_VOLUMEDOWN>;
>> +
>> +    status = "okay";
>> +};
>> +
>> +&sdhc_2 {
>> +    pinctrl-0 = <&sdc2_state_on>;
>> +    pinctrl-1 = <&sdc2_state_off>;
>> +    pinctrl-names = "default", "sleep";
>> +
>> +    bus-width = <4>;
>> +    cd-gpios = <&tlmm 99 GPIO_ACTIVE_LOW>;
>> +
>> +    vmmc-supply = <&vreg_l10a>;
>> +    vqmmc-supply = <&vreg_s4a>;
>> +
>> +    status = "okay";
>> +};
> 


^ permalink raw reply

* Re: [PATCH] dt-bindings: display: ti,am65x-dss: Add AM62L DSS support
From: Swamil Jain @ 2026-01-16  5:51 UTC (permalink / raw)
  To: Rob Herring
  Cc: jyri.sarha, tomi.valkeinen, airlied, simona, maarten.lankhorst,
	mripard, tzimmermann, krzk+dt, conor+dt, devarsht, praneeth, bb,
	vigneshr, dri-devel, devicetree, linux-kernel
In-Reply-To: <CAL_JsqKYsf9dmY3qKx6MVT0-0emTzO=0z32rOzt3070LykhrPA@mail.gmail.com>

Hi Rob,

On 1/3/26 04:04, Rob Herring wrote:
> On Tue, Dec 30, 2025 at 8:23 AM Swamil Jain <s-jain1@ti.com> wrote:
>>
>> Hi Rob,
>>
>> On 12/30/25 07:57, Rob Herring wrote:
>>> On Wed, Dec 24, 2025 at 07:01:50PM +0530, Swamil Jain wrote:
>>>> Update the AM65x DSS bindings to support AM62L which has a single video
>>>> port. Add conditional constraints for AM62L.
>>>>
>>>> Signed-off-by: Swamil Jain <s-jain1@ti.com>
>>>> ---
>>>>    .../bindings/display/ti/ti,am65x-dss.yaml     | 95 +++++++++++++++----
>>>>    1 file changed, 76 insertions(+), 19 deletions(-)
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/display/ti/ti,am65x-dss.yaml b/Documentation/devicetree/bindings/display/ti/ti,am65x-dss.yaml
>>>> index 38fcee91211e..ce39690df4e5 100644
>>>> --- a/Documentation/devicetree/bindings/display/ti/ti,am65x-dss.yaml
>>>> +++ b/Documentation/devicetree/bindings/display/ti/ti,am65x-dss.yaml
>>>> @@ -36,34 +36,50 @@ properties:
>>>>      reg:
>>>>        description:
>>>>          Addresses to each DSS memory region described in the SoC's TRM.
>>>> -    items:
>>>> -      - description: common DSS register area
>>>> -      - description: VIDL1 light video plane
>>>> -      - description: VID video plane
>>>> -      - description: OVR1 overlay manager for vp1
>>>> -      - description: OVR2 overlay manager for vp2
>>>> -      - description: VP1 video port 1
>>>> -      - description: VP2 video port 2
>>>> -      - description: common1 DSS register area
>>>> +    oneOf:
>>>> +      - items:
>>>> +          - description: common DSS register area
>>>> +          - description: VIDL1 light video plane
>>>> +          - description: VID video plane
>>>> +          - description: OVR1 overlay manager for vp1
>>>> +          - description: OVR2 overlay manager for vp2
>>>> +          - description: VP1 video port 1
>>>> +          - description: VP2 video port 2
>>>> +          - description: common1 DSS register area
>>>> +      - items:
>>>> +          - description: common DSS register area
>>>> +          - description: VIDL1 light video plane
>>>> +          - description: OVR1 overlay manager for vp1
>>>> +          - description: VP1 video port 1
>>>> +          - description: common1 DSS register area
>>>>
>>>>      reg-names:
>>>> -    items:
>>>> -      - const: common
>>>> -      - const: vidl1
>>>> -      - const: vid
>>>> -      - const: ovr1
>>>> -      - const: ovr2
>>>> -      - const: vp1
>>>> -      - const: vp2
>>>> -      - const: common1
>>>> +    oneOf:
>>>> +      - items:
>>>> +          - const: common
>>>> +          - const: vidl1
>>>> +          - const: vid
>>>> +          - const: ovr1
>>>> +          - const: ovr2
>>>> +          - const: vp1
>>>> +          - const: vp2
>>>> +          - const: common1
>>>> +      - items:
>>>> +          - const: common
>>>> +          - const: vidl1
>>>> +          - const: ovr1
>>>> +          - const: vp1
>>>> +          - const: common1
>>>>
>>>>      clocks:
>>>> +    minItems: 2
>>>>        items:
>>>>          - description: fck DSS functional clock
>>>>          - description: vp1 Video Port 1 pixel clock
>>>>          - description: vp2 Video Port 2 pixel clock
>>>>
>>>>      clock-names:
>>>> +    minItems: 2
>>>>        items:
>>>>          - const: fck
>>>>          - const: vp1
>>>> @@ -84,7 +100,8 @@ properties:
>>>>        maxItems: 1
>>>>        description: phandle to the associated power domain
>>>>
>>>> -  dma-coherent: true
>>>> +  dma-coherent:
>>>> +    type: boolean
>>>>
>>>>      ports:
>>>>        $ref: /schemas/graph.yaml#/properties/ports
>>>> @@ -195,6 +212,46 @@ allOf:
>>>>                port@0:
>>>>                  properties:
>>>>                    endpoint@1: false
>>>> +  - if:
>>>> +      properties:
>>>> +        compatible:
>>>> +          contains:
>>>> +            const: ti,am62l-dss
>>>> +    then:
>>>> +      properties:
>>>> +        clock-names:
>>>> +          maxItems: 2
>>>> +        clocks:
>>>> +          maxItems: 2
>>>> +        reg:
>>>> +          maxItems: 5
>>>
>>>              reg-names:
>>>                minItems: 8
>>>          else:
>>>            properties:
>>>              reg:
>>>                minItems: 8
>>>              reg-names:
>>>                minItems: 8
>>>
>>> clocks needs similar constraints...
>>
>> Sure, will add in v2.
>>
>>>
>>>> +
>>>> +  - if:
>>>> +      properties:
>>>> +        compatible:
>>>> +          contains:
>>>> +            const: ti,am62l-dss
>>>> +    then:
>>>> +      properties:
>>>> +        reg-names:
>>>> +          items:
>>>> +            - const: common
>>>> +            - const: vidl1
>>>> +            - const: ovr1
>>>> +            - const: vp1
>>>> +            - const: common1
>>>> +    else:
>>>> +      properties:
>>>> +        reg-names:
>>>> +          items:
>>>> +            - const: common
>>>> +            - const: vidl1
>>>> +            - const: vid
>>>> +            - const: ovr1
>>>> +            - const: ovr2
>>>> +            - const: vp1
>>>> +            - const: vp2
>>>> +            - const: common1
>>>
>>> Why are you defining the names twice?
>>>
>>
>> For AM62L we don't have "vid", "ovr2" and "vp2", the dtbs_check will fail.
>> Could you please suggest a better way?
> 
> You already defined them at the top level. Here in the if/then schema,
> all you need is 'maxItems: 5' and 'minItems: 8'. But then you already
> have that as well.

Is this fine or needs some changes?

Regards,
Swamil.

> 
> Rob


^ permalink raw reply

* Re: [PATCH v1 0/3] mmc: dw_mmc-rockchip: Add stability quirk for NanoPi R76S
From: Shawn Lin @ 2026-01-16  0:31 UTC (permalink / raw)
  To: Marco Schirrmeister
  Cc: shawn.lin, robh, krzk+dt, conor+dt, linux-rockchip, linux-mmc,
	devicetree, linux-arm-kernel, heiko, ulf.hansson
In-Reply-To: <CAGJh8eB0i5UYN=05SfNGQc4dMA5_ffktErY8GjX85JWjDna3Dw@mail.gmail.com>

在 2026/01/16 星期五 3:39, Marco Schirrmeister 写道:
> Hello Shawn,
> 
> On Thu, Jan 15, 2026 at 1:25 AM Shawn Lin <shawn.lin@rock-chips.com> wrote:
>>>
>>> Based on this, it makes me believe that power to the sd card is completely cut
>>> and when it wakes up and knows how to continue, it still must go through the
>>> retraining phase.
>>>
>>
>> This is another mistake for your NanoPi R76S board. Before sent this
>> patch, I already checked the dts and saw sdmmc uses vmmc-supply =
>> <&vcc_3v3_s3> which is marked as regulator-always-on, but it's *NOT*
>> actually per the shcematic[1][2]. So need another fix for your board
>> to make it actually gpio-based power controller instaed of function
>> IO based, as when powering off the power domain, the power control bit
>> will not be able to maintain.
>>
>> [1]
>> https://wiki.friendlyelec.com/wiki/images/6/60/NanoPi_R76S_LP4X_2411_SCH.pdf
>> [2]
>> https://wiki.friendlyelec.com/wiki/images/9/90/NanoPi_R76S_LP5_2411_SCH.pdf
>>
>> Except for the patches you have tested, please append the blow patch as
>> well to test.
> 
> Bingo. These DTS additions fixed the power stability for the SD card.
> I have verified that the card now wakes up instantly with no errors and
> no retraining delay.
> 

Thanks, I will fold these two patches into my thread of V4 to fix all
these problems.

> I tested this both with and without your driver patch (the save/restore
> phases logic).
> 
> This confirms the issue was indeed the power rail being cut during
> idle periods. Great find on the schematic!
> 
>> --- a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts
>> +++ b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts
>> @@ -192,6 +192,18 @@
>>                   regulator-name = "vcc_3v3_s0";
>>                   vin-supply = <&vcc_3v3_s3>;
>>           };
>> +
>> +       vcc3v3_sd: regulator-vcc-3v3-sd {
>> +               compatible = "regulator-fixed";
>> +               enable-active-high;
>> +               gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_HIGH>;
>> +               pinctrl-names = "default";
>> +               pinctrl-0 = <&sdmmc_pwren>;
>> +               regulator-name = "vcc3v3_sd";
>> +               regulator-min-microvolt = <3300000>;
>> +               regulator-max-microvolt = <3300000>;
>> +               vin-supply = <&vcc_3v3_s0>;
>> +       };
>>    };
>>
>>    &combphy0_ps {
>> @@ -726,6 +738,12 @@
>>                   };
>>           };
>>
>> +       sdmmc {
>> +               sdmmc_pwren: sdmmc-pwren {
>> +                       rockchip,pins = <0 RK_PB6 RK_FUNC_GPIO
>> &pcfg_pull_none>;
>> +               };
>> +       };
>> +
>>           usb {
>>                   usb_otg0_pwren_h: usb-otg0-pwren-h {
>>                           rockchip,pins = <0 RK_PD1 RK_FUNC_GPIO
>> &pcfg_pull_none>;
>> @@ -751,11 +769,14 @@
>>           bus-width = <4>;
>>           cap-mmc-highspeed;
>>           cap-sd-highspeed;
>> +       cd-gpios = <&gpio0 RK_PA7 GPIO_ACTIVE_LOW>;
>>           disable-wp;
>>           no-mmc;
>>           no-sdio;
>> +       pinctrl-names = "default";
>> +       pinctrl-0 = <&sdmmc0_clk &sdmmc0_cmd &sdmmc0_det &sdmmc0_bus4>;
>>           sd-uhs-sdr104;
>> -       vmmc-supply = <&vcc_3v3_s3>;
>> +       vmmc-supply = <&vcc3v3_sd>;
>>           vqmmc-supply = <&vccio_sd_s0>;
>>           status = "okay";
>>
>>
>>
>>
>>>> --- a/drivers/mmc/host/dw_mmc-rockchip.c
>>>> +++ b/drivers/mmc/host/dw_mmc-rockchip.c
>>>> @@ -36,6 +36,8 @@ struct dw_mci_rockchip_priv_data {
>>>>            int                     default_sample_phase;
>>>>            int                     num_phases;
>>>>            bool                    internal_phase;
>>>> +       int                     sample_phase;
>>>> +       int                     drv_phase;
>>>>     };
>>>>
>>>>     /*
>>>> @@ -573,9 +575,43 @@ static void dw_mci_rockchip_remove(struct
>>>> platform_device *pdev)
>>>>            dw_mci_pltfm_remove(pdev);
>>>>     }
>>>>
>>>> +static int dw_mci_rockchip_runtime_suspend(struct device *dev)
>>>> +{
>>>> +       struct platform_device *pdev = to_platform_device(dev);
>>>> +       struct dw_mci *host = platform_get_drvdata(pdev);
>>>> +       struct dw_mci_rockchip_priv_data *priv = host->priv;
>>>> +
>>>> +       if (priv->internal_phase) {
>>>> +               priv->sample_phase = rockchip_mmc_get_phase(host, true);
>>>> +               priv->drv_phase = rockchip_mmc_get_phase(host, false);
>>>> +       }
>>>> +
>>>> +       return dw_mci_runtime_suspend(dev);
>>>> +}
>>>> +
>>>> +static int dw_mci_rockchip_runtime_resume(struct device *dev)
>>>> +{
>>>> +       struct platform_device *pdev = to_platform_device(dev);
>>>> +       struct dw_mci *host = platform_get_drvdata(pdev);
>>>> +       struct dw_mci_rockchip_priv_data *priv = host->priv;
>>>> +       int ret;
>>>> +
>>>> +       ret = dw_mci_runtime_resume(dev);
>>>> +       if (ret)
>>>> +               return ret;
>>>> +
>>>> +       if (priv->internal_phase) {
>>>> +               rockchip_mmc_set_phase(host, true, priv->sample_phase);
>>>> +               rockchip_mmc_set_phase(host, false, priv->drv_phase);
>>>> +               mci_writel(host, MISC_CON, MEM_CLK_AUTOGATE_ENABLE);
>>>> +       }
>>>> +
>>>> +       return ret;
>>>> +}
>>>> +
>>>>     static const struct dev_pm_ops dw_mci_rockchip_dev_pm_ops = {
>>>>            SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>>> pm_runtime_force_resume)
>>>> -       RUNTIME_PM_OPS(dw_mci_runtime_suspend, dw_mci_runtime_resume, NULL)
>>>> +       RUNTIME_PM_OPS(dw_mci_rockchip_runtime_suspend,
>>>> dw_mci_rockchip_runtime_resume, NULL)
>>>>     };
>>>>
>>>>     static struct platform_driver dw_mci_rockchip_pltfm_driver = {
>>>
>>
> 


^ permalink raw reply

* [PATCH v5 9/9] mailmap: map all Icenowy Zheng's mail addresses
From: Icenowy Zheng @ 2026-01-16  4:37 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei
  Cc: Philipp Zabel, Dmitry Baryshkov, Michal Wilczynski, Luca Ceresoli,
	Han Gao, Yao Zi, linux-kernel, dri-devel, devicetree, linux-riscv,
	Icenowy Zheng, Icenowy Zheng
In-Reply-To: <20260116043746.336328-1-zhengxingda@iscas.ac.cn>

From: Icenowy Zheng <uwu@icenowy.me>

Map all mail addresses Icenowy Zheng had used to the personal mailbox
prefixed "uwu".

All these mailboxes, except the one of Sipeed (which was only used
during a summer vacation internship), can accept mails now.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
No changes since v4.

New patch in v3.

 .mailmap | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.mailmap b/.mailmap
index 84309a39d329c..0fc9602ddd9b0 100644
--- a/.mailmap
+++ b/.mailmap
@@ -316,6 +316,10 @@ Henrik Rydberg <rydberg@bitmath.org>
 Herbert Xu <herbert@gondor.apana.org.au>
 Huacai Chen <chenhuacai@kernel.org> <chenhc@lemote.com>
 Huacai Chen <chenhuacai@kernel.org> <chenhuacai@loongson.cn>
+Icenowy Zheng <uwu@icenowy.me> <zhengxingda@iscas.ac.cn>
+Icenowy Zheng <uwu@icenowy.me> <icenowy@aosc.io>
+Icenowy Zheng <uwu@icenowy.me> <icenowy@aosc.xyz>
+Icenowy Zheng <uwu@icenowy.me> <icenowy@sipeed.com>
 Ike Panhc <ikepanhc@gmail.com> <ike.pan@canonical.com>
 J. Bruce Fields <bfields@fieldses.org> <bfields@redhat.com>
 J. Bruce Fields <bfields@fieldses.org> <bfields@citi.umich.edu>
-- 
2.52.0


^ permalink raw reply related

* [PATCH v5 8/9] MAINTAINERS: assign myself as maintainer for verisilicon DC driver
From: Icenowy Zheng @ 2026-01-16  4:37 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei
  Cc: Philipp Zabel, Dmitry Baryshkov, Michal Wilczynski, Luca Ceresoli,
	Han Gao, Yao Zi, linux-kernel, dri-devel, devicetree, linux-riscv,
	Icenowy Zheng, Icenowy Zheng
In-Reply-To: <20260116043746.336328-1-zhengxingda@iscas.ac.cn>

From: Icenowy Zheng <uwu@icenowy.me>

As I am the author of this rewritten driver, it makes sense for me to be
the maintainer.

Confirm this in MAINTAINERS file.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
No changes since v4.

Changes in v3:
- Switch to my ISCAS mailbox.

No changes in v2.

 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index fc04fc007a054..507ffef26dac1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8658,6 +8658,13 @@ F:	Documentation/devicetree/bindings/display/brcm,bcm2835-*.yaml
 F:	drivers/gpu/drm/vc4/
 F:	include/uapi/drm/vc4_drm.h
 
+DRM DRIVERS FOR VERISILICON DISPLAY CONTROLLER IP
+M:	Icenowy Zheng <zhengxingda@iscas.ac.cn>
+L:	dri-devel@lists.freedesktop.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/display/verisilicon,dc.yaml
+F:	drivers/gpu/drm/verisilicon/
+
 DRM DRIVERS FOR VIVANTE GPU IP
 M:	Lucas Stach <l.stach@pengutronix.de>
 R:	Russell King <linux+etnaviv@armlinux.org.uk>
-- 
2.52.0


^ permalink raw reply related

* [PATCH v5 7/9] riscv: dts: thead: lichee-pi-4a: enable HDMI
From: Icenowy Zheng @ 2026-01-16  4:37 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei
  Cc: Philipp Zabel, Dmitry Baryshkov, Michal Wilczynski, Luca Ceresoli,
	Han Gao, Yao Zi, linux-kernel, dri-devel, devicetree, linux-riscv,
	Icenowy Zheng, Icenowy Zheng, Han Gao
In-Reply-To: <20260116043746.336328-1-zhengxingda@iscas.ac.cn>

Lichee Pi 4A board features a HDMI Type-A connector connected to the
HDMI TX controller of TH1520 SoC.

Add a device tree node describing the connector, connect it to the HDMI
controller, and enable everything on this display pipeline.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
Tested-by: Han Gao <gaohan@iscas.ac.cn>
Tested-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
No changes in v5.

Changes in v4:
- Rebased on top of v6.19-rc1.

No changes in v2, v3.

 .../boot/dts/thead/th1520-lichee-pi-4a.dts    | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
index c58c2085ca92a..7cb7d28683bce 100644
--- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
+++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
@@ -29,6 +29,17 @@ chosen {
 		stdout-path = "serial0:115200n8";
 	};
 
+	hdmi-connector {
+		compatible = "hdmi-connector";
+		type = "a";
+
+		port {
+			hdmi_con_in: endpoint {
+				remote-endpoint = <&hdmi_out_con>;
+			};
+		};
+	};
+
 	thermal-zones {
 		cpu-thermal {
 			polling-delay = <1000>;
@@ -121,6 +132,20 @@ rx-pins {
 	};
 };
 
+&dpu {
+	status = "okay";
+};
+
+&hdmi {
+	status = "okay";
+};
+
+&hdmi_out_port {
+	hdmi_out_con: endpoint {
+		remote-endpoint = <&hdmi_con_in>;
+	};
+};
+
 &uart0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart0_pins>;
-- 
2.52.0


^ permalink raw reply related

* [PATCH v5 5/9] drm/bridge: add a driver for T-Head TH1520 HDMI controller
From: Icenowy Zheng @ 2026-01-16  4:37 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei
  Cc: Philipp Zabel, Dmitry Baryshkov, Michal Wilczynski, Luca Ceresoli,
	Han Gao, Yao Zi, linux-kernel, dri-devel, devicetree, linux-riscv,
	Icenowy Zheng, Icenowy Zheng, Han Gao
In-Reply-To: <20260116043746.336328-1-zhengxingda@iscas.ac.cn>

From: Icenowy Zheng <uwu@icenowy.me>

T-Head TH1520 SoC contains a Synopsys DesignWare HDMI controller (paired
with DesignWare HDMI TX PHY Gen2) that takes the "DP" output from the
display controller.

Add a driver for this controller utilizing the common DesignWare HDMI
code in the kernel.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
Tested-by: Han Gao <gaohan@iscas.ac.cn>
Tested-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
No changes since v3.

Changes in v2:
- Created a new function to set PHY parameters and refactored the
  control flow of the configure_phy callback.

 MAINTAINERS                             |   1 +
 drivers/gpu/drm/bridge/Kconfig          |  10 ++
 drivers/gpu/drm/bridge/Makefile         |   1 +
 drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 173 ++++++++++++++++++++++++
 4 files changed, 185 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 5b11839cba9de..fc04fc007a054 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22556,6 +22556,7 @@ F:	Documentation/devicetree/bindings/reset/thead,th1520-reset.yaml
 F:	arch/riscv/boot/dts/thead/
 F:	drivers/clk/thead/clk-th1520-ap.c
 F:	drivers/firmware/thead,th1520-aon.c
+F:	drivers/gpu/drm/bridge/th1520-dw-hdmi.c
 F:	drivers/mailbox/mailbox-th1520.c
 F:	drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c
 F:	drivers/pinctrl/pinctrl-th1520.c
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index a250afd8d6622..8e19f5fb9ad7c 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -335,6 +335,16 @@ config DRM_THINE_THC63LVD1024
 	help
 	  Thine THC63LVD1024 LVDS/parallel converter driver.
 
+config DRM_THEAD_TH1520_DW_HDMI
+	tristate "T-Head TH1520 DesignWare HDMI bridge"
+	depends on OF
+	depends on COMMON_CLK
+	depends on ARCH_THEAD || COMPILE_TEST
+	select DRM_DW_HDMI
+	help
+	  Choose this to enable support for the internal HDMI bridge found
+	  on the T-Head TH1520 SoC.
+
 config DRM_TOSHIBA_TC358762
 	tristate "TC358762 DSI/DPI bridge"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index c7dc03182e592..085b5db45d6fd 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_DRM_SII902X) += sii902x.o
 obj-$(CONFIG_DRM_SII9234) += sii9234.o
 obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o
 obj-$(CONFIG_DRM_SOLOMON_SSD2825) += ssd2825.o
+obj-$(CONFIG_DRM_THEAD_TH1520_DW_HDMI) += th1520-dw-hdmi.o
 obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o
 obj-$(CONFIG_DRM_TOSHIBA_TC358762) += tc358762.o
 obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
new file mode 100644
index 0000000000000..389eead5f1c45
--- /dev/null
+++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on rcar_dw_hdmi.c, which is:
+ *   Copyright (C) 2016 Renesas Electronics Corporation
+ * Based on imx8mp-hdmi-tx.c, which is:
+ *   Copyright (C) 2022 Pengutronix, Lucas Stach <kernel@pengutronix.de>
+ */
+
+#include <linux/clk.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_modes.h>
+
+#define TH1520_HDMI_PHY_OPMODE_PLLCFG	0x06	/* Mode of operation and PLL dividers */
+#define TH1520_HDMI_PHY_CKSYMTXCTRL	0x09	/* Clock Symbol and Transmitter Control Register */
+#define TH1520_HDMI_PHY_VLEVCTRL	0x0e	/* Voltage Level Control Register */
+#define TH1520_HDMI_PHY_PLLCURRGMPCTRL	0x10	/* PLL current and Gmp (conductance) */
+#define TH1520_HDMI_PHY_PLLDIVCTRL	0x11	/* PLL dividers */
+#define TH1520_HDMI_PHY_TXTERM		0x19	/* Transmission Termination Register */
+
+struct th1520_hdmi_phy_params {
+	unsigned long mpixelclock;
+	u16 opmode_pllcfg;
+	u16 pllcurrgmpctrl;
+	u16 plldivctrl;
+	u16 cksymtxctrl;
+	u16 vlevctrl;
+	u16 txterm;
+};
+
+static const struct th1520_hdmi_phy_params th1520_hdmi_phy_params[] = {
+	{ 35500000,  0x0003, 0x0283, 0x0628, 0x8088, 0x01a0, 0x0007 },
+	{ 44900000,  0x0003, 0x0285, 0x0228, 0x8088, 0x01a0, 0x0007 },
+	{ 71000000,  0x0002, 0x1183, 0x0614, 0x8088, 0x01a0, 0x0007 },
+	{ 90000000,  0x0002, 0x1142, 0x0214, 0x8088, 0x01a0, 0x0007 },
+	{ 121750000, 0x0001, 0x20c0, 0x060a, 0x8088, 0x01a0, 0x0007 },
+	{ 165000000, 0x0001, 0x2080, 0x020a, 0x8088, 0x01a0, 0x0007 },
+	{ 198000000, 0x0000, 0x3040, 0x0605, 0x83c8, 0x0120, 0x0004 },
+	{ 297000000, 0x0000, 0x3041, 0x0205, 0x81dc, 0x0200, 0x0005 },
+	{ 371250000, 0x0640, 0x3041, 0x0205, 0x80f6, 0x0140, 0x0000 },
+	{ 495000000, 0x0640, 0x3080, 0x0005, 0x80f6, 0x0140, 0x0000 },
+	{ 594000000, 0x0640, 0x3080, 0x0005, 0x80fa, 0x01e0, 0x0004 },
+};
+
+struct th1520_hdmi {
+	struct dw_hdmi_plat_data plat_data;
+	struct dw_hdmi *dw_hdmi;
+	struct clk *pixclk;
+	struct reset_control *mainrst, *prst;
+};
+
+static enum drm_mode_status
+th1520_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
+		       const struct drm_display_info *info,
+		       const struct drm_display_mode *mode)
+{
+	/*
+	 * The maximum supported clock frequency is 594 MHz, as shown in the PHY
+	 * parameters table.
+	 */
+	if (mode->clock > 594000)
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static void th1520_hdmi_phy_set_params(struct dw_hdmi *hdmi,
+				const struct th1520_hdmi_phy_params *params)
+{
+	dw_hdmi_phy_i2c_write(hdmi, params->opmode_pllcfg,
+			      TH1520_HDMI_PHY_OPMODE_PLLCFG);
+	dw_hdmi_phy_i2c_write(hdmi, params->pllcurrgmpctrl,
+			      TH1520_HDMI_PHY_PLLCURRGMPCTRL);
+	dw_hdmi_phy_i2c_write(hdmi, params->plldivctrl,
+			      TH1520_HDMI_PHY_PLLDIVCTRL);
+	dw_hdmi_phy_i2c_write(hdmi, params->vlevctrl,
+			      TH1520_HDMI_PHY_VLEVCTRL);
+	dw_hdmi_phy_i2c_write(hdmi, params->cksymtxctrl,
+			      TH1520_HDMI_PHY_CKSYMTXCTRL);
+	dw_hdmi_phy_i2c_write(hdmi, params->txterm,
+			      TH1520_HDMI_PHY_TXTERM);
+}
+
+static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi, void *data,
+				     unsigned long mpixelclock)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(th1520_hdmi_phy_params); i++) {
+		if (mpixelclock <= th1520_hdmi_phy_params[i].mpixelclock) {
+			th1520_hdmi_phy_set_params(hdmi,
+						   &th1520_hdmi_phy_params[i]);
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int th1520_dw_hdmi_probe(struct platform_device *pdev)
+{
+	struct th1520_hdmi *hdmi;
+	struct dw_hdmi_plat_data *plat_data;
+	struct device *dev = &pdev->dev;
+
+	hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+
+	plat_data = &hdmi->plat_data;
+
+	hdmi->pixclk = devm_clk_get_enabled(dev, "pix");
+	if (IS_ERR(hdmi->pixclk))
+		return dev_err_probe(dev, PTR_ERR(hdmi->pixclk),
+				     "Unable to get pixel clock\n");
+
+	hdmi->mainrst = devm_reset_control_get_exclusive_deasserted(dev, "main");
+	if (IS_ERR(hdmi->mainrst))
+		return dev_err_probe(dev, PTR_ERR(hdmi->mainrst),
+				     "Unable to get main reset\n");
+
+	hdmi->prst = devm_reset_control_get_exclusive_deasserted(dev, "apb");
+	if (IS_ERR(hdmi->prst))
+		return dev_err_probe(dev, PTR_ERR(hdmi->prst),
+				     "Unable to get apb reset\n");
+
+	plat_data->output_port = 1;
+	plat_data->mode_valid = th1520_hdmi_mode_valid;
+	plat_data->configure_phy = th1520_hdmi_phy_configure;
+	plat_data->priv_data = hdmi;
+
+	hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data);
+	if (IS_ERR(hdmi))
+		return PTR_ERR(hdmi);
+
+	platform_set_drvdata(pdev, hdmi);
+
+	return 0;
+}
+
+static void th1520_dw_hdmi_remove(struct platform_device *pdev)
+{
+	struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
+
+	dw_hdmi_remove(hdmi);
+}
+
+static const struct of_device_id th1520_dw_hdmi_of_table[] = {
+	{ .compatible = "thead,th1520-dw-hdmi" },
+	{ /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, th1520_dw_hdmi_of_table);
+
+static struct platform_driver th1520_dw_hdmi_platform_driver = {
+	.probe		= th1520_dw_hdmi_probe,
+	.remove		= th1520_dw_hdmi_remove,
+	.driver		= {
+		.name	= "th1520-dw-hdmi",
+		.of_match_table = th1520_dw_hdmi_of_table,
+	},
+};
+
+module_platform_driver(th1520_dw_hdmi_platform_driver);
+
+MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>");
+MODULE_DESCRIPTION("T-Head TH1520 HDMI Encoder Driver");
+MODULE_LICENSE("GPL");
-- 
2.52.0


^ permalink raw reply related

* [PATCH v5 6/9] riscv: dts: thead: add DPU and HDMI device tree nodes
From: Icenowy Zheng @ 2026-01-16  4:37 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei
  Cc: Philipp Zabel, Dmitry Baryshkov, Michal Wilczynski, Luca Ceresoli,
	Han Gao, Yao Zi, linux-kernel, dri-devel, devicetree, linux-riscv,
	Icenowy Zheng, Icenowy Zheng, Han Gao
In-Reply-To: <20260116043746.336328-1-zhengxingda@iscas.ac.cn>

From: Icenowy Zheng <uwu@icenowy.me>

T-Head TH1520 SoC contains a Verisilicon DC8200 display controller
(called DPU in manual) and a Synopsys DesignWare HDMI TX controller.

Add device tree nodes to them.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
Tested-by: Han Gao <gaohan@iscas.ac.cn>
Tested-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
No changes since v4.

Changes in v3:
- Adapting to the changed binding.

No changes in v2.

 arch/riscv/boot/dts/thead/th1520.dtsi | 66 +++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi
index bd5d33840884e..b663077428940 100644
--- a/arch/riscv/boot/dts/thead/th1520.dtsi
+++ b/arch/riscv/boot/dts/thead/th1520.dtsi
@@ -585,6 +585,72 @@ clk_vo: clock-controller@ffef528050 {
 			#clock-cells = <1>;
 		};
 
+		hdmi: hdmi@ffef540000 {
+			compatible = "thead,th1520-dw-hdmi";
+			reg = <0xff 0xef540000 0x0 0x40000>;
+			reg-io-width = <4>;
+			interrupts = <111 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk_vo CLK_HDMI_PCLK>,
+				 <&clk_vo CLK_HDMI_SFR>,
+				 <&clk_vo CLK_HDMI_CEC>,
+				 <&clk_vo CLK_HDMI_PIXCLK>;
+			clock-names = "iahb", "isfr", "cec", "pix";
+			resets = <&rst TH1520_RESET_ID_HDMI>,
+				 <&rst TH1520_RESET_ID_HDMI_APB>;
+			reset-names = "main", "apb";
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				port@0 {
+					reg = <0>;
+
+					hdmi_in: endpoint {
+						remote-endpoint = <&dpu_out_dp1>;
+					};
+				};
+
+				hdmi_out_port: port@1 {
+					reg = <1>;
+				};
+			};
+		};
+
+		dpu: display@ffef600000 {
+			compatible = "thead,th1520-dc8200", "verisilicon,dc";
+			reg = <0xff 0xef600000 0x0 0x100000>;
+			interrupts = <93 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk_vo CLK_DPU_CCLK>,
+				 <&clk_vo CLK_DPU_ACLK>,
+				 <&clk_vo CLK_DPU_HCLK>,
+				 <&clk_vo CLK_DPU_PIXELCLK0>,
+				 <&clk_vo CLK_DPU_PIXELCLK1>;
+			clock-names = "core", "axi", "ahb", "pix0", "pix1";
+			resets = <&rst TH1520_RESET_ID_DPU_CORE>,
+				 <&rst TH1520_RESET_ID_DPU_AXI>,
+				 <&rst TH1520_RESET_ID_DPU_AHB>;
+			reset-names = "core", "axi", "ahb";
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				dpu_port1: port@1 {
+					reg = <1>;
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					dpu_out_dp1: endpoint@1 {
+						reg = <1>;
+						remote-endpoint = <&hdmi_in>;
+					};
+				};
+			};
+		};
+
 		dmac0: dma-controller@ffefc00000 {
 			compatible = "snps,axi-dma-1.01a";
 			reg = <0xff 0xefc00000 0x0 0x1000>;
-- 
2.52.0


^ permalink raw reply related

* [PATCH v5 3/9] drm: verisilicon: add a driver for Verisilicon display controllers
From: Icenowy Zheng @ 2026-01-16  4:37 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei
  Cc: Philipp Zabel, Dmitry Baryshkov, Michal Wilczynski, Luca Ceresoli,
	Han Gao, Yao Zi, linux-kernel, dri-devel, devicetree, linux-riscv,
	Icenowy Zheng, Icenowy Zheng, Han Gao
In-Reply-To: <20260116043746.336328-1-zhengxingda@iscas.ac.cn>

From: Icenowy Zheng <uwu@icenowy.me>

This is a from-scratch driver targeting Verisilicon DC-series display
controllers, which feature self-identification functionality like their
GC-series GPUs.

Only DC8200 is being supported now, and only the main framebuffer is set
up (as the DRM primary plane). Support for more DC models and more
features is my further targets.

As the display controller is delivered to SoC vendors as a whole part,
this driver does not use component framework and extra bridges inside a
SoC is expected to be implemented as dedicated bridges (this driver
properly supports bridge chaining).

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
Tested-by: Han Gao <gaohan@iscas.ac.cn>
Tested-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
Changes in v5:
- Switching to drm_atomic_get_new_bridge_state, which seems to let the
  driver get rid of the hack of saving bus format itself.
- Add the internal bridge before attaching it.
- Adapted next_bridge struct field name suggested by Luca Ceresoli.
- Refactored the probe code to not use port count, to allow port@0 being
  missing.

Changes in v4:
- Switch to drm_* logger when we're handling with struct drm_device.

Changes in v3:
- Get rid of drm_atomic_get_existing_crtc_state() which is marked
  deprecated.

Changes in v2:
- Changed some Control flows according to previous reviews.
- Added missing of_node_put when checking of endpoints for output type.
- Switched all userspace-visible modeset objects to be managed by drmm
  instead of devm.
- Utilize devm_drm_bridge_alloc() in internal bridge.
- Prevented the usage of simple encoder helpers by passing a NULL funcs pointer.
- Let devm enable clocks when getting them.
- Removed explicit `.cache_type = REGCACHE_NONE` in regmap config.
- Fixed a debug print using a variable before initialization.
- Fixed a wrong index when using bulk to handle resets.
- Added missing configuration for DPI format (currently fixed RGB888).

 drivers/gpu/drm/Kconfig                       |   2 +
 drivers/gpu/drm/Makefile                      |   1 +
 drivers/gpu/drm/verisilicon/Kconfig           |  15 +
 drivers/gpu/drm/verisilicon/Makefile          |   5 +
 drivers/gpu/drm/verisilicon/vs_bridge.c       | 336 ++++++++++++++++++
 drivers/gpu/drm/verisilicon/vs_bridge.h       |  39 ++
 drivers/gpu/drm/verisilicon/vs_bridge_regs.h  |  54 +++
 drivers/gpu/drm/verisilicon/vs_crtc.c         | 217 +++++++++++
 drivers/gpu/drm/verisilicon/vs_crtc.h         |  29 ++
 drivers/gpu/drm/verisilicon/vs_crtc_regs.h    |  60 ++++
 drivers/gpu/drm/verisilicon/vs_dc.c           | 204 +++++++++++
 drivers/gpu/drm/verisilicon/vs_dc.h           |  38 ++
 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h  |  27 ++
 drivers/gpu/drm/verisilicon/vs_drm.c          | 177 +++++++++
 drivers/gpu/drm/verisilicon/vs_drm.h          |  29 ++
 drivers/gpu/drm/verisilicon/vs_hwdb.c         | 150 ++++++++
 drivers/gpu/drm/verisilicon/vs_hwdb.h         |  29 ++
 drivers/gpu/drm/verisilicon/vs_plane.c        | 102 ++++++
 drivers/gpu/drm/verisilicon/vs_plane.h        |  68 ++++
 .../gpu/drm/verisilicon/vs_primary_plane.c    | 157 ++++++++
 .../drm/verisilicon/vs_primary_plane_regs.h   |  53 +++
 21 files changed, 1792 insertions(+)
 create mode 100644 drivers/gpu/drm/verisilicon/Kconfig
 create mode 100644 drivers/gpu/drm/verisilicon/Makefile
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 7e6bc0b3a589c..41363da2cc59f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -398,6 +398,8 @@ source "drivers/gpu/drm/imagination/Kconfig"
 
 source "drivers/gpu/drm/tyr/Kconfig"
 
+source "drivers/gpu/drm/verisilicon/Kconfig"
+
 config DRM_HYPERV
 	tristate "DRM Support for Hyper-V synthetic video device"
 	depends on DRM && PCI && HYPERV_VMBUS
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0e1c668b46d21..f2dfa0ad0ab78 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -235,6 +235,7 @@ obj-y			+= solomon/
 obj-$(CONFIG_DRM_SPRD) += sprd/
 obj-$(CONFIG_DRM_LOONGSON) += loongson/
 obj-$(CONFIG_DRM_POWERVR) += imagination/
+obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon/
 
 # Ensure drm headers are self-contained and pass kernel-doc
 hdrtest-files := \
diff --git a/drivers/gpu/drm/verisilicon/Kconfig b/drivers/gpu/drm/verisilicon/Kconfig
new file mode 100644
index 0000000000000..0235577c72824
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_VERISILICON_DC
+	tristate "DRM Support for Verisilicon DC-series display controllers"
+	depends on DRM && COMMON_CLK
+	depends on RISCV || COMPILER_TEST
+	select DRM_CLIENT_SELECTION
+	select DRM_GEM_DMA_HELPER
+	select DRM_KMS_HELPER
+	select DRM_BRIDGE_CONNECTOR
+	select REGMAP_MMIO
+	select VIDEOMODE_HELPERS
+	help
+	  Choose this option if you have a SoC with Verisilicon DC-series
+	  display controllers. If M is selected, the module will be called
+	  verisilicon-dc.
diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile
new file mode 100644
index 0000000000000..fd8d805fbcde1
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+verisilicon-dc-objs := vs_bridge.o vs_crtc.o vs_dc.o vs_drm.o vs_hwdb.o vs_plane.o vs_primary_plane.o
+
+obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon-dc.o
diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.c b/drivers/gpu/drm/verisilicon/vs_bridge.c
new file mode 100644
index 0000000000000..ff3b604983bf6
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#include <uapi/linux/media-bus-format.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "vs_bridge.h"
+#include "vs_bridge_regs.h"
+#include "vs_crtc.h"
+#include "vs_dc.h"
+
+static int vs_bridge_attach(struct drm_bridge *bridge,
+			    struct drm_encoder *encoder,
+			    enum drm_bridge_attach_flags flags)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+
+	return drm_bridge_attach(encoder, vbridge->next_bridge,
+				 bridge, flags);
+}
+
+struct vsdc_dp_format {
+	u32 linux_fmt;
+	bool is_yuv;
+	u32 vsdc_fmt;
+};
+
+static struct vsdc_dp_format vsdc_dp_supported_fmts[] = {
+	/* default to RGB888 */
+	{ MEDIA_BUS_FMT_FIXED, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
+	{ MEDIA_BUS_FMT_RGB888_1X24, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
+	{ MEDIA_BUS_FMT_RGB565_1X16, false, VSDC_DISP_DP_CONFIG_FMT_RGB565 },
+	{ MEDIA_BUS_FMT_RGB666_1X18, false, VSDC_DISP_DP_CONFIG_FMT_RGB666 },
+	{ MEDIA_BUS_FMT_RGB101010_1X30,
+	  false, VSDC_DISP_DP_CONFIG_FMT_RGB101010 },
+	{ MEDIA_BUS_FMT_UYVY8_1X16, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 },
+	{ MEDIA_BUS_FMT_UYVY10_1X20, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 },
+	{ MEDIA_BUS_FMT_YUV8_1X24, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 },
+	{ MEDIA_BUS_FMT_YUV10_1X30, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 },
+	{ MEDIA_BUS_FMT_UYYVYY8_0_5X24,
+	  true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 },
+	{ MEDIA_BUS_FMT_UYYVYY10_0_5X30,
+	  true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 },
+};
+
+static u32 *vs_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
+					struct drm_bridge_state *bridge_state,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state,
+					unsigned int *num_output_fmts)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+	u32 *output_fmts;
+	unsigned int i;
+
+	if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
+		*num_output_fmts = 2;
+	else
+		*num_output_fmts = ARRAY_SIZE(vsdc_dp_supported_fmts);
+
+	output_fmts = kcalloc(*num_output_fmts, sizeof(*output_fmts),
+			      GFP_KERNEL);
+	if (!output_fmts)
+		return NULL;
+
+	if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI) {
+		/* TODO: support more DPI output formats */
+		output_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
+		output_fmts[1] = MEDIA_BUS_FMT_FIXED;
+	} else {
+		for (i = 0; i < *num_output_fmts; i++)
+			output_fmts[i] = vsdc_dp_supported_fmts[i].linux_fmt;
+	}
+
+	return output_fmts;
+}
+
+static bool vs_bridge_out_dp_fmt_supported(u32 out_fmt)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++)
+		if (vsdc_dp_supported_fmts[i].linux_fmt == out_fmt)
+			return true;
+
+	return false;
+}
+
+static u32 *vs_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
+					struct drm_bridge_state *bridge_state,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state,
+					u32 output_fmt,
+					unsigned int *num_input_fmts)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+
+	if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DP &&
+	    !vs_bridge_out_dp_fmt_supported(output_fmt)) {
+		*num_input_fmts = 0;
+		return NULL;
+	}
+
+	return drm_atomic_helper_bridge_propagate_bus_fmt(bridge, bridge_state,
+							  crtc_state,
+							  conn_state,
+							  output_fmt,
+							  num_input_fmts);
+}
+
+static int vs_bridge_atomic_check(struct drm_bridge *bridge,
+				  struct drm_bridge_state *bridge_state,
+				  struct drm_crtc_state *crtc_state,
+				  struct drm_connector_state *conn_state)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+
+	if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DP &&
+	    !vs_bridge_out_dp_fmt_supported(bridge_state->output_bus_cfg.format))
+		return -EINVAL;
+
+	return 0;
+}
+
+static void vs_bridge_atomic_enable(struct drm_bridge *bridge,
+				    struct drm_atomic_state *state)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+	struct drm_bridge_state *br_state =
+		drm_atomic_get_new_bridge_state(state, bridge);
+	struct vs_crtc *crtc = vbridge->crtc;
+	struct vs_dc *dc = crtc->dc;
+	unsigned int output = crtc->id;
+	u32 dp_fmt;
+	unsigned int i;
+
+	DRM_DEBUG_DRIVER("Enabling output %u\n", output);
+
+	switch (vbridge->intf) {
+	case VSDC_OUTPUT_INTERFACE_DPI:
+		regmap_clear_bits(dc->regs, VSDC_DISP_DP_CONFIG(output),
+				  VSDC_DISP_DP_CONFIG_DP_EN);
+		regmap_write(dc->regs, VSDC_DISP_DPI_CONFIG(output),
+			     VSDC_DISP_DPI_CONFIG_FMT_RGB888);
+		break;
+	case VSDC_OUTPUT_INTERFACE_DP:
+		for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++) {
+			if (vsdc_dp_supported_fmts[i].linux_fmt ==
+			    br_state->output_bus_cfg.format)
+				break;
+		}
+		if (WARN_ON_ONCE(i == ARRAY_SIZE(vsdc_dp_supported_fmts)))
+			return;
+		dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
+		dp_fmt |= VSDC_DISP_DP_CONFIG_DP_EN;
+		regmap_write(dc->regs, VSDC_DISP_DP_CONFIG(output), dp_fmt);
+		regmap_assign_bits(dc->regs,
+				   VSDC_DISP_PANEL_CONFIG(output),
+				   VSDC_DISP_PANEL_CONFIG_YUV,
+				   vsdc_dp_supported_fmts[i].is_yuv);
+		break;
+	}
+
+	regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			  VSDC_DISP_PANEL_CONFIG_DAT_POL);
+	regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			   VSDC_DISP_PANEL_CONFIG_DE_POL,
+			   br_state->output_bus_cfg.flags &
+			   DRM_BUS_FLAG_DE_LOW);
+	regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			   VSDC_DISP_PANEL_CONFIG_CLK_POL,
+			   br_state->output_bus_cfg.flags &
+			   DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE);
+	regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			VSDC_DISP_PANEL_CONFIG_DE_EN |
+			VSDC_DISP_PANEL_CONFIG_DAT_EN |
+			VSDC_DISP_PANEL_CONFIG_CLK_EN);
+	regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			VSDC_DISP_PANEL_CONFIG_RUNNING);
+	regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START,
+			  VSDC_DISP_PANEL_START_MULTI_DISP_SYNC);
+	regmap_set_bits(dc->regs, VSDC_DISP_PANEL_START,
+			VSDC_DISP_PANEL_START_RUNNING(output));
+
+	regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id),
+			VSDC_DISP_PANEL_CONFIG_EX_COMMIT);
+}
+
+static void vs_bridge_atomic_disable(struct drm_bridge *bridge,
+				     struct drm_atomic_state *state)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+	struct vs_crtc *crtc = vbridge->crtc;
+	struct vs_dc *dc = crtc->dc;
+	unsigned int output = crtc->id;
+
+	DRM_DEBUG_DRIVER("Disabling output %u\n", output);
+
+	regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START,
+			  VSDC_DISP_PANEL_START_MULTI_DISP_SYNC |
+			  VSDC_DISP_PANEL_START_RUNNING(output));
+	regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			  VSDC_DISP_PANEL_CONFIG_RUNNING);
+
+	regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id),
+			VSDC_DISP_PANEL_CONFIG_EX_COMMIT);
+}
+
+static const struct drm_bridge_funcs vs_bridge_funcs = {
+	.attach = vs_bridge_attach,
+	.atomic_enable = vs_bridge_atomic_enable,
+	.atomic_disable = vs_bridge_atomic_disable,
+	.atomic_check = vs_bridge_atomic_check,
+	.atomic_get_input_bus_fmts = vs_bridge_atomic_get_input_bus_fmts,
+	.atomic_get_output_bus_fmts = vs_bridge_atomic_get_output_bus_fmts,
+	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+	.atomic_reset = drm_atomic_helper_bridge_reset,
+};
+
+static int vs_bridge_detect_output_interface(struct device_node *of_node,
+					     unsigned int output)
+{
+	int ret;
+	struct device_node *remote;
+
+	remote = of_graph_get_remote_node(of_node, output,
+					  VSDC_OUTPUT_INTERFACE_DPI);
+	if (remote) {
+		ret = VSDC_OUTPUT_INTERFACE_DPI;
+	} else {
+		remote = of_graph_get_remote_node(of_node, output,
+						  VSDC_OUTPUT_INTERFACE_DP);
+		if (remote)
+			ret = VSDC_OUTPUT_INTERFACE_DP;
+		else
+			ret = -ENODEV;
+	}
+
+	if (remote)
+		of_node_put(remote);
+
+	return ret;
+}
+
+struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev,
+				 struct vs_crtc *crtc)
+{
+	unsigned int output = crtc->id;
+	struct vs_bridge *bridge;
+	struct drm_bridge *next;
+	enum vs_bridge_output_interface intf;
+	int ret, enctype;
+
+	intf = vs_bridge_detect_output_interface(drm_dev->dev->of_node,
+						 output);
+	if (intf == -ENODEV) {
+		drm_info(drm_dev, "Skipping output %u\n", output);
+		return NULL;
+	}
+
+	next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev->of_node,
+				      output, intf);
+	if (IS_ERR(next)) {
+		ret = PTR_ERR(next);
+		if (ret != -EPROBE_DEFER)
+			drm_err(drm_dev,
+				"Cannot get downstream bridge of output %u\n",
+				output);
+		return ERR_PTR(ret);
+	}
+
+	bridge = devm_drm_bridge_alloc(drm_dev->dev, struct vs_bridge, base,
+				       &vs_bridge_funcs);
+	if (!bridge)
+		return ERR_PTR(-ENOMEM);
+
+	bridge->crtc = crtc;
+	bridge->intf = intf;
+	bridge->next_bridge = next;
+
+	if (intf == VSDC_OUTPUT_INTERFACE_DPI)
+		enctype = DRM_MODE_ENCODER_DPI;
+	else
+		enctype = DRM_MODE_ENCODER_NONE;
+
+	bridge->enc = drmm_plain_encoder_alloc(drm_dev, NULL, enctype, NULL);
+	if (IS_ERR(bridge->enc)) {
+		drm_err(drm_dev,
+			"Cannot initialize encoder for output %u\n", output);
+		ret = PTR_ERR(bridge->enc);
+		return ERR_PTR(ret);
+	}
+
+	bridge->enc->possible_crtcs = drm_crtc_mask(&crtc->base);
+
+	ret = devm_drm_bridge_add(drm_dev->dev, &bridge->base);
+	if (ret) {
+		drm_err(drm_dev,
+			"Cannot add bridge for output %u\n", output);
+		return ERR_PTR(ret);
+	}
+
+	ret = drm_bridge_attach(bridge->enc, &bridge->base, NULL,
+				DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+	if (ret) {
+		drm_err(drm_dev,
+			"Cannot attach bridge for output %u\n", output);
+		return ERR_PTR(ret);
+	}
+
+	bridge->conn = drm_bridge_connector_init(drm_dev, bridge->enc);
+	if (IS_ERR(bridge->conn)) {
+		drm_err(drm_dev,
+			"Cannot create connector for output %u\n", output);
+		ret = PTR_ERR(bridge->conn);
+		return ERR_PTR(ret);
+	}
+	drm_connector_attach_encoder(bridge->conn, bridge->enc);
+
+	return bridge;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h b/drivers/gpu/drm/verisilicon/vs_bridge.h
new file mode 100644
index 0000000000000..70fee1749699a
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_bridge.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#ifndef _VS_BRIDGE_H_
+#define _VS_BRIDGE_H_
+
+#include <linux/types.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+
+struct vs_crtc;
+
+enum vs_bridge_output_interface {
+	VSDC_OUTPUT_INTERFACE_DPI = 0,
+	VSDC_OUTPUT_INTERFACE_DP = 1
+};
+
+struct vs_bridge {
+	struct drm_bridge base;
+	struct drm_encoder *enc;
+	struct drm_connector *conn;
+
+	struct vs_crtc *crtc;
+	struct drm_bridge *next_bridge;
+	enum vs_bridge_output_interface intf;
+};
+
+static inline struct vs_bridge *drm_bridge_to_vs_bridge(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct vs_bridge, base);
+}
+
+struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev,
+				 struct vs_crtc *crtc);
+#endif /* _VS_BRIDGE_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_bridge_regs.h b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
new file mode 100644
index 0000000000000..9eb30e4564beb
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_BRIDGE_REGS_H_
+#define _VS_BRIDGE_REGS_H_
+
+#include <linux/bits.h>
+
+#define VSDC_DISP_PANEL_CONFIG(n)		(0x1418 + 0x4 * (n))
+#define VSDC_DISP_PANEL_CONFIG_DE_EN		BIT(0)
+#define VSDC_DISP_PANEL_CONFIG_DE_POL		BIT(1)
+#define VSDC_DISP_PANEL_CONFIG_DAT_EN		BIT(4)
+#define VSDC_DISP_PANEL_CONFIG_DAT_POL		BIT(5)
+#define VSDC_DISP_PANEL_CONFIG_CLK_EN		BIT(8)
+#define VSDC_DISP_PANEL_CONFIG_CLK_POL		BIT(9)
+#define VSDC_DISP_PANEL_CONFIG_RUNNING		BIT(12)
+#define VSDC_DISP_PANEL_CONFIG_GAMMA		BIT(13)
+#define VSDC_DISP_PANEL_CONFIG_YUV		BIT(16)
+
+#define VSDC_DISP_DPI_CONFIG(n)			(0x14B8 + 0x4 * (n))
+#define VSDC_DISP_DPI_CONFIG_FMT_MASK		GENMASK(2, 0)
+#define VSDC_DISP_DPI_CONFIG_FMT_RGB565		(0)
+#define VSDC_DISP_DPI_CONFIG_FMT_RGB666		(3)
+#define VSDC_DISP_DPI_CONFIG_FMT_RGB888		(5)
+#define VSDC_DISP_DPI_CONFIG_FMT_RGB101010	(6)
+
+#define VSDC_DISP_PANEL_START			0x1CCC
+#define VSDC_DISP_PANEL_START_RUNNING(n)	BIT(n)
+#define VSDC_DISP_PANEL_START_MULTI_DISP_SYNC	BIT(3)
+
+#define VSDC_DISP_DP_CONFIG(n)			(0x1CD0 + 0x4 * (n))
+#define VSDC_DISP_DP_CONFIG_DP_EN		BIT(3)
+#define VSDC_DISP_DP_CONFIG_FMT_MASK		GENMASK(2, 0)
+#define VSDC_DISP_DP_CONFIG_FMT_RGB565		(0)
+#define VSDC_DISP_DP_CONFIG_FMT_RGB666		(1)
+#define VSDC_DISP_DP_CONFIG_FMT_RGB888		(2)
+#define VSDC_DISP_DP_CONFIG_FMT_RGB101010	(3)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_MASK	GENMASK(7, 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8	(2 << 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8	(4 << 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10	(8 << 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10	(10 << 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8	(12 << 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10	(13 << 4)
+
+#define VSDC_DISP_PANEL_CONFIG_EX(n)		(0x2518 + 0x4 * (n))
+#define VSDC_DISP_PANEL_CONFIG_EX_COMMIT	BIT(0)
+
+#endif /* _VS_BRIDGE_REGS_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.c b/drivers/gpu/drm/verisilicon/vs_crtc.c
new file mode 100644
index 0000000000000..bd5304a25368a
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_crtc.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_managed.h>
+
+#include "vs_crtc_regs.h"
+#include "vs_crtc.h"
+#include "vs_dc.h"
+#include "vs_dc_top_regs.h"
+#include "vs_plane.h"
+
+static void vs_crtc_atomic_flush(struct drm_crtc *crtc,
+				 struct drm_atomic_state *state)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+									  crtc);
+	struct drm_pending_vblank_event *event = crtc_state->event;
+
+	DRM_DEBUG_DRIVER("Flushing CRTC %u vblank events\n", vcrtc->id);
+
+	if (event) {
+		crtc_state->event = NULL;
+
+		spin_lock_irq(&crtc->dev->event_lock);
+		if (drm_crtc_vblank_get(crtc) == 0)
+			drm_crtc_arm_vblank_event(crtc, event);
+		else
+			drm_crtc_send_vblank_event(crtc, event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+	}
+}
+
+static void vs_crtc_atomic_disable(struct drm_crtc *crtc,
+				   struct drm_atomic_state *state)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+	unsigned int output = vcrtc->id;
+
+	DRM_DEBUG_DRIVER("Disabling CRTC %u\n", output);
+
+	drm_crtc_vblank_off(crtc);
+
+	clk_disable_unprepare(dc->pix_clk[output]);
+}
+
+static void vs_crtc_atomic_enable(struct drm_crtc *crtc,
+				     struct drm_atomic_state *state)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+	unsigned int output = vcrtc->id;
+
+	DRM_DEBUG_DRIVER("Enabling CRTC %u\n", output);
+
+	WARN_ON(clk_prepare_enable(dc->pix_clk[output]));
+
+	drm_crtc_vblank_on(crtc);
+}
+
+static void vs_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+	unsigned int output = vcrtc->id;
+
+	DRM_DEBUG_DRIVER("Setting mode on CRTC %u\n", output);
+
+	regmap_write(dc->regs, VSDC_DISP_HSIZE(output),
+		     VSDC_DISP_HSIZE_DISP(mode->hdisplay) |
+		     VSDC_DISP_HSIZE_TOTAL(mode->htotal));
+	regmap_write(dc->regs, VSDC_DISP_VSIZE(output),
+		     VSDC_DISP_VSIZE_DISP(mode->vdisplay) |
+		     VSDC_DISP_VSIZE_TOTAL(mode->vtotal));
+	regmap_write(dc->regs, VSDC_DISP_HSYNC(output),
+		     VSDC_DISP_HSYNC_START(mode->hsync_start) |
+		     VSDC_DISP_HSYNC_END(mode->hsync_end) |
+		     VSDC_DISP_HSYNC_EN);
+	if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
+		regmap_set_bits(dc->regs, VSDC_DISP_HSYNC(output),
+				VSDC_DISP_HSYNC_POL);
+	regmap_write(dc->regs, VSDC_DISP_VSYNC(output),
+		     VSDC_DISP_VSYNC_START(mode->vsync_start) |
+		     VSDC_DISP_VSYNC_END(mode->vsync_end) |
+		     VSDC_DISP_VSYNC_EN);
+	if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
+		regmap_set_bits(dc->regs, VSDC_DISP_VSYNC(output),
+				VSDC_DISP_VSYNC_POL);
+
+	WARN_ON(clk_set_rate(dc->pix_clk[output], mode->crtc_clock * 1000));
+}
+
+static enum drm_mode_status
+vs_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+	unsigned int output = vcrtc->id;
+	long rate;
+
+	if (mode->htotal > 0x7FFF)
+		return MODE_BAD_HVALUE;
+	if (mode->vtotal > 0x7FFF)
+		return MODE_BAD_VVALUE;
+
+	rate = clk_round_rate(dc->pix_clk[output], mode->clock * 1000);
+	if (rate <= 0)
+		return MODE_CLOCK_RANGE;
+
+	return MODE_OK;
+}
+
+static bool vs_crtc_mode_fixup(struct drm_crtc *crtc,
+			       const struct drm_display_mode *m,
+			       struct drm_display_mode *adjusted_mode)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+	unsigned int output = vcrtc->id;
+	long clk_rate;
+
+	drm_mode_set_crtcinfo(adjusted_mode, 0);
+
+	/* Feedback the pixel clock to crtc_clock */
+	clk_rate = adjusted_mode->crtc_clock * 1000;
+	clk_rate = clk_round_rate(dc->pix_clk[output], clk_rate);
+	if (clk_rate <= 0)
+		return false;
+
+	adjusted_mode->crtc_clock = clk_rate / 1000;
+
+	return true;
+}
+
+static const struct drm_crtc_helper_funcs vs_crtc_helper_funcs = {
+	.atomic_flush	= vs_crtc_atomic_flush,
+	.atomic_enable	= vs_crtc_atomic_enable,
+	.atomic_disable	= vs_crtc_atomic_disable,
+	.mode_set_nofb	= vs_crtc_mode_set_nofb,
+	.mode_valid	= vs_crtc_mode_valid,
+	.mode_fixup	= vs_crtc_mode_fixup,
+};
+
+static int vs_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+
+	DRM_DEBUG_DRIVER("Enabling VBLANK on CRTC %u\n", vcrtc->id);
+	regmap_set_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id));
+
+	return 0;
+}
+
+static void vs_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+
+	DRM_DEBUG_DRIVER("Disabling VBLANK on CRTC %u\n", vcrtc->id);
+	regmap_clear_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id));
+}
+
+static const struct drm_crtc_funcs vs_crtc_funcs = {
+	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
+	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state,
+	.page_flip		= drm_atomic_helper_page_flip,
+	.reset			= drm_atomic_helper_crtc_reset,
+	.set_config		= drm_atomic_helper_set_config,
+	.enable_vblank		= vs_crtc_enable_vblank,
+	.disable_vblank		= vs_crtc_disable_vblank,
+};
+
+struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc,
+			     unsigned int output)
+{
+	struct vs_crtc *vcrtc;
+	struct drm_plane *primary;
+	int ret;
+
+	vcrtc = drmm_kzalloc(drm_dev, sizeof(*vcrtc), GFP_KERNEL);
+	if (!vcrtc)
+		return ERR_PTR(-ENOMEM);
+	vcrtc->dc = dc;
+	vcrtc->id = output;
+
+	/* Create our primary plane */
+	primary = vs_primary_plane_init(drm_dev, dc);
+	if (IS_ERR(primary)) {
+		drm_err(drm_dev, "Couldn't create the primary plane\n");
+		return ERR_PTR(PTR_ERR(primary));
+	}
+
+	ret = drmm_crtc_init_with_planes(drm_dev, &vcrtc->base,
+					 primary,
+					 NULL,
+					 &vs_crtc_funcs,
+					 NULL);
+	if (ret) {
+		drm_err(drm_dev, "Couldn't initialize CRTC\n");
+		return ERR_PTR(ret);
+	}
+
+	drm_crtc_helper_add(&vcrtc->base, &vs_crtc_helper_funcs);
+
+	return vcrtc;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.h b/drivers/gpu/drm/verisilicon/vs_crtc.h
new file mode 100644
index 0000000000000..6f862d609b984
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_crtc.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#ifndef _VS_CRTC_H_
+#define _VS_CRTC_H_
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_vblank.h>
+
+struct vs_dc;
+
+struct vs_crtc {
+	struct drm_crtc base;
+
+	struct vs_dc *dc;
+	unsigned int id;
+};
+
+static inline struct vs_crtc *drm_crtc_to_vs_crtc(struct drm_crtc *crtc)
+{
+	return container_of(crtc, struct vs_crtc, base);
+}
+
+struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc,
+			     unsigned int output);
+
+#endif /* _VS_CRTC_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_crtc_regs.h b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h
new file mode 100644
index 0000000000000..c7930e817635c
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_CRTC_REGS_H_
+#define _VS_CRTC_REGS_H_
+
+#include <linux/bits.h>
+
+#define VSDC_DISP_DITHER_CONFIG(n)		(0x1410 + 0x4 * (n))
+
+#define VSDC_DISP_DITHER_TABLE_LOW(n)		(0x1420 + 0x4 * (n))
+#define VSDC_DISP_DITHER_TABLE_LOW_DEFAULT	0x7B48F3C0
+
+#define VSDC_DISP_DITHER_TABLE_HIGH(n)		(0x1428 + 0x4 * (n))
+#define VSDC_DISP_DITHER_TABLE_HIGH_DEFAULT	0x596AD1E2
+
+#define VSDC_DISP_HSIZE(n)			(0x1430 + 0x4 * (n))
+#define VSDC_DISP_HSIZE_DISP_MASK		GENMASK(14, 0)
+#define VSDC_DISP_HSIZE_DISP(v)			((v) << 0)
+#define VSDC_DISP_HSIZE_TOTAL_MASK		GENMASK(30, 16)
+#define VSDC_DISP_HSIZE_TOTAL(v)		((v) << 16)
+
+#define VSDC_DISP_HSYNC(n)			(0x1438 + 0x4 * (n))
+#define VSDC_DISP_HSYNC_START_MASK		GENMASK(14, 0)
+#define VSDC_DISP_HSYNC_START(v)		((v) << 0)
+#define VSDC_DISP_HSYNC_END_MASK		GENMASK(29, 15)
+#define VSDC_DISP_HSYNC_END(v)			((v) << 15)
+#define VSDC_DISP_HSYNC_EN			BIT(30)
+#define VSDC_DISP_HSYNC_POL			BIT(31)
+
+#define VSDC_DISP_VSIZE(n)			(0x1440 + 0x4 * (n))
+#define VSDC_DISP_VSIZE_DISP_MASK		GENMASK(14, 0)
+#define VSDC_DISP_VSIZE_DISP(v)			((v) << 0)
+#define VSDC_DISP_VSIZE_TOTAL_MASK		GENMASK(30, 16)
+#define VSDC_DISP_VSIZE_TOTAL(v)		((v) << 16)
+
+#define VSDC_DISP_VSYNC(n)			(0x1448 + 0x4 * (n))
+#define VSDC_DISP_VSYNC_START_MASK		GENMASK(14, 0)
+#define VSDC_DISP_VSYNC_START(v)		((v) << 0)
+#define VSDC_DISP_VSYNC_END_MASK		GENMASK(29, 15)
+#define VSDC_DISP_VSYNC_END(v)			((v) << 15)
+#define VSDC_DISP_VSYNC_EN			BIT(30)
+#define VSDC_DISP_VSYNC_POL			BIT(31)
+
+#define VSDC_DISP_CURRENT_LOCATION(n)		(0x1450 + 0x4 * (n))
+
+#define VSDC_DISP_GAMMA_INDEX(n)		(0x1458 + 0x4 * (n))
+
+#define VSDC_DISP_GAMMA_DATA(n)			(0x1460 + 0x4 * (n))
+
+#define VSDC_DISP_IRQ_STA			0x147C
+
+#define VSDC_DISP_IRQ_EN			0x1480
+
+#endif /* _VS_CRTC_REGS_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_dc.c b/drivers/gpu/drm/verisilicon/vs_dc.c
new file mode 100644
index 0000000000000..07ff62d8949b8
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_dc.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+
+#include "vs_crtc.h"
+#include "vs_dc.h"
+#include "vs_dc_top_regs.h"
+#include "vs_drm.h"
+#include "vs_hwdb.h"
+
+static const struct regmap_config vs_dc_regmap_cfg = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = sizeof(u32),
+	/* VSDC_OVL_CONFIG_EX(1) */
+	.max_register = 0x2544,
+};
+
+static const struct of_device_id vs_dc_driver_dt_match[] = {
+	{ .compatible = "verisilicon,dc" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, vs_dc_driver_dt_match);
+
+static irqreturn_t vs_dc_irq_handler(int irq, void *private)
+{
+	struct vs_dc *dc = private;
+	u32 irqs;
+
+	regmap_read(dc->regs, VSDC_TOP_IRQ_ACK, &irqs);
+
+	return vs_drm_handle_irq(dc, irqs);
+}
+
+static int vs_dc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct vs_dc *dc;
+	void __iomem *regs;
+	unsigned int port_count, i;
+	/* pix0/pix1 */
+	char pixclk_name[5];
+	int irq, ret;
+
+	if (!dev->of_node) {
+		dev_err(dev, "can't find DC devices\n");
+		return -ENODEV;
+	}
+
+	port_count = of_graph_get_port_count(dev->of_node);
+	if (!port_count) {
+		dev_err(dev, "can't find DC downstream ports\n");
+		return -ENODEV;
+	}
+	if (port_count > VSDC_MAX_OUTPUTS) {
+		dev_err(dev, "too many DC downstream ports than possible\n");
+		return -EINVAL;
+	}
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret) {
+		dev_err(dev, "No suitable DMA available\n");
+		return ret;
+	}
+
+	dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL);
+	if (!dc)
+		return -ENOMEM;
+
+	dc->rsts[0].id = "core";
+	dc->rsts[1].id = "axi";
+	dc->rsts[2].id = "ahb";
+
+	ret = devm_reset_control_bulk_get_optional_shared(dev, VSDC_RESET_COUNT,
+							  dc->rsts);
+	if (ret) {
+		dev_err(dev, "can't get reset lines\n");
+		return ret;
+	}
+
+	dc->core_clk = devm_clk_get_enabled(dev, "core");
+	if (IS_ERR(dc->core_clk)) {
+		dev_err(dev, "can't get core clock\n");
+		return PTR_ERR(dc->core_clk);
+	}
+
+	dc->axi_clk = devm_clk_get_enabled(dev, "axi");
+	if (IS_ERR(dc->axi_clk)) {
+		dev_err(dev, "can't get axi clock\n");
+		return PTR_ERR(dc->axi_clk);
+	}
+
+	dc->ahb_clk = devm_clk_get_enabled(dev, "ahb");
+	if (IS_ERR(dc->ahb_clk)) {
+		dev_err(dev, "can't get ahb clock\n");
+		return PTR_ERR(dc->ahb_clk);
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "can't get irq\n");
+		return irq;
+	}
+
+	ret = reset_control_bulk_deassert(VSDC_RESET_COUNT, dc->rsts);
+	if (ret) {
+		dev_err(dev, "can't deassert reset lines\n");
+		return ret;
+	}
+
+	regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(regs)) {
+		dev_err(dev, "can't map registers");
+		ret = PTR_ERR(regs);
+		goto err_rst_assert;
+	}
+
+	dc->regs = devm_regmap_init_mmio(dev, regs, &vs_dc_regmap_cfg);
+	if (IS_ERR(dc->regs)) {
+		ret = PTR_ERR(dc->regs);
+		goto err_rst_assert;
+	}
+
+	ret = vs_fill_chip_identity(dc->regs, &dc->identity);
+	if (ret)
+		goto err_rst_assert;
+
+	dev_info(dev, "DC%x rev %x customer %x\n", dc->identity.model,
+		 dc->identity.revision, dc->identity.customer_id);
+
+	if (port_count > dc->identity.display_count) {
+		dev_err(dev, "too many downstream ports than HW capability\n");
+		ret = -EINVAL;
+		goto err_rst_assert;
+	}
+
+	for (i = 0; i < dc->identity.display_count; i++) {
+		snprintf(pixclk_name, sizeof(pixclk_name), "pix%u", i);
+		dc->pix_clk[i] = devm_clk_get(dev, pixclk_name);
+		if (IS_ERR(dc->pix_clk[i])) {
+			dev_err(dev, "can't get pixel clk %u\n", i);
+			ret = PTR_ERR(dc->pix_clk[i]);
+			goto err_rst_assert;
+		}
+	}
+
+	ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0,
+			       dev_name(dev), dc);
+	if (ret) {
+		dev_err(dev, "can't request irq\n");
+		goto err_rst_assert;
+	}
+
+	dev_set_drvdata(dev, dc);
+
+	ret = vs_drm_initialize(dc, pdev);
+	if (ret)
+		goto err_rst_assert;
+
+	return 0;
+
+err_rst_assert:
+	reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts);
+	return ret;
+}
+
+static void vs_dc_remove(struct platform_device *pdev)
+{
+	struct vs_dc *dc = dev_get_drvdata(&pdev->dev);
+
+	vs_drm_finalize(dc);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts);
+}
+
+static void vs_dc_shutdown(struct platform_device *pdev)
+{
+	struct vs_dc *dc = dev_get_drvdata(&pdev->dev);
+
+	vs_drm_shutdown_handler(dc);
+}
+
+struct platform_driver vs_dc_platform_driver = {
+	.probe = vs_dc_probe,
+	.remove = vs_dc_remove,
+	.shutdown = vs_dc_shutdown,
+	.driver = {
+		.name = "verisilicon-dc",
+		.of_match_table = vs_dc_driver_dt_match,
+	},
+};
+
+module_platform_driver(vs_dc_platform_driver);
+
+MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>");
+MODULE_DESCRIPTION("Verisilicon display controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/verisilicon/vs_dc.h b/drivers/gpu/drm/verisilicon/vs_dc.h
new file mode 100644
index 0000000000000..ed1016f18758e
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_dc.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_DC_H_
+#define _VS_DC_H_
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <drm/drm_device.h>
+
+#include "vs_hwdb.h"
+
+#define VSDC_MAX_OUTPUTS 2
+#define VSDC_RESET_COUNT 3
+
+struct vs_drm_dev;
+struct vs_crtc;
+
+struct vs_dc {
+	struct regmap *regs;
+	struct clk *core_clk;
+	struct clk *axi_clk;
+	struct clk *ahb_clk;
+	struct clk *pix_clk[VSDC_MAX_OUTPUTS];
+	struct reset_control_bulk_data rsts[VSDC_RESET_COUNT];
+
+	struct vs_drm_dev *drm_dev;
+	struct vs_chip_identity identity;
+};
+
+#endif /* _VS_DC_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
new file mode 100644
index 0000000000000..50509bbbff08f
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_DC_TOP_H_
+#define _VS_DC_TOP_H_
+
+#include <linux/bits.h>
+
+#define VSDC_TOP_RST				0x0000
+
+#define VSDC_TOP_IRQ_ACK			0x0010
+#define VSDC_TOP_IRQ_VSYNC(n)			BIT(n)
+
+#define VSDC_TOP_IRQ_EN				0x0014
+
+#define VSDC_TOP_CHIP_MODEL			0x0020
+
+#define VSDC_TOP_CHIP_REV			0x0024
+
+#define VSDC_TOP_CHIP_CUSTOMER_ID		0x0030
+
+#endif /* _VS_DC_TOP_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_drm.c b/drivers/gpu/drm/verisilicon/vs_drm.c
new file mode 100644
index 0000000000000..cdeb75c2c8d79
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_drm.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/aperture.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/console.h>
+
+#include <drm/clients/drm_client_setup.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "vs_bridge.h"
+#include "vs_crtc.h"
+#include "vs_dc.h"
+#include "vs_dc_top_regs.h"
+#include "vs_drm.h"
+
+#define DRIVER_NAME	"verisilicon"
+#define DRIVER_DESC	"Verisilicon DC-series display controller driver"
+#define DRIVER_MAJOR	1
+#define DRIVER_MINOR	0
+
+static int vs_gem_dumb_create(struct drm_file *file_priv,
+			      struct drm_device *drm,
+			      struct drm_mode_create_dumb *args)
+{
+	/* The hardware wants 128B-aligned pitches for linear buffers. */
+	args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 128);
+
+	return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
+}
+
+DEFINE_DRM_GEM_FOPS(vs_drm_driver_fops);
+
+static const struct drm_driver vs_drm_driver = {
+	.driver_features	= DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+	.fops			= &vs_drm_driver_fops,
+	.name	= DRIVER_NAME,
+	.desc	= DRIVER_DESC,
+	.major	= DRIVER_MAJOR,
+	.minor	= DRIVER_MINOR,
+
+	/* GEM Operations */
+	DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vs_gem_dumb_create),
+	DRM_FBDEV_DMA_DRIVER_OPS,
+};
+
+static const struct drm_mode_config_funcs vs_mode_config_funcs = {
+	.fb_create		= drm_gem_fb_create,
+	.atomic_check		= drm_atomic_helper_check,
+	.atomic_commit		= drm_atomic_helper_commit,
+};
+
+static struct drm_mode_config_helper_funcs vs_mode_config_helper_funcs = {
+	.atomic_commit_tail = drm_atomic_helper_commit_tail,
+};
+
+static void vs_mode_config_init(struct drm_device *drm)
+{
+	drm_mode_config_reset(drm);
+
+	drm->mode_config.min_width = 0;
+	drm->mode_config.min_height = 0;
+	drm->mode_config.max_width = 8192;
+	drm->mode_config.max_height = 8192;
+	drm->mode_config.funcs = &vs_mode_config_funcs;
+	drm->mode_config.helper_private = &vs_mode_config_helper_funcs;
+}
+
+int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct vs_drm_dev *vdrm;
+	struct drm_device *drm;
+	struct vs_crtc *crtc;
+	struct vs_bridge *bridge;
+	unsigned int i;
+	int ret;
+
+	vdrm = devm_drm_dev_alloc(dev, &vs_drm_driver, struct vs_drm_dev, base);
+	if (IS_ERR(vdrm))
+		return PTR_ERR(vdrm);
+
+	drm = &vdrm->base;
+	vdrm->dc = dc;
+	dc->drm_dev = vdrm;
+
+	ret = drmm_mode_config_init(drm);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < dc->identity.display_count; i++) {
+		crtc = vs_crtc_init(drm, dc, i);
+		if (IS_ERR(crtc))
+			return PTR_ERR(crtc);
+
+		bridge = vs_bridge_init(drm, crtc);
+		if (IS_ERR(bridge))
+			return PTR_ERR(bridge);
+
+		vdrm->crtcs[i] = crtc;
+	}
+
+	ret = drm_vblank_init(drm, dc->identity.display_count);
+	if (ret)
+		return ret;
+
+	/* Remove early framebuffers (ie. simplefb) */
+	ret = aperture_remove_all_conflicting_devices(DRIVER_NAME);
+	if (ret)
+		return ret;
+
+	vs_mode_config_init(drm);
+
+	/* Enable connectors polling */
+	drm_kms_helper_poll_init(drm);
+
+	ret = drm_dev_register(drm, 0);
+	if (ret)
+		goto err_fini_poll;
+
+	drm_client_setup(drm, NULL);
+
+	return 0;
+
+err_fini_poll:
+	drm_kms_helper_poll_fini(drm);
+	return ret;
+}
+
+void vs_drm_finalize(struct vs_dc *dc)
+{
+	struct vs_drm_dev *vdrm = dc->drm_dev;
+	struct drm_device *drm = &vdrm->base;
+
+	drm_dev_unregister(drm);
+	drm_kms_helper_poll_fini(drm);
+	drm_atomic_helper_shutdown(drm);
+	dc->drm_dev = NULL;
+}
+
+void vs_drm_shutdown_handler(struct vs_dc *dc)
+{
+	struct vs_drm_dev *vdrm = dc->drm_dev;
+
+	drm_atomic_helper_shutdown(&vdrm->base);
+}
+
+irqreturn_t vs_drm_handle_irq(struct vs_dc *dc, u32 irqs)
+{
+	unsigned int i;
+
+	for (i = 0; i < dc->identity.display_count; i++) {
+		if (irqs & VSDC_TOP_IRQ_VSYNC(i)) {
+			irqs &= ~VSDC_TOP_IRQ_VSYNC(i);
+			if (dc->drm_dev->crtcs[i])
+				drm_crtc_handle_vblank(&dc->drm_dev->crtcs[i]->base);
+		}
+	}
+
+	if (irqs)
+		pr_warn("Unknown Verisilicon DC interrupt 0x%x fired!\n", irqs);
+
+	return IRQ_HANDLED;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_drm.h b/drivers/gpu/drm/verisilicon/vs_drm.h
new file mode 100644
index 0000000000000..bbcd2e527deb6
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_drm.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#ifndef _VS_DRM_H_
+#define _VS_DRM_H_
+
+#include <linux/irqreturn.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#include <drm/drm_device.h>
+
+struct vs_dc;
+
+struct vs_drm_dev {
+	struct drm_device base;
+
+	struct vs_dc *dc;
+	struct vs_crtc *crtcs[VSDC_MAX_OUTPUTS];
+};
+
+int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev);
+void vs_drm_finalize(struct vs_dc *dc);
+void vs_drm_shutdown_handler(struct vs_dc *dc);
+irqreturn_t vs_drm_handle_irq(struct vs_dc *dc, u32 irqs);
+
+#endif /* _VS_DRM_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c
new file mode 100644
index 0000000000000..09336af0900ae
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/errno.h>
+
+#include <drm/drm_fourcc.h>
+
+#include "vs_dc_top_regs.h"
+#include "vs_hwdb.h"
+
+static const u32 vs_formats_array_no_yuv444[] = {
+	DRM_FORMAT_XRGB4444,
+	DRM_FORMAT_XBGR4444,
+	DRM_FORMAT_RGBX4444,
+	DRM_FORMAT_BGRX4444,
+	DRM_FORMAT_ARGB4444,
+	DRM_FORMAT_ABGR4444,
+	DRM_FORMAT_RGBA4444,
+	DRM_FORMAT_BGRA4444,
+	DRM_FORMAT_XRGB1555,
+	DRM_FORMAT_XBGR1555,
+	DRM_FORMAT_RGBX5551,
+	DRM_FORMAT_BGRX5551,
+	DRM_FORMAT_ARGB1555,
+	DRM_FORMAT_ABGR1555,
+	DRM_FORMAT_RGBA5551,
+	DRM_FORMAT_BGRA5551,
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_BGR565,
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_RGBX8888,
+	DRM_FORMAT_BGRX8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_ABGR8888,
+	DRM_FORMAT_RGBA8888,
+	DRM_FORMAT_BGRA8888,
+	DRM_FORMAT_ARGB2101010,
+	DRM_FORMAT_ABGR2101010,
+	DRM_FORMAT_RGBA1010102,
+	DRM_FORMAT_BGRA1010102,
+	/* TODO: non-RGB formats */
+};
+
+static const u32 vs_formats_array_with_yuv444[] = {
+	DRM_FORMAT_XRGB4444,
+	DRM_FORMAT_XBGR4444,
+	DRM_FORMAT_RGBX4444,
+	DRM_FORMAT_BGRX4444,
+	DRM_FORMAT_ARGB4444,
+	DRM_FORMAT_ABGR4444,
+	DRM_FORMAT_RGBA4444,
+	DRM_FORMAT_BGRA4444,
+	DRM_FORMAT_XRGB1555,
+	DRM_FORMAT_XBGR1555,
+	DRM_FORMAT_RGBX5551,
+	DRM_FORMAT_BGRX5551,
+	DRM_FORMAT_ARGB1555,
+	DRM_FORMAT_ABGR1555,
+	DRM_FORMAT_RGBA5551,
+	DRM_FORMAT_BGRA5551,
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_BGR565,
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_RGBX8888,
+	DRM_FORMAT_BGRX8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_ABGR8888,
+	DRM_FORMAT_RGBA8888,
+	DRM_FORMAT_BGRA8888,
+	DRM_FORMAT_ARGB2101010,
+	DRM_FORMAT_ABGR2101010,
+	DRM_FORMAT_RGBA1010102,
+	DRM_FORMAT_BGRA1010102,
+	/* TODO: non-RGB formats */
+};
+
+static const struct vs_formats vs_formats_no_yuv444 = {
+	.array = vs_formats_array_no_yuv444,
+	.num = ARRAY_SIZE(vs_formats_array_no_yuv444)
+};
+
+static const struct vs_formats vs_formats_with_yuv444 = {
+	.array = vs_formats_array_with_yuv444,
+	.num = ARRAY_SIZE(vs_formats_array_with_yuv444)
+};
+
+static struct vs_chip_identity vs_chip_identities[] = {
+	{
+		.model = 0x8200,
+		.revision = 0x5720,
+		.customer_id = ~0U,
+
+		.display_count = 2,
+		.formats = &vs_formats_no_yuv444,
+	},
+	{
+		.model = 0x8200,
+		.revision = 0x5721,
+		.customer_id = 0x30B,
+
+		.display_count = 2,
+		.formats = &vs_formats_no_yuv444,
+	},
+	{
+		.model = 0x8200,
+		.revision = 0x5720,
+		.customer_id = 0x310,
+
+		.display_count = 2,
+		.formats = &vs_formats_with_yuv444,
+	},
+	{
+		.model = 0x8200,
+		.revision = 0x5720,
+		.customer_id = 0x311,
+
+		.display_count = 2,
+		.formats = &vs_formats_no_yuv444,
+	},
+};
+
+int vs_fill_chip_identity(struct regmap *regs,
+			  struct vs_chip_identity *ident)
+{
+	u32 model;
+	u32 revision;
+	u32 customer_id;
+	int i;
+
+	regmap_read(regs, VSDC_TOP_CHIP_MODEL, &model);
+	regmap_read(regs, VSDC_TOP_CHIP_REV, &revision);
+	regmap_read(regs, VSDC_TOP_CHIP_CUSTOMER_ID, &customer_id);
+
+	for (i = 0; i < ARRAY_SIZE(vs_chip_identities); i++) {
+		if (vs_chip_identities[i].model == model &&
+		    vs_chip_identities[i].revision == revision &&
+		    (vs_chip_identities[i].customer_id == customer_id ||
+		     vs_chip_identities[i].customer_id == ~0U)) {
+			memcpy(ident, &vs_chip_identities[i], sizeof(*ident));
+			ident->customer_id = customer_id;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.h b/drivers/gpu/drm/verisilicon/vs_hwdb.h
new file mode 100644
index 0000000000000..92192e4fa0862
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_hwdb.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#ifndef _VS_HWDB_H_
+#define _VS_HWDB_H_
+
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+struct vs_formats {
+	const u32 *array;
+	unsigned int num;
+};
+
+struct vs_chip_identity {
+	u32 model;
+	u32 revision;
+	u32 customer_id;
+
+	u32 display_count;
+	const struct vs_formats *formats;
+};
+
+int vs_fill_chip_identity(struct regmap *regs,
+			  struct vs_chip_identity *ident);
+
+#endif /* _VS_HWDB_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_plane.c b/drivers/gpu/drm/verisilicon/vs_plane.c
new file mode 100644
index 0000000000000..f3c9963b6a4ea
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_plane.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/errno.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_print.h>
+
+#include "vs_plane.h"
+
+void drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format)
+{
+	switch (drm_format) {
+	case DRM_FORMAT_XRGB4444:
+	case DRM_FORMAT_RGBX4444:
+	case DRM_FORMAT_XBGR4444:
+	case DRM_FORMAT_BGRX4444:
+		vs_format->color = VSDC_COLOR_FORMAT_X4R4G4B4;
+		break;
+	case DRM_FORMAT_ARGB4444:
+	case DRM_FORMAT_RGBA4444:
+	case DRM_FORMAT_ABGR4444:
+	case DRM_FORMAT_BGRA4444:
+		vs_format->color = VSDC_COLOR_FORMAT_A4R4G4B4;
+		break;
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_RGBX5551:
+	case DRM_FORMAT_XBGR1555:
+	case DRM_FORMAT_BGRX5551:
+		vs_format->color = VSDC_COLOR_FORMAT_X1R5G5B5;
+		break;
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_RGBA5551:
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_BGRA5551:
+		vs_format->color = VSDC_COLOR_FORMAT_A1R5G5B5;
+		break;
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_BGR565:
+		vs_format->color = VSDC_COLOR_FORMAT_R5G6B5;
+		break;
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_RGBX8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_BGRX8888:
+		vs_format->color = VSDC_COLOR_FORMAT_X8R8G8B8;
+		break;
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_RGBA8888:
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_BGRA8888:
+		vs_format->color = VSDC_COLOR_FORMAT_A8R8G8B8;
+		break;
+	case DRM_FORMAT_ARGB2101010:
+	case DRM_FORMAT_RGBA1010102:
+	case DRM_FORMAT_ABGR2101010:
+	case DRM_FORMAT_BGRA1010102:
+		vs_format->color = VSDC_COLOR_FORMAT_A2R10G10B10;
+		break;
+	default:
+		DRM_WARN("Unexpected drm format!\n");
+	}
+
+	switch (drm_format) {
+	case DRM_FORMAT_RGBX4444:
+	case DRM_FORMAT_RGBA4444:
+	case DRM_FORMAT_RGBX5551:
+	case DRM_FORMAT_RGBA5551:
+	case DRM_FORMAT_RGBX8888:
+	case DRM_FORMAT_RGBA8888:
+	case DRM_FORMAT_RGBA1010102:
+		vs_format->swizzle = VSDC_SWIZZLE_RGBA;
+		break;
+	case DRM_FORMAT_XBGR4444:
+	case DRM_FORMAT_ABGR4444:
+	case DRM_FORMAT_XBGR1555:
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_BGR565:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_ABGR2101010:
+		vs_format->swizzle = VSDC_SWIZZLE_ABGR;
+		break;
+	case DRM_FORMAT_BGRX4444:
+	case DRM_FORMAT_BGRA4444:
+	case DRM_FORMAT_BGRX5551:
+	case DRM_FORMAT_BGRA5551:
+	case DRM_FORMAT_BGRX8888:
+	case DRM_FORMAT_BGRA8888:
+	case DRM_FORMAT_BGRA1010102:
+		vs_format->swizzle = VSDC_SWIZZLE_BGRA;
+		break;
+	default:
+		/* N/A for YUV formats */
+		vs_format->swizzle = VSDC_SWIZZLE_ARGB;
+	}
+
+	/* N/A for non-YUV formats */
+	vs_format->uv_swizzle = false;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_plane.h b/drivers/gpu/drm/verisilicon/vs_plane.h
new file mode 100644
index 0000000000000..3595267c89b53
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_plane.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_PLANE_H_
+#define _VS_PLANE_H_
+
+#include <linux/types.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_plane.h>
+
+#define VSDC_MAKE_PLANE_SIZE(w, h) (((w) & 0x7fff) | (((h) & 0x7fff) << 15))
+#define VSDC_MAKE_PLANE_POS(x, y) (((x) & 0x7fff) | (((y) & 0x7fff) << 15))
+
+struct vs_dc;
+
+enum vs_color_format {
+	VSDC_COLOR_FORMAT_X4R4G4B4,
+	VSDC_COLOR_FORMAT_A4R4G4B4,
+	VSDC_COLOR_FORMAT_X1R5G5B5,
+	VSDC_COLOR_FORMAT_A1R5G5B5,
+	VSDC_COLOR_FORMAT_R5G6B5,
+	VSDC_COLOR_FORMAT_X8R8G8B8,
+	VSDC_COLOR_FORMAT_A8R8G8B8,
+	VSDC_COLOR_FORMAT_YUY2,
+	VSDC_COLOR_FORMAT_UYVY,
+	VSDC_COLOR_FORMAT_INDEX8,
+	VSDC_COLOR_FORMAT_MONOCHROME,
+	VSDC_COLOR_FORMAT_YV12 = 0xf,
+	VSDC_COLOR_FORMAT_A8,
+	VSDC_COLOR_FORMAT_NV12,
+	VSDC_COLOR_FORMAT_NV16,
+	VSDC_COLOR_FORMAT_RG16,
+	VSDC_COLOR_FORMAT_R8,
+	VSDC_COLOR_FORMAT_NV12_10BIT,
+	VSDC_COLOR_FORMAT_A2R10G10B10,
+	VSDC_COLOR_FORMAT_NV16_10BIT,
+	VSDC_COLOR_FORMAT_INDEX1,
+	VSDC_COLOR_FORMAT_INDEX2,
+	VSDC_COLOR_FORMAT_INDEX4,
+	VSDC_COLOR_FORMAT_P010,
+	VSDC_COLOR_FORMAT_YUV444,
+	VSDC_COLOR_FORMAT_YUV444_10BIT
+};
+
+enum vs_swizzle {
+	VSDC_SWIZZLE_ARGB,
+	VSDC_SWIZZLE_RGBA,
+	VSDC_SWIZZLE_ABGR,
+	VSDC_SWIZZLE_BGRA,
+};
+
+struct vs_format {
+	enum vs_color_format color;
+	enum vs_swizzle swizzle;
+	bool uv_swizzle;
+};
+
+void drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format);
+
+struct drm_plane *vs_primary_plane_init(struct drm_device *dev, struct vs_dc *dc);
+
+#endif /* _VS_PLANE_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane.c b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
new file mode 100644
index 0000000000000..5b03783d4f284
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include "vs_crtc.h"
+#include "vs_plane.h"
+#include "vs_dc.h"
+#include "vs_primary_plane_regs.h"
+
+static int vs_primary_plane_atomic_check(struct drm_plane *plane,
+					 struct drm_atomic_state *state)
+{
+	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+										 plane);
+	struct drm_crtc *crtc = new_plane_state->crtc;
+	struct drm_crtc_state *crtc_state;
+
+	if (!crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	if (WARN_ON(!crtc_state))
+		return -EINVAL;
+
+	return drm_atomic_helper_check_plane_state(new_plane_state,
+						   crtc_state,
+						   DRM_PLANE_NO_SCALING,
+						   DRM_PLANE_NO_SCALING,
+						   false, true);
+}
+
+
+static void vs_primary_plane_atomic_update(struct drm_plane *plane,
+					   struct drm_atomic_state *atomic_state)
+{
+	struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state,
+								       plane);
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_crtc *crtc = state->crtc;
+	struct drm_gem_dma_object *gem;
+	struct vs_dc *dc;
+	struct vs_crtc *vcrtc;
+	struct vs_format fmt;
+	unsigned int output, bpp;
+	dma_addr_t dma_addr;
+
+	if (!crtc)
+		return;
+
+	vcrtc = drm_crtc_to_vs_crtc(crtc);
+	output = vcrtc->id;
+	dc = vcrtc->dc;
+
+	DRM_DEBUG_DRIVER("Updating output %d primary plane\n", output);
+
+	regmap_update_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
+			   VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK,
+			   VSDC_FB_CONFIG_EX_DISPLAY_ID(output));
+
+	if (!state->visible || !fb) {
+		regmap_write(dc->regs, VSDC_FB_CONFIG(output), 0);
+		regmap_write(dc->regs, VSDC_FB_CONFIG_EX(output), 0);
+		goto commit;
+	} else {
+		regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
+				VSDC_FB_CONFIG_EX_FB_EN);
+	}
+
+	drm_format_to_vs_format(state->fb->format->format, &fmt);
+
+	regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output),
+			   VSDC_FB_CONFIG_FMT_MASK,
+			   VSDC_FB_CONFIG_FMT(fmt.color));
+	regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output),
+			   VSDC_FB_CONFIG_SWIZZLE_MASK,
+			   VSDC_FB_CONFIG_SWIZZLE(fmt.swizzle));
+	regmap_assign_bits(dc->regs, VSDC_FB_CONFIG(output),
+			   VSDC_FB_CONFIG_UV_SWIZZLE_EN, fmt.uv_swizzle);
+
+	/* Get the physical address of the buffer in memory */
+	gem = drm_fb_dma_get_gem_obj(fb, 0);
+
+	/* Compute the start of the displayed memory */
+	bpp = fb->format->cpp[0];
+	dma_addr = gem->dma_addr + fb->offsets[0];
+
+	/* Fixup framebuffer address for src coordinates */
+	dma_addr += (state->src.x1 >> 16) * bpp;
+	dma_addr += (state->src.y1 >> 16) * fb->pitches[0];
+
+	regmap_write(dc->regs, VSDC_FB_ADDRESS(output),
+		     lower_32_bits(dma_addr));
+	regmap_write(dc->regs, VSDC_FB_STRIDE(output),
+		     fb->pitches[0]);
+
+	regmap_write(dc->regs, VSDC_FB_TOP_LEFT(output),
+		     VSDC_MAKE_PLANE_POS(state->crtc_x, state->crtc_y));
+	regmap_write(dc->regs, VSDC_FB_BOTTOM_RIGHT(output),
+		     VSDC_MAKE_PLANE_POS(state->crtc_x + state->crtc_w,
+					 state->crtc_y + state->crtc_h));
+	regmap_write(dc->regs, VSDC_FB_SIZE(output),
+		     VSDC_MAKE_PLANE_SIZE(state->crtc_w, state->crtc_h));
+
+	regmap_write(dc->regs, VSDC_FB_BLEND_CONFIG(output),
+		     VSDC_FB_BLEND_CONFIG_BLEND_DISABLE);
+commit:
+	regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
+			VSDC_FB_CONFIG_EX_COMMIT);
+}
+
+static const struct drm_plane_helper_funcs vs_primary_plane_helper_funcs = {
+	.atomic_check	= vs_primary_plane_atomic_check,
+	.atomic_update	= vs_primary_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs vs_primary_plane_funcs = {
+	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
+	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
+	.disable_plane		= drm_atomic_helper_disable_plane,
+	.reset			= drm_atomic_helper_plane_reset,
+	.update_plane		= drm_atomic_helper_update_plane,
+};
+
+struct drm_plane *vs_primary_plane_init(struct drm_device *drm_dev, struct vs_dc *dc)
+{
+	struct drm_plane *plane;
+
+	plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0,
+					   &vs_primary_plane_funcs,
+					   dc->identity.formats->array,
+					   dc->identity.formats->num,
+					   NULL,
+					   DRM_PLANE_TYPE_PRIMARY,
+					   NULL);
+
+	if (IS_ERR(plane))
+		return plane;
+
+	drm_plane_helper_add(plane, &vs_primary_plane_helper_funcs);
+
+	return plane;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h
new file mode 100644
index 0000000000000..cbb125c46b390
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_PRIMARY_PLANE_REGS_H_
+#define _VS_PRIMARY_PLANE_REGS_H_
+
+#include <linux/bits.h>
+
+#define VSDC_FB_ADDRESS(n)			(0x1400 + 0x4 * (n))
+
+#define VSDC_FB_STRIDE(n)			(0x1408 + 0x4 * (n))
+
+#define VSDC_FB_CONFIG(n)			(0x1518 + 0x4 * (n))
+#define VSDC_FB_CONFIG_CLEAR_EN			BIT(8)
+#define VSDC_FB_CONFIG_ROT_MASK			GENMASK(13, 11)
+#define VSDC_FB_CONFIG_ROT(v)			((v) << 11)
+#define VSDC_FB_CONFIG_YUV_SPACE_MASK		GENMASK(16, 14)
+#define VSDC_FB_CONFIG_YUV_SPACE(v)		((v) << 14)
+#define VSDC_FB_CONFIG_TILE_MODE_MASK		GENMASK(21, 17)
+#define VSDC_FB_CONFIG_TILE_MODE(v)		((v) << 14)
+#define VSDC_FB_CONFIG_SCALE_EN			BIT(22)
+#define VSDC_FB_CONFIG_SWIZZLE_MASK		GENMASK(24, 23)
+#define VSDC_FB_CONFIG_SWIZZLE(v)		((v) << 23)
+#define VSDC_FB_CONFIG_UV_SWIZZLE_EN		BIT(25)
+#define VSDC_FB_CONFIG_FMT_MASK			GENMASK(31, 26)
+#define VSDC_FB_CONFIG_FMT(v)			((v) << 26)
+
+#define VSDC_FB_SIZE(n)				(0x1810 + 0x4 * (n))
+/* Fill with value generated with VSDC_MAKE_PLANE_SIZE(w, h) */
+
+#define VSDC_FB_CONFIG_EX(n)			(0x1CC0 + 0x4 * (n))
+#define VSDC_FB_CONFIG_EX_COMMIT		BIT(12)
+#define VSDC_FB_CONFIG_EX_FB_EN			BIT(13)
+#define VSDC_FB_CONFIG_EX_ZPOS_MASK		GENMASK(18, 16)
+#define VSDC_FB_CONFIG_EX_ZPOS(v)		((v) << 16)
+#define VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK	GENMASK(19, 19)
+#define VSDC_FB_CONFIG_EX_DISPLAY_ID(v)		((v) << 19)
+
+#define VSDC_FB_TOP_LEFT(n)			(0x24D8 + 0x4 * (n))
+/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */
+
+#define VSDC_FB_BOTTOM_RIGHT(n)			(0x24E0 + 0x4 * (n))
+/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */
+
+#define VSDC_FB_BLEND_CONFIG(n)			(0x2510 + 0x4 * (n))
+#define VSDC_FB_BLEND_CONFIG_BLEND_DISABLE	BIT(1)
+
+#endif /* _VS_PRIMARY_PLANE_REGS_H_ */
-- 
2.52.0


^ permalink raw reply related

* [PATCH v5 4/9] dt-bindings: display/bridge: add binding for TH1520 HDMI controller
From: Icenowy Zheng @ 2026-01-16  4:37 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei
  Cc: Philipp Zabel, Dmitry Baryshkov, Michal Wilczynski, Luca Ceresoli,
	Han Gao, Yao Zi, linux-kernel, dri-devel, devicetree, linux-riscv,
	Icenowy Zheng, Icenowy Zheng, Krzysztof Kozlowski
In-Reply-To: <20260116043746.336328-1-zhengxingda@iscas.ac.cn>

From: Icenowy Zheng <uwu@icenowy.me>

T-Head TH1520 SoC contains a Synopsys DesignWare HDMI controller paired
with DesignWare HDMI PHY, with an extra clock gate for HDMI pixel clock
and two reset controls.

Add a device tree binding to it.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
---
No changes since v3.

Changes in v2:
- Re-aligned multi-line clocks/resets in example.
- Added Krzysztof's R-b.

 .../display/bridge/thead,th1520-dw-hdmi.yaml  | 120 ++++++++++++++++++
 1 file changed, 120 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml

diff --git a/Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml b/Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml
new file mode 100644
index 0000000000000..68fff885ce15b
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml
@@ -0,0 +1,120 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/bridge/thead,th1520-dw-hdmi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: T-Head TH1520 DesignWare HDMI TX Encoder
+
+maintainers:
+  - Icenowy Zheng <uwu@icenowy.me>
+
+description:
+  The HDMI transmitter is a Synopsys DesignWare HDMI TX controller
+  paired with a DesignWare HDMI Gen2 TX PHY.
+
+allOf:
+  - $ref: /schemas/display/bridge/synopsys,dw-hdmi.yaml#
+
+properties:
+  compatible:
+    enum:
+      - thead,th1520-dw-hdmi
+
+  reg-io-width:
+    const: 4
+
+  clocks:
+    maxItems: 4
+
+  clock-names:
+    items:
+      - const: iahb
+      - const: isfr
+      - const: cec
+      - const: pix
+
+  resets:
+    items:
+      - description: Main reset
+      - description: Configuration APB reset
+
+  reset-names:
+    items:
+      - const: main
+      - const: apb
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Input port connected to DC8200 DPU "DP" output
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: HDMI output port
+
+    required:
+      - port@0
+      - port@1
+
+required:
+  - compatible
+  - reg
+  - reg-io-width
+  - clocks
+  - clock-names
+  - resets
+  - reset-names
+  - interrupts
+  - ports
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/clock/thead,th1520-clk-ap.h>
+    #include <dt-bindings/reset/thead,th1520-reset.h>
+
+    soc {
+      #address-cells = <2>;
+      #size-cells = <2>;
+
+      hdmi@ffef540000 {
+        compatible = "thead,th1520-dw-hdmi";
+        reg = <0xff 0xef540000 0x0 0x40000>;
+        reg-io-width = <4>;
+        interrupts = <111 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&clk_vo CLK_HDMI_PCLK>,
+                 <&clk_vo CLK_HDMI_SFR>,
+                 <&clk_vo CLK_HDMI_CEC>,
+                 <&clk_vo CLK_HDMI_PIXCLK>;
+        clock-names = "iahb", "isfr", "cec", "pix";
+        resets = <&rst_vo TH1520_RESET_ID_HDMI>,
+                 <&rst_vo TH1520_RESET_ID_HDMI_APB>;
+        reset-names = "main", "apb";
+
+        ports {
+          #address-cells = <1>;
+          #size-cells = <0>;
+          port@0 {
+            reg = <0>;
+
+            hdmi_in: endpoint {
+              remote-endpoint = <&dpu_out_dp1>;
+            };
+          };
+
+          port@1 {
+            reg = <1>;
+
+            hdmi_out_conn: endpoint {
+              remote-endpoint = <&hdmi_conn_in>;
+            };
+          };
+        };
+      };
+    };
-- 
2.52.0


^ permalink raw reply related

* [PATCH v5 2/9] dt-bindings: display: add verisilicon,dc
From: Icenowy Zheng @ 2026-01-16  4:37 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei
  Cc: Philipp Zabel, Dmitry Baryshkov, Michal Wilczynski, Luca Ceresoli,
	Han Gao, Yao Zi, linux-kernel, dri-devel, devicetree, linux-riscv,
	Icenowy Zheng, Icenowy Zheng
In-Reply-To: <20260116043746.336328-1-zhengxingda@iscas.ac.cn>

From: Icenowy Zheng <uwu@icenowy.me>

Verisilicon has a series of display controllers prefixed with DC and
with self-identification facility like their GC series GPUs.

Add a device tree binding for it.

Depends on the specific DC model, it can have either one or two display
outputs, and each display output could be set to DPI signal or "DP"
signal (which seems to be some plain parallel bus to HDMI controllers).

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
Changes in v5:
- Dropped the requirement of port@0.
- Dropped the if clause for TH1520, which seems to be not needed because
  of implicit DT binding rules.

Changes in v4:
- Added a comment for "verisilicon,dc" that says the ID/revision is
  discoverable via registers.
- Removed clock minItems constraint w/o specific compatible strings.

Changes in v3:
- Added SoC-specific compatible string, and arm the binding with clock /
  port checking for the specific SoC (with a 2-output DC).

Changes in v2:
- Fixed misspelt "versilicon" in title.
- Moved minItems in clock properties to be earlier than items.
- Re-aligned multi-line clocks and resets in example.

 .../bindings/display/verisilicon,dc.yaml      | 122 ++++++++++++++++++
 1 file changed, 122 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/verisilicon,dc.yaml

diff --git a/Documentation/devicetree/bindings/display/verisilicon,dc.yaml b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
new file mode 100644
index 0000000000000..9dc35ab973f20
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
@@ -0,0 +1,122 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/verisilicon,dc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Verisilicon DC-series display controllers
+
+maintainers:
+  - Icenowy Zheng <uwu@icenowy.me>
+
+properties:
+  $nodename:
+    pattern: "^display@[0-9a-f]+$"
+
+  compatible:
+    items:
+      - enum:
+          - thead,th1520-dc8200
+      - const: verisilicon,dc # DC IPs have discoverable ID/revision registers
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: DC Core clock
+      - description: DMA AXI bus clock
+      - description: Configuration AHB bus clock
+      - description: Pixel clock of output 0
+      - description: Pixel clock of output 1
+
+  clock-names:
+    items:
+      - const: core
+      - const: axi
+      - const: ahb
+      - const: pix0
+      - const: pix1
+
+  resets:
+    items:
+      - description: DC Core reset
+      - description: DMA AXI bus reset
+      - description: Configuration AHB bus reset
+
+  reset-names:
+    items:
+      - const: core
+      - const: axi
+      - const: ahb
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: The first output channel , endpoint 0 should be
+          used for DPI format output and endpoint 1 should be used
+          for DP format output.
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: The second output channel if the DC variant
+          supports. Follow the same endpoint addressing rule with
+          the first port.
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - clock-names
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/clock/thead,th1520-clk-ap.h>
+    #include <dt-bindings/reset/thead,th1520-reset.h>
+    soc {
+      #address-cells = <2>;
+      #size-cells = <2>;
+
+      display@ffef600000 {
+        compatible = "thead,th1520-dc8200", "verisilicon,dc";
+        reg = <0xff 0xef600000 0x0 0x100000>;
+        interrupts = <93 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&clk_vo CLK_DPU_CCLK>,
+                 <&clk_vo CLK_DPU_ACLK>,
+                 <&clk_vo CLK_DPU_HCLK>,
+                 <&clk_vo CLK_DPU_PIXELCLK0>,
+                 <&clk_vo CLK_DPU_PIXELCLK1>;
+        clock-names = "core", "axi", "ahb", "pix0", "pix1";
+        resets = <&rst TH1520_RESET_ID_DPU_CORE>,
+                 <&rst TH1520_RESET_ID_DPU_AXI>,
+                 <&rst TH1520_RESET_ID_DPU_AHB>;
+        reset-names = "core", "axi", "ahb";
+
+        ports {
+          #address-cells = <1>;
+          #size-cells = <0>;
+
+          port@1 {
+            reg = <1>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            dpu_out_dp1: endpoint@1 {
+              reg = <1>;
+              remote-endpoint = <&hdmi_in>;
+            };
+          };
+        };
+      };
+    };
-- 
2.52.0


^ permalink raw reply related

* [PATCH v5 0/9] Verisilicon DC8200 driver (and adaption to TH1520)
From: Icenowy Zheng @ 2026-01-16  4:37 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei
  Cc: Philipp Zabel, Dmitry Baryshkov, Michal Wilczynski, Luca Ceresoli,
	Han Gao, Yao Zi, linux-kernel, dri-devel, devicetree, linux-riscv,
	Icenowy Zheng, Icenowy Zheng

This patchset tries to add a driver for Verisilicon DC8200 driver, and
demonstrates the driver on T-Head TH1520 with its HDMI output.

This display controller IP is used on StarFive JH7110 too, but as the
HDMI controller used there isn't as common as the DesignWare one, I
choose to use TH1520 in this patchset.

The DC driver is written with other DC-series (mainly DC8000, which is
known to be used on Eswin EIC7700 SoC) display controllers in mind, and
uses the identification registers available on all Vivante branded IPs.
A known exception is DCNano display controller, which is unlikely to be
supported by this driver because of totally different register map and
no known identification registers. (P.S. the in-tree loongson DRM driver
seems to be for some DCNano instances based on the register map.)

The HDMI controller seems to come with some common PHY by Synopsys, the
DesignWare HDMI TX 2.0 PHY. By searching a few register names from the
BSP driver of that PHY, that PHY seems to be used by a in-tree dw-hdmi
glue, rcar_dw_hdmi -- an updated downstream version of rcar_dw_hdmi
contains all 6 registers set here in the th1520-dw-hdmi driver. Some
more suprising thing is that RK3288 uses the same PHY too, but the
in-tree dw_hdmi-rockchip driver writes the configuration data array in a
weird way to reuse the HDMI 3D TX PHY configuring function. It might be
valuable to add common configuring function and configuration data
definition for this HDMI 2.0 PHY too, but the current driver in this
patchset simply duplicated most configuration logic from rcar_dw_hdmi
driver (but with 3 extra configuration registers configured, which is
done by their downstream kernel).

Another point to discuss about the dw-hdmi glue is the placement of the
driver file -- because of the display controller being generic, the glue
driver is currently directly placed into drivers/gpu/drm/bridge/ .

Icenowy Zheng (9):
  dt-bindings: vendor-prefixes: add verisilicon
  dt-bindings: display: add verisilicon,dc
  drm: verisilicon: add a driver for Verisilicon display controllers
  dt-bindings: display/bridge: add binding for TH1520 HDMI controller
  drm/bridge: add a driver for T-Head TH1520 HDMI controller
  riscv: dts: thead: add DPU and HDMI device tree nodes
  riscv: dts: thead: lichee-pi-4a: enable HDMI
  MAINTAINERS: assign myself as maintainer for verisilicon DC driver
  mailmap: map all Icenowy Zheng's mail addresses

 .mailmap                                      |   4 +
 .../display/bridge/thead,th1520-dw-hdmi.yaml  | 120 +++++++
 .../bindings/display/verisilicon,dc.yaml      | 122 +++++++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 MAINTAINERS                                   |   8 +
 .../boot/dts/thead/th1520-lichee-pi-4a.dts    |  25 ++
 arch/riscv/boot/dts/thead/th1520.dtsi         |  66 ++++
 drivers/gpu/drm/Kconfig                       |   2 +
 drivers/gpu/drm/Makefile                      |   1 +
 drivers/gpu/drm/bridge/Kconfig                |  10 +
 drivers/gpu/drm/bridge/Makefile               |   1 +
 drivers/gpu/drm/bridge/th1520-dw-hdmi.c       | 173 +++++++++
 drivers/gpu/drm/verisilicon/Kconfig           |  15 +
 drivers/gpu/drm/verisilicon/Makefile          |   5 +
 drivers/gpu/drm/verisilicon/vs_bridge.c       | 336 ++++++++++++++++++
 drivers/gpu/drm/verisilicon/vs_bridge.h       |  39 ++
 drivers/gpu/drm/verisilicon/vs_bridge_regs.h  |  54 +++
 drivers/gpu/drm/verisilicon/vs_crtc.c         | 217 +++++++++++
 drivers/gpu/drm/verisilicon/vs_crtc.h         |  29 ++
 drivers/gpu/drm/verisilicon/vs_crtc_regs.h    |  60 ++++
 drivers/gpu/drm/verisilicon/vs_dc.c           | 204 +++++++++++
 drivers/gpu/drm/verisilicon/vs_dc.h           |  38 ++
 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h  |  27 ++
 drivers/gpu/drm/verisilicon/vs_drm.c          | 177 +++++++++
 drivers/gpu/drm/verisilicon/vs_drm.h          |  29 ++
 drivers/gpu/drm/verisilicon/vs_hwdb.c         | 150 ++++++++
 drivers/gpu/drm/verisilicon/vs_hwdb.h         |  29 ++
 drivers/gpu/drm/verisilicon/vs_plane.c        | 102 ++++++
 drivers/gpu/drm/verisilicon/vs_plane.h        |  68 ++++
 .../gpu/drm/verisilicon/vs_primary_plane.c    | 157 ++++++++
 .../drm/verisilicon/vs_primary_plane_regs.h   |  53 +++
 31 files changed, 2323 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml
 create mode 100644 Documentation/devicetree/bindings/display/verisilicon,dc.yaml
 create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
 create mode 100644 drivers/gpu/drm/verisilicon/Kconfig
 create mode 100644 drivers/gpu/drm/verisilicon/Makefile
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h

-- 
2.52.0


^ permalink raw reply

* [PATCH v5 1/9] dt-bindings: vendor-prefixes: add verisilicon
From: Icenowy Zheng @ 2026-01-16  4:37 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei
  Cc: Philipp Zabel, Dmitry Baryshkov, Michal Wilczynski, Luca Ceresoli,
	Han Gao, Yao Zi, linux-kernel, dri-devel, devicetree, linux-riscv,
	Icenowy Zheng, Icenowy Zheng
In-Reply-To: <20260116043746.336328-1-zhengxingda@iscas.ac.cn>

From: Icenowy Zheng <uwu@icenowy.me>

VeriSilicon is a Silicon IP vendor, which is the current owner of
Vivante series video-related IPs and Hantro series video codec IPs.

Add a vendor prefix for this company.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
No changes since v4.

Changes in v3:
- Add Rob's ACK.

No changes in v2.

 Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index c7591b2aec2a7..18f931f369198 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1745,6 +1745,8 @@ patternProperties:
     description: Variscite Ltd.
   "^vdl,.*":
     description: Van der Laan b.v.
+  "^verisilicon,.*":
+    description: VeriSilicon Microelectronics (Shanghai) Co., Ltd.
   "^vertexcom,.*":
     description: Vertexcom Technologies, Inc.
   "^via,.*":
-- 
2.52.0


^ permalink raw reply related

* Re: [PATCH v3 2/3] hwmon: (tmp108) Add support for P3T1035 and P3T2030
From: kernel test robot @ 2026-01-16  4:31 UTC (permalink / raw)
  To: Mayank Mahajan, linux, corbet, robh, krzk+dt, conor+dt,
	linux-hwmon, devicetree, linux-doc, linux-kernel
  Cc: oe-kbuild-all, priyanka.jain, vikash.bansal, Mayank Mahajan
In-Reply-To: <20260115111418.1851-2-mayankmahajan.x@nxp.com>

Hi Mayank,

kernel test robot noticed the following build warnings:

[auto build test WARNING on groeck-staging/hwmon-next]
[also build test WARNING on linus/master v6.19-rc5]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Mayank-Mahajan/hwmon-tmp108-Add-support-for-P3T1035-and-P3T2030/20260115-191549
base:   https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link:    https://lore.kernel.org/r/20260115111418.1851-2-mayankmahajan.x%40nxp.com
patch subject: [PATCH v3 2/3] hwmon: (tmp108) Add support for P3T1035 and P3T2030
config: xtensa-randconfig-r122-20260116 (https://download.01.org/0day-ci/archive/20260116/202601161234.jWOgBbs8-lkp@intel.com/config)
compiler: xtensa-linux-gcc (GCC) 8.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260116/202601161234.jWOgBbs8-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601161234.jWOgBbs8-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
   drivers/hwmon/tmp108.c: note: in included file (through arch/xtensa/include/asm/bitops.h, include/linux/bitops.h, include/linux/log2.h, ...):
   arch/xtensa/include/asm/processor.h:105:2: sparse: sparse: Unsupported xtensa ABI
   arch/xtensa/include/asm/processor.h:135:2: sparse: sparse: Unsupported Xtensa ABI
>> drivers/hwmon/tmp108.c:455:17: sparse: sparse: macro "memcpy" passed 6 arguments, but takes just 3
   drivers/hwmon/tmp108.c:458:17: sparse: sparse: macro "memcpy" passed 6 arguments, but takes just 3
   drivers/hwmon/tmp108.c:618:1: sparse: sparse: bad integer constant expression
   drivers/hwmon/tmp108.c:618:1: sparse: sparse: static assertion failed: "MODULE_INFO(author, ...) contains embedded NUL byte"
   drivers/hwmon/tmp108.c:619:1: sparse: sparse: bad integer constant expression
   drivers/hwmon/tmp108.c:619:1: sparse: sparse: static assertion failed: "MODULE_INFO(description, ...) contains embedded NUL byte"
   drivers/hwmon/tmp108.c:620:1: sparse: sparse: bad integer constant expression
   drivers/hwmon/tmp108.c:620:1: sparse: sparse: static assertion failed: "MODULE_INFO(license, ...) contains embedded NUL byte"

vim +/memcpy +455 drivers/hwmon/tmp108.c

   433	
   434	static int tmp108_common_probe(struct device *dev, struct regmap *regmap, char *name,
   435				       enum tmp108_hw_id hw_id)
   436	{
   437		struct device *hwmon_dev;
   438		struct tmp108 *tmp108;
   439		u32 config;
   440		int err;
   441	
   442		err = devm_regulator_get_enable(dev, "vcc");
   443		if (err)
   444			return dev_err_probe(dev, err, "Failed to enable regulator\n");
   445	
   446		tmp108 = devm_kzalloc(dev, sizeof(*tmp108), GFP_KERNEL);
   447		if (!tmp108)
   448			return -ENOMEM;
   449	
   450		dev_set_drvdata(dev, tmp108);
   451		tmp108->regmap = regmap;
   452		tmp108->hw_id = hw_id;
   453		tmp108->config_reg_16bits = (hw_id == P3T1035_ID) ? false : true;
   454		if (hw_id == P3T1035_ID)
 > 455			memcpy(tmp108->sample_times, (unsigned int[]){ 125, 250, 1000, 4000 },
   456			       sizeof(tmp108->sample_times));
   457		else
   458			memcpy(tmp108->sample_times, (unsigned int[]){ 63, 250, 1000, 4000 },
   459			       sizeof(tmp108->sample_times));
   460	
   461		err = regmap_read(tmp108->regmap, TMP108_REG_CONF, &config);
   462		if (err < 0) {
   463			dev_err(dev, "error reading config register: %d", err);
   464			return err;
   465		}
   466		tmp108->orig_config = config;
   467	
   468		/* Only continuous mode is supported. */
   469		config &= ~TMP108_CONF_MODE_MASK;
   470		config |= TMP108_MODE_CONTINUOUS;
   471		/* Only comparator mode is supported. */
   472		config &= ~TMP108_CONF_TM;
   473	
   474		err = regmap_write(tmp108->regmap, TMP108_REG_CONF, config);
   475		if (err < 0) {
   476			dev_err(dev, "error writing config register: %d", err);
   477			return err;
   478		}
   479	
   480		tmp108->ready_time = jiffies;
   481		if ((tmp108->orig_config & TMP108_CONF_MODE_MASK) ==
   482		    TMP108_MODE_SHUTDOWN)
   483			tmp108->ready_time +=
   484				msecs_to_jiffies(TMP108_CONVERSION_TIME_MS);
   485	
   486		err = devm_add_action_or_reset(dev, tmp108_restore_config, tmp108);
   487		if (err) {
   488			dev_err(dev, "add action or reset failed: %d", err);
   489			return err;
   490		}
   491	
   492		hwmon_dev = devm_hwmon_device_register_with_info(dev, name,
   493								 tmp108,
   494								 &tmp108_chip_info,
   495								 NULL);
   496		return PTR_ERR_OR_ZERO(hwmon_dev);
   497	}
   498	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: Re: [PATCH v1 2/2] hwmon: Add Eswin EIC7700 PVT sensor driver
From: Huan He @ 2026-01-16  4:30 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: robh, krzk+dt, conor+dt, linux-hwmon, devicetree, linux-kernel,
	p.zabel, ningyu, linmin, pinkesh.vaghela, luyulin, weishangjuan
In-Reply-To: <592de73c-e1c9-4f1e-92c5-81dc17e1901b@roeck-us.net>

> > 
> > Add support for ESWIN EIC7700 Process, Voltage and Temperature sensor. The
> > driver supports temperature and voltage monitoring with polynomial
> > conversion, and provides sysfs interface for sensor data access.
> > 
> > The PVT IP contains one temperature sensor and four voltage sensors for
> > process variation monitoring.
> > 
> > Signed-off-by: Yulin Lu <luyulin@eswincomputing.com>
> > Signed-off-by: Huan He <hehuan1@eswincomputing.com>
> > ---
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * ESWIN EIC7700 Process, Voltage, Temperature sensor driver
> > + *
> > + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd.
> > + *
> > + * Authors:
> > + *   Yulin Lu <luyulin@eswincomputing.com>
> > + *   Huan He <hehuan1@eswincomputing.com>
> > + */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/hwmon-sysfs.h>
> 
> I do not see why this include would be needed.

Thank you for pointing this out. We will remove this #include in v2.

> 
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/polynomial.h>
> > +#include <linux/reset.h>
> > +#include "eic7700-pvt.h"
> > +
> > +/*
> > + * For the sake of the code simplification we created the sensors info table
> > + * with the sensor names, activation modes.
> > + */
> 
> Simplification ? I'd argue that this code is fragile and complicated,
> and doesn't simplify anything.
> 
> > +static int eic7700_pvt_create_sensor_info(struct pvt_hwmon *pvt)
> > +{
> > +	const char *suffixes[PVT_SENSORS_NUM] = {
> 
> PVT_SENSORS_NUM == 6, so this allocates one too many array entries.

The value of PVT_SENSORS_NUM is 5, and the PVT_SENSOR_FIRST and
PVT_SENSOR_LAST macros are redundant. They will be removed.

> 
> > +		" Temperature",
> > +		" Voltage",
> > +		" Low-Vt",
> > +		" UltraLow-Vt",
> > +		" Standard-Vt"
> > +	};
> > +	struct device *dev = pvt->dev;
> > +	struct device_node *np = dev->of_node;
> > +	struct pvt_sensor_info *info_array;
> > +	char *labels[PVT_SENSORS_NUM];
> 
> This is a character array with PVT_SENSORS_NUM entries.
> 
> > +	const char *prefix = NULL;
> > +	const char *node_label;
> > +	int i;
> > +
> > +	if (of_property_read_string(np, "label", &node_label)) {
> > +		dev_err(dev, "Missing 'label' property in DTS node\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	struct {
> > +		const char *label;
> > +		const char *prefix;
> > +	} label_map[] = {
> > +		{ "pvt0", "SoC"},
> > +		{ "pvt1", "DDR Core"},
> > +	};
> 
> It seems to me that using a string property such as "label"
> to distinguish SoC from DDR core is just wrong. First of all,
> the assignment is really fixed. The driver instance name could
> be used to identify that. Having each individual sensor named
> "SoC <something>" or "DDR core <something>" seems excessive.
> It would be much simpler and less error prone to name the instances
> instead and use the same labels for both.
> 
> Ah, I see in the probe function that this is already the case.
> So the instances are named "soc_pvt" and "ddr_pvt", and then
> the individual sensors are again named "SoC ..." and "DDR Core ...".
> That seems redundant.

The original design was indeed redundant. In v2, we will:

1.Standardize the naming of all temperature sensors as "Temperature", and
voltage sensors as "Voltage", "Low-Vt", etc.;
2.Keep soc_pvt / ddr_pvt as the hwmon device names, which are sufficient
to differentiate the sources.

> 
> > +
> > +	for (i = 0; i < ARRAY_SIZE(label_map); i++) {
> > +		if (strcmp(node_label, label_map[i].label) == 0) {
> > +			prefix = label_map[i].prefix;
> > +			break;
> > +		}
> > +	}
> > +
> > +	info_array = devm_kzalloc(dev, PVT_SENSORS_NUM * sizeof(*info_array),
> > +				  GFP_KERNEL);
> > +	if (!info_array)
> > +		return -ENOMEM;
> > +
> > +	for (i = 0; i < PVT_SENSORS_NUM; i++) {
> > +		labels[i] = devm_kasprintf(dev, GFP_KERNEL, "%s%s", prefix,
> > +					   suffixes[i]);
> 
> So the property is evaluated twice, in the probe function and here.
> Why ?

To concatenate the SOC/DDR prefix with 'Temperature'.

> 
> Also, labels[i] is a character. Does this code even compile ?

char *labels[PVT_SENSORS_NUM]; is an array of pointers, and devm_kasprintf
returns a char*.

> 
> > +		if (!labels[i])
> > +			return -ENOMEM;
> > +	}
> > +
> > +	info_array[0] = (struct pvt_sensor_info)PVT_SENSOR_INFO(0, labels[0],
> > +								hwmon_temp,
> > +								TEMP);
> > +	info_array[1] = (struct pvt_sensor_info)PVT_SENSOR_INFO(0, labels[1],
> > +								hwmon_in,
> > +								VOLT);
> > +	info_array[2] = (struct pvt_sensor_info)PVT_SENSOR_INFO(1, labels[2],
> > +								hwmon_in,
> > +								LVT);
> > +	info_array[3] = (struct pvt_sensor_info)PVT_SENSOR_INFO(2, labels[3],
> > +								hwmon_in,
> > +								ULVT);
> > +	info_array[4] = (struct pvt_sensor_info)PVT_SENSOR_INFO(3, labels[4],
> > +								hwmon_in,
> > +								SVT);
> > +
> > +	pvt->sensor_info = info_array;
> > +	return 0;
> > +}
> > +
> > +static int eic7700_pvt_read_data(struct pvt_hwmon *pvt,
> > +				 enum pvt_sensor_type type, long *val)
> > +{
> > +	const struct pvt_sensor_info *pvt_info = pvt->sensor_info;
> > +	struct pvt_cache *cache = &pvt->cache[type];
> > +	u32 data;
> > +	int ret;
> > +
> > +	if (!pvt_info) {
> > +		dev_err(pvt->dev, "No matching device data found\n");
> > +		return -EINVAL;
> > +	}
> 
> How would pvt_info ever be NULL ?

It is impossible for this to be NULL at runtime. We will remove this
validation in v2.

> 
> > +	/*
> > +	 * Lock PVT conversion interface until data cache is updated. The data
> > +	 * read procedure is following: set the requested PVT sensor mode,
> > +	 * enable conversion, wait until conversion is finished, then disable
> > +	 * conversion and IRQ, and read the cached data.
> > +	 */
> > +	ret = mutex_lock_interruptible(&pvt->iface_mtx);
> > +	if (ret)
> > +		return ret;
> > +
> > +	pvt->sensor = type;
> > +	eic7700_pvt_set_mode(pvt, pvt_info[type].mode);
> > +
> > +	eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, PVT_ENA_EN);
> > +
> > +	ret = wait_for_completion_interruptible(&cache->conversion);
> > +
> > +	eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0);
> > +	eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR);
> > +
> > +	data = READ_ONCE(cache->data);
> > +
> > +	mutex_unlock(&pvt->iface_mtx);
> > +
> > +	if (ret && (ret != -ERESTARTSYS))
> > +		return ret;
> > +
> > +	if (type == PVT_TEMP)
> > +		*val = polynomial_calc(&poly_N_to_temp, data);
> > +	else if (type == PVT_VOLT)
> > +		*val = polynomial_calc(&poly_N_to_volt, data);
> > +	else
> > +		*val = data;
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct hwmon_channel_info *pvt_channel_info[] = {
> > +	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
> > +	HWMON_CHANNEL_INFO(temp,
> > +			   HWMON_T_INPUT | HWMON_T_TYPE | HWMON_T_LABEL |
> > +			   HWMON_T_OFFSET),
> > +	HWMON_CHANNEL_INFO(in,
> > +			   HWMON_I_INPUT | HWMON_I_LABEL,
> > +			   HWMON_I_INPUT | HWMON_I_LABEL,
> > +			   HWMON_I_INPUT | HWMON_I_LABEL,
> > +			   HWMON_I_INPUT | HWMON_I_LABEL),
> > +	NULL
> > +};
> > +
> > +static inline bool
> > +eic7700_pvt_hwmon_channel_is_valid(enum hwmon_sensor_types type, int ch)
> > +{
> > +	switch (type) {
> > +	case hwmon_temp:
> > +		if (ch < 0 || ch >= PVT_TEMP_CHS)
> > +			return false;
> > +		break;
> > +	case hwmon_in:
> > +		if (ch < 0 || ch >= PVT_VOLT_CHS)
> > +			return false;
> > +		break;
> > +	default:
> > +		break;
> > +	}
> > +
> > +	return true;
> > +}
> 
> This validation is unnecessary.

This validation will be removed.

> 
> > +
> > +static int eic7700_pvt_hwmon_read(struct device *dev,
> > +				  enum hwmon_sensor_types type, u32 attr,
> > +				  int ch, long *val)
> > +{
> > +	struct pvt_hwmon *pvt = dev_get_drvdata(dev);
> > +	int ret;
> > +
> > +	ret = pm_runtime_get_sync(pvt->dev);
> > +	if (ret < 0) {
> > +		dev_err(pvt->dev, "Failed to resume PVT device: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	if (!eic7700_pvt_hwmon_channel_is_valid(type, ch)) {
> 
> How would that ever be invalid ? This validation is pointless.

This validation will be removed.

> 
> > +		ret = -EINVAL;
> > +		goto out_put;
> > +	}
> > +
> > +	switch (type) {
> > +	case hwmon_temp:
> > +		switch (attr) {
> > +		case hwmon_temp_input:
> > +			ret = eic7700_pvt_read_data(pvt, ch, val);
> > +			break;
> > +		case hwmon_temp_type:
> > +			*val = 1;
> 
> Drop this attribute.

This attribute will be removed.

> 
> > +			ret = 0;
> > +			break;
> > +		case hwmon_temp_offset:
> > +			ret = eic7700_pvt_read_trim(pvt, val);
> > +			break;
> > +		default:
> > +			ret = -EOPNOTSUPP;
> > +		}
> > +		break;
> > +	case hwmon_in:
> > +		if (attr == hwmon_in_input)
> > +			ret = eic7700_pvt_read_data(pvt, PVT_VOLT + ch, val);
> > +		else
> > +			ret = -EOPNOTSUPP;
> > +		break;
> > +	default:
> > +		ret = -EOPNOTSUPP;
> > +	}
> > +
> > +out_put:
> > +	pm_runtime_mark_last_busy(pvt->dev);
> > +	pm_runtime_put_autosuspend(pvt->dev);
> > +	return ret;
> > +}
> > +
> > +static int eic7700_pvt_hwmon_read_string(struct device *dev,
> > +					 enum hwmon_sensor_types type, u32 attr,
> > +					 int ch, const char **str)
> > +{
> > +	struct pvt_hwmon *pvt = dev_get_drvdata(dev);
> > +	const struct pvt_sensor_info *pvt_info = pvt->sensor_info;
> > +
> > +	if (!eic7700_pvt_hwmon_channel_is_valid(type, ch))
> > +		return -EINVAL;
> > +
> > +	if (!pvt_info) {
> > +		dev_err(pvt->dev, "No matching device data found\n");
> > +		return -EINVAL;
> > +	}
> 
> More unnecessary validations.

This validation will be removed.

> 
> > +
> > +	switch (type) {
> > +	case hwmon_temp:
> > +		if (attr == hwmon_temp_label) {
> > +			*str = pvt_info[ch].label;
> > +			return 0;
> > +		}
> > +		break;
> > +	case hwmon_in:
> > +		if (attr == hwmon_in_label) {
> > +			*str = pvt_info[PVT_VOLT + ch].label;
> > +			return 0;
> > +		}
> > +		break;
> > +	default:
> > +		break;
> > +	}
> > +
> > +	return -EOPNOTSUPP;
> > +}
> > +
> > +static int eic7700_pvt_hwmon_write(struct device *dev,
> > +				   enum hwmon_sensor_types type, u32 attr,
> > +				   int ch, long val)
> > +{
> > +	struct pvt_hwmon *pvt = dev_get_drvdata(dev);
> > +	int ret;
> > +
> > +	ret = pm_runtime_get_sync(pvt->dev);
> > +	if (ret < 0) {
> > +		dev_err(pvt->dev, "Failed to resume PVT device: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	if (!eic7700_pvt_hwmon_channel_is_valid(type, ch)) {
> > +		ret = -EINVAL;
> > +		goto out_put;
> > +	}
> 
> And again.

This validation will be removed.

> 
> > +
> > +	if (type == hwmon_temp || attr == hwmon_temp_offset)
> > +		ret = eic7700_pvt_write_trim(pvt, val);
> > +	else
> > +		ret = -EOPNOTSUPP;
> > +
> > +out_put:
> > +	pm_runtime_mark_last_busy(pvt->dev);
> > +	pm_runtime_put_autosuspend(pvt->dev);
> > +	return ret;
> > +}
> > +
> > +static const struct hwmon_ops pvt_hwmon_ops = {
> > +	.is_visible = eic7700_pvt_hwmon_is_visible,
> > +	.read = eic7700_pvt_hwmon_read,
> > +	.read_string = eic7700_pvt_hwmon_read_string,
> > +	.write = eic7700_pvt_hwmon_write
> > +};
> > +
> > +static const struct hwmon_chip_info pvt_hwmon_info = {
> > +	.ops = &pvt_hwmon_ops,
> > +	.info = pvt_channel_info
> > +};
> > +
> > +static void pvt_clear_data(void *data)
> > +{
> > +	struct pvt_hwmon *pvt = data;
> > +	int idx;
> > +
> > +	for (idx = 0; idx < PVT_SENSORS_NUM; ++idx)
> > +		complete_all(&pvt->cache[idx].conversion);
> > +
> > +	mutex_destroy(&pvt->iface_mtx);
> > +}
> > +
> > +static struct pvt_hwmon *eic7700_pvt_create_data(struct platform_device *pdev)
> > +{
> > +	struct device *dev = &pdev->dev;
> > +	struct pvt_hwmon *pvt;
> > +	int ret, idx;
> > +
> > +	pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL);
> > +	if (!pvt)
> > +		return ERR_PTR(-ENOMEM);
> > +
> > +	ret = devm_add_action(dev, pvt_clear_data, pvt);
> > +	if (ret) {
> > +		dev_err(dev, "Can't add PVT data clear action\n");
> > +		return ERR_PTR(ret);
> > +	}
> 
> Doing that before any data is initialized seems wrong.

We will move the devm_add_action() call to after initialization.

> 
> > +
> > +	pvt->dev = dev;
> > +	pvt->sensor = PVT_SENSOR_FIRST;
> 
> What is this supposed to accomplish ?

This initialization is redundant, we will remove this line to avoid
unnecessary initialization.

> 
> > +	mutex_init(&pvt->iface_mtx);
> 
> The hwmon core already serializes accesses. Why would this driver need
> another mutex ?

Since hwmon serializes access, the iface_mtx mutex is redundant. It will
be removed in v2, along with the related locking operations.

> 
> > +
> > +	for (idx = 0; idx < PVT_SENSORS_NUM; ++idx)
> > +		init_completion(&pvt->cache[idx].conversion);
> 
> Why would more than one completion ever be needed ? All accesses are serialized
> in the hwmon core, so there will only be one outstanding conversion
> at any given time. On top of that, the iface_mtx serializs accesses again.
> 
> Why is more than one cache entry needed anyway, given that all accesses are serialized ?

Due to hwmon's serialized access, we will simplify the code to use a
single data structure and a single completion signal, eliminating the need
for arrays based on sensor types.

> 
> > +
> > +	return pvt;
> > +}
> > +
> > +static void eic7700_pvt_remove(void *data)
> > +{
> > +	int ret;
> > +	struct pvt_hwmon *pvt = data;
> > +
> > +	pm_runtime_disable(pvt->dev);
> > +	pm_runtime_dont_use_autosuspend(pvt->dev);
> > +	pm_runtime_get_sync(pvt->dev);
> > +
> > +	ret = reset_control_assert(pvt->rst);
> > +	if (ret)
> > +		dev_err(pvt->dev, "Failed to assert reset: %d\n", ret);
> > +
> > +	if (pm_runtime_active(pvt->dev))
> > +		clk_disable_unprepare(pvt->clk);
> 
> The probe function calls devm_clk_get_enabled(), which means that the clock
> will be disabled on remove. Why is it necessary to disable it again here ?

The clk_disable_unprepare() call is redundant and will be removed.

> 
> > +
> > +	pm_runtime_put_noidle(pvt->dev);
> > +}
> > +
> > +/*
> > + * enum pvt_sensor_type - ESWIN EIC7700 PVT sensor types (correspond to each PVT
> > + *			  sampling mode)
> > + * @PVT_SENSOR*: helpers to traverse the sensors in loops.
> > + * @PVT_TEMP: PVT Temperature sensor.
> > + * @PVT_VOLT: PVT Voltage sensor.
> > + * @PVT_LVT: PVT Low-Voltage threshold sensor.
> > + * @PVT_HVT: PVT High-Voltage threshold sensor.
> > + * @PVT_SVT: PVT Standard-Voltage threshold sensor.
> > + */
> > +enum pvt_sensor_type {
> > +	PVT_SENSOR_FIRST,
> > +	PVT_TEMP = PVT_SENSOR_FIRST,
> > +	PVT_VOLT,
> > +	PVT_LVT,
> > +	PVT_ULVT,
> > +	PVT_SVT,
> > +	PVT_SENSOR_LAST = PVT_SVT,
> 
> Why PVT_SENSOR_FIRST and PVT_SENSOR_LAST ?

The PVT_SENSOR_FIRST and PVT_SENSOR_LAST macros are redundant and will be
removed.

> 
> > +	PVT_SENSORS_NUM
> > +};
> > +

Thank you very much for taking the time to review the patch and for your
valuable feedback.
All these issues will be addressed in the next patch.

Best regards,
Huan He

^ permalink raw reply

* [PATCH v4 5/5] arm64: dts: rockchip: Fix SD card support for RK3576 Nanopi R76s
From: Shawn Lin @ 2026-01-16  0:55 UTC (permalink / raw)
  To: Heiko Stuebner, Ulf Hansson
  Cc: linux-rockchip, linux-mmc, devicetree, FUKAUMI Naoki,
	Marco Schirrmeister, John Clark, Tianling Shen, Detlev Casanova,
	Shawn Lin
In-Reply-To: <1768524932-163929-1-git-send-email-shawn.lin@rock-chips.com>

When runtime suspend is enabled, the associated power domain is powered
off, which resets the registers, including the power control bit. As a result,
the card loses power during runtime suspend. The card should still be able
to process I/O with the help of mmc_blk_mq_rw_recovery(), which is suboptimal.
To address this issue, we must use vmmc-supply with a GPIO based method to
maintain power to the card and store valid tuning phases. Also, add cd-gpios
method to make hot-plug work correctly during idle periods.

Fixes: 7fee88882704 ("arm64: dts: rockchip: Add devicetree for the FriendlyElec NanoPi R76S")
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
Tested-by: Marco Schirrmeister <mschirrmeister@gmail.com>
---

Changes in v3: None
Changes in v2: None

 .../arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts | 23 +++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts
index 31fbefa..7ec27b0 100644
--- a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-r76s.dts
@@ -192,6 +192,18 @@
 		regulator-name = "vcc_3v3_s0";
 		vin-supply = <&vcc_3v3_s3>;
 	};
+
+	vcc3v3_sd: regulator-vcc-3v3-sd {
+		compatible = "regulator-fixed";
+		enable-active-high;
+		gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_HIGH>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&sdmmc_pwren>;
+		regulator-name = "vcc3v3_sd";
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		vin-supply = <&vcc_3v3_s0>;
+	};
 };
 
 &combphy0_ps {
@@ -726,6 +738,12 @@
 		};
 	};
 
+	sdmmc {
+		sdmmc_pwren: sdmmc-pwren {
+			rockchip,pins = <0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
 	usb {
 		usb_otg0_pwren_h: usb-otg0-pwren-h {
 			rockchip,pins = <0 RK_PD1 RK_FUNC_GPIO &pcfg_pull_none>;
@@ -751,11 +769,14 @@
 	bus-width = <4>;
 	cap-mmc-highspeed;
 	cap-sd-highspeed;
+	cd-gpios = <&gpio0 RK_PA7 GPIO_ACTIVE_LOW>;
 	disable-wp;
 	no-mmc;
 	no-sdio;
+	pinctrl-names = "default";
+	pinctrl-0 = <&sdmmc0_clk &sdmmc0_cmd &sdmmc0_det &sdmmc0_bus4>;
 	sd-uhs-sdr104;
-	vmmc-supply = <&vcc_3v3_s3>;
+	vmmc-supply = <&vcc3v3_sd>;
 	vqmmc-supply = <&vccio_sd_s0>;
 	status = "okay";
 };
-- 
2.7.4


^ permalink raw reply related

* [PATCH v4 1/5] soc: rockchip: grf: Fix wrong RK3576_IOCGRF_MISC_CON definition
From: Shawn Lin @ 2026-01-16  0:55 UTC (permalink / raw)
  To: Heiko Stuebner, Ulf Hansson
  Cc: linux-rockchip, linux-mmc, devicetree, FUKAUMI Naoki,
	Marco Schirrmeister, John Clark, Tianling Shen, Detlev Casanova,
	Shawn Lin
In-Reply-To: <1768524932-163929-1-git-send-email-shawn.lin@rock-chips.com>

RK3576_IOCGRF_MISC_CON is IOC_GRF + 0x40F0, fix it.

Fixes: e1aaecacfa13 ("soc: rockchip: grf: Add rk3576 default GRF values")
Cc: Detlev Casanova <detlev.casanova@collabora.com>
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
Reviewed-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
Tested-by: Marco Schirrmeister <mschirrmeister@gmail.com>
---

Changes in v3: None
Changes in v2: None

 drivers/soc/rockchip/grf.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/soc/rockchip/grf.c b/drivers/soc/rockchip/grf.c
index 27bfa09..8974d1c 100644
--- a/drivers/soc/rockchip/grf.c
+++ b/drivers/soc/rockchip/grf.c
@@ -146,7 +146,7 @@ static const struct rockchip_grf_info rk3576_sysgrf __initconst = {
 	.num_values = ARRAY_SIZE(rk3576_defaults_sys_grf),
 };
 
-#define RK3576_IOCGRF_MISC_CON		0x04F0
+#define RK3576_IOCGRF_MISC_CON		0x40F0
 
 static const struct rockchip_grf_value rk3576_defaults_ioc_grf[] __initconst = {
 	{ "jtag switching", RK3576_IOCGRF_MISC_CON, FIELD_PREP_WM16_CONST(BIT(1), 0) },
-- 
2.7.4


^ permalink raw reply related

* Re: [PATCH 1/8] dt-bindings: soc: tenstorrent: Add tenstorrent,atlantis-syscon
From: Rob Herring @ 2026-01-16  4:03 UTC (permalink / raw)
  To: Anirudh Srinivasan
  Cc: Drew Fustini, Joel Stanley, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Philipp Zabel, linux-riscv,
	devicetree, linux-kernel, linux-clk, joel, fustini, mpe, mpe,
	npiggin, agross, agross
In-Reply-To: <20260115-atlantis-clocks-v1-1-7356e671f28b@oss.tenstorrent.com>

On Thu, Jan 15, 2026 at 05:42:00PM -0600, Anirudh Srinivasan wrote:
> Document bindings for Tenstorrent Atlantis syscon that manages clocks
> and resets. This syscon block is instantiated 4 times in the SoC.
> This commit documents the clocks from the RCPU syscon block.
> 
> Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> ---
>  .../tenstorrent/tenstorrent,atlantis-syscon.yaml   | 58 +++++++++++++++++++

Filename should match compatible.

>  MAINTAINERS                                        |  2 +
>  .../clock/tenstorrent,atlantis-syscon.h            | 67 ++++++++++++++++++++++
>  3 files changed, 127 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/soc/tenstorrent/tenstorrent,atlantis-syscon.yaml b/Documentation/devicetree/bindings/soc/tenstorrent/tenstorrent,atlantis-syscon.yaml
> new file mode 100644
> index 000000000000..3915d78dfeda
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/tenstorrent/tenstorrent,atlantis-syscon.yaml
> @@ -0,0 +1,58 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/soc/tenstorrent/tenstorrent,atlantis-syscon.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Tenstorrent Atlantis SoC System Controller
> +
> +maintainers:
> +  - Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> +
> +description:
> +  System controller found in Tenstorrent Atlantis SoC, which is capable of
> +  clock and reset functions.
> +
> +  RCPU syscon controls clocks and resets for low speed IO interfaces on chip
> +
> +properties:
> +  compatible:
> +    enum:
> +      - tenstorrent,atlantis-syscon-rcpu

If "RCPU" is what the h/w block is called and sufficient to identify it, 
then drop the "syscon-" part.

> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +
> +  "#clock-cells":
> +    const: 1
> +    description:
> +      See <dt-bindings/clock/tenstorrent,atlantis-syscon.h> for valid indices.

Be consistent with the compatible string for the file name.

> +
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - "#clock-cells"
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    clocks {
> +      osc_24m: clock-24m {
> +        compatible = "fixed-clock";
> +        clock-frequency = <24000000>;
> +        clock-output-names = "osc_24m";
> +        #clock-cells = <0>;
> +      };
> +    };

Drop this node. Not relevant to the example.

> +
> +    syscon_rcpu: system-controller@a8000000 {
> +      compatible = "tenstorrent,atlantis-sycon-rcpu";
> +      reg = <0x0 0xa8000000 0x0 0x10000>;
> +      clocks = <&osc_24m>;
> +      #clock-cells = <1>;
> +    };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index dc731d37c8fe..19a98b1fa456 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22535,7 +22535,9 @@ L:	linux-riscv@lists.infradead.org
>  S:	Maintained
>  T:	git https://github.com/tenstorrent/linux.git
>  F:	Documentation/devicetree/bindings/riscv/tenstorrent.yaml
> +F:	Documentation/devicetree/bindings/soc/tenstorrent/tenstorrent,atlantis-syscon.yaml
>  F:	arch/riscv/boot/dts/tenstorrent/
> +F:	include/dt-bindings/clock/tenstorrent,atlantis-syscon.h
>  
>  RISC-V THEAD SoC SUPPORT
>  M:	Drew Fustini <fustini@kernel.org>
> diff --git a/include/dt-bindings/clock/tenstorrent,atlantis-syscon.h b/include/dt-bindings/clock/tenstorrent,atlantis-syscon.h
> new file mode 100644
> index 000000000000..a8518319642a
> --- /dev/null
> +++ b/include/dt-bindings/clock/tenstorrent,atlantis-syscon.h
> @@ -0,0 +1,67 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +/*
> + * Copyright (c) 2026 Tenstorrent
> + */
> +
> +#ifndef _DT_BINDINGS_ATLANTIS_SYSCON_H
> +#define _DT_BINDINGS_ATLANTIS_SYSCON_H
> +
> +/*
> + * RCPU Domain Clock IDs
> + */
> +#define CLK_RCPU_PLL 0
> +#define CLK_RCPU_ROOT 1
> +#define CLK_RCPU_DIV2 2
> +#define CLK_RCPU_DIV4 3
> +#define CLK_RCPU_RTC 4
> +#define CLK_SMNDMA0_ACLK 5
> +#define CLK_SMNDMA1_ACLK 6
> +#define CLK_WDT0_PCLK 7
> +#define CLK_WDT1_PCLK 8
> +#define CLK_TIMER_PCLK 9
> +#define CLK_PVTC_PCLK 10
> +#define CLK_PMU_PCLK 11
> +#define CLK_MAILBOX_HCLK 12
> +#define CLK_SEC_SPACC_HCLK 13
> +#define CLK_SEC_OTP_HCLK 14
> +#define CLK_TRNG_PCLK 15
> +#define CLK_SEC_CRC_HCLK 16
> +#define CLK_SMN_HCLK 17
> +#define CLK_AHB0_HCLK 18
> +#define CLK_SMN_PCLK 19
> +#define CLK_SMN_CLK 20
> +#define CLK_SCRATCHPAD_CLK 21
> +#define CLK_RCPU_CORE_CLK 22
> +#define CLK_RCPU_ROM_CLK 23
> +#define CLK_OTP_LOAD_CLK 24
> +#define CLK_NOC_PLL 25
> +#define CLK_NOCC_CLK 26
> +#define CLK_NOCC_DIV2 27
> +#define CLK_NOCC_DIV4 28
> +#define CLK_NOCC_RTC 29
> +#define CLK_NOCC_CAN 30
> +#define CLK_QSPI_SCLK 31
> +#define CLK_QSPI_HCLK 32
> +#define CLK_I2C0_PCLK 33
> +#define CLK_I2C1_PCLK 34
> +#define CLK_I2C2_PCLK 35
> +#define CLK_I2C3_PCLK 36
> +#define CLK_I2C4_PCLK 37
> +#define CLK_UART0_PCLK 38
> +#define CLK_UART1_PCLK 39
> +#define CLK_UART2_PCLK 40
> +#define CLK_UART3_PCLK 41
> +#define CLK_UART4_PCLK 42
> +#define CLK_SPI0_PCLK 43
> +#define CLK_SPI1_PCLK 44
> +#define CLK_SPI2_PCLK 45
> +#define CLK_SPI3_PCLK 46
> +#define CLK_GPIO_PCLK 47
> +#define CLK_CAN0_HCLK 48
> +#define CLK_CAN0_CLK 49
> +#define CLK_CAN1_HCLK 50
> +#define CLK_CAN1_CLK 51
> +#define CLK_CAN0_TIMER_CLK 52
> +#define CLK_CAN1_TIMER_CLK 53
> +
> +#endif /* _DT_BINDINGS_ATLANTIS_SYSCON_H */
> 
> -- 
> 2.43.0
> 

^ permalink raw reply

* Re: [PATCH v2] arm64: dts: broadcom: bcm2712: Add V3D device node
From: Peter Robinson @ 2026-01-16  3:53 UTC (permalink / raw)
  To: Maíra Canal, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Florian Fainelli, Stefan Wahren,
	Broadcom internal kernel review list
  Cc: dri-devel, devicetree, linux-rpi-kernel, linux-arm-kernel,
	kernel-dev
In-Reply-To: <20260114120610.82531-1-mcanal@igalia.com>

Hi Maira,

On 14/01/2026 12:04, Maíra Canal wrote:
> Commits 0ad5bc1ce463 ("drm/v3d: fix up register addresses for V3D 7.x")
> and 6fd9487147c4 ("drm/v3d: add brcm,2712-v3d as a compatible V3D device")
> added driver support for V3D on BCM2712, but the corresponding device
> tree node is still missing.
>
> Add the V3D device tree node to the BCM2712 DTS.
>
> Signed-off-by: Maíra Canal <mcanal@igalia.com>
>
> ---
> v1 -> v2:
>
> - Rebased on top of linux-next (Stefan Wahren)
> - Fixed node's address (2000000 -> 1002000000) (Stefan Wahren)
> - Link to v1: https://lore.kernel.org/linux-devicetree/20260113192902.48046-2-mcanal@igalia.com/
> ---
>   .../boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi    |  4 ++++
>   arch/arm64/boot/dts/broadcom/bcm2712.dtsi          | 14 ++++++++++++++
>   2 files changed, 18 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi
> index 7d4742ebe247..97522c6803c5 100644
> --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi
> +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi
> @@ -247,3 +247,7 @@ &pcie1 {
>   &pcie2 {
>   	status = "okay";
>   };
> +
> +&v3d {
> +	clocks = <&firmware_clocks 5>;

Looking at the upstream DT [1] I think this also needs a clock-names entry.

[1] 
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b-base.dtsi#n233

> +};
> diff --git a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
> index 330a121ebfcb..661668ef7419 100644
> --- a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
> +++ b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
> @@ -1,5 +1,6 @@
>   // SPDX-License-Identifier: (GPL-2.0 OR MIT)
>   #include <dt-bindings/interrupt-controller/arm-gic.h>
> +#include <dt-bindings/soc/bcm2835-pm.h>
>   
>   / {
>   	compatible = "brcm,bcm2712";
> @@ -642,6 +643,19 @@ mip1: msi-controller@1000131000 {
>   			msi-ranges = <&gicv2 GIC_SPI 247 IRQ_TYPE_EDGE_RISING 8>;
>   			brcm,msi-offset = <8>;
>   		};
> +
> +		v3d: gpu@1002000000 {
> +			compatible = "brcm,2712-v3d";
> +			reg = <0x10 0x02000000 0x00 0x4000>,
> +			      <0x10 0x02008000 0x00 0x6000>,
> +			      <0x10 0x02030800 0x00 0x0700>;
> +			reg-names = "hub", "core0", "sms";
> +
> +			power-domains = <&pm BCM2835_POWER_DOMAIN_GRAFX_V3D>;
> +			resets = <&pm BCM2835_RESET_V3D>;
> +			interrupts = <GIC_SPI 250 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 249 IRQ_TYPE_LEVEL_HIGH>;
> +		};
>   	};
>   
>   	vc4: gpu {

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox