* [PATCH v7 15/22] RISC-V: perf: Skip PMU SBI extension when not implemented
From: Atish Patra @ 2026-06-22 8:04 UTC (permalink / raw)
To: Jiri Olsa, James Clark, Mark Rutland, Will Deacon,
Arnaldo Carvalho de Melo, Rob Herring, Ian Rogers,
Krzysztof Kozlowski, Anup Patel, Paul Walmsley, Atish Patra,
Namhyung Kim
Cc: devicetree, linux-perf-users, Conor Dooley, linux-arm-kernel,
linux-kernel, linux-riscv
In-Reply-To: <20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com>
From: Charlie Jenkins <charlie@rivosinc.com>
When the PMU SBI extension is not implemented, sbi_v2_available should
not be set to true. The SBI implementation for counter config matching
and firmware counter read should also be skipped when the SBI extension
is not implemented.
Signed-off-by: Atish Patra <atishp@meta.com>
Signed-off-by: Charlie Jenkins <charlie@rivosinc.com>
---
drivers/perf/riscv_pmu_sbi.c | 49 ++++++++++++++++++++++++++------------------
1 file changed, 29 insertions(+), 20 deletions(-)
diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c
index 6407e229c2c3..4f3a30143db1 100644
--- a/drivers/perf/riscv_pmu_sbi.c
+++ b/drivers/perf/riscv_pmu_sbi.c
@@ -495,27 +495,32 @@ static void rvpmu_sbi_check_event(struct sbi_pmu_event_data *edata)
}
}
-static void rvpmu_sbi_check_std_events(struct work_struct *work)
+static void rvpmu_check_std_events(struct work_struct *work)
{
int ret;
- if (sbi_v3_available) {
- ret = pmu_sbi_check_event_info();
- if (ret)
- pr_err("pmu_sbi_check_event_info failed with error %d\n", ret);
- return;
- }
+ if (riscv_pmu_sbi_available()) {
+ if (sbi_v3_available) {
+ ret = pmu_sbi_check_event_info();
+ if (ret)
+ pr_err("pmu_sbi_check_event_info failed with error %d\n", ret);
+ return;
+ }
- for (int i = 0; i < ARRAY_SIZE(pmu_hw_event_sbi_map); i++)
- rvpmu_sbi_check_event(&pmu_hw_event_sbi_map[i]);
+ for (int i = 0; i < ARRAY_SIZE(pmu_hw_event_sbi_map); i++)
+ rvpmu_sbi_check_event(&pmu_hw_event_sbi_map[i]);
- for (int i = 0; i < ARRAY_SIZE(pmu_cache_event_sbi_map); i++)
- for (int j = 0; j < ARRAY_SIZE(pmu_cache_event_sbi_map[i]); j++)
- for (int k = 0; k < ARRAY_SIZE(pmu_cache_event_sbi_map[i][j]); k++)
- rvpmu_sbi_check_event(&pmu_cache_event_sbi_map[i][j][k]);
+ for (int i = 0; i < ARRAY_SIZE(pmu_cache_event_sbi_map); i++)
+ for (int j = 0; j < ARRAY_SIZE(pmu_cache_event_sbi_map[i]); j++)
+ for (int k = 0; k < ARRAY_SIZE(pmu_cache_event_sbi_map[i][j]); k++)
+ rvpmu_sbi_check_event(&pmu_cache_event_sbi_map[i][j][k]);
+ } else {
+ DO_ONCE_LITE_IF(1, pr_info,
+ "Boot time config matching not required for smcdeleg\n");
+ }
}
-static DECLARE_WORK(check_std_events_work, rvpmu_sbi_check_std_events);
+static DECLARE_WORK(check_std_events_work, rvpmu_check_std_events);
static ssize_t rvpmu_format_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -708,6 +713,9 @@ static int rvpmu_sbi_ctr_get_idx(struct perf_event *event)
cflags = rvpmu_sbi_get_filter_flags(event);
+ if (!riscv_pmu_sbi_available())
+ return -ENOENT;
+
/*
* In legacy mode, we have to force the fixed counters for those events
* but not in the user access mode as we want to use the other counters
@@ -985,7 +993,7 @@ static u64 rvpmu_ctr_read(struct perf_event *event)
return val;
}
- if (pmu_sbi_is_fw_event(event)) {
+ if (pmu_sbi_is_fw_event(event) && riscv_pmu_sbi_available()) {
ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_FW_READ,
hwc->idx, 0, 0, 0, 0, 0);
if (ret.error)
@@ -2084,12 +2092,13 @@ static int __init rvpmu_devinit(void)
int ret;
struct platform_device *pdev;
- if (sbi_spec_version >= sbi_mk_version(0, 3) &&
- sbi_probe_extension(SBI_EXT_PMU))
- static_branch_enable(&riscv_pmu_sbi_available);
+ if (sbi_probe_extension(SBI_EXT_PMU)) {
+ if (sbi_spec_version >= sbi_mk_version(0, 3))
+ static_branch_enable(&riscv_pmu_sbi_available);
+ if (sbi_spec_version >= sbi_mk_version(2, 0))
+ sbi_v2_available = true;
+ }
- if (sbi_spec_version >= sbi_mk_version(2, 0))
- sbi_v2_available = true;
/*
* We need all three extensions to be present to access the counters
* in S-mode via Supervisor Counter delegation.
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v7 16/22] RISC-V: perf: Use config2/vendor table for event to counter mapping
From: Atish Patra @ 2026-06-22 8:04 UTC (permalink / raw)
To: Jiri Olsa, James Clark, Mark Rutland, Will Deacon,
Arnaldo Carvalho de Melo, Rob Herring, Ian Rogers,
Krzysztof Kozlowski, Anup Patel, Paul Walmsley, Atish Patra,
Namhyung Kim
Cc: devicetree, linux-perf-users, Conor Dooley, linux-arm-kernel,
linux-kernel, linux-riscv
In-Reply-To: <20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com>
From: Atish Patra <atishp@rivosinc.com>
The counter restriction specified in the json file is passed to
the drivers via config2 paarameter in perf attributes. This allows
any platform vendor to define their custom mapping between event and
hpmcounters without any rules defined in the ISA.
For legacy events, the platform vendor may define the mapping in
the driver in the vendor event table.
The fixed cycle and instruction counters are fixed (0 and 2
respectively) by the ISA and maps to the legacy events. The platform
vendor must specify this in the driver if intended to be used while
profiling. Otherwise, they can just specify the alternate hpmcounters
that may monitor and/or sample the cycle/instruction counts.
Signed-off-by: Atish Patra <atishp@rivosinc.com>
---
drivers/perf/riscv_pmu_sbi.c | 95 +++++++++++++++++++++++++++++++++++-------
include/linux/perf/riscv_pmu.h | 2 +
2 files changed, 81 insertions(+), 16 deletions(-)
diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c
index 4f3a30143db1..1c846cdc96cf 100644
--- a/drivers/perf/riscv_pmu_sbi.c
+++ b/drivers/perf/riscv_pmu_sbi.c
@@ -77,6 +77,7 @@ static ssize_t __maybe_unused rvpmu_format_show(struct device *dev, struct devic
RVPMU_ATTR_ENTRY(_name, rvpmu_format_show, (char *)_config)
PMU_FORMAT_ATTR(firmware, "config:62-63");
+PMU_FORMAT_ATTR(counterid_mask, "config2:0-31");
static bool sbi_v2_available;
static bool sbi_v3_available;
@@ -121,6 +122,7 @@ static const struct attribute_group *riscv_sbi_pmu_attr_groups[] = {
static struct attribute *riscv_cdeleg_pmu_formats_attr[] = {
RVPMU_FORMAT_ATTR_ENTRY(event, RVPMU_CDELEG_PMU_FORMAT_ATTR),
&format_attr_firmware.attr,
+ &format_attr_counterid_mask.attr,
NULL,
};
@@ -1501,24 +1503,85 @@ static int rvpmu_deleg_find_ctrs(void)
return num_hw_ctr;
}
+/*
+ * The json file must correctly specify counter 0 or counter 2 is available
+ * in the counter lists for cycle/instret events. Otherwise, the drivers have
+ * no way to figure out if a fixed counter must be used and pick a programmable
+ * counter if available.
+ */
static int get_deleg_fixed_hw_idx(struct cpu_hw_events *cpuc, struct perf_event *event)
{
- return -EINVAL;
+ bool guest_events = event->attr.config1 & RISCV_PMU_CONFIG1_GUEST_EVENTS;
+ int idx;
+
+ /* event_base is 0 on the delegation path; match via the original perf attrs. */
+ if (guest_events) {
+ if (event->attr.type != PERF_TYPE_HARDWARE)
+ return -EINVAL;
+ if (event->attr.config == PERF_COUNT_HW_CPU_CYCLES)
+ idx = 0; /* CY counter */
+ else if (event->attr.config == PERF_COUNT_HW_INSTRUCTIONS)
+ idx = 2; /* IR counter */
+ else
+ return -EINVAL;
+ } else if (event->attr.config2 & RISCV_PMU_CYCLE_FIXED_CTR_MASK) {
+ idx = 0; /* CY counter */
+ } else if (event->attr.config2 & RISCV_PMU_INSTRUCTION_FIXED_CTR_MASK) {
+ idx = 2; /* IR counter */
+ } else {
+ return -EINVAL;
+ }
+
+ /* Take the fixed counter only if delegated and free, else fall back. */
+ if (!(cmask & BIT(idx)) || test_bit(idx, cpuc->used_hw_ctrs))
+ return -EINVAL;
+
+ return idx;
}
static int get_deleg_next_hpm_hw_idx(struct cpu_hw_events *cpuc, struct perf_event *event)
{
- unsigned long hw_ctr_mask = 0;
+ u32 hw_ctr_mask = 0, temp_mask = 0;
+ u32 type = event->attr.type;
+ u64 config = event->attr.config;
+ int ret;
- /*
- * TODO: Treat every hpmcounter can monitor every event for now.
- * The event to counter mapping should come from the json file.
- * The mapping should also tell if sampling is supported or not.
- */
+ /* Select only available hpmcounters */
+ hw_ctr_mask = cmask & (~0x7) & ~(cpuc->used_hw_ctrs[0]);
+
+ switch (type) {
+ case PERF_TYPE_HARDWARE:
+ temp_mask = current_pmu_hw_event_map[config].counter_mask;
+ break;
+ case PERF_TYPE_HW_CACHE:
+ ret = cdeleg_pmu_event_find_cache(config, NULL, &temp_mask);
+ if (ret)
+ return ret;
+ break;
+ case PERF_TYPE_RAW:
+ /*
+ * Mask off the counters that can't monitor this event (specified via json)
+ * The counter mask for this event is set in config2 via the property 'Counter'
+ * in the json file or manual configuration of config2. If the config2 is not set,
+ * it is assumed all the available hpmcounters can monitor this event.
+ * Note: This assumption may fail for virtualization use case where they hypervisor
+ * (e.g. KVM) virtualizes the counter. Any event to counter mapping provided by the
+ * guest is meaningless from a hypervisor perspective. Thus, the hypervisor doesn't
+ * set config2 when creating kernel counter and relies default host mapping.
+ */
+ if (event->attr.config2)
+ temp_mask = event->attr.config2;
+ break;
+ default:
+ break;
+ }
+
+ if (temp_mask)
+ hw_ctr_mask &= temp_mask;
+
+ if (!hw_ctr_mask)
+ return -EINVAL;
- /* Select only hpmcounters */
- hw_ctr_mask = cmask & (~0x7);
- hw_ctr_mask &= ~(cpuc->used_hw_ctrs[0]);
return __ffs(hw_ctr_mask);
}
@@ -1547,10 +1610,6 @@ static int rvpmu_deleg_ctr_get_idx(struct perf_event *event)
u64 priv_filter;
int idx;
- /*
- * TODO: We should not rely on SBI Perf encoding to check if the event
- * is a fixed one or not.
- */
if (!is_sampling_event(event)) {
idx = get_deleg_fixed_hw_idx(cpuc, event);
if (idx == 0 || idx == 2) {
@@ -1570,10 +1629,14 @@ static int rvpmu_deleg_ctr_get_idx(struct perf_event *event)
goto out_err;
found_idx:
priv_filter = get_deleg_priv_filter_bits(event);
+ if (test_and_set_bit(idx, cpuc->used_hw_ctrs))
+ goto out_err;
update_deleg_hpmevent(idx, hwc->config, priv_filter);
+ return idx;
skip_update:
- if (!test_and_set_bit(idx, cpuc->used_hw_ctrs))
- return idx;
+ if (test_and_set_bit(idx, cpuc->used_hw_ctrs))
+ goto out_err;
+ return idx;
out_err:
return -ENOENT;
}
diff --git a/include/linux/perf/riscv_pmu.h b/include/linux/perf/riscv_pmu.h
index 3c64151cb038..b23b71cb4e66 100644
--- a/include/linux/perf/riscv_pmu.h
+++ b/include/linux/perf/riscv_pmu.h
@@ -30,6 +30,8 @@
#define RISCV_PMU_CONFIG1_GUEST_EVENTS 0x1
#define RISCV_PMU_DELEG_RAW_EVENT_MASK GENMASK_ULL(55, 0)
+#define RISCV_PMU_CYCLE_FIXED_CTR_MASK 0x01
+#define RISCV_PMU_INSTRUCTION_FIXED_CTR_MASK 0x04
struct cpu_hw_events {
/* currently enabled events */
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v7 17/22] RISC-V: perf: Add legacy event encodings via sysfs
From: Atish Patra @ 2026-06-22 8:04 UTC (permalink / raw)
To: Jiri Olsa, James Clark, Mark Rutland, Will Deacon,
Arnaldo Carvalho de Melo, Rob Herring, Ian Rogers,
Krzysztof Kozlowski, Anup Patel, Paul Walmsley, Atish Patra,
Namhyung Kim
Cc: devicetree, linux-perf-users, Conor Dooley, linux-arm-kernel,
linux-kernel, linux-riscv
In-Reply-To: <20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com>
From: Atish Patra <atishp@rivosinc.com>
Define sysfs details for the legacy events so that any tool can
parse these to understand the minimum set of legacy events
supported by the platform. The sysfs entry will describe both event
encoding and corresponding counter map so that an perf event can be
programmed accordingly.
Signed-off-by: Atish Patra <atishp@rivosinc.com>
---
drivers/perf/riscv_pmu_sbi.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c
index 1c846cdc96cf..8aaf16e31fdf 100644
--- a/drivers/perf/riscv_pmu_sbi.c
+++ b/drivers/perf/riscv_pmu_sbi.c
@@ -131,7 +131,20 @@ static struct attribute_group riscv_cdeleg_pmu_format_group = {
.attrs = riscv_cdeleg_pmu_formats_attr,
};
+#define RVPMU_EVENT_ATTR_RESOLVE(m) #m
+#define RVPMU_EVENT_CMASK_ATTR(_name, _var, config, mask) \
+ PMU_EVENT_ATTR_STRING(_name, rvpmu_event_attr_##_var, \
+ "event=" RVPMU_EVENT_ATTR_RESOLVE(config) \
+ ",counterid_mask=" RVPMU_EVENT_ATTR_RESOLVE(mask))
+
+#define RVPMU_EVENT_ATTR_PTR(name) (&rvpmu_event_attr_##name.attr.attr)
+
+static struct attribute_group riscv_cdeleg_pmu_event_group __ro_after_init = {
+ .name = "events",
+};
+
static const struct attribute_group *riscv_cdeleg_pmu_attr_groups[] = {
+ &riscv_cdeleg_pmu_event_group,
&riscv_cdeleg_pmu_format_group,
NULL,
};
@@ -447,11 +460,14 @@ struct riscv_vendor_pmu_events {
const struct riscv_pmu_event *hw_event_map;
const struct riscv_pmu_event (*cache_event_map)[PERF_COUNT_HW_CACHE_OP_MAX]
[PERF_COUNT_HW_CACHE_RESULT_MAX];
+ struct attribute **attrs_events;
};
-#define RISCV_VENDOR_PMU_EVENTS(_vendorid, _archid, _implid, _hw_event_map, _cache_event_map) \
+#define RISCV_VENDOR_PMU_EVENTS(_vendorid, _archid, _implid, _hw_event_map, \
+ _cache_event_map, _attrs) \
{ .vendorid = _vendorid, .archid = _archid, .implid = _implid, \
- .hw_event_map = _hw_event_map, .cache_event_map = _cache_event_map },
+ .hw_event_map = _hw_event_map, .cache_event_map = _cache_event_map, \
+ .attrs_events = _attrs },
static struct riscv_vendor_pmu_events pmu_vendor_events_table[] = {
};
@@ -473,6 +489,8 @@ static void __init rvpmu_vendor_register_events(void)
pmu_vendor_events_table[i].archid == arch_id) {
current_pmu_hw_event_map = pmu_vendor_events_table[i].hw_event_map;
current_pmu_cache_event_map = pmu_vendor_events_table[i].cache_event_map;
+ riscv_cdeleg_pmu_event_group.attrs =
+ pmu_vendor_events_table[i].attrs_events;
break;
}
}
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v7 18/22] RISC-V: perf: Add Qemu virt machine events
From: Atish Patra @ 2026-06-22 8:04 UTC (permalink / raw)
To: Jiri Olsa, James Clark, Mark Rutland, Will Deacon,
Arnaldo Carvalho de Melo, Rob Herring, Ian Rogers,
Krzysztof Kozlowski, Anup Patel, Paul Walmsley, Atish Patra,
Namhyung Kim
Cc: devicetree, linux-perf-users, Conor Dooley, linux-arm-kernel,
linux-kernel, linux-riscv
In-Reply-To: <20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com>
From: Atish Patra <atishp@rivosinc.com>
Qemu virt machine supports a very minimal set of legacy perf events.
Add them to the vendor table so that users can use them when
counter delegation is enabled.
Signed-off-by: Atish Patra <atishp@rivosinc.com>
---
arch/riscv/include/asm/vendorid_list.h | 4 ++++
drivers/perf/riscv_pmu_sbi.c | 36 ++++++++++++++++++++++++++++++++++
2 files changed, 40 insertions(+)
diff --git a/arch/riscv/include/asm/vendorid_list.h b/arch/riscv/include/asm/vendorid_list.h
index 7f5030ee1fcf..603aa2b21c0b 100644
--- a/arch/riscv/include/asm/vendorid_list.h
+++ b/arch/riscv/include/asm/vendorid_list.h
@@ -11,4 +11,8 @@
#define SIFIVE_VENDOR_ID 0x489
#define THEAD_VENDOR_ID 0x5b7
+#define QEMU_VIRT_VENDOR_ID 0x000
+#define QEMU_VIRT_IMPL_ID 0x000
+#define QEMU_VIRT_ARCH_ID 0x000
+
#endif
diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c
index 8aaf16e31fdf..3cb7a1f4035e 100644
--- a/drivers/perf/riscv_pmu_sbi.c
+++ b/drivers/perf/riscv_pmu_sbi.c
@@ -27,6 +27,7 @@
#include <asm/sbi.h>
#include <asm/cpufeature.h>
#include <asm/vendor_extensions.h>
+#include <asm/vendorid_list.h>
#include <asm/vendor_extensions/andes.h>
#include <asm/hwcap.h>
#include <asm/csr_ind.h>
@@ -469,7 +470,42 @@ struct riscv_vendor_pmu_events {
.hw_event_map = _hw_event_map, .cache_event_map = _cache_event_map, \
.attrs_events = _attrs },
+/* QEMU virt PMU events */
+static const struct riscv_pmu_event qemu_virt_hw_event_map[PERF_COUNT_HW_MAX] = {
+ PERF_MAP_ALL_UNSUPPORTED,
+ [PERF_COUNT_HW_CPU_CYCLES] = {0x01, 0xFFFFFFF8},
+ [PERF_COUNT_HW_INSTRUCTIONS] = {0x02, 0xFFFFFFF8}
+};
+
+static const struct riscv_pmu_event qemu_virt_cache_event_map[PERF_COUNT_HW_CACHE_MAX]
+ [PERF_COUNT_HW_CACHE_OP_MAX]
+ [PERF_COUNT_HW_CACHE_RESULT_MAX] = {
+ PERF_CACHE_MAP_ALL_UNSUPPORTED,
+ [C(DTLB)][C(OP_READ)][C(RESULT_MISS)] = {0x10019, 0xFFFFFFF8},
+ [C(DTLB)][C(OP_WRITE)][C(RESULT_MISS)] = {0x1001B, 0xFFFFFFF8},
+
+ [C(ITLB)][C(OP_READ)][C(RESULT_MISS)] = {0x10021, 0xFFFFFFF8},
+};
+
+RVPMU_EVENT_CMASK_ATTR(cycles, cycles, 0x01, 0xFFFFFFF8);
+RVPMU_EVENT_CMASK_ATTR(instructions, instructions, 0x02, 0xFFFFFFF8);
+RVPMU_EVENT_CMASK_ATTR(dTLB-load-misses, dTLB_load_miss, 0x10019, 0xFFFFFFF8);
+RVPMU_EVENT_CMASK_ATTR(dTLB-store-misses, dTLB_store_miss, 0x1001B, 0xFFFFFFF8);
+RVPMU_EVENT_CMASK_ATTR(iTLB-load-misses, iTLB_load_miss, 0x10021, 0xFFFFFFF8);
+
+static struct attribute *qemu_virt_event_group[] = {
+ RVPMU_EVENT_ATTR_PTR(cycles),
+ RVPMU_EVENT_ATTR_PTR(instructions),
+ RVPMU_EVENT_ATTR_PTR(dTLB_load_miss),
+ RVPMU_EVENT_ATTR_PTR(dTLB_store_miss),
+ RVPMU_EVENT_ATTR_PTR(iTLB_load_miss),
+ NULL,
+};
+
static struct riscv_vendor_pmu_events pmu_vendor_events_table[] = {
+ RISCV_VENDOR_PMU_EVENTS(QEMU_VIRT_VENDOR_ID, QEMU_VIRT_ARCH_ID, QEMU_VIRT_IMPL_ID,
+ qemu_virt_hw_event_map, qemu_virt_cache_event_map,
+ qemu_virt_event_group)
};
static const struct riscv_pmu_event *current_pmu_hw_event_map;
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v7 19/22] tools/perf: Support event code for arch standard events
From: Atish Patra @ 2026-06-22 8:04 UTC (permalink / raw)
To: Jiri Olsa, James Clark, Mark Rutland, Will Deacon,
Arnaldo Carvalho de Melo, Rob Herring, Ian Rogers,
Krzysztof Kozlowski, Anup Patel, Paul Walmsley, Atish Patra,
Namhyung Kim
Cc: devicetree, linux-perf-users, Conor Dooley, linux-arm-kernel,
linux-kernel, linux-riscv
In-Reply-To: <20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com>
From: Atish Patra <atishp@rivosinc.com>
RISC-V relies on the event encoding from the json file. That includes
arch standard events. If event code is present, event is already updated
with correct encoding. No need to update it again which results in losing
the event encoding.
Signed-off-by: Atish Patra <atishp@rivosinc.com>
---
tools/perf/pmu-events/arch/riscv/arch-standard.json | 10 ++++++++++
tools/perf/pmu-events/jevents.py | 6 +++++-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/tools/perf/pmu-events/arch/riscv/arch-standard.json b/tools/perf/pmu-events/arch/riscv/arch-standard.json
new file mode 100644
index 000000000000..96e21f088558
--- /dev/null
+++ b/tools/perf/pmu-events/arch/riscv/arch-standard.json
@@ -0,0 +1,10 @@
+[
+ {
+ "EventName": "cycles",
+ "BriefDescription": "cycle executed"
+ },
+ {
+ "EventName": "instructions",
+ "BriefDescription": "instruction retired"
+ }
+]
diff --git a/tools/perf/pmu-events/jevents.py b/tools/perf/pmu-events/jevents.py
index 3a1bcdcdc685..457fce7a5982 100755
--- a/tools/perf/pmu-events/jevents.py
+++ b/tools/perf/pmu-events/jevents.py
@@ -413,7 +413,11 @@ class JsonEvent:
self.long_desc = None
if arch_std:
if arch_std.lower() in _arch_std_events:
- event = _arch_std_events[arch_std.lower()].event
+ # If the JSON event already specified an event code, the encoding has
+ # been set above; don't overwrite it with the arch standard event or
+ # the event encoding would be lost.
+ if not eventcode:
+ event = _arch_std_events[arch_std.lower()].event
# Copy from the architecture standard event to self for undefined fields.
for attr, value in _arch_std_events[arch_std.lower()].__dict__.items():
if hasattr(self, attr) and not getattr(self, attr):
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v7 20/22] tools/perf: Add RISC-V CounterIDMask event field
From: Atish Patra @ 2026-06-22 8:04 UTC (permalink / raw)
To: Jiri Olsa, James Clark, Mark Rutland, Will Deacon,
Arnaldo Carvalho de Melo, Rob Herring, Ian Rogers,
Krzysztof Kozlowski, Anup Patel, Paul Walmsley, Atish Patra,
Namhyung Kim
Cc: devicetree, linux-perf-users, Conor Dooley, linux-arm-kernel,
linux-kernel, linux-riscv
In-Reply-To: <20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com>
From: Atish Patra <atishp@rivosinc.com>
Counter delegation lets supervisor mode choose the hpmcounter for an event,
but the hardware may only allow a given event on a subset of counters. Add
a RISC-V specific "CounterIDMask" json event field, handled like the other
arch-specific entries in event_fields[], that carries the allowed-counter
bitmask through to the driver's existing counterid_mask (config2:0-31)
format.
The value is the bitmask directly so no counter-list to bitmask
conversion is needed, and because the field is RISC-V specific it is a
no-op for every other architecture's events (unlike the shared "Counter"
field).
Signed-off-by: Atish Patra <atishp@rivosinc.com>
---
tools/perf/pmu-events/jevents.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/tools/perf/pmu-events/jevents.py b/tools/perf/pmu-events/jevents.py
index 457fce7a5982..c1ed8a05c9a4 100755
--- a/tools/perf/pmu-events/jevents.py
+++ b/tools/perf/pmu-events/jevents.py
@@ -396,6 +396,7 @@ class JsonEvent:
('EnAllSlices', 'enallslices='),
('SliceId', 'sliceid='),
('ThreadMask', 'threadmask='),
+ ('CounterIDMask', 'counterid_mask='),
]
for key, value in event_fields:
if key in jd and not is_zero(jd[key]):
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v7 21/22] TEST(do-not-upstream): fake qemu-virt PMU events for cdeleg counter-mask testing
From: Atish Patra @ 2026-06-22 8:04 UTC (permalink / raw)
To: Jiri Olsa, James Clark, Mark Rutland, Will Deacon,
Arnaldo Carvalho de Melo, Rob Herring, Ian Rogers,
Krzysztof Kozlowski, Anup Patel, Paul Walmsley, Atish Patra,
Namhyung Kim
Cc: devicetree, linux-perf-users, Conor Dooley, linux-arm-kernel,
linux-kernel, linux-riscv
In-Reply-To: <20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com>
From: Atish Patra <atishp@meta.com>
Adds fake-any/fake-ctr3/fake-ctr34 (event codes 0xF0x QEMU doesn't model) with
counterid_masks, to exercise the counter-delegation allocation + counter-mask
constraint in QEMU (events read 0 = allocated/programmed, vs 'not supported').
Signed-off-by: Atish Patra <atishp@meta.com>
---
drivers/perf/riscv_pmu_sbi.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c
index 3cb7a1f4035e..13a9f1fe4293 100644
--- a/drivers/perf/riscv_pmu_sbi.c
+++ b/drivers/perf/riscv_pmu_sbi.c
@@ -492,6 +492,12 @@ RVPMU_EVENT_CMASK_ATTR(instructions, instructions, 0x02, 0xFFFFFFF8);
RVPMU_EVENT_CMASK_ATTR(dTLB-load-misses, dTLB_load_miss, 0x10019, 0xFFFFFFF8);
RVPMU_EVENT_CMASK_ATTR(dTLB-store-misses, dTLB_store_miss, 0x1001B, 0xFFFFFFF8);
RVPMU_EVENT_CMASK_ATTR(iTLB-load-misses, iTLB_load_miss, 0x10021, 0xFFFFFFF8);
+/*
+ * FAKE events for cdeleg mechanism testing: event codes QEMU does NOT model.
+ */
+RVPMU_EVENT_CMASK_ATTR(fake-any, fake_any, 0xF00, 0xFFFFFFF8);
+RVPMU_EVENT_CMASK_ATTR(fake-ctr3, fake_ctr3, 0xF01, 0x8);
+RVPMU_EVENT_CMASK_ATTR(fake-ctr34, fake_ctr34, 0xF02, 0x18);
static struct attribute *qemu_virt_event_group[] = {
RVPMU_EVENT_ATTR_PTR(cycles),
@@ -499,6 +505,9 @@ static struct attribute *qemu_virt_event_group[] = {
RVPMU_EVENT_ATTR_PTR(dTLB_load_miss),
RVPMU_EVENT_ATTR_PTR(dTLB_store_miss),
RVPMU_EVENT_ATTR_PTR(iTLB_load_miss),
+ RVPMU_EVENT_ATTR_PTR(fake_any),
+ RVPMU_EVENT_ATTR_PTR(fake_ctr3),
+ RVPMU_EVENT_ATTR_PTR(fake_ctr34),
NULL,
};
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v7 22/22] TEST(do-not-upstream): fake qemu vendor JSON + mapfile entry for CounterIDMask path
From: Atish Patra @ 2026-06-22 8:04 UTC (permalink / raw)
To: Jiri Olsa, James Clark, Mark Rutland, Will Deacon,
Arnaldo Carvalho de Melo, Rob Herring, Ian Rogers,
Krzysztof Kozlowski, Anup Patel, Paul Walmsley, Atish Patra,
Namhyung Kim
Cc: devicetree, linux-perf-users, Conor Dooley, linux-arm-kernel,
linux-kernel, linux-riscv
In-Reply-To: <20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com>
From: Atish Patra <atishp@meta.com>
arch/riscv/qemu/virt/events.json: fake-json-{any,ctr3,ctr34,ctr6} with EventCode
+ CounterIDMask; mapfile.csv: 0x0-0x0-0x0 -> qemu/virt. Exercises jevents
CounterIDMask -> counterid_mask= -> config2 -> cdeleg counter allocation.
Signed-off-by: Atish Patra <atishp@meta.com>
---
tools/perf/pmu-events/arch/riscv/mapfile.csv | 1 +
.../pmu-events/arch/riscv/qemu/virt/events.json | 26 ++++++++++++++++++++++
2 files changed, 27 insertions(+)
diff --git a/tools/perf/pmu-events/arch/riscv/mapfile.csv b/tools/perf/pmu-events/arch/riscv/mapfile.csv
index 87cfb0e0849f..3533a8c0253f 100644
--- a/tools/perf/pmu-events/arch/riscv/mapfile.csv
+++ b/tools/perf/pmu-events/arch/riscv/mapfile.csv
@@ -24,3 +24,4 @@
0x602-0x3-0x0,v1,openhwgroup/cva6,core
0x67e-0x80000000db0000[89]0-0x[[:xdigit:]]+,v1,starfive/dubhe-80,core
0x31e-0x8000000000008a45-0x[[:xdigit:]]+,v1,andes/ax45,core
+0x0-0x0-0x0,v1,qemu/virt,core
diff --git a/tools/perf/pmu-events/arch/riscv/qemu/virt/events.json b/tools/perf/pmu-events/arch/riscv/qemu/virt/events.json
new file mode 100644
index 000000000000..294c4ed645f6
--- /dev/null
+++ b/tools/perf/pmu-events/arch/riscv/qemu/virt/events.json
@@ -0,0 +1,26 @@
+[
+ {
+ "EventName": "fake-json-any",
+ "EventCode": "0xF10",
+ "CounterIDMask": "0xFFFFFFF8",
+ "BriefDescription": "FAKE json event (any hpmcounter 3-31) - QEMU does not model 0xF10"
+ },
+ {
+ "EventName": "fake-json-ctr3",
+ "EventCode": "0xF11",
+ "CounterIDMask": "0x8",
+ "BriefDescription": "FAKE json event constrained to hpmcounter3"
+ },
+ {
+ "EventName": "fake-json-ctr34",
+ "EventCode": "0xF12",
+ "CounterIDMask": "0x18",
+ "BriefDescription": "FAKE json event constrained to hpmcounter3,4"
+ },
+ {
+ "EventName": "fake-json-ctr6",
+ "EventCode": "0xF13",
+ "CounterIDMask": "0x40",
+ "BriefDescription": "FAKE json event constrained to hpmcounter6 (out of a small pmu-mask)"
+ }
+]
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v11 1/2] media: dt-bindings: i2c: add Himax HM1246 image sensor
From: Matthias Fend @ 2026-06-22 8:13 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans Verkuil, Sakari Ailus, Hans de Goede,
Ricardo Ribalda, André Apitzsch, Tarang Raval,
Andy Shevchenko, Benjamin Mugnier, Sylvain Petinot, Dongcheng Yan,
Bryan O'Donoghue, Alan Stern, Jingjing Xiong,
Heimir Thor Sverrisson, Mehdi Djait, Vladimir Zapolskiy,
Laurent Pinchart, Hardevsinh Palaniya, Svyatoslav Ryhel,
Philipp Zabel, Hans Verkuil, Hans de Goede, Xiaolei Wang,
Walter Werner Schneider, Kate Hsuan, Bartosz Golaszewski,
Miguel Vadillo
Cc: linux-media, devicetree, linux-kernel, Hao Yao, Himanshu Bhavani,
Matthias Fend
In-Reply-To: <20260622-hm1246-v11-0-c62ef9516ab1@emfend.at>
Add YAML device tree binding for Himax HM1246 image sensor.
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Matthias Fend <matthias.fend@emfend.at>
---
.../bindings/media/i2c/himax,hm1246.yaml | 120 +++++++++++++++++++++
MAINTAINERS | 6 ++
2 files changed, 126 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml b/Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a65488fcc643fd1e2b9435224813f35f2b4397c6
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml
@@ -0,0 +1,120 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright 2025 Matthias Fend <matthias.fend@emfend.at>
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/himax,hm1246.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Himax HM1246-AWD 1/3.7-Inch megapixel SoC image sensor
+
+maintainers:
+ - Matthias Fend <matthias.fend@emfend.at>
+
+description:
+ The Himax HM1246-AWD is a 1/3.7-Inch CMOS image sensor SoC with an active
+ array size of 1296 x 976. It is programmable through an I2C interface and
+ connected via parallel bus.
+
+allOf:
+ - $ref: /schemas/media/video-interface-devices.yaml#
+
+properties:
+ compatible:
+ const: himax,hm1246
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ description: Input reference clock (6 - 27 MHz)
+ maxItems: 1
+
+ reset-gpios:
+ description: Active low XSHUTDOWN pin
+ maxItems: 1
+
+ avdd-supply:
+ description: Power for analog circuit (3.0 - 3.6 V)
+
+ iovdd-supply:
+ description: Power for I/O circuit (1.7 - 3.6 V)
+
+ dvdd-supply:
+ description: Power for digital circuit (1.5 / 1.8 V)
+
+ port:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ additionalProperties: false
+ description: Parallel video output port
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ bus-width:
+ default: 10
+
+ hsync-active:
+ default: 1
+
+ vsync-active:
+ default: 1
+
+ pclk-sample:
+ default: 0
+
+ required:
+ - link-frequencies
+
+ required:
+ - endpoint
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - avdd-supply
+ - iovdd-supply
+ - dvdd-supply
+ - port
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/media/video-interfaces.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ sensor@24 {
+ compatible = "himax,hm1246";
+ reg = <0x24>;
+
+ clocks = <&hm1246_clk>;
+
+ reset-gpios = <&gpio0 0 GPIO_ACTIVE_LOW>;
+
+ avdd-supply = <&hm1246_avdd>;
+ iovdd-supply = <&hm1246_iovdd>;
+ dvdd-supply = <&hm1246_dvdd>;
+
+ orientation = <2>;
+ rotation = <0>;
+
+ port {
+ endpoint {
+ remote-endpoint = <&isp_par_in>;
+ bus-width = <10>;
+ hsync-active = <1>; /* active high */
+ vsync-active = <1>; /* active high */
+ pclk-sample = <1>; /* sample on rising edge */
+ link-frequencies = /bits/ 64 <42200000>;
+ };
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index efbf808063e5066004180c36af88cb1f32c76a80..c320e7e9a2bd88319945bfe1202bc891d35f0f7c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11553,6 +11553,12 @@ L: linux-kernel@vger.kernel.org
S: Maintained
F: drivers/misc/hisi_hikey_usb.c
+HIMAX HM1246 SENSOR DRIVER
+M: Matthias Fend <matthias.fend@emfend.at>
+L: linux-media@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml
+
HIMAX HX83112B TOUCHSCREEN SUPPORT
M: Job Noorman <job@noorman.info>
L: linux-input@vger.kernel.org
--
2.34.1
^ permalink raw reply related
* [PATCH v11 0/2] media: add Himax HM1246 image sensor
From: Matthias Fend @ 2026-06-22 8:13 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans Verkuil, Sakari Ailus, Hans de Goede,
Ricardo Ribalda, André Apitzsch, Tarang Raval,
Andy Shevchenko, Benjamin Mugnier, Sylvain Petinot, Dongcheng Yan,
Bryan O'Donoghue, Alan Stern, Jingjing Xiong,
Heimir Thor Sverrisson, Mehdi Djait, Vladimir Zapolskiy,
Laurent Pinchart, Hardevsinh Palaniya, Svyatoslav Ryhel,
Philipp Zabel, Hans Verkuil, Hans de Goede, Xiaolei Wang,
Walter Werner Schneider, Kate Hsuan, Bartosz Golaszewski,
Miguel Vadillo
Cc: linux-media, devicetree, linux-kernel, Hao Yao, Himanshu Bhavani,
Matthias Fend
Hello,
this series adds support for the Himax HM1246 image sensor.
The Himax HM1246-AWD is a 1/3.7-Inch CMOS image sensor SoC with an active
array size of 1296 x 976.
Currently, only the native RAW mode is supported. Other modes and the
internal image signal processing pipeline are not currently supported.
The data sheet is available on the manufacturer's website [1].
Tested on i.MX8MP hardware. A Toshiba TC358746 bridge was used to convert
the sensor's parallel video output into MIPI signals for the i.MX8MP.
Best regards
~Matthias
[1] https://www.himax.com.tw/wp-content/uploads/2024/03/HM1246-AWD_DS_v01.pdf
v4l2-compliance 1.28.1, 64 bits, 64-bit time_t
Compliance test for device /dev/v4l-subdev4:
Driver Info:
Driver version : 6.12.0
Capabilities : 0x00000000
Client Capabilities: 0x0000000000000003
streams interval-uses-which
Required ioctls:
test VIDIOC_SUDBEV_QUERYCAP: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/v4l-subdev4 open: OK
test VIDIOC_SUBDEV_QUERYCAP: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 0 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 15 Private Controls: 0
Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK (Not Supported)
test VIDIOC_TRY_FMT: OK (Not Supported)
test VIDIOC_S_FMT: OK (Not Supported)
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK (Not Supported)
Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK (Not Supported)
test Requests: OK (Not Supported)
Total for device /dev/v4l-subdev4: 45, Succeeded: 45, Failed: 0, Warnings: 0
Signed-off-by: Matthias Fend <matthias.fend@emfend.at>
---
Changes in v11:
- Renamed testmode from "Checkboard" to "Checkerboard" (Sakari)
- Drop endpoint check before v4l2_fwnode_endpoint_alloc_parse (Sakari)
- Use loop-scoped variable in hm1246_get_regulators (Sakari)
- Allow node with multiple link frequencies (Sakari)
- Drop pm_runtime_idle (Sakari)
- Add dependency on OF (Sakari)
- Link to v10: https://lore.kernel.org/r/20260619-hm1246-v10-0-d88e431a6c11@emfend.at
Changes in v10:
- Reordered struct hm1246 to place embedded struct first (Andy)
- Used hex format (0x01) for gain step definition (Andy)
- Marked local tables and constants as static const (Andy)
- Improved line wrapping in hm1246_cci_write_test_pattern (Andy)
- Decoupled declaration and initialization of ret in hm1246_set_ctrl (Andy)
- Rebased
- Link to v9: https://lore.kernel.org/r/20260128-hm1246-v9-0-f9c996486a95@emfend.at
Changes in v9:
- Use v4l2_rect in hm1246_mode
- Return in default case of hm1246_get_selection()
- Convert gpio based reset handling to use generic reset controller
- Link to v8: https://lore.kernel.org/r/20260113-hm1246-v8-0-ea93947b192e@emfend.at
Changes in v8:
- Adjusted copyright year (Sakari)
- Use local var for unreferenced v4l2 controls (Sakari)
- Return NULL instead of PTR_ERR if hm1246_find_mode_by_mbus_code() fails (Sakari)
- Removed unnecessary parentheses (Sakari)
- Dropped git reference in MAINTAINER entry (Sakari)
- Added default for bus-width in bindings (Sakari)
- Link to v7: https://lore.kernel.org/r/20260112-hm1246-v7-0-fee8587f2808@emfend.at
Changes in v7:
- Expect the PLL to generate the exact link frequency specified in DT (Laurent, Sakari)
- Dropped {g,s}_register functions
- Link to v6: https://lore.kernel.org/r/20251202-hm1246-v6-0-3e96ed6b3ffa@emfend.at
Changes in v6:
- Rework includes to follow include-what-you-use (Andy)
- Replace 'fsleep(6000)' with 'fsleep(6 * USEC_PER_MSEC)' (Andy)
- Simplify hm1246_get_selection() return (Andy)
- Use explicit indexes for test pattern array (Andy)
- Improve some line-wrappings (Andy)
- Avoid using __maybe_unused (Andy)
- Drop an unnecessary type cast (Andy)
- Use '0' instead of '0x0' (Andy)
- Reword comments about timings (Andy)
- Simplify error handling of hm1246_init_controls() (Sakari)
- Revert 'rework PLL calc to use goto' (Andy, Sakari)
- Link to v5: https://lore.kernel.org/r/20251104-hm1246-v5-0-97c8f25b5419@emfend.at
Changes in v5:
- Converted to lower case hexadecimals
- Use consistent returns in switch of hm1246_get_selection()
- Adjust some variable types/attributes
- Removed redundant parentheses
- Rework PLL calc to use goto
- Simplified some function returns
- Use array definition for test patterns
- Source format adjustments
- Properly init minimum of pixel_rate control
- dropped hm1246_update_controls()
- require and check DT link frequencies
- Link to v4: https://lore.kernel.org/r/20251017-hm1246-v4-0-e3388ea2f08c@emfend.at
Changes in v4:
- Split changes to MAINTAINERS into commits
- Fix comma after statement (use semicolon)
- Replace abs() with abs_diff() in PLL calculation
- Inverse needs_cmu_update logic
- Drop mode from hm1246_set_ctrl()
- Return if xclk frequency is out of range
- Fix reset_gpio dev_err_probe()
- Rebased on media-committers/next
- Link to v3: https://lore.kernel.org/r/20250912-hm1246-v3-0-3b89f47dfa43@emfend.at
Changes in v3:
- Bindings: Remove bus-type and add default polarity values
- Select V4L2_CCI_I2C
- Convert additional macros to use HZ_PER_*
- Replace cur_mode with v4l2_find_nearest_size()
- Remove duplicates in the register init sequence
- Use container_of_const
- Check return of hm1246_update_controls()
- Correct multi-line comments
- Replace hm1246_cci_write_cmu()
- Consistently use hm1246->dev
- Use pm_runtime_put_autosuspend()
- Remove v4l2 event handling
- Convert to devm_v4l2_sensor_clk_get()
- Configure PM before registering subdev
- Link to v2: https://lore.kernel.org/r/20250526-hm1246-v2-0-6b882827a3a5@emfend.at
- Depends-on: https://lore.kernel.org/all/20250707143253.167910-1-mehdi.djait@linux.intel.com/
Changes in v2:
- Use macros for 64-bit division
- Avoid compiler warnings about potentially uninitialized variables
- Fix two uses of dev_err_probe
- Link to v1: https://lore.kernel.org/r/20250403-hm1246-v1-0-30990d71bc42@emfend.at
---
Matthias Fend (2):
media: dt-bindings: i2c: add Himax HM1246 image sensor
media: i2c: add Himax HM1246 image sensor driver
.../bindings/media/i2c/himax,hm1246.yaml | 120 ++
MAINTAINERS | 7 +
drivers/media/i2c/Kconfig | 11 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/hm1246.c | 1285 ++++++++++++++++++++
5 files changed, 1424 insertions(+)
---
base-commit: 06cb687a5132fcffe624c0070576ab852ac6b568
change-id: 20250403-hm1246-96b0cdab773c
Best regards,
--
Matthias Fend <matthias.fend@emfend.at>
^ permalink raw reply
* [PATCH v11 2/2] media: i2c: add Himax HM1246 image sensor driver
From: Matthias Fend @ 2026-06-22 8:13 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans Verkuil, Sakari Ailus, Hans de Goede,
Ricardo Ribalda, André Apitzsch, Tarang Raval,
Andy Shevchenko, Benjamin Mugnier, Sylvain Petinot, Dongcheng Yan,
Bryan O'Donoghue, Alan Stern, Jingjing Xiong,
Heimir Thor Sverrisson, Mehdi Djait, Vladimir Zapolskiy,
Laurent Pinchart, Hardevsinh Palaniya, Svyatoslav Ryhel,
Philipp Zabel, Hans Verkuil, Hans de Goede, Xiaolei Wang,
Walter Werner Schneider, Kate Hsuan, Bartosz Golaszewski,
Miguel Vadillo
Cc: linux-media, devicetree, linux-kernel, Hao Yao, Himanshu Bhavani,
Matthias Fend
In-Reply-To: <20260622-hm1246-v11-0-c62ef9516ab1@emfend.at>
Add a V4L2 sub-device driver for Himax HM1246 image sensor.
The Himax HM1246-AWD is a 1/3.7-Inch CMOS image sensor SoC with an active
array size of 1296 x 976. It is programmable through an I2C interface and
connected via parallel bus.
The sensor has an internal ISP with a complete image processing pipeline
including control loops. However, this driver uses the sensor in raw mode
and the entire ISP is bypassed.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Matthias Fend <matthias.fend@emfend.at>
---
MAINTAINERS | 1 +
drivers/media/i2c/Kconfig | 11 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/hm1246.c | 1285 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1298 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c320e7e9a2bd88319945bfe1202bc891d35f0f7c..ca13a5b21306e921e077e2548f6242041c6ee24c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11558,6 +11558,7 @@ M: Matthias Fend <matthias.fend@emfend.at>
L: linux-media@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml
+F: drivers/media/i2c/hm1246.c
HIMAX HX83112B TOUCHSCREEN SUPPORT
M: Job Noorman <job@noorman.info>
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 5d173e0ecf424f2f204f8d426be818e44357f8e4..a3a7102491148ef1f24fcd432a492655f56bbcdd 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -137,6 +137,17 @@ config VIDEO_HI847
To compile this driver as a module, choose M here: the
module will be called hi847.
+config VIDEO_HM1246
+ tristate "Himax HM1246 sensor support"
+ depends on OF
+ select V4L2_CCI_I2C
+ help
+ This is a Video4Linux2 sensor driver for the Himax
+ HM1246 camera.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hm1246.
+
config VIDEO_IMX111
tristate "Sony IMX111 sensor support"
select V4L2_CCI_I2C
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index e45359efe0e41e13e3c0869e5ead7d6cf4aca3a7..df420ff4e1d6304ef62f9cd84c8ddb9e2db30a11 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_VIDEO_GC2145) += gc2145.o
obj-$(CONFIG_VIDEO_HI556) += hi556.o
obj-$(CONFIG_VIDEO_HI846) += hi846.o
obj-$(CONFIG_VIDEO_HI847) += hi847.o
+obj-$(CONFIG_VIDEO_HM1246) += hm1246.o
obj-$(CONFIG_VIDEO_I2C) += video-i2c.o
obj-$(CONFIG_VIDEO_IMX111) += imx111.o
obj-$(CONFIG_VIDEO_IMX208) += imx208.o
diff --git a/drivers/media/i2c/hm1246.c b/drivers/media/i2c/hm1246.c
new file mode 100644
index 0000000000000000000000000000000000000000..f87ccb9b5a560fdd1273834ac4ec3999cc1c4bc2
--- /dev/null
+++ b/drivers/media/i2c/hm1246.c
@@ -0,0 +1,1285 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for Himax HM1246 image sensor
+ *
+ * Copyright 2026 Matthias Fend <matthias.fend@emfend.at>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/limits.h>
+#include <linux/math.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+#include <linux/units.h>
+#include <media/media-entity.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-cci.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+/* Status registers */
+#define HM1246_MODEL_ID_REG CCI_REG16(0x0000)
+
+/* General setup registers */
+#define HM1246_MODE_SELECT_REG CCI_REG8(0x0100)
+#define HM1246_MODE_SELECT_STANDBY 0x00
+#define HM1246_MODE_SELECT_STREAM 0x01
+#define HM1246_MODE_SELECT_STOP 0x02
+#define HM1246_IMAGE_ORIENTATION_REG CCI_REG8(0x0101)
+#define HM1246_IMAGE_ORIENTATION_VFLIP BIT(1)
+#define HM1246_IMAGE_ORIENTATION_HFLIP BIT(0)
+#define HM1246_CMU_UPDATE_REG CCI_REG8(0x0104)
+
+/* Output setup registers */
+#define HM1246_COARSE_INTG_REG CCI_REG16(0x0202)
+#define HM1246_ANALOG_GLOBAL_GAIN_REG CCI_REG8(0x0205)
+
+/* Clock setup registers */
+#define HM1246_PLL1CFG_REG CCI_REG8(0x0303)
+#define HM1246_PLL1CFG_MULTIPLIER(x) (((x) & 0xff) << 0)
+#define HM1246_PLL2CFG_REG CCI_REG8(0x0305)
+#define HM1246_PLL2CFG_PRE_DIV(x) (((x) & 0x1f) << 1)
+#define HM1246_PLL2CFG_MULTIPLIER(x) (((x) & 0x01) << 0)
+#define HM1246_PLL3CFG_REG CCI_REG8(0x0307)
+#define HM1246_PLL3CFG_POST_DIV(x) (((x) & 0x3) << 6)
+#define HM1246_PLL3CFG_SYSCLK_DIV(x) (((x) & 0x3) << 4)
+#define HM1246_PLL3CFG_PCLK_DIV(x) (((x) & 0x7) << 0)
+
+/* Frame timing registers */
+#define HM1246_FRAME_LENGTH_LINES_REG CCI_REG16(0x0340)
+#define HM1246_LINE_LENGTH_PCK_REG CCI_REG16(0x0342)
+
+/* Image size registers */
+#define HM1246_X_ADDR_START_REG CCI_REG16(0x0344)
+#define HM1246_Y_ADDR_START_REG CCI_REG16(0x0346)
+#define HM1246_X_ADDR_END_REG CCI_REG16(0x0348)
+#define HM1246_Y_ADDR_END_REG CCI_REG16(0x034a)
+#define HM1246_X_LA_START_REG CCI_REG16(0x0351)
+#define HM1246_X_LA_END_REG CCI_REG16(0x0353)
+#define HM1246_Y_LA_START_REG CCI_REG16(0x0355)
+#define HM1246_Y_LA_END_REG CCI_REG16(0x0357)
+
+/* Test pattern registers */
+#define HM1246_TEST_PATTERN_MODE_REG CCI_REG8(0x0601)
+#define HM1246_TEST_PATTERN_MODE_MODE(x) (((x) & 0xf) << 4)
+#define HM1246_TEST_PATTERN_MODE_ENABLE BIT(0)
+#define HM1246_TEST_DATA_BLUE_REG CCI_REG16(0x0602)
+#define HM1246_TEST_DATA_GB_REG CCI_REG16(0x0604)
+#define HM1246_TEST_DATA_RED_REG CCI_REG16(0x0606)
+#define HM1246_TEST_DATA_GR_REG CCI_REG16(0x0608)
+
+/* SBC registers */
+#define HM1246_SBC_BOOT_REF2_REG CCI_REG8(0x2001)
+#define HM1246_SBC_BOOT_REF2_PLL_LOCK BIT(4)
+#define HM1246_SBC_CTRL_REG CCI_REG8(0x2003)
+#define HM1246_SBC_CTRL_PLL_EN BIT(0)
+
+/* System registers */
+#define HM1246_OUTPUT_PRT_CTRL_REG CCI_REG8(0x2f02)
+#define HM1246_POLARITY_CTRL_REG CCI_REG8(0x2f20)
+#define HM1246_POLARITY_CTRL_HSYNC BIT(7)
+#define HM1246_POLARITY_CTRL_VSYNC BIT(6)
+#define HM1246_PCLK_CTRL_REG CCI_REG8(0x2f24)
+#define HM1246_PCLK_CTRL_POL BIT(3)
+
+/* Digital window control & parameter registers */
+#define HM1246_DWIN_XOFFSET_REG CCI_REG16(0xd5e4)
+#define HM1246_DWIN_XSIZE_REG CCI_REG16(0xd5e6)
+#define HM1246_DWIN_YOFFSET_REG CCI_REG16(0xd5e8)
+#define HM1246_DWIN_YSIZE_REG CCI_REG16(0xd5ea)
+
+#define HM1246_MODEL_ID 0x1245
+
+#define HM1246_NATIVE_WIDTH 1296
+#define HM1246_NATIVE_HEIGHT 976
+
+#define HM1246_VTS_MAX 65535
+
+#define HM1246_COARSE_INTG_MARGIN 2
+#define HM1246_COARSE_INTG_MIN 4
+#define HM1246_COARSE_INTG_STEP 1
+
+#define HM1246_ANALOG_GLOBAL_GAIN_MIN 0x00
+#define HM1246_ANALOG_GLOBAL_GAIN_MAX 0xe8
+#define HM1246_ANALOG_GLOBAL_GAIN_STEP 0x01
+
+#define HM1246_XCLK_MIN (6 * HZ_PER_MHZ)
+#define HM1246_XCLK_MAX (27 * HZ_PER_MHZ)
+
+#define HM1246_PCLK_MIN (8 * HZ_PER_MHZ)
+#define HM1246_PCLK_MAX (96 * HZ_PER_MHZ)
+
+#define HM1246_PLL_VCO_MIN (360 * HZ_PER_MHZ)
+#define HM1246_PLL_VCO_MAX (680 * HZ_PER_MHZ)
+
+#define HM1246_PLL_INCLK_MIN (1000 * HZ_PER_KHZ)
+#define HM1246_PLL_INCLK_MAX (2500 * HZ_PER_KHZ)
+
+#define HM1246_PLL_MULTI_L_MIN 1
+#define HM1246_PLL_MULTI_L_MAX 256
+
+#define HM1246_PLL_MULTI_H_MIN 2
+#define HM1246_PLL_MULTI_H_MAX 3
+
+#define HM1246_PLL_MULTI_MIN \
+ (HM1246_PLL_MULTI_H_MIN * HM1246_PLL_MULTI_L_MIN)
+#define HM1246_PLL_MULTI_MAX \
+ (HM1246_PLL_MULTI_H_MAX * HM1246_PLL_MULTI_L_MAX)
+
+static const char *const hm1246_test_pattern_menu[] = {
+ "Disabled",
+ "Checkerboard",
+ "Ramp",
+ "Moving ones",
+ "Blending color bars",
+ "Color bars",
+ "Solid white",
+ "Solid black",
+ "Solid red",
+ "Solid green",
+ "Solid blue",
+};
+
+static const char *const hm1246_supply_names[] = {
+ "avdd",
+ "iovdd",
+ "dvdd",
+};
+
+struct hm1246 {
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+ struct device *dev;
+
+ struct regulator_bulk_data supplies[ARRAY_SIZE(hm1246_supply_names)];
+ struct clk *xclk;
+ unsigned long xclk_freq;
+ struct reset_control *reset;
+ unsigned int mbus_flags;
+ s64 link_frequency;
+
+ struct v4l2_ctrl_handler ctrls;
+ struct v4l2_ctrl *exposure_ctrl;
+ struct v4l2_ctrl *hflip_ctrl;
+ struct v4l2_ctrl *vflip_ctrl;
+
+ struct regmap *regmap;
+
+ bool identified;
+};
+
+static const struct cci_reg_sequence mode_1296x976_raw[] = {
+ { HM1246_X_LA_START_REG, 60 },
+ { HM1246_X_LA_END_REG, 1355 },
+ { HM1246_Y_LA_START_REG, 0 },
+ { HM1246_Y_LA_END_REG, 975 },
+ { HM1246_OUTPUT_PRT_CTRL_REG, 0x20 },
+ { CCI_REG8(0x300a), 0x01 },
+ { CCI_REG8(0x300b), 0x00 },
+ { CCI_REG8(0x50f5), 0x01 },
+ { CCI_REG8(0x50dd), 0x00 },
+ { CCI_REG8(0x50a1), 0x02 },
+ { CCI_REG8(0x50aa), 0x1c },
+ { CCI_REG8(0x50ac), 0xdd },
+ { CCI_REG8(0x50ad), 0x08 },
+ { CCI_REG8(0x50ab), 0x04 },
+ { CCI_REG8(0x50a0), 0x40 },
+ { CCI_REG8(0x50a2), 0x12 },
+ { CCI_REG8(0x50ae), 0x30 },
+ { CCI_REG8(0x50b3), 0x04 },
+ { CCI_REG8(0x5204), 0x40 },
+ { CCI_REG8(0x5208), 0x55 },
+ { CCI_REG8(0x520b), 0x05 },
+ { CCI_REG8(0x520d), 0x40 },
+ { CCI_REG8(0x5214), 0x18 },
+ { CCI_REG8(0x5215), 0x0f },
+ { CCI_REG8(0x5217), 0x01 },
+ { CCI_REG8(0x5218), 0x07 },
+ { CCI_REG8(0x5219), 0x01 },
+ { CCI_REG8(0x521a), 0x50 },
+ { CCI_REG8(0x521b), 0x24 },
+ { CCI_REG8(0x5232), 0x01 },
+ { CCI_REG8(0x5220), 0x11 },
+ { CCI_REG8(0x5227), 0x01 },
+ { CCI_REG8(0x5106), 0xc1 },
+ { CCI_REG8(0x5115), 0xc0 },
+ { CCI_REG8(0x5116), 0xc1 },
+ { CCI_REG8(0x5138), 0x40 },
+ { CCI_REG8(0x5139), 0x60 },
+ { CCI_REG8(0x513a), 0x80 },
+ { CCI_REG8(0x513b), 0xa0 },
+ { CCI_REG8(0x513c), 0xa1 },
+ { CCI_REG8(0x513d), 0xa2 },
+ { CCI_REG8(0x513e), 0xa3 },
+ { CCI_REG8(0x5140), 0x40 },
+ { CCI_REG8(0x5141), 0x60 },
+ { CCI_REG8(0x5142), 0x80 },
+ { CCI_REG8(0x5143), 0x81 },
+ { CCI_REG8(0x5144), 0x82 },
+ { CCI_REG8(0x5145), 0x83 },
+ { CCI_REG8(0x5146), 0x93 },
+ { CCI_REG8(0x51c1), 0xc3 },
+ { CCI_REG8(0x51c5), 0xc3 },
+ { CCI_REG8(0x51c9), 0xc3 },
+ { CCI_REG8(0x51cd), 0xc2 },
+ { CCI_REG8(0x51d1), 0xc1 },
+ { CCI_REG8(0x51d5), 0xc1 },
+ { CCI_REG8(0x51d9), 0x81 },
+ { CCI_REG8(0x51dd), 0x81 },
+ { CCI_REG8(0x51c2), 0x49 },
+ { CCI_REG8(0x51c6), 0x49 },
+ { CCI_REG8(0x51ca), 0x49 },
+ { CCI_REG8(0x51ce), 0x49 },
+ { CCI_REG8(0x51d2), 0x49 },
+ { CCI_REG8(0x51d6), 0x59 },
+ { CCI_REG8(0x51da), 0x59 },
+ { CCI_REG8(0x51de), 0x59 },
+ { CCI_REG8(0x51c3), 0x20 },
+ { CCI_REG8(0x51c7), 0x38 },
+ { CCI_REG8(0x51cb), 0x21 },
+ { CCI_REG8(0x51cf), 0x11 },
+ { CCI_REG8(0x51d3), 0x11 },
+ { CCI_REG8(0x51d7), 0x13 },
+ { CCI_REG8(0x51db), 0x13 },
+ { CCI_REG8(0x51df), 0x13 },
+ { CCI_REG8(0x51e0), 0x03 },
+ { CCI_REG8(0x51e2), 0x03 },
+ { CCI_REG8(0x51f0), 0x42 },
+ { CCI_REG8(0x51f1), 0x40 },
+ { CCI_REG8(0x51f2), 0x4a },
+ { CCI_REG8(0x51f3), 0x48 },
+ { CCI_REG8(0x5015), 0x73 },
+ { CCI_REG8(0x504a), 0x04 },
+ { CCI_REG8(0x5044), 0x07 },
+ { CCI_REG8(0x5040), 0x03 },
+ { CCI_REG8(0x5135), 0xc4 },
+ { CCI_REG8(0x5136), 0xc5 },
+ { CCI_REG8(0x5166), 0xc4 },
+ { CCI_REG8(0x5196), 0xc4 },
+ { CCI_REG8(0x51c0), 0x10 },
+ { CCI_REG8(0x51c4), 0x10 },
+ { CCI_REG8(0x51c8), 0xa0 },
+ { CCI_REG8(0x51cc), 0xa0 },
+ { CCI_REG8(0x51d0), 0xa1 },
+ { CCI_REG8(0x51d4), 0xa5 },
+ { CCI_REG8(0x51d8), 0xa5 },
+ { CCI_REG8(0x51dc), 0xa5 },
+ { CCI_REG8(0x5200), 0xe4 },
+ { CCI_REG8(0x5209), 0x04 },
+ { CCI_REG8(0x301b), 0x01 },
+ { CCI_REG8(0x3130), 0x01 },
+ { CCI_REG8(0x5013), 0x07 },
+ { CCI_REG8(0x5016), 0x01 },
+ { CCI_REG8(0x501d), 0x50 },
+ { CCI_REG8(0x0350), 0xfe },
+ { CCI_REG8(0x2f03), 0x15 },
+ { CCI_REG8(0xd380), 0x00 },
+ { CCI_REG8(0x3047), 0x7f },
+ { CCI_REG8(0x304d), 0x34 },
+ { CCI_REG8(0x3041), 0x4b },
+ { CCI_REG8(0x3042), 0x2d },
+ { CCI_REG8(0x3056), 0x64 },
+ { CCI_REG8(0x3059), 0x1e },
+ { CCI_REG8(0x305e), 0x10 },
+ { CCI_REG8(0x305f), 0x10 },
+ { CCI_REG8(0x306d), 0x10 },
+ { CCI_REG8(0x306e), 0x0c },
+ { CCI_REG8(0x3064), 0x50 },
+ { CCI_REG8(0x3067), 0x78 },
+ { CCI_REG8(0x3068), 0x4b },
+ { CCI_REG8(0x306a), 0x78 },
+ { CCI_REG8(0x306b), 0x4b },
+ { CCI_REG8(0xd442), 0x3d },
+ { CCI_REG8(0xd443), 0x06 },
+ { CCI_REG8(0xd440), 0x63 },
+ { CCI_REG8(0xd446), 0xb0 },
+ { CCI_REG8(0xd447), 0x60 },
+ { CCI_REG8(0xd448), 0x48 },
+ { CCI_REG8(0xd449), 0x30 },
+ { CCI_REG8(0xd44a), 0x18 },
+ { CCI_REG8(0xd360), 0x03 },
+ { CCI_REG8(0x30ac), 0x10 },
+ { CCI_REG8(0x30ad), 0x10 },
+ { CCI_REG8(0x30ae), 0x10 },
+ { CCI_REG8(0x3040), 0x0b },
+ { CCI_REG8(0x2002), 0x00 },
+ { CCI_REG8(0x2000), 0x08 },
+};
+
+struct hm1246_reg_list {
+ u32 num_of_regs;
+ const struct cci_reg_sequence *regs;
+};
+
+struct hm1246_mode {
+ u32 codes[4];
+ u32 clocks_per_pixel;
+ struct v4l2_rect rect;
+ u32 hts;
+ u32 vts_min;
+ const struct hm1246_reg_list reg_list;
+};
+
+#define FLIP_FORMAT_INDEX(v, h) ((v ? 2 : 0) | (h ? 1 : 0))
+
+/* Get the format code of the mode considering current flip setting. */
+static u32 hm1246_get_format_code(struct hm1246 *hm1246,
+ const struct hm1246_mode *hm1246_mode)
+{
+ return hm1246_mode->codes[FLIP_FORMAT_INDEX(hm1246->vflip_ctrl->val,
+ hm1246->hflip_ctrl->val)];
+}
+
+static const struct hm1246_mode hm1246_modes[] = {
+ {
+ .codes = {
+ [FLIP_FORMAT_INDEX(0, 0)] = MEDIA_BUS_FMT_SBGGR10_1X10,
+ [FLIP_FORMAT_INDEX(0, 1)] = MEDIA_BUS_FMT_SGBRG10_1X10,
+ [FLIP_FORMAT_INDEX(1, 0)] = MEDIA_BUS_FMT_SGRBG10_1X10,
+ [FLIP_FORMAT_INDEX(1, 1)] = MEDIA_BUS_FMT_SRGGB10_1X10,
+ },
+ .clocks_per_pixel = 1,
+ .rect.top = 0,
+ .rect.left = 0,
+ .rect.width = 1296,
+ .rect.height = 976,
+ .hts = 1420,
+ .vts_min = 990,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1296x976_raw),
+ .regs = mode_1296x976_raw,
+ },
+ },
+};
+
+static inline struct hm1246 *to_hm1246(struct v4l2_subdev *sd)
+{
+ return container_of_const(sd, struct hm1246, sd);
+}
+
+static const struct hm1246_mode *
+hm1246_find_mode_by_mbus_code(struct hm1246 *hm1246, u32 code)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE(hm1246_modes); i++) {
+ if (code == hm1246_get_format_code(hm1246, &hm1246_modes[i]))
+ return &hm1246_modes[i];
+ }
+
+ return NULL;
+}
+
+static int hm1246_power_on(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct hm1246 *hm1246 = to_hm1246(sd);
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(hm1246_supply_names),
+ hm1246->supplies);
+ if (ret) {
+ dev_err(hm1246->dev, "failed to enable regulators\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(hm1246->xclk);
+ if (ret) {
+ regulator_bulk_disable(ARRAY_SIZE(hm1246_supply_names),
+ hm1246->supplies);
+ dev_err(hm1246->dev, "failed to enable clock\n");
+ return ret;
+ }
+
+ reset_control_deassert(hm1246->reset);
+
+ /*
+ * XSHUTDOWN to crystal clock oscillation (tcrystal): 650us (typical)
+ * Sample bootstrap pin (tsample): 2000us (maximum)
+ * Built in self test (tbist): 3000us (maximum)
+ */
+ fsleep(6 * USEC_PER_MSEC);
+
+ return 0;
+}
+
+static int hm1246_power_off(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct hm1246 *hm1246 = to_hm1246(sd);
+
+ reset_control_assert(hm1246->reset);
+
+ clk_disable_unprepare(hm1246->xclk);
+
+ regulator_bulk_disable(ARRAY_SIZE(hm1246_supply_names),
+ hm1246->supplies);
+
+ return 0;
+}
+
+static int hm1246_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct hm1246 *hm1246 = to_hm1246(sd);
+
+ if (code->index >= ARRAY_SIZE(hm1246_modes))
+ return -EINVAL;
+
+ code->code = hm1246_get_format_code(hm1246, &hm1246_modes[code->index]);
+
+ return 0;
+}
+
+static int hm1246_enum_frame_size(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct hm1246 *hm1246 = to_hm1246(subdev);
+ const struct hm1246_mode *mode;
+
+ if (fse->index > 0)
+ return -EINVAL;
+
+ mode = hm1246_find_mode_by_mbus_code(hm1246, fse->code);
+ if (!mode)
+ return -EINVAL;
+
+ fse->min_width = mode->rect.width;
+ fse->max_width = mode->rect.width;
+ fse->min_height = mode->rect.height;
+ fse->max_height = mode->rect.height;
+
+ return 0;
+}
+
+static void hm1246_update_pad_format(struct hm1246 *hm1246,
+ const struct hm1246_mode *hm1246_mode,
+ struct v4l2_mbus_framefmt *fmt)
+{
+ fmt->width = hm1246_mode->rect.width;
+ fmt->height = hm1246_mode->rect.height;
+ fmt->code = hm1246_get_format_code(hm1246, hm1246_mode);
+ fmt->field = V4L2_FIELD_NONE;
+ fmt->colorspace = V4L2_COLORSPACE_RAW;
+ fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+ fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ fmt->xfer_func = V4L2_XFER_FUNC_NONE;
+}
+
+static int hm1246_set_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct hm1246 *hm1246 = to_hm1246(sd);
+ struct v4l2_mbus_framefmt *mbus_fmt;
+ struct v4l2_rect *crop;
+ const struct hm1246_mode *mode;
+
+ mode = hm1246_find_mode_by_mbus_code(hm1246, fmt->format.code);
+ if (!mode)
+ mode = &hm1246_modes[0];
+
+ crop = v4l2_subdev_state_get_crop(state, 0);
+ *crop = mode->rect;
+
+ hm1246_update_pad_format(hm1246, mode, &fmt->format);
+ mbus_fmt = v4l2_subdev_state_get_format(state, 0);
+ *mbus_fmt = fmt->format;
+
+ return 0;
+}
+
+static int hm1246_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_selection *sel)
+{
+ const struct v4l2_mbus_framefmt *format;
+ const struct hm1246_mode *mode;
+
+ format = v4l2_subdev_state_get_format(state, 0);
+ mode = v4l2_find_nearest_size(hm1246_modes, ARRAY_SIZE(hm1246_modes),
+ rect.width, rect.height, format->width,
+ format->height);
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP:
+ sel->r = *v4l2_subdev_state_get_crop(state, 0);
+ return 0;
+
+ case V4L2_SEL_TGT_NATIVE_SIZE:
+ sel->r.top = 0;
+ sel->r.left = 0;
+ sel->r.width = HM1246_NATIVE_WIDTH;
+ sel->r.height = HM1246_NATIVE_HEIGHT;
+ return 0;
+
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ sel->r = mode->rect;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int hm1246_init_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct hm1246 *hm1246 = to_hm1246(sd);
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_TRY,
+ .pad = 0,
+ .format = {
+ .code = hm1246_get_format_code(hm1246,
+ &hm1246_modes[0]),
+ .width = hm1246_modes[0].rect.width,
+ .height = hm1246_modes[0].rect.height,
+ },
+ };
+
+ hm1246_set_format(sd, state, &fmt);
+
+ return 0;
+}
+
+static int hm1246_calc_pll(u32 xclk, u32 link_freq, u32 clocks_per_pixel,
+ u8 *pll1, u8 *pll2, u8 *pll3)
+{
+ static const u8 pclk_div_table[] = { 4, 5, 6, 7, 8, 12, 14, 16 };
+ static const u8 sysclk_div_table[] = { 1, 2, 3, 4 };
+ static const u8 post_div_table[] = { 1, 2, 4, 8 };
+ static const int sysclk_pclk_ratio = 3; /* Recommended value */
+ u32 pclk, vco_out;
+ int pclk_div_index, sysclk_div_index, post_div_index;
+ bool sysclk_pclk_ratio_found = false;
+
+ if (link_freq < HM1246_PCLK_MIN || link_freq > HM1246_PCLK_MAX)
+ return -EINVAL;
+
+ /*
+ * In raw mode (1 pixel per clock) the pixel clock is internally
+ * divided by two.
+ */
+ pclk = 2 * link_freq / clocks_per_pixel;
+
+ /* Find suitable PCLK and SYSCLK dividers. */
+ for (pclk_div_index = 0; pclk_div_index < ARRAY_SIZE(pclk_div_table);
+ pclk_div_index++) {
+ for (sysclk_div_index = 0;
+ sysclk_div_index < ARRAY_SIZE(sysclk_div_table);
+ sysclk_div_index++) {
+ if (sysclk_div_table[sysclk_div_index] *
+ sysclk_pclk_ratio ==
+ pclk_div_table[pclk_div_index]) {
+ sysclk_pclk_ratio_found = true;
+ break;
+ }
+ }
+ if (sysclk_pclk_ratio_found)
+ break;
+ }
+
+ if (!sysclk_pclk_ratio_found)
+ return -EINVAL;
+
+ /* Determine an appropriate post divider. */
+ for (post_div_index = 0; post_div_index < ARRAY_SIZE(post_div_table);
+ post_div_index++) {
+ vco_out = pclk * pclk_div_table[pclk_div_index] *
+ post_div_table[post_div_index];
+
+ if (vco_out >= HM1246_PLL_VCO_MIN &&
+ vco_out <= HM1246_PLL_VCO_MAX)
+ break;
+ }
+ if (post_div_index >= ARRAY_SIZE(post_div_table))
+ return -EINVAL;
+
+ /* Find pre-divider and multiplier values. */
+ for (u32 div = DIV_ROUND_UP(xclk, HM1246_PLL_INCLK_MAX);
+ div <= xclk / HM1246_PLL_INCLK_MIN; div++) {
+ u32 multi, multi_h, multi_l, vco;
+
+ multi = DIV_ROUND_CLOSEST_ULL((u64)vco_out * div, xclk);
+ if (multi < HM1246_PLL_MULTI_MIN ||
+ multi > HM1246_PLL_MULTI_MAX)
+ continue;
+
+ multi_h = multi / (HM1246_PLL_MULTI_H_MIN *
+ HM1246_PLL_MULTI_L_MAX) +
+ 2;
+ multi_l = multi / multi_h;
+ vco = div_u64((u64)xclk * multi_h * multi_l, div);
+
+ if (vco != vco_out)
+ continue;
+
+ if (pll1 && pll2 && pll3) {
+ *pll1 = HM1246_PLL1CFG_MULTIPLIER(multi_l - 1);
+ *pll2 = HM1246_PLL2CFG_PRE_DIV(div - 1) |
+ HM1246_PLL2CFG_MULTIPLIER(multi_h - 2);
+ *pll3 = HM1246_PLL3CFG_POST_DIV(post_div_index) |
+ HM1246_PLL3CFG_SYSCLK_DIV(sysclk_div_index) |
+ HM1246_PLL3CFG_PCLK_DIV(pclk_div_index);
+ }
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int hm1246_cci_write_pll(struct hm1246 *hm1246, u8 pll1, u8 pll2,
+ u8 pll3)
+{
+ const struct cci_reg_sequence pll_regs[] = {
+ { HM1246_PLL1CFG_REG, pll1 },
+ { HM1246_PLL2CFG_REG, pll2 },
+ { HM1246_PLL3CFG_REG, pll3 },
+ { HM1246_SBC_CTRL_REG, HM1246_SBC_CTRL_PLL_EN },
+ };
+
+ return cci_multi_reg_write(hm1246->regmap, pll_regs,
+ ARRAY_SIZE(pll_regs), NULL);
+}
+
+static int hm1246_pll_check_locked(struct hm1246 *hm1246)
+{
+ u64 boot_ref2;
+ int ret;
+
+ ret = cci_read(hm1246->regmap, HM1246_SBC_BOOT_REF2_REG, &boot_ref2,
+ NULL);
+ if (ret)
+ return ret;
+
+ return (boot_ref2 & HM1246_SBC_BOOT_REF2_PLL_LOCK) ? 0 : -EIO;
+}
+
+static int hm1246_setup_pll(struct hm1246 *hm1246,
+ const struct hm1246_mode *mode)
+{
+ u8 pll1, pll2, pll3;
+ int ret;
+
+ ret = hm1246_calc_pll(hm1246->xclk_freq, hm1246->link_frequency,
+ mode->clocks_per_pixel, &pll1, &pll2, &pll3);
+ if (ret)
+ return ret;
+
+ ret = hm1246_cci_write_pll(hm1246, pll1, pll2, pll3);
+ if (ret)
+ return ret;
+
+ /* PLL lock time (tpll): 100us (typical) */
+ fsleep(200);
+
+ return hm1246_pll_check_locked(hm1246);
+}
+
+static int hm1246_cci_write_test_pattern(struct hm1246 *hm1246, u8 mode,
+ u16 r, u16 g, u16 b)
+{
+ const struct cci_reg_sequence tpg_enable_regs[] = {
+ { HM1246_TEST_DATA_RED_REG, r },
+ { HM1246_TEST_DATA_GR_REG, g },
+ { HM1246_TEST_DATA_GB_REG, g },
+ { HM1246_TEST_DATA_BLUE_REG, b },
+ { HM1246_TEST_PATTERN_MODE_REG, mode },
+ };
+
+ return cci_multi_reg_write(hm1246->regmap, tpg_enable_regs,
+ ARRAY_SIZE(tpg_enable_regs), NULL);
+}
+
+static int hm1246_test_pattern(struct hm1246 *hm1246, u32 index)
+{
+ static const u16 RGBMIN = 0, RGBMAX = 0x3ff;
+ static const struct tp {
+ int pattern;
+ u16 r, g, b;
+ } tps[] = {
+ /* Disabled */
+ [0] = { .pattern = 0, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN },
+ /* Checkboard pattern */
+ [1] = { .pattern = 0, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN },
+ /* Ramp */
+ [2] = { .pattern = 1, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN },
+ /* Moving ones */
+ [3] = { .pattern = 2, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN },
+ /* Blending color bars */
+ [4] = { .pattern = 3, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN },
+ /* Color bars */
+ [5] = { .pattern = 4, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN },
+ /* Solid white */
+ [6] = { .pattern = 15, .r = RGBMAX, .g = RGBMAX, .b = RGBMAX },
+ /* Solid black */
+ [7] = { .pattern = 15, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN },
+ /* Solid red */
+ [8] = { .pattern = 15, .r = RGBMAX, .g = RGBMIN, .b = RGBMIN },
+ /* Solid green */
+ [9] = { .pattern = 15, .r = RGBMIN, .g = RGBMAX, .b = RGBMIN },
+ /* Solid blue */
+ [10] = { .pattern = 15, .r = RGBMIN, .g = RGBMIN, .b = RGBMAX },
+ };
+ u8 mode;
+
+ if (index >= ARRAY_SIZE(tps))
+ return -EINVAL;
+
+ mode = HM1246_TEST_PATTERN_MODE_MODE(tps[index].pattern);
+ if (index)
+ mode |= HM1246_TEST_PATTERN_MODE_ENABLE;
+
+ return hm1246_cci_write_test_pattern(hm1246, mode, tps[index].r,
+ tps[index].g, tps[index].b);
+}
+
+static int hm1246_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct hm1246 *hm1246 =
+ container_of_const(ctrl->handler, struct hm1246, ctrls);
+ struct v4l2_subdev_state *state;
+ const struct v4l2_mbus_framefmt *format;
+ u32 val;
+ bool needs_cmu_update = true;
+ int ret;
+
+ state = v4l2_subdev_get_locked_active_state(&hm1246->sd);
+ format = v4l2_subdev_state_get_format(state, 0);
+
+ if (ctrl->id == V4L2_CID_VBLANK) {
+ s64 exposure_max;
+
+ exposure_max =
+ format->height + ctrl->val - HM1246_COARSE_INTG_MARGIN;
+ ret = __v4l2_ctrl_modify_range(hm1246->exposure_ctrl,
+ hm1246->exposure_ctrl->minimum,
+ exposure_max,
+ hm1246->exposure_ctrl->step,
+ exposure_max);
+
+ if (ret) {
+ dev_err(hm1246->dev, "exposure ctrl range update failed\n");
+ return ret;
+ }
+ }
+
+ if (!pm_runtime_get_if_active(hm1246->dev))
+ return 0;
+
+ ret = 0;
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ cci_write(hm1246->regmap, HM1246_COARSE_INTG_REG, ctrl->val,
+ &ret);
+ break;
+
+ case V4L2_CID_ANALOGUE_GAIN:
+ cci_write(hm1246->regmap, HM1246_ANALOG_GLOBAL_GAIN_REG,
+ ctrl->val, &ret);
+ break;
+
+ case V4L2_CID_VBLANK:
+ val = format->height + ctrl->val;
+ cci_write(hm1246->regmap, HM1246_FRAME_LENGTH_LINES_REG, val,
+ &ret);
+ break;
+
+ case V4L2_CID_HFLIP:
+ case V4L2_CID_VFLIP:
+ val = 0;
+ if (hm1246->hflip_ctrl->val)
+ val |= HM1246_IMAGE_ORIENTATION_HFLIP;
+ if (hm1246->vflip_ctrl->val)
+ val |= HM1246_IMAGE_ORIENTATION_VFLIP;
+
+ cci_write(hm1246->regmap, HM1246_IMAGE_ORIENTATION_REG, val,
+ &ret);
+ break;
+
+ case V4L2_CID_TEST_PATTERN:
+ ret = hm1246_test_pattern(hm1246, ctrl->val);
+ needs_cmu_update = false;
+ break;
+
+ default:
+ ret = -EINVAL;
+ needs_cmu_update = false;
+ break;
+ }
+
+ if (needs_cmu_update)
+ cci_write(hm1246->regmap, HM1246_CMU_UPDATE_REG, 0, &ret);
+
+ pm_runtime_put(hm1246->dev);
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops hm1246_ctrl_ops = {
+ .s_ctrl = hm1246_set_ctrl,
+};
+
+static int hm1246_identify_module(struct hm1246 *hm1246)
+{
+ u64 model_id;
+ int ret;
+
+ if (hm1246->identified)
+ return 0;
+
+ ret = cci_read(hm1246->regmap, HM1246_MODEL_ID_REG, &model_id, NULL);
+ if (ret)
+ return ret;
+
+ if (model_id != HM1246_MODEL_ID) {
+ dev_err(hm1246->dev, "model id mismatch: 0x%llx!=0x%x\n",
+ model_id, HM1246_MODEL_ID);
+ return -ENXIO;
+ }
+
+ hm1246->identified = true;
+
+ return 0;
+}
+
+static int hm1246_setup_moderegs(struct hm1246 *hm1246,
+ const struct hm1246_mode *mode)
+{
+ const struct hm1246_reg_list *reg_list = &mode->reg_list;
+ const struct cci_reg_sequence modeaw[] = {
+ { HM1246_X_ADDR_START_REG, mode->rect.left },
+ { HM1246_Y_ADDR_START_REG, mode->rect.top },
+ { HM1246_X_ADDR_END_REG, mode->rect.width - 1 },
+ { HM1246_Y_ADDR_END_REG, mode->rect.height - 1 },
+ { HM1246_DWIN_XOFFSET_REG, mode->rect.left },
+ { HM1246_DWIN_YOFFSET_REG, mode->rect.top },
+ { HM1246_DWIN_XSIZE_REG, mode->rect.width },
+ { HM1246_DWIN_YSIZE_REG, mode->rect.height },
+ { HM1246_LINE_LENGTH_PCK_REG, mode->hts },
+ };
+ int ret = 0;
+
+ cci_multi_reg_write(hm1246->regmap, modeaw, ARRAY_SIZE(modeaw), &ret);
+ cci_multi_reg_write(hm1246->regmap, reg_list->regs,
+ reg_list->num_of_regs, &ret);
+
+ return ret;
+}
+
+static int hm1246_setup_bus(struct hm1246 *hm1246)
+{
+ u64 polarity_ctrl = 0, pclk_ctrl = 0;
+ int ret = 0;
+
+ if (hm1246->mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)
+ polarity_ctrl |= HM1246_POLARITY_CTRL_HSYNC;
+
+ if (hm1246->mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)
+ polarity_ctrl |= HM1246_POLARITY_CTRL_VSYNC;
+
+ cci_write(hm1246->regmap, HM1246_POLARITY_CTRL_REG, polarity_ctrl,
+ &ret);
+
+ /*
+ * If the clock output polarity flag PCLK_CTRL[3] is set (high), the
+ * data lines change state on the falling edge of PCLK and should
+ * therefore be sampled on the rising edge.
+ * This is different than described in the data sheet.
+ */
+ if (hm1246->mbus_flags & V4L2_MBUS_PCLK_SAMPLE_RISING)
+ pclk_ctrl |= HM1246_PCLK_CTRL_POL;
+
+ cci_write(hm1246->regmap, HM1246_PCLK_CTRL_REG, pclk_ctrl, &ret);
+
+ return ret;
+}
+
+static int hm1246_enable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct hm1246 *hm1246 = to_hm1246(sd);
+ const struct v4l2_mbus_framefmt *format;
+ const struct hm1246_mode *mode;
+ int ret;
+
+ format = v4l2_subdev_state_get_format(state, 0);
+ mode = v4l2_find_nearest_size(hm1246_modes, ARRAY_SIZE(hm1246_modes),
+ rect.width, rect.height, format->width,
+ format->height);
+
+ ret = pm_runtime_resume_and_get(hm1246->dev);
+ if (ret)
+ return ret;
+
+ ret = hm1246_identify_module(hm1246);
+ if (ret)
+ goto err_rpm_put;
+
+ ret = hm1246_setup_pll(hm1246, mode);
+ if (ret) {
+ dev_err(hm1246->dev, "failed to setup PLL\n");
+ goto err_rpm_put;
+ }
+
+ ret = hm1246_setup_moderegs(hm1246, mode);
+ if (ret)
+ goto err_rpm_put;
+
+ ret = hm1246_setup_bus(hm1246);
+ if (ret)
+ goto err_rpm_put;
+
+ ret = __v4l2_ctrl_handler_setup(&hm1246->ctrls);
+ if (ret) {
+ dev_err(hm1246->dev, "failed to setup v4l2 controls\n");
+ goto err_rpm_put;
+ }
+
+ ret = cci_write(hm1246->regmap, HM1246_MODE_SELECT_REG,
+ HM1246_MODE_SELECT_STREAM, NULL);
+ if (ret)
+ goto err_rpm_put;
+
+ /*
+ * Since mirroring may change the actual pixel format, it must not be
+ * changed during streaming.
+ */
+ __v4l2_ctrl_grab(hm1246->vflip_ctrl, true);
+ __v4l2_ctrl_grab(hm1246->hflip_ctrl, true);
+
+ return 0;
+
+err_rpm_put:
+ pm_runtime_put_autosuspend(hm1246->dev);
+
+ return ret;
+}
+
+static int hm1246_disable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct hm1246 *hm1246 = to_hm1246(sd);
+ int ret;
+
+ ret = cci_write(hm1246->regmap, HM1246_MODE_SELECT_REG,
+ HM1246_MODE_SELECT_STANDBY, NULL);
+
+ __v4l2_ctrl_grab(hm1246->vflip_ctrl, false);
+ __v4l2_ctrl_grab(hm1246->hflip_ctrl, false);
+
+ pm_runtime_put_autosuspend(hm1246->dev);
+
+ return ret;
+}
+
+static const struct v4l2_subdev_video_ops hm1246_video_ops = {
+ .s_stream = v4l2_subdev_s_stream_helper,
+};
+
+static const struct v4l2_subdev_pad_ops hm1246_subdev_pad_ops = {
+ .enum_mbus_code = hm1246_enum_mbus_code,
+ .enum_frame_size = hm1246_enum_frame_size,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = hm1246_set_format,
+ .get_selection = hm1246_get_selection,
+ .enable_streams = hm1246_enable_streams,
+ .disable_streams = hm1246_disable_streams,
+};
+
+static const struct v4l2_subdev_ops hm1246_subdev_ops = {
+ .video = &hm1246_video_ops,
+ .pad = &hm1246_subdev_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops hm1246_internal_ops = {
+ .init_state = hm1246_init_state,
+};
+
+static int hm1246_get_regulators(struct device *dev, struct hm1246 *hm1246)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE(hm1246_supply_names); i++)
+ hm1246->supplies[i].supply = hm1246_supply_names[i];
+
+ return devm_regulator_bulk_get(dev, ARRAY_SIZE(hm1246_supply_names),
+ hm1246->supplies);
+}
+
+static int hm1246_parse_fwnode(struct hm1246 *hm1246)
+{
+ struct fwnode_handle *endpoint;
+ struct v4l2_fwnode_endpoint bus_cfg = {
+ .bus_type = V4L2_MBUS_PARALLEL,
+ };
+ int ret;
+
+ endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(hm1246->dev),
+ 0, 0,
+ FWNODE_GRAPH_ENDPOINT_NEXT);
+
+ ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg);
+ fwnode_handle_put(endpoint);
+ if (ret)
+ return dev_err_probe(hm1246->dev, ret,
+ "parsing endpoint node failed\n");
+
+ hm1246->mbus_flags = bus_cfg.bus.parallel.flags;
+
+ if (bus_cfg.nr_of_link_frequencies > 0)
+ hm1246->link_frequency = bus_cfg.link_frequencies[0];
+
+ v4l2_fwnode_endpoint_free(&bus_cfg);
+
+ if (!hm1246->link_frequency)
+ return dev_err_probe(hm1246->dev, -EINVAL,
+ "one link frequency expected\n");
+
+ return 0;
+}
+
+static int hm1246_init_controls(struct hm1246 *hm1246)
+{
+ const struct hm1246_mode *mode = &hm1246_modes[0];
+ struct v4l2_fwnode_device_properties props;
+ struct v4l2_ctrl_handler *ctrl_hdlr = &hm1246->ctrls;
+ struct v4l2_ctrl *ctrl;
+ s64 pixel_rate, exposure_max, vblank_min, hblank;
+ int ret;
+
+ ret = v4l2_fwnode_device_parse(hm1246->dev, &props);
+ if (ret)
+ return ret;
+
+ v4l2_ctrl_handler_init(ctrl_hdlr, 11);
+
+ hm1246->hflip_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ if (hm1246->hflip_ctrl)
+ hm1246->hflip_ctrl->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+ hm1246->vflip_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+ if (hm1246->vflip_ctrl)
+ hm1246->vflip_ctrl->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+ v4l2_ctrl_cluster(2, &hm1246->hflip_ctrl);
+
+ ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, &hm1246_ctrl_ops,
+ V4L2_CID_LINK_FREQ,
+ 0, 0,
+ &hm1246->link_frequency);
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ pixel_rate = div_u64(hm1246->link_frequency, mode->clocks_per_pixel);
+ ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops,
+ V4L2_CID_PIXEL_RATE,
+ pixel_rate, pixel_rate, 1,
+ pixel_rate);
+
+ vblank_min = mode->vts_min - mode->rect.height;
+ ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops,
+ V4L2_CID_VBLANK, vblank_min,
+ HM1246_VTS_MAX - mode->rect.height,
+ 1, vblank_min);
+
+ hblank = mode->hts - mode->rect.width;
+ ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops,
+ V4L2_CID_HBLANK, hblank, hblank,
+ 1, hblank);
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+ HM1246_ANALOG_GLOBAL_GAIN_MIN,
+ HM1246_ANALOG_GLOBAL_GAIN_MAX,
+ HM1246_ANALOG_GLOBAL_GAIN_STEP,
+ HM1246_ANALOG_GLOBAL_GAIN_MIN);
+
+ exposure_max = mode->vts_min - HM1246_COARSE_INTG_MARGIN;
+ hm1246->exposure_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops,
+ V4L2_CID_EXPOSURE,
+ HM1246_COARSE_INTG_MIN,
+ exposure_max,
+ HM1246_COARSE_INTG_STEP,
+ exposure_max);
+
+ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &hm1246_ctrl_ops,
+ V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(hm1246_test_pattern_menu) - 1,
+ 0, 0, hm1246_test_pattern_menu);
+
+ v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &hm1246_ctrl_ops, &props);
+
+ if (ctrl_hdlr->error) {
+ v4l2_ctrl_handler_free(ctrl_hdlr);
+ return ctrl_hdlr->error;
+ }
+
+ hm1246->sd.ctrl_handler = ctrl_hdlr;
+
+ return 0;
+}
+
+static int hm1246_probe(struct i2c_client *client)
+{
+ struct hm1246 *hm1246;
+ int ret;
+
+ hm1246 = devm_kzalloc(&client->dev, sizeof(*hm1246), GFP_KERNEL);
+ if (!hm1246)
+ return -ENOMEM;
+
+ hm1246->dev = &client->dev;
+
+ ret = hm1246_parse_fwnode(hm1246);
+ if (ret)
+ return ret;
+
+ hm1246->regmap = devm_cci_regmap_init_i2c(client, 16);
+ if (IS_ERR(hm1246->regmap))
+ return dev_err_probe(hm1246->dev, PTR_ERR(hm1246->regmap),
+ "failed to init CCI\n");
+
+ hm1246->xclk = devm_v4l2_sensor_clk_get(hm1246->dev, NULL);
+ if (IS_ERR(hm1246->xclk))
+ return dev_err_probe(hm1246->dev, PTR_ERR(hm1246->xclk),
+ "failed to get xclk\n");
+
+ hm1246->xclk_freq = clk_get_rate(hm1246->xclk);
+ if (hm1246->xclk_freq < HM1246_XCLK_MIN ||
+ hm1246->xclk_freq > HM1246_XCLK_MAX)
+ return dev_err_probe(hm1246->dev, -EINVAL,
+ "xclk frequency out of range: %luHz\n",
+ hm1246->xclk_freq);
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(hm1246_modes); i++) {
+ ret = hm1246_calc_pll(hm1246->xclk_freq, hm1246->link_frequency,
+ hm1246_modes[i].clocks_per_pixel,
+ NULL, NULL, NULL);
+ if (ret)
+ return dev_err_probe(hm1246->dev, ret,
+ "no PLL setup for %lld Hz\n",
+ hm1246->link_frequency);
+ }
+
+ ret = hm1246_get_regulators(hm1246->dev, hm1246);
+ if (ret)
+ return dev_err_probe(hm1246->dev, ret,
+ "failed to get regulators\n");
+
+ hm1246->reset = devm_reset_control_get_optional(hm1246->dev, NULL);
+ if (IS_ERR(hm1246->reset))
+ return dev_err_probe(hm1246->dev, PTR_ERR(hm1246->reset),
+ "failed to get reset\n");
+ reset_control_assert(hm1246->reset);
+
+ v4l2_i2c_subdev_init(&hm1246->sd, client, &hm1246_subdev_ops);
+ hm1246->sd.internal_ops = &hm1246_internal_ops;
+
+ ret = hm1246_init_controls(hm1246);
+ if (ret)
+ return dev_err_probe(hm1246->dev, ret,
+ "failed to init controls\n");
+
+ hm1246->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ hm1246->pad.flags = MEDIA_PAD_FL_SOURCE;
+ hm1246->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+ ret = media_entity_pads_init(&hm1246->sd.entity, 1, &hm1246->pad);
+ if (ret) {
+ dev_err_probe(hm1246->dev, ret, "failed to init media pads\n");
+ goto err_v4l2_ctrl_handler_free;
+ }
+
+ hm1246->sd.state_lock = hm1246->ctrls.lock;
+ ret = v4l2_subdev_init_finalize(&hm1246->sd);
+ if (ret) {
+ dev_err_probe(hm1246->dev, ret, "failed to init v4l2 subdev\n");
+ goto err_media_entity_cleanup;
+ }
+
+ pm_runtime_enable(hm1246->dev);
+ pm_runtime_set_autosuspend_delay(hm1246->dev, 1000);
+ pm_runtime_use_autosuspend(hm1246->dev);
+
+ ret = v4l2_async_register_subdev_sensor(&hm1246->sd);
+ if (ret) {
+ dev_err_probe(hm1246->dev, ret,
+ "failed to register v4l2 subdev\n");
+ goto err_subdev_cleanup;
+ }
+
+ return 0;
+
+err_subdev_cleanup:
+ v4l2_subdev_cleanup(&hm1246->sd);
+ pm_runtime_disable(hm1246->dev);
+ pm_runtime_set_suspended(hm1246->dev);
+
+err_media_entity_cleanup:
+ media_entity_cleanup(&hm1246->sd.entity);
+
+err_v4l2_ctrl_handler_free:
+ v4l2_ctrl_handler_free(&hm1246->ctrls);
+
+ return ret;
+}
+
+static void hm1246_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct hm1246 *hm1246 = to_hm1246(sd);
+
+ v4l2_async_unregister_subdev(&hm1246->sd);
+ v4l2_subdev_cleanup(sd);
+ media_entity_cleanup(&hm1246->sd.entity);
+ v4l2_ctrl_handler_free(&hm1246->ctrls);
+
+ pm_runtime_disable(&client->dev);
+ if (!pm_runtime_status_suspended(&client->dev)) {
+ hm1246_power_off(hm1246->dev);
+ pm_runtime_set_suspended(&client->dev);
+ }
+}
+
+static const struct of_device_id hm1246_of_match[] = {
+ { .compatible = "himax,hm1246" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, hm1246_of_match);
+
+static DEFINE_RUNTIME_DEV_PM_OPS(hm1246_pm_ops,
+ hm1246_power_off, hm1246_power_on, NULL);
+
+static struct i2c_driver hm1246_i2c_driver = {
+ .driver = {
+ .of_match_table = hm1246_of_match,
+ .pm = pm_ptr(&hm1246_pm_ops),
+ .name = "hm1246",
+ },
+ .probe = hm1246_probe,
+ .remove = hm1246_remove,
+};
+module_i2c_driver(hm1246_i2c_driver);
+
+MODULE_DESCRIPTION("Himax HM1246 camera driver");
+MODULE_AUTHOR("Matthias Fend <matthias.fend@emfend.at>");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v7 06/22] RISC-V: Add Smcntrpmf extension parsing
From: sashiko-bot @ 2026-06-22 8:17 UTC (permalink / raw)
To: Atish Patra; +Cc: devicetree, robh, linux-perf-users, conor+dt
In-Reply-To: <20260622-counter_delegation-v7-6-0ba2fd34614e@meta.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Missing architectural dependency validation for `Smcntrpmf` requiring `Sscofpmf`.
- [Low] The commit message contains a confusing, grammatically incorrect sentence that obscures the technical meaning.
--
commit f81e6e1ce235d73d77e6f5d1061ddb41f7aab7b6
Author: Atish Patra <atishp@rivosinc.com>
RISC-V: Add Smcntrpmf extension parsing
This commit adds parsing for the Smcntrpmf extension, allowing the kernel
to detect its presence. The extension enables M-mode to configure privilege
mode filtering for cycle/instret counters.
> Smcntrpmf extension allows M-mode to enable privilege mode filtering
> for cycle/instret counters. However, the cyclecfg/instretcfg CSRs are
> only available only in Ssccfg only Smcntrpmf is present.
[Severity: Low]
This isn't a bug, but there is a typo in the commit message where the word
"only" is repeated three times. Did you mean to say "... are available in
Ssccfg only if Smcntrpmf is present"?
> diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c
> index 3fa0a563fb214..1452521d740a6 100644
> --- a/arch/riscv/kernel/cpufeature.c
> +++ b/arch/riscv/kernel/cpufeature.c
[ ... ]
> @@ -576,6 +576,7 @@ const struct riscv_isa_ext_data riscv_isa_ext[] = {
> __RISCV_ISA_EXT_BUNDLE_VALIDATE(zvksg, riscv_zvksg_bundled_exts, riscv_ext_vector_crypto_validate),
> __RISCV_ISA_EXT_DATA_VALIDATE(zvkt, RISCV_ISA_EXT_ZVKT, riscv_ext_vector_crypto_validate),
> __RISCV_ISA_EXT_DATA(smaia, RISCV_ISA_EXT_SMAIA),
> + __RISCV_ISA_EXT_DATA(smcntrpmf, RISCV_ISA_EXT_SMCNTRPMF),
[Severity: Medium]
Does this extension need a validation callback to enforce its dependency
on Sscofpmf?
According to the RISC-V specification, the Smcntrpmf extension explicitly
depends on the Sscofpmf extension. Using __RISCV_ISA_EXT_DATA instead of
__RISCV_ISA_EXT_DATA_VALIDATE omits these dependency checks.
A malformed device tree claiming smcntrpmf support without the requisite
Sscofpmf infrastructure might bypass validation and lead to an inconsistent
PMU driver state.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com?part=6
^ permalink raw reply
* Re: [PATCH v7 05/22] RISC-V: Define indirect CSR access helpers
From: sashiko-bot @ 2026-06-22 8:17 UTC (permalink / raw)
To: Atish Patra; +Cc: robh, devicetree, linux-perf-users, conor+dt
In-Reply-To: <20260622-counter_delegation-v7-5-0ba2fd34614e@meta.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Macro Variable Shadowing / Hygiene vulnerability in statement expressions
- [Medium] API Asymmetry / Hardcoded Selector in Indirect CSR Access Helpers
--
commit aa0528a3e619974b7113157843897b147ef398e9
Author: Atish Patra <atishp@rivosinc.com>
RISC-V: Define indirect CSR access helpers
The indirect CSR requires multiple instructions to read/write CSR. Add a few
helper functions for ease of usage.
> diff --git a/arch/riscv/include/asm/csr_ind.h b/arch/riscv/include/asm/csr_ind.h
> new file mode 100644
> index 0000000000000..6fd7d44dc640a
> --- /dev/null
> +++ b/arch/riscv/include/asm/csr_ind.h
> @@ -0,0 +1,44 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2024 Rivos Inc.
> + */
> +
> +#ifndef _ASM_RISCV_CSR_IND_H
> +#define _ASM_RISCV_CSR_IND_H
> +
> +#include <linux/irqflags.h>
> +
> +#include <asm/csr.h>
> +
> +#define csr_ind_read(iregcsr, iselbase, iseloff) ({ \
> + unsigned long __value = 0; \
> + unsigned long __flags; \
> + local_irq_save(__flags); \
[Severity: Medium]
Is there a risk of variable shadowing here? Since this macro evaluates caller
arguments inside the statement expression, passing an argument named __flags
or __value could silently shadow these local variables.
For example, if a caller passes a variable named __flags as the iselbase
argument, local_irq_save() would save the IRQ state into the inner shadowed
__flags variable before iselbase is evaluated.
Could these internal variables use more unique namespace prefixes to avoid
collisions?
> + csr_write(CSR_ISELECT, (iselbase) + (iseloff)); \
> + __value = csr_read(iregcsr); \
[Severity: Medium]
Does this hardcoded use of CSR_ISELECT create an issue if callers use other
data registers?
The macro allows specifying the data register via iregcsr, but hardcodes the
selector. If these macros are used with virtualized state registers like
CSR_VSIREG, the macro would write to the host's CSR_ISELECT but then read from
the guest's CSR_VSIREG using an unmodified CSR_VSISELECT.
Would it be safer to either accept the selector register as an explicit
parameter or enforce that iregcsr must be the corresponding data register?
> + local_irq_restore(__flags); \
> + __value; \
> +})
> +
> +#define csr_ind_write(iregcsr, iselbase, iseloff, value) ({ \
> + unsigned long __flags; \
> + local_irq_save(__flags); \
[Severity: Medium]
Similarly, does the local __flags variable here risk shadowing arguments
passed as iselbase, iseloff, or value?
> + csr_write(CSR_ISELECT, (iselbase) + (iseloff)); \
> + csr_write(iregcsr, (value)); \
> + local_irq_restore(__flags); \
> +})
> +
> +#define csr_ind_warl(iregcsr, iselbase, iseloff, warl_val) ({ \
> + unsigned long __old_val = 0, __value = 0; \
> + unsigned long __flags; \
> + local_irq_save(__flags); \
[Severity: Medium]
Does this macro have the same shadowing concerns with __old_val, __value, and
__flags colliding with the evaluated macro arguments?
> + csr_write(CSR_ISELECT, (iselbase) + (iseloff)); \
> + __old_val = csr_read(iregcsr); \
> + csr_write(iregcsr, (warl_val)); \
> + __value = csr_read(iregcsr); \
> + csr_write(iregcsr, __old_val); \
> + local_irq_restore(__flags); \
> + __value; \
> +})
> +
> +#endif
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com?part=5
^ permalink raw reply
* Re: [PATCH v7 09/22] RISC-V: Add Ssccfg/Smcdeleg ISA extension definition and parsing
From: sashiko-bot @ 2026-06-22 8:18 UTC (permalink / raw)
To: Atish Patra; +Cc: robh, conor+dt, linux-perf-users, devicetree
In-Reply-To: <20260622-counter_delegation-v7-9-0ba2fd34614e@meta.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] `riscv_ext_smcdeleg_validate` incorrectly checks for the Supervisor-level extension `SSCSRIND` instead of the Machine-level extension `SMCSRIND`.
- [High] The supervisor-level extension `Ssccfg` improperly mandates the presence of the Machine-level `Smcdeleg` extension and relies on flawed dependency logic, breaking `Ssccfg` in S-mode guests.
--
commit ad250a6901a4cd821647231579298f446a592807
Author: Atish Patra <atishp@rivosinc.com>
RISC-V: Add Ssccfg/Smcdeleg ISA extension definition and parsing
Smcdeleg extension allows the M-mode to delegate selected counters
to S-mode so that it can access those counters and correpsonding
hpmevent CSRs without M-mode. Ssccfg provides access to delegated
counters and new supervisor-level state.
> diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c
> index 1452521d740a6..1fe647e035157 100644
> --- a/arch/riscv/kernel/cpufeature.c
> +++ b/arch/riscv/kernel/cpufeature.c
> @@ -330,6 +330,27 @@ static const unsigned int riscv_a_exts[] = {
> RISCV_ISA_EXT_ZKNE, \
> RISCV_ISA_EXT_ZKNH
>
> +static int riscv_ext_smcdeleg_validate(const struct riscv_isa_ext_data *data,
> + const unsigned long *isa_bitmap)
> +{
> + if (__riscv_isa_extension_available(isa_bitmap, RISCV_ISA_EXT_SSCSRIND) &&
[Severity: High]
Should this validate against the Machine-level extension SMCSRIND instead of
the Supervisor-level SSCSRIND?
Since Smcdeleg is a Machine-mode extension, checking the S-mode equivalent
might cause Smcdeleg to incorrectly fail validation on hardware that exposes
SMCSRIND but not SSCSRIND. It could also erroneously pass if SMCSRIND is
missing.
> + __riscv_isa_extension_available(isa_bitmap, RISCV_ISA_EXT_ZIHPM) &&
> + __riscv_isa_extension_available(isa_bitmap, RISCV_ISA_EXT_ZICNTR))
> + return 0;
> +
> + return -EPROBE_DEFER;
> +}
> +
> +static int riscv_ext_ssccfg_validate(const struct riscv_isa_ext_data *data,
> + const unsigned long *isa_bitmap)
> +{
> + if (!riscv_ext_smcdeleg_validate(data, isa_bitmap) &&
> + __riscv_isa_extension_available(isa_bitmap, RISCV_ISA_EXT_SMCDELEG))
[Severity: High]
Does enforcing a Machine-mode extension dependency (SMCDELEG) here break
Ssccfg in virtualized environments?
In a guest OS, the hypervisor typically exposes S-mode extensions like Ssccfg
but filters out M-mode extensions like Smcdeleg. Enforcing the presence of
SMCDELEG means the guest kernel will silently fail to enable Ssccfg.
Also, is it necessary to call riscv_ext_smcdeleg_validate() directly here?
Reusing the Machine-mode validation logic prevents Ssccfg from independently
checking its own software-visible S-mode dependencies (SSCSRIND, ZIHPM,
and ZICNTR).
> + return 0;
> +
> + return -EPROBE_DEFER;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com?part=9
^ permalink raw reply
* Re: [PATCH 2/3] arm64: dts: qcom: Add HP EliteBook X G2q 14 AI
From: Abel Vesa @ 2026-06-22 8:18 UTC (permalink / raw)
To: Jason Pettit
Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, linux-arm-msm, devicetree, linux-kernel,
Akhil P Oommen, Mahadevan P, Sibi Sankar, Jingyi Wang,
Ananthu C V
In-Reply-To: <20260620-glymur-send-v1-2-fc4a2cfd107c@oss.qualcomm.com>
On 26-06-20 21:50:42, Jason Pettit wrote:
> Add board support for the HP EliteBook X G2q 14" Next Gen AI PC
> (product SKU C4JG0AV, board 8E91), a Snapdragon X2 Elite (Glymur)
> laptop, using the "hp,elitebook-x-g2q" / "qcom,glymur" compatible.
>
> Enabled by this device tree:
>
> - internal eDP panel (samsung,atna33xc20)
> - 2x USB Type-C with DisplayPort alt-mode and USB
> - chassis HDMI output
> - chassis USB-A host port (usb_mp multiport controller)
> - internal eUSB2 host with the Elan fingerprint reader
> - NVMe SSD on PCIe5
> - Wi-Fi and Bluetooth
> - HID-over-I2C keyboard, touchpad, touchscreen; lid switch
> - Adreno GPU and GMU (Freedreno GL on Mesa)
> - audio playback and capture
>
> The HDMI jack is driven by a power-only DisplayPort-to-HDMI LSPCON on
> the usb_2 combo-PHY DP lanes rather than being a third USB-C port; HPD
> is on gpio126. The LSPCON is on an I/O sub-board with no I2C/AUX control
> path, so it is modelled with the generic simple-bridge "parade,ps185hdm"
> compatible used by the in-tree x1e80100 HDMI-bridge boards (the exact
> bridge part is unconfirmed) and it needs CONFIG_DRM_SIMPLE_BRIDGE.
>
> The &gpu/&gmu enable, the audio nodes and &remoteproc_soccp opt into
> glymur.dtsi SoC nodes that are still in-flight; those series are
> declared as prerequisites in the cover letter.
>
> Signed-off-by: Jason Pettit <jason.pettit@oss.qualcomm.com>
> Assisted-by: Claude:claude-opus-4-8
> ---
> arch/arm64/boot/dts/qcom/Makefile | 1 +
> .../boot/dts/qcom/glymur-hp-elitebook-x-g2q.dts | 977 +++++++++++++++++++++
> 2 files changed, 978 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile
> index 6f33c4e2f09c..b10629808b76 100644
> --- a/arch/arm64/boot/dts/qcom/Makefile
> +++ b/arch/arm64/boot/dts/qcom/Makefile
> @@ -16,6 +16,7 @@ dtb-$(CONFIG_ARCH_QCOM) += apq8096sg-db820c.dtb
> dtb-$(CONFIG_ARCH_QCOM) += apq8096-ifc6640.dtb
> dtb-$(CONFIG_ARCH_QCOM) += eliza-mtp.dtb
> dtb-$(CONFIG_ARCH_QCOM) += glymur-crd.dtb
> +dtb-$(CONFIG_ARCH_QCOM) += glymur-hp-elitebook-x-g2q.dtb
> dtb-$(CONFIG_ARCH_QCOM) += hamoa-iot-evk.dtb
>
> hamoa-iot-evk-el2-dtbs := hamoa-iot-evk.dtb x1-el2.dtbo
> diff --git a/arch/arm64/boot/dts/qcom/glymur-hp-elitebook-x-g2q.dts b/arch/arm64/boot/dts/qcom/glymur-hp-elitebook-x-g2q.dts
> new file mode 100644
> index 000000000000..795940a99946
> --- /dev/null
> +++ b/arch/arm64/boot/dts/qcom/glymur-hp-elitebook-x-g2q.dts
> @@ -0,0 +1,977 @@
> +// SPDX-License-Identifier: BSD-3-Clause
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +/dts-v1/;
> +
> +#include "glymur.dtsi"
> +
> +#include "pmcx0102.dtsi" /* SPMI0: SID-2/3 SPMI1: SID-2/3 */
> +#include "pmh0101.dtsi" /* SPMI0: SID-1 */
> +#include "pmh0110-glymur.dtsi" /* SPMI0: SID-5/7 SPMI1: SID-5 */
> +#include "pmh0104-glymur.dtsi" /* SPMI0: SID-8/9 SPMI1: SID-11 */
> +#include "pmk8850.dtsi" /* SPMI0: SID-0 */
> +
> +#include <dt-bindings/gpio/gpio.h>
> +#include <dt-bindings/input/gpio-keys.h>
> +#include <dt-bindings/input/input.h>
> +#include <dt-bindings/interrupt-controller/irq.h>
> +#include <dt-bindings/regulator/qcom,rpmh-regulator.h>
> +#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h>
> +
[...]
> +
> +&usb_0 {
> + status = "okay";
> +};
> +
> +&usb_0_dwc3_hs {
> + remote-endpoint = <&pmic_glink_hs_in>;
> +};
> +
> +&usb_0_hsphy {
> + vdd-supply = <&vreg_l3f_e0_0p91>;
> + vdda12-supply = <&vreg_l4h_e0_1p2>;
No redriver ?
> +
> + status = "okay";
> +};
> +
> +&usb_0_qmpphy {
> + vdda-phy-supply = <&vreg_l4h_e0_1p2>;
> + vdda-pll-supply = <&vreg_l3f_e0_0p91>;
> + refgen-supply = <&vreg_l2f_e0_0p94>;
> +
> + status = "okay";
> +};
> +
> +&usb_0_qmpphy_out {
> + remote-endpoint = <&pmic_glink_ss_in>;
> +};
> +
> +&usb_1 {
> + status = "okay";
> +};
> +
> +&usb_1_dwc3_hs {
> + remote-endpoint = <&pmic_glink_hs_in1>;
> +};
> +
> +&usb_1_hsphy {
> + vdd-supply = <&vreg_l3f_e0_0p91>;
> + vdda12-supply = <&vreg_l4h_e0_1p2>;
No redriver ?
> +
> + status = "okay";
> +};
> +
> +&usb_1_qmpphy {
> + vdda-phy-supply = <&vreg_l4h_e0_1p2>;
> + vdda-pll-supply = <&vreg_l1h_e0_0p94>;
> + refgen-supply = <&vreg_l2f_e0_0p94>;
> +
> + status = "okay";
> +};
> +
> +&usb_1_qmpphy_out {
> + remote-endpoint = <&pmic_glink_ss_in1>;
> +};
> +
> +/*
> + * usb_2 is not exposed as a connector; its combo-PHY's DP lanes drive the
> + * chassis HDMI bridge (see hdmi-bridge).
> + */
> +&usb_2 {
> + maximum-speed = "high-speed";
> +
> + phys = <&usb_2_hsphy>;
> + phy-names = "usb2-phy";
> +
> + status = "okay";
> +};
> +
> +&usb_2_hsphy {
> + vdd-supply = <&vreg_l3f_e0_0p91>;
> + vdda12-supply = <&vreg_l4h_e0_1p2>;
> +
> + status = "okay";
> +};
> +
> +&usb_2_qmpphy {
> + vdda-phy-supply = <&vreg_l4h_e0_1p2>;
> + vdda-pll-supply = <&vreg_l3f_e0_0p91>;
> + refgen-supply = <&vreg_l2f_e0_0p94>;
> +
> + /delete-property/ mode-switch;
> + /delete-property/ orientation-switch;
> +
> + status = "okay";
> +
> + ports {
> + port@0 {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + /delete-node/ endpoint;
> +
> + usb_2_qmpphy_out_dp: endpoint@0 {
> + reg = <0>;
> +
> + data-lanes = <3 2 1 0>;
> + remote-endpoint = <&hdmi_bridge_in>;
> + };
> + };
> + };
> +};
> +
> +/* Internal USB2 host for the Elan fingerprint reader (usb 04f3:0ca8). */
> +&usb_hs {
> + status = "okay";
> +};
> +
> +&usb_hs_phy {
> + vdd-supply = <&vreg_l2h_e0_0p88>;
> + vdda12-supply = <&vreg_l4h_e0_1p2>;
> +
No redriver ?
> + status = "okay";
> +};
> +
> +/*
> + * Multiport USB host: port 0 is the chassis USB-A port; port 1 is an
> + * internal USB HID device.
> + */
> +&usb_mp {
> + status = "okay";
> +};
> +
> +&usb_mp_hsphy0 {
> + vdd-supply = <&vreg_l2h_e0_0p88>;
> + vdda12-supply = <&vreg_l4h_e0_1p2>;
> +
> + status = "okay";
> +};
> +
> +&usb_mp_hsphy1 {
> + vdd-supply = <&vreg_l2h_e0_0p88>;
> + vdda12-supply = <&vreg_l4h_e0_1p2>;
> +
> + status = "okay";
> +};
> +
> +&usb_mp_qmpphy0 {
> + vdda-phy-supply = <&vreg_l4h_e0_1p2>;
> + vdda-pll-supply = <&vreg_l2h_e0_0p88>;
> + refgen-supply = <&vreg_l4f_e1_1p2>;
> +
> + status = "okay";
> +};
> +
> +&usb_mp_qmpphy1 {
> + vdda-phy-supply = <&vreg_l4h_e0_1p2>;
> + vdda-pll-supply = <&vreg_l2h_e0_0p88>;
> + refgen-supply = <&vreg_l4f_e1_1p2>;
> +
> + status = "okay";
> +};
>
> --
> 2.50.1
>
^ permalink raw reply
* Re: [PATCH v7 10/22] dt-bindings: riscv: add Counter delegation ISA extensions description
From: sashiko-bot @ 2026-06-22 8:20 UTC (permalink / raw)
To: Atish Patra; +Cc: linux-perf-users, robh, devicetree, conor+dt
In-Reply-To: <20260622-counter_delegation-v7-10-0ba2fd34614e@meta.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The DT binding and kernel validation logic incorrectly specifies that the machine-level `smcdeleg` extension depends on the supervisor-level `sscsrind` extension instead of the machine-level `smcsrind` extension.
--
commit 42d2ddd2073c82637c3671b249f0ee2a2583f0b7
Author: Atish Patra <atishp@rivosinc.com>
Date: Mon Jun 22 08:04:22 2026 +0000
dt-bindings: riscv: add Counter delegation ISA extensions description
Add description for the Smcdeleg/Ssccfg extension.
> diff --git a/Documentation/devicetree/bindings/riscv/extensions.yaml b/Documentation/devicetree/bindings/riscv/extensions.yaml
> index 2493766e956d5..eef5eeb198d05 100644
> --- a/Documentation/devicetree/bindings/riscv/extensions.yaml
> +++ b/Documentation/devicetree/bindings/riscv/extensions.yaml
[ ... ]
> @@ -1135,6 +1150,32 @@ properties:
> allOf:
> - const: zilsd
> - const: zca
> + # Smcdeleg depends on Sscsrind, Zihpm, Zicntr
> + - if:
> + contains:
> + const: smcdeleg
> + then:
> + allOf:
> + - contains:
> + const: sscsrind
[Severity: Medium]
Does the smcdeleg machine-level extension architecturally depend on the
supervisor-level sscsrind extension, or should it depend on the machine-level
smcsrind extension instead?
According to the RISC-V Privileged ISA, smcdeleg utilizes machine-level
indirect CSR access (smcsrind) to configure counter delegation. By tying it to
sscsrind here, valid device trees featuring smcdeleg and smcsrind (but lacking
sscsrind) will erroneously fail validation.
Additionally, the kernel CPU feature validator in
arch/riscv/kernel/cpufeature.c:riscv_ext_smcdeleg_validate() appears to mirror
this logic and checks for RISCV_ISA_EXT_SSCSRIND. Will the kernel also
silently discard the smcdeleg capability on such platforms and fall back to
slower PMU access methods?
> + - contains:
> + const: zihpm
> + - contains:
> + const: zicntr
> + # Ssccfg depends on Smcdeleg, Sscsrind, Zihpm, Zicntr
> + - if:
> + contains:
> + const: ssccfg
> + then:
> + allOf:
> + - contains:
> + const: smcdeleg
> + - contains:
> + const: sscsrind
> + - contains:
> + const: zihpm
> + - contains:
> + const: zicntr
>
> allOf:
> # Zcf extension does not exist on rv64
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com?part=10
^ permalink raw reply
* [PATCH 0/4] Support partitioned Host1x
From: Mikko Perttunen @ 2026-06-22 8:22 UTC (permalink / raw)
To: Thierry Reding, Jonathan Hunter, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-tegra, dri-devel, devicetree, linux-kernel, Mikko Perttunen
The resources (channels, syncpoints) provided by Host1x can be split
into multiple independent partitions similar to e.g. PCIe VFs, which can
be assigned to virtual machines, non-CPU embedded controllers, or other
bus masters.
Each partition has its own MMIO register region and interrupts through
which the set of channels and syncpoints assigned to it can be accessed.
This set is configured via the hypervisor MMIO region earlier during
boot.
This series adds device tree bindings and support in the driver to
function when Host1x has been partitioned in this way, by making the
driver work without access to the hypervisor region and with a limited
set of channels and syncpoints.
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
Mikko Perttunen (4):
dt-bindings: display: tegra: Make non-vm registers optional
dt-bindings: display: tegra: Add channel/syncpoint range properties
gpu: host1x: Support running without hv/common registers
gpu: host1x: Allow limiting usable channel and syncpoint ranges
.../display/tegra/nvidia,tegra20-host1x.yaml | 52 ++++++++++++++++------
drivers/gpu/host1x/channel.c | 6 +--
drivers/gpu/host1x/dev.c | 52 +++++++++++++++++++---
drivers/gpu/host1x/dev.h | 3 ++
drivers/gpu/host1x/hw/cdma_hw.c | 3 ++
drivers/gpu/host1x/hw/debug_hw_1x06.c | 3 ++
drivers/gpu/host1x/syncpt.c | 21 +++++----
7 files changed, 110 insertions(+), 30 deletions(-)
---
base-commit: 4549871118cf616eecdd2d939f78e3b9e1dddc48
change-id: 20250919-host1x-nohv-071ed7c6ac4f
^ permalink raw reply
* [PATCH 1/4] dt-bindings: display: tegra: Make non-vm registers optional
From: Mikko Perttunen @ 2026-06-22 8:22 UTC (permalink / raw)
To: Thierry Reding, Jonathan Hunter, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-tegra, dri-devel, devicetree, linux-kernel, Mikko Perttunen
In-Reply-To: <20260622-host1x-nohv-v1-0-65bc682a617f@nvidia.com>
Host1x can be used without accessing the hypervisor or common
registers. Adjust the bindings to make them optional.
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
.../display/tegra/nvidia,tegra20-host1x.yaml | 40 ++++++++++++++--------
1 file changed, 26 insertions(+), 14 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
index 3563378a01af..ffd0ed857d28 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
@@ -145,14 +145,20 @@ allOf:
then:
properties:
reg-names:
- items:
- - const: hypervisor
- - const: vm
+ oneOf:
+ - items:
+ - const: hypervisor
+ - const: vm
+ - items:
+ - const: vm
reg:
- items:
- - description: region used by the hypervisor
- - description: region assigned to the virtual machine
+ oneOf:
+ - items:
+ - description: region used by the hypervisor
+ - description: region assigned to the virtual machine
+ - items:
+ - description: region assigned to the virtual machine
resets:
maxItems: 1
@@ -195,16 +201,22 @@ allOf:
then:
properties:
reg-names:
- items:
- - const: common
- - const: hypervisor
- - const: vm
+ oneOf:
+ - items:
+ - const: common
+ - const: hypervisor
+ - const: vm
+ - items:
+ - const: vm
reg:
- items:
- - description: region used by host1x server
- - description: region used by the hypervisor
- - description: region assigned to the virtual machine
+ oneOf:
+ - items:
+ - description: region used by host1x server
+ - description: region used by the hypervisor
+ - description: region assigned to the virtual machine
+ - items:
+ - description: region assigned to the virtual machine
interrupts:
items:
--
2.53.0
^ permalink raw reply related
* [PATCH 2/4] dt-bindings: display: tegra: Add channel/syncpoint range properties
From: Mikko Perttunen @ 2026-06-22 8:22 UTC (permalink / raw)
To: Thierry Reding, Jonathan Hunter, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-tegra, dri-devel, devicetree, linux-kernel, Mikko Perttunen
In-Reply-To: <20260622-host1x-nohv-v1-0-65bc682a617f@nvidia.com>
Channels and syncpoints available may be limited when other system
components are using them. Add properties nvidia,channels and
nvidia,syncpoints to limit the range of usable channels and/or
syncpoints.
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
.../bindings/display/tegra/nvidia,tegra20-host1x.yaml | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
index ffd0ed857d28..7a543b8b2f3d 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
@@ -96,6 +96,18 @@ properties:
items:
- description: phandle to the HEG or core power domain
+ nvidia,channels:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 2
+ maxItems: 2
+ description: Describes usable channel range as a <base count> tuple.
+
+ nvidia,syncpoints:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 2
+ maxItems: 2
+ description: Describes writable syncpoint range as a <base count> tuple.
+
required:
- compatible
- interrupts
--
2.53.0
^ permalink raw reply related
* [PATCH 3/4] gpu: host1x: Support running without hv/common registers
From: Mikko Perttunen @ 2026-06-22 8:22 UTC (permalink / raw)
To: Thierry Reding, Jonathan Hunter, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-tegra, dri-devel, devicetree, linux-kernel, Mikko Perttunen
In-Reply-To: <20260622-host1x-nohv-v1-0-65bc682a617f@nvidia.com>
Host1x can be used without access to the hypervisor or common regions
when they are configured by other components in the system. Add support
for running without access to them.
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
drivers/gpu/host1x/dev.c | 15 ++++++++++-----
drivers/gpu/host1x/hw/cdma_hw.c | 3 +++
drivers/gpu/host1x/hw/debug_hw_1x06.c | 3 +++
3 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index 3f475f0e6545..545fa2e3f180 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -305,7 +305,7 @@ static void host1x_setup_virtualization_tables(struct host1x *host)
const struct host1x_info *info = host->info;
unsigned int i;
- if (!info->has_hypervisor)
+ if (!host->hv_regs)
return;
for (i = 0; i < info->num_sid_entries; i++) {
@@ -532,15 +532,20 @@ static int host1x_probe(struct platform_device *pdev)
host->info = of_device_get_match_data(&pdev->dev);
if (host->info->has_hypervisor) {
+ struct resource *res;
+
host->regs = devm_platform_ioremap_resource_byname(pdev, "vm");
if (IS_ERR(host->regs))
return PTR_ERR(host->regs);
- host->hv_regs = devm_platform_ioremap_resource_byname(pdev, "hypervisor");
- if (IS_ERR(host->hv_regs))
- return PTR_ERR(host->hv_regs);
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hypervisor");
+ if (res) {
+ host->hv_regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(host->hv_regs))
+ return PTR_ERR(host->hv_regs);
+ }
- if (host->info->has_common) {
+ if (res && host->info->has_common) {
host->common_regs = devm_platform_ioremap_resource_byname(pdev, "common");
if (IS_ERR(host->common_regs))
return PTR_ERR(host->common_regs);
diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c
index 3f3f0018eee0..85f7e5633555 100644
--- a/drivers/gpu/host1x/hw/cdma_hw.c
+++ b/drivers/gpu/host1x/hw/cdma_hw.c
@@ -253,6 +253,9 @@ static void timeout_release_mlock(struct host1x_cdma *cdma)
struct host1x *host1x = cdma_to_host1x(cdma);
u32 offset;
+ if (!host1x->common_regs)
+ return;
+
switch (ch->client->class) {
case HOST1X_CLASS_NVJPG1:
offset = HOST1X_COMMON_NVJPG1_MLOCK;
diff --git a/drivers/gpu/host1x/hw/debug_hw_1x06.c b/drivers/gpu/host1x/hw/debug_hw_1x06.c
index 9d0667879a19..324e76436f7b 100644
--- a/drivers/gpu/host1x/hw/debug_hw_1x06.c
+++ b/drivers/gpu/host1x/hw/debug_hw_1x06.c
@@ -87,6 +87,9 @@ static void host1x_debug_show_channel_fifo(struct host1x *host,
host1x_debug_output(o, "CMDFIFO_RDATA %08x\n", val);
#if HOST1X_HW <= 6
+ if (!host->hv_regs)
+ return;
+
/* Peek pointer values are invalid during SLCG, so disable it */
host1x_hypervisor_writel(host, 0x1, HOST1X_HV_ICG_EN_OVERRIDE);
--
2.53.0
^ permalink raw reply related
* [PATCH 4/4] gpu: host1x: Allow limiting usable channel and syncpoint ranges
From: Mikko Perttunen @ 2026-06-22 8:22 UTC (permalink / raw)
To: Thierry Reding, Jonathan Hunter, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-tegra, dri-devel, devicetree, linux-kernel, Mikko Perttunen
In-Reply-To: <20260622-host1x-nohv-v1-0-65bc682a617f@nvidia.com>
Allow specifying usable channel, and allocatable syncpoint ranges
through the device tree. This is necessary when Host1x resources
are shared between multiple operating systems or independent hardware
units and Linux has limited access to them.
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
drivers/gpu/host1x/channel.c | 6 +++---
drivers/gpu/host1x/dev.c | 37 +++++++++++++++++++++++++++++++++++++
drivers/gpu/host1x/dev.h | 3 +++
drivers/gpu/host1x/syncpt.c | 21 +++++++++++++--------
4 files changed, 56 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/host1x/channel.c b/drivers/gpu/host1x/channel.c
index ca5d0f51cf7d..38fcdda8388a 100644
--- a/drivers/gpu/host1x/channel.c
+++ b/drivers/gpu/host1x/channel.c
@@ -121,13 +121,13 @@ EXPORT_SYMBOL(host1x_channel_put);
static struct host1x_channel *acquire_unused_channel(struct host1x *host)
{
struct host1x_channel_list *chlist = &host->channel_list;
- unsigned int max_channels = host->info->nb_channels;
unsigned int index;
mutex_lock(&chlist->lock);
- index = find_first_zero_bit(chlist->allocated_channels, max_channels);
- if (index >= max_channels) {
+ index = find_next_zero_bit(chlist->allocated_channels,
+ host->channel_end, host->channel_base);
+ if (index >= host->channel_end) {
mutex_unlock(&chlist->lock);
dev_err(host->dev, "failed to find free channel\n");
return NULL;
diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index 545fa2e3f180..7103f018cb1b 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -331,6 +331,39 @@ static void host1x_setup_virtualization_tables(struct host1x *host)
}
}
+static int host1x_get_assigned_resources(struct host1x *host)
+{
+ struct device_node *np = host->dev->of_node;
+ u32 vals[2];
+ int err;
+
+ err = of_property_read_u32_array(np, "nvidia,channels", vals, 2);
+ if (err == 0) {
+ host->channel_base = vals[0];
+ host->channel_end = vals[0] + vals[1];
+ } else if (err == -EINVAL) {
+ host->channel_base = 0;
+ host->channel_end = host->info->nb_channels;
+ } else {
+ dev_err(host->dev, "invalid nvidia,channels property: %d\n", err);
+ return err;
+ }
+
+ err = of_property_read_u32_array(np, "nvidia,syncpoints", vals, 2);
+ if (err == 0) {
+ host->syncpt_base = vals[0];
+ host->syncpt_end = vals[0] + vals[1];
+ } else if (err == -EINVAL) {
+ host->syncpt_base = 0;
+ host->syncpt_end = host->info->nb_pts;
+ } else {
+ dev_err(host->dev, "invalid nvidia,syncpoints property: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
static bool host1x_wants_iommu(struct host1x *host1x)
{
/* Our IOMMU usage policy doesn't currently play well with GART */
@@ -602,6 +635,10 @@ static int host1x_probe(struct platform_device *pdev)
if (IS_ERR(host->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(host->clk), "failed to get clock\n");
+ err = host1x_get_assigned_resources(host);
+ if (err)
+ return err;
+
err = host1x_get_resets(host);
if (err)
return err;
diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h
index ef44618ed88a..89f1fc838a1c 100644
--- a/drivers/gpu/host1x/dev.h
+++ b/drivers/gpu/host1x/dev.h
@@ -141,6 +141,9 @@ struct host1x {
struct reset_control_bulk_data resets[2];
unsigned int nresets;
+ unsigned int syncpt_base, syncpt_end;
+ unsigned int channel_base, channel_end;
+
struct iommu_group *group;
struct iommu_domain *domain;
struct iova_domain iova;
diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c
index acc7d82e0585..fe27a386cc0c 100644
--- a/drivers/gpu/host1x/syncpt.c
+++ b/drivers/gpu/host1x/syncpt.c
@@ -59,7 +59,7 @@ struct host1x_syncpt *host1x_syncpt_alloc(struct host1x *host,
unsigned long flags,
const char *name)
{
- struct host1x_syncpt *sp = host->syncpt;
+ struct host1x_syncpt *sp = host->syncpt + host->syncpt_base;
char *full_name;
unsigned int i;
@@ -68,10 +68,10 @@ struct host1x_syncpt *host1x_syncpt_alloc(struct host1x *host,
mutex_lock(&host->syncpt_mutex);
- for (i = 0; i < host->info->nb_pts && kref_read(&sp->ref); i++, sp++)
+ for (i = host->syncpt_base; i < host->syncpt_end && kref_read(&sp->ref); i++, sp++)
;
- if (i >= host->info->nb_pts)
+ if (i >= host->syncpt_end)
goto unlock;
if (flags & HOST1X_SYNCPT_HAS_BASE) {
@@ -138,7 +138,7 @@ void host1x_syncpt_restore(struct host1x *host)
struct host1x_syncpt *sp_base = host->syncpt;
unsigned int i;
- for (i = 0; i < host1x_syncpt_nb_pts(host); i++) {
+ for (i = host->syncpt_base; i < host->syncpt_end; i++) {
/*
* Unassign syncpt from channels for purposes of Tegra186
* syncpoint protection. This prevents any channel from
@@ -296,6 +296,9 @@ int host1x_syncpt_init(struct host1x *host)
for (i = 0; i < host->info->nb_pts; i++) {
syncpt[i].id = i;
syncpt[i].host = host;
+
+ /* Default to client managed for syncpoints not owned by us */
+ syncpt[i].client_managed = true;
}
for (i = 0; i < host->info->nb_bases; i++)
@@ -305,10 +308,12 @@ int host1x_syncpt_init(struct host1x *host)
host->syncpt = syncpt;
host->bases = bases;
- /* Allocate sync point to use for clearing waits for expired fences */
- host->nop_sp = host1x_syncpt_alloc(host, 0, "reserved-nop");
- if (!host->nop_sp)
- return -ENOMEM;
+ /* Prevent syncpoint 0 from being allocated by users */
+ if (host->syncpt_base == 0) {
+ host->nop_sp = host1x_syncpt_alloc(host, 0, "reserved-nop");
+ if (!host->nop_sp)
+ return -ENOMEM;
+ }
if (host->info->reserve_vblank_syncpts) {
kref_init(&host->syncpt[26].ref);
--
2.53.0
^ permalink raw reply related
* Re: [PATCH] arm64: dts: qcom: kodiak: Elite-ify LPASS macros
From: Luca Weiss @ 2026-06-22 8:23 UTC (permalink / raw)
To: Konrad Dybcio, Luca Weiss, Bjorn Andersson, Konrad Dybcio,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
cros-qcom-dts-watchers
Cc: ~postmarketos/upstreaming, phone-devel, linux-arm-msm, devicetree,
linux-kernel
In-Reply-To: <b39e4b4f-c166-48e1-901c-51694cf8172b@oss.qualcomm.com>
On Wed Jun 17, 2026 at 12:05 PM CEST, Konrad Dybcio wrote:
> On 5/22/26 4:46 PM, Luca Weiss wrote:
>> Due to initial kodiak/sc7280 bringup being done for Chrome platforms,
>> some Chrome-specific bits still remain in kodiak.dtsi, like the clocks
>> and power-domains for the LPASS RX/TX/WSA/VA macros.
>>
>> Move them to sc7280-chrome-common.dtsi and put Elite (q6afecc)
>> equivalents in its place. The qcs6490-audioreach.dtsi file can also drop
>> deletion of power-domains properties then.
>>
>> This follows previous commits moving Chrome-specific configuration to
>> the correct file, leaving kodiak.dtsi for Elite and
>> qcs6490-audioreach.dtsi for AudioReach.
>>
>> No functional change intended. The clock-output-names property will now
>> exist for both Chrome and AudioReach devices but this shouldn't have any
>> relevant effect. And WSA macro clocks weren't added to Chrome because I
>> don't believe this would've ever worked given it already referenced
>> q6afecc and the nodes were originally added during AudioReach bringup.
>
> I think it's better to keep them, if only to make sure that the result
> of dtx_diff isn't outside the expectation of a commit that claims to
> only reshuffle data for these platforms
I documented this in the commit message for this purpose.
But I'd argue not putting (likely incorrect) wsa_macro overrides there
is the better thing to do. Probably somebody should've taken care of the
Chrome platforms when adding the wsa nodes in the first place.
>
> It looks OK as-is for Elite and Audioreach platforms (for the record, I
> checked rb3gen2 and FP5)
Thanks!
Regards
Luca
^ permalink raw reply
* Re: [PATCH v7 01/22] RISC-V: perf: fix resource cleanup on driver probe failure
From: sashiko-bot @ 2026-06-22 8:24 UTC (permalink / raw)
To: Atish Patra; +Cc: linux-perf-users, robh, conor+dt, devicetree
In-Reply-To: <20260622-counter_delegation-v7-1-0ba2fd34614e@meta.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Premature PMU registration exposes the PMU to userspace before all initialization steps are complete, risking a Use-After-Free in the error path.
- [High] Global flag `riscv_pmu_use_irq` is not cleared when IRQ setup fails, causing a kernel panic during CPU hotplug initialization.
- [High] Probe failure leaves a dangling PMU snapshot physical page configured in the firmware for the boot CPU, leading to UAF and kernel memory corruption.
- [High] The CPU PM notifier is unregistered on error paths, but PMU structures are freed immediately without waiting for concurrent readers, leading to Use-After-Free.
--
commit e8a230ead53ac1c197ed8d75659a624a38133975
Author: Atish Patra <atishp@meta.com>
RISC-V: perf: fix resource cleanup on driver probe failure
This commit reorganizes the error paths in pmu_sbi_device_probe() to
fix several resource cleanups. It ensures previously registered PMUs
and per-cpu IRQs are released appropriately on failure.
> diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c
> index 385af5e6e6d0d..8753007cc57ec 100644
> --- a/drivers/perf/riscv_pmu_sbi.c
> +++ b/drivers/perf/riscv_pmu_sbi.c
[ ... ]
> @@ -1452,6 +1454,7 @@ static int pmu_sbi_device_probe(struct platform_device *pdev)
> pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
> pmu->pmu.capabilities |= PERF_PMU_CAP_NO_EXCLUDE;
> }
> + irq_requested = (ret == 0);
[Severity: High]
This is a pre-existing issue, but when IRQ setup fails in
pmu_sbi_setup_irqs(), the global flag riscv_pmu_use_irq is not cleared.
Since the driver continues probing even if ret < 0 here, could this lead to a
kernel panic later during CPU hotplug? When pmu_sbi_starting_cpu() runs, it
will check riscv_pmu_use_irq and call enable_percpu_irq(riscv_pmu_irq, ...).
Because the IRQ was never successfully requested, its desc->action is NULL,
which would cause a panic when handle_percpu_devid_irq() dereferences it.
[ ... ]
> @@ -1515,9 +1518,17 @@ static int pmu_sbi_device_probe(struct platform_device *pdev)
> return 0;
>
> out_unregister:
> + perf_pmu_unregister(&pmu->pmu);
[Severity: Critical]
This isn't a bug introduced by this patch, but does ignoring the return value
of perf_pmu_unregister() here risk a use-after-free?
Because the PMU was registered earlier in the probe sequence, userspace might
have already opened a perf event by the time we hit an error (such as failing
to add the CPU hotplug instance).
If an event is active, perf_pmu_unregister() will return -EBUSY and leave the
PMU fully registered.
kernel/events/core.c:perf_pmu_unregister() {
...
if (pmu->event_unmapped && !pmu_empty(pmu)) {
...
return -EBUSY;
}
...
}
Because the error path ignores the return value and proceeds to free the PMU
structures, userspace could continue interacting with the active perf events,
triggering a use-after-free of the PMU data structures.
> +
> +out_destroy:
> riscv_pmu_destroy(pmu);
[Severity: High]
This is a pre-existing issue, but does this error path properly handle PMU
snapshot cleanup on the boot CPU?
In pmu_sbi_device_probe(), the snapshot is manually configured for the boot
CPU using an SBI ecall inside pmu_sbi_snapshot_setup(), after which
preemption is enabled.
If a subsequent step fails, riscv_pmu_destroy() calls
pmu_sbi_snapshot_disable() which only disables the snapshot on the current
CPU. If the task migrated due to preemption, the snapshot would be disabled
on the wrong CPU.
This would leave the boot CPU with a snapshot pointing to a physical page
that gets freed shortly after, allowing the firmware to silently overwrite
freed kernel memory.
[Severity: High]
This is also a pre-existing issue, but could this cause a use-after-free
during CPU PM transitions?
riscv_pmu_destroy() unregisters the CPU PM notifier using
riscv_pm_pmu_unregister(). Since cpu_pm_notifier is a raw notifier chain,
concurrent traversals during cpu_pm_enter() do not use RCU and do not block
unregistration.
Because the driver immediately calls kfree(pmu) and free_percpu() after this,
another CPU entering idle and traversing the notifier chain could dereference
the freed notifier_block and pmu struct.
> + if (irq_requested)
> + free_percpu_irq(riscv_pmu_irq, pmu->hw_events);
>
> out_free:
> + free_percpu(pmu->hw_events);
> + kfree(pmu_ctr_list);
> + pmu_ctr_list = NULL;
> kfree(pmu);
> return ret;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com?part=1
^ permalink raw reply
* Re: [PATCH v7 12/22] RISC-V: perf: Modify the counter discovery mechanism
From: sashiko-bot @ 2026-06-22 8:24 UTC (permalink / raw)
To: Atish Patra; +Cc: linux-perf-users, robh, conor+dt, devicetree
In-Reply-To: <20260622-counter_delegation-v7-12-0ba2fd34614e@meta.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Hardware counter physical indices and SBI logical counter indices for firmware counters can overlap, causing array overwrites and state clobbering in `pmu_ctr_list` and the per-CPU `events` array.
- [Medium] Firmware counters are not stopped in `rvpmu_sbi_stop_all()` when counter delegation is disabled, leaving them running unexpectedly.
Pre-existing issues:
- [High] `cmask` and `firmware_cmask` are defined as `unsigned long` and accessed via `BIT(i)` and `for_each_set_bit()`, causing shift overflow and out-of-bounds memory reads on 32-bit architectures.
- [High] Missing `.remove` hook combined with global `pmu_ctr_list` overwrite causes a memory leak and a subsequent NULL pointer dereference on re-probe failure.
--
commit 98634f15c972a5ad2282dff7c8f2da3bfc47efa8
Author: Atish Patra <atishp@rivosinc.com>
RISC-V: perf: Modify the counter discovery mechanism
If both counter delegation and SBI PMU are present, counter delegation
is used for hardware counters while the SBI PMU is used for firmware
counters. The driver is updated to probe via SBI PMU to distinguish
the firmware counters and cache the discovery info.
> diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c
> index 80c7249ee1662..d44f47613aa1a 100644
> --- a/drivers/perf/riscv_pmu_sbi.c
> +++ b/drivers/perf/riscv_pmu_sbi.c
[ ... ]
> @@ -101,6 +116,8 @@ static unsigned int riscv_pmu_irq;
>
> /* Cache the available counters in a bitmask */
> static unsigned long cmask;
> +/* Cache the available firmware counters in another bitmask */
> +static unsigned long firmware_cmask;
[Severity: High]
This is a pre-existing issue, but on 32-bit architectures (RV32), unsigned
long is 32 bits wide, while RISCV_MAX_COUNTERS is 64.
Does this cause shift overflows when accessed via BIT(i) where i can reach
up to 63?
Additionally, does using for_each_set_bit() with this 32-bit mask cause
out-of-bounds memory reads, as the macro expects an array large enough for
64 bits?
[ ... ]
> @@ -868,34 +885,38 @@ static int rvpmu_sbi_find_num_ctrs(void)
[ ... ]
> -static int rvpmu_sbi_get_ctrinfo(int nctr, unsigned long *mask)
> +static int rvpmu_sbi_get_ctrinfo(u32 nsbi_ctr, u32 *num_fw_ctr, u32 *num_hw_ctr)
> {
[ ... ]
> - for (i = 0; i < nctr; i++) {
> + for (i = 0; i < nsbi_ctr; i++) {
> ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_GET_INFO, i, 0, 0, 0, 0, 0);
> if (ret.error)
> /* The logical counter ids are not expected to be contiguous */
> continue;
>
> - *mask |= BIT(i);
> -
> cinfo.value = ret.value;
> - if (cinfo.type == SBI_PMU_CTR_TYPE_FW)
> - num_fw_ctr++;
> - else
> - num_hw_ctr++;
> - pmu_ctr_list[i].value = cinfo.value;
> + if (cinfo.type == SBI_PMU_CTR_TYPE_FW) {
> + /* Track firmware counters in a different mask */
> + firmware_cmask |= BIT(i);
> + pmu_ctr_list[i].value = cinfo.value;
[Severity: High]
Can the hardware counter physical indices and the SBI logical counter
indices overlap, causing array overwrites in pmu_ctr_list?
When both counter delegation and the SBI PMU are available, pmu_ctr_list
is first populated using hardware physical CSR indices (0-31) in
rvpmu_deleg_find_ctrs().
Then, rvpmu_sbi_get_ctrinfo() populates firmware counters using the SBI
logical index i. Because the SBI specification allows logical indices to
start at 0, an SBI implementation might assign logical indices to firmware
counters that overlap with the physical CSR indices.
Could this silently overwrite the hardware counter definitions in
pmu_ctr_list[i], and later corrupt the active cpuc->events[idx] array when
both types of counters share overlapping indices?
[Severity: Medium]
Does splitting firmware counters into firmware_cmask and intentionally
excluding them from cmask leave firmware counters running unexpectedly?
In rvpmu_sbi_stop_all(), the code only stops counters present in pmu->cmask:
static inline void rvpmu_sbi_stop_all(struct riscv_pmu *pmu)
{
...
sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP,
0, pmu->cmask, SBI_PMU_STOP_FLAG_RESET, 0, 0, 0);
}
When counter delegation is disabled, the fallback path calls
rvpmu_sbi_stop_all(pmu), which now exclusively stops hardware counters.
Could this leave firmware counters running across CPU hotplug and PMU
initialization, potentially leading to stale counts and unexpected overflows?
> + *num_fw_ctr = *num_fw_ctr + 1;
> + } else if (cinfo.type == SBI_PMU_CTR_TYPE_HW &&
> + !riscv_pmu_cdeleg_available_boot()) {
> + *num_hw_ctr = *num_hw_ctr + 1;
> + cmask |= BIT(i);
> + pmu_ctr_list[i].value = cinfo.value;
> + }
> }
[ ... ]
> @@ -1159,16 +1180,48 @@ static void rvpmu_ctr_stop(struct perf_event *event, unsigned long flag)
[ ... ]
> -static int rvpmu_find_num_ctrs(void)
> +static int rvpmu_find_ctrs(void)
> {
> - return rvpmu_sbi_find_num_ctrs();
> - /* TODO: Counter delegation implementation */
> -}
> + int num_sbi_counters = 0;
> + u32 num_deleg_counters = 0;
> + u32 num_hw_ctr = 0, num_fw_ctr = 0, num_ctr = 0;
> + /*
> + * We don't know how many firmware counters are available. Just allocate
> + * for maximum counters the driver can support. The default is 64 anyways.
> + */
> + pmu_ctr_list = kcalloc(RISCV_MAX_COUNTERS, sizeof(*pmu_ctr_list),
> + GFP_KERNEL);
[Severity: High]
This is a pre-existing issue, but does missing a .remove hook combined with
this global pmu_ctr_list overwrite cause a memory leak and subsequent NULL
pointer dereference on re-probe failure?
The platform driver lacks a .remove callback and does not set
suppress_bind_attrs, meaning it can be unbound via sysfs, leaving the PMU
registered and active.
If the driver is rebound, rvpmu_device_probe() executes again and calls
rvpmu_find_ctrs(), which unconditionally allocates pmu_ctr_list via kcalloc,
overwriting the global pointer and leaking the original memory.
When perf_pmu_register() then fails because the PMU is already registered,
the error path in rvpmu_device_probe() calls kfree(pmu_ctr_list) and sets it
to NULL.
Could the still-active PMU from the first probe then dereference this NULL
pointer during any subsequent perf event operations?
> + if (!pmu_ctr_list)
> + return -ENOMEM;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622-counter_delegation-v7-0-0ba2fd34614e@meta.com?part=12
^ permalink raw reply
* Re: [PATCH 0/5] Shikra: Add DT support for ice, rng and qce
From: Bartosz Golaszewski @ 2026-06-22 8:25 UTC (permalink / raw)
To: Eric Biggers
Cc: Herbert Xu, David S. Miller, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Bjorn Andersson, Vinod Koul, Thara Gopinath,
Konrad Dybcio, Frank Li, Andy Gross, Harshal Dev, linux-arm-msm,
linux-crypto, devicetree, linux-kernel, dmaengine, Kuldeep Singh
In-Reply-To: <20260619164506.GA3223@sol>
On Fri, 19 Jun 2026 18:45:06 +0200, Eric Biggers <ebiggers@kernel.org> said:
> On Fri, Jun 19, 2026 at 02:13:28PM +0530, Kuldeep Singh wrote:
>> On 21-05-2026 18:47, Kuldeep Singh wrote:
>> > This patchseries attempt to enable sdhc-ice, rng and qce on shikra
>> > platform similar to other platforms.
>> >
>> > Previously, the 3 dt-bindigs/DT changes were sent as individual series
>> > and with feedback received, clubbed them together as all belong to same
>> > crypto subsystem.
>> >
>> > Here's link to old patchsets.
>> > QCE: https://lore.kernel.org/lkml/20260515-shikra_qcrypto-v1-0-80f07b345c29@oss.qualcomm.com/
>>
>> Hi Eric,
>>
>> As selftests issues for QCE are now fixed[1], so shikra series should be
>> good to proceed? as your concerns[2] are now addressed.
>> I am waiting for merge window to end and will send next rev post that.
>>
>> [1]
>> https://lore.kernel.org/linux-arm-msm/20260617-qce-fix-self-tests-v3-0-ecc2b4dedcfd@oss.qualcomm.com/
>> [2] https://lore.kernel.org/lkml/20260522024912.GC5937@quark/
>
> If you think that then it sounds like you need to read what I actually
> said. The fixes are appreciated but don't change the big picture.
>
> - Eric
>
Eric,
I mentioned it in another thread[1]. This series is not adding any new features
to the QCE driver, it describes the hardware. The SoC *does have* this IP and
no matter the state of the support in the kernel, there's nothing wrong in
extending the existing bindings and adding new dts nodes.
Thanks,
Bartosz
[1] https://lore.kernel.org/all/CAMRc=MfY-tmMCdw9FVBgfkX-FvB5Nx2X06S023GhASenSCQSNA@mail.gmail.com/
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox