From 01a2f672b0eea4569aaf08cc092e7bf505d9a459 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 12 Jun 2026 17:02:57 +0200 Subject: [PATCH 1/3] input: remotectl: rockchip-pwm: prefer dedicated channel-3 IRQ On PWM v4 (RK3588), channel 3 of each PWM controller block exposes two interrupts: a group IRQ at index 0 shared with channels 0/1/2 of the same block, and a dedicated capture IRQ at index 1 intended for IR receive / power-key wakeup use of channel 3. The driver always grabbed index 0 and requested it without IRQF_SHARED, which collides with pwm-rockchip when another channel in the same block is enabled (e.g. pwm1 for a fan on the FriendlyElec CM3588-NAS). The in-tree v4 path in pwm-rockchip.c also omits IRQF_SHARED, so probe fails with -EBUSY: remotectl-pwm fd8b0030.pwm: cannot claim IRQ 28 remotectl-pwm: probe of fd8b0030.pwm failed with error -16 Prefer index 1 when present and fall back to index 0 for PWM v1-v3 nodes that only expose a single interrupt. The capture-mode handler (rockchip_pwm_irq_v4) is unchanged and remains correct for the dedicated IRQ. Signed-off-by: Ricardo Pardini Co-Authored-By: Claude Opus 4.7 --- drivers/input/remotectl/rockchip_pwm_remotectl.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/input/remotectl/rockchip_pwm_remotectl.c b/drivers/input/remotectl/rockchip_pwm_remotectl.c index 83d73a77327f2..6ddcb46ddf207 100644 --- a/drivers/input/remotectl/rockchip_pwm_remotectl.c +++ b/drivers/input/remotectl/rockchip_pwm_remotectl.c @@ -932,7 +932,17 @@ static int rk_pwm_probe(struct platform_device *pdev) input->id.product = 0x0006; input->id.version = 0x0100; ddata->input = input; - irq = platform_get_irq(pdev, 0); + /* + * On PWM v4 (RK3588), channel 3 has a dedicated capture IRQ at + * index 1, separate from the group IRQ at index 0 that is shared + * with the other channels in the same PWM controller. Prefer the + * dedicated one so we don't collide with the pwm-rockchip driver + * bound to another channel (which claims the group IRQ without + * IRQF_SHARED on v4). Fall back to index 0 for older PWM v1-v3. + */ + irq = platform_get_irq(pdev, 1); + if (irq < 0) + irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "cannot find IRQ\n"); goto error_pclk; From 48e2acef9599a3be9333c4600477295a747a5860 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Sun, 14 Jun 2026 11:51:49 +0200 Subject: [PATCH 2/3] auxdisplay: vk2c21: add sysfs brightness control Expose a `brightness` sysfs attribute that drives the chip's IVASET register (0x8A): - "default" (the boot-time state): IVASET=0x0F, IVA off, bias driven by the board's external VR resistor (same as before this change). - 0..15: IVASET=0x3F..0x30 (IVA on, SEG mode); 15 = brightest (1.000 x VDD), 0 = dimmest (0.529 x VDD at 1/3 bias). SEG mode (0x30..0x3F) is used rather than VLCD-output mode (0x10..0x1F) because the R58X-Pro ties the VLCD pin to VDD through an external resistor: in VLCD-output mode the external circuit dominates the chip's internal driver and the brightness register has no visible effect. In SEG mode the chip ignores the VLCD pin and generates the bias voltage purely internally, so the 16 levels actually take effect. Probe-time behaviour is unchanged: init still sends IVA=0x0F, and the attribute reads back as "default" until userspace writes a number. Signed-off-by: Ricardo Pardini Co-Authored-By: Claude Opus 4.7 --- drivers/auxdisplay/lcd-vk2c21.c | 62 ++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/drivers/auxdisplay/lcd-vk2c21.c b/drivers/auxdisplay/lcd-vk2c21.c index ee181bf57f7c3..28e75ea4cdcec 100644 --- a/drivers/auxdisplay/lcd-vk2c21.c +++ b/drivers/auxdisplay/lcd-vk2c21.c @@ -41,7 +41,23 @@ #define VK2C21_SYS_OFF_LCD_OFF 0x00 /* System + LCD off */ #define VK2C21_FRAME_80HZ 0x00 /* 80 Hz frame rate */ #define VK2C21_BLINK_OFF 0x00 /* Blinking off */ -#define VK2C21_IVA_DEFAULT 0x0F /* VLCD selected, IVA off, R1 */ +#define VK2C21_IVA_DEFAULT 0x0F /* VLCD selected, IVA off, R1 (external VR) */ + +/* + * IVA on, SEG mode: 16 internal bias-voltage levels. + * 0x30 = level 0 = 1.000 x VDD (highest contrast / "brightest") + * 0x3F = level 15 = 0.529 x VDD (lowest contrast / "dimmest") + * SEG mode (vs. VLCD-output mode at 0x10..0x1F) is used because the R58X-Pro + * has an external resistor on the VLCD pin: in VLCD-output mode the external + * resistor dominates the chip's internal driver and the brightness register + * has no visible effect. SEG mode tells the chip to ignore the VLCD pin and + * generate the bias voltage purely internally. + * Userspace brightness is inverted (0 = dimmest, 15 = brightest) so that + * higher numbers look brighter, matching the usual convention. + */ +#define VK2C21_IVA_ON_BASE 0x30 +#define VK2C21_BRIGHTNESS_MAX 15 +#define VK2C21_BRIGHTNESS_DEFAULT (-1) /* IVA off, external VR in use */ /* Display RAM has 10 addressable bytes (SEG/COM matrix) */ #define VK2C21_RAM_SIZE 10 @@ -185,6 +201,7 @@ struct vk2c21_data { unsigned int half_period_ns; u8 dispram[VK2C21_RAM_SIZE]; char display_text[VK2C21_MAX_DIGITS + 1]; + int brightness; /* -1 = IVA off / "default", 0..15 = IVA level */ }; /* ------------------------------------------------------------------ */ @@ -414,9 +431,51 @@ static ssize_t clear_store(struct device *dev, } static DEVICE_ATTR_WO(clear); +static ssize_t brightness_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vk2c21_data *d = dev_get_drvdata(dev); + + if (d->brightness == VK2C21_BRIGHTNESS_DEFAULT) + return sysfs_emit(buf, "default\n"); + return sysfs_emit(buf, "%d\n", d->brightness); +} + +static ssize_t brightness_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vk2c21_data *d = dev_get_drvdata(dev); + int ret, level; + u8 iva; + + if (sysfs_streq(buf, "default")) { + level = VK2C21_BRIGHTNESS_DEFAULT; + iva = VK2C21_IVA_DEFAULT; + } else { + ret = kstrtoint(buf, 0, &level); + if (ret) + return ret; + if (level < 0 || level > VK2C21_BRIGHTNESS_MAX) + return -EINVAL; + /* Invert: userspace 15 = brightest = IVA level 0 = 0x30 */ + iva = VK2C21_IVA_ON_BASE + (VK2C21_BRIGHTNESS_MAX - level); + } + + mutex_lock(&d->lock); + ret = vk2c21_send_cmd(d, VK2C21_IVASET, iva); + if (!ret) + d->brightness = level; + mutex_unlock(&d->lock); + + return ret ? ret : count; +} +static DEVICE_ATTR_RW(brightness); + static struct attribute *vk2c21_sysfs_attrs[] = { &dev_attr_display.attr, &dev_attr_clear.attr, + &dev_attr_brightness.attr, NULL, }; @@ -445,6 +504,7 @@ static int vk2c21_probe(struct platform_device *pdev) return -ENOMEM; d->dev = dev; + d->brightness = VK2C21_BRIGHTNESS_DEFAULT; mutex_init(&d->lock); platform_set_drvdata(pdev, d); From 7585a639ca98e868a282c9e820ab94de368c20bb Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 14 Jul 2026 20:46:03 +0200 Subject: [PATCH 3/3] video: rockchip: mpp: report zero load for idle devices The per-device statistics shown in /proc/mpp_service/load are only ever recomputed inside mpp_dev_load(), which is called exclusively from the task-completion paths (mpp_common.c task worker and the rkvdec2 link/ccu workers). Within that function the load and utilization figures are only refreshed when a task finishes *and* at least one load_interval has elapsed; the result is stored in mpp->load_info and reset for the next window. There is no timer, runtime-PM hook, or reader-side logic that ages the value out, so mpp->load_info.load is a snapshot of the last completed interval, not a live measurement. As a consequence, once a device stops receiving tasks its stored load is never updated again and /proc/mpp_service/load keeps reporting the last busy interval indefinitely (until load_interval is toggled, which clears the stats via mpp_dev_load_clear()). This is most visible on the standalone AV1 decoder (fdc70000.av1d). It is driven one task at a time by the default worker, so when playback stops the final frame's completion is genuinely the last event that will ever call mpp_dev_load() for that core, and its load freezes at the busy value. Multi-core rkvdec2 decoders in link/ccu mode keep draining their queued task list after playback ends, which happens to log a further, near-idle interval and pulls the figure back down, so the staleness goes unnoticed there. The underlying defect is common to every device. Rather than introduce a periodic recompute (a per-core timer with the associated runtime-PM interactions), detect the idle case in the reader. A device whose load_info has not been updated for more than one full load_interval has, by definition, completed no task in that window and is idle; report zero for it instead of the stale snapshot. Devices that never started load tracking (!load_en) are likewise reported as zero. Signed-off-by: Ricardo Pardini Co-Authored-By: Claude Opus 4.8 --- drivers/video/rockchip/mpp/mpp_service.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/video/rockchip/mpp/mpp_service.c b/drivers/video/rockchip/mpp/mpp_service.c index 882002e20097b..73cbc46452f5c 100644 --- a/drivers/video/rockchip/mpp/mpp_service.c +++ b/drivers/video/rockchip/mpp/mpp_service.c @@ -329,13 +329,33 @@ static int mpp_show_device_load(struct seq_file *file, void *v) for (j = 0; j < MPP_MAX_CORE_NUM; j++) { struct mpp_dev *mpp = queue->cores[j]; + struct mpp_load_info *load_info; + u32 load, load_frac, util, util_frac; if (!mpp) continue; + + load_info = &mpp->load_info; + load = load_info->load; + load_frac = load_info->load_frac; + util = load_info->utilization; + util_frac = load_info->utilization_frac; + + /* + * load_info is only refreshed by mpp_dev_load() on task + * completion, so an idle device keeps reporting the last + * busy interval forever. If no task has updated the stats + * for more than one load_interval, the device has gone + * idle: report zero instead of the stale snapshot. + */ + if (!mpp->load_en || + ktime_us_delta(ktime_get(), load_info->load_time) > + (s64)srv->load_interval * 1000 * 2) + load = load_frac = util = util_frac = 0; + seq_printf(file, "%-25s load: %3d.%02d%% utilization: %3d.%02d%%\n", dev_name(mpp->dev), - mpp->load_info.load, mpp->load_info.load_frac, - mpp->load_info.utilization, mpp->load_info.utilization_frac); + load, load_frac, util, util_frac); } }