diff options
Diffstat (limited to 'drivers')
149 files changed, 6614 insertions, 1065 deletions
diff --git a/drivers/acpi/numa/srat.c b/drivers/acpi/numa/srat.c index 0a725e46d017..53816dfab645 100644 --- a/drivers/acpi/numa/srat.c +++ b/drivers/acpi/numa/srat.c @@ -14,6 +14,7 @@ #include <linux/errno.h> #include <linux/acpi.h> #include <linux/memblock.h> +#include <linux/memory.h> #include <linux/numa.h> #include <linux/nodemask.h> #include <linux/topology.h> @@ -429,13 +430,23 @@ static int __init acpi_parse_cfmws(union acpi_subtable_headers *header, { struct acpi_cedt_cfmws *cfmws; int *fake_pxm = arg; - u64 start, end; + u64 start, end, align; int node; + int err; cfmws = (struct acpi_cedt_cfmws *)header; start = cfmws->base_hpa; end = cfmws->base_hpa + cfmws->window_size; + /* Align memblock size to CFMW regions if possible */ + align = 1UL << __ffs(start | end); + if (align >= SZ_256M) { + err = memory_block_advise_max_size(align); + if (err) + pr_warn("CFMWS: memblock size advise failed (%d)\n", err); + } else + pr_err("CFMWS: [BIOS BUG] base/size alignment violates spec\n"); + /* * The SRAT may have already described NUMA details for all, * or a portion of, this CFMWS HPA range. Extend the memblks @@ -453,7 +464,7 @@ static int __init acpi_parse_cfmws(union acpi_subtable_headers *header, return -EINVAL; } - if (numa_add_memblk(node, start, end) < 0) { + if (numa_add_reserved_memblk(node, start, end) < 0) { /* CXL driver must handle the NUMA_NO_NODE case */ pr_warn("ACPI NUMA: Failed to add memblk for CFMWS node %d [mem %#llx-%#llx]\n", node, start, end); diff --git a/drivers/base/memory.c b/drivers/base/memory.c index 19469e7f88c2..ed3e69dc785c 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -110,6 +110,57 @@ static void memory_block_release(struct device *dev) kfree(mem); } + +/* Max block size to be set by memory_block_advise_max_size */ +static unsigned long memory_block_advised_size; +static bool memory_block_advised_size_queried; + +/** + * memory_block_advise_max_size() - advise memory hotplug on the max suggested + * block size, usually for alignment. + * @size: suggestion for maximum block size. must be aligned on power of 2. + * + * Early boot software (pre-allocator init) may advise archs on the max block + * size. This value can only decrease after initialization, as the intent is + * to identify the largest supported alignment for all sources. + * + * Use of this value is arch-defined, as is min/max block size. + * + * Return: 0 on success + * -EINVAL if size is 0 or not pow2 aligned + * -EBUSY if value has already been probed + */ +int __init memory_block_advise_max_size(unsigned long size) +{ + if (!size || !is_power_of_2(size)) + return -EINVAL; + + if (memory_block_advised_size_queried) + return -EBUSY; + + if (memory_block_advised_size) + memory_block_advised_size = min(memory_block_advised_size, size); + else + memory_block_advised_size = size; + + return 0; +} + +/** + * memory_block_advised_max_size() - query advised max hotplug block size. + * + * After the first call, the value can never change. Callers looking for the + * actual block size should use memory_block_size_bytes. This interface is + * intended for use by arch-init when initializing the hotplug block size. + * + * Return: advised size in bytes, or 0 if never set. + */ +unsigned long memory_block_advised_max_size(void) +{ + memory_block_advised_size_queried = true; + return memory_block_advised_size; +} + unsigned long __weak memory_block_size_bytes(void) { return MIN_MEMORY_BLOCK_SIZE; diff --git a/drivers/base/node.c b/drivers/base/node.c index 618712071a1e..c19094481630 100644 --- a/drivers/base/node.c +++ b/drivers/base/node.c @@ -7,6 +7,7 @@ #include <linux/init.h> #include <linux/mm.h> #include <linux/memory.h> +#include <linux/mempolicy.h> #include <linux/vmstat.h> #include <linux/notifier.h> #include <linux/node.h> @@ -214,6 +215,14 @@ void node_set_perf_attrs(unsigned int nid, struct access_coordinate *coord, break; } } + + /* When setting CPU access coordinates, update mempolicy */ + if (access == ACCESS_COORDINATE_CPU) { + if (mempolicy_set_node_perf(nid, coord)) { + pr_info("failed to set mempolicy attrs for node %d\n", + nid); + } + } } EXPORT_SYMBOL_GPL(node_set_perf_attrs); diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c index fda7d8624889..94e6e9b80bf0 100644 --- a/drivers/block/zram/zram_drv.c +++ b/drivers/block/zram/zram_drv.c @@ -734,114 +734,19 @@ static void read_from_bdev_async(struct zram *zram, struct page *page, submit_bio(bio); } -#define PAGE_WB_SIG "page_index=" - -#define PAGE_WRITEBACK 0 -#define HUGE_WRITEBACK (1<<0) -#define IDLE_WRITEBACK (1<<1) -#define INCOMPRESSIBLE_WRITEBACK (1<<2) - -static int scan_slots_for_writeback(struct zram *zram, u32 mode, - unsigned long nr_pages, - unsigned long index, - struct zram_pp_ctl *ctl) +static int zram_writeback_slots(struct zram *zram, struct zram_pp_ctl *ctl) { - for (; nr_pages != 0; index++, nr_pages--) { - bool ok = true; - - zram_slot_lock(zram, index); - if (!zram_allocated(zram, index)) - goto next; - - if (zram_test_flag(zram, index, ZRAM_WB) || - zram_test_flag(zram, index, ZRAM_SAME)) - goto next; - - if (mode & IDLE_WRITEBACK && - !zram_test_flag(zram, index, ZRAM_IDLE)) - goto next; - if (mode & HUGE_WRITEBACK && - !zram_test_flag(zram, index, ZRAM_HUGE)) - goto next; - if (mode & INCOMPRESSIBLE_WRITEBACK && - !zram_test_flag(zram, index, ZRAM_INCOMPRESSIBLE)) - goto next; - - ok = place_pp_slot(zram, ctl, index); -next: - zram_slot_unlock(zram, index); - if (!ok) - break; - } - - return 0; -} - -static ssize_t writeback_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t len) -{ - struct zram *zram = dev_to_zram(dev); - unsigned long nr_pages = zram->disksize >> PAGE_SHIFT; - struct zram_pp_ctl *ctl = NULL; + unsigned long blk_idx = 0; + struct page *page = NULL; struct zram_pp_slot *pps; - unsigned long index = 0; - struct bio bio; struct bio_vec bio_vec; - struct page *page = NULL; - ssize_t ret = len; - int mode, err; - unsigned long blk_idx = 0; - - if (sysfs_streq(buf, "idle")) - mode = IDLE_WRITEBACK; - else if (sysfs_streq(buf, "huge")) - mode = HUGE_WRITEBACK; - else if (sysfs_streq(buf, "huge_idle")) - mode = IDLE_WRITEBACK | HUGE_WRITEBACK; - else if (sysfs_streq(buf, "incompressible")) - mode = INCOMPRESSIBLE_WRITEBACK; - else { - if (strncmp(buf, PAGE_WB_SIG, sizeof(PAGE_WB_SIG) - 1)) - return -EINVAL; - - if (kstrtol(buf + sizeof(PAGE_WB_SIG) - 1, 10, &index) || - index >= nr_pages) - return -EINVAL; - - nr_pages = 1; - mode = PAGE_WRITEBACK; - } - - down_read(&zram->init_lock); - if (!init_done(zram)) { - ret = -EINVAL; - goto release_init_lock; - } - - /* Do not permit concurrent post-processing actions. */ - if (atomic_xchg(&zram->pp_in_progress, 1)) { - up_read(&zram->init_lock); - return -EAGAIN; - } - - if (!zram->backing_dev) { - ret = -ENODEV; - goto release_init_lock; - } + struct bio bio; + int ret = 0, err; + u32 index; page = alloc_page(GFP_KERNEL); - if (!page) { - ret = -ENOMEM; - goto release_init_lock; - } - - ctl = init_pp_ctl(); - if (!ctl) { - ret = -ENOMEM; - goto release_init_lock; - } - - scan_slots_for_writeback(zram, mode, nr_pages, index, ctl); + if (!page) + return -ENOMEM; while ((pps = select_pp_slot(ctl))) { spin_lock(&zram->wb_limit_lock); @@ -929,10 +834,215 @@ next: if (blk_idx) free_block_bdev(zram, blk_idx); - -release_init_lock: if (page) __free_page(page); + + return ret; +} + +#define PAGE_WRITEBACK 0 +#define HUGE_WRITEBACK (1 << 0) +#define IDLE_WRITEBACK (1 << 1) +#define INCOMPRESSIBLE_WRITEBACK (1 << 2) + +static int parse_page_index(char *val, unsigned long nr_pages, + unsigned long *lo, unsigned long *hi) +{ + int ret; + + ret = kstrtoul(val, 10, lo); + if (ret) + return ret; + if (*lo >= nr_pages) + return -ERANGE; + *hi = *lo + 1; + return 0; +} + +static int parse_page_indexes(char *val, unsigned long nr_pages, + unsigned long *lo, unsigned long *hi) +{ + char *delim; + int ret; + + delim = strchr(val, '-'); + if (!delim) + return -EINVAL; + + *delim = 0x00; + ret = kstrtoul(val, 10, lo); + if (ret) + return ret; + if (*lo >= nr_pages) + return -ERANGE; + + ret = kstrtoul(delim + 1, 10, hi); + if (ret) + return ret; + if (*hi >= nr_pages || *lo > *hi) + return -ERANGE; + *hi += 1; + return 0; +} + +static int parse_mode(char *val, u32 *mode) +{ + *mode = 0; + + if (!strcmp(val, "idle")) + *mode = IDLE_WRITEBACK; + if (!strcmp(val, "huge")) + *mode = HUGE_WRITEBACK; + if (!strcmp(val, "huge_idle")) + *mode = IDLE_WRITEBACK | HUGE_WRITEBACK; + if (!strcmp(val, "incompressible")) + *mode = INCOMPRESSIBLE_WRITEBACK; + + if (*mode == 0) + return -EINVAL; + return 0; +} + +static int scan_slots_for_writeback(struct zram *zram, u32 mode, + unsigned long lo, unsigned long hi, + struct zram_pp_ctl *ctl) +{ + u32 index = lo; + + while (index < hi) { + bool ok = true; + + zram_slot_lock(zram, index); + if (!zram_allocated(zram, index)) + goto next; + + if (zram_test_flag(zram, index, ZRAM_WB) || + zram_test_flag(zram, index, ZRAM_SAME)) + goto next; + + if (mode & IDLE_WRITEBACK && + !zram_test_flag(zram, index, ZRAM_IDLE)) + goto next; + if (mode & HUGE_WRITEBACK && + !zram_test_flag(zram, index, ZRAM_HUGE)) + goto next; + if (mode & INCOMPRESSIBLE_WRITEBACK && + !zram_test_flag(zram, index, ZRAM_INCOMPRESSIBLE)) + goto next; + + ok = place_pp_slot(zram, ctl, index); +next: + zram_slot_unlock(zram, index); + if (!ok) + break; + index++; + } + + return 0; +} + +static ssize_t writeback_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct zram *zram = dev_to_zram(dev); + u64 nr_pages = zram->disksize >> PAGE_SHIFT; + unsigned long lo = 0, hi = nr_pages; + struct zram_pp_ctl *ctl = NULL; + char *args, *param, *val; + ssize_t ret = len; + int err, mode = 0; + + down_read(&zram->init_lock); + if (!init_done(zram)) { + up_read(&zram->init_lock); + return -EINVAL; + } + + /* Do not permit concurrent post-processing actions. */ + if (atomic_xchg(&zram->pp_in_progress, 1)) { + up_read(&zram->init_lock); + return -EAGAIN; + } + + if (!zram->backing_dev) { + ret = -ENODEV; + goto release_init_lock; + } + + ctl = init_pp_ctl(); + if (!ctl) { + ret = -ENOMEM; + goto release_init_lock; + } + + args = skip_spaces(buf); + while (*args) { + args = next_arg(args, ¶m, &val); + + /* + * Workaround to support the old writeback interface. + * + * The old writeback interface has a minor inconsistency and + * requires key=value only for page_index parameter, while the + * writeback mode is a valueless parameter. + * + * This is not the case anymore and now all parameters are + * required to have values, however, we need to support the + * legacy writeback interface format so we check if we can + * recognize a valueless parameter as the (legacy) writeback + * mode. + */ + if (!val || !*val) { + err = parse_mode(param, &mode); + if (err) { + ret = err; + goto release_init_lock; + } + + scan_slots_for_writeback(zram, mode, lo, hi, ctl); + break; + } + + if (!strcmp(param, "type")) { + err = parse_mode(val, &mode); + if (err) { + ret = err; + goto release_init_lock; + } + + scan_slots_for_writeback(zram, mode, lo, hi, ctl); + break; + } + + if (!strcmp(param, "page_index")) { + err = parse_page_index(val, nr_pages, &lo, &hi); + if (err) { + ret = err; + goto release_init_lock; + } + + scan_slots_for_writeback(zram, mode, lo, hi, ctl); + continue; + } + + if (!strcmp(param, "page_indexes")) { + err = parse_page_indexes(val, nr_pages, &lo, &hi); + if (err) { + ret = err; + goto release_init_lock; + } + + scan_slots_for_writeback(zram, mode, lo, hi, ctl); + continue; + } + } + + err = zram_writeback_slots(zram, ctl); + if (err) + ret = err; + +release_init_lock: release_pp_ctl(zram, ctl); atomic_set(&zram->pp_in_progress, 0); up_read(&zram->init_lock); @@ -1694,7 +1804,7 @@ static int write_incompressible_page(struct zram *zram, struct page *page, */ handle = zs_malloc(zram->mem_pool, PAGE_SIZE, GFP_NOIO | __GFP_NOWARN | - __GFP_HIGHMEM | __GFP_MOVABLE); + __GFP_HIGHMEM | __GFP_MOVABLE, page_to_nid(page)); if (IS_ERR_VALUE(handle)) return PTR_ERR((void *)handle); @@ -1761,7 +1871,7 @@ static int zram_write_page(struct zram *zram, struct page *page, u32 index) handle = zs_malloc(zram->mem_pool, comp_len, GFP_NOIO | __GFP_NOWARN | - __GFP_HIGHMEM | __GFP_MOVABLE); + __GFP_HIGHMEM | __GFP_MOVABLE, page_to_nid(page)); if (IS_ERR_VALUE(handle)) { zcomp_stream_put(zstrm); return PTR_ERR((void *)handle); @@ -1981,10 +2091,15 @@ static int recompress_slot(struct zram *zram, u32 index, struct page *page, * We are holding per-CPU stream mutex and entry lock so better * avoid direct reclaim. Allocation error is not fatal since * we still have the old object in the mem_pool. + * + * XXX: technically, the node we really want here is the node that holds + * the original compressed data. But that would require us to modify + * zsmalloc API to return this information. For now, we will make do with + * the node of the page allocated for recompression. */ handle_new = zs_malloc(zram->mem_pool, comp_len_new, GFP_NOIO | __GFP_NOWARN | - __GFP_HIGHMEM | __GFP_MOVABLE); + __GFP_HIGHMEM | __GFP_MOVABLE, page_to_nid(page)); if (IS_ERR_VALUE(handle_new)) { zcomp_stream_put(zstrm); return PTR_ERR((void *)handle_new); diff --git a/drivers/bus/brcmstb_gisb.c b/drivers/bus/brcmstb_gisb.c index ee29162da4ee..91ef99c42344 100644 --- a/drivers/bus/brcmstb_gisb.c +++ b/drivers/bus/brcmstb_gisb.c @@ -395,10 +395,7 @@ static struct attribute *gisb_arb_sysfs_attrs[] = { &dev_attr_gisb_arb_timeout.attr, NULL, }; - -static struct attribute_group gisb_arb_sysfs_attr_group = { - .attrs = gisb_arb_sysfs_attrs, -}; +ATTRIBUTE_GROUPS(gisb_arb_sysfs); static const struct of_device_id brcmstb_gisb_arb_of_match[] = { { .compatible = "brcm,gisb-arb", .data = gisb_offsets_bcm7445 }, @@ -490,10 +487,6 @@ static int __init brcmstb_gisb_arb_probe(struct platform_device *pdev) } } - err = sysfs_create_group(&pdev->dev.kobj, &gisb_arb_sysfs_attr_group); - if (err) - return err; - platform_set_drvdata(pdev, gdev); list_add_tail(&gdev->next, &brcmstb_gisb_arb_device_list); @@ -550,6 +543,7 @@ static struct platform_driver brcmstb_gisb_arb_driver = { .name = "brcm-gisb-arb", .of_match_table = brcmstb_gisb_arb_of_match, .pm = &brcmstb_gisb_arb_pm_ops, + .dev_groups = gisb_arb_sysfs_groups, }, }; diff --git a/drivers/bus/fsl-mc/dprc-driver.c b/drivers/bus/fsl-mc/dprc-driver.c index 52053f7c6d9a..c63a7e688db6 100644 --- a/drivers/bus/fsl-mc/dprc-driver.c +++ b/drivers/bus/fsl-mc/dprc-driver.c @@ -806,8 +806,6 @@ int dprc_cleanup(struct fsl_mc_device *mc_dev) dev_set_msi_domain(&mc_dev->dev, NULL); } - fsl_mc_cleanup_all_resource_pools(mc_dev); - /* if this step fails we cannot go further with cleanup as there is no way of * communicating with the firmware */ diff --git a/drivers/bus/fsl-mc/dprc.c b/drivers/bus/fsl-mc/dprc.c index dd1b5c0fb7e2..38d40c09b719 100644 --- a/drivers/bus/fsl-mc/dprc.c +++ b/drivers/bus/fsl-mc/dprc.c @@ -489,7 +489,7 @@ int dprc_set_obj_irq(struct fsl_mc_io *mc_io, cmd_params->irq_addr = cpu_to_le64(irq_cfg->paddr); cmd_params->irq_num = cpu_to_le32(irq_cfg->irq_num); cmd_params->obj_id = cpu_to_le32(obj_id); - strscpy_pad(cmd_params->obj_type, obj_type, 16); + strscpy(cmd_params->obj_type, obj_type); /* send command to mc*/ return mc_send_command(mc_io, &cmd); @@ -561,7 +561,7 @@ int dprc_get_obj_region(struct fsl_mc_io *mc_io, cmd_params = (struct dprc_cmd_get_obj_region *)cmd.params; cmd_params->obj_id = cpu_to_le32(obj_id); cmd_params->region_index = region_index; - strscpy_pad(cmd_params->obj_type, obj_type, 16); + strscpy(cmd_params->obj_type, obj_type); /* send command to mc*/ err = mc_send_command(mc_io, &cmd); diff --git a/drivers/bus/fsl-mc/fsl-mc-allocator.c b/drivers/bus/fsl-mc/fsl-mc-allocator.c index 6c3beb82dd1b..d2ea59471323 100644 --- a/drivers/bus/fsl-mc/fsl-mc-allocator.c +++ b/drivers/bus/fsl-mc/fsl-mc-allocator.c @@ -555,27 +555,6 @@ void fsl_mc_init_all_resource_pools(struct fsl_mc_device *mc_bus_dev) } } -static void fsl_mc_cleanup_resource_pool(struct fsl_mc_device *mc_bus_dev, - enum fsl_mc_pool_type pool_type) -{ - struct fsl_mc_resource *resource; - struct fsl_mc_resource *next; - struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); - struct fsl_mc_resource_pool *res_pool = - &mc_bus->resource_pools[pool_type]; - - list_for_each_entry_safe(resource, next, &res_pool->free_list, node) - devm_kfree(&mc_bus_dev->dev, resource); -} - -void fsl_mc_cleanup_all_resource_pools(struct fsl_mc_device *mc_bus_dev) -{ - int pool_type; - - for (pool_type = 0; pool_type < FSL_MC_NUM_POOL_TYPES; pool_type++) - fsl_mc_cleanup_resource_pool(mc_bus_dev, pool_type); -} - /* * fsl_mc_allocator_probe - callback invoked when an allocatable device is * being added to the system diff --git a/drivers/bus/fsl-mc/fsl-mc-bus.c b/drivers/bus/fsl-mc/fsl-mc-bus.c index a8be8cf246fb..7671bd158545 100644 --- a/drivers/bus/fsl-mc/fsl-mc-bus.c +++ b/drivers/bus/fsl-mc/fsl-mc-bus.c @@ -139,9 +139,9 @@ static int fsl_mc_bus_uevent(const struct device *dev, struct kobj_uevent_env *e static int fsl_mc_dma_configure(struct device *dev) { + const struct device_driver *drv = READ_ONCE(dev->driver); struct device *dma_dev = dev; struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); - struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver); u32 input_id = mc_dev->icid; int ret; @@ -153,8 +153,8 @@ static int fsl_mc_dma_configure(struct device *dev) else ret = acpi_dma_configure_id(dev, DEV_DMA_COHERENT, &input_id); - /* @mc_drv may not be valid when we're called from the IOMMU layer */ - if (!ret && dev->driver && !mc_drv->driver_managed_dma) { + /* @drv may not be valid when we're called from the IOMMU layer */ + if (!ret && drv && !to_fsl_mc_driver(drv)->driver_managed_dma) { ret = iommu_device_use_default_domain(dev); if (ret) arch_teardown_dma_ops(dev); @@ -906,8 +906,10 @@ int fsl_mc_device_add(struct fsl_mc_obj_desc *obj_desc, error_cleanup_dev: kfree(mc_dev->regions); - kfree(mc_bus); - kfree(mc_dev); + if (mc_bus) + kfree(mc_bus); + else + kfree(mc_dev); return error; } diff --git a/drivers/bus/fsl-mc/fsl-mc-private.h b/drivers/bus/fsl-mc/fsl-mc-private.h index e1b7ec3ed1a7..beed4c53533d 100644 --- a/drivers/bus/fsl-mc/fsl-mc-private.h +++ b/drivers/bus/fsl-mc/fsl-mc-private.h @@ -629,8 +629,6 @@ int __init fsl_mc_allocator_driver_init(void); void fsl_mc_init_all_resource_pools(struct fsl_mc_device *mc_bus_dev); -void fsl_mc_cleanup_all_resource_pools(struct fsl_mc_device *mc_bus_dev); - int __must_check fsl_mc_resource_allocate(struct fsl_mc_bus *mc_bus, enum fsl_mc_pool_type pool_type, struct fsl_mc_resource diff --git a/drivers/bus/fsl-mc/fsl-mc-uapi.c b/drivers/bus/fsl-mc/fsl-mc-uapi.c index 9c4c1395fcdb..823969e4159c 100644 --- a/drivers/bus/fsl-mc/fsl-mc-uapi.c +++ b/drivers/bus/fsl-mc/fsl-mc-uapi.c @@ -48,6 +48,7 @@ enum fsl_mc_cmd_index { DPRC_GET_POOL, DPRC_GET_POOL_COUNT, DPRC_GET_CONNECTION, + DPRC_GET_MEM, DPCI_GET_LINK_STATE, DPCI_GET_PEER_ATTR, DPAIOP_GET_SL_VERSION, @@ -194,6 +195,12 @@ static struct fsl_mc_cmd_desc fsl_mc_accepted_cmds[] = { .token = true, .size = 32, }, + [DPRC_GET_MEM] = { + .cmdid_value = 0x16D0, + .cmdid_mask = 0xFFF0, + .token = true, + .size = 12, + }, [DPCI_GET_LINK_STATE] = { .cmdid_value = 0x0E10, @@ -275,13 +282,13 @@ static struct fsl_mc_cmd_desc fsl_mc_accepted_cmds[] = { .size = 8, }, [DPSW_GET_TAILDROP] = { - .cmdid_value = 0x0A80, + .cmdid_value = 0x0A90, .cmdid_mask = 0xFFF0, .token = true, .size = 14, }, [DPSW_SET_TAILDROP] = { - .cmdid_value = 0x0A90, + .cmdid_value = 0x0A80, .cmdid_mask = 0xFFF0, .token = true, .size = 24, diff --git a/drivers/bus/fsl-mc/mc-io.c b/drivers/bus/fsl-mc/mc-io.c index a0ad7866cbfc..cd8754763f40 100644 --- a/drivers/bus/fsl-mc/mc-io.c +++ b/drivers/bus/fsl-mc/mc-io.c @@ -214,12 +214,19 @@ int __must_check fsl_mc_portal_allocate(struct fsl_mc_device *mc_dev, if (error < 0) goto error_cleanup_resource; - dpmcp_dev->consumer_link = device_link_add(&mc_dev->dev, - &dpmcp_dev->dev, - DL_FLAG_AUTOREMOVE_CONSUMER); - if (!dpmcp_dev->consumer_link) { - error = -EINVAL; - goto error_cleanup_mc_io; + /* If the DPRC device itself tries to allocate a portal (usually for + * UAPI interaction), don't add a device link between them since the + * DPMCP device is an actual child device of the DPRC and a reverse + * dependency is not allowed. + */ + if (mc_dev != mc_bus_dev) { + dpmcp_dev->consumer_link = device_link_add(&mc_dev->dev, + &dpmcp_dev->dev, + DL_FLAG_AUTOREMOVE_CONSUMER); + if (!dpmcp_dev->consumer_link) { + error = -EINVAL; + goto error_cleanup_mc_io; + } } *new_mc_io = mc_io; diff --git a/drivers/bus/fsl-mc/mc-sys.c b/drivers/bus/fsl-mc/mc-sys.c index f2052cd0a051..b22c59d57c8f 100644 --- a/drivers/bus/fsl-mc/mc-sys.c +++ b/drivers/bus/fsl-mc/mc-sys.c @@ -19,7 +19,7 @@ /* * Timeout in milliseconds to wait for the completion of an MC command */ -#define MC_CMD_COMPLETION_TIMEOUT_MS 500 +#define MC_CMD_COMPLETION_TIMEOUT_MS 15000 /* * usleep_range() min and max values used to throttle down polling diff --git a/drivers/bus/ti-sysc.c b/drivers/bus/ti-sysc.c index f67b927ae4ca..9f624e5da991 100644 --- a/drivers/bus/ti-sysc.c +++ b/drivers/bus/ti-sysc.c @@ -677,51 +677,6 @@ static int sysc_parse_and_check_child_range(struct sysc *ddata) return 0; } -/* Interconnect instances to probe before l4_per instances */ -static struct resource early_bus_ranges[] = { - /* am3/4 l4_wkup */ - { .start = 0x44c00000, .end = 0x44c00000 + 0x300000, }, - /* omap4/5 and dra7 l4_cfg */ - { .start = 0x4a000000, .end = 0x4a000000 + 0x300000, }, - /* omap4 l4_wkup */ - { .start = 0x4a300000, .end = 0x4a300000 + 0x30000, }, - /* omap5 and dra7 l4_wkup without dra7 dcan segment */ - { .start = 0x4ae00000, .end = 0x4ae00000 + 0x30000, }, -}; - -static atomic_t sysc_defer = ATOMIC_INIT(10); - -/** - * sysc_defer_non_critical - defer non_critical interconnect probing - * @ddata: device driver data - * - * We want to probe l4_cfg and l4_wkup interconnect instances before any - * l4_per instances as l4_per instances depend on resources on l4_cfg and - * l4_wkup interconnects. - */ -static int sysc_defer_non_critical(struct sysc *ddata) -{ - struct resource *res; - int i; - - if (!atomic_read(&sysc_defer)) - return 0; - - for (i = 0; i < ARRAY_SIZE(early_bus_ranges); i++) { - res = &early_bus_ranges[i]; - if (ddata->module_pa >= res->start && - ddata->module_pa <= res->end) { - atomic_set(&sysc_defer, 0); - - return 0; - } - } - - atomic_dec_if_positive(&sysc_defer); - - return -EPROBE_DEFER; -} - static struct device_node *stdout_path; static void sysc_init_stdout_path(struct sysc *ddata) @@ -947,10 +902,6 @@ static int sysc_map_and_check_registers(struct sysc *ddata) if (error) return error; - error = sysc_defer_non_critical(ddata); - if (error) - return error; - sysc_check_children(ddata); if (!of_property_present(np, "reg")) @@ -2036,6 +1987,21 @@ static void sysc_module_disable_quirk_pruss(struct sysc *ddata) sysc_write(ddata, ddata->offsets[SYSC_SYSCONFIG], reg); } +static void sysc_module_enable_quirk_pruss(struct sysc *ddata) +{ + u32 reg; + + reg = sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]); + + /* + * Clearing the SYSC_PRUSS_STANDBY_INIT bit - Updates OCP master + * port configuration to enable memory access outside of the + * PRU-ICSS subsystem. + */ + reg &= (~SYSC_PRUSS_STANDBY_INIT); + sysc_write(ddata, ddata->offsets[SYSC_SYSCONFIG], reg); +} + static void sysc_init_module_quirks(struct sysc *ddata) { if (ddata->legacy_mode || !ddata->name) @@ -2088,8 +2054,10 @@ static void sysc_init_module_quirks(struct sysc *ddata) ddata->module_disable_quirk = sysc_reset_done_quirk_wdt; } - if (ddata->cfg.quirks & SYSC_MODULE_QUIRK_PRUSS) + if (ddata->cfg.quirks & SYSC_MODULE_QUIRK_PRUSS) { + ddata->module_enable_quirk = sysc_module_enable_quirk_pruss; ddata->module_disable_quirk = sysc_module_disable_quirk_pruss; + } } static int sysc_clockdomain_init(struct sysc *ddata) diff --git a/drivers/cache/sifive_ccache.c b/drivers/cache/sifive_ccache.c index 6874b72ec59d..e1a283805ea7 100644 --- a/drivers/cache/sifive_ccache.c +++ b/drivers/cache/sifive_ccache.c @@ -118,6 +118,8 @@ static void ccache_config_read(void) } static const struct of_device_id sifive_ccache_ids[] = { + { .compatible = "eswin,eic7700-l3-cache", + .data = (void *)(QUIRK_NONSTANDARD_CACHE_OPS) }, { .compatible = "sifive,fu540-c000-ccache" }, { .compatible = "sifive,fu740-c000-ccache" }, { .compatible = "starfive,jh7100-ccache", diff --git a/drivers/cpufreq/powernow-k8.c b/drivers/cpufreq/powernow-k8.c index 4e3ba6e68c32..f7512b4e923e 100644 --- a/drivers/cpufreq/powernow-k8.c +++ b/drivers/cpufreq/powernow-k8.c @@ -482,7 +482,7 @@ static void check_supported_cpu(void *_rc) cpuid(CPUID_FREQ_VOLT_CAPABILITIES, &eax, &ebx, &ecx, &edx); if ((edx & P_STATE_TRANSITION_CAPABLE) != P_STATE_TRANSITION_CAPABLE) { - pr_info("Power state transitions not supported\n"); + pr_info_once("Power state transitions not supported\n"); return; } *rc = 0; diff --git a/drivers/dax/kmem.c b/drivers/dax/kmem.c index e97d47f42ee2..584c70a34b52 100644 --- a/drivers/dax/kmem.c +++ b/drivers/dax/kmem.c @@ -13,6 +13,7 @@ #include <linux/mman.h> #include <linux/memory-tiers.h> #include <linux/memory_hotplug.h> +#include <linux/string_helpers.h> #include "dax-private.h" #include "bus.h" @@ -68,7 +69,7 @@ static void kmem_put_memory_types(void) static int dev_dax_kmem_probe(struct dev_dax *dev_dax) { struct device *dev = &dev_dax->dev; - unsigned long total_len = 0; + unsigned long total_len = 0, orig_len = 0; struct dax_kmem_data *data; struct memory_dev_type *mtype; int i, rc, mapped = 0; @@ -97,6 +98,7 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax) for (i = 0; i < dev_dax->nr_range; i++) { struct range range; + orig_len += range_len(&dev_dax->ranges[i].range); rc = dax_kmem_range(dev_dax, i, &range); if (rc) { dev_info(dev, "mapping%d: %#llx-%#llx too small after alignment\n", @@ -109,6 +111,12 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax) if (!total_len) { dev_warn(dev, "rejecting DAX region without any memory after alignment\n"); return -EINVAL; + } else if (total_len != orig_len) { + char buf[16]; + + string_get_size(orig_len - total_len, 1, STRING_UNITS_2, + buf, sizeof(buf)); + dev_warn(dev, "DAX region truncated by %s due to alignment\n", buf); } init_node_memory_type(numa_node, mtype); diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 20333608b983..cae52c654a15 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1746,9 +1746,9 @@ altr_edac_a10_device_trig(struct file *file, const char __user *user_buf, local_irq_save(flags); if (trig_type == ALTR_UE_TRIGGER_CHAR) - writel(priv->ue_set_mask, set_addr); + writew(priv->ue_set_mask, set_addr); else - writel(priv->ce_set_mask, set_addr); + writew(priv->ce_set_mask, set_addr); /* Ensure the interrupt test bits are set */ wmb(); @@ -1778,7 +1778,7 @@ altr_edac_a10_device_trig2(struct file *file, const char __user *user_buf, local_irq_save(flags); if (trig_type == ALTR_UE_TRIGGER_CHAR) { - writel(priv->ue_set_mask, set_addr); + writew(priv->ue_set_mask, set_addr); } else { /* Setup read/write of 4 bytes */ writel(ECC_WORD_WRITE, drvdata->base + ECC_BLK_DBYTECTRL_OFST); diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 7df19d82aa68..bbd2155d8483 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -267,6 +267,23 @@ config TURRIS_MOX_RWTM other manufacturing data and also utilize the Entropy Bit Generator for hardware random number generation. +if TURRIS_MOX_RWTM + +config TURRIS_MOX_RWTM_KEYCTL + bool "Turris Mox rWTM ECDSA message signing" + default y + depends on KEYS + depends on ASYMMETRIC_KEY_TYPE + select CZNIC_PLATFORMS + select TURRIS_SIGNING_KEY + help + Say Y here to add support for ECDSA message signing with board private + key (each Turris Mox has an ECDSA private key generated in the secure + coprocessor when manufactured). This functionality is exposed via the + keyctl() syscall. + +endif # TURRIS_MOX_RWTM + source "drivers/firmware/arm_ffa/Kconfig" source "drivers/firmware/broadcom/Kconfig" source "drivers/firmware/cirrus/Kconfig" diff --git a/drivers/firmware/arm_scmi/Kconfig b/drivers/firmware/arm_scmi/Kconfig index dabd874641d0..e3fb36825978 100644 --- a/drivers/firmware/arm_scmi/Kconfig +++ b/drivers/firmware/arm_scmi/Kconfig @@ -69,6 +69,19 @@ config ARM_SCMI_DEBUG_COUNTERS such useful debug counters. This can be helpful for debugging and SCMI monitoring. +config ARM_SCMI_QUIRKS + bool "Enable SCMI Quirks framework" + depends on JUMP_LABEL || COMPILE_TEST + default y + help + Enables support for SCMI Quirks framework to workaround SCMI platform + firmware bugs on system already deployed in the wild. + + The framework allows the definition of platform-specific code quirks + that will be associated and enabled only on the desired platforms + depending on the SCMI firmware advertised versions and/or machine + compatibles. + source "drivers/firmware/arm_scmi/transports/Kconfig" source "drivers/firmware/arm_scmi/vendors/imx/Kconfig" diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 9ac81adff567..780cd62b2f78 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -3,6 +3,7 @@ scmi-bus-y = bus.o scmi-core-objs := $(scmi-bus-y) scmi-driver-y = driver.o notify.o +scmi-driver-$(CONFIG_ARM_SCMI_QUIRKS) += quirks.o scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) += raw_mode.o scmi-transport-$(CONFIG_ARM_SCMI_HAVE_SHMEM) = shmem.o scmi-transport-$(CONFIG_ARM_SCMI_HAVE_MSG) += msg.o diff --git a/drivers/firmware/arm_scmi/bus.c b/drivers/firmware/arm_scmi/bus.c index 3a5474015f7d..1adef0389475 100644 --- a/drivers/firmware/arm_scmi/bus.c +++ b/drivers/firmware/arm_scmi/bus.c @@ -201,55 +201,51 @@ scmi_protocol_table_unregister(const struct scmi_device_id *id_table) scmi_protocol_device_unrequest(entry); } -static const struct scmi_device_id * -scmi_dev_match_id(struct scmi_device *scmi_dev, const struct scmi_driver *scmi_drv) +static int scmi_dev_match_by_id_table(struct scmi_device *scmi_dev, + const struct scmi_device_id *id_table) { - const struct scmi_device_id *id = scmi_drv->id_table; - - if (!id) - return NULL; - - for (; id->protocol_id; id++) - if (id->protocol_id == scmi_dev->protocol_id) { - if (!id->name) - return id; - else if (!strcmp(id->name, scmi_dev->name)) - return id; - } + if (!id_table || !id_table->name) + return 0; + + /* Always skip transport devices from matching */ + for (; id_table->protocol_id && id_table->name; id_table++) + if (id_table->protocol_id == scmi_dev->protocol_id && + strncmp(scmi_dev->name, "__scmi_transport_device", 23) && + !strcmp(id_table->name, scmi_dev->name)) + return 1; + return 0; +} - return NULL; +static int scmi_dev_match_id(struct scmi_device *scmi_dev, + const struct scmi_driver *scmi_drv) +{ + return scmi_dev_match_by_id_table(scmi_dev, scmi_drv->id_table); } static int scmi_dev_match(struct device *dev, const struct device_driver *drv) { const struct scmi_driver *scmi_drv = to_scmi_driver(drv); struct scmi_device *scmi_dev = to_scmi_dev(dev); - const struct scmi_device_id *id; - - id = scmi_dev_match_id(scmi_dev, scmi_drv); - if (id) - return 1; - return 0; + return scmi_dev_match_id(scmi_dev, scmi_drv); } static int scmi_match_by_id_table(struct device *dev, const void *data) { - struct scmi_device *sdev = to_scmi_dev(dev); + struct scmi_device *scmi_dev = to_scmi_dev(dev); const struct scmi_device_id *id_table = data; - return sdev->protocol_id == id_table->protocol_id && - (id_table->name && !strcmp(sdev->name, id_table->name)); + return scmi_dev_match_by_id_table(scmi_dev, id_table); } static struct scmi_device *scmi_child_dev_find(struct device *parent, int prot_id, const char *name) { - struct scmi_device_id id_table; + struct scmi_device_id id_table[2] = { 0 }; struct device *dev; - id_table.protocol_id = prot_id; - id_table.name = name; + id_table[0].protocol_id = prot_id; + id_table[0].name = name; dev = device_find_child(parent, &id_table, scmi_match_by_id_table); if (!dev) @@ -463,6 +459,20 @@ put_dev: return NULL; } +static struct scmi_device * +_scmi_device_create(struct device_node *np, struct device *parent, + int protocol, const char *name) +{ + struct scmi_device *sdev; + + sdev = __scmi_device_create(np, parent, protocol, name); + if (!sdev) + pr_err("(%s) Failed to create device for protocol 0x%x (%s)\n", + of_node_full_name(parent->of_node), protocol, name); + + return sdev; +} + /** * scmi_device_create - A method to create one or more SCMI devices * @@ -495,7 +505,7 @@ struct scmi_device *scmi_device_create(struct device_node *np, struct scmi_device *scmi_dev = NULL; if (name) - return __scmi_device_create(np, parent, protocol, name); + return _scmi_device_create(np, parent, protocol, name); mutex_lock(&scmi_requested_devices_mtx); phead = idr_find(&scmi_requested_devices, protocol); @@ -509,18 +519,13 @@ struct scmi_device *scmi_device_create(struct device_node *np, list_for_each_entry(rdev, phead, node) { struct scmi_device *sdev; - sdev = __scmi_device_create(np, parent, - rdev->id_table->protocol_id, - rdev->id_table->name); - /* Report errors and carry on... */ + sdev = _scmi_device_create(np, parent, + rdev->id_table->protocol_id, + rdev->id_table->name); if (sdev) scmi_dev = sdev; - else - pr_err("(%s) Failed to create device for protocol 0x%x (%s)\n", - of_node_full_name(parent->of_node), - rdev->id_table->protocol_id, - rdev->id_table->name); } + mutex_unlock(&scmi_requested_devices_mtx); return scmi_dev; diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index 2ed2279388f0..afa7981efe82 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -11,6 +11,7 @@ #include "protocols.h" #include "notify.h" +#include "quirks.h" /* Updated only after ALL the mandatory features for that version are merged */ #define SCMI_PROTOCOL_SUPPORTED_VERSION 0x30000 @@ -429,6 +430,23 @@ static void iter_clk_describe_prepare_message(void *message, msg->rate_index = cpu_to_le32(desc_index); } +#define QUIRK_OUT_OF_SPEC_TRIPLET \ + ({ \ + /* \ + * A known quirk: a triplet is returned but num_returned != 3 \ + * Check for a safe payload size and fix. \ + */ \ + if (st->num_returned != 3 && st->num_remaining == 0 && \ + st->rx_len == sizeof(*r) + sizeof(__le32) * 2 * 3) { \ + st->num_returned = 3; \ + st->num_remaining = 0; \ + } else { \ + dev_err(p->dev, \ + "Cannot fix out-of-spec reply !\n"); \ + return -EPROTO; \ + } \ + }) + static int iter_clk_describe_update_state(struct scmi_iterator_state *st, const void *response, void *priv) @@ -450,19 +468,8 @@ iter_clk_describe_update_state(struct scmi_iterator_state *st, p->clk->name, st->num_returned, st->num_remaining, st->rx_len); - /* - * A known quirk: a triplet is returned but num_returned != 3 - * Check for a safe payload size and fix. - */ - if (st->num_returned != 3 && st->num_remaining == 0 && - st->rx_len == sizeof(*r) + sizeof(__le32) * 2 * 3) { - st->num_returned = 3; - st->num_remaining = 0; - } else { - dev_err(p->dev, - "Cannot fix out-of-spec reply !\n"); - return -EPROTO; - } + SCMI_QUIRK(clock_rates_triplet_out_of_spec, + QUIRK_OUT_OF_SPEC_TRIPLET); } return 0; diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 10ea7962323e..dab758c5fdea 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -475,6 +475,7 @@ static int __tag##_probe(struct platform_device *pdev) \ if (ret) \ goto err; \ \ + spdev->dev.parent = dev; \ ret = platform_device_add(spdev); \ if (ret) \ goto err; \ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index 0390d5ff195e..395fe9289035 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -11,7 +11,7 @@ * various power domain DVFS including the core/cluster, certain system * clocks configuration, thermal sensors and many others. * - * Copyright (C) 2018-2024 ARM Ltd. + * Copyright (C) 2018-2025 ARM Ltd. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -38,6 +38,7 @@ #include "common.h" #include "notify.h" +#include "quirks.h" #include "raw_mode.h" @@ -439,14 +440,8 @@ static void scmi_create_protocol_devices(struct device_node *np, struct scmi_info *info, int prot_id, const char *name) { - struct scmi_device *sdev; - mutex_lock(&info->devreq_mtx); - sdev = scmi_device_create(np, info->dev, prot_id, name); - if (name && !sdev) - dev_err(info->dev, - "failed to create device for protocol 0x%X (%s)\n", - prot_id, name); + scmi_device_create(np, info->dev, prot_id, name); mutex_unlock(&info->devreq_mtx); } @@ -1190,7 +1185,8 @@ static void scmi_handle_response(struct scmi_chan_info *cinfo, * RX path since it will be already queued at the end of the TX * poll loop. */ - if (!xfer->hdr.poll_completion) + if (!xfer->hdr.poll_completion || + xfer->hdr.type == MSG_TYPE_DELAYED_RESP) scmi_raw_message_report(info->raw, xfer, SCMI_RAW_REPLY_QUEUE, cinfo->id); @@ -1738,6 +1734,39 @@ static int scmi_common_get_max_msg_size(const struct scmi_protocol_handle *ph) } /** + * scmi_protocol_msg_check - Check protocol message attributes + * + * @ph: A reference to the protocol handle. + * @message_id: The ID of the message to check. + * @attributes: A parameter to optionally return the retrieved message + * attributes, in case of Success. + * + * An helper to check protocol message attributes for a specific protocol + * and message pair. + * + * Return: 0 on SUCCESS + */ +static int scmi_protocol_msg_check(const struct scmi_protocol_handle *ph, + u32 message_id, u32 *attributes) +{ + int ret; + struct scmi_xfer *t; + + ret = xfer_get_init(ph, PROTOCOL_MESSAGE_ATTRIBUTES, + sizeof(__le32), 0, &t); + if (ret) + return ret; + + put_unaligned_le32(message_id, t->tx.buf); + ret = do_xfer(ph, t); + if (!ret && attributes) + *attributes = get_unaligned_le32(t->rx.buf); + xfer_put(ph, t); + + return ret; +} + +/** * struct scmi_iterator - Iterator descriptor * @msg: A reference to the message TX buffer; filled by @prepare_message with * a proper custom command payload for each multi-part command request. @@ -1869,6 +1898,13 @@ struct scmi_msg_resp_desc_fc { __le32 db_preserve_hmask; }; +#define QUIRK_PERF_FC_FORCE \ + ({ \ + if (pi->proto->id == SCMI_PROTOCOL_PERF && \ + message_id == 0x8 /* PERF_LEVEL_GET */) \ + attributes |= BIT(0); \ + }) + static void scmi_common_fastchannel_init(const struct scmi_protocol_handle *ph, u8 describe_id, u32 message_id, u32 valid_size, @@ -1878,6 +1914,7 @@ scmi_common_fastchannel_init(const struct scmi_protocol_handle *ph, int ret; u32 flags; u64 phys_addr; + u32 attributes; u8 size; void __iomem *addr; struct scmi_xfer *t; @@ -1886,6 +1923,16 @@ scmi_common_fastchannel_init(const struct scmi_protocol_handle *ph, struct scmi_msg_resp_desc_fc *resp; const struct scmi_protocol_instance *pi = ph_to_pi(ph); + /* Check if the MSG_ID supports fastchannel */ + ret = scmi_protocol_msg_check(ph, message_id, &attributes); + SCMI_QUIRK(perf_level_get_fc_force, QUIRK_PERF_FC_FORCE); + if (ret || !MSG_SUPPORTS_FASTCHANNEL(attributes)) { + dev_dbg(ph->dev, + "Skip FC init for 0x%02X/%d domain:%d - ret:%d\n", + pi->proto->id, message_id, domain, ret); + return; + } + if (!p_addr) { ret = -EINVAL; goto err_out; @@ -2003,39 +2050,6 @@ static void scmi_common_fastchannel_db_ring(struct scmi_fc_db_info *db) SCMI_PROTO_FC_RING_DB(64); } -/** - * scmi_protocol_msg_check - Check protocol message attributes - * - * @ph: A reference to the protocol handle. - * @message_id: The ID of the message to check. - * @attributes: A parameter to optionally return the retrieved message - * attributes, in case of Success. - * - * An helper to check protocol message attributes for a specific protocol - * and message pair. - * - * Return: 0 on SUCCESS - */ -static int scmi_protocol_msg_check(const struct scmi_protocol_handle *ph, - u32 message_id, u32 *attributes) -{ - int ret; - struct scmi_xfer *t; - - ret = xfer_get_init(ph, PROTOCOL_MESSAGE_ATTRIBUTES, - sizeof(__le32), 0, &t); - if (ret) - return ret; - - put_unaligned_le32(message_id, t->tx.buf); - ret = do_xfer(ph, t); - if (!ret && attributes) - *attributes = get_unaligned_le32(t->rx.buf); - xfer_put(ph, t); - - return ret; -} - static const struct scmi_proto_helpers_ops helpers_ops = { .extended_name_get = scmi_common_extended_name_get, .get_max_msg_size = scmi_common_get_max_msg_size, @@ -2828,9 +2842,8 @@ static int scmi_bus_notifier(struct notifier_block *nb, struct scmi_info *info = bus_nb_to_scmi_info(nb); struct scmi_device *sdev = to_scmi_dev(data); - /* Skip transport devices and devices of different SCMI instances */ - if (!strncmp(sdev->name, "__scmi_transport_device", 23) || - sdev->dev.parent != info->dev) + /* Skip devices of different SCMI instances */ + if (sdev->dev.parent != info->dev) return NOTIFY_DONE; switch (action) { @@ -3101,6 +3114,18 @@ static const struct scmi_desc *scmi_transport_setup(struct device *dev) return &trans->desc; } +static void scmi_enable_matching_quirks(struct scmi_info *info) +{ + struct scmi_revision_info *rev = &info->version; + + dev_dbg(info->dev, "Looking for quirks matching: %s/%s/0x%08X\n", + rev->vendor_id, rev->sub_vendor_id, rev->impl_ver); + + /* Enable applicable quirks */ + scmi_quirks_enable(info->dev, rev->vendor_id, + rev->sub_vendor_id, rev->impl_ver); +} + static int scmi_probe(struct platform_device *pdev) { int ret; @@ -3222,6 +3247,8 @@ static int scmi_probe(struct platform_device *pdev) list_add_tail(&info->node, &scmi_list); mutex_unlock(&scmi_list_mutex); + scmi_enable_matching_quirks(info); + for_each_available_child_of_node(np, child) { u32 prot_id; @@ -3380,6 +3407,8 @@ static struct dentry *scmi_debugfs_init(void) static int __init scmi_driver_init(void) { + scmi_quirks_initialize(); + /* Bail out if no SCMI transport was configured */ if (WARN_ON(!IS_ENABLED(CONFIG_ARM_SCMI_HAVE_TRANSPORT))) return -EINVAL; diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h index aaee57cdcd55..d62c4469d1fd 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -31,6 +31,8 @@ #define SCMI_PROTOCOL_VENDOR_BASE 0x80 +#define MSG_SUPPORTS_FASTCHANNEL(x) ((x) & BIT(0)) + enum scmi_common_cmd { PROTOCOL_VERSION = 0x0, PROTOCOL_ATTRIBUTES = 0x1, diff --git a/drivers/firmware/arm_scmi/quirks.c b/drivers/firmware/arm_scmi/quirks.c new file mode 100644 index 000000000000..03960aca3610 --- /dev/null +++ b/drivers/firmware/arm_scmi/quirks.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Message Protocol Quirks + * + * Copyright (C) 2025 ARM Ltd. + */ + +/** + * DOC: Theory of operation + * + * A framework to define SCMI quirks and their activation conditions based on + * existing static_keys kernel facilities. + * + * Quirks are named and their activation conditions defined using the macro + * DEFINE_SCMI_QUIRK() in this file. + * + * After a quirk is defined, a corresponding entry must also be added to the + * global @scmi_quirks_table in this file using __DECLARE_SCMI_QUIRK_ENTRY(). + * + * Additionally a corresponding quirk declaration must be added also to the + * quirk.h file using DECLARE_SCMI_QUIRK(). + * + * The needed quirk code-snippet itself will be defined local to the SCMI code + * that is meant to fix and will be associated to the previously defined quirk + * and related activation conditions using the macro SCMI_QUIRK(). + * + * At runtime, during the SCMI stack probe sequence, once the SCMI Server had + * advertised the running platform Vendor, SubVendor and Implementation Version + * data, all the defined quirks matching the activation conditions will be + * enabled. + * + * Example + * + * quirk.c + * ------- + * DEFINE_SCMI_QUIRK(fix_me, "vendor", "subvend", "0x12000-0x30000", + * "someone,plat_A", "another,plat_b", "vend,sku"); + * + * static struct scmi_quirk *scmi_quirks_table[] = { + * ... + * __DECLARE_SCMI_QUIRK_ENTRY(fix_me), + * NULL + * }; + * + * quirk.h + * ------- + * DECLARE_SCMI_QUIRK(fix_me); + * + * <somewhere_in_the_scmi_stack.c> + * ------------------------------ + * + * #define QUIRK_CODE_SNIPPET_FIX_ME() \ + * ({ \ + * if (p->condition) \ + * a_ptr->calculated_val = 123; \ + * }) + * + * + * int some_function_to_fix(int param, struct something *p) + * { + * struct some_strut *a_ptr; + * + * a_ptr = some_load_func(p); + * SCMI_QUIRK(fix_me, QUIRK_CODE_SNIPPET_FIX_ME); + * some_more_func(a_ptr); + * ... + * + * return 0; + * } + * + */ + +#include <linux/ctype.h> +#include <linux/device.h> +#include <linux/export.h> +#include <linux/hashtable.h> +#include <linux/kstrtox.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/static_key.h> +#include <linux/string.h> +#include <linux/stringhash.h> +#include <linux/types.h> + +#include "quirks.h" + +#define SCMI_QUIRKS_HT_SZ 4 + +struct scmi_quirk { + bool enabled; + const char *name; + char *vendor; + char *sub_vendor_id; + char *impl_ver_range; + u32 start_range; + u32 end_range; + struct static_key_false *key; + struct hlist_node hash; + unsigned int hkey; + const char *const compats[]; +}; + +#define __DEFINE_SCMI_QUIRK_ENTRY(_qn, _ven, _sub, _impl, ...) \ + static struct scmi_quirk scmi_quirk_entry_ ## _qn = { \ + .name = __stringify(quirk_ ## _qn), \ + .vendor = _ven, \ + .sub_vendor_id = _sub, \ + .impl_ver_range = _impl, \ + .key = &(scmi_quirk_ ## _qn), \ + .compats = { __VA_ARGS__ __VA_OPT__(,) NULL }, \ + } + +#define __DECLARE_SCMI_QUIRK_ENTRY(_qn) (&(scmi_quirk_entry_ ## _qn)) + +/* + * Define a quirk by name and provide the matching tokens where: + * + * _qn: A string which will be used to build the quirk and the global + * static_key names. + * _ven : SCMI Vendor ID string match, NULL means any. + * _sub : SCMI SubVendor ID string match, NULL means any. + * _impl : SCMI Implementation Version string match, NULL means any. + * This string can be used to express version ranges which will be + * interpreted as follows: + * + * NULL [0, 0xFFFFFFFF] + * "X" [X, X] + * "X-" [X, 0xFFFFFFFF] + * "-X" [0, X] + * "X-Y" [X, Y] + * + * with X <= Y and <v> in [X, Y] meaning X <= <v> <= Y + * + * ... : An optional variadic macros argument used to provide a comma-separated + * list of compatible strings matches; when no variadic argument is + * provided, ANY compatible will match this quirk. + * + * This implicitly define also a properly named global static-key that + * will be used to dynamically enable the quirk at initialization time. + * + * Note that it is possible to associate multiple quirks to the same + * matching pattern, if your firmware quality is really astounding :P + * + * Example: + * + * Compatibles list NOT provided, so ANY compatible will match: + * + * DEFINE_SCMI_QUIRK(my_new_issue, "Vend", "SVend", "0x12000-0x30000"); + * + * + * A few compatibles provided to match against: + * + * DEFINE_SCMI_QUIRK(my_new_issue, "Vend", "SVend", "0x12000-0x30000", + * "xvend,plat_a", "xvend,plat_b", "xvend,sku_name"); + */ +#define DEFINE_SCMI_QUIRK(_qn, _ven, _sub, _impl, ...) \ + DEFINE_STATIC_KEY_FALSE(scmi_quirk_ ## _qn); \ + __DEFINE_SCMI_QUIRK_ENTRY(_qn, _ven, _sub, _impl, ##__VA_ARGS__) + +/* + * Same as DEFINE_SCMI_QUIRK but EXPORTED: this is meant to address quirks + * that possibly reside in code that is included in loadable kernel modules + * that needs to be able to access the global static keys at runtime to + * determine if enabled or not. (see SCMI_QUIRK to understand usage) + */ +#define DEFINE_SCMI_QUIRK_EXPORTED(_qn, _ven, _sub, _impl, ...) \ + DEFINE_STATIC_KEY_FALSE(scmi_quirk_ ## _qn); \ + EXPORT_SYMBOL_GPL(scmi_quirk_ ## _qn); \ + __DEFINE_SCMI_QUIRK_ENTRY(_qn, _ven, _sub, _impl, ##__VA_ARGS__) + +/* Global Quirks Definitions */ +DEFINE_SCMI_QUIRK(clock_rates_triplet_out_of_spec, NULL, NULL, NULL); +DEFINE_SCMI_QUIRK(perf_level_get_fc_force, "Qualcomm", NULL, "0x20000-"); + +/* + * Quirks Pointers Array + * + * This is filled at compile-time with the list of pointers to all the currently + * defined quirks descriptors. + */ +static struct scmi_quirk *scmi_quirks_table[] = { + __DECLARE_SCMI_QUIRK_ENTRY(clock_rates_triplet_out_of_spec), + __DECLARE_SCMI_QUIRK_ENTRY(perf_level_get_fc_force), + NULL +}; + +/* + * Quirks HashTable + * + * A run-time populated hashtable containing all the defined quirks descriptors + * hashed by matching pattern. + */ +static DEFINE_READ_MOSTLY_HASHTABLE(scmi_quirks_ht, SCMI_QUIRKS_HT_SZ); + +static unsigned int scmi_quirk_signature(const char *vend, const char *sub_vend) +{ + char *signature, *p; + unsigned int hash32; + unsigned long hash = 0; + + /* vendor_id/sub_vendor_id guaranteed <= SCMI_SHORT_NAME_MAX_SIZE */ + signature = kasprintf(GFP_KERNEL, "|%s|%s|", vend ?: "", sub_vend ?: ""); + if (!signature) + return 0; + + pr_debug("SCMI Quirk Signature >>>%s<<<\n", signature); + + p = signature; + while (*p) + hash = partial_name_hash(tolower(*p++), hash); + hash32 = end_name_hash(hash); + + kfree(signature); + + return hash32; +} + +static int scmi_quirk_range_parse(struct scmi_quirk *quirk) +{ + const char *last, *first = quirk->impl_ver_range; + size_t len; + char *sep; + int ret; + + quirk->start_range = 0; + quirk->end_range = 0xFFFFFFFF; + len = quirk->impl_ver_range ? strlen(quirk->impl_ver_range) : 0; + if (!len) + return 0; + + last = first + len - 1; + sep = strchr(quirk->impl_ver_range, '-'); + if (sep) + *sep = '\0'; + + if (sep == first) /* -X */ + ret = kstrtouint(first + 1, 0, &quirk->end_range); + else /* X OR X- OR X-y */ + ret = kstrtouint(first, 0, &quirk->start_range); + if (ret) + return ret; + + if (!sep) + quirk->end_range = quirk->start_range; + else if (sep != last) /* x-Y */ + ret = kstrtouint(sep + 1, 0, &quirk->end_range); + + if (quirk->start_range > quirk->end_range) + return -EINVAL; + + return ret; +} + +void scmi_quirks_initialize(void) +{ + struct scmi_quirk *quirk; + int i; + + for (i = 0, quirk = scmi_quirks_table[0]; quirk; + i++, quirk = scmi_quirks_table[i]) { + int ret; + + ret = scmi_quirk_range_parse(quirk); + if (ret) { + pr_err("SCMI skip QUIRK [%s] - BAD RANGE - |%s|\n", + quirk->name, quirk->impl_ver_range); + continue; + } + quirk->hkey = scmi_quirk_signature(quirk->vendor, + quirk->sub_vendor_id); + + hash_add(scmi_quirks_ht, &quirk->hash, quirk->hkey); + + pr_debug("Registered SCMI QUIRK [%s] -- %p - Key [0x%08X] - %s/%s/[0x%08X-0x%08X]\n", + quirk->name, quirk, quirk->hkey, + quirk->vendor, quirk->sub_vendor_id, + quirk->start_range, quirk->end_range); + } + + pr_debug("SCMI Quirks initialized\n"); +} + +void scmi_quirks_enable(struct device *dev, const char *vend, + const char *subv, const u32 impl) +{ + for (int i = 3; i >= 0; i--) { + struct scmi_quirk *quirk; + unsigned int hkey; + + hkey = scmi_quirk_signature(i > 1 ? vend : NULL, + i > 2 ? subv : NULL); + + /* + * Note that there could be multiple matches so we + * will enable multiple quirk part of a hash collision + * domain...BUT we cannot assume that ALL quirks on the + * same collision domain are a full match. + */ + hash_for_each_possible(scmi_quirks_ht, quirk, hash, hkey) { + if (quirk->enabled || quirk->hkey != hkey || + impl < quirk->start_range || + impl > quirk->end_range) + continue; + + if (quirk->compats[0] && + !of_machine_compatible_match(quirk->compats)) + continue; + + dev_info(dev, "Enabling SCMI Quirk [%s]\n", + quirk->name); + + dev_dbg(dev, + "Quirk matched on: %s/%s/%s/[0x%08X-0x%08X]\n", + quirk->compats[0], quirk->vendor, + quirk->sub_vendor_id, + quirk->start_range, quirk->end_range); + + static_branch_enable(quirk->key); + quirk->enabled = true; + } + } +} diff --git a/drivers/firmware/arm_scmi/quirks.h b/drivers/firmware/arm_scmi/quirks.h new file mode 100644 index 000000000000..a71fde85a527 --- /dev/null +++ b/drivers/firmware/arm_scmi/quirks.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * System Control and Management Interface (SCMI) Message Protocol Quirks + * + * Copyright (C) 2025 ARM Ltd. + */ +#ifndef _SCMI_QUIRKS_H +#define _SCMI_QUIRKS_H + +#include <linux/static_key.h> +#include <linux/types.h> + +#ifdef CONFIG_ARM_SCMI_QUIRKS + +#define DECLARE_SCMI_QUIRK(_qn) \ + DECLARE_STATIC_KEY_FALSE(scmi_quirk_ ## _qn) + +/* + * A helper to associate the actual code snippet to use as a quirk + * named as _qn. + */ +#define SCMI_QUIRK(_qn, _blk) \ + do { \ + if (static_branch_unlikely(&(scmi_quirk_ ## _qn))) \ + (_blk); \ + } while (0) + +void scmi_quirks_initialize(void); +void scmi_quirks_enable(struct device *dev, const char *vend, + const char *subv, const u32 impl); + +#else + +#define DECLARE_SCMI_QUIRK(_qn) +/* Force quirks compilation even when SCMI Quirks are disabled */ +#define SCMI_QUIRK(_qn, _blk) \ + do { \ + if (0) \ + (_blk); \ + } while (0) + +static inline void scmi_quirks_initialize(void) { } +static inline void scmi_quirks_enable(struct device *dev, const char *vend, + const char *sub_vend, const u32 impl) { } + +#endif /* CONFIG_ARM_SCMI_QUIRKS */ + +/* Quirk delarations */ +DECLARE_SCMI_QUIRK(clock_rates_triplet_out_of_spec); +DECLARE_SCMI_QUIRK(perf_level_get_fc_force); + +#endif /* _SCMI_QUIRKS_H */ diff --git a/drivers/firmware/arm_scmi/raw_mode.c b/drivers/firmware/arm_scmi/raw_mode.c index 7cc0d616b8de..3d543b1d8947 100644 --- a/drivers/firmware/arm_scmi/raw_mode.c +++ b/drivers/firmware/arm_scmi/raw_mode.c @@ -671,11 +671,13 @@ static int scmi_do_xfer_raw_start(struct scmi_raw_mode_info *raw, * @len: Length of the message in @buf. * @chan_id: The channel ID to use. * @async: A flag stating if an asynchronous command is required. + * @poll: A flag stating if a polling transmission is required. * * Return: 0 on Success */ static int scmi_raw_message_send(struct scmi_raw_mode_info *raw, - void *buf, size_t len, u8 chan_id, bool async) + void *buf, size_t len, u8 chan_id, + bool async, bool poll) { int ret; struct scmi_xfer *xfer; @@ -684,6 +686,16 @@ static int scmi_raw_message_send(struct scmi_raw_mode_info *raw, if (ret) return ret; + if (poll) { + if (is_transport_polling_capable(raw->desc)) { + xfer->hdr.poll_completion = true; + } else { + dev_err(raw->handle->dev, + "Failed to send RAW message - Polling NOT supported\n"); + return -EINVAL; + } + } + ret = scmi_do_xfer_raw_start(raw, xfer, chan_id, async); if (ret) scmi_xfer_raw_put(raw->handle, xfer); @@ -801,7 +813,7 @@ static ssize_t scmi_dbg_raw_mode_common_read(struct file *filp, static ssize_t scmi_dbg_raw_mode_common_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos, - bool async) + bool async, bool poll) { int ret; struct scmi_dbg_raw_data *rd = filp->private_data; @@ -831,7 +843,7 @@ static ssize_t scmi_dbg_raw_mode_common_write(struct file *filp, } ret = scmi_raw_message_send(rd->raw, rd->tx.buf, rd->tx_size, - rd->chan_id, async); + rd->chan_id, async, poll); /* Reset ppos for next message ... */ rd->tx_size = 0; @@ -875,7 +887,8 @@ static ssize_t scmi_dbg_raw_mode_message_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { - return scmi_dbg_raw_mode_common_write(filp, buf, count, ppos, false); + return scmi_dbg_raw_mode_common_write(filp, buf, count, ppos, + false, false); } static __poll_t scmi_dbg_raw_mode_message_poll(struct file *filp, @@ -964,7 +977,8 @@ static ssize_t scmi_dbg_raw_mode_message_async_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { - return scmi_dbg_raw_mode_common_write(filp, buf, count, ppos, true); + return scmi_dbg_raw_mode_common_write(filp, buf, count, ppos, + true, false); } static const struct file_operations scmi_dbg_raw_mode_message_async_fops = { @@ -976,6 +990,40 @@ static const struct file_operations scmi_dbg_raw_mode_message_async_fops = { .owner = THIS_MODULE, }; +static ssize_t scmi_dbg_raw_mode_message_poll_write(struct file *filp, + const char __user *buf, + size_t count, loff_t *ppos) +{ + return scmi_dbg_raw_mode_common_write(filp, buf, count, ppos, + false, true); +} + +static const struct file_operations scmi_dbg_raw_mode_message_poll_fops = { + .open = scmi_dbg_raw_mode_open, + .release = scmi_dbg_raw_mode_release, + .read = scmi_dbg_raw_mode_message_read, + .write = scmi_dbg_raw_mode_message_poll_write, + .poll = scmi_dbg_raw_mode_message_poll, + .owner = THIS_MODULE, +}; + +static ssize_t scmi_dbg_raw_mode_message_poll_async_write(struct file *filp, + const char __user *buf, + size_t count, loff_t *ppos) +{ + return scmi_dbg_raw_mode_common_write(filp, buf, count, ppos, + true, true); +} + +static const struct file_operations scmi_dbg_raw_mode_message_poll_async_fops = { + .open = scmi_dbg_raw_mode_open, + .release = scmi_dbg_raw_mode_release, + .read = scmi_dbg_raw_mode_message_read, + .write = scmi_dbg_raw_mode_message_poll_async_write, + .poll = scmi_dbg_raw_mode_message_poll, + .owner = THIS_MODULE, +}; + static ssize_t scmi_test_dbg_raw_mode_notif_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) @@ -1199,6 +1247,12 @@ void *scmi_raw_mode_init(const struct scmi_handle *handle, debugfs_create_file("message_async", 0600, raw->dentry, raw, &scmi_dbg_raw_mode_message_async_fops); + debugfs_create_file("message_poll", 0600, raw->dentry, raw, + &scmi_dbg_raw_mode_message_poll_fops); + + debugfs_create_file("message_poll_async", 0600, raw->dentry, raw, + &scmi_dbg_raw_mode_message_poll_async_fops); + debugfs_create_file("notification", 0400, raw->dentry, raw, &scmi_dbg_raw_mode_notification_fops); @@ -1230,6 +1284,14 @@ void *scmi_raw_mode_init(const struct scmi_handle *handle, debugfs_create_file_aux_num("message_async", 0600, chd, raw, channels[i], &scmi_dbg_raw_mode_message_async_fops); + + debugfs_create_file_aux_num("message_poll", 0600, chd, + raw, channels[i], + &scmi_dbg_raw_mode_message_poll_fops); + + debugfs_create_file_aux_num("message_poll_async", 0600, + chd, raw, channels[i], + &scmi_dbg_raw_mode_message_poll_async_fops); } } diff --git a/drivers/firmware/arm_scmi/vendors/imx/Kconfig b/drivers/firmware/arm_scmi/vendors/imx/Kconfig index a01bf5e47301..c34c8c837441 100644 --- a/drivers/firmware/arm_scmi/vendors/imx/Kconfig +++ b/drivers/firmware/arm_scmi/vendors/imx/Kconfig @@ -12,6 +12,30 @@ config IMX_SCMI_BBM_EXT To compile this driver as a module, choose M here: the module will be called imx-sm-bbm. +config IMX_SCMI_CPU_EXT + tristate "i.MX SCMI CPU EXTENSION" + depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF) + depends on IMX_SCMI_CPU_DRV + default y if ARCH_MXC + help + This enables i.MX System CPU Protocol to manage cpu + start, stop and etc. + + To compile this driver as a module, choose M here: the + module will be called imx-sm-cpu. + +config IMX_SCMI_LMM_EXT + tristate "i.MX SCMI LMM EXTENSION" + depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF) + depends on IMX_SCMI_LMM_DRV + default y if ARCH_MXC + help + This enables i.MX System Logical Machine Protocol to + manage Logical Machines boot, shutdown and etc. + + To compile this driver as a module, choose M here: the + module will be called imx-sm-lmm. + config IMX_SCMI_MISC_EXT tristate "i.MX SCMI MISC EXTENSION" depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF) diff --git a/drivers/firmware/arm_scmi/vendors/imx/Makefile b/drivers/firmware/arm_scmi/vendors/imx/Makefile index d3ee6d544924..e3a5ea46345c 100644 --- a/drivers/firmware/arm_scmi/vendors/imx/Makefile +++ b/drivers/firmware/arm_scmi/vendors/imx/Makefile @@ -1,3 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_IMX_SCMI_BBM_EXT) += imx-sm-bbm.o +obj-$(CONFIG_IMX_SCMI_CPU_EXT) += imx-sm-cpu.o +obj-$(CONFIG_IMX_SCMI_LMM_EXT) += imx-sm-lmm.o obj-$(CONFIG_IMX_SCMI_MISC_EXT) += imx-sm-misc.o diff --git a/drivers/firmware/arm_scmi/vendors/imx/imx-sm-cpu.c b/drivers/firmware/arm_scmi/vendors/imx/imx-sm-cpu.c new file mode 100644 index 000000000000..66f47f5371e5 --- /dev/null +++ b/drivers/firmware/arm_scmi/vendors/imx/imx-sm-cpu.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System control and Management Interface (SCMI) NXP CPU Protocol + * + * Copyright 2025 NXP + */ + +#include <linux/bits.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/scmi_protocol.h> +#include <linux/scmi_imx_protocol.h> + +#include "../../protocols.h" +#include "../../notify.h" + +#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000 + +enum scmi_imx_cpu_protocol_cmd { + SCMI_IMX_CPU_ATTRIBUTES = 0x3, + SCMI_IMX_CPU_START = 0x4, + SCMI_IMX_CPU_STOP = 0x5, + SCMI_IMX_CPU_RESET_VECTOR_SET = 0x6, + SCMI_IMX_CPU_INFO_GET = 0xC, +}; + +struct scmi_imx_cpu_info { + u32 nr_cpu; +}; + +#define SCMI_IMX_CPU_NR_CPU_MASK GENMASK(15, 0) +struct scmi_msg_imx_cpu_protocol_attributes { + __le32 attributes; +}; + +struct scmi_msg_imx_cpu_attributes_out { + __le32 attributes; +#define CPU_MAX_NAME 16 + u8 name[CPU_MAX_NAME]; +}; + +struct scmi_imx_cpu_reset_vector_set_in { + __le32 cpuid; +#define CPU_VEC_FLAGS_RESUME BIT(31) +#define CPU_VEC_FLAGS_START BIT(30) +#define CPU_VEC_FLAGS_BOOT BIT(29) + __le32 flags; + __le32 resetvectorlow; + __le32 resetvectorhigh; +}; + +struct scmi_imx_cpu_info_get_out { +#define CPU_RUN_MODE_START 0 +#define CPU_RUN_MODE_HOLD 1 +#define CPU_RUN_MODE_STOP 2 +#define CPU_RUN_MODE_SLEEP 3 + __le32 runmode; + __le32 sleepmode; + __le32 resetvectorlow; + __le32 resetvectorhigh; +}; + +static int scmi_imx_cpu_validate_cpuid(const struct scmi_protocol_handle *ph, + u32 cpuid) +{ + struct scmi_imx_cpu_info *info = ph->get_priv(ph); + + if (cpuid >= info->nr_cpu) + return -EINVAL; + + return 0; +} + +static int scmi_imx_cpu_start(const struct scmi_protocol_handle *ph, + u32 cpuid, bool start) +{ + struct scmi_xfer *t; + u8 msg_id; + int ret; + + ret = scmi_imx_cpu_validate_cpuid(ph, cpuid); + if (ret) + return ret; + + if (start) + msg_id = SCMI_IMX_CPU_START; + else + msg_id = SCMI_IMX_CPU_STOP; + + ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(u32), 0, &t); + if (ret) + return ret; + + put_unaligned_le32(cpuid, t->tx.buf); + ret = ph->xops->do_xfer(ph, t); + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_imx_cpu_reset_vector_set(const struct scmi_protocol_handle *ph, + u32 cpuid, u64 vector, bool start, + bool boot, bool resume) +{ + struct scmi_imx_cpu_reset_vector_set_in *in; + struct scmi_xfer *t; + int ret; + + ret = scmi_imx_cpu_validate_cpuid(ph, cpuid); + if (ret) + return ret; + + ret = ph->xops->xfer_get_init(ph, SCMI_IMX_CPU_RESET_VECTOR_SET, sizeof(*in), + 0, &t); + if (ret) + return ret; + + in = t->tx.buf; + in->cpuid = cpu_to_le32(cpuid); + in->flags = cpu_to_le32(0); + if (start) + in->flags |= le32_encode_bits(1, CPU_VEC_FLAGS_START); + if (boot) + in->flags |= le32_encode_bits(1, CPU_VEC_FLAGS_BOOT); + if (resume) + in->flags |= le32_encode_bits(1, CPU_VEC_FLAGS_RESUME); + in->resetvectorlow = cpu_to_le32(lower_32_bits(vector)); + in->resetvectorhigh = cpu_to_le32(upper_32_bits(vector)); + ret = ph->xops->do_xfer(ph, t); + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_imx_cpu_started(const struct scmi_protocol_handle *ph, u32 cpuid, + bool *started) +{ + struct scmi_imx_cpu_info_get_out *out; + struct scmi_xfer *t; + u32 mode; + int ret; + + if (!started) + return -EINVAL; + + *started = false; + ret = scmi_imx_cpu_validate_cpuid(ph, cpuid); + if (ret) + return ret; + + ret = ph->xops->xfer_get_init(ph, SCMI_IMX_CPU_INFO_GET, sizeof(u32), + 0, &t); + if (ret) + return ret; + + put_unaligned_le32(cpuid, t->tx.buf); + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + out = t->rx.buf; + mode = le32_to_cpu(out->runmode); + if (mode == CPU_RUN_MODE_START || mode == CPU_RUN_MODE_SLEEP) + *started = true; + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static const struct scmi_imx_cpu_proto_ops scmi_imx_cpu_proto_ops = { + .cpu_reset_vector_set = scmi_imx_cpu_reset_vector_set, + .cpu_start = scmi_imx_cpu_start, + .cpu_started = scmi_imx_cpu_started, +}; + +static int scmi_imx_cpu_protocol_attributes_get(const struct scmi_protocol_handle *ph, + struct scmi_imx_cpu_info *info) +{ + struct scmi_msg_imx_cpu_protocol_attributes *attr; + struct scmi_xfer *t; + int ret; + + ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0, + sizeof(*attr), &t); + if (ret) + return ret; + + attr = t->rx.buf; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + info->nr_cpu = le32_get_bits(attr->attributes, SCMI_IMX_CPU_NR_CPU_MASK); + dev_info(ph->dev, "i.MX SM CPU: %d cpus\n", + info->nr_cpu); + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_imx_cpu_attributes_get(const struct scmi_protocol_handle *ph, + u32 cpuid) +{ + struct scmi_msg_imx_cpu_attributes_out *out; + char name[SCMI_SHORT_NAME_MAX_SIZE] = {'\0'}; + struct scmi_xfer *t; + int ret; + + ret = ph->xops->xfer_get_init(ph, SCMI_IMX_CPU_ATTRIBUTES, sizeof(u32), 0, &t); + if (ret) + return ret; + + put_unaligned_le32(cpuid, t->tx.buf); + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + out = t->rx.buf; + strscpy(name, out->name, SCMI_SHORT_NAME_MAX_SIZE); + dev_info(ph->dev, "i.MX CPU: name: %s\n", name); + } else { + dev_err(ph->dev, "i.MX cpu: Failed to get info of cpu(%u)\n", cpuid); + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_imx_cpu_protocol_init(const struct scmi_protocol_handle *ph) +{ + struct scmi_imx_cpu_info *info; + u32 version; + int ret, i; + + ret = ph->xops->version_get(ph, &version); + if (ret) + return ret; + + dev_info(ph->dev, "NXP SM CPU Protocol Version %d.%d\n", + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); + + info = devm_kzalloc(ph->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = scmi_imx_cpu_protocol_attributes_get(ph, info); + if (ret) + return ret; + + for (i = 0; i < info->nr_cpu; i++) { + ret = scmi_imx_cpu_attributes_get(ph, i); + if (ret) + return ret; + } + + return ph->set_priv(ph, info, version); +} + +static const struct scmi_protocol scmi_imx_cpu = { + .id = SCMI_PROTOCOL_IMX_CPU, + .owner = THIS_MODULE, + .instance_init = &scmi_imx_cpu_protocol_init, + .ops = &scmi_imx_cpu_proto_ops, + .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION, + .vendor_id = SCMI_IMX_VENDOR, + .sub_vendor_id = SCMI_IMX_SUBVENDOR, +}; +module_scmi_protocol(scmi_imx_cpu); + +MODULE_ALIAS("scmi-protocol-" __stringify(SCMI_PROTOCOL_IMX_CPU) "-" SCMI_IMX_VENDOR); +MODULE_DESCRIPTION("i.MX SCMI CPU driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/arm_scmi/vendors/imx/imx-sm-lmm.c b/drivers/firmware/arm_scmi/vendors/imx/imx-sm-lmm.c new file mode 100644 index 000000000000..b519c67fe920 --- /dev/null +++ b/drivers/firmware/arm_scmi/vendors/imx/imx-sm-lmm.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System control and Management Interface (SCMI) NXP LMM Protocol + * + * Copyright 2025 NXP + */ + +#include <linux/bits.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/scmi_protocol.h> +#include <linux/scmi_imx_protocol.h> + +#include "../../protocols.h" +#include "../../notify.h" + +#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000 + +enum scmi_imx_lmm_protocol_cmd { + SCMI_IMX_LMM_ATTRIBUTES = 0x3, + SCMI_IMX_LMM_BOOT = 0x4, + SCMI_IMX_LMM_RESET = 0x5, + SCMI_IMX_LMM_SHUTDOWN = 0x6, + SCMI_IMX_LMM_WAKE = 0x7, + SCMI_IMX_LMM_SUSPEND = 0x8, + SCMI_IMX_LMM_NOTIFY = 0x9, + SCMI_IMX_LMM_RESET_REASON = 0xA, + SCMI_IMX_LMM_POWER_ON = 0xB, + SCMI_IMX_LMM_RESET_VECTOR_SET = 0xC, +}; + +struct scmi_imx_lmm_priv { + u32 nr_lmm; +}; + +#define SCMI_IMX_LMM_NR_LM_MASK GENMASK(5, 0) +#define SCMI_IMX_LMM_NR_MAX 16 +struct scmi_msg_imx_lmm_protocol_attributes { + __le32 attributes; +}; + +struct scmi_msg_imx_lmm_attributes_out { + __le32 lmid; + __le32 attributes; + __le32 state; + __le32 errstatus; + u8 name[LMM_MAX_NAME]; +}; + +struct scmi_imx_lmm_reset_vector_set_in { + __le32 lmid; + __le32 cpuid; + __le32 flags; /* reserved for future extension */ + __le32 resetvectorlow; + __le32 resetvectorhigh; +}; + +struct scmi_imx_lmm_shutdown_in { + __le32 lmid; +#define SCMI_IMX_LMM_SHUTDOWN_GRACEFUL BIT(0) + __le32 flags; +}; + +static int scmi_imx_lmm_validate_lmid(const struct scmi_protocol_handle *ph, u32 lmid) +{ + struct scmi_imx_lmm_priv *priv = ph->get_priv(ph); + + if (lmid >= priv->nr_lmm) + return -EINVAL; + + return 0; +} + +static int scmi_imx_lmm_attributes(const struct scmi_protocol_handle *ph, + u32 lmid, struct scmi_imx_lmm_info *info) +{ + struct scmi_msg_imx_lmm_attributes_out *out; + struct scmi_xfer *t; + int ret; + + ret = ph->xops->xfer_get_init(ph, SCMI_IMX_LMM_ATTRIBUTES, sizeof(u32), 0, &t); + if (ret) + return ret; + + put_unaligned_le32(lmid, t->tx.buf); + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + out = t->rx.buf; + info->lmid = le32_to_cpu(out->lmid); + info->state = le32_to_cpu(out->state); + info->errstatus = le32_to_cpu(out->errstatus); + strscpy(info->name, out->name); + dev_dbg(ph->dev, "i.MX LMM: Logical Machine(%d), name: %s\n", + info->lmid, info->name); + } else { + dev_err(ph->dev, "i.MX LMM: Failed to get info of Logical Machine(%u)\n", lmid); + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int +scmi_imx_lmm_power_boot(const struct scmi_protocol_handle *ph, u32 lmid, bool boot) +{ + struct scmi_xfer *t; + u8 msg_id; + int ret; + + ret = scmi_imx_lmm_validate_lmid(ph, lmid); + if (ret) + return ret; + + if (boot) + msg_id = SCMI_IMX_LMM_BOOT; + else + msg_id = SCMI_IMX_LMM_POWER_ON; + + ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(u32), 0, &t); + if (ret) + return ret; + + put_unaligned_le32(lmid, t->tx.buf); + ret = ph->xops->do_xfer(ph, t); + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_imx_lmm_reset_vector_set(const struct scmi_protocol_handle *ph, + u32 lmid, u32 cpuid, u32 flags, u64 vector) +{ + struct scmi_imx_lmm_reset_vector_set_in *in; + struct scmi_xfer *t; + int ret; + + ret = ph->xops->xfer_get_init(ph, SCMI_IMX_LMM_RESET_VECTOR_SET, sizeof(*in), + 0, &t); + if (ret) + return ret; + + in = t->tx.buf; + in->lmid = cpu_to_le32(lmid); + in->cpuid = cpu_to_le32(cpuid); + in->flags = cpu_to_le32(0); + in->resetvectorlow = cpu_to_le32(lower_32_bits(vector)); + in->resetvectorhigh = cpu_to_le32(upper_32_bits(vector)); + ret = ph->xops->do_xfer(ph, t); + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_imx_lmm_shutdown(const struct scmi_protocol_handle *ph, u32 lmid, + u32 flags) +{ + struct scmi_imx_lmm_shutdown_in *in; + struct scmi_xfer *t; + int ret; + + ret = scmi_imx_lmm_validate_lmid(ph, lmid); + if (ret) + return ret; + + ret = ph->xops->xfer_get_init(ph, SCMI_IMX_LMM_SHUTDOWN, sizeof(*in), + 0, &t); + if (ret) + return ret; + + in = t->tx.buf; + in->lmid = cpu_to_le32(lmid); + if (flags & SCMI_IMX_LMM_SHUTDOWN_GRACEFUL) + in->flags = cpu_to_le32(SCMI_IMX_LMM_SHUTDOWN_GRACEFUL); + else + in->flags = cpu_to_le32(0); + ret = ph->xops->do_xfer(ph, t); + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static const struct scmi_imx_lmm_proto_ops scmi_imx_lmm_proto_ops = { + .lmm_power_boot = scmi_imx_lmm_power_boot, + .lmm_info = scmi_imx_lmm_attributes, + .lmm_reset_vector_set = scmi_imx_lmm_reset_vector_set, + .lmm_shutdown = scmi_imx_lmm_shutdown, +}; + +static int scmi_imx_lmm_protocol_attributes_get(const struct scmi_protocol_handle *ph, + struct scmi_imx_lmm_priv *priv) +{ + struct scmi_msg_imx_lmm_protocol_attributes *attr; + struct scmi_xfer *t; + int ret; + + ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0, + sizeof(*attr), &t); + if (ret) + return ret; + + attr = t->rx.buf; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + priv->nr_lmm = le32_get_bits(attr->attributes, SCMI_IMX_LMM_NR_LM_MASK); + if (priv->nr_lmm > SCMI_IMX_LMM_NR_MAX) { + dev_err(ph->dev, "i.MX LMM: %d:Exceed max supported Logical Machines\n", + priv->nr_lmm); + ret = -EINVAL; + } else { + dev_info(ph->dev, "i.MX LMM: %d Logical Machines\n", priv->nr_lmm); + } + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_imx_lmm_protocol_init(const struct scmi_protocol_handle *ph) +{ + struct scmi_imx_lmm_priv *info; + u32 version; + int ret; + + ret = ph->xops->version_get(ph, &version); + if (ret) + return ret; + + dev_info(ph->dev, "NXP SM LMM Version %d.%d\n", + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); + + info = devm_kzalloc(ph->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = scmi_imx_lmm_protocol_attributes_get(ph, info); + if (ret) + return ret; + + return ph->set_priv(ph, info, version); +} + +static const struct scmi_protocol scmi_imx_lmm = { + .id = SCMI_PROTOCOL_IMX_LMM, + .owner = THIS_MODULE, + .instance_init = &scmi_imx_lmm_protocol_init, + .ops = &scmi_imx_lmm_proto_ops, + .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION, + .vendor_id = SCMI_IMX_VENDOR, + .sub_vendor_id = SCMI_IMX_SUBVENDOR, +}; +module_scmi_protocol(scmi_imx_lmm); + +MODULE_ALIAS("scmi-protocol-" __stringify(SCMI_PROTOCOL_IMX_LMM) "-" SCMI_IMX_VENDOR); +MODULE_DESCRIPTION("i.MX SCMI LMM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/arm_scmi/vendors/imx/imx95.rst b/drivers/firmware/arm_scmi/vendors/imx/imx95.rst index b2dfd6c46ca2..4e246a78a042 100644 --- a/drivers/firmware/arm_scmi/vendors/imx/imx95.rst +++ b/drivers/firmware/arm_scmi/vendors/imx/imx95.rst @@ -32,6 +32,518 @@ port, and deploy the SM on supported processors. The SM implements an interface compliant with the Arm SCMI Specification with additional vendor specific extensions. +System Control and Management Logical Machine Management Vendor Protocol +======================================================================== + +The SM adds the concept of logical machines (LMs). These are analogous to +VMs and each has its own instance of SCMI. All normal SCMI calls only apply +the LM running the calling agent. That includes boot, shutdown, reset, +suspend, wake, etc. If a caller makes the SCMI base call to get a list +of agents, it will only get those on that LM. Each LM is completely isolated +from the others. This is mandatory for these to operate independently. + +This protocol is intended to support boot, shutdown, and reset of other logical +machines (LM). It is usually used to allow one LM(e.g. OSPM) to manage +another LM which is usually an offload or accelerator engine. Notifications +from this protocol can also be used to manage a communication link to another +LM. The LMM protocol provides commands to: + +- Describe the protocol version. +- Discover implementation attributes. +- Discover all the LMs defined in the system. +- Boot a target LM. +- Shutdown a target LM (gracefully or forcibly). +- Reset a target LM (gracefully or forcibly). +- Wake a target LM from suspend. +- Suspend a target LM (gracefully). +- Read boot/shutdown/reset information for a target LM. +- Get notifications when a target LM boots or shuts down (e.g. LM 'X' requested + notification of LM 'Y' boots or shuts down, when LM 'Y' boots or shuts down, + SCMI firmware will send notification to LM 'X'). + +'Graceful' means asking LM itself to shutdown/reset/etc (e.g. sending +notification to Linux, Then Linux reboots or powers down itself). It is async +command that the SUCCESS of the command just means the command successfully +return, not means reboot/reset successfully finished. + +'Forceful' means the SM will force shutdown/reset/etc the LM. It is sync +command that the SUCCESS of the command means the LM has been successfully +shutdown/reset/etc. +If the commands not have Graceful/Forceful flag settings, such as WAKE, SUSEND, +it is a Graceful command. + +Commands: +_________ + +PROTOCOL_VERSION +~~~~~~~~~~~~~~~~ + +message_id: 0x0 +protocol_id: 0x80 +This command is mandatory. + ++---------------+--------------------------------------------------------------+ +|Return values | ++---------------+--------------------------------------------------------------+ +|Name |Description | ++---------------+--------------------------------------------------------------+ +|int32 status | See ARM SCMI Specification for status code definitions. | ++---------------+--------------------------------------------------------------+ +|uint32 version | For this revision of the specification, this value must be | +| | 0x10000. | ++---------------+--------------------------------------------------------------+ + +PROTOCOL_ATTRIBUTES +~~~~~~~~~~~~~~~~~~~ + +message_id: 0x1 +protocol_id: 0x80 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status | See ARM SCMI Specification for status code definitions. | ++------------------+-----------------------------------------------------------+ +|uint32 attributes |Protocol attributes: | +| |Bits[31:5] Reserved, must be zero. | +| |Bits[4:0] Number of Logical Machines | +| |Note that due to both hardware limitations and reset reason| +| |field limitations, the max number of LM is 16. The minimum | +| |is 1. | ++------------------+-----------------------------------------------------------+ + +PROTOCOL_MESSAGE_ATTRIBUTES +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +message_id: 0x2 +protocol_id: 0x80 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: in case the message is implemented and available | +| |to use. | +| |NOT_FOUND: if the message identified by message_id is | +| |invalid or not implemented | ++------------------+-----------------------------------------------------------+ +|uint32 attributes |Flags that are associated with a specific command in the | +| |protocol. For all commands in this protocol, this | +| |parameter has a value of 0 | ++------------------+-----------------------------------------------------------+ + +LMM_ATTRIBUTES +~~~~~~~~~~~~~~ + +message_id: 0x3 +protocol_id: 0x80 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 lmid |ID of the Logical Machine | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if valid attributes are returned. | +| |NOT_FOUND: if lmid not points to a valid logical machine. | +| |DENIED: if the agent does not have permission to get info | +| |for the LM specified by lmid. | ++------------------+-----------------------------------------------------------+ +|uint32 lmid |Identifier of the LM whose identification is requested. | +| |This field is: Populated with the lmid of the calling | +| |agent, when the lmid parameter passed via the command is | +| |0xFFFFFFFF. Identical to the lmid field passed via the | +| |calling parameters, in all other cases | ++------------------+-----------------------------------------------------------+ +|uint32 attributes | Bits[31:0] reserved. must be zero | ++------------------+-----------------------------------------------------------+ +|uint32 state | Current state of the LM | ++------------------+-----------------------------------------------------------+ +|uint32 errStatus | Last error status recorded | ++------------------+-----------------------------------------------------------+ +|char name[16] | A NULL terminated ASCII string with the LM name, of up | +| | to 16 bytes | ++------------------+-----------------------------------------------------------+ + +LMM_BOOT +~~~~~~~~ + +message_id: 0x4 +protocol_id: 0x80 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 lmid |ID of the Logical Machine | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if LM boots successfully started. | +| |NOT_FOUND: if lmid not points to a valid logical machine. | +| |INVALID_PARAMETERS: if lmid is same as the caller. | +| |DENIED: if the agent does not have permission to manage the| +| |the LM specified by lmid. | ++------------------+-----------------------------------------------------------+ + +LMM_RESET +~~~~~~~~~ + +message_id: 0x5 +protocol_id: 0x80 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 lmid |ID of the Logical Machine | ++------------------+-----------------------------------------------------------+ +|uint32 flags |Reset flags: | +| |Bits[31:1] Reserved, must be zero. | +| |Bit[0] Graceful request: | +| |Set to 1 if the request is a graceful request. | +| |Set to 0 if the request is a forceful request. | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: The LMM RESET command finished successfully in | +| |graceful reset or LM successfully resets in forceful reset.| +| |NOT_FOUND: if lmid not points to a valid logical machine. | +| |INVALID_PARAMETERS: if lmid is same as the caller. | +| |DENIED: if the agent does not have permission to manage the| +| |the LM specified by lmid. | ++------------------+-----------------------------------------------------------+ + +LMM_SHUTDOWN +~~~~~~~~~~~~ + +message_id: 0x6 +protocol_id: 0x80 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 lmid |ID of the Logical Machine | ++------------------+-----------------------------------------------------------+ +|uint32 flags |Reset flags: | +| |Bits[31:1] Reserved, must be zero. | +| |Bit[0] Graceful request: | +| |Set to 1 if the request is a graceful request. | +| |Set to 0 if the request is a forceful request. | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: The LMM shutdown command finished successfully in | +| |graceful request or LM successfully shutdown in forceful | +| |request. | +| |NOT_FOUND: if lmid not points to a valid logical machine. | +| |INVALID_PARAMETERS: if lmid is same as the caller. | +| |DENIED: if the agent does not have permission to manage the| +| |the LM specified by lmid. | ++------------------+-----------------------------------------------------------+ + +LMM_WAKE +~~~~~~~~ + +message_id: 0x7 +protocol_id: 0x80 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 lmid |ID of the Logical Machine | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if LM wake command successfully returns. | +| |NOT_FOUND: if lmid not points to a valid logical machine. | +| |INVALID_PARAMETERS: if lmid is same as the caller. | +| |DENIED: if the agent does not have permission to manage the| +| |the LM specified by lmid. | ++------------------+-----------------------------------------------------------+ + +LMM_SUSPEND +~~~~~~~~~~~ + +message_id: 0x8 +protocol_id: 0x80 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 lmid |ID of the Logical Machine | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if LM suspend command successfully returns. | +| |NOT_FOUND: if lmid not points to a valid logical machine. | +| |INVALID_PARAMETERS: if lmid is same as the caller. | +| |DENIED: if the agent does not have permission to manage the| +| |the LM specified by lmid. | ++------------------+-----------------------------------------------------------+ + +LMM_NOTIFY +~~~~~~~~~~ + +message_id: 0x9 +protocol_id: 0x80 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 lmid |ID of the Logical Machine | ++------------------+-----------------------------------------------------------+ +|uint32 flags |Notification flags: | +| |Bits[31:3] Reserved, must be zero. | +| |Bit[3] Wake (resume) notification: | +| |Set to 1 to send notification. | +| |Set to 0 if no notification. | +| |Bit[2] Suspend (sleep) notification: | +| |Set to 1 to send notification. | +| |Set to 0 if no notification. | +| |Bit[1] Shutdown (off) notification: | +| |Set to 1 to send notification. | +| |Set to 0 if no notification. | +| |Bit[0] Boot (on) notification: | +| |Set to 1 to send notification. | +| |Set to 0 if no notification | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if the notification state successfully updated. | +| |NOT_FOUND: if lmid not points to a valid logical machine. | +| |INVALID_PARAMETERS: if input attributes flag specifies | +| |unsupported or invalid configurations. | +| |DENIED: if the agent does not have permission to request | +| |the notification. | ++------------------+-----------------------------------------------------------+ + +LMM_RESET_REASON +~~~~~~~~~~~~~~~~ + +message_id: 0xA +protocol_id: 0x80 +This command is mandatory. + +This command is to return the reset reason that caused the last reset, such as +POR, WDOG, JTAG and etc. + ++---------------------+--------------------------------------------------------+ +|Parameters | ++---------------------+--------------------------------------------------------+ +|Name |Description | ++---------------------+--------------------------------------------------------+ +|uint32 lmid |ID of the Logical Machine | ++---------------------+--------------------------------------------------------+ +|Return values | ++---------------------+--------------------------------------------------------+ +|Name |Description | ++---------------------+--------------------------------------------------------+ +|int32 status |SUCCESS: if the reset reason of the LM successfully | +| |updated. | +| |NOT_FOUND: if lmid not points to a valid logical machine| +| |DENIED: if the agent does not have permission to request| +| |the reset reason. | ++---------------------+--------------------------------------------------------+ +|uint32 bootflags |Boot reason flags. This parameter has the format: | +| |Bits[31] Valid. | +| |Set to 1 if the entire reason is valid. | +| |Set to 0 if the entire reason is not valid. | +| |Bits[30:29] Reserved, must be zero. | +| |Bit[28] Valid origin: | +| |Set to 1 if the origin field is valid. | +| |Set to 0 if the origin field is not valid. | +| |Bits[27:24] Origin. | +| |Logical Machine(LM) ID that causes the BOOT of this LM | +| |Bit[23] Valid err ID: | +| |Set to 1 if the error ID field is valid. | +| |Set to 0 if the error ID field is not valid. | +| |Bits[22:8] Error ID(Agent ID of the system). | +| |Bit[7:0] Reason(WDOG, POR, FCCU and etc): | +| |See the SRESR register description in the System | +| |Reset Controller (SRC) section in SoC reference mannual | +| |One reason maps to BIT(reason) in SRESR | ++---------------------+--------------------------------------------------------+ +|uint32 shutdownflags |Shutdown reason flags. This parameter has the format: | +| |Bits[31] Valid. | +| |Set to 1 if the entire reason is valid. | +| |Set to 0 if the entire reason is not valid. | +| |Bits[30:29] Number of valid extended info words. | +| |Bit[28] Valid origin: | +| |Set to 1 if the origin field is valid. | +| |Set to 0 if the origin field is not valid. | +| |Bits[27:24] Origin. | +| |Logical Machine(LM) ID that causes the BOOT of this LM | +| |Bit[23] Valid err ID: | +| |Set to 1 if the error ID field is valid. | +| |Set to 0 if the error ID field is not valid. | +| |Bits[22:8] Error ID(Agent ID of the System). | +| |Bit[7:0] Reason | +| |See the SRESR register description in the System | +| |Reset Controller (SRC) section in SoC reference mannual | +| |One reason maps to BIT(reason) in SRESR | ++---------------------+--------------------------------------------------------+ +|uint32 extinfo[3] |Array of extended info words(e.g. fault pc) | ++---------------------+--------------------------------------------------------+ + +LMM_POWER_ON +~~~~~~~~~~~~ + +message_id: 0xB +protocol_id: 0x80 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 lmid |ID of the Logical Machine | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if LM successfully powers on. | +| |NOT_FOUND: if lmid not points to a valid logical machine. | +| |INVALID_PARAMETERS: if lmid is same as the caller. | +| |DENIED: if the agent does not have permission to manage the| +| |the LM specified by lmid. | ++------------------+-----------------------------------------------------------+ + +LMM_RESET_VECTOR_SET +~~~~~~~~~~~~~~~~~~~~ + +message_id: 0xC +protocol_id: 0x80 +This command is mandatory. + ++-----------------------+------------------------------------------------------+ +|Parameters | ++-----------------------+------------------------------------------------------+ +|Name |Description | ++-----------------------+------------------------------------------------------+ +|uint32 lmid |ID of the Logical Machine | ++-----------------------+------------------------------------------------------+ +|uint32 cpuid |ID of the CPU inside the LM | ++-----------------------+------------------------------------------------------+ +|uint32 flags |Reset vector flags | +| |Bits[31:0] Reserved, must be zero. | ++-----------------------+------------------------------------------------------+ +|uint32 resetVectorLow |Lower vector | ++-----------------------+------------------------------------------------------+ +|uint32 resetVectorHigh |Higher vector | ++-----------------------+------------------------------------------------------+ +|Return values | ++-----------------------+------------------------------------------------------+ +|Name |Description | ++-----------------------+------------------------------------------------------+ +|int32 status |SUCCESS: If reset vector is set successfully. | +| |NOT_FOUND: if lmid not points to a valid logical | +| |machine, or cpuId is not valid. | +| |INVALID_PARAMETERS: if reset vector is invalid. | +| |DENIED: if the agent does not have permission to set | +| |the reset vector for the CPU in the LM. | ++-----------------------+------------------------------------------------------+ + +NEGOTIATE_PROTOCOL_VERSION +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +message_id: 0x10 +protocol_id: 0x80 +This command is mandatory. + ++--------------------+---------------------------------------------------------+ +|Parameters | ++--------------------+---------------------------------------------------------+ +|Name |Description | ++--------------------+---------------------------------------------------------+ +|uint32 version |The negotiated protocol version the agent intends to use | ++--------------------+---------------------------------------------------------+ +|Return values | ++--------------------+---------------------------------------------------------+ +|Name |Description | ++--------------------+---------------------------------------------------------+ +|int32 status |SUCCESS: if the negotiated protocol version is supported | +| |by the platform. All commands, responses, and | +| |notifications post successful return of this command must| +| |comply with the negotiated version. | +| |NOT_SUPPORTED: if the protocol version is not supported. | ++--------------------+---------------------------------------------------------+ + +Notifications +_____________ + +LMM_EVENT +~~~~~~~~~ + +message_id: 0x0 +protocol_id: 0x80 + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 lmid |Identifier for the LM that caused the transition. | ++------------------+-----------------------------------------------------------+ +|uint32 eventlm |Identifier of the LM this event refers to. | ++------------------+-----------------------------------------------------------+ +|uint32 flags |LM events: | +| |Bits[31:3] Reserved, must be zero. | +| |Bit[3] Wake (resume) event: | +| |1 LM has awakened. | +| |0 not a wake event. | +| |Bit[2] Suspend (sleep) event: | +| |1 LM has suspended. | +| |0 not a suspend event. | +| |Bit[1] Shutdown (off) event: | +| |1 LM has shutdown. | +| |0 not a shutdown event. | +| |Bit[0] Boot (on) event: | +| |1 LM has booted. | +| |0 not a boot event. | ++------------------+-----------------------------------------------------------+ + SCMI_BBM: System Control and Management BBM Vendor Protocol ============================================================== @@ -436,6 +948,322 @@ protocol_id: 0x81 | |0 no button change detected. | +------------------+-----------------------------------------------------------+ +System Control and Management CPU Vendor Protocol +================================================= + +This protocol allows an agent to start or stop a CPU. It is used to manage +auxiliary CPUs in a target LM (e.g. additional cores in an AP cluster or +Cortex-M cores). +Note: + - For cores in AP cluster, PSCI should be used and PSCI firmware will use CPU + protocol to handle them. For cores in non-AP cluster, Operating System(e.g. + Linux OS) could use CPU protocols to control Cortex-M7 cores. + - CPU indicates the core and its auxiliary peripherals(e.g. TCM) inside + i.MX SoC + +There are cases where giving an agent full control of a CPU via the CPU +protocol is not desired. The LMM protocol is more restricted to just boot, +shutdown, etc. So an agent might boot another logical machine but not be +able to directly mess the state of its CPUs. Its also the reason there is an +LMM power on command even though that could have been done through the +power protocol. + +The CPU protocol provides commands to: + +- Describe the protocol version. +- Discover implementation attributes. +- Discover the CPUs defined in the system. +- Start a CPU. +- Stop a CPU. +- Set the boot and resume addresses for a CPU. +- Set the sleep mode of a CPU. +- Configure wake-up sources for a CPU. +- Configure power domain reactions (LPM mode and retention mask) for a CPU. +- The CPU IDs can be found in the CPU section of the SoC DEVICE: SM Device + Interface. They can also be found in the SoC RM. See the CPU Mode Control + (CMC) list in General Power Controller (GPC) section. + +CPU settings are not aggregated and setting their state is normally exclusive +to one client. + +Commands: +_________ + +PROTOCOL_VERSION +~~~~~~~~~~~~~~~~ + +message_id: 0x0 +protocol_id: 0x82 +This command is mandatory. + ++---------------+--------------------------------------------------------------+ +|Return values | ++---------------+--------------------------------------------------------------+ +|Name |Description | ++---------------+--------------------------------------------------------------+ +|int32 status | See ARM SCMI Specification for status code definitions. | ++---------------+--------------------------------------------------------------+ +|uint32 version | For this revision of the specification, this value must be | +| | 0x10000. | ++---------------+--------------------------------------------------------------+ + +PROTOCOL_ATTRIBUTES +~~~~~~~~~~~~~~~~~~~ + +message_id: 0x1 +protocol_id: 0x82 +This command is mandatory. + ++---------------+--------------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status | See ARM SCMI Specification for status code definitions. | ++------------------+-----------------------------------------------------------+ +|uint32 attributes |Protocol attributes: | +| |Bits[31:16] Reserved, must be zero. | +| |Bits[15:0] Number of CPUs | ++------------------+-----------------------------------------------------------+ + +PROTOCOL_MESSAGE_ATTRIBUTES +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +message_id: 0x2 +protocol_id: 0x82 +This command is mandatory. + ++---------------+--------------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: in case the message is implemented and available | +| |to use. | +| |NOT_FOUND: if the message identified by message_id is | +| |invalid or not implemented | ++------------------+-----------------------------------------------------------+ +|uint32 attributes |Flags that are associated with a specific command in the | +| |protocol. For all commands in this protocol, this | +| |parameter has a value of 0 | ++------------------+-----------------------------------------------------------+ + +CPU_ATTRIBUTES +~~~~~~~~~~~~~~ + +message_id: 0x4 +protocol_id: 0x82 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 cpuid |Identifier for the CPU | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if valid attributes are returned successfully. | +| |NOT_FOUND: if the cpuid is not valid. | ++------------------+-----------------------------------------------------------+ +|uint32 attributes |Bits[31:0] Reserved, must be zero | ++------------------+-----------------------------------------------------------+ +|char name[16] |NULL terminated ASCII string with CPU name up to 16 bytes | ++------------------+-----------------------------------------------------------+ + +CPU_START +~~~~~~~~~ + +message_id: 0x4 +protocol_id: 0x82 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 cpuid |Identifier for the CPU | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if the cpu is started successfully. | +| |NOT_FOUND: if cpuid is not valid. | +| |DENIED: the calling agent is not allowed to start this CPU.| ++------------------+-----------------------------------------------------------+ + +CPU_STOP +~~~~~~~~ + +message_id: 0x5 +protocol_id: 0x82 +This command is mandatory. + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 cpuid |Identifier for the CPU | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if the cpu is started successfully. | +| |NOT_FOUND: if cpuid is not valid. | +| |DENIED: the calling agent is not allowed to stop this CPU. | ++------------------+-----------------------------------------------------------+ + +CPU_RESET_VECTOR_SET +~~~~~~~~~~~~~~~~~~~~ + +message_id: 0x6 +protocol_id: 0x82 +This command is mandatory. + ++----------------------+-------------------------------------------------------+ +|Parameters | ++----------------------+-------------------------------------------------------+ +|Name |Description | ++----------------------+-------------------------------------------------------+ +|uint32 cpuid |Identifier for the CPU | ++----------------------+-------------------------------------------------------+ +|uint32 flags |Reset vector flags: | +| |Bit[31] Resume flag. | +| |Set to 1 to update the reset vector used on resume. | +| |Bit[30] Boot flag. | +| |Set to 1 to update the reset vector used for boot. | +| |Bits[29:1] Reserved, must be zero. | +| |Bit[0] Table flag. | +| |Set to 1 if vector is the vector table base address. | ++----------------------+-------------------------------------------------------+ +|uint32 resetVectorLow |Lower vector: | +| |If bit[0] of flags is 0, the lower 32 bits of the | +| |physical address where the CPU should execute from on | +| |reset. If bit[0] of flags is 1, the lower 32 bits of | +| |the vector table base address | ++----------------------+-------------------------------------------------------+ +|uint32 resetVectorhigh|Upper vector: | +| |If bit[0] of flags is 0, the upper 32 bits of the | +| |physical address where the CPU should execute from on | +| |reset. If bit[0] of flags is 1, the upper 32 bits of | +| |the vector table base address | ++----------------------+-------------------------------------------------------+ +|Return values | ++----------------------+-------------------------------------------------------+ +|Name |Description | ++----------------------+-------------------------------------------------------+ +|int32 status |SUCCESS: if the CPU reset vector is set successfully. | +| |NOT_FOUND: if cpuId does not point to a valid CPU. | +| |INVALID_PARAMETERS: the requested vector type is not | +| |supported by this CPU. | +| |DENIED: the calling agent is not allowed to set the | +| |reset vector of this CPU | ++----------------------+-------------------------------------------------------+ + +CPU_SLEEP_MODE_SET +~~~~~~~~~~~~~~~~~~ + +message_id: 0x7 +protocol_id: 0x82 +This command is mandatory. + ++----------------------+-------------------------------------------------------+ +|Parameters | ++----------------------+-------------------------------------------------------+ +|Name |Description | ++----------------------+-------------------------------------------------------+ +|uint32 cpuid |Identifier for the CPU | ++----------------------+-------------------------------------------------------+ +|uint32 flags |Sleep mode flags: | +| |Bits[31:1] Reserved, must be zero. | +| |Bit[0] IRQ mux: | +| |If set to 1 the wakeup mux source is the GIC, else if 0| +| |then the GPC | ++----------------------+-------------------------------------------------------+ +|uint32 sleepmode |target sleep mode. When CPU runs into WFI, the GPC mode| +| |will be triggered to be in below modes: | +| |RUN: (0) | +| |WAIT: (1) | +| |STOP: (2) | +| |SUSPEND: (3) | ++----------------------+-------------------------------------------------------+ +|Return values | ++----------------------+-------------------------------------------------------+ +|Name |Description | ++----------------------+-------------------------------------------------------+ +|int32 status |SUCCESS: if the CPU sleep mode is set successfully. | +| |NOT_FOUND: if cpuId does not point to a valid CPU. | +| |INVALID_PARAMETERS: the sleepmode or flags is invalid. | +| |DENIED: the calling agent is not allowed to configure | +| |the CPU | ++----------------------+-------------------------------------------------------+ + +CPU_INFO_GET +~~~~~~~~~~~~ + +message_id: 0xC +protocol_id: 0x82 +This command is mandatory. + ++----------------------+-------------------------------------------------------+ +|Parameters | ++----------------------+-------------------------------------------------------+ +|Name |Description | ++----------------------+-------------------------------------------------------+ +|uint32 cpuid |Identifier for the CPU | ++----------------------+-------------------------------------------------------+ +|Return values | ++----------------------+-------------------------------------------------------+ +|Name |Description | ++----------------------+-------------------------------------------------------+ +|int32 status |SUCCESS: if valid attributes are returned successfully.| +| |NOT_FOUND: if the cpuid is not valid. | ++----------------------+-------------------------------------------------------+ +|uint32 runmode |Run mode for the CPU | +| |RUN(0):cpu started | +| |HOLD(1):cpu powered up and reset asserted | +| |STOP(2):cpu reseted and hold cpu | +| |SUSPEND(3):in cpuidle state | ++----------------------+-------------------------------------------------------+ +|uint32 sleepmode |Sleep mode for the CPU, see CPU_SLEEP_MODE_SET | ++----------------------+-------------------------------------------------------+ +|uint32 resetvectorlow |Reset vector low 32 bits for the CPU | ++----------------------+-------------------------------------------------------+ +|uint32 resetvecothigh |Reset vector high 32 bits for the CPU | ++----------------------+-------------------------------------------------------+ + +NEGOTIATE_PROTOCOL_VERSION +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +message_id: 0x10 +protocol_id: 0x82 +This command is mandatory. + ++--------------------+---------------------------------------------------------+ +|Parameters | ++--------------------+---------------------------------------------------------+ +|Name |Description | ++--------------------+---------------------------------------------------------+ +|uint32 version |The negotiated protocol version the agent intends to use | ++--------------------+---------------------------------------------------------+ +|Return values | ++--------------------+---------------------------------------------------------+ +|Name |Description | ++--------------------+---------------------------------------------------------+ +|int32 status |SUCCESS: if the negotiated protocol version is supported | +| |by the platform. All commands, responses, and | +| |notifications post successful return of this command must| +| |comply with the negotiated version. | +| |NOT_SUPPORTED: if the protocol version is not supported. | ++--------------------+---------------------------------------------------------+ + SCMI_MISC: System Control and Management MISC Vendor Protocol ================================================================ diff --git a/drivers/firmware/imx/Kconfig b/drivers/firmware/imx/Kconfig index c964f4924359..127ad752acf8 100644 --- a/drivers/firmware/imx/Kconfig +++ b/drivers/firmware/imx/Kconfig @@ -23,6 +23,28 @@ config IMX_SCU This driver manages the IPC interface between host CPU and the SCU firmware running on M4. +config IMX_SCMI_CPU_DRV + tristate "IMX SCMI CPU Protocol driver" + depends on ARCH_MXC || COMPILE_TEST + default y if ARCH_MXC + help + The System Controller Management Interface firmware (SCMI FW) is + a low-level system function which runs on a dedicated Cortex-M + core that could provide cpu management features. + + This driver can also be built as a module. + +config IMX_SCMI_LMM_DRV + tristate "IMX SCMI LMM Protocol driver" + depends on ARCH_MXC || COMPILE_TEST + default y if ARCH_MXC + help + The System Controller Management Interface firmware (SCMI FW) is + a low-level system function which runs on a dedicated Cortex-M + core that could provide Logical Machine management features. + + This driver can also be built as a module. + config IMX_SCMI_MISC_DRV tristate "IMX SCMI MISC Protocol driver" depends on ARCH_MXC || COMPILE_TEST diff --git a/drivers/firmware/imx/Makefile b/drivers/firmware/imx/Makefile index 8d046c341be8..3bbaffa6e347 100644 --- a/drivers/firmware/imx/Makefile +++ b/drivers/firmware/imx/Makefile @@ -1,4 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_IMX_DSP) += imx-dsp.o obj-$(CONFIG_IMX_SCU) += imx-scu.o misc.o imx-scu-irq.o rm.o imx-scu-soc.o +obj-${CONFIG_IMX_SCMI_CPU_DRV} += sm-cpu.o obj-${CONFIG_IMX_SCMI_MISC_DRV} += sm-misc.o +obj-${CONFIG_IMX_SCMI_LMM_DRV} += sm-lmm.o diff --git a/drivers/firmware/imx/sm-cpu.c b/drivers/firmware/imx/sm-cpu.c new file mode 100644 index 000000000000..091b014f739f --- /dev/null +++ b/drivers/firmware/imx/sm-cpu.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 NXP + */ + +#include <linux/firmware/imx/sm.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/scmi_protocol.h> +#include <linux/scmi_imx_protocol.h> + +static const struct scmi_imx_cpu_proto_ops *imx_cpu_ops; +static struct scmi_protocol_handle *ph; + +int scmi_imx_cpu_reset_vector_set(u32 cpuid, u64 vector, bool start, bool boot, + bool resume) +{ + if (!ph) + return -EPROBE_DEFER; + + return imx_cpu_ops->cpu_reset_vector_set(ph, cpuid, vector, start, + boot, resume); +} +EXPORT_SYMBOL(scmi_imx_cpu_reset_vector_set); + +int scmi_imx_cpu_start(u32 cpuid, bool start) +{ + if (!ph) + return -EPROBE_DEFER; + + if (start) + return imx_cpu_ops->cpu_start(ph, cpuid, true); + + return imx_cpu_ops->cpu_start(ph, cpuid, false); +}; +EXPORT_SYMBOL(scmi_imx_cpu_start); + +int scmi_imx_cpu_started(u32 cpuid, bool *started) +{ + if (!ph) + return -EPROBE_DEFER; + + if (!started) + return -EINVAL; + + return imx_cpu_ops->cpu_started(ph, cpuid, started); +}; +EXPORT_SYMBOL(scmi_imx_cpu_started); + +static int scmi_imx_cpu_probe(struct scmi_device *sdev) +{ + const struct scmi_handle *handle = sdev->handle; + + if (!handle) + return -ENODEV; + + if (imx_cpu_ops) { + dev_err(&sdev->dev, "sm cpu already initialized\n"); + return -EEXIST; + } + + imx_cpu_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_IMX_CPU, &ph); + if (IS_ERR(imx_cpu_ops)) + return PTR_ERR(imx_cpu_ops); + + return 0; +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_IMX_CPU, "imx-cpu" }, + { }, +}; +MODULE_DEVICE_TABLE(scmi, scmi_id_table); + +static struct scmi_driver scmi_imx_cpu_driver = { + .name = "scmi-imx-cpu", + .probe = scmi_imx_cpu_probe, + .id_table = scmi_id_table, +}; +module_scmi_driver(scmi_imx_cpu_driver); + +MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); +MODULE_DESCRIPTION("IMX SM CPU driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/imx/sm-lmm.c b/drivers/firmware/imx/sm-lmm.c new file mode 100644 index 000000000000..6807bf563c03 --- /dev/null +++ b/drivers/firmware/imx/sm-lmm.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 NXP + */ + +#include <linux/firmware/imx/sm.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/scmi_protocol.h> +#include <linux/scmi_imx_protocol.h> + +static const struct scmi_imx_lmm_proto_ops *imx_lmm_ops; +static struct scmi_protocol_handle *ph; + +int scmi_imx_lmm_info(u32 lmid, struct scmi_imx_lmm_info *info) +{ + if (!ph) + return -EPROBE_DEFER; + + if (!info) + return -EINVAL; + + return imx_lmm_ops->lmm_info(ph, lmid, info); +}; +EXPORT_SYMBOL(scmi_imx_lmm_info); + +int scmi_imx_lmm_reset_vector_set(u32 lmid, u32 cpuid, u32 flags, u64 vector) +{ + if (!ph) + return -EPROBE_DEFER; + + return imx_lmm_ops->lmm_reset_vector_set(ph, lmid, cpuid, flags, vector); +} +EXPORT_SYMBOL(scmi_imx_lmm_reset_vector_set); + +int scmi_imx_lmm_operation(u32 lmid, enum scmi_imx_lmm_op op, u32 flags) +{ + if (!ph) + return -EPROBE_DEFER; + + switch (op) { + case SCMI_IMX_LMM_BOOT: + return imx_lmm_ops->lmm_power_boot(ph, lmid, true); + case SCMI_IMX_LMM_POWER_ON: + return imx_lmm_ops->lmm_power_boot(ph, lmid, false); + case SCMI_IMX_LMM_SHUTDOWN: + return imx_lmm_ops->lmm_shutdown(ph, lmid, flags); + default: + break; + } + + return -EINVAL; +} +EXPORT_SYMBOL(scmi_imx_lmm_operation); + +static int scmi_imx_lmm_probe(struct scmi_device *sdev) +{ + const struct scmi_handle *handle = sdev->handle; + + if (!handle) + return -ENODEV; + + if (imx_lmm_ops) { + dev_err(&sdev->dev, "lmm already initialized\n"); + return -EEXIST; + } + + imx_lmm_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_IMX_LMM, &ph); + if (IS_ERR(imx_lmm_ops)) + return PTR_ERR(imx_lmm_ops); + + return 0; +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_IMX_LMM, "imx-lmm" }, + { }, +}; +MODULE_DEVICE_TABLE(scmi, scmi_id_table); + +static struct scmi_driver scmi_imx_lmm_driver = { + .name = "scmi-imx-lmm", + .probe = scmi_imx_lmm_probe, + .id_table = scmi_id_table, +}; +module_scmi_driver(scmi_imx_lmm_driver); + +MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); +MODULE_DESCRIPTION("IMX SM LMM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index fc4d67e4c4a6..f63b716be5b0 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -1986,7 +1986,10 @@ EXPORT_SYMBOL_GPL(qcom_scm_qseecom_app_send); */ static const struct of_device_id qcom_scm_qseecom_allowlist[] __maybe_unused = { { .compatible = "asus,vivobook-s15" }, + { .compatible = "asus,zenbook-a14-ux3407qa" }, + { .compatible = "asus,zenbook-a14-ux3407ra" }, { .compatible = "dell,xps13-9345" }, + { .compatible = "hp,elitebook-ultra-g1q" }, { .compatible = "hp,omnibook-x14" }, { .compatible = "huawei,gaokun3" }, { .compatible = "lenovo,flex-5g" }, diff --git a/drivers/firmware/qcom/qcom_scm.h b/drivers/firmware/qcom/qcom_scm.h index 097369d38b84..3133d826f5fa 100644 --- a/drivers/firmware/qcom/qcom_scm.h +++ b/drivers/firmware/qcom/qcom_scm.h @@ -44,8 +44,11 @@ enum qcom_scm_arg_types { /** * struct qcom_scm_desc + * @svc: Service identifier + * @cmd: Command identifier * @arginfo: Metadata describing the arguments in args[] * @args: The array of arguments for the secure syscall + * @owner: Owner identifier */ struct qcom_scm_desc { u32 svc; diff --git a/drivers/firmware/qcom/qcom_tzmem.c b/drivers/firmware/qcom/qcom_tzmem.c index 92b365178235..94196ad87105 100644 --- a/drivers/firmware/qcom/qcom_tzmem.c +++ b/drivers/firmware/qcom/qcom_tzmem.c @@ -79,6 +79,7 @@ static const char *const qcom_tzmem_blacklist[] = { "qcom,sc8180x", "qcom,sdm670", /* failure in GPU firmware loading */ "qcom,sdm845", /* reset in rmtfs memory assignment */ + "qcom,sm7150", /* reset in rmtfs memory assignment */ "qcom,sm8150", /* reset in rmtfs memory assignment */ NULL }; diff --git a/drivers/firmware/samsung/exynos-acpm-pmic.c b/drivers/firmware/samsung/exynos-acpm-pmic.c index 85e90d236da2..39b33a356ebd 100644 --- a/drivers/firmware/samsung/exynos-acpm-pmic.c +++ b/drivers/firmware/samsung/exynos-acpm-pmic.c @@ -43,13 +43,13 @@ static inline u32 acpm_pmic_get_bulk(u32 data, unsigned int i) return (data >> (ACPM_PMIC_BULK_SHIFT * i)) & ACPM_PMIC_BULK_MASK; } -static void acpm_pmic_set_xfer(struct acpm_xfer *xfer, u32 *cmd, +static void acpm_pmic_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen, unsigned int acpm_chan_id) { xfer->txd = cmd; xfer->rxd = cmd; - xfer->txlen = sizeof(cmd); - xfer->rxlen = sizeof(cmd); + xfer->txlen = cmdlen; + xfer->rxlen = cmdlen; xfer->acpm_chan_id = acpm_chan_id; } @@ -71,7 +71,7 @@ int acpm_pmic_read_reg(const struct acpm_handle *handle, int ret; acpm_pmic_init_read_cmd(cmd, type, reg, chan); - acpm_pmic_set_xfer(&xfer, cmd, acpm_chan_id); + acpm_pmic_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -104,7 +104,7 @@ int acpm_pmic_bulk_read(const struct acpm_handle *handle, return -EINVAL; acpm_pmic_init_bulk_read_cmd(cmd, type, reg, chan, count); - acpm_pmic_set_xfer(&xfer, cmd, acpm_chan_id); + acpm_pmic_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -144,7 +144,7 @@ int acpm_pmic_write_reg(const struct acpm_handle *handle, int ret; acpm_pmic_init_write_cmd(cmd, type, reg, chan, value); - acpm_pmic_set_xfer(&xfer, cmd, acpm_chan_id); + acpm_pmic_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -184,7 +184,7 @@ int acpm_pmic_bulk_write(const struct acpm_handle *handle, return -EINVAL; acpm_pmic_init_bulk_write_cmd(cmd, type, reg, chan, count, buf); - acpm_pmic_set_xfer(&xfer, cmd, acpm_chan_id); + acpm_pmic_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -214,7 +214,7 @@ int acpm_pmic_update_reg(const struct acpm_handle *handle, int ret; acpm_pmic_init_update_cmd(cmd, type, reg, chan, value, mask); - acpm_pmic_set_xfer(&xfer, cmd, acpm_chan_id); + acpm_pmic_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id); ret = acpm_do_xfer(handle, &xfer); if (ret) diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index 15e991b99f5a..e02f14f4bd7c 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -15,6 +15,7 @@ #include <linux/firmware/samsung/exynos-acpm-protocol.h> #include <linux/io.h> #include <linux/iopoll.h> +#include <linux/ktime.h> #include <linux/mailbox/exynos-message.h> #include <linux/mailbox_client.h> #include <linux/module.h> @@ -32,8 +33,7 @@ #define ACPM_PROTOCOL_SEQNUM GENMASK(21, 16) -/* The unit of counter is 20 us. 5000 * 20 = 100 ms */ -#define ACPM_POLL_TIMEOUT 5000 +#define ACPM_POLL_TIMEOUT_US (100 * USEC_PER_MSEC) #define ACPM_TX_TIMEOUT_US 500000 #define ACPM_GS101_INITDATA_BASE 0xa000 @@ -300,12 +300,13 @@ static int acpm_dequeue_by_polling(struct acpm_chan *achan, const struct acpm_xfer *xfer) { struct device *dev = achan->acpm->dev; - unsigned int cnt_20us = 0; + ktime_t timeout; u32 seqnum; int ret; seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, xfer->txd[0]); + timeout = ktime_add_us(ktime_get(), ACPM_POLL_TIMEOUT_US); do { ret = acpm_get_rx(achan, xfer); if (ret) @@ -315,12 +316,11 @@ static int acpm_dequeue_by_polling(struct acpm_chan *achan, return 0; /* Determined experimentally. */ - usleep_range(20, 30); - cnt_20us++; - } while (cnt_20us < ACPM_POLL_TIMEOUT); + udelay(20); + } while (ktime_before(ktime_get(), timeout)); - dev_err(dev, "Timeout! ch:%u s:%u bitmap:%lx, cnt_20us = %d.\n", - achan->id, seqnum, achan->bitmap_seqnum[0], cnt_20us); + dev_err(dev, "Timeout! ch:%u s:%u bitmap:%lx.\n", + achan->id, seqnum, achan->bitmap_seqnum[0]); return -ETIME; } @@ -649,7 +649,7 @@ static int acpm_probe(struct platform_device *pdev) platform_set_drvdata(pdev, acpm); - return 0; + return devm_of_platform_populate(dev); } /** @@ -677,43 +677,30 @@ static void devm_acpm_release(struct device *dev, void *res) } /** - * acpm_get_by_phandle() - get the ACPM handle using DT phandle. - * @dev: device pointer requesting ACPM handle. - * @property: property name containing phandle on ACPM node. + * acpm_get_by_node() - get the ACPM handle using node pointer. + * @dev: device pointer requesting ACPM handle. + * @np: ACPM device tree node. * * Return: pointer to handle on success, ERR_PTR(-errno) otherwise. */ -static const struct acpm_handle *acpm_get_by_phandle(struct device *dev, - const char *property) +static const struct acpm_handle *acpm_get_by_node(struct device *dev, + struct device_node *np) { struct platform_device *pdev; - struct device_node *acpm_np; struct device_link *link; struct acpm_info *acpm; - acpm_np = of_parse_phandle(dev->of_node, property, 0); - if (!acpm_np) - return ERR_PTR(-ENODEV); - - pdev = of_find_device_by_node(acpm_np); - if (!pdev) { - dev_err(dev, "Cannot find device node %s\n", acpm_np->name); - of_node_put(acpm_np); + pdev = of_find_device_by_node(np); + if (!pdev) return ERR_PTR(-EPROBE_DEFER); - } - - of_node_put(acpm_np); acpm = platform_get_drvdata(pdev); if (!acpm) { - dev_err(dev, "Cannot get drvdata from %s\n", - dev_name(&pdev->dev)); platform_device_put(pdev); return ERR_PTR(-EPROBE_DEFER); } if (!try_module_get(pdev->dev.driver->owner)) { - dev_err(dev, "Cannot get module reference.\n"); platform_device_put(pdev); return ERR_PTR(-EPROBE_DEFER); } @@ -732,14 +719,14 @@ static const struct acpm_handle *acpm_get_by_phandle(struct device *dev, } /** - * devm_acpm_get_by_phandle() - managed get handle using phandle. - * @dev: device pointer requesting ACPM handle. - * @property: property name containing phandle on ACPM node. + * devm_acpm_get_by_node() - managed get handle using node pointer. + * @dev: device pointer requesting ACPM handle. + * @np: ACPM device tree node. * * Return: pointer to handle on success, ERR_PTR(-errno) otherwise. */ -const struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev, - const char *property) +const struct acpm_handle *devm_acpm_get_by_node(struct device *dev, + struct device_node *np) { const struct acpm_handle **ptr, *handle; @@ -747,7 +734,7 @@ const struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev, if (!ptr) return ERR_PTR(-ENOMEM); - handle = acpm_get_by_phandle(dev, property); + handle = acpm_get_by_node(dev, np); if (!IS_ERR(handle)) { *ptr = handle; devres_add(dev, ptr); @@ -757,6 +744,7 @@ const struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev, return handle; } +EXPORT_SYMBOL_GPL(devm_acpm_get_by_node); static const struct acpm_match_data acpm_gs101 = { .initdata_base = ACPM_GS101_INITDATA_BASE, diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index 806a975fff22..ae5fd1936ad3 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -2,7 +2,7 @@ /* * Texas Instruments System Control Interface Protocol Driver * - * Copyright (C) 2015-2024 Texas Instruments Incorporated - https://www.ti.com/ + * Copyright (C) 2015-2025 Texas Instruments Incorporated - https://www.ti.com/ * Nishanth Menon */ @@ -3670,6 +3670,7 @@ static int __maybe_unused ti_sci_suspend(struct device *dev) struct ti_sci_info *info = dev_get_drvdata(dev); struct device *cpu_dev, *cpu_dev_max = NULL; s32 val, cpu_lat = 0; + u16 cpu_lat_ms; int i, ret; if (info->fw_caps & MSG_FLAG_CAPS_LPM_DM_MANAGED) { @@ -3682,9 +3683,16 @@ static int __maybe_unused ti_sci_suspend(struct device *dev) } } if (cpu_dev_max) { - dev_dbg(cpu_dev_max, "%s: sending max CPU latency=%u\n", __func__, cpu_lat); + /* + * PM QoS latency unit is usecs, device manager uses msecs. + * Convert to msecs and round down for device manager. + */ + cpu_lat_ms = cpu_lat / USEC_PER_MSEC; + dev_dbg(cpu_dev_max, "%s: sending max CPU latency=%u ms\n", __func__, + cpu_lat_ms); ret = ti_sci_cmd_set_latency_constraint(&info->handle, - cpu_lat, TISCI_MSG_CONSTRAINT_SET); + cpu_lat_ms, + TISCI_MSG_CONSTRAINT_SET); if (ret) return ret; } diff --git a/drivers/firmware/turris-mox-rwtm.c b/drivers/firmware/turris-mox-rwtm.c index 47fe6261f5a3..1eac9948148f 100644 --- a/drivers/firmware/turris-mox-rwtm.c +++ b/drivers/firmware/turris-mox-rwtm.c @@ -2,29 +2,31 @@ /* * Turris Mox rWTM firmware driver * - * Copyright (C) 2019, 2024 Marek Behún <kabel@kernel.org> + * Copyright (C) 2019, 2024, 2025 Marek Behún <kabel@kernel.org> */ #include <crypto/sha2.h> #include <linux/align.h> #include <linux/armada-37xx-rwtm-mailbox.h> +#include <linux/cleanup.h> #include <linux/completion.h> #include <linux/container_of.h> -#include <linux/debugfs.h> #include <linux/device.h> #include <linux/dma-mapping.h> #include <linux/err.h> -#include <linux/fs.h> #include <linux/hw_random.h> #include <linux/if_ether.h> +#include <linux/key.h> #include <linux/kobject.h> #include <linux/mailbox_client.h> +#include <linux/math.h> #include <linux/minmax.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/platform_device.h> #include <linux/sizes.h> #include <linux/sysfs.h> +#include <linux/turris-signing-key.h> #include <linux/types.h> #define DRIVER_NAME "turris-mox-rwtm" @@ -37,10 +39,13 @@ * https://gitlab.labs.nic.cz/turris/mox-boot-builder/tree/master/wtmi. */ -#define MOX_ECC_NUMBER_WORDS 17 -#define MOX_ECC_NUMBER_LEN (MOX_ECC_NUMBER_WORDS * sizeof(u32)) - -#define MOX_ECC_SIGNATURE_WORDS (2 * MOX_ECC_NUMBER_WORDS) +enum { + MOX_ECC_NUM_BITS = 521, + MOX_ECC_NUM_LEN = DIV_ROUND_UP(MOX_ECC_NUM_BITS, 8), + MOX_ECC_NUM_WORDS = DIV_ROUND_UP(MOX_ECC_NUM_BITS, 32), + MOX_ECC_SIG_LEN = 2 * MOX_ECC_NUM_LEN, + MOX_ECC_PUBKEY_LEN = 1 + MOX_ECC_NUM_LEN, +}; #define MBOX_STS_SUCCESS (0 << 30) #define MBOX_STS_FAIL (1 << 30) @@ -77,10 +82,7 @@ enum mbox_cmd { * @ram_size: RAM size of the device * @mac_address1: first MAC address of the device * @mac_address2: second MAC address of the device - * @has_pubkey: whether board ECDSA public key is present * @pubkey: board ECDSA public key - * @last_sig: last ECDSA signature generated with board ECDSA private key - * @last_sig_done: whether the last ECDSA signing is complete */ struct mox_rwtm { struct mbox_client mbox_client; @@ -100,18 +102,8 @@ struct mox_rwtm { int board_version, ram_size; u8 mac_address1[ETH_ALEN], mac_address2[ETH_ALEN]; - bool has_pubkey; - u8 pubkey[135]; - -#ifdef CONFIG_DEBUG_FS - /* - * Signature process. This is currently done via debugfs, because it - * does not conform to the sysfs standard "one file per attribute". - * It should be rewritten via crypto API once akcipher API is available - * from userspace. - */ - u32 last_sig[MOX_ECC_SIGNATURE_WORDS]; - bool last_sig_done; +#ifdef CONFIG_TURRIS_MOX_RWTM_KEYCTL + u8 pubkey[MOX_ECC_PUBKEY_LEN]; #endif }; @@ -120,24 +112,23 @@ static inline struct device *rwtm_dev(struct mox_rwtm *rwtm) return rwtm->mbox_client.dev; } -#define MOX_ATTR_RO(name, format, cat) \ +#define MOX_ATTR_RO(name, format) \ static ssize_t \ name##_show(struct device *dev, struct device_attribute *a, \ char *buf) \ { \ struct mox_rwtm *rwtm = dev_get_drvdata(dev); \ - if (!rwtm->has_##cat) \ + if (!rwtm->has_board_info) \ return -ENODATA; \ return sysfs_emit(buf, format, rwtm->name); \ } \ static DEVICE_ATTR_RO(name) -MOX_ATTR_RO(serial_number, "%016llX\n", board_info); -MOX_ATTR_RO(board_version, "%i\n", board_info); -MOX_ATTR_RO(ram_size, "%i\n", board_info); -MOX_ATTR_RO(mac_address1, "%pM\n", board_info); -MOX_ATTR_RO(mac_address2, "%pM\n", board_info); -MOX_ATTR_RO(pubkey, "%s\n", pubkey); +MOX_ATTR_RO(serial_number, "%016llX\n"); +MOX_ATTR_RO(board_version, "%i\n"); +MOX_ATTR_RO(ram_size, "%i\n"); +MOX_ATTR_RO(mac_address1, "%pM\n"); +MOX_ATTR_RO(mac_address2, "%pM\n"); static struct attribute *turris_mox_rwtm_attrs[] = { &dev_attr_serial_number.attr, @@ -145,7 +136,6 @@ static struct attribute *turris_mox_rwtm_attrs[] = { &dev_attr_ram_size.attr, &dev_attr_mac_address1.attr, &dev_attr_mac_address2.attr, - &dev_attr_pubkey.attr, NULL }; ATTRIBUTE_GROUPS(turris_mox_rwtm); @@ -247,24 +237,6 @@ static int mox_get_board_info(struct mox_rwtm *rwtm) pr_info(" burned RAM size %i MiB\n", rwtm->ram_size); } - ret = mox_rwtm_exec(rwtm, MBOX_CMD_ECDSA_PUB_KEY, NULL, false); - if (ret == -ENODATA) { - dev_warn(dev, "Board has no public key burned!\n"); - } else if (ret == -EOPNOTSUPP) { - dev_notice(dev, - "Firmware does not support the ECDSA_PUB_KEY command\n"); - } else if (ret < 0) { - return ret; - } else { - u32 *s = reply->status; - - rwtm->has_pubkey = true; - sprintf(rwtm->pubkey, - "%06x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x", - ret, s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], - s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15]); - } - return 0; } @@ -306,127 +278,139 @@ unlock_mutex: return ret; } -#ifdef CONFIG_DEBUG_FS -static int rwtm_debug_open(struct inode *inode, struct file *file) -{ - file->private_data = inode->i_private; +#ifdef CONFIG_TURRIS_MOX_RWTM_KEYCTL - return nonseekable_open(inode, file); -} - -static ssize_t do_sign_read(struct file *file, char __user *buf, size_t len, - loff_t *ppos) +static void mox_ecc_number_to_bin(void *dst, const u32 *src) { - struct mox_rwtm *rwtm = file->private_data; - ssize_t ret; + __be32 tmp[MOX_ECC_NUM_WORDS]; - /* only allow one read, of whole signature, from position 0 */ - if (*ppos != 0) - return 0; + cpu_to_be32_array(tmp, src, MOX_ECC_NUM_WORDS); - if (len < sizeof(rwtm->last_sig)) - return -EINVAL; + memcpy(dst, (void *)tmp + 2, MOX_ECC_NUM_LEN); +} - if (!rwtm->last_sig_done) - return -ENODATA; +static void mox_ecc_public_key_to_bin(void *dst, u32 src_first, + const u32 *src_rest) +{ + __be32 tmp[MOX_ECC_NUM_WORDS - 1]; + u8 *p = dst; - ret = simple_read_from_buffer(buf, len, ppos, rwtm->last_sig, - sizeof(rwtm->last_sig)); - rwtm->last_sig_done = false; + /* take 3 bytes from the first word */ + *p++ = src_first >> 16; + *p++ = src_first >> 8; + *p++ = src_first; - return ret; + /* take the rest of the words */ + cpu_to_be32_array(tmp, src_rest, MOX_ECC_NUM_WORDS - 1); + memcpy(p, tmp, sizeof(tmp)); } -static ssize_t do_sign_write(struct file *file, const char __user *buf, - size_t len, loff_t *ppos) +static int mox_rwtm_sign(const struct key *key, const void *data, void *signature) { - struct mox_rwtm *rwtm = file->private_data; - struct armada_37xx_rwtm_tx_msg msg; - loff_t dummy = 0; - ssize_t ret; - - if (len != SHA512_DIGEST_SIZE) - return -EINVAL; - - /* if last result is not zero user has not read that information yet */ - if (rwtm->last_sig_done) - return -EBUSY; + struct mox_rwtm *rwtm = dev_get_drvdata(turris_signing_key_get_dev(key)); + struct armada_37xx_rwtm_tx_msg msg = {}; + u32 offset_r, offset_s; + int ret; - if (!mutex_trylock(&rwtm->busy)) - return -EBUSY; + guard(mutex)(&rwtm->busy); /* - * Here we have to send: - * 1. Address of the input to sign. - * The input is an array of 17 32-bit words, the first (most - * significat) is 0, the rest 16 words are copied from the SHA-512 - * hash given by the user and converted from BE to LE. - * 2. Address of the buffer where ECDSA signature value R shall be - * stored by the rWTM firmware. - * 3. Address of the buffer where ECDSA signature value S shall be - * stored by the rWTM firmware. + * For MBOX_CMD_SIGN command: + * args[0] - must be 1 + * args[1] - address of message M to sign; message is a 521-bit number + * args[2] - address where the R part of the signature will be stored + * args[3] - address where the S part of the signature will be stored + * + * M, R and S are 521-bit numbers encoded as seventeen 32-bit words, + * most significat word first. + * Since the message in @data is a sha512 digest, the most significat + * word is always zero. */ + + offset_r = MOX_ECC_NUM_WORDS * sizeof(u32); + offset_s = 2 * MOX_ECC_NUM_WORDS * sizeof(u32); + memset(rwtm->buf, 0, sizeof(u32)); - ret = simple_write_to_buffer(rwtm->buf + sizeof(u32), - SHA512_DIGEST_SIZE, &dummy, buf, len); - if (ret < 0) - goto unlock_mutex; - be32_to_cpu_array(rwtm->buf, rwtm->buf, MOX_ECC_NUMBER_WORDS); + memcpy(rwtm->buf + sizeof(u32), data, SHA512_DIGEST_SIZE); + be32_to_cpu_array(rwtm->buf, rwtm->buf, MOX_ECC_NUM_WORDS); msg.args[0] = 1; msg.args[1] = rwtm->buf_phys; - msg.args[2] = rwtm->buf_phys + MOX_ECC_NUMBER_LEN; - msg.args[3] = rwtm->buf_phys + 2 * MOX_ECC_NUMBER_LEN; + msg.args[2] = rwtm->buf_phys + offset_r; + msg.args[3] = rwtm->buf_phys + offset_s; ret = mox_rwtm_exec(rwtm, MBOX_CMD_SIGN, &msg, true); if (ret < 0) - goto unlock_mutex; + return ret; - /* - * Here we read the R and S values of the ECDSA signature - * computed by the rWTM firmware and convert their words from - * LE to BE. - */ - memcpy(rwtm->last_sig, rwtm->buf + MOX_ECC_NUMBER_LEN, - sizeof(rwtm->last_sig)); - cpu_to_be32_array(rwtm->last_sig, rwtm->last_sig, - MOX_ECC_SIGNATURE_WORDS); - rwtm->last_sig_done = true; + /* convert R and S parts of the signature */ + mox_ecc_number_to_bin(signature, rwtm->buf + offset_r); + mox_ecc_number_to_bin(signature + MOX_ECC_NUM_LEN, rwtm->buf + offset_s); - mutex_unlock(&rwtm->busy); - return len; -unlock_mutex: - mutex_unlock(&rwtm->busy); - return ret; + return 0; } -static const struct file_operations do_sign_fops = { - .owner = THIS_MODULE, - .open = rwtm_debug_open, - .read = do_sign_read, - .write = do_sign_write, -}; - -static void rwtm_debugfs_release(void *root) +static const void *mox_rwtm_get_public_key(const struct key *key) { - debugfs_remove_recursive(root); + struct mox_rwtm *rwtm = dev_get_drvdata(turris_signing_key_get_dev(key)); + + return rwtm->pubkey; } -static void rwtm_register_debugfs(struct mox_rwtm *rwtm) +static const struct turris_signing_key_subtype mox_signing_key_subtype = { + .key_size = MOX_ECC_NUM_BITS, + .data_size = SHA512_DIGEST_SIZE, + .sig_size = MOX_ECC_SIG_LEN, + .public_key_size = MOX_ECC_PUBKEY_LEN, + .hash_algo = "sha512", + .get_public_key = mox_rwtm_get_public_key, + .sign = mox_rwtm_sign, +}; + +static int mox_register_signing_key(struct mox_rwtm *rwtm) { - struct dentry *root; + struct armada_37xx_rwtm_rx_msg *reply = &rwtm->reply; + struct device *dev = rwtm_dev(rwtm); + int ret; - root = debugfs_create_dir("turris-mox-rwtm", NULL); + ret = mox_rwtm_exec(rwtm, MBOX_CMD_ECDSA_PUB_KEY, NULL, false); + if (ret == -ENODATA) { + dev_warn(dev, "Board has no public key burned!\n"); + } else if (ret == -EOPNOTSUPP) { + dev_notice(dev, + "Firmware does not support the ECDSA_PUB_KEY command\n"); + } else if (ret < 0) { + return ret; + } else { + char sn[17] = "unknown"; + char desc[46]; + + if (rwtm->has_board_info) + sprintf(sn, "%016llX", rwtm->serial_number); + + sprintf(desc, "Turris MOX SN %s rWTM ECDSA key", sn); - debugfs_create_file_unsafe("do_sign", 0600, root, rwtm, &do_sign_fops); + mox_ecc_public_key_to_bin(rwtm->pubkey, ret, reply->status); - devm_add_action_or_reset(rwtm_dev(rwtm), rwtm_debugfs_release, root); + ret = devm_turris_signing_key_create(dev, + &mox_signing_key_subtype, + desc); + if (ret) + return dev_err_probe(dev, ret, + "Cannot create signing key\n"); + } + + return 0; } -#else -static inline void rwtm_register_debugfs(struct mox_rwtm *rwtm) + +#else /* CONFIG_TURRIS_MOX_RWTM_KEYCTL */ + +static inline int mox_register_signing_key(struct mox_rwtm *rwtm) { + return 0; } -#endif + +#endif /* !CONFIG_TURRIS_MOX_RWTM_KEYCTL */ static void rwtm_devm_mbox_release(void *mbox) { @@ -477,6 +461,10 @@ static int turris_mox_rwtm_probe(struct platform_device *pdev) if (ret < 0) dev_warn(dev, "Cannot read board information: %i\n", ret); + ret = mox_register_signing_key(rwtm); + if (ret < 0) + return ret; + ret = check_get_random_support(rwtm); if (ret < 0) { dev_notice(dev, @@ -491,8 +479,6 @@ static int turris_mox_rwtm_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Cannot register HWRNG!\n"); - rwtm_register_debugfs(rwtm); - dev_info(dev, "HWRNG successfully registered\n"); /* diff --git a/drivers/gpu/drm/i915/i915_mm.c b/drivers/gpu/drm/i915/i915_mm.c index 76e2801619f0..c33bd3d83069 100644 --- a/drivers/gpu/drm/i915/i915_mm.c +++ b/drivers/gpu/drm/i915/i915_mm.c @@ -100,7 +100,7 @@ int remap_io_mapping(struct vm_area_struct *vma, GEM_BUG_ON((vma->vm_flags & EXPECTED_FLAGS) != EXPECTED_FLAGS); - /* We rely on prevalidation of the io-mapping to skip track_pfn(). */ + /* We rely on prevalidation of the io-mapping to skip pfnmap tracking. */ r.mm = vma->vm_mm; r.pfn = pfn; r.prot = __pgprot((pgprot_val(iomap->prot) & _PAGE_CACHE_MASK) | @@ -140,7 +140,7 @@ int remap_io_sg(struct vm_area_struct *vma, }; int err; - /* We rely on prevalidation of the io-mapping to skip track_pfn(). */ + /* We rely on prevalidation of the io-mapping to skip pfnmap tracking. */ GEM_BUG_ON((vma->vm_flags & EXPECTED_FLAGS) != EXPECTED_FLAGS); while (offset >= r.sgt.max >> PAGE_SHIFT) { diff --git a/drivers/gpu/drm/panel/panel-samsung-sofef00.c b/drivers/gpu/drm/panel/panel-samsung-sofef00.c index 210a25afe82b..d92ae6b6100f 100644 --- a/drivers/gpu/drm/panel/panel-samsung-sofef00.c +++ b/drivers/gpu/drm/panel/panel-samsung-sofef00.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2020 Caleb Connolly <caleb@connolly.tech> +/* Copyright (c) 2020 Casey Connolly <casey.connolly@linaro.org> * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: * Copyright (c) 2020, The Linux Foundation. All rights reserved. */ @@ -260,6 +260,6 @@ static struct mipi_dsi_driver sofef00_panel_driver = { module_mipi_dsi_driver(sofef00_panel_driver); -MODULE_AUTHOR("Caleb Connolly <caleb@connolly.tech>"); +MODULE_AUTHOR("Casey Connolly <casey.connolly@linaro.org>"); MODULE_DESCRIPTION("DRM driver for Samsung AMOLED DSI panels found in OnePlus 6/6T phones"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c index 4dbf8b88f264..11d460d2ea19 100644 --- a/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c +++ b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c @@ -86,11 +86,7 @@ struct td028ttec1_panel { #define to_td028ttec1_device(p) container_of(p, struct td028ttec1_panel, panel) -/* - * noinline_for_stack so we don't get multiple copies of tx_buf - * on the stack in case of gcc-plugin-structleak - */ -static int noinline_for_stack +static int jbt_ret_write_0(struct td028ttec1_panel *lcd, u8 reg, int *err) { struct spi_device *spi = lcd->spi; diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig index 77da199c7413..7b30db3253af 100644 --- a/drivers/i3c/master/Kconfig +++ b/drivers/i3c/master/Kconfig @@ -1,7 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only config CDNS_I3C_MASTER tristate "Cadence I3C master driver" - depends on I3C depends on HAS_IOMEM depends on !(ALPHA || PARISC) help @@ -9,7 +8,6 @@ config CDNS_I3C_MASTER config DW_I3C_MASTER tristate "Synospsys DesignWare I3C master driver" - depends on I3C depends on HAS_IOMEM depends on !(ALPHA || PARISC) # ALPHA and PARISC needs {read,write}sl() @@ -38,7 +36,6 @@ config AST2600_I3C_MASTER config SVC_I3C_MASTER tristate "Silvaco I3C Dual-Role Master driver" - depends on I3C depends on HAS_IOMEM depends on !(ALPHA || PARISC) help @@ -46,7 +43,6 @@ config SVC_I3C_MASTER config MIPI_I3C_HCI tristate "MIPI I3C Host Controller Interface driver (EXPERIMENTAL)" - depends on I3C depends on HAS_IOMEM help Support for hardware following the MIPI Aliance's I3C Host Controller diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index a71226d7ca59..bc4538694540 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -78,7 +78,7 @@ #define INTR_SIGNAL_ENABLE 0x28 #define INTR_FORCE 0x2c #define INTR_HC_CMD_SEQ_UFLOW_STAT BIT(12) /* Cmd Sequence Underflow */ -#define INTR_HC_RESET_CANCEL BIT(11) /* HC Cancelled Reset */ +#define INTR_HC_SEQ_CANCEL BIT(11) /* HC Cancelled Transaction Sequence */ #define INTR_HC_INTERNAL_ERR BIT(10) /* HC Internal Error */ #define DAT_SECTION 0x30 /* Device Address Table */ @@ -590,26 +590,27 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id) u32 val; val = reg_read(INTR_STATUS); + reg_write(INTR_STATUS, val); DBG("INTR_STATUS = %#x", val); - if (val) { - reg_write(INTR_STATUS, val); - } + if (val) + result = IRQ_HANDLED; - if (val & INTR_HC_RESET_CANCEL) { - DBG("cancelled reset"); - val &= ~INTR_HC_RESET_CANCEL; + if (val & INTR_HC_SEQ_CANCEL) { + dev_dbg(&hci->master.dev, + "Host Controller Cancelled Transaction Sequence\n"); + val &= ~INTR_HC_SEQ_CANCEL; } if (val & INTR_HC_INTERNAL_ERR) { dev_err(&hci->master.dev, "Host Controller Internal Error\n"); val &= ~INTR_HC_INTERNAL_ERR; } - hci->io->irq_handler(hci); - if (val) - dev_err(&hci->master.dev, "unexpected INTR_STATUS %#x\n", val); - else + dev_warn_once(&hci->master.dev, + "unexpected INTR_STATUS %#x\n", val); + + if (hci->io->irq_handler(hci)) result = IRQ_HANDLED; return result; @@ -699,9 +700,14 @@ static int i3c_hci_init(struct i3c_hci *hci) if (ret) return -ENXIO; - /* Disable all interrupts and allow all signal updates */ + /* Disable all interrupts */ reg_write(INTR_SIGNAL_ENABLE, 0x0); - reg_write(INTR_STATUS_ENABLE, 0xffffffff); + /* + * Only allow bit 31:10 signal updates because + * Bit 0:9 are reserved in IP version >= 0.8 + * Bit 0:5 are defined in IP version < 0.8 but not handled by PIO code + */ + reg_write(INTR_STATUS_ENABLE, GENMASK(31, 10)); /* Make sure our data ordering fits the host's */ regval = reg_read(HC_CONTROL); diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index 85e16de208d3..7e1a7cb94b43 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -201,11 +201,10 @@ struct svc_i3c_drvdata { * @addrs: Array containing the dynamic addresses of each attached device * @descs: Array of descriptors, one per attached device * @hj_work: Hot-join work - * @ibi_work: IBI work * @irq: Main interrupt - * @pclk: System clock + * @num_clks: I3C clock number * @fclk: Fast clock (bus) - * @sclk: Slow clock (other events) + * @clks: I3C clock array * @xferqueue: Transfer queue structure * @xferqueue.list: List member * @xferqueue.cur: Current ongoing transfer @@ -229,11 +228,10 @@ struct svc_i3c_master { u8 addrs[SVC_I3C_MAX_DEVS]; struct i3c_dev_desc *descs[SVC_I3C_MAX_DEVS]; struct work_struct hj_work; - struct work_struct ibi_work; int irq; - struct clk *pclk; + int num_clks; struct clk *fclk; - struct clk *sclk; + struct clk_bulk_data *clks; struct { struct list_head list; struct svc_i3c_xfer *cur; @@ -487,9 +485,8 @@ static int svc_i3c_master_handle_ibi_won(struct svc_i3c_master *master, u32 msta return ret; } -static void svc_i3c_master_ibi_work(struct work_struct *work) +static void svc_i3c_master_ibi_isr(struct svc_i3c_master *master) { - struct svc_i3c_master *master = container_of(work, struct svc_i3c_master, ibi_work); struct svc_i3c_i2c_dev_data *data; unsigned int ibitype, ibiaddr; struct i3c_dev_desc *dev; @@ -504,7 +501,7 @@ static void svc_i3c_master_ibi_work(struct work_struct *work) * schedule during the whole I3C transaction, otherwise, the I3C bus timeout may happen if * any irq or schedule happen during transaction. */ - guard(spinlock_irqsave)(&master->xferqueue.lock); + guard(spinlock)(&master->xferqueue.lock); /* * IBIWON may be set before SVC_I3C_MCTRL_REQUEST_AUTO_IBI, causing @@ -530,7 +527,7 @@ static void svc_i3c_master_ibi_work(struct work_struct *work) if (ret) { dev_err(master->dev, "Timeout when polling for IBIWON\n"); svc_i3c_master_emit_stop(master); - goto reenable_ibis; + return; } status = readl(master->regs + SVC_I3C_MSTATUS); @@ -574,17 +571,17 @@ static void svc_i3c_master_ibi_work(struct work_struct *work) svc_i3c_master_emit_stop(master); - goto reenable_ibis; + return; } /* Handle the non critical tasks */ switch (ibitype) { case SVC_I3C_MSTATUS_IBITYPE_IBI: + svc_i3c_master_emit_stop(master); if (dev) { i3c_master_queue_ibi(dev, master->ibi.tbq_slot); master->ibi.tbq_slot = NULL; } - svc_i3c_master_emit_stop(master); break; case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN: svc_i3c_master_emit_stop(master); @@ -597,9 +594,6 @@ static void svc_i3c_master_ibi_work(struct work_struct *work) default: break; } - -reenable_ibis: - svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART); } static irqreturn_t svc_i3c_master_irq_handler(int irq, void *dev_id) @@ -618,10 +612,12 @@ static irqreturn_t svc_i3c_master_irq_handler(int irq, void *dev_id) !SVC_I3C_MSTATUS_STATE_SLVREQ(active)) return IRQ_HANDLED; - svc_i3c_master_disable_interrupts(master); - - /* Handle the interrupt in a non atomic context */ - queue_work(master->base.wq, &master->ibi_work); + /* + * The SDA line remains low until the request is processed. + * Receive the request in the interrupt context to respond promptly + * and restore the bus to idle state. + */ + svc_i3c_master_ibi_isr(master); return IRQ_HANDLED; } @@ -1281,9 +1277,9 @@ static int svc_i3c_master_write(struct svc_i3c_master *master, static int svc_i3c_master_xfer(struct svc_i3c_master *master, bool rnw, unsigned int xfer_type, u8 addr, u8 *in, const u8 *out, unsigned int xfer_len, - unsigned int *actual_len, bool continued) + unsigned int *actual_len, bool continued, bool repeat_start) { - int retry = 2; + int retry = repeat_start ? 1 : 2; u32 reg; int ret; @@ -1468,7 +1464,7 @@ static void svc_i3c_master_start_xfer_locked(struct svc_i3c_master *master) ret = svc_i3c_master_xfer(master, cmd->rnw, xfer->type, cmd->addr, cmd->in, cmd->out, cmd->len, &cmd->actual_len, - cmd->continued); + cmd->continued, i > 0); /* cmd->xfer is NULL if I2C or CCC transfer */ if (cmd->xfer) cmd->xfer->actual_len = cmd->actual_len; @@ -1875,42 +1871,11 @@ static const struct i3c_master_controller_ops svc_i3c_master_ops = { .set_speed = svc_i3c_master_set_speed, }; -static int svc_i3c_master_prepare_clks(struct svc_i3c_master *master) -{ - int ret = 0; - - ret = clk_prepare_enable(master->pclk); - if (ret) - return ret; - - ret = clk_prepare_enable(master->fclk); - if (ret) { - clk_disable_unprepare(master->pclk); - return ret; - } - - ret = clk_prepare_enable(master->sclk); - if (ret) { - clk_disable_unprepare(master->pclk); - clk_disable_unprepare(master->fclk); - return ret; - } - - return 0; -} - -static void svc_i3c_master_unprepare_clks(struct svc_i3c_master *master) -{ - clk_disable_unprepare(master->pclk); - clk_disable_unprepare(master->fclk); - clk_disable_unprepare(master->sclk); -} - static int svc_i3c_master_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct svc_i3c_master *master; - int ret; + int ret, i; master = devm_kzalloc(dev, sizeof(*master), GFP_KERNEL); if (!master) @@ -1924,30 +1889,33 @@ static int svc_i3c_master_probe(struct platform_device *pdev) if (IS_ERR(master->regs)) return PTR_ERR(master->regs); - master->pclk = devm_clk_get(dev, "pclk"); - if (IS_ERR(master->pclk)) - return PTR_ERR(master->pclk); + master->num_clks = devm_clk_bulk_get_all(dev, &master->clks); + if (master->num_clks < 0) + return dev_err_probe(dev, -EINVAL, "can't get I3C clocks\n"); - master->fclk = devm_clk_get(dev, "fast_clk"); + for (i = 0; i < master->num_clks; i++) { + if (!strcmp(master->clks[i].id, "fast_clk")) + break; + } + + if (i == master->num_clks) + return dev_err_probe(dev, -EINVAL, + "can't get I3C peripheral clock\n"); + + master->fclk = master->clks[i].clk; if (IS_ERR(master->fclk)) return PTR_ERR(master->fclk); - master->sclk = devm_clk_get(dev, "slow_clk"); - if (IS_ERR(master->sclk)) - return PTR_ERR(master->sclk); - master->irq = platform_get_irq(pdev, 0); if (master->irq < 0) return master->irq; master->dev = dev; - - ret = svc_i3c_master_prepare_clks(master); + ret = clk_bulk_prepare_enable(master->num_clks, master->clks); if (ret) - return ret; + return dev_err_probe(dev, ret, "can't enable I3C clocks\n"); INIT_WORK(&master->hj_work, svc_i3c_master_hj_work); - INIT_WORK(&master->ibi_work, svc_i3c_master_ibi_work); mutex_init(&master->lock); ret = devm_request_irq(dev, master->irq, svc_i3c_master_irq_handler, @@ -1998,7 +1966,7 @@ rpm_disable: pm_runtime_set_suspended(&pdev->dev); err_disable_clks: - svc_i3c_master_unprepare_clks(master); + clk_bulk_disable_unprepare(master->num_clks, master->clks); return ret; } @@ -2036,7 +2004,7 @@ static int __maybe_unused svc_i3c_runtime_suspend(struct device *dev) struct svc_i3c_master *master = dev_get_drvdata(dev); svc_i3c_save_regs(master); - svc_i3c_master_unprepare_clks(master); + clk_bulk_disable_unprepare(master->num_clks, master->clks); pinctrl_pm_select_sleep_state(dev); return 0; @@ -2045,9 +2013,12 @@ static int __maybe_unused svc_i3c_runtime_suspend(struct device *dev) static int __maybe_unused svc_i3c_runtime_resume(struct device *dev) { struct svc_i3c_master *master = dev_get_drvdata(dev); + int ret; pinctrl_pm_select_default_state(dev); - svc_i3c_master_prepare_clks(master); + ret = clk_bulk_prepare_enable(master->num_clks, master->clks); + if (ret) + return ret; svc_i3c_restore_regs(master); diff --git a/drivers/iio/adc/qcom-spmi-rradc.c b/drivers/iio/adc/qcom-spmi-rradc.c index 63ebaf13ef19..f61ad0510f04 100644 --- a/drivers/iio/adc/qcom-spmi-rradc.c +++ b/drivers/iio/adc/qcom-spmi-rradc.c @@ -2,7 +2,7 @@ /* * Copyright (c) 2016-2017, 2019, The Linux Foundation. All rights reserved. * Copyright (c) 2022 Linaro Limited. - * Author: Caleb Connolly <caleb.connolly@linaro.org> + * Author: Casey Connolly <casey.connolly@linaro.org> * * This driver is for the Round Robin ADC found in the pmi8998 and pm660 PMICs. */ @@ -1016,5 +1016,5 @@ static struct platform_driver rradc_driver = { module_platform_driver(rradc_driver); MODULE_DESCRIPTION("QCOM SPMI PMIC RR ADC driver"); -MODULE_AUTHOR("Caleb Connolly <caleb.connolly@linaro.org>"); +MODULE_AUTHOR("Casey Connolly <casey.connolly@linaro.org>"); MODULE_LICENSE("GPL"); diff --git a/drivers/iommu/arm/Makefile b/drivers/iommu/arm/Makefile index 35a7e13eef34..0f9efeab709f 100644 --- a/drivers/iommu/arm/Makefile +++ b/drivers/iommu/arm/Makefile @@ -1,3 +1,2 @@ # SPDX-License-Identifier: GPL-2.0 -obj-y += arm-smmu/ -obj-$(CONFIG_ARM_SMMU_V3) += arm-smmu-v3/ +obj-y += arm-smmu/ arm-smmu-v3/ diff --git a/drivers/iommu/arm/arm-smmu-v3/Makefile b/drivers/iommu/arm/arm-smmu-v3/Makefile index 6cc7c8557b9e..493a659cc66b 100644 --- a/drivers/iommu/arm/arm-smmu-v3/Makefile +++ b/drivers/iommu/arm/arm-smmu-v3/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -obj-y += arm_smmu_v3.o +obj-$(CONFIG_ARM_SMMU_V3) += arm_smmu_v3.o arm_smmu_v3-y := arm-smmu-v3.o arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_IOMMUFD) += arm-smmu-v3-iommufd.o arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_SVA) += arm-smmu-v3-sva.o diff --git a/drivers/md/bcache/btree.c b/drivers/md/bcache/btree.c index ed40d8600656..2cc2eb24dc8a 100644 --- a/drivers/md/bcache/btree.c +++ b/drivers/md/bcache/btree.c @@ -36,6 +36,7 @@ #include <linux/sched/clock.h> #include <linux/rculist.h> #include <linux/delay.h> +#include <linux/sort.h> #include <trace/events/bcache.h> /* @@ -559,8 +560,6 @@ static void mca_data_alloc(struct btree *b, struct bkey *k, gfp_t gfp) } } -#define cmp_int(l, r) ((l > r) - (l < r)) - #ifdef CONFIG_PROVE_LOCKING static int btree_lock_cmp_fn(const struct lockdep_map *_a, const struct lockdep_map *_b) diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index c82d8d8a16ea..79df0d22e218 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -32,7 +32,7 @@ config ARM_PL172_MPMC config ATMEL_EBI bool "Atmel EBI driver" - default y if ARCH_AT91 + default ARCH_AT91 depends on ARCH_AT91 || COMPILE_TEST depends on OF select MFD_SYSCON @@ -147,7 +147,7 @@ config FPGA_DFL_EMIF config MVEBU_DEVBUS bool "Marvell EBU Device Bus Controller" - default y if PLAT_ORION + default PLAT_ORION depends on PLAT_ORION || COMPILE_TEST depends on OF help @@ -198,7 +198,7 @@ config DA8XX_DDRCTL config PL353_SMC tristate "ARM PL35X Static Memory Controller(SMC) driver" - default y if ARM + default ARM depends on ARM || COMPILE_TEST depends on ARM_AMBA help @@ -225,6 +225,23 @@ config STM32_FMC2_EBI devices (like SRAM, ethernet adapters, FPGAs, LCD displays, ...) on SOCs containing the FMC2 External Bus Interface. +config STM32_OMM + tristate "STM32 Octo Memory Manager" + depends on SPI_STM32_OSPI || COMPILE_TEST + help + This driver manages the muxing between the 2 OSPI busses and + the 2 output ports. There are 4 possible muxing configurations: + - direct mode (no multiplexing): OSPI1 output is on port 1 and OSPI2 + output is on port 2 + - OSPI1 and OSPI2 are multiplexed over the same output port 1 + - swapped mode (no multiplexing), OSPI1 output is on port 2, + OSPI2 output is on port 1 + - OSPI1 and OSPI2 are multiplexed over the same output port 2 + It also manages : + - the split of the memory area shared between the 2 OSPI instances. + - chip select selection override. + - the time between 2 transactions in multiplexed mode. + source "drivers/memory/samsung/Kconfig" source "drivers/memory/tegra/Kconfig" diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index d2e6ca9abbe0..c1959661bf63 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_DA8XX_DDRCTL) += da8xx-ddrctl.o obj-$(CONFIG_PL353_SMC) += pl353-smc.o obj-$(CONFIG_RENESAS_RPCIF) += renesas-rpc-if.o obj-$(CONFIG_STM32_FMC2_EBI) += stm32-fmc2-ebi.o +obj-$(CONFIG_STM32_OMM) += stm32_omm.o obj-$(CONFIG_SAMSUNG_MC) += samsung/ obj-$(CONFIG_TEGRA_MC) += tegra/ diff --git a/drivers/memory/bt1-l2-ctl.c b/drivers/memory/bt1-l2-ctl.c index 78bd71b203f2..0fd96abc172a 100644 --- a/drivers/memory/bt1-l2-ctl.c +++ b/drivers/memory/bt1-l2-ctl.c @@ -222,7 +222,7 @@ static ssize_t l2_ctl_latency_show(struct device *dev, if (ret) return ret; - return scnprintf(buf, PAGE_SIZE, "%u\n", data); + return sysfs_emit(buf, "%u\n", data); } static ssize_t l2_ctl_latency_store(struct device *dev, diff --git a/drivers/memory/mtk-smi.c b/drivers/memory/mtk-smi.c index a8f5467d6b31..c086c22511f7 100644 --- a/drivers/memory/mtk-smi.c +++ b/drivers/memory/mtk-smi.c @@ -283,6 +283,43 @@ static int mtk_smi_larb_config_port_gen2_general(struct device *dev) return 0; } +static const u8 mtk_smi_larb_mt6893_ostd[][SMI_LARB_PORT_NR_MAX] = { + [0] = {0x2, 0x6, 0x2, 0x2, 0x2, 0x28, 0x18, 0x18, 0x1, 0x1, 0x1, 0x8, + 0x8, 0x1, 0x3f}, + [1] = {0x2, 0x6, 0x2, 0x2, 0x2, 0x28, 0x18, 0x18, 0x1, 0x1, 0x1, 0x8, + 0x8, 0x1, 0x3f}, + [2] = {0x5, 0x5, 0x5, 0x5, 0x1, 0x3f}, + [3] = {0x5, 0x5, 0x5, 0x5, 0x1, 0x3f}, + [4] = {0x28, 0x19, 0xb, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1}, + [5] = {0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x16}, + [6] = {}, + [7] = {0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x4, 0x1, + 0x4, 0x1, 0xa, 0x6, 0x1, 0xa, 0x6, 0x1, 0x1, 0x1, 0x1, 0x5, + 0x3, 0x3, 0x4}, + [8] = {0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x4, 0x1, + 0x4, 0x1, 0xa, 0x6, 0x1, 0xa, 0x6, 0x1, 0x1, 0x1, 0x1, 0x5, + 0x3, 0x3, 0x4}, + [9] = {0x9, 0x7, 0xf, 0x8, 0x1, 0x8, 0x9, 0x3, 0x3, 0x6, 0x7, 0x4, + 0x9, 0x3, 0x4, 0xe, 0x1, 0x7, 0x8, 0x7, 0x7, 0x1, 0x6, 0x2, + 0xf, 0x8, 0x1, 0x1, 0x1}, + [10] = {}, + [11] = {0x9, 0x7, 0xf, 0x8, 0x1, 0x8, 0x9, 0x3, 0x3, 0x6, 0x7, 0x4, + 0x9, 0x3, 0x4, 0xe, 0x1, 0x7, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1}, + [12] = {}, + [13] = {0x2, 0xc, 0xc, 0xe, 0x6, 0x6, 0x6, 0x6, 0x6, 0x12, 0x6, 0x1}, + [14] = {0x2, 0xc, 0xc, 0x28, 0x12, 0x6}, + [15] = {0x28, 0x1, 0x2, 0x28, 0x1}, + [16] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x2, 0x14, 0x14, 0x4, 0x4, 0x4, 0x2, + 0x4, 0x2, 0x8, 0x4, 0x4}, + [17] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x2, 0x14, 0x14, 0x4, 0x4, 0x4, 0x2, + 0x4, 0x2, 0x8, 0x4, 0x4}, + [18] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x2, 0x14, 0x14, 0x4, 0x4, 0x4, 0x2, + 0x4, 0x2, 0x8, 0x4, 0x4}, + [19] = {0x2, 0x2, 0x4, 0x2}, + [20] = {0x9, 0x9, 0x5, 0x5, 0x1, 0x1}, +}; + static const u8 mtk_smi_larb_mt8188_ostd[][SMI_LARB_PORT_NR_MAX] = { [0] = {0x02, 0x18, 0x22, 0x22, 0x01, 0x02, 0x0a,}, [1] = {0x12, 0x02, 0x14, 0x14, 0x01, 0x18, 0x0a,}, @@ -429,6 +466,12 @@ static const struct mtk_smi_larb_gen mtk_smi_larb_mt6779 = { /* DUMMY | IPU0 | IPU1 | CCU | MDLA */ }; +static const struct mtk_smi_larb_gen mtk_smi_larb_mt6893 = { + .config_port = mtk_smi_larb_config_port_gen2_general, + .flags_general = MTK_SMI_FLAG_THRT_UPDATE | MTK_SMI_FLAG_SW_FLAG, + .ostd = mtk_smi_larb_mt6893_ostd, +}; + static const struct mtk_smi_larb_gen mtk_smi_larb_mt8167 = { /* mt8167 do not need the port in larb */ .config_port = mtk_smi_larb_config_port_mt8167, @@ -474,6 +517,7 @@ static const struct of_device_id mtk_smi_larb_of_ids[] = { {.compatible = "mediatek,mt2712-smi-larb", .data = &mtk_smi_larb_mt2712}, {.compatible = "mediatek,mt6779-smi-larb", .data = &mtk_smi_larb_mt6779}, {.compatible = "mediatek,mt6795-smi-larb", .data = &mtk_smi_larb_mt8173}, + {.compatible = "mediatek,mt6893-smi-larb", .data = &mtk_smi_larb_mt6893}, {.compatible = "mediatek,mt8167-smi-larb", .data = &mtk_smi_larb_mt8167}, {.compatible = "mediatek,mt8173-smi-larb", .data = &mtk_smi_larb_mt8173}, {.compatible = "mediatek,mt8183-smi-larb", .data = &mtk_smi_larb_mt8183}, @@ -694,6 +738,13 @@ static const struct mtk_smi_common_plat mtk_smi_common_mt6795 = { .init = mtk_smi_common_mt6795_init, }; +static const struct mtk_smi_common_plat mtk_smi_common_mt6893 = { + .type = MTK_SMI_GEN2, + .has_gals = true, + .bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(4) | + F_MMU1_LARB(5) | F_MMU1_LARB(7), +}; + static const struct mtk_smi_common_plat mtk_smi_common_mt8183 = { .type = MTK_SMI_GEN2, .has_gals = true, @@ -756,6 +807,7 @@ static const struct of_device_id mtk_smi_common_of_ids[] = { {.compatible = "mediatek,mt2712-smi-common", .data = &mtk_smi_common_gen2}, {.compatible = "mediatek,mt6779-smi-common", .data = &mtk_smi_common_mt6779}, {.compatible = "mediatek,mt6795-smi-common", .data = &mtk_smi_common_mt6795}, + {.compatible = "mediatek,mt6893-smi-common", .data = &mtk_smi_common_mt6893}, {.compatible = "mediatek,mt8167-smi-common", .data = &mtk_smi_common_gen2}, {.compatible = "mediatek,mt8173-smi-common", .data = &mtk_smi_common_gen2}, {.compatible = "mediatek,mt8183-smi-common", .data = &mtk_smi_common_mt8183}, diff --git a/drivers/memory/omap-gpmc.c b/drivers/memory/omap-gpmc.c index d5bf3243fe78..9c96eed00194 100644 --- a/drivers/memory/omap-gpmc.c +++ b/drivers/memory/omap-gpmc.c @@ -2374,7 +2374,7 @@ static void gpmc_probe_dt_children(struct platform_device *pdev) static int gpmc_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) { - return 1; /* we're input only */ + return GPIO_LINE_DIRECTION_IN; /* we're input only */ } static int gpmc_gpio_direction_input(struct gpio_chip *chip, @@ -2383,17 +2383,6 @@ static int gpmc_gpio_direction_input(struct gpio_chip *chip, return 0; /* we're input only */ } -static int gpmc_gpio_direction_output(struct gpio_chip *chip, - unsigned int offset, int value) -{ - return -EINVAL; /* we're input only */ -} - -static void gpmc_gpio_set(struct gpio_chip *chip, unsigned int offset, - int value) -{ -} - static int gpmc_gpio_get(struct gpio_chip *chip, unsigned int offset) { u32 reg; @@ -2415,8 +2404,6 @@ static int gpmc_gpio_init(struct gpmc_device *gpmc) gpmc->gpio_chip.ngpio = gpmc_nr_waitpins; gpmc->gpio_chip.get_direction = gpmc_gpio_get_direction; gpmc->gpio_chip.direction_input = gpmc_gpio_direction_input; - gpmc->gpio_chip.direction_output = gpmc_gpio_direction_output; - gpmc->gpio_chip.set = gpmc_gpio_set; gpmc->gpio_chip.get = gpmc_gpio_get; gpmc->gpio_chip.base = -1; diff --git a/drivers/memory/stm32_omm.c b/drivers/memory/stm32_omm.c new file mode 100644 index 000000000000..79ceb1635698 --- /dev/null +++ b/drivers/memory/stm32_omm.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) STMicroelectronics 2025 - All Rights Reserved + * Author(s): Patrice Chotard <patrice.chotard@foss.st.com> for STMicroelectronics. + */ + +#include <linux/bitfield.h> +#include <linux/bus/stm32_firewall_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mfd/syscon.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#define OMM_CR 0 +#define CR_MUXEN BIT(0) +#define CR_MUXENMODE_MASK GENMASK(1, 0) +#define CR_CSSEL_OVR_EN BIT(4) +#define CR_CSSEL_OVR_MASK GENMASK(6, 5) +#define CR_REQ2ACK_MASK GENMASK(23, 16) + +#define OMM_CHILD_NB 2 +#define OMM_CLK_NB 3 + +struct stm32_omm { + struct resource *mm_res; + struct clk_bulk_data clk_bulk[OMM_CLK_NB]; + struct reset_control *child_reset[OMM_CHILD_NB]; + void __iomem *io_base; + u32 cr; + u8 nb_child; + bool restore_omm; +}; + +static int stm32_omm_set_amcr(struct device *dev, bool set) +{ + struct stm32_omm *omm = dev_get_drvdata(dev); + resource_size_t mm_ospi2_size = 0; + static const char * const mm_name[] = { "ospi1", "ospi2" }; + struct regmap *syscfg_regmap; + struct device_node *node; + struct resource res, res1; + u32 amcr_base, amcr_mask; + int ret, idx; + unsigned int i, amcr, read_amcr; + + for (i = 0; i < omm->nb_child; i++) { + idx = of_property_match_string(dev->of_node, + "memory-region-names", + mm_name[i]); + if (idx < 0) + continue; + + /* res1 only used on second loop iteration */ + res1.start = res.start; + res1.end = res.end; + + node = of_parse_phandle(dev->of_node, "memory-region", idx); + if (!node) + continue; + + ret = of_address_to_resource(node, 0, &res); + if (ret) { + of_node_put(node); + dev_err(dev, "unable to resolve memory region\n"); + return ret; + } + + /* check that memory region fits inside OMM memory map area */ + if (!resource_contains(omm->mm_res, &res)) { + dev_err(dev, "%s doesn't fit inside OMM memory map area\n", + mm_name[i]); + dev_err(dev, "%pR doesn't fit inside %pR\n", &res, omm->mm_res); + of_node_put(node); + + return -EFAULT; + } + + if (i == 1) { + mm_ospi2_size = resource_size(&res); + + /* check that OMM memory region 1 doesn't overlap memory region 2 */ + if (resource_overlaps(&res, &res1)) { + dev_err(dev, "OMM memory-region %s overlaps memory region %s\n", + mm_name[0], mm_name[1]); + dev_err(dev, "%pR overlaps %pR\n", &res1, &res); + of_node_put(node); + + return -EFAULT; + } + } + of_node_put(node); + } + + syscfg_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "st,syscfg-amcr"); + if (IS_ERR(syscfg_regmap)) + return dev_err_probe(dev, PTR_ERR(syscfg_regmap), + "Failed to get st,syscfg-amcr property\n"); + + ret = of_property_read_u32_index(dev->of_node, "st,syscfg-amcr", 1, + &amcr_base); + if (ret) + return ret; + + ret = of_property_read_u32_index(dev->of_node, "st,syscfg-amcr", 2, + &amcr_mask); + if (ret) + return ret; + + amcr = mm_ospi2_size / SZ_64M; + + if (set) + regmap_update_bits(syscfg_regmap, amcr_base, amcr_mask, amcr); + + /* read AMCR and check coherency with memory-map areas defined in DT */ + regmap_read(syscfg_regmap, amcr_base, &read_amcr); + read_amcr = read_amcr >> (ffs(amcr_mask) - 1); + + if (amcr != read_amcr) { + dev_err(dev, "AMCR value not coherent with DT memory-map areas\n"); + ret = -EINVAL; + } + + return ret; +} + +static int stm32_omm_toggle_child_clock(struct device *dev, bool enable) +{ + struct stm32_omm *omm = dev_get_drvdata(dev); + int i, ret; + + for (i = 0; i < omm->nb_child; i++) { + if (enable) { + ret = clk_prepare_enable(omm->clk_bulk[i + 1].clk); + if (ret) { + dev_err(dev, "Can not enable clock\n"); + goto clk_error; + } + } else { + clk_disable_unprepare(omm->clk_bulk[i + 1].clk); + } + } + + return 0; + +clk_error: + while (i--) + clk_disable_unprepare(omm->clk_bulk[i + 1].clk); + + return ret; +} + +static int stm32_omm_disable_child(struct device *dev) +{ + struct stm32_omm *omm = dev_get_drvdata(dev); + struct reset_control *reset; + int ret; + u8 i; + + ret = stm32_omm_toggle_child_clock(dev, true); + if (ret) + return ret; + + for (i = 0; i < omm->nb_child; i++) { + /* reset OSPI to ensure CR_EN bit is set to 0 */ + reset = omm->child_reset[i]; + ret = reset_control_acquire(reset); + if (ret) { + stm32_omm_toggle_child_clock(dev, false); + dev_err(dev, "Can not acquire reset %d\n", ret); + return ret; + } + + reset_control_assert(reset); + udelay(2); + reset_control_deassert(reset); + + reset_control_release(reset); + } + + return stm32_omm_toggle_child_clock(dev, false); +} + +static int stm32_omm_configure(struct device *dev) +{ + static const char * const clocks_name[] = {"omm", "ospi1", "ospi2"}; + struct stm32_omm *omm = dev_get_drvdata(dev); + unsigned long clk_rate_max = 0; + u32 mux = 0; + u32 cssel_ovr = 0; + u32 req2ack = 0; + struct reset_control *rstc; + unsigned long clk_rate; + int ret; + u8 i; + + for (i = 0; i < OMM_CLK_NB; i++) + omm->clk_bulk[i].id = clocks_name[i]; + + /* retrieve OMM, OSPI1 and OSPI2 clocks */ + ret = devm_clk_bulk_get(dev, OMM_CLK_NB, omm->clk_bulk); + if (ret) + return dev_err_probe(dev, ret, "Failed to get OMM/OSPI's clocks\n"); + + /* Ensure both OSPI instance are disabled before configuring OMM */ + ret = stm32_omm_disable_child(dev); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* parse children's clock */ + for (i = 1; i <= omm->nb_child; i++) { + clk_rate = clk_get_rate(omm->clk_bulk[i].clk); + if (!clk_rate) { + dev_err(dev, "Invalid clock rate\n"); + ret = -EINVAL; + goto error; + } + + if (clk_rate > clk_rate_max) + clk_rate_max = clk_rate; + } + + rstc = devm_reset_control_get_exclusive(dev, "omm"); + if (IS_ERR(rstc)) { + ret = dev_err_probe(dev, PTR_ERR(rstc), "reset get failed\n"); + goto error; + } + + reset_control_assert(rstc); + udelay(2); + reset_control_deassert(rstc); + + omm->cr = readl_relaxed(omm->io_base + OMM_CR); + /* optional */ + ret = of_property_read_u32(dev->of_node, "st,omm-mux", &mux); + if (!ret) { + if (mux & CR_MUXEN) { + ret = of_property_read_u32(dev->of_node, "st,omm-req2ack-ns", + &req2ack); + if (!ret && !req2ack) { + req2ack = DIV_ROUND_UP(req2ack, NSEC_PER_SEC / clk_rate_max) - 1; + + if (req2ack > 256) + req2ack = 256; + } + + req2ack = FIELD_PREP(CR_REQ2ACK_MASK, req2ack); + + omm->cr &= ~CR_REQ2ACK_MASK; + omm->cr |= FIELD_PREP(CR_REQ2ACK_MASK, req2ack); + + /* + * If the mux is enabled, the 2 OSPI clocks have to be + * always enabled + */ + ret = stm32_omm_toggle_child_clock(dev, true); + if (ret) + goto error; + } + + omm->cr &= ~CR_MUXENMODE_MASK; + omm->cr |= FIELD_PREP(CR_MUXENMODE_MASK, mux); + } + + /* optional */ + ret = of_property_read_u32(dev->of_node, "st,omm-cssel-ovr", &cssel_ovr); + if (!ret) { + omm->cr &= ~CR_CSSEL_OVR_MASK; + omm->cr |= FIELD_PREP(CR_CSSEL_OVR_MASK, cssel_ovr); + omm->cr |= CR_CSSEL_OVR_EN; + } + + omm->restore_omm = true; + writel_relaxed(omm->cr, omm->io_base + OMM_CR); + + ret = stm32_omm_set_amcr(dev, true); + +error: + pm_runtime_put_sync_suspend(dev); + + return ret; +} + +static int stm32_omm_check_access(struct device_node *np) +{ + struct stm32_firewall firewall; + int ret; + + ret = stm32_firewall_get_firewall(np, &firewall, 1); + if (ret) + return ret; + + return stm32_firewall_grant_access(&firewall); +} + +static int stm32_omm_probe(struct platform_device *pdev) +{ + static const char * const resets_name[] = {"ospi1", "ospi2"}; + struct device *dev = &pdev->dev; + u8 child_access_granted = 0; + struct stm32_omm *omm; + int i, ret; + + omm = devm_kzalloc(dev, sizeof(*omm), GFP_KERNEL); + if (!omm) + return -ENOMEM; + + omm->io_base = devm_platform_ioremap_resource_byname(pdev, "regs"); + if (IS_ERR(omm->io_base)) + return PTR_ERR(omm->io_base); + + omm->mm_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory_map"); + if (!omm->mm_res) + return -ENODEV; + + /* check child's access */ + for_each_child_of_node_scoped(dev->of_node, child) { + if (omm->nb_child >= OMM_CHILD_NB) { + dev_err(dev, "Bad DT, found too much children\n"); + return -E2BIG; + } + + ret = stm32_omm_check_access(child); + if (ret < 0 && ret != -EACCES) + return ret; + + if (!ret) + child_access_granted++; + + omm->nb_child++; + } + + if (omm->nb_child != OMM_CHILD_NB) + return -EINVAL; + + platform_set_drvdata(pdev, omm); + + devm_pm_runtime_enable(dev); + + /* check if OMM's resource access is granted */ + ret = stm32_omm_check_access(dev->of_node); + if (ret < 0 && ret != -EACCES) + return ret; + + for (i = 0; i < omm->nb_child; i++) { + omm->child_reset[i] = devm_reset_control_get_exclusive_released(dev, + resets_name[i]); + + if (IS_ERR(omm->child_reset[i])) + return dev_err_probe(dev, PTR_ERR(omm->child_reset[i]), + "Can't get %s reset\n", resets_name[i]); + } + + if (!ret && child_access_granted == OMM_CHILD_NB) { + ret = stm32_omm_configure(dev); + if (ret) + return ret; + } else { + dev_dbg(dev, "Octo Memory Manager resource's access not granted\n"); + /* + * AMCR can't be set, so check if current value is coherent + * with memory-map areas defined in DT + */ + ret = stm32_omm_set_amcr(dev, false); + if (ret) + return ret; + } + + ret = devm_of_platform_populate(dev); + if (ret) { + if (omm->cr & CR_MUXEN) + stm32_omm_toggle_child_clock(&pdev->dev, false); + + return dev_err_probe(dev, ret, "Failed to create Octo Memory Manager child\n"); + } + + return 0; +} + +static void stm32_omm_remove(struct platform_device *pdev) +{ + struct stm32_omm *omm = platform_get_drvdata(pdev); + + if (omm->cr & CR_MUXEN) + stm32_omm_toggle_child_clock(&pdev->dev, false); +} + +static const struct of_device_id stm32_omm_of_match[] = { + { .compatible = "st,stm32mp25-omm", }, + {} +}; +MODULE_DEVICE_TABLE(of, stm32_omm_of_match); + +static int __maybe_unused stm32_omm_runtime_suspend(struct device *dev) +{ + struct stm32_omm *omm = dev_get_drvdata(dev); + + clk_disable_unprepare(omm->clk_bulk[0].clk); + + return 0; +} + +static int __maybe_unused stm32_omm_runtime_resume(struct device *dev) +{ + struct stm32_omm *omm = dev_get_drvdata(dev); + + return clk_prepare_enable(omm->clk_bulk[0].clk); +} + +static int __maybe_unused stm32_omm_suspend(struct device *dev) +{ + struct stm32_omm *omm = dev_get_drvdata(dev); + + if (omm->restore_omm && omm->cr & CR_MUXEN) + stm32_omm_toggle_child_clock(dev, false); + + return pinctrl_pm_select_sleep_state(dev); +} + +static int __maybe_unused stm32_omm_resume(struct device *dev) +{ + struct stm32_omm *omm = dev_get_drvdata(dev); + int ret; + + pinctrl_pm_select_default_state(dev); + + if (!omm->restore_omm) + return 0; + + /* Ensure both OSPI instance are disabled before configuring OMM */ + ret = stm32_omm_disable_child(dev); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + writel_relaxed(omm->cr, omm->io_base + OMM_CR); + ret = stm32_omm_set_amcr(dev, true); + pm_runtime_put_sync_suspend(dev); + if (ret) + return ret; + + if (omm->cr & CR_MUXEN) + ret = stm32_omm_toggle_child_clock(dev, true); + + return ret; +} + +static const struct dev_pm_ops stm32_omm_pm_ops = { + SET_RUNTIME_PM_OPS(stm32_omm_runtime_suspend, + stm32_omm_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(stm32_omm_suspend, stm32_omm_resume) +}; + +static struct platform_driver stm32_omm_driver = { + .probe = stm32_omm_probe, + .remove = stm32_omm_remove, + .driver = { + .name = "stm32-omm", + .of_match_table = stm32_omm_of_match, + .pm = &stm32_omm_pm_ops, + }, +}; +module_platform_driver(stm32_omm_driver); + +MODULE_DESCRIPTION("STMicroelectronics Octo Memory Manager driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig index 3fe83d7c2bf8..fc5a27791826 100644 --- a/drivers/memory/tegra/Kconfig +++ b/drivers/memory/tegra/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config TEGRA_MC bool "NVIDIA Tegra Memory Controller support" - default y + default ARCH_TEGRA depends on ARCH_TEGRA || (COMPILE_TEST && COMMON_CLK) select INTERCONNECT help @@ -12,7 +12,7 @@ if TEGRA_MC config TEGRA20_EMC tristate "NVIDIA Tegra20 External Memory Controller driver" - default y + default ARCH_TEGRA_2x_SOC depends on ARCH_TEGRA_2x_SOC || COMPILE_TEST select DEVFREQ_GOV_SIMPLE_ONDEMAND select PM_DEVFREQ @@ -25,7 +25,7 @@ config TEGRA20_EMC config TEGRA30_EMC tristate "NVIDIA Tegra30 External Memory Controller driver" - default y + default ARCH_TEGRA_3x_SOC depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST select PM_OPP select DDR @@ -37,7 +37,7 @@ config TEGRA30_EMC config TEGRA124_EMC tristate "NVIDIA Tegra124 External Memory Controller driver" - default y + default ARCH_TEGRA_124_SOC depends on ARCH_TEGRA_124_SOC || COMPILE_TEST select TEGRA124_CLK_EMC if ARCH_TEGRA select PM_OPP diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index 339b148d6793..c776543cbba5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -1757,7 +1757,7 @@ iwl_mld_send_proto_offload(struct iwl_mld *mld, addrconf_addr_solict_mult(&wowlan_data->target_ipv6_addrs[i], &solicited_addr); - for (j = 0; j < c; j++) + for (j = 0; j < n_nsc && j < c; j++) if (ipv6_addr_cmp(&nsc[j].dest_ipv6_addr, &solicited_addr) == 0) break; diff --git a/drivers/nvme/target/Kconfig b/drivers/nvme/target/Kconfig index 4c253b433bf7..4904097dfd49 100644 --- a/drivers/nvme/target/Kconfig +++ b/drivers/nvme/target/Kconfig @@ -3,7 +3,7 @@ config NVME_TARGET tristate "NVMe Target support" depends on BLOCK - depends on CONFIGFS_FS + select CONFIGFS_FS select NVME_KEYRING if NVME_TARGET_TCP_TLS select KEYS if NVME_TARGET_TCP_TLS select SGL_ALLOC diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index aedd0e2dcd89..0edd639898a6 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -25,6 +25,7 @@ #include <linux/serial_core.h> #include <linux/sysfs.h> #include <linux/random.h> +#include <linux/kexec_handover.h> #include <asm/setup.h> /* for COMMAND_LINE_SIZE */ #include <asm/page.h> @@ -875,6 +876,36 @@ void __init early_init_dt_check_for_usable_mem_range(void) memblock_add(rgn[i].base, rgn[i].size); } +/** + * early_init_dt_check_kho - Decode info required for kexec handover from DT + */ +static void __init early_init_dt_check_kho(void) +{ + unsigned long node = chosen_node_offset; + u64 fdt_start, fdt_size, scratch_start, scratch_size; + const __be32 *p; + int l; + + if (!IS_ENABLED(CONFIG_KEXEC_HANDOVER) || (long)node < 0) + return; + + p = of_get_flat_dt_prop(node, "linux,kho-fdt", &l); + if (l != (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32)) + return; + + fdt_start = dt_mem_next_cell(dt_root_addr_cells, &p); + fdt_size = dt_mem_next_cell(dt_root_addr_cells, &p); + + p = of_get_flat_dt_prop(node, "linux,kho-scratch", &l); + if (l != (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32)) + return; + + scratch_start = dt_mem_next_cell(dt_root_addr_cells, &p); + scratch_size = dt_mem_next_cell(dt_root_addr_cells, &p); + + kho_populate(fdt_start, fdt_size, scratch_start, scratch_size); +} + #ifdef CONFIG_SERIAL_EARLYCON int __init early_init_dt_scan_chosen_stdout(void) @@ -1169,6 +1200,9 @@ void __init early_init_dt_scan_nodes(void) /* Handle linux,usable-memory-range property */ early_init_dt_check_for_usable_mem_range(); + + /* Handle kexec handover */ + early_init_dt_check_kho(); } bool __init early_init_dt_scan(void *dt_virt, phys_addr_t dt_phys) diff --git a/drivers/of/kexec.c b/drivers/of/kexec.c index 5b924597a4de..1ee2d31816ae 100644 --- a/drivers/of/kexec.c +++ b/drivers/of/kexec.c @@ -264,6 +264,43 @@ static inline int setup_ima_buffer(const struct kimage *image, void *fdt, } #endif /* CONFIG_IMA_KEXEC */ +static int kho_add_chosen(const struct kimage *image, void *fdt, int chosen_node) +{ + int ret = 0; +#ifdef CONFIG_KEXEC_HANDOVER + phys_addr_t fdt_mem = 0; + phys_addr_t fdt_len = 0; + phys_addr_t scratch_mem = 0; + phys_addr_t scratch_len = 0; + + ret = fdt_delprop(fdt, chosen_node, "linux,kho-fdt"); + if (ret && ret != -FDT_ERR_NOTFOUND) + return ret; + ret = fdt_delprop(fdt, chosen_node, "linux,kho-scratch"); + if (ret && ret != -FDT_ERR_NOTFOUND) + return ret; + + if (!image->kho.fdt || !image->kho.scratch) + return 0; + + fdt_mem = image->kho.fdt; + fdt_len = PAGE_SIZE; + scratch_mem = image->kho.scratch->mem; + scratch_len = image->kho.scratch->bufsz; + + pr_debug("Adding kho metadata to DT"); + + ret = fdt_appendprop_addrrange(fdt, 0, chosen_node, "linux,kho-fdt", + fdt_mem, fdt_len); + if (ret) + return ret; + ret = fdt_appendprop_addrrange(fdt, 0, chosen_node, "linux,kho-scratch", + scratch_mem, scratch_len); + +#endif /* CONFIG_KEXEC_HANDOVER */ + return ret; +} + /* * of_kexec_alloc_and_setup_fdt - Alloc and setup a new Flattened Device Tree * @@ -414,6 +451,11 @@ void *of_kexec_alloc_and_setup_fdt(const struct kimage *image, #endif } + /* Add kho metadata if this is a KHO image */ + ret = kho_add_chosen(image, fdt, chosen_node); + if (ret) + goto out; + /* add bootargs */ if (cmdline) { ret = fdt_setprop_string(fdt, chosen_node, "bootargs", cmdline); diff --git a/drivers/platform/cznic/Kconfig b/drivers/platform/cznic/Kconfig index 13e37b49d9d0..61cff5f7e02e 100644 --- a/drivers/platform/cznic/Kconfig +++ b/drivers/platform/cznic/Kconfig @@ -76,6 +76,23 @@ config TURRIS_OMNIA_MCU_TRNG Say Y here to add support for the true random number generator provided by CZ.NIC's Turris Omnia MCU. +config TURRIS_OMNIA_MCU_KEYCTL + bool "Turris Omnia MCU ECDSA message signing" + default y + depends on KEYS + depends on ASYMMETRIC_KEY_TYPE + depends on TURRIS_OMNIA_MCU_GPIO + select TURRIS_SIGNING_KEY + help + Say Y here to add support for ECDSA message signing with board private + key (if available on the MCU). This is exposed via the keyctl() + syscall. + endif # TURRIS_OMNIA_MCU +config TURRIS_SIGNING_KEY + tristate + depends on KEYS + depends on ASYMMETRIC_KEY_TYPE + endif # CZNIC_PLATFORMS diff --git a/drivers/platform/cznic/Makefile b/drivers/platform/cznic/Makefile index ce6d997f34d6..ccad7bec82e1 100644 --- a/drivers/platform/cznic/Makefile +++ b/drivers/platform/cznic/Makefile @@ -3,6 +3,9 @@ obj-$(CONFIG_TURRIS_OMNIA_MCU) += turris-omnia-mcu.o turris-omnia-mcu-y := turris-omnia-mcu-base.o turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_GPIO) += turris-omnia-mcu-gpio.o +turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_KEYCTL) += turris-omnia-mcu-keyctl.o turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_SYSOFF_WAKEUP) += turris-omnia-mcu-sys-off-wakeup.o turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_TRNG) += turris-omnia-mcu-trng.o turris-omnia-mcu-$(CONFIG_TURRIS_OMNIA_MCU_WATCHDOG) += turris-omnia-mcu-watchdog.o + +obj-$(CONFIG_TURRIS_SIGNING_KEY) += turris-signing-key.o diff --git a/drivers/platform/cznic/turris-omnia-mcu-base.c b/drivers/platform/cznic/turris-omnia-mcu-base.c index 770e680b96f9..e8fc0d7b3343 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-base.c +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c @@ -392,6 +392,10 @@ static int omnia_mcu_probe(struct i2c_client *client) if (err) return err; + err = omnia_mcu_register_keyctl(mcu); + if (err) + return err; + return omnia_mcu_register_trng(mcu); } diff --git a/drivers/platform/cznic/turris-omnia-mcu-gpio.c b/drivers/platform/cznic/turris-omnia-mcu-gpio.c index 5f35f7c5d5d7..c2df24ea8686 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-gpio.c +++ b/drivers/platform/cznic/turris-omnia-mcu-gpio.c @@ -13,6 +13,7 @@ #include <linux/device.h> #include <linux/devm-helpers.h> #include <linux/errno.h> +#include <linux/gpio/consumer.h> #include <linux/gpio/driver.h> #include <linux/i2c.h> #include <linux/interrupt.h> @@ -195,7 +196,7 @@ static const struct omnia_gpio omnia_gpios[64] = { }; /* mapping from interrupts to indexes of GPIOs in the omnia_gpios array */ -const u8 omnia_int_to_gpio_idx[32] = { +static const u8 omnia_int_to_gpio_idx[32] = { [__bf_shf(OMNIA_INT_CARD_DET)] = 4, [__bf_shf(OMNIA_INT_MSATA_IND)] = 5, [__bf_shf(OMNIA_INT_USB30_OVC)] = 6, @@ -1093,3 +1094,21 @@ int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu) return 0; } + +int omnia_mcu_request_irq(struct omnia_mcu *mcu, u32 spec, + irq_handler_t thread_fn, const char *devname) +{ + u8 irq_idx; + int irq; + + if (!spec) + return -EINVAL; + + irq_idx = omnia_int_to_gpio_idx[ffs(spec) - 1]; + irq = gpiod_to_irq(gpio_device_get_desc(mcu->gc.gpiodev, irq_idx)); + if (irq < 0) + return irq; + + return devm_request_threaded_irq(&mcu->client->dev, irq, NULL, + thread_fn, IRQF_ONESHOT, devname, mcu); +} diff --git a/drivers/platform/cznic/turris-omnia-mcu-keyctl.c b/drivers/platform/cznic/turris-omnia-mcu-keyctl.c new file mode 100644 index 000000000000..dc40f942f082 --- /dev/null +++ b/drivers/platform/cznic/turris-omnia-mcu-keyctl.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CZ.NIC's Turris Omnia MCU ECDSA message signing via keyctl + * + * 2025 by Marek Behún <kabel@kernel.org> + */ + +#include <crypto/sha2.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/key.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <linux/turris-omnia-mcu-interface.h> +#include <linux/turris-signing-key.h> +#include "turris-omnia-mcu.h" + +static irqreturn_t omnia_msg_signed_irq_handler(int irq, void *dev_id) +{ + u8 reply[1 + OMNIA_MCU_CRYPTO_SIGNATURE_LEN]; + struct omnia_mcu *mcu = dev_id; + int err; + + err = omnia_cmd_read(mcu->client, OMNIA_CMD_CRYPTO_COLLECT_SIGNATURE, + reply, sizeof(reply)); + if (!err && reply[0] != OMNIA_MCU_CRYPTO_SIGNATURE_LEN) + err = -EIO; + + guard(mutex)(&mcu->sign_lock); + + if (mcu->sign_requested) { + mcu->sign_err = err; + if (!err) + memcpy(mcu->signature, &reply[1], + OMNIA_MCU_CRYPTO_SIGNATURE_LEN); + mcu->sign_requested = false; + complete(&mcu->msg_signed); + } + + return IRQ_HANDLED; +} + +static int omnia_mcu_sign(const struct key *key, const void *msg, + void *signature) +{ + struct omnia_mcu *mcu = dev_get_drvdata(turris_signing_key_get_dev(key)); + u8 cmd[1 + SHA256_DIGEST_SIZE], reply; + int err; + + scoped_guard(mutex, &mcu->sign_lock) { + if (mcu->sign_requested) + return -EBUSY; + + cmd[0] = OMNIA_CMD_CRYPTO_SIGN_MESSAGE; + memcpy(&cmd[1], msg, SHA256_DIGEST_SIZE); + + err = omnia_cmd_write_read(mcu->client, cmd, sizeof(cmd), + &reply, 1); + if (err) + return err; + + if (!reply) + return -EBUSY; + + mcu->sign_requested = true; + } + + if (wait_for_completion_interruptible(&mcu->msg_signed)) + return -EINTR; + + guard(mutex)(&mcu->sign_lock); + + if (mcu->sign_err) + return mcu->sign_err; + + memcpy(signature, mcu->signature, OMNIA_MCU_CRYPTO_SIGNATURE_LEN); + + /* forget the signature, for security */ + memzero_explicit(mcu->signature, sizeof(mcu->signature)); + + return OMNIA_MCU_CRYPTO_SIGNATURE_LEN; +} + +static const void *omnia_mcu_get_public_key(const struct key *key) +{ + struct omnia_mcu *mcu = dev_get_drvdata(turris_signing_key_get_dev(key)); + + return mcu->board_public_key; +} + +static const struct turris_signing_key_subtype omnia_signing_key_subtype = { + .key_size = 256, + .data_size = SHA256_DIGEST_SIZE, + .sig_size = OMNIA_MCU_CRYPTO_SIGNATURE_LEN, + .public_key_size = OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN, + .hash_algo = "sha256", + .get_public_key = omnia_mcu_get_public_key, + .sign = omnia_mcu_sign, +}; + +static int omnia_mcu_read_public_key(struct omnia_mcu *mcu) +{ + u8 reply[1 + OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN]; + int err; + + err = omnia_cmd_read(mcu->client, OMNIA_CMD_CRYPTO_GET_PUBLIC_KEY, + reply, sizeof(reply)); + if (err) + return err; + + if (reply[0] != OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN) + return -EIO; + + memcpy(mcu->board_public_key, &reply[1], + OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN); + + return 0; +} + +int omnia_mcu_register_keyctl(struct omnia_mcu *mcu) +{ + struct device *dev = &mcu->client->dev; + char desc[48]; + int err; + + if (!(mcu->features & OMNIA_FEAT_CRYPTO)) + return 0; + + err = omnia_mcu_read_public_key(mcu); + if (err) + return dev_err_probe(dev, err, + "Cannot read board public key\n"); + + err = devm_mutex_init(dev, &mcu->sign_lock); + if (err) + return err; + + init_completion(&mcu->msg_signed); + + err = omnia_mcu_request_irq(mcu, OMNIA_INT_MESSAGE_SIGNED, + omnia_msg_signed_irq_handler, + "turris-omnia-mcu-keyctl"); + if (err) + return dev_err_probe(dev, err, + "Cannot request MESSAGE_SIGNED IRQ\n"); + + sprintf(desc, "Turris Omnia SN %016llX MCU ECDSA key", + mcu->board_serial_number); + + err = devm_turris_signing_key_create(dev, &omnia_signing_key_subtype, + desc); + if (err) + return dev_err_probe(dev, err, "Cannot create signing key\n"); + + return 0; +} diff --git a/drivers/platform/cznic/turris-omnia-mcu-trng.c b/drivers/platform/cznic/turris-omnia-mcu-trng.c index 9a1d9292dc9a..e3826959e6de 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-trng.c +++ b/drivers/platform/cznic/turris-omnia-mcu-trng.c @@ -5,12 +5,9 @@ * 2024 by Marek Behún <kabel@kernel.org> */ -#include <linux/bitfield.h> #include <linux/completion.h> #include <linux/container_of.h> #include <linux/errno.h> -#include <linux/gpio/consumer.h> -#include <linux/gpio/driver.h> #include <linux/hw_random.h> #include <linux/i2c.h> #include <linux/interrupt.h> @@ -62,17 +59,12 @@ static int omnia_trng_read(struct hwrng *rng, void *data, size_t max, bool wait) int omnia_mcu_register_trng(struct omnia_mcu *mcu) { struct device *dev = &mcu->client->dev; - u8 irq_idx, dummy; - int irq, err; + u8 dummy; + int err; if (!(mcu->features & OMNIA_FEAT_TRNG)) return 0; - irq_idx = omnia_int_to_gpio_idx[__bf_shf(OMNIA_INT_TRNG)]; - irq = gpiod_to_irq(gpio_device_get_desc(mcu->gc.gpiodev, irq_idx)); - if (irq < 0) - return dev_err_probe(dev, irq, "Cannot get TRNG IRQ\n"); - /* * If someone else cleared the TRNG interrupt but did not read the * entropy, a new interrupt won't be generated, and entropy collection @@ -86,9 +78,8 @@ int omnia_mcu_register_trng(struct omnia_mcu *mcu) init_completion(&mcu->trng_entropy_ready); - err = devm_request_threaded_irq(dev, irq, NULL, omnia_trng_irq_handler, - IRQF_ONESHOT, "turris-omnia-mcu-trng", - mcu); + err = omnia_mcu_request_irq(mcu, OMNIA_INT_TRNG, omnia_trng_irq_handler, + "turris-omnia-mcu-trng"); if (err) return dev_err_probe(dev, err, "Cannot request TRNG IRQ\n"); diff --git a/drivers/platform/cznic/turris-omnia-mcu.h b/drivers/platform/cznic/turris-omnia-mcu.h index 088541be3f4c..8473a3031917 100644 --- a/drivers/platform/cznic/turris-omnia-mcu.h +++ b/drivers/platform/cznic/turris-omnia-mcu.h @@ -12,11 +12,17 @@ #include <linux/gpio/driver.h> #include <linux/hw_random.h> #include <linux/if_ether.h> +#include <linux/interrupt.h> #include <linux/mutex.h> #include <linux/types.h> #include <linux/watchdog.h> #include <linux/workqueue.h> +enum { + OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN = 1 + 32, + OMNIA_MCU_CRYPTO_SIGNATURE_LEN = 64, +}; + struct i2c_client; struct rtc_device; @@ -55,6 +61,12 @@ struct rtc_device; * @wdt: watchdog driver structure * @trng: RNG driver structure * @trng_entropy_ready: RNG entropy ready completion + * @msg_signed: message signed completion + * @sign_lock: mutex to protect message signing state + * @sign_requested: flag indicating that message signing was requested but not completed + * @sign_err: message signing error number, filled in interrupt handler + * @signature: message signing signature, filled in interrupt handler + * @board_public_key: board public key, if stored in MCU */ struct omnia_mcu { struct i2c_client *client; @@ -88,12 +100,22 @@ struct omnia_mcu { struct hwrng trng; struct completion trng_entropy_ready; #endif + +#ifdef CONFIG_TURRIS_OMNIA_MCU_KEYCTL + struct completion msg_signed; + struct mutex sign_lock; + bool sign_requested; + int sign_err; + u8 signature[OMNIA_MCU_CRYPTO_SIGNATURE_LEN]; + u8 board_public_key[OMNIA_MCU_CRYPTO_PUBLIC_KEY_LEN]; +#endif }; #ifdef CONFIG_TURRIS_OMNIA_MCU_GPIO -extern const u8 omnia_int_to_gpio_idx[32]; extern const struct attribute_group omnia_mcu_gpio_group; int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu); +int omnia_mcu_request_irq(struct omnia_mcu *mcu, u32 spec, + irq_handler_t thread_fn, const char *devname); #else static inline int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu) { @@ -101,6 +123,15 @@ static inline int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu) } #endif +#ifdef CONFIG_TURRIS_OMNIA_MCU_KEYCTL +int omnia_mcu_register_keyctl(struct omnia_mcu *mcu); +#else +static inline int omnia_mcu_register_keyctl(struct omnia_mcu *mcu) +{ + return 0; +} +#endif + #ifdef CONFIG_TURRIS_OMNIA_MCU_SYSOFF_WAKEUP extern const struct attribute_group omnia_mcu_poweroff_group; int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu); diff --git a/drivers/platform/cznic/turris-signing-key.c b/drivers/platform/cznic/turris-signing-key.c new file mode 100644 index 000000000000..3827178565e2 --- /dev/null +++ b/drivers/platform/cznic/turris-signing-key.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Some of CZ.NIC's Turris devices support signing messages with a per-device unique asymmetric + * cryptographic key that was burned into the device at manufacture. + * + * This helper module exposes this message signing ability via the keyctl() syscall. Upon load, it + * creates the `.turris-signing-keys` keyring. A device-specific driver then has to create a signing + * key by calling devm_turris_signing_key_create(). + * + * 2025 by Marek Behún <kabel@kernel.org> + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/key-type.h> +#include <linux/key.h> +#include <linux/keyctl.h> +#include <linux/module.h> +#include <linux/seq_file.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <linux/turris-signing-key.h> + +static int turris_signing_key_instantiate(struct key *key, + struct key_preparsed_payload *payload) +{ + return 0; +} + +static void turris_signing_key_describe(const struct key *key, struct seq_file *m) +{ + const struct turris_signing_key_subtype *subtype = dereference_key_rcu(key); + + if (!subtype) + return; + + seq_printf(m, "%s: %*phN", key->description, subtype->public_key_size, + subtype->get_public_key(key)); +} + +static long turris_signing_key_read(const struct key *key, char *buffer, size_t buflen) +{ + const struct turris_signing_key_subtype *subtype = dereference_key_rcu(key); + + if (!subtype) + return -EIO; + + if (buffer) { + if (buflen > subtype->public_key_size) + buflen = subtype->public_key_size; + + memcpy(buffer, subtype->get_public_key(key), subtype->public_key_size); + } + + return subtype->public_key_size; +} + +static bool turris_signing_key_asym_valid_params(const struct turris_signing_key_subtype *subtype, + const struct kernel_pkey_params *params) +{ + if (params->encoding && strcmp(params->encoding, "raw")) + return false; + + if (params->hash_algo && strcmp(params->hash_algo, subtype->hash_algo)) + return false; + + return true; +} + +static int turris_signing_key_asym_query(const struct kernel_pkey_params *params, + struct kernel_pkey_query *info) +{ + const struct turris_signing_key_subtype *subtype = dereference_key_rcu(params->key); + + if (!subtype) + return -EIO; + + if (!turris_signing_key_asym_valid_params(subtype, params)) + return -EINVAL; + + info->supported_ops = KEYCTL_SUPPORTS_SIGN; + info->key_size = subtype->key_size; + info->max_data_size = subtype->data_size; + info->max_sig_size = subtype->sig_size; + info->max_enc_size = 0; + info->max_dec_size = 0; + + return 0; +} + +static int turris_signing_key_asym_eds_op(struct kernel_pkey_params *params, + const void *in, void *out) +{ + const struct turris_signing_key_subtype *subtype = dereference_key_rcu(params->key); + int err; + + if (!subtype) + return -EIO; + + if (!turris_signing_key_asym_valid_params(subtype, params)) + return -EINVAL; + + if (params->op != kernel_pkey_sign) + return -EOPNOTSUPP; + + if (params->in_len != subtype->data_size || params->out_len != subtype->sig_size) + return -EINVAL; + + err = subtype->sign(params->key, in, out); + if (err) + return err; + + return subtype->sig_size; +} + +static struct key_type turris_signing_key_type = { + .name = "turris-signing-key", + .instantiate = turris_signing_key_instantiate, + .describe = turris_signing_key_describe, + .read = turris_signing_key_read, + .asym_query = turris_signing_key_asym_query, + .asym_eds_op = turris_signing_key_asym_eds_op, +}; + +static struct key *turris_signing_keyring; + +static void turris_signing_key_release(void *key) +{ + key_unlink(turris_signing_keyring, key); + key_put(key); +} + +int +devm_turris_signing_key_create(struct device *dev, const struct turris_signing_key_subtype *subtype, + const char *desc) +{ + struct key *key; + key_ref_t kref; + + kref = key_create(make_key_ref(turris_signing_keyring, true), + turris_signing_key_type.name, desc, NULL, 0, + (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | KEY_USR_READ | + KEY_USR_SEARCH, + KEY_ALLOC_BUILT_IN | KEY_ALLOC_SET_KEEP | KEY_ALLOC_NOT_IN_QUOTA); + if (IS_ERR(kref)) + return PTR_ERR(kref); + + key = key_ref_to_ptr(kref); + key->payload.data[1] = dev; + rcu_assign_keypointer(key, subtype); + + return devm_add_action_or_reset(dev, turris_signing_key_release, key); +} +EXPORT_SYMBOL_GPL(devm_turris_signing_key_create); + +static int turris_signing_key_init(void) +{ + int err; + + err = register_key_type(&turris_signing_key_type); + if (err) + return err; + + turris_signing_keyring = keyring_alloc(".turris-signing-keys", + GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, current_cred(), + (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | + KEY_USR_READ | KEY_USR_SEARCH, + KEY_ALLOC_BUILT_IN | KEY_ALLOC_SET_KEEP | + KEY_ALLOC_NOT_IN_QUOTA, + NULL, NULL); + if (IS_ERR(turris_signing_keyring)) { + pr_err("Cannot allocate Turris keyring\n"); + + unregister_key_type(&turris_signing_key_type); + + return PTR_ERR(turris_signing_keyring); + } + + return 0; +} +module_init(turris_signing_key_init); + +static void turris_signing_key_exit(void) +{ + key_put(turris_signing_keyring); + unregister_key_type(&turris_signing_key_type); +} +module_exit(turris_signing_key_exit); + +MODULE_AUTHOR("Marek Behun <kabel@kernel.org>"); +MODULE_DESCRIPTION("CZ.NIC's Turris signing key helper"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/qcom_pmi8998_charger.c b/drivers/power/supply/qcom_pmi8998_charger.c index 74a8d8ed8d9f..c2f8f2e24398 100644 --- a/drivers/power/supply/qcom_pmi8998_charger.c +++ b/drivers/power/supply/qcom_pmi8998_charger.c @@ -2,7 +2,7 @@ /* * Copyright (c) 2016-2019 The Linux Foundation. All rights reserved. * Copyright (c) 2023, Linaro Ltd. - * Author: Caleb Connolly <caleb.connolly@linaro.org> + * Author: Casey Connolly <casey.connolly@linaro.org> * * This driver is for the switch-mode battery charger and boost * hardware found in pmi8998 and related PMICs. @@ -1045,6 +1045,6 @@ static struct platform_driver qcom_spmi_smb2 = { module_platform_driver(qcom_spmi_smb2); -MODULE_AUTHOR("Caleb Connolly <caleb.connolly@linaro.org>"); +MODULE_AUTHOR("Casey Connolly <casey.connolly@linaro.org>"); MODULE_DESCRIPTION("Qualcomm SMB2 Charger Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/rapidio/devices/rio_mport_cdev.c b/drivers/rapidio/devices/rio_mport_cdev.c index cbf531d0ba68..995cfeca972b 100644 --- a/drivers/rapidio/devices/rio_mport_cdev.c +++ b/drivers/rapidio/devices/rio_mport_cdev.c @@ -98,18 +98,6 @@ MODULE_PARM_DESC(dbg_level, "Debugging output level (default 0 = none)"); #endif /* - * An internal DMA coherent buffer - */ -struct mport_dma_buf { - void *ib_base; - dma_addr_t ib_phys; - u32 ib_size; - u64 ib_rio_base; - bool ib_map; - struct file *filp; -}; - -/* * Internal memory mapping structure */ enum rio_mport_map_dir { @@ -131,14 +119,6 @@ struct rio_mport_mapping { struct file *filp; }; -struct rio_mport_dma_map { - int valid; - u64 length; - void *vaddr; - dma_addr_t paddr; -}; - -#define MPORT_MAX_DMA_BUFS 16 #define MPORT_EVENT_DEPTH 10 /* diff --git a/drivers/rapidio/rio.c b/drivers/rapidio/rio.c index 9544b8ee0c96..46daf32ea13b 100644 --- a/drivers/rapidio/rio.c +++ b/drivers/rapidio/rio.c @@ -1775,19 +1775,6 @@ struct dma_chan *rio_request_mport_dma(struct rio_mport *mport) EXPORT_SYMBOL_GPL(rio_request_mport_dma); /** - * rio_request_dma - request RapidIO capable DMA channel that supports - * specified target RapidIO device. - * @rdev: RIO device associated with DMA transfer - * - * Returns pointer to allocated DMA channel or NULL if failed. - */ -struct dma_chan *rio_request_dma(struct rio_dev *rdev) -{ - return rio_request_mport_dma(rdev->net->hport); -} -EXPORT_SYMBOL_GPL(rio_request_dma); - -/** * rio_release_dma - release specified DMA channel * @dchan: DMA channel to release */ @@ -1834,57 +1821,9 @@ struct dma_async_tx_descriptor *rio_dma_prep_xfer(struct dma_chan *dchan, } EXPORT_SYMBOL_GPL(rio_dma_prep_xfer); -/** - * rio_dma_prep_slave_sg - RapidIO specific wrapper - * for device_prep_slave_sg callback defined by DMAENGINE. - * @rdev: RIO device control structure - * @dchan: DMA channel to configure - * @data: RIO specific data descriptor - * @direction: DMA data transfer direction (TO or FROM the device) - * @flags: dmaengine defined flags - * - * Initializes RapidIO capable DMA channel for the specified data transfer. - * Uses DMA channel private extension to pass information related to remote - * target RIO device. - * - * Returns: pointer to DMA transaction descriptor if successful, - * error-valued pointer or NULL if failed. - */ -struct dma_async_tx_descriptor *rio_dma_prep_slave_sg(struct rio_dev *rdev, - struct dma_chan *dchan, struct rio_dma_data *data, - enum dma_transfer_direction direction, unsigned long flags) -{ - return rio_dma_prep_xfer(dchan, rdev->destid, data, direction, flags); -} -EXPORT_SYMBOL_GPL(rio_dma_prep_slave_sg); - #endif /* CONFIG_RAPIDIO_DMA_ENGINE */ /** - * rio_find_mport - find RIO mport by its ID - * @mport_id: number (ID) of mport device - * - * Given a RIO mport number, the desired mport is located - * in the global list of mports. If the mport is found, a pointer to its - * data structure is returned. If no mport is found, %NULL is returned. - */ -struct rio_mport *rio_find_mport(int mport_id) -{ - struct rio_mport *port; - - mutex_lock(&rio_mport_list_lock); - list_for_each_entry(port, &rio_mports, node) { - if (port->id == mport_id) - goto found; - } - port = NULL; -found: - mutex_unlock(&rio_mport_list_lock); - - return port; -} - -/** * rio_register_scan - enumeration/discovery method registration interface * @mport_id: mport device ID for which fabric scan routine has to be set * (RIO_MPORT_ANY = set for all available mports) @@ -1962,48 +1901,6 @@ err_out: EXPORT_SYMBOL_GPL(rio_register_scan); /** - * rio_unregister_scan - removes enumeration/discovery method from mport - * @mport_id: mport device ID for which fabric scan routine has to be - * unregistered (RIO_MPORT_ANY = apply to all mports that use - * the specified scan_ops) - * @scan_ops: enumeration/discovery operations structure - * - * Removes enumeration or discovery method assigned to the specified mport - * device. If RIO_MPORT_ANY is specified, removes the specified operations from - * all mports that have them attached. - */ -int rio_unregister_scan(int mport_id, struct rio_scan *scan_ops) -{ - struct rio_mport *port; - struct rio_scan_node *scan; - - pr_debug("RIO: %s for mport_id=%d\n", __func__, mport_id); - - if (mport_id != RIO_MPORT_ANY && mport_id >= RIO_MAX_MPORTS) - return -EINVAL; - - mutex_lock(&rio_mport_list_lock); - - list_for_each_entry(port, &rio_mports, node) - if (port->id == mport_id || - (mport_id == RIO_MPORT_ANY && port->nscan == scan_ops)) - port->nscan = NULL; - - list_for_each_entry(scan, &rio_scans, node) { - if (scan->mport_id == mport_id) { - list_del(&scan->node); - kfree(scan); - break; - } - } - - mutex_unlock(&rio_mport_list_lock); - - return 0; -} -EXPORT_SYMBOL_GPL(rio_unregister_scan); - -/** * rio_mport_scan - execute enumeration/discovery on the specified mport * @mport_id: number (ID) of mport device */ diff --git a/drivers/rapidio/rio.h b/drivers/rapidio/rio.h index f482de0d0370..a0e2a09ddb8e 100644 --- a/drivers/rapidio/rio.h +++ b/drivers/rapidio/rio.h @@ -41,9 +41,7 @@ extern void rio_del_device(struct rio_dev *rdev, enum rio_device_state state); extern int rio_enable_rx_tx_port(struct rio_mport *port, int local, u16 destid, u8 hopcount, u8 port_num); extern int rio_register_scan(int mport_id, struct rio_scan *scan_ops); -extern int rio_unregister_scan(int mport_id, struct rio_scan *scan_ops); extern void rio_attach_device(struct rio_dev *rdev); -extern struct rio_mport *rio_find_mport(int mport_id); extern int rio_mport_scan(int mport_id); /* Structures internal to the RIO core code */ diff --git a/drivers/rapidio/rio_cm.c b/drivers/rapidio/rio_cm.c index 9135227301c8..97287e838ce1 100644 --- a/drivers/rapidio/rio_cm.c +++ b/drivers/rapidio/rio_cm.c @@ -198,12 +198,6 @@ struct cm_peer { struct rio_dev *rdev; }; -struct rio_cm_work { - struct work_struct work; - struct cm_dev *cm; - void *data; -}; - struct conn_req { struct list_head node; u32 destid; /* requester destID */ diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 99f6f9784e68..d85be5899da6 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -225,6 +225,13 @@ config RESET_RZG2L_USBPHY_CTRL Support for USBPHY Control found on RZ/G2L family. It mainly controls reset and power down of the USB/PHY. +config RESET_RZV2H_USB2PHY + tristate "Renesas RZ/V2H(P) (and similar SoCs) USB2PHY Reset driver" + depends on ARCH_RENESAS || COMPILE_TEST + help + Support for USB2PHY Port reset Control found on the RZ/V2H(P) SoC + (and similar SoCs). + config RESET_SCMI tristate "Reset driver controlled via ARM SCMI interface" depends on ARM_SCMI_PROTOCOL || COMPILE_TEST @@ -279,6 +286,16 @@ config RESET_SUNXI help This enables the reset driver for Allwinner SoCs. +config RESET_TH1520 + tristate "T-HEAD 1520 reset controller" + depends on ARCH_THEAD || COMPILE_TEST + select REGMAP_MMIO + help + This driver provides support for the T-HEAD TH1520 SoC reset controller, + which manages hardware reset lines for SoC components such as the GPU. + Enable this option if you need to control hardware resets on TH1520-based + systems. + config RESET_TI_SCI tristate "TI System Control Interface (TI-SCI) reset driver" depends on TI_SCI_PROTOCOL || (COMPILE_TEST && TI_SCI_PROTOCOL=n) diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 31f9904d13f9..91e6348e3351 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -31,11 +31,13 @@ obj-$(CONFIG_RESET_QCOM_AOSS) += reset-qcom-aoss.o obj-$(CONFIG_RESET_QCOM_PDC) += reset-qcom-pdc.o obj-$(CONFIG_RESET_RASPBERRYPI) += reset-raspberrypi.o obj-$(CONFIG_RESET_RZG2L_USBPHY_CTRL) += reset-rzg2l-usbphy-ctrl.o +obj-$(CONFIG_RESET_RZV2H_USB2PHY) += reset-rzv2h-usb2phy.o obj-$(CONFIG_RESET_SCMI) += reset-scmi.o obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o obj-$(CONFIG_RESET_SUNPLUS) += reset-sunplus.o obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o +obj-$(CONFIG_RESET_TH1520) += reset-th1520.o obj-$(CONFIG_RESET_TI_SCI) += reset-ti-sci.o obj-$(CONFIG_RESET_TI_SYSCON) += reset-ti-syscon.o obj-$(CONFIG_RESET_TI_TPS380X) += reset-tps380x.o diff --git a/drivers/reset/reset-rzv2h-usb2phy.c b/drivers/reset/reset-rzv2h-usb2phy.c new file mode 100644 index 000000000000..ae643575b067 --- /dev/null +++ b/drivers/reset/reset-rzv2h-usb2phy.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/V2H(P) USB2PHY Port reset control driver + * + * Copyright (C) 2025 Renesas Electronics Corporation + */ + +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> +#include <linux/reset-controller.h> + +struct rzv2h_usb2phy_regval { + u16 reg; + u16 val; +}; + +struct rzv2h_usb2phy_reset_of_data { + const struct rzv2h_usb2phy_regval *init_vals; + unsigned int init_val_count; + + u16 reset_reg; + u16 reset_assert_val; + u16 reset_deassert_val; + u16 reset_status_bits; + u16 reset_release_val; + + u16 reset2_reg; + u16 reset2_acquire_val; + u16 reset2_release_val; +}; + +struct rzv2h_usb2phy_reset_priv { + const struct rzv2h_usb2phy_reset_of_data *data; + void __iomem *base; + struct device *dev; + struct reset_controller_dev rcdev; + spinlock_t lock; /* protects register accesses */ +}; + +static inline struct rzv2h_usb2phy_reset_priv +*rzv2h_usbphy_rcdev_to_priv(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct rzv2h_usb2phy_reset_priv, rcdev); +} + +/* This function must be called only after pm_runtime_resume_and_get() has been called */ +static void rzv2h_usbphy_assert_helper(struct rzv2h_usb2phy_reset_priv *priv) +{ + const struct rzv2h_usb2phy_reset_of_data *data = priv->data; + + scoped_guard(spinlock, &priv->lock) { + writel(data->reset2_acquire_val, priv->base + data->reset2_reg); + writel(data->reset_assert_val, priv->base + data->reset_reg); + } + + usleep_range(11, 20); +} + +static int rzv2h_usbphy_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct rzv2h_usb2phy_reset_priv *priv = rzv2h_usbphy_rcdev_to_priv(rcdev); + struct device *dev = priv->dev; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "pm_runtime_resume_and_get failed\n"); + return ret; + } + + rzv2h_usbphy_assert_helper(priv); + + pm_runtime_put(dev); + + return 0; +} + +static int rzv2h_usbphy_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct rzv2h_usb2phy_reset_priv *priv = rzv2h_usbphy_rcdev_to_priv(rcdev); + const struct rzv2h_usb2phy_reset_of_data *data = priv->data; + struct device *dev = priv->dev; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "pm_runtime_resume_and_get failed\n"); + return ret; + } + + scoped_guard(spinlock, &priv->lock) { + writel(data->reset_deassert_val, priv->base + data->reset_reg); + writel(data->reset2_release_val, priv->base + data->reset2_reg); + writel(data->reset_release_val, priv->base + data->reset_reg); + } + + pm_runtime_put(dev); + + return 0; +} + +static int rzv2h_usbphy_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct rzv2h_usb2phy_reset_priv *priv = rzv2h_usbphy_rcdev_to_priv(rcdev); + struct device *dev = priv->dev; + int ret; + u32 reg; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "pm_runtime_resume_and_get failed\n"); + return ret; + } + + reg = readl(priv->base + priv->data->reset_reg); + + pm_runtime_put(dev); + + return (reg & priv->data->reset_status_bits) == priv->data->reset_status_bits; +} + +static const struct reset_control_ops rzv2h_usbphy_reset_ops = { + .assert = rzv2h_usbphy_reset_assert, + .deassert = rzv2h_usbphy_reset_deassert, + .status = rzv2h_usbphy_reset_status, +}; + +static int rzv2h_usb2phy_reset_of_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + /* No special handling needed, we have only one reset line per device */ + return 0; +} + +static int rzv2h_usb2phy_reset_probe(struct platform_device *pdev) +{ + const struct rzv2h_usb2phy_reset_of_data *data; + struct rzv2h_usb2phy_reset_priv *priv; + struct device *dev = &pdev->dev; + struct reset_control *rstc; + int error; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + data = of_device_get_match_data(dev); + priv->data = data; + priv->dev = dev; + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + rstc = devm_reset_control_get_shared_deasserted(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), + "failed to get deasserted reset\n"); + + spin_lock_init(&priv->lock); + + error = devm_pm_runtime_enable(dev); + if (error) + return dev_err_probe(dev, error, "Failed to enable pm_runtime\n"); + + error = pm_runtime_resume_and_get(dev); + if (error) + return dev_err_probe(dev, error, "pm_runtime_resume_and_get failed\n"); + + for (unsigned int i = 0; i < data->init_val_count; i++) + writel(data->init_vals[i].val, priv->base + data->init_vals[i].reg); + + /* keep usb2phy in asserted state */ + rzv2h_usbphy_assert_helper(priv); + + pm_runtime_put(dev); + + priv->rcdev.ops = &rzv2h_usbphy_reset_ops; + priv->rcdev.of_reset_n_cells = 0; + priv->rcdev.nr_resets = 1; + priv->rcdev.of_xlate = rzv2h_usb2phy_reset_of_xlate; + priv->rcdev.of_node = dev->of_node; + priv->rcdev.dev = dev; + + return devm_reset_controller_register(dev, &priv->rcdev); +} + +/* + * initialization values required to prepare the PHY to receive + * assert and deassert requests. + */ +static const struct rzv2h_usb2phy_regval rzv2h_init_vals[] = { + { .reg = 0xc10, .val = 0x67c }, + { .reg = 0xc14, .val = 0x1f }, + { .reg = 0x600, .val = 0x909 }, +}; + +static const struct rzv2h_usb2phy_reset_of_data rzv2h_reset_of_data = { + .init_vals = rzv2h_init_vals, + .init_val_count = ARRAY_SIZE(rzv2h_init_vals), + .reset_reg = 0, + .reset_assert_val = 0x206, + .reset_status_bits = BIT(2), + .reset_deassert_val = 0x200, + .reset_release_val = 0x0, + .reset2_reg = 0xb04, + .reset2_acquire_val = 0x303, + .reset2_release_val = 0x3, +}; + +static const struct of_device_id rzv2h_usb2phy_reset_of_match[] = { + { .compatible = "renesas,r9a09g057-usb2phy-reset", .data = &rzv2h_reset_of_data }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rzv2h_usb2phy_reset_of_match); + +static struct platform_driver rzv2h_usb2phy_reset_driver = { + .driver = { + .name = "rzv2h_usb2phy_reset", + .of_match_table = rzv2h_usb2phy_reset_of_match, + }, + .probe = rzv2h_usb2phy_reset_probe, +}; +module_platform_driver(rzv2h_usb2phy_reset_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); +MODULE_DESCRIPTION("Renesas RZ/V2H(P) USB2PHY Control"); diff --git a/drivers/reset/reset-th1520.c b/drivers/reset/reset-th1520.c new file mode 100644 index 000000000000..7874f0693e1b --- /dev/null +++ b/drivers/reset/reset-th1520.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Author: Michal Wilczynski <m.wilczynski@samsung.com> + */ + +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/regmap.h> + +#include <dt-bindings/reset/thead,th1520-reset.h> + + /* register offset in VOSYS_REGMAP */ +#define TH1520_GPU_RST_CFG 0x0 +#define TH1520_GPU_RST_CFG_MASK GENMASK(1, 0) + +/* register values */ +#define TH1520_GPU_SW_GPU_RST BIT(0) +#define TH1520_GPU_SW_CLKGEN_RST BIT(1) + +struct th1520_reset_priv { + struct reset_controller_dev rcdev; + struct regmap *map; +}; + +struct th1520_reset_map { + u32 bit; + u32 reg; +}; + +static const struct th1520_reset_map th1520_resets[] = { + [TH1520_RESET_ID_GPU] = { + .bit = TH1520_GPU_SW_GPU_RST, + .reg = TH1520_GPU_RST_CFG, + }, + [TH1520_RESET_ID_GPU_CLKGEN] = { + .bit = TH1520_GPU_SW_CLKGEN_RST, + .reg = TH1520_GPU_RST_CFG, + } +}; + +static inline struct th1520_reset_priv * +to_th1520_reset(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct th1520_reset_priv, rcdev); +} + +static int th1520_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct th1520_reset_priv *priv = to_th1520_reset(rcdev); + const struct th1520_reset_map *reset; + + reset = &th1520_resets[id]; + + return regmap_update_bits(priv->map, reset->reg, reset->bit, 0); +} + +static int th1520_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct th1520_reset_priv *priv = to_th1520_reset(rcdev); + const struct th1520_reset_map *reset; + + reset = &th1520_resets[id]; + + return regmap_update_bits(priv->map, reset->reg, reset->bit, + reset->bit); +} + +static const struct reset_control_ops th1520_reset_ops = { + .assert = th1520_reset_assert, + .deassert = th1520_reset_deassert, +}; + +static const struct regmap_config th1520_reset_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .fast_io = true, +}; + +static int th1520_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct th1520_reset_priv *priv; + void __iomem *base; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->map = devm_regmap_init_mmio(dev, base, + &th1520_reset_regmap_config); + if (IS_ERR(priv->map)) + return PTR_ERR(priv->map); + + /* Initialize GPU resets to asserted state */ + ret = regmap_update_bits(priv->map, TH1520_GPU_RST_CFG, + TH1520_GPU_RST_CFG_MASK, 0); + if (ret) + return ret; + + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.nr_resets = ARRAY_SIZE(th1520_resets); + priv->rcdev.ops = &th1520_reset_ops; + priv->rcdev.of_node = dev->of_node; + + return devm_reset_controller_register(dev, &priv->rcdev); +} + +static const struct of_device_id th1520_reset_match[] = { + { .compatible = "thead,th1520-reset" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, th1520_reset_match); + +static struct platform_driver th1520_reset_driver = { + .driver = { + .name = "th1520-reset", + .of_match_table = th1520_reset_match, + }, + .probe = th1520_reset_probe, +}; +module_platform_driver(th1520_reset_driver); + +MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>"); +MODULE_DESCRIPTION("T-HEAD TH1520 SoC reset controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/s390/char/vmlogrdr.c b/drivers/s390/char/vmlogrdr.c index dac85294d2f5..e284eea331d7 100644 --- a/drivers/s390/char/vmlogrdr.c +++ b/drivers/s390/char/vmlogrdr.c @@ -255,7 +255,7 @@ static int vmlogrdr_recording(struct vmlogrdr_priv_t * logptr, /* * The recording commands needs to be called with option QID - * for guests that have previlege classes A or B. + * for guests that have privilege classes A or B. * Purging has to be done as separate step, because recording * can't be switched on as long as records are on the queue. * Doing both at the same time doesn't work. @@ -557,7 +557,7 @@ static ssize_t vmlogrdr_purge_store(struct device * dev, /* * The recording command needs to be called with option QID - * for guests that have previlege classes A or B. + * for guests that have privilege classes A or B. * Other guests will not recognize the command and we have to * issue the same command without the QID parameter. */ diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index 6a8daeb8c4b9..a2d65adffb80 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -23,11 +23,13 @@ source "drivers/soc/qcom/Kconfig" source "drivers/soc/renesas/Kconfig" source "drivers/soc/rockchip/Kconfig" source "drivers/soc/samsung/Kconfig" +source "drivers/soc/sophgo/Kconfig" source "drivers/soc/sunxi/Kconfig" source "drivers/soc/tegra/Kconfig" source "drivers/soc/ti/Kconfig" source "drivers/soc/ux500/Kconfig" source "drivers/soc/versatile/Kconfig" +source "drivers/soc/vt8500/Kconfig" source "drivers/soc/xilinx/Kconfig" endmenu diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 2037a8695cb2..c9e689080ceb 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -29,9 +29,11 @@ obj-y += qcom/ obj-y += renesas/ obj-y += rockchip/ obj-$(CONFIG_SOC_SAMSUNG) += samsung/ +obj-y += sophgo/ obj-y += sunxi/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-y += ti/ obj-$(CONFIG_ARCH_U8500) += ux500/ obj-y += versatile/ +obj-y += vt8500/ obj-y += xilinx/ diff --git a/drivers/soc/amlogic/meson-clk-measure.c b/drivers/soc/amlogic/meson-clk-measure.c index a6453ffeb753..d862e30a244e 100644 --- a/drivers/soc/amlogic/meson-clk-measure.c +++ b/drivers/soc/amlogic/meson-clk-measure.c @@ -14,11 +14,6 @@ static DEFINE_MUTEX(measure_lock); -#define MSR_CLK_DUTY 0x0 -#define MSR_CLK_REG0 0x4 -#define MSR_CLK_REG1 0x8 -#define MSR_CLK_REG2 0xc - #define MSR_DURATION GENMASK(15, 0) #define MSR_ENABLE BIT(16) #define MSR_CONT BIT(17) /* continuous measurement */ @@ -33,23 +28,34 @@ static DEFINE_MUTEX(measure_lock); #define DIV_STEP 32 #define DIV_MAX 640 -#define CLK_MSR_MAX 128 - struct meson_msr_id { struct meson_msr *priv; unsigned int id; const char *name; }; +struct msr_reg_offset { + unsigned int duty_val; + unsigned int freq_ctrl; + unsigned int duty_ctrl; + unsigned int freq_val; +}; + +struct meson_msr_data { + struct meson_msr_id *msr_table; + unsigned int msr_count; + const struct msr_reg_offset *reg; +}; + struct meson_msr { struct regmap *regmap; - struct meson_msr_id msr_table[CLK_MSR_MAX]; + struct meson_msr_data data; }; #define CLK_MSR_ID(__id, __name) \ [__id] = {.id = __id, .name = __name,} -static struct meson_msr_id clk_msr_m8[CLK_MSR_MAX] = { +static const struct meson_msr_id clk_msr_m8[] = { CLK_MSR_ID(0, "ring_osc_out_ee0"), CLK_MSR_ID(1, "ring_osc_out_ee1"), CLK_MSR_ID(2, "ring_osc_out_ee2"), @@ -98,7 +104,7 @@ static struct meson_msr_id clk_msr_m8[CLK_MSR_MAX] = { CLK_MSR_ID(63, "mipi_csi_cfg"), }; -static struct meson_msr_id clk_msr_gx[CLK_MSR_MAX] = { +static const struct meson_msr_id clk_msr_gx[] = { CLK_MSR_ID(0, "ring_osc_out_ee_0"), CLK_MSR_ID(1, "ring_osc_out_ee_1"), CLK_MSR_ID(2, "ring_osc_out_ee_2"), @@ -168,7 +174,7 @@ static struct meson_msr_id clk_msr_gx[CLK_MSR_MAX] = { CLK_MSR_ID(82, "ge2d"), }; -static struct meson_msr_id clk_msr_axg[CLK_MSR_MAX] = { +static const struct meson_msr_id clk_msr_axg[] = { CLK_MSR_ID(0, "ring_osc_out_ee_0"), CLK_MSR_ID(1, "ring_osc_out_ee_1"), CLK_MSR_ID(2, "ring_osc_out_ee_2"), @@ -242,7 +248,7 @@ static struct meson_msr_id clk_msr_axg[CLK_MSR_MAX] = { CLK_MSR_ID(109, "audio_locker_in"), }; -static struct meson_msr_id clk_msr_g12a[CLK_MSR_MAX] = { +static const struct meson_msr_id clk_msr_g12a[] = { CLK_MSR_ID(0, "ring_osc_out_ee_0"), CLK_MSR_ID(1, "ring_osc_out_ee_1"), CLK_MSR_ID(2, "ring_osc_out_ee_2"), @@ -358,7 +364,7 @@ static struct meson_msr_id clk_msr_g12a[CLK_MSR_MAX] = { CLK_MSR_ID(122, "audio_pdm_dclk"), }; -static struct meson_msr_id clk_msr_sm1[CLK_MSR_MAX] = { +static const struct meson_msr_id clk_msr_sm1[] = { CLK_MSR_ID(0, "ring_osc_out_ee_0"), CLK_MSR_ID(1, "ring_osc_out_ee_1"), CLK_MSR_ID(2, "ring_osc_out_ee_2"), @@ -488,10 +494,304 @@ static struct meson_msr_id clk_msr_sm1[CLK_MSR_MAX] = { CLK_MSR_ID(127, "csi2_data"), }; +static const struct meson_msr_id clk_msr_c3[] = { + CLK_MSR_ID(0, "sys_clk"), + CLK_MSR_ID(1, "axi_clk"), + CLK_MSR_ID(2, "rtc_clk"), + CLK_MSR_ID(3, "p20_usb2_ckout"), + CLK_MSR_ID(4, "eth_mpll_test"), + CLK_MSR_ID(5, "sys_pll"), + CLK_MSR_ID(6, "cpu_clk_div16"), + CLK_MSR_ID(7, "ts_pll"), + CLK_MSR_ID(8, "fclk_div2"), + CLK_MSR_ID(9, "fclk_div2p5"), + CLK_MSR_ID(10, "fclk_div3"), + CLK_MSR_ID(11, "fclk_div4"), + CLK_MSR_ID(12, "fclk_div5"), + CLK_MSR_ID(13, "fclk_div7"), + CLK_MSR_ID(15, "fclk_50m"), + CLK_MSR_ID(16, "sys_oscin32k_i"), + CLK_MSR_ID(17, "mclk_pll"), + CLK_MSR_ID(19, "hifi_pll"), + CLK_MSR_ID(20, "gp0_pll"), + CLK_MSR_ID(21, "gp1_pll"), + CLK_MSR_ID(22, "eth_mppll_50m_ckout"), + CLK_MSR_ID(23, "sys_pll_div16"), + CLK_MSR_ID(24, "ddr_dpll_pt_clk"), + CLK_MSR_ID(26, "nna_core"), + CLK_MSR_ID(27, "rtc_sec_pulse_out"), + CLK_MSR_ID(28, "rtc_osc_clk_out"), + CLK_MSR_ID(29, "debug_in_clk"), + CLK_MSR_ID(30, "mod_eth_phy_ref_clk"), + CLK_MSR_ID(31, "mod_eth_tx_clk"), + CLK_MSR_ID(32, "eth_125m"), + CLK_MSR_ID(33, "eth_rmii"), + CLK_MSR_ID(34, "co_clkin_to_mac"), + CLK_MSR_ID(36, "co_rx_clk"), + CLK_MSR_ID(37, "co_tx_clk"), + CLK_MSR_ID(38, "eth_phy_rxclk"), + CLK_MSR_ID(39, "eth_phy_plltxclk"), + CLK_MSR_ID(40, "ephy_test_clk"), + CLK_MSR_ID(66, "vapb"), + CLK_MSR_ID(67, "ge2d"), + CLK_MSR_ID(68, "dewarpa"), + CLK_MSR_ID(70, "mipi_dsi_meas"), + CLK_MSR_ID(71, "dsi_phy"), + CLK_MSR_ID(79, "rama"), + CLK_MSR_ID(94, "vc9000e_core"), + CLK_MSR_ID(95, "vc9000e_sys"), + CLK_MSR_ID(96, "vc9000e_aclk"), + CLK_MSR_ID(97, "hcodec"), + CLK_MSR_ID(106, "deskew_pll_clk_div32_out"), + CLK_MSR_ID(107, "mipi_csi_phy_clk_out[0]"), + CLK_MSR_ID(108, "mipi_csi_phy_clk_out[1]"), + CLK_MSR_ID(110, "spifc"), + CLK_MSR_ID(111, "saradc"), + CLK_MSR_ID(112, "ts"), + CLK_MSR_ID(113, "sd_emmc_c"), + CLK_MSR_ID(114, "sd_emmc_b"), + CLK_MSR_ID(115, "sd_emmc_a"), + CLK_MSR_ID(116, "gpio_msr_clk"), + CLK_MSR_ID(117, "spicc_b"), + CLK_MSR_ID(118, "spicc_a"), + CLK_MSR_ID(122, "mod_audio_pdm_dclk_o"), + CLK_MSR_ID(124, "o_earcrx_dmac_clk"), + CLK_MSR_ID(125, "o_earcrx_cmdc_clk"), + CLK_MSR_ID(126, "o_earctx_dmac_clk"), + CLK_MSR_ID(127, "o_earctx_cmdc_clk"), + CLK_MSR_ID(128, "o_tohdmitx_bclk"), + CLK_MSR_ID(129, "o_tohdmitx_mclk"), + CLK_MSR_ID(130, "o_tohdmitx_spdif_clk"), + CLK_MSR_ID(131, "o_toacodec_bclk"), + CLK_MSR_ID(132, "o_toacodec_mclk"), + CLK_MSR_ID(133, "o_spdifout_b_mst_clk"), + CLK_MSR_ID(134, "o_spdifout_mst_clk"), + CLK_MSR_ID(135, "o_spdifin_mst_clk"), + CLK_MSR_ID(136, "o_audio_mclk"), + CLK_MSR_ID(137, "o_vad_clk"), + CLK_MSR_ID(138, "o_tdmout_d_sclk"), + CLK_MSR_ID(139, "o_tdmout_c_sclk"), + CLK_MSR_ID(140, "o_tdmout_b_sclk"), + CLK_MSR_ID(141, "o_tdmout_a_sclk"), + CLK_MSR_ID(142, "o_tdminb_1b_sclk"), + CLK_MSR_ID(143, "o_tdmin_1b_sclk"), + CLK_MSR_ID(144, "o_tdmin_d_sclk"), + CLK_MSR_ID(145, "o_tdmin_c_sclk"), + CLK_MSR_ID(146, "o_tdmin_b_sclk"), + CLK_MSR_ID(147, "o_tdmin_a_sclk"), + CLK_MSR_ID(148, "o_resampleb_clk"), + CLK_MSR_ID(149, "o_resamplea_clk"), + CLK_MSR_ID(150, "o_pdmb_sysclk"), + CLK_MSR_ID(151, "o_pdmb_dclk"), + CLK_MSR_ID(152, "o_pdm_sysclk"), + CLK_MSR_ID(153, "o_pdm_dclk"), + CLK_MSR_ID(154, "c_alockerb_out_clk"), + CLK_MSR_ID(155, "c_alockerb_in_clk"), + CLK_MSR_ID(156, "c_alocker_out_clk"), + CLK_MSR_ID(157, "c_alocker_in_clk"), + CLK_MSR_ID(158, "audio_mst_clk[34]"), + CLK_MSR_ID(159, "audio_mst_clk[35]"), + CLK_MSR_ID(160, "pwm_n"), + CLK_MSR_ID(161, "pwm_m"), + CLK_MSR_ID(162, "pwm_l"), + CLK_MSR_ID(163, "pwm_k"), + CLK_MSR_ID(164, "pwm_j"), + CLK_MSR_ID(165, "pwm_i"), + CLK_MSR_ID(166, "pwm_h"), + CLK_MSR_ID(167, "pwm_g"), + CLK_MSR_ID(168, "pwm_f"), + CLK_MSR_ID(169, "pwm_e"), + CLK_MSR_ID(170, "pwm_d"), + CLK_MSR_ID(171, "pwm_c"), + CLK_MSR_ID(172, "pwm_b"), + CLK_MSR_ID(173, "pwm_a"), + CLK_MSR_ID(174, "AU_DAC1_CLK_TO_GPIO"), + CLK_MSR_ID(175, "AU_ADC_CLK_TO_GPIO"), + CLK_MSR_ID(176, "rng_ring_osc_clk[0]"), + CLK_MSR_ID(177, "rng_ring_osc_clk[1]"), + CLK_MSR_ID(178, "rng_ring_osc_clk[2]"), + CLK_MSR_ID(179, "rng_ring_osc_clk[3]"), + CLK_MSR_ID(180, "sys_cpu_ring_osc_clk[0]"), + CLK_MSR_ID(181, "sys_cpu_ring_osc_clk[1]"), + CLK_MSR_ID(182, "sys_cpu_ring_osc_clk[2]"), + CLK_MSR_ID(183, "sys_cpu_ring_osc_clk[3]"), + CLK_MSR_ID(184, "sys_cpu_ring_osc_clk[4]"), + CLK_MSR_ID(185, "sys_cpu_ring_osc_clk[5]"), + CLK_MSR_ID(186, "sys_cpu_ring_osc_clk[6]"), + CLK_MSR_ID(187, "sys_cpu_ring_osc_clk[7]"), + CLK_MSR_ID(188, "sys_cpu_ring_osc_clk[8]"), + CLK_MSR_ID(189, "sys_cpu_ring_osc_clk[9]"), + CLK_MSR_ID(190, "sys_cpu_ring_osc_clk[10]"), + CLK_MSR_ID(191, "sys_cpu_ring_osc_clk[11]"), + CLK_MSR_ID(192, "am_ring_osc_clk_out[12](dmc)"), + CLK_MSR_ID(193, "am_ring_osc_clk_out[13](rama)"), + CLK_MSR_ID(194, "am_ring_osc_clk_out[14](nna)"), + CLK_MSR_ID(195, "am_ring_osc_clk_out[15](nna)"), + CLK_MSR_ID(200, "rng_ring_osc_clk_1[0]"), + CLK_MSR_ID(201, "rng_ring_osc_clk_1[1]"), + CLK_MSR_ID(202, "rng_ring_osc_clk_1[2]"), + CLK_MSR_ID(203, "rng_ring_osc_clk_1[3]"), + +}; + +static const struct meson_msr_id clk_msr_s4[] = { + CLK_MSR_ID(0, "sys_clk"), + CLK_MSR_ID(1, "axi_clk"), + CLK_MSR_ID(2, "rtc_clk"), + CLK_MSR_ID(5, "mali"), + CLK_MSR_ID(6, "cpu_clk_div16"), + CLK_MSR_ID(7, "ceca_clk"), + CLK_MSR_ID(8, "cecb_clk"), + CLK_MSR_ID(10, "fclk_div5"), + CLK_MSR_ID(11, "mpll0"), + CLK_MSR_ID(12, "mpll1"), + CLK_MSR_ID(13, "mpll2"), + CLK_MSR_ID(14, "mpll3"), + CLK_MSR_ID(15, "fclk_50m"), + CLK_MSR_ID(16, "pcie_clk_inp"), + CLK_MSR_ID(17, "pcie_clk_inn"), + CLK_MSR_ID(18, "mpll_clk_test_out"), + CLK_MSR_ID(19, "hifi_pll"), + CLK_MSR_ID(20, "gp0_pll"), + CLK_MSR_ID(21, "gp1_pll"), + CLK_MSR_ID(22, "eth_mppll_50m_ckout"), + CLK_MSR_ID(23, "sys_pll_div16"), + CLK_MSR_ID(24, "ddr_dpll_pt_clk"), + CLK_MSR_ID(30, "mod_eth_phy_ref_clk"), + CLK_MSR_ID(31, "mod_eth_tx_clk"), + CLK_MSR_ID(32, "eth_125m"), + CLK_MSR_ID(33, "eth_rmii"), + CLK_MSR_ID(34, "co_clkin_to_mac"), + CLK_MSR_ID(35, "mod_eth_rx_clk_rmii"), + CLK_MSR_ID(36, "co_rx_clk"), + CLK_MSR_ID(37, "co_tx_clk"), + CLK_MSR_ID(38, "eth_phy_rxclk"), + CLK_MSR_ID(39, "eth_phy_plltxclk"), + CLK_MSR_ID(40, "ephy_test_clk"), + CLK_MSR_ID(50, "vid_pll_div_clk_out"), + CLK_MSR_ID(51, "enci"), + CLK_MSR_ID(52, "encp"), + CLK_MSR_ID(53, "encl"), + CLK_MSR_ID(54, "vdac"), + CLK_MSR_ID(55, "cdac_clk_c"), + CLK_MSR_ID(56, "mod_tcon_clko"), + CLK_MSR_ID(57, "lcd_an_clk_ph2"), + CLK_MSR_ID(58, "lcd_an_clk_ph3"), + CLK_MSR_ID(59, "hdmitx_pixel"), + CLK_MSR_ID(60, "vdin_meas"), + CLK_MSR_ID(61, "vpu"), + CLK_MSR_ID(62, "vpu_clkb"), + CLK_MSR_ID(63, "vpu_clkb_tmp"), + CLK_MSR_ID(64, "vpu_clkc"), + CLK_MSR_ID(65, "vid_lock"), + CLK_MSR_ID(66, "vapb"), + CLK_MSR_ID(67, "ge2d"), + CLK_MSR_ID(68, "cts_hdcp22_esmclk"), + CLK_MSR_ID(69, "cts_hdcp22_skpclk"), + CLK_MSR_ID(76, "hdmitx_tmds"), + CLK_MSR_ID(77, "hdmitx_sys_clk"), + CLK_MSR_ID(78, "hdmitx_fe_clk"), + CLK_MSR_ID(79, "rama"), + CLK_MSR_ID(93, "vdec"), + CLK_MSR_ID(99, "hevcf"), + CLK_MSR_ID(100, "demod_core"), + CLK_MSR_ID(101, "adc_extclk_in"), + CLK_MSR_ID(102, "cts_demod_core_t2_clk"), + CLK_MSR_ID(103, "adc_dpll_intclk"), + CLK_MSR_ID(104, "adc_dpll_clk_b3"), + CLK_MSR_ID(105, "s2_adc_clk"), + CLK_MSR_ID(106, "deskew_pll_clk_div32_out"), + CLK_MSR_ID(110, "sc"), + CLK_MSR_ID(111, "sar_adc"), + CLK_MSR_ID(113, "sd_emmc_c"), + CLK_MSR_ID(114, "sd_emmc_b"), + CLK_MSR_ID(115, "sd_emmc_a"), + CLK_MSR_ID(116, "gpio_msr_clk"), + CLK_MSR_ID(118, "spicc0"), + CLK_MSR_ID(121, "ts"), + CLK_MSR_ID(130, "audio_vad_clk"), + CLK_MSR_ID(131, "acodec_dac_clk_x128"), + CLK_MSR_ID(132, "audio_locker_in_clk"), + CLK_MSR_ID(133, "audio_locker_out_clk"), + CLK_MSR_ID(134, "audio_tdmout_c_sclk"), + CLK_MSR_ID(135, "audio_tdmout_b_sclk"), + CLK_MSR_ID(136, "audio_tdmout_a_sclk"), + CLK_MSR_ID(137, "audio_tdmin_lb_sclk"), + CLK_MSR_ID(138, "audio_tdmin_c_sclk"), + CLK_MSR_ID(139, "audio_tdmin_b_sclk"), + CLK_MSR_ID(140, "audio_tdmin_a_sclk"), + CLK_MSR_ID(141, "audio_resamplea_clk"), + CLK_MSR_ID(142, "audio_pdm_sysclk"), + CLK_MSR_ID(143, "audio_spdifout_b_mst_clk"), + CLK_MSR_ID(144, "audio_spdifout_mst_clk"), + CLK_MSR_ID(145, "audio_spdifin_mst_clk"), + CLK_MSR_ID(146, "audio_pdm_dclk"), + CLK_MSR_ID(147, "audio_resampleb_clk"), + CLK_MSR_ID(160, "pwm_j"), + CLK_MSR_ID(161, "pwm_i"), + CLK_MSR_ID(162, "pwm_h"), + CLK_MSR_ID(163, "pwm_g"), + CLK_MSR_ID(164, "pwm_f"), + CLK_MSR_ID(165, "pwm_e"), + CLK_MSR_ID(166, "pwm_d"), + CLK_MSR_ID(167, "pwm_c"), + CLK_MSR_ID(168, "pwm_b"), + CLK_MSR_ID(169, "pwm_a"), + CLK_MSR_ID(176, "rng_ring_0"), + CLK_MSR_ID(177, "rng_ring_1"), + CLK_MSR_ID(178, "rng_ring_2"), + CLK_MSR_ID(179, "rng_ring_3"), + CLK_MSR_ID(180, "dmc_osc_ring(LVT16)"), + CLK_MSR_ID(181, "gpu_osc_ring0(LVT16)"), + CLK_MSR_ID(182, "gpu_osc_ring1(ULVT16)"), + CLK_MSR_ID(183, "gpu_osc_ring2(SLVT16)"), + CLK_MSR_ID(184, "vpu_osc_ring0(SVT24)"), + CLK_MSR_ID(185, "vpu_osc_ring1(LVT20)"), + CLK_MSR_ID(186, "vpu_osc_ring2(LVT16)"), + CLK_MSR_ID(187, "dos_osc_ring0(SVT24)"), + CLK_MSR_ID(188, "dos_osc_ring1(SVT16)"), + CLK_MSR_ID(189, "dos_osc_ring2(LVT16)"), + CLK_MSR_ID(190, "dos_osc_ring3(ULVT20)"), + CLK_MSR_ID(192, "axi_sram_osc_ring(SVT16)"), + CLK_MSR_ID(193, "demod_osc_ring0"), + CLK_MSR_ID(194, "demod_osc_ring1"), + CLK_MSR_ID(195, "sar_osc_ring"), + CLK_MSR_ID(196, "sys_cpu_osc_ring0"), + CLK_MSR_ID(197, "sys_cpu_osc_ring1"), + CLK_MSR_ID(198, "sys_cpu_osc_ring2"), + CLK_MSR_ID(199, "sys_cpu_osc_ring3"), + CLK_MSR_ID(200, "sys_cpu_osc_ring4"), + CLK_MSR_ID(201, "sys_cpu_osc_ring5"), + CLK_MSR_ID(202, "sys_cpu_osc_ring6"), + CLK_MSR_ID(203, "sys_cpu_osc_ring7"), + CLK_MSR_ID(204, "sys_cpu_osc_ring8"), + CLK_MSR_ID(205, "sys_cpu_osc_ring9"), + CLK_MSR_ID(206, "sys_cpu_osc_ring10"), + CLK_MSR_ID(207, "sys_cpu_osc_ring11"), + CLK_MSR_ID(208, "sys_cpu_osc_ring12"), + CLK_MSR_ID(209, "sys_cpu_osc_ring13"), + CLK_MSR_ID(210, "sys_cpu_osc_ring14"), + CLK_MSR_ID(211, "sys_cpu_osc_ring15"), + CLK_MSR_ID(212, "sys_cpu_osc_ring16"), + CLK_MSR_ID(213, "sys_cpu_osc_ring17"), + CLK_MSR_ID(214, "sys_cpu_osc_ring18"), + CLK_MSR_ID(215, "sys_cpu_osc_ring19"), + CLK_MSR_ID(216, "sys_cpu_osc_ring20"), + CLK_MSR_ID(217, "sys_cpu_osc_ring21"), + CLK_MSR_ID(218, "sys_cpu_osc_ring22"), + CLK_MSR_ID(219, "sys_cpu_osc_ring23"), + CLK_MSR_ID(220, "sys_cpu_osc_ring24"), + CLK_MSR_ID(221, "sys_cpu_osc_ring25"), + CLK_MSR_ID(222, "sys_cpu_osc_ring26"), + CLK_MSR_ID(223, "sys_cpu_osc_ring27"), + +}; + static int meson_measure_id(struct meson_msr_id *clk_msr_id, - unsigned int duration) + unsigned int duration) { struct meson_msr *priv = clk_msr_id->priv; + const struct msr_reg_offset *reg = priv->data.reg; unsigned int val; int ret; @@ -499,22 +799,22 @@ static int meson_measure_id(struct meson_msr_id *clk_msr_id, if (ret) return ret; - regmap_write(priv->regmap, MSR_CLK_REG0, 0); + regmap_write(priv->regmap, reg->freq_ctrl, 0); /* Set measurement duration */ - regmap_update_bits(priv->regmap, MSR_CLK_REG0, MSR_DURATION, + regmap_update_bits(priv->regmap, reg->freq_ctrl, MSR_DURATION, FIELD_PREP(MSR_DURATION, duration - 1)); /* Set ID */ - regmap_update_bits(priv->regmap, MSR_CLK_REG0, MSR_CLK_SRC, + regmap_update_bits(priv->regmap, reg->freq_ctrl, MSR_CLK_SRC, FIELD_PREP(MSR_CLK_SRC, clk_msr_id->id)); /* Enable & Start */ - regmap_update_bits(priv->regmap, MSR_CLK_REG0, + regmap_update_bits(priv->regmap, reg->freq_ctrl, MSR_RUN | MSR_ENABLE, MSR_RUN | MSR_ENABLE); - ret = regmap_read_poll_timeout(priv->regmap, MSR_CLK_REG0, + ret = regmap_read_poll_timeout(priv->regmap, reg->freq_ctrl, val, !(val & MSR_BUSY), 10, 10000); if (ret) { mutex_unlock(&measure_lock); @@ -522,10 +822,10 @@ static int meson_measure_id(struct meson_msr_id *clk_msr_id, } /* Disable */ - regmap_update_bits(priv->regmap, MSR_CLK_REG0, MSR_ENABLE, 0); + regmap_update_bits(priv->regmap, reg->freq_ctrl, MSR_ENABLE, 0); /* Get the value in multiple of gate time counts */ - regmap_read(priv->regmap, MSR_CLK_REG2, &val); + regmap_read(priv->regmap, reg->freq_val, &val); mutex_unlock(&measure_lock); @@ -573,13 +873,14 @@ DEFINE_SHOW_ATTRIBUTE(clk_msr); static int clk_msr_summary_show(struct seq_file *s, void *data) { struct meson_msr_id *msr_table = s->private; + unsigned int msr_count = msr_table->priv->data.msr_count; unsigned int precision = 0; int val, i; seq_puts(s, " clock rate precision\n"); seq_puts(s, "---------------------------------------------\n"); - for (i = 0 ; i < CLK_MSR_MAX ; ++i) { + for (i = 0 ; i < msr_count ; ++i) { if (!msr_table[i].name) continue; @@ -595,18 +896,18 @@ static int clk_msr_summary_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(clk_msr_summary); -static const struct regmap_config meson_clk_msr_regmap_config = { +static struct regmap_config meson_clk_msr_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .max_register = MSR_CLK_REG2, }; static int meson_msr_probe(struct platform_device *pdev) { - const struct meson_msr_id *match_data; + const struct meson_msr_data *match_data; struct meson_msr *priv; struct dentry *root, *clks; + struct resource *res; void __iomem *base; int i; @@ -621,60 +922,142 @@ static int meson_msr_probe(struct platform_device *pdev) return -ENODEV; } - memcpy(priv->msr_table, match_data, sizeof(priv->msr_table)); + priv->data.msr_table = devm_kcalloc(&pdev->dev, + match_data->msr_count, + sizeof(struct meson_msr_id), + GFP_KERNEL); + if (!priv->data.msr_table) + return -ENOMEM; - base = devm_platform_ioremap_resource(pdev, 0); + memcpy(priv->data.msr_table, match_data->msr_table, + match_data->msr_count * sizeof(struct meson_msr_id)); + priv->data.msr_count = match_data->msr_count; + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(base)) return PTR_ERR(base); + meson_clk_msr_regmap_config.max_register = resource_size(res) - 4; priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, &meson_clk_msr_regmap_config); if (IS_ERR(priv->regmap)) return PTR_ERR(priv->regmap); + priv->data.reg = devm_kzalloc(&pdev->dev, sizeof(struct msr_reg_offset), + GFP_KERNEL); + if (!priv->data.reg) + return -ENOMEM; + + memcpy((void *)priv->data.reg, match_data->reg, + sizeof(struct msr_reg_offset)); + root = debugfs_create_dir("meson-clk-msr", NULL); clks = debugfs_create_dir("clks", root); debugfs_create_file("measure_summary", 0444, root, - priv->msr_table, &clk_msr_summary_fops); + priv->data.msr_table, &clk_msr_summary_fops); - for (i = 0 ; i < CLK_MSR_MAX ; ++i) { - if (!priv->msr_table[i].name) + for (i = 0 ; i < priv->data.msr_count ; ++i) { + if (!priv->data.msr_table[i].name) continue; - priv->msr_table[i].priv = priv; + priv->data.msr_table[i].priv = priv; - debugfs_create_file(priv->msr_table[i].name, 0444, clks, - &priv->msr_table[i], &clk_msr_fops); + debugfs_create_file(priv->data.msr_table[i].name, 0444, clks, + &priv->data.msr_table[i], &clk_msr_fops); } return 0; } +static const struct msr_reg_offset msr_reg_offset = { + .duty_val = 0x0, + .freq_ctrl = 0x4, + .duty_ctrl = 0x8, + .freq_val = 0xc, +}; + +static const struct meson_msr_data clk_msr_gx_data = { + .msr_table = (void *)clk_msr_gx, + .msr_count = ARRAY_SIZE(clk_msr_gx), + .reg = &msr_reg_offset, +}; + +static const struct meson_msr_data clk_msr_m8_data = { + .msr_table = (void *)clk_msr_m8, + .msr_count = ARRAY_SIZE(clk_msr_m8), + .reg = &msr_reg_offset, +}; + +static const struct meson_msr_data clk_msr_axg_data = { + .msr_table = (void *)clk_msr_axg, + .msr_count = ARRAY_SIZE(clk_msr_axg), + .reg = &msr_reg_offset, +}; + +static const struct meson_msr_data clk_msr_g12a_data = { + .msr_table = (void *)clk_msr_g12a, + .msr_count = ARRAY_SIZE(clk_msr_g12a), + .reg = &msr_reg_offset, +}; + +static const struct meson_msr_data clk_msr_sm1_data = { + .msr_table = (void *)clk_msr_sm1, + .msr_count = ARRAY_SIZE(clk_msr_sm1), + .reg = &msr_reg_offset, +}; + +static const struct msr_reg_offset msr_reg_offset_v2 = { + .freq_ctrl = 0x0, + .duty_ctrl = 0x4, + .freq_val = 0x8, + .duty_val = 0x18, +}; + +static const struct meson_msr_data clk_msr_c3_data = { + .msr_table = (void *)clk_msr_c3, + .msr_count = ARRAY_SIZE(clk_msr_c3), + .reg = &msr_reg_offset_v2, +}; + +static const struct meson_msr_data clk_msr_s4_data = { + .msr_table = (void *)clk_msr_s4, + .msr_count = ARRAY_SIZE(clk_msr_s4), + .reg = &msr_reg_offset_v2, +}; + static const struct of_device_id meson_msr_match_table[] = { { .compatible = "amlogic,meson-gx-clk-measure", - .data = (void *)clk_msr_gx, + .data = &clk_msr_gx_data, }, { .compatible = "amlogic,meson8-clk-measure", - .data = (void *)clk_msr_m8, + .data = &clk_msr_m8_data, }, { .compatible = "amlogic,meson8b-clk-measure", - .data = (void *)clk_msr_m8, + .data = &clk_msr_m8_data, }, { .compatible = "amlogic,meson-axg-clk-measure", - .data = (void *)clk_msr_axg, + .data = &clk_msr_axg_data, }, { .compatible = "amlogic,meson-g12a-clk-measure", - .data = (void *)clk_msr_g12a, + .data = &clk_msr_g12a_data, }, { .compatible = "amlogic,meson-sm1-clk-measure", - .data = (void *)clk_msr_sm1, + .data = &clk_msr_sm1_data, + }, + { + .compatible = "amlogic,c3-clk-measure", + .data = &clk_msr_c3_data, + }, + { + .compatible = "amlogic,s4-clk-measure", + .data = &clk_msr_s4_data, }, { /* sentinel */ } }; diff --git a/drivers/soc/aspeed/aspeed-lpc-snoop.c b/drivers/soc/aspeed/aspeed-lpc-snoop.c index 9ab5ba9cf1d6..ef8f355589a5 100644 --- a/drivers/soc/aspeed/aspeed-lpc-snoop.c +++ b/drivers/soc/aspeed/aspeed-lpc-snoop.c @@ -166,7 +166,7 @@ static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop, int rc; lpc_snoop->irq = platform_get_irq(pdev, 0); - if (!lpc_snoop->irq) + if (lpc_snoop->irq < 0) return -ENODEV; rc = devm_request_irq(dev, lpc_snoop->irq, @@ -200,11 +200,15 @@ static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR; lpc_snoop->chan[channel].miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); + if (!lpc_snoop->chan[channel].miscdev.name) { + rc = -ENOMEM; + goto err_free_fifo; + } lpc_snoop->chan[channel].miscdev.fops = &snoop_fops; lpc_snoop->chan[channel].miscdev.parent = dev; rc = misc_register(&lpc_snoop->chan[channel].miscdev); if (rc) - return rc; + goto err_free_fifo; /* Enable LPC snoop channel at requested port */ switch (channel) { @@ -221,7 +225,8 @@ static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, hicrb_en = HICRB_ENSNP1D; break; default: - return -EINVAL; + rc = -EINVAL; + goto err_misc_deregister; } regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en); @@ -231,6 +236,12 @@ static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, regmap_update_bits(lpc_snoop->regmap, HICRB, hicrb_en, hicrb_en); + return 0; + +err_misc_deregister: + misc_deregister(&lpc_snoop->chan[channel].miscdev); +err_free_fifo: + kfifo_free(&lpc_snoop->chan[channel].fifo); return rc; } diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig index a1e0bc8c1757..47870e29c290 100644 --- a/drivers/soc/fsl/Kconfig +++ b/drivers/soc/fsl/Kconfig @@ -36,7 +36,7 @@ config FSL_MC_DPIO config DPAA2_CONSOLE tristate "QorIQ DPAA2 console driver" depends on OF && (ARCH_LAYERSCAPE || COMPILE_TEST) - default y + default ARCH_LAYERSCAPE help Console driver for DPAA2 platforms. Exports 2 char devices, /dev/dpaa2_mc_console and /dev/dpaa2_aiop_console, diff --git a/drivers/soc/fsl/qbman/qman.c b/drivers/soc/fsl/qbman/qman.c index 4dc8aba33d9b..9be240999f87 100644 --- a/drivers/soc/fsl/qbman/qman.c +++ b/drivers/soc/fsl/qbman/qman.c @@ -1270,7 +1270,7 @@ static int qman_create_portal(struct qman_portal *portal, qm_dqrr_set_ithresh(p, QMAN_PIRQ_DQRR_ITHRESH); qm_mr_set_ithresh(p, QMAN_PIRQ_MR_ITHRESH); qm_out(p, QM_REG_ITPR, QMAN_PIRQ_IPERIOD); - portal->cgrs = kmalloc_array(2, sizeof(*cgrs), GFP_KERNEL); + portal->cgrs = kmalloc_array(2, sizeof(*portal->cgrs), GFP_KERNEL); if (!portal->cgrs) goto fail_cgrs; /* initial snapshot is no-depletion */ diff --git a/drivers/soc/fsl/qe/qe_ic.c b/drivers/soc/fsl/qe/qe_ic.c index e4b6ff2cc76b..4068b501a3a3 100644 --- a/drivers/soc/fsl/qe/qe_ic.c +++ b/drivers/soc/fsl/qe/qe_ic.c @@ -232,11 +232,6 @@ static inline void qe_ic_write(__be32 __iomem *base, unsigned int reg, iowrite32be(value, base + (reg >> 2)); } -static inline struct qe_ic *qe_ic_from_irq(unsigned int virq) -{ - return irq_get_chip_data(virq); -} - static inline struct qe_ic *qe_ic_from_irq_data(struct irq_data *d) { return irq_data_get_irq_chip_data(d); @@ -455,13 +450,11 @@ static int qe_ic_init(struct platform_device *pdev) qe_ic_write(qe_ic->regs, QEIC_CICR, 0); - irq_set_handler_data(qe_ic->virq_low, qe_ic); - irq_set_chained_handler(qe_ic->virq_low, low_handler); + irq_set_chained_handler_and_data(qe_ic->virq_low, low_handler, qe_ic); - if (high_handler) { - irq_set_handler_data(qe_ic->virq_high, qe_ic); - irq_set_chained_handler(qe_ic->virq_high, high_handler); - } + if (high_handler) + irq_set_chained_handler_and_data(qe_ic->virq_high, + high_handler, qe_ic); return 0; } static const struct of_device_id qe_ic_ids[] = { diff --git a/drivers/soc/hisilicon/kunpeng_hccs.c b/drivers/soc/hisilicon/kunpeng_hccs.c index 444a8f59b7da..7fc353732d55 100644 --- a/drivers/soc/hisilicon/kunpeng_hccs.c +++ b/drivers/soc/hisilicon/kunpeng_hccs.c @@ -167,10 +167,6 @@ static void hccs_pcc_rx_callback(struct mbox_client *cl, void *mssg) static void hccs_unregister_pcc_channel(struct hccs_dev *hdev) { - struct hccs_mbox_client_info *cl_info = &hdev->cl_info; - - if (cl_info->pcc_comm_addr) - iounmap(cl_info->pcc_comm_addr); pcc_mbox_free_channel(hdev->cl_info.pcc_chan); } @@ -179,6 +175,7 @@ static int hccs_register_pcc_channel(struct hccs_dev *hdev) struct hccs_mbox_client_info *cl_info = &hdev->cl_info; struct mbox_client *cl = &cl_info->client; struct pcc_mbox_chan *pcc_chan; + struct mbox_chan *mbox_chan; struct device *dev = hdev->dev; int rc; @@ -196,7 +193,7 @@ static int hccs_register_pcc_channel(struct hccs_dev *hdev) goto out; } cl_info->pcc_chan = pcc_chan; - cl_info->mbox_chan = pcc_chan->mchan; + mbox_chan = pcc_chan->mchan; /* * pcc_chan->latency is just a nominal value. In reality the remote @@ -206,34 +203,24 @@ static int hccs_register_pcc_channel(struct hccs_dev *hdev) cl_info->deadline_us = HCCS_PCC_CMD_WAIT_RETRIES_NUM * pcc_chan->latency; if (!hdev->verspec_data->has_txdone_irq && - cl_info->mbox_chan->mbox->txdone_irq) { + mbox_chan->mbox->txdone_irq) { dev_err(dev, "PCC IRQ in PCCT is enabled.\n"); rc = -EINVAL; goto err_mbx_channel_free; } else if (hdev->verspec_data->has_txdone_irq && - !cl_info->mbox_chan->mbox->txdone_irq) { + !mbox_chan->mbox->txdone_irq) { dev_err(dev, "PCC IRQ in PCCT isn't supported.\n"); rc = -EINVAL; goto err_mbx_channel_free; } - if (!pcc_chan->shmem_base_addr || - pcc_chan->shmem_size != HCCS_PCC_SHARE_MEM_BYTES) { - dev_err(dev, "The base address or size (%llu) of PCC communication region is invalid.\n", - pcc_chan->shmem_size); + if (pcc_chan->shmem_size != HCCS_PCC_SHARE_MEM_BYTES) { + dev_err(dev, "Base size (%llu) of PCC communication region must be %d bytes.\n", + pcc_chan->shmem_size, HCCS_PCC_SHARE_MEM_BYTES); rc = -EINVAL; goto err_mbx_channel_free; } - cl_info->pcc_comm_addr = ioremap(pcc_chan->shmem_base_addr, - pcc_chan->shmem_size); - if (!cl_info->pcc_comm_addr) { - dev_err(dev, "Failed to ioremap PCC communication region for channel-%u.\n", - hdev->chan_id); - rc = -ENOMEM; - goto err_mbx_channel_free; - } - return 0; err_mbx_channel_free: @@ -246,7 +233,7 @@ static int hccs_wait_cmd_complete_by_poll(struct hccs_dev *hdev) { struct hccs_mbox_client_info *cl_info = &hdev->cl_info; struct acpi_pcct_shared_memory __iomem *comm_base = - cl_info->pcc_comm_addr; + cl_info->pcc_chan->shmem; u16 status; int ret; @@ -289,7 +276,7 @@ static inline void hccs_fill_pcc_shared_mem_region(struct hccs_dev *hdev, .status = 0, }; - memcpy_toio(hdev->cl_info.pcc_comm_addr, (void *)&tmp, + memcpy_toio(hdev->cl_info.pcc_chan->shmem, (void *)&tmp, sizeof(struct acpi_pcct_shared_memory)); /* Copy the message to the PCC comm space */ @@ -309,7 +296,7 @@ static inline void hccs_fill_ext_pcc_shared_mem_region(struct hccs_dev *hdev, .command = cmd, }; - memcpy_toio(hdev->cl_info.pcc_comm_addr, (void *)&tmp, + memcpy_toio(hdev->cl_info.pcc_chan->shmem, (void *)&tmp, sizeof(struct acpi_pcct_ext_pcc_shared_memory)); /* Copy the message to the PCC comm space */ @@ -321,12 +308,13 @@ static int hccs_pcc_cmd_send(struct hccs_dev *hdev, u8 cmd, { const struct hccs_verspecific_data *verspec_data = hdev->verspec_data; struct hccs_mbox_client_info *cl_info = &hdev->cl_info; + struct mbox_chan *mbox_chan = cl_info->pcc_chan->mchan; struct hccs_fw_inner_head *fw_inner_head; void __iomem *comm_space; u16 space_size; int ret; - comm_space = cl_info->pcc_comm_addr + verspec_data->shared_mem_size; + comm_space = cl_info->pcc_chan->shmem + verspec_data->shared_mem_size; space_size = HCCS_PCC_SHARE_MEM_BYTES - verspec_data->shared_mem_size; verspec_data->fill_pcc_shared_mem(hdev, cmd, desc, comm_space, space_size); @@ -334,7 +322,7 @@ static int hccs_pcc_cmd_send(struct hccs_dev *hdev, u8 cmd, reinit_completion(&cl_info->done); /* Ring doorbell */ - ret = mbox_send_message(cl_info->mbox_chan, &cmd); + ret = mbox_send_message(mbox_chan, &cmd); if (ret < 0) { dev_err(hdev->dev, "Send PCC mbox message failed, ret = %d.\n", ret); @@ -356,9 +344,9 @@ static int hccs_pcc_cmd_send(struct hccs_dev *hdev, u8 cmd, end: if (verspec_data->has_txdone_irq) - mbox_chan_txdone(cl_info->mbox_chan, ret); + mbox_chan_txdone(mbox_chan, ret); else - mbox_client_txdone(cl_info->mbox_chan, ret); + mbox_client_txdone(mbox_chan, ret); return ret; } diff --git a/drivers/soc/hisilicon/kunpeng_hccs.h b/drivers/soc/hisilicon/kunpeng_hccs.h index dc267136919b..f0a9a5618d97 100644 --- a/drivers/soc/hisilicon/kunpeng_hccs.h +++ b/drivers/soc/hisilicon/kunpeng_hccs.h @@ -60,10 +60,8 @@ struct hccs_chip_info { struct hccs_mbox_client_info { struct mbox_client client; - struct mbox_chan *mbox_chan; struct pcc_mbox_chan *pcc_chan; u64 deadline_us; - void __iomem *pcc_comm_addr; struct completion done; }; diff --git a/drivers/soc/imx/soc-imx8m.c b/drivers/soc/imx/soc-imx8m.c index 3ed8161d7d28..04a1b60f2f2b 100644 --- a/drivers/soc/imx/soc-imx8m.c +++ b/drivers/soc/imx/soc-imx8m.c @@ -24,13 +24,21 @@ #define OCOTP_UID_HIGH 0x420 #define IMX8MP_OCOTP_UID_OFFSET 0x10 +#define IMX8MP_OCOTP_UID_HIGH 0xE00 /* Same as ANADIG_DIGPROG_IMX7D */ #define ANADIG_DIGPROG_IMX8MM 0x800 struct imx8_soc_data { char *name; - int (*soc_revision)(u32 *socrev, u64 *socuid); + const char *ocotp_compatible; + int (*soc_revision)(struct platform_device *pdev, u32 *socrev); + int (*soc_uid)(struct platform_device *pdev, u64 *socuid); +}; + +struct imx8_soc_drvdata { + void __iomem *ocotp_base; + struct clk *clk; }; #ifdef CONFIG_HAVE_ARM_SMCCC @@ -49,30 +57,24 @@ static u32 imx8mq_soc_revision_from_atf(void) static inline u32 imx8mq_soc_revision_from_atf(void) { return 0; }; #endif -static int imx8mq_soc_revision(u32 *socrev, u64 *socuid) +static int imx8m_soc_uid(struct platform_device *pdev, u64 *socuid) { - struct device_node *np __free(device_node) = - of_find_compatible_node(NULL, NULL, "fsl,imx8mq-ocotp"); - void __iomem *ocotp_base; - u32 magic; - u32 rev; - struct clk *clk; - int ret; + struct imx8_soc_drvdata *drvdata = platform_get_drvdata(pdev); + void __iomem *ocotp_base = drvdata->ocotp_base; - if (!np) - return -EINVAL; - - ocotp_base = of_iomap(np, 0); - if (!ocotp_base) - return -EINVAL; + *socuid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH); + *socuid <<= 32; + *socuid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW); - clk = of_clk_get_by_name(np, NULL); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - goto err_clk; - } + return 0; +} - clk_prepare_enable(clk); +static int imx8mq_soc_revision(struct platform_device *pdev, u32 *socrev) +{ + struct imx8_soc_drvdata *drvdata = platform_get_drvdata(pdev); + void __iomem *ocotp_base = drvdata->ocotp_base; + u32 magic; + u32 rev; /* * SOC revision on older imx8mq is not available in fuses so query @@ -85,98 +87,109 @@ static int imx8mq_soc_revision(u32 *socrev, u64 *socuid) rev = REV_B1; } - *socuid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH); - *socuid <<= 32; - *socuid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW); - *socrev = rev; - clk_disable_unprepare(clk); - clk_put(clk); - iounmap(ocotp_base); - return 0; +} -err_clk: - iounmap(ocotp_base); - return ret; +static int imx8mp_soc_uid(struct platform_device *pdev, u64 *socuid) +{ + struct imx8_soc_drvdata *drvdata = platform_get_drvdata(pdev); + void __iomem *ocotp_base = drvdata->ocotp_base; + + socuid[0] = readl_relaxed(ocotp_base + OCOTP_UID_HIGH + IMX8MP_OCOTP_UID_OFFSET); + socuid[0] <<= 32; + socuid[0] |= readl_relaxed(ocotp_base + OCOTP_UID_LOW + IMX8MP_OCOTP_UID_OFFSET); + + socuid[1] = readl_relaxed(ocotp_base + IMX8MP_OCOTP_UID_HIGH + 0x10); + socuid[1] <<= 32; + socuid[1] |= readl_relaxed(ocotp_base + IMX8MP_OCOTP_UID_HIGH); + + return 0; } -static int imx8mm_soc_uid(u64 *socuid) +static int imx8mm_soc_revision(struct platform_device *pdev, u32 *socrev) { struct device_node *np __free(device_node) = - of_find_compatible_node(NULL, NULL, "fsl,imx8mm-ocotp"); - void __iomem *ocotp_base; - struct clk *clk; - int ret = 0; - u32 offset = of_machine_is_compatible("fsl,imx8mp") ? - IMX8MP_OCOTP_UID_OFFSET : 0; + of_find_compatible_node(NULL, NULL, "fsl,imx8mm-anatop"); + void __iomem *anatop_base; if (!np) return -EINVAL; - ocotp_base = of_iomap(np, 0); - if (!ocotp_base) + anatop_base = of_iomap(np, 0); + if (!anatop_base) return -EINVAL; - clk = of_clk_get_by_name(np, NULL); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - goto err_clk; - } - - clk_prepare_enable(clk); - - *socuid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH + offset); - *socuid <<= 32; - *socuid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW + offset); + *socrev = readl_relaxed(anatop_base + ANADIG_DIGPROG_IMX8MM); - clk_disable_unprepare(clk); - clk_put(clk); + iounmap(anatop_base); -err_clk: - iounmap(ocotp_base); - return ret; + return 0; } -static int imx8mm_soc_revision(u32 *socrev, u64 *socuid) +static int imx8m_soc_prepare(struct platform_device *pdev, const char *ocotp_compatible) { struct device_node *np __free(device_node) = - of_find_compatible_node(NULL, NULL, "fsl,imx8mm-anatop"); - void __iomem *anatop_base; + of_find_compatible_node(NULL, NULL, ocotp_compatible); + struct imx8_soc_drvdata *drvdata = platform_get_drvdata(pdev); + int ret = 0; if (!np) return -EINVAL; - anatop_base = of_iomap(np, 0); - if (!anatop_base) + drvdata->ocotp_base = of_iomap(np, 0); + if (!drvdata->ocotp_base) return -EINVAL; - *socrev = readl_relaxed(anatop_base + ANADIG_DIGPROG_IMX8MM); + drvdata->clk = of_clk_get_by_name(np, NULL); + if (IS_ERR(drvdata->clk)) { + ret = PTR_ERR(drvdata->clk); + goto err_clk; + } - iounmap(anatop_base); + return clk_prepare_enable(drvdata->clk); + +err_clk: + iounmap(drvdata->ocotp_base); + return ret; +} + +static void imx8m_soc_unprepare(struct platform_device *pdev) +{ + struct imx8_soc_drvdata *drvdata = platform_get_drvdata(pdev); - return imx8mm_soc_uid(socuid); + clk_disable_unprepare(drvdata->clk); + clk_put(drvdata->clk); + iounmap(drvdata->ocotp_base); } static const struct imx8_soc_data imx8mq_soc_data = { .name = "i.MX8MQ", + .ocotp_compatible = "fsl,imx8mq-ocotp", .soc_revision = imx8mq_soc_revision, + .soc_uid = imx8m_soc_uid, }; static const struct imx8_soc_data imx8mm_soc_data = { .name = "i.MX8MM", + .ocotp_compatible = "fsl,imx8mm-ocotp", .soc_revision = imx8mm_soc_revision, + .soc_uid = imx8m_soc_uid, }; static const struct imx8_soc_data imx8mn_soc_data = { .name = "i.MX8MN", + .ocotp_compatible = "fsl,imx8mm-ocotp", .soc_revision = imx8mm_soc_revision, + .soc_uid = imx8m_soc_uid, }; static const struct imx8_soc_data imx8mp_soc_data = { .name = "i.MX8MP", + .ocotp_compatible = "fsl,imx8mm-ocotp", .soc_revision = imx8mm_soc_revision, + .soc_uid = imx8mp_soc_uid, }; static __maybe_unused const struct of_device_id imx8_soc_match[] = { @@ -207,17 +220,24 @@ static int imx8m_soc_probe(struct platform_device *pdev) struct soc_device_attribute *soc_dev_attr; struct platform_device *cpufreq_dev; const struct imx8_soc_data *data; + struct imx8_soc_drvdata *drvdata; struct device *dev = &pdev->dev; const struct of_device_id *id; struct soc_device *soc_dev; u32 soc_rev = 0; - u64 soc_uid = 0; + u64 soc_uid[2] = {0, 0}; int ret; soc_dev_attr = devm_kzalloc(dev, sizeof(*soc_dev_attr), GFP_KERNEL); if (!soc_dev_attr) return -ENOMEM; + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + platform_set_drvdata(pdev, drvdata); + soc_dev_attr->family = "Freescale i.MX"; ret = of_property_read_string(of_root, "model", &soc_dev_attr->machine); @@ -231,18 +251,37 @@ static int imx8m_soc_probe(struct platform_device *pdev) data = id->data; if (data) { soc_dev_attr->soc_id = data->name; + ret = imx8m_soc_prepare(pdev, data->ocotp_compatible); + if (ret) + return ret; + if (data->soc_revision) { - ret = data->soc_revision(&soc_rev, &soc_uid); - if (ret) + ret = data->soc_revision(pdev, &soc_rev); + if (ret) { + imx8m_soc_unprepare(pdev); + return ret; + } + } + if (data->soc_uid) { + ret = data->soc_uid(pdev, soc_uid); + if (ret) { + imx8m_soc_unprepare(pdev); return ret; + } } + imx8m_soc_unprepare(pdev); } soc_dev_attr->revision = imx8_revision(dev, soc_rev); if (!soc_dev_attr->revision) return -ENOMEM; - soc_dev_attr->serial_number = devm_kasprintf(dev, GFP_KERNEL, "%016llX", soc_uid); + if (soc_uid[1]) + soc_dev_attr->serial_number = devm_kasprintf(dev, GFP_KERNEL, "%016llX%016llX", + soc_uid[1], soc_uid[0]); + else + soc_dev_attr->serial_number = devm_kasprintf(dev, GFP_KERNEL, "%016llX", + soc_uid[0]); if (!soc_dev_attr->serial_number) return -ENOMEM; diff --git a/drivers/soc/mediatek/mtk-dvfsrc.c b/drivers/soc/mediatek/mtk-dvfsrc.c index 83bf46fdcf2d..41add5636b03 100644 --- a/drivers/soc/mediatek/mtk-dvfsrc.c +++ b/drivers/soc/mediatek/mtk-dvfsrc.c @@ -446,6 +446,46 @@ static int mtk_dvfsrc_probe(struct platform_device *pdev) return 0; } +static const struct dvfsrc_bw_constraints dvfsrc_bw_constr_v1 = { 0, 0, 0 }; +static const struct dvfsrc_bw_constraints dvfsrc_bw_constr_v2 = { + .max_dram_nom_bw = 255, + .max_dram_peak_bw = 255, + .max_dram_hrt_bw = 1023, +}; + +static const struct dvfsrc_opp dvfsrc_opp_mt6893_lp4[] = { + { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, + { 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1 }, + { 0, 2 }, { 1, 2 }, { 2, 2 }, { 3, 2 }, + { 0, 3 }, { 1, 3 }, { 2, 3 }, { 3, 3 }, + { 1, 4 }, { 2, 4 }, { 3, 4 }, { 2, 5 }, + { 3, 5 }, { 3, 6 }, { 4, 6 }, { 4, 7 }, +}; + +static const struct dvfsrc_opp_desc dvfsrc_opp_mt6893_desc[] = { + [0] = { + .opps = dvfsrc_opp_mt6893_lp4, + .num_opp = ARRAY_SIZE(dvfsrc_opp_mt6893_lp4), + } +}; + +static const struct dvfsrc_soc_data mt6893_data = { + .opps_desc = dvfsrc_opp_mt6893_desc, + .regs = dvfsrc_mt8195_regs, + .get_target_level = dvfsrc_get_target_level_v2, + .get_current_level = dvfsrc_get_current_level_v2, + .get_vcore_level = dvfsrc_get_vcore_level_v2, + .get_vscp_level = dvfsrc_get_vscp_level_v2, + .set_dram_bw = dvfsrc_set_dram_bw_v1, + .set_dram_peak_bw = dvfsrc_set_dram_peak_bw_v1, + .set_dram_hrt_bw = dvfsrc_set_dram_hrt_bw_v1, + .set_vcore_level = dvfsrc_set_vcore_level_v2, + .set_vscp_level = dvfsrc_set_vscp_level_v2, + .wait_for_opp_level = dvfsrc_wait_for_opp_level_v2, + .wait_for_vcore_level = dvfsrc_wait_for_vcore_level_v1, + .bw_constraints = &dvfsrc_bw_constr_v2, +}; + static const struct dvfsrc_opp dvfsrc_opp_mt8183_lp4[] = { { 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 2 }, }; @@ -469,8 +509,6 @@ static const struct dvfsrc_opp_desc dvfsrc_opp_mt8183_desc[] = { } }; -static const struct dvfsrc_bw_constraints dvfsrc_bw_constr_mt8183 = { 0, 0, 0 }; - static const struct dvfsrc_soc_data mt8183_data = { .opps_desc = dvfsrc_opp_mt8183_desc, .regs = dvfsrc_mt8183_regs, @@ -482,7 +520,7 @@ static const struct dvfsrc_soc_data mt8183_data = { .set_vcore_level = dvfsrc_set_vcore_level_v1, .wait_for_opp_level = dvfsrc_wait_for_opp_level_v1, .wait_for_vcore_level = dvfsrc_wait_for_vcore_level_v1, - .bw_constraints = &dvfsrc_bw_constr_mt8183, + .bw_constraints = &dvfsrc_bw_constr_v1, }; static const struct dvfsrc_opp dvfsrc_opp_mt8195_lp4[] = { @@ -501,12 +539,6 @@ static const struct dvfsrc_opp_desc dvfsrc_opp_mt8195_desc[] = { } }; -static const struct dvfsrc_bw_constraints dvfsrc_bw_constr_mt8195 = { - .max_dram_nom_bw = 255, - .max_dram_peak_bw = 255, - .max_dram_hrt_bw = 1023, -}; - static const struct dvfsrc_soc_data mt8195_data = { .opps_desc = dvfsrc_opp_mt8195_desc, .regs = dvfsrc_mt8195_regs, @@ -521,10 +553,11 @@ static const struct dvfsrc_soc_data mt8195_data = { .set_vscp_level = dvfsrc_set_vscp_level_v2, .wait_for_opp_level = dvfsrc_wait_for_opp_level_v2, .wait_for_vcore_level = dvfsrc_wait_for_vcore_level_v1, - .bw_constraints = &dvfsrc_bw_constr_mt8195, + .bw_constraints = &dvfsrc_bw_constr_v2, }; static const struct of_device_id mtk_dvfsrc_of_match[] = { + { .compatible = "mediatek,mt6893-dvfsrc", .data = &mt6893_data }, { .compatible = "mediatek,mt8183-dvfsrc", .data = &mt8183_data }, { .compatible = "mediatek,mt8195-dvfsrc", .data = &mt8195_data }, { /* sentinel */ } diff --git a/drivers/soc/qcom/llcc-qcom.c b/drivers/soc/qcom/llcc-qcom.c index 56823b6a2fac..192edc3f64dc 100644 --- a/drivers/soc/qcom/llcc-qcom.c +++ b/drivers/soc/qcom/llcc-qcom.c @@ -35,6 +35,11 @@ #define ATTR0_RES_WAYS_MASK GENMASK(15, 0) #define ATTR0_BONUS_WAYS_MASK GENMASK(31, 16) #define ATTR0_BONUS_WAYS_SHIFT 16 +#define ATTR2_PROBE_TARGET_WAYS_MASK BIT(4) +#define ATTR2_FIXED_SIZE_MASK BIT(8) +#define ATTR2_PRIORITY_MASK GENMASK(14, 12) +#define ATTR2_PARENT_SCID_MASK GENMASK(21, 16) +#define ATTR2_IN_A_GROUP_MASK BIT(24) #define LLCC_STATUS_READ_DELAY 100 #define CACHE_LINE_SIZE_SHIFT 6 @@ -49,6 +54,10 @@ #define LLCC_TRP_ATTR0_CFGn(n) (0x21000 + SZ_8 * n) #define LLCC_TRP_ATTR1_CFGn(n) (0x21004 + SZ_8 * n) #define LLCC_TRP_ATTR2_CFGn(n) (0x21100 + SZ_4 * n) +#define LLCC_V6_TRP_ATTR0_CFGn(n) (cfg->reg_offset[LLCC_TRP_ATTR0_CFG] + SZ_64 * (n)) +#define LLCC_V6_TRP_ATTR1_CFGn(n) (cfg->reg_offset[LLCC_TRP_ATTR1_CFG] + SZ_64 * (n)) +#define LLCC_V6_TRP_ATTR2_CFGn(n) (cfg->reg_offset[LLCC_TRP_ATTR2_CFG] + SZ_64 * (n)) +#define LLCC_V6_TRP_ATTR3_CFGn(n) (cfg->reg_offset[LLCC_TRP_ATTR3_CFG] + SZ_64 * (n)) #define LLCC_TRP_SCID_DIS_CAP_ALLOC 0x21f00 #define LLCC_TRP_PCB_ACT 0x21f04 @@ -66,6 +75,7 @@ #define LLCC_VERSION_2_0_0_0 0x02000000 #define LLCC_VERSION_2_1_0_0 0x02010000 #define LLCC_VERSION_4_1_0_0 0x04010000 +#define LLCC_VERSION_6_0_0_0 0X06000000 /** * struct llcc_slice_config - Data associated with the llcc slice @@ -106,6 +116,7 @@ * ovcap_en. * @vict_prio: When current scid is under-capacity, allocate over other * lower-than victim priority-line threshold scid. + * @parent_slice_id: For grouped slices, specifies the slice id of the parent. */ struct llcc_slice_config { u32 usecase_id; @@ -130,6 +141,7 @@ struct llcc_slice_config { bool ovcap_en; bool ovcap_prio; bool vict_prio; + u32 parent_slice_id; }; struct qcom_llcc_config { @@ -153,6 +165,21 @@ struct qcom_sct_config { enum llcc_reg_offset { LLCC_COMMON_HW_INFO, LLCC_COMMON_STATUS0, + LLCC_TRP_ATTR0_CFG, + LLCC_TRP_ATTR1_CFG, + LLCC_TRP_ATTR2_CFG, + LLCC_TRP_ATTR3_CFG, + LLCC_TRP_SID_DIS_CAP_ALLOC, + LLCC_TRP_ALGO_STALE_EN, + LLCC_TRP_ALGO_STALE_CAP_EN, + LLCC_TRP_ALGO_MRU0, + LLCC_TRP_ALGO_MRU1, + LLCC_TRP_ALGO_ALLOC0, + LLCC_TRP_ALGO_ALLOC1, + LLCC_TRP_ALGO_ALLOC2, + LLCC_TRP_ALGO_ALLOC3, + LLCC_TRP_WRS_EN, + LLCC_TRP_WRS_CACHEABLE_EN, }; static const struct llcc_slice_config ipq5424_data[] = { @@ -2662,6 +2689,263 @@ static const struct llcc_slice_config sm8650_data[] = { }, }; +static const struct llcc_slice_config sm8750_data[] = { + { + .usecase_id = LLCC_CPUSS, + .slice_id = 1, + .max_cap = 5120, + .priority = 1, + .bonus_ways = 0xffffffff, + .activate_on_init = true, + .write_scid_en = true, + }, { + .usecase_id = LLCC_MDMHPFX, + .slice_id = 24, + .max_cap = 1024, + .priority = 5, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_VIDSC0, + .slice_id = 2, + .max_cap = 512, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_AUDIO, + .slice_id = 35, + .max_cap = 512, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_MDMHPGRW, + .slice_id = 25, + .max_cap = 1024, + .priority = 5, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_MODHW, + .slice_id = 26, + .max_cap = 1024, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_CMPT, + .slice_id = 34, + .max_cap = 4096, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_GPUHTW, + .slice_id = 11, + .max_cap = 512, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_GPU, + .slice_id = 9, + .max_cap = 5632, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .write_scid_en = true, + .write_scid_cacheable_en = true + }, { + .usecase_id = LLCC_MMUHWT, + .slice_id = 18, + .max_cap = 768, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .activate_on_init = true, + }, { + .usecase_id = LLCC_DISP, + .slice_id = 16, + .max_cap = 7168, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .cache_mode = 2, + .stale_en = true, + }, { + .usecase_id = LLCC_VIDFW, + .slice_id = 17, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_CAMFW, + .slice_id = 20, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_MDMPNG, + .slice_id = 27, + .max_cap = 256, + .priority = 5, + .fixed_size = true, + .bonus_ways = 0xf0000000, + }, { + .usecase_id = LLCC_AUDHW, + .slice_id = 22, + .max_cap = 512, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_CVP, + .slice_id = 8, + .max_cap = 800, + .priority = 5, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .vict_prio = true, + }, { + .usecase_id = LLCC_MODPE, + .slice_id = 29, + .max_cap = 256, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xf0000000, + .alloc_oneway_en = true, + }, { + .usecase_id = LLCC_WRCACHE, + .slice_id = 31, + .max_cap = 512, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .activate_on_init = true, + }, { + .usecase_id = LLCC_CVPFW, + .slice_id = 19, + .max_cap = 64, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_CMPTHCP, + .slice_id = 15, + .max_cap = 256, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_LCPDARE, + .slice_id = 30, + .max_cap = 128, + .priority = 5, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .activate_on_init = true, + .alloc_oneway_en = true, + }, { + .usecase_id = LLCC_AENPU, + .slice_id = 3, + .max_cap = 3072, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .cache_mode = 2, + }, { + .usecase_id = LLCC_ISLAND1, + .slice_id = 12, + .max_cap = 7936, + .priority = 7, + .fixed_size = true, + .bonus_ways = 0x7fffffff, + }, { + .usecase_id = LLCC_DISP_WB, + .slice_id = 23, + .max_cap = 512, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_VIDVSP, + .slice_id = 4, + .max_cap = 256, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + }, { + .usecase_id = LLCC_VIDDEC, + .slice_id = 5, + .max_cap = 6144, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .cache_mode = 2, + .ovcap_prio = true, + .parent_slice_id = 33, + }, { + .usecase_id = LLCC_CAMOFE, + .slice_id = 33, + .max_cap = 6144, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .stale_en = true, + .ovcap_prio = true, + .parent_slice_id = 33, + }, { + .usecase_id = LLCC_CAMRTIP, + .slice_id = 13, + .max_cap = 1024, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .stale_en = true, + .ovcap_prio = true, + .parent_slice_id = 33, + }, { + .usecase_id = LLCC_CAMSRTIP, + .slice_id = 14, + .max_cap = 6144, + .priority = 4, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .stale_en = true, + .ovcap_prio = true, + .parent_slice_id = 33, + }, { + .usecase_id = LLCC_CAMRTRF, + .slice_id = 7, + .max_cap = 3584, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .stale_en = true, + .ovcap_prio = true, + .parent_slice_id = 33, + }, { + .usecase_id = LLCC_CAMSRTRF, + .slice_id = 21, + .max_cap = 6144, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .stale_en = true, + .ovcap_prio = true, + .parent_slice_id = 33, + }, { + .usecase_id = LLCC_CPUSSMPAM, + .slice_id = 6, + .max_cap = 2048, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xffffffff, + .activate_on_init = true, + .write_scid_en = true, + }, +}; + static const struct llcc_slice_config qcs615_data[] = { { .usecase_id = LLCC_CPUSS, @@ -3161,6 +3445,33 @@ static const struct llcc_edac_reg_offset llcc_v2_1_edac_reg_offset = { .drp_ecc_db_err_syn0 = 0x52120, }; +static const struct llcc_edac_reg_offset llcc_v6_edac_reg_offset = { + .trp_ecc_error_status0 = 0x47448, + .trp_ecc_error_status1 = 0x47450, + .trp_ecc_sb_err_syn0 = 0x47490, + .trp_ecc_db_err_syn0 = 0x474d0, + .trp_ecc_error_cntr_clear = 0x47444, + .trp_interrupt_0_status = 0x47600, + .trp_interrupt_0_clear = 0x47604, + .trp_interrupt_0_enable = 0x47608, + + /* LLCC Common registers */ + .cmn_status0 = 0x6400c, + .cmn_interrupt_0_enable = 0x6401c, + .cmn_interrupt_2_enable = 0x6403c, + + /* LLCC DRP registers */ + .drp_ecc_error_cfg = 0x80000, + .drp_ecc_error_cntr_clear = 0x80004, + .drp_interrupt_status = 0x80020, + .drp_interrupt_clear = 0x80028, + .drp_interrupt_enable = 0x8002c, + .drp_ecc_error_status0 = 0x820f4, + .drp_ecc_error_status1 = 0x820f8, + .drp_ecc_sb_err_syn0 = 0x820fc, + .drp_ecc_db_err_syn0 = 0x82120, +}; + /* LLCC register offset starting from v1.0.0 */ static const u32 llcc_v1_reg_offset[] = { [LLCC_COMMON_HW_INFO] = 0x00030000, @@ -3173,6 +3484,27 @@ static const u32 llcc_v2_1_reg_offset[] = { [LLCC_COMMON_STATUS0] = 0x0003400c, }; +/* LLCC register offset starting from v6.0.0 */ +static const u32 llcc_v6_reg_offset[] = { + [LLCC_COMMON_HW_INFO] = 0x00064000, + [LLCC_COMMON_STATUS0] = 0x0006400c, + [LLCC_TRP_ATTR0_CFG] = 0x00041000, + [LLCC_TRP_ATTR1_CFG] = 0x00041008, + [LLCC_TRP_ATTR2_CFG] = 0x00041010, + [LLCC_TRP_ATTR3_CFG] = 0x00041014, + [LLCC_TRP_SID_DIS_CAP_ALLOC] = 0x00042000, + [LLCC_TRP_ALGO_STALE_EN] = 0x00042008, + [LLCC_TRP_ALGO_STALE_CAP_EN] = 0x00042010, + [LLCC_TRP_ALGO_MRU0] = 0x00042018, + [LLCC_TRP_ALGO_MRU1] = 0x00042020, + [LLCC_TRP_ALGO_ALLOC0] = 0x00042028, + [LLCC_TRP_ALGO_ALLOC1] = 0x00042030, + [LLCC_TRP_ALGO_ALLOC2] = 0x00042038, + [LLCC_TRP_ALGO_ALLOC3] = 0x00042040, + [LLCC_TRP_WRS_EN] = 0x00042080, + [LLCC_TRP_WRS_CACHEABLE_EN] = 0x00042088, +}; + static const struct qcom_llcc_config qcs615_cfg[] = { { .sct_data = qcs615_data, @@ -3379,6 +3711,16 @@ static const struct qcom_llcc_config sm8650_cfg[] = { }, }; +static const struct qcom_llcc_config sm8750_cfg[] = { + { + .sct_data = sm8750_data, + .size = ARRAY_SIZE(sm8750_data), + .skip_llcc_cfg = false, + .reg_offset = llcc_v6_reg_offset, + .edac_reg_offset = &llcc_v6_edac_reg_offset, + }, +}; + static const struct qcom_llcc_config x1e80100_cfg[] = { { .sct_data = x1e80100_data, @@ -3489,6 +3831,11 @@ static const struct qcom_sct_config sm8650_cfgs = { .num_config = ARRAY_SIZE(sm8650_cfg), }; +static const struct qcom_sct_config sm8750_cfgs = { + .llcc_config = sm8750_cfg, + .num_config = ARRAY_SIZE(sm8750_cfg), +}; + static const struct qcom_sct_config x1e80100_cfgs = { .llcc_config = x1e80100_cfg, .num_config = ARRAY_SIZE(x1e80100_cfg), @@ -3869,6 +4216,139 @@ static int _qcom_llcc_cfg_program(const struct llcc_slice_config *config, return ret; } +static int _qcom_llcc_cfg_program_v6(const struct llcc_slice_config *config, + const struct qcom_llcc_config *cfg) +{ + u32 stale_en, stale_cap_en, mru_uncap_en, mru_rollover; + u32 alloc_oneway_en, ovcap_en, ovcap_prio, vict_prio; + u32 attr0_cfg, attr1_cfg, attr2_cfg, attr3_cfg; + u32 attr0_val, attr1_val, attr2_val, attr3_val; + u32 slice_offset, reg_offset; + struct llcc_slice_desc *desc; + u32 wren, wr_cache_en; + int ret; + + attr0_cfg = LLCC_V6_TRP_ATTR0_CFGn(config->slice_id); + attr1_cfg = LLCC_V6_TRP_ATTR1_CFGn(config->slice_id); + attr2_cfg = LLCC_V6_TRP_ATTR2_CFGn(config->slice_id); + attr3_cfg = LLCC_V6_TRP_ATTR3_CFGn(config->slice_id); + + attr0_val = config->res_ways; + attr1_val = config->bonus_ways; + attr2_val = config->cache_mode; + attr2_val |= FIELD_PREP(ATTR2_PROBE_TARGET_WAYS_MASK, config->probe_target_ways); + attr2_val |= FIELD_PREP(ATTR2_FIXED_SIZE_MASK, config->fixed_size); + attr2_val |= FIELD_PREP(ATTR2_PRIORITY_MASK, config->priority); + + if (config->parent_slice_id && config->fixed_size) { + attr2_val |= FIELD_PREP(ATTR2_PARENT_SCID_MASK, config->parent_slice_id); + attr2_val |= ATTR2_IN_A_GROUP_MASK; + } + + attr3_val = MAX_CAP_TO_BYTES(config->max_cap); + attr3_val /= drv_data->num_banks; + attr3_val >>= CACHE_LINE_SIZE_SHIFT; + + ret = regmap_write(drv_data->bcast_regmap, attr0_cfg, attr0_val); + if (ret) + return ret; + + ret = regmap_write(drv_data->bcast_regmap, attr1_cfg, attr1_val); + if (ret) + return ret; + + ret = regmap_write(drv_data->bcast_regmap, attr2_cfg, attr2_val); + if (ret) + return ret; + + ret = regmap_write(drv_data->bcast_regmap, attr3_cfg, attr3_val); + if (ret) + return ret; + + slice_offset = config->slice_id % 32; + reg_offset = (config->slice_id / 32) * 4; + + wren = config->write_scid_en << slice_offset; + ret = regmap_update_bits(drv_data->bcast_regmap, + cfg->reg_offset[LLCC_TRP_WRS_EN] + reg_offset, + BIT(slice_offset), wren); + if (ret) + return ret; + + wr_cache_en = config->write_scid_cacheable_en << slice_offset; + ret = regmap_update_bits(drv_data->bcast_regmap, + cfg->reg_offset[LLCC_TRP_WRS_CACHEABLE_EN] + reg_offset, + BIT(slice_offset), wr_cache_en); + if (ret) + return ret; + + stale_en = config->stale_en << slice_offset; + ret = regmap_update_bits(drv_data->bcast_regmap, + cfg->reg_offset[LLCC_TRP_ALGO_STALE_EN] + reg_offset, + BIT(slice_offset), stale_en); + if (ret) + return ret; + + stale_cap_en = config->stale_cap_en << slice_offset; + ret = regmap_update_bits(drv_data->bcast_regmap, + cfg->reg_offset[LLCC_TRP_ALGO_STALE_CAP_EN] + reg_offset, + BIT(slice_offset), stale_cap_en); + if (ret) + return ret; + + mru_uncap_en = config->mru_uncap_en << slice_offset; + ret = regmap_update_bits(drv_data->bcast_regmap, + cfg->reg_offset[LLCC_TRP_ALGO_MRU0] + reg_offset, + BIT(slice_offset), mru_uncap_en); + if (ret) + return ret; + + mru_rollover = config->mru_rollover << slice_offset; + ret = regmap_update_bits(drv_data->bcast_regmap, + cfg->reg_offset[LLCC_TRP_ALGO_MRU1] + reg_offset, + BIT(slice_offset), mru_rollover); + if (ret) + return ret; + + alloc_oneway_en = config->alloc_oneway_en << slice_offset; + ret = regmap_update_bits(drv_data->bcast_regmap, + cfg->reg_offset[LLCC_TRP_ALGO_ALLOC0] + reg_offset, + BIT(slice_offset), alloc_oneway_en); + if (ret) + return ret; + + ovcap_en = config->ovcap_en << slice_offset; + ret = regmap_update_bits(drv_data->bcast_regmap, + cfg->reg_offset[LLCC_TRP_ALGO_ALLOC1] + reg_offset, + BIT(slice_offset), ovcap_en); + if (ret) + return ret; + + ovcap_prio = config->ovcap_prio << slice_offset; + ret = regmap_update_bits(drv_data->bcast_regmap, + cfg->reg_offset[LLCC_TRP_ALGO_ALLOC2] + reg_offset, + BIT(slice_offset), ovcap_prio); + if (ret) + return ret; + + vict_prio = config->vict_prio << slice_offset; + ret = regmap_update_bits(drv_data->bcast_regmap, + cfg->reg_offset[LLCC_TRP_ALGO_ALLOC3] + reg_offset, + BIT(slice_offset), vict_prio); + if (ret) + return ret; + + if (config->activate_on_init) { + desc = llcc_slice_getd(config->usecase_id); + if (PTR_ERR_OR_ZERO(desc)) + return -EINVAL; + + ret = llcc_slice_activate(desc); + } + + return ret; +} + static int qcom_llcc_cfg_program(struct platform_device *pdev, const struct qcom_llcc_config *cfg) { @@ -3880,10 +4360,18 @@ static int qcom_llcc_cfg_program(struct platform_device *pdev, sz = drv_data->cfg_size; llcc_table = drv_data->cfg; - for (i = 0; i < sz; i++) { - ret = _qcom_llcc_cfg_program(&llcc_table[i], cfg); - if (ret) - return ret; + if (drv_data->version >= LLCC_VERSION_6_0_0_0) { + for (i = 0; i < sz; i++) { + ret = _qcom_llcc_cfg_program_v6(&llcc_table[i], cfg); + if (ret) + return ret; + } + } else { + for (i = 0; i < sz; i++) { + ret = _qcom_llcc_cfg_program(&llcc_table[i], cfg); + if (ret) + return ret; + } } return ret; @@ -4102,6 +4590,7 @@ static const struct of_device_id qcom_llcc_of_match[] = { { .compatible = "qcom,sm8450-llcc", .data = &sm8450_cfgs }, { .compatible = "qcom,sm8550-llcc", .data = &sm8550_cfgs }, { .compatible = "qcom,sm8650-llcc", .data = &sm8650_cfgs }, + { .compatible = "qcom,sm8750-llcc", .data = &sm8750_cfgs }, { .compatible = "qcom,x1e80100-llcc", .data = &x1e80100_cfgs }, { } }; diff --git a/drivers/soc/qcom/pmic_glink.c b/drivers/soc/qcom/pmic_glink.c index cde19cdfd3c7..0a6d325b195c 100644 --- a/drivers/soc/qcom/pmic_glink.c +++ b/drivers/soc/qcom/pmic_glink.c @@ -371,15 +371,11 @@ static void pmic_glink_remove(struct platform_device *pdev) __pmic_glink = NULL; } -static const unsigned long pmic_glink_sc8280xp_client_mask = BIT(PMIC_GLINK_CLIENT_BATT) | - BIT(PMIC_GLINK_CLIENT_ALTMODE); - static const unsigned long pmic_glink_sm8450_client_mask = BIT(PMIC_GLINK_CLIENT_BATT) | BIT(PMIC_GLINK_CLIENT_ALTMODE) | BIT(PMIC_GLINK_CLIENT_UCSI); static const struct of_device_id pmic_glink_of_match[] = { - { .compatible = "qcom,sc8280xp-pmic-glink", .data = &pmic_glink_sc8280xp_client_mask }, { .compatible = "qcom,pmic-glink", .data = &pmic_glink_sm8450_client_mask }, {} }; diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c index bd06ce161804..7f11acd33323 100644 --- a/drivers/soc/qcom/pmic_glink_altmode.c +++ b/drivers/soc/qcom/pmic_glink_altmode.c @@ -218,21 +218,29 @@ static void pmic_glink_altmode_worker(struct work_struct *work) { struct pmic_glink_altmode_port *alt_port = work_to_altmode_port(work); struct pmic_glink_altmode *altmode = alt_port->altmode; + enum drm_connector_status conn_status; typec_switch_set(alt_port->typec_switch, alt_port->orientation); - if (alt_port->svid == USB_TYPEC_DP_SID && alt_port->mode == 0xff) - pmic_glink_altmode_safe(altmode, alt_port); - else if (alt_port->svid == USB_TYPEC_DP_SID) - pmic_glink_altmode_enable_dp(altmode, alt_port, alt_port->mode, - alt_port->hpd_state, alt_port->hpd_irq); - else - pmic_glink_altmode_enable_usb(altmode, alt_port); + if (alt_port->svid == USB_TYPEC_DP_SID) { + if (alt_port->mode == 0xff) { + pmic_glink_altmode_safe(altmode, alt_port); + } else { + pmic_glink_altmode_enable_dp(altmode, alt_port, + alt_port->mode, + alt_port->hpd_state, + alt_port->hpd_irq); + } - drm_aux_hpd_bridge_notify(&alt_port->bridge->dev, - alt_port->hpd_state ? - connector_status_connected : - connector_status_disconnected); + if (alt_port->hpd_state) + conn_status = connector_status_connected; + else + conn_status = connector_status_disconnected; + + drm_aux_hpd_bridge_notify(&alt_port->bridge->dev, conn_status); + } else { + pmic_glink_altmode_enable_usb(altmode, alt_port); + } pmic_glink_altmode_request(altmode, ALTMODE_PAN_ACK, alt_port->index); } diff --git a/drivers/soc/qcom/qcom_pd_mapper.c b/drivers/soc/qcom/qcom_pd_mapper.c index 1d1c438be3e7..3abea241b1c4 100644 --- a/drivers/soc/qcom/qcom_pd_mapper.c +++ b/drivers/soc/qcom/qcom_pd_mapper.c @@ -488,6 +488,16 @@ static const struct qcom_pdm_domain_data *sm6350_domains[] = { NULL, }; +static const struct qcom_pdm_domain_data *sm7150_domains[] = { + &adsp_audio_pd, + &adsp_root_pd, + &adsp_sensor_pd, + &cdsp_root_pd, + &mpss_root_pd_gps, + &mpss_wlan_pd, + NULL, +}; + static const struct qcom_pdm_domain_data *sm8150_domains[] = { &adsp_audio_pd, &adsp_root_pd, @@ -565,6 +575,7 @@ static const struct of_device_id qcom_pdm_domains[] __maybe_unused = { { .compatible = "qcom,sm4250", .data = sm6115_domains, }, { .compatible = "qcom,sm6115", .data = sm6115_domains, }, { .compatible = "qcom,sm6350", .data = sm6350_domains, }, + { .compatible = "qcom,sm7150", .data = sm7150_domains, }, { .compatible = "qcom,sm7225", .data = sm6350_domains, }, { .compatible = "qcom,sm7325", .data = sc7280_domains, }, { .compatible = "qcom,sm8150", .data = sm8150_domains, }, diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 592819701809..cf425930539e 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -86,7 +86,7 @@ #define SMEM_GLOBAL_HOST 0xfffe /* Max number of processors/hosts in a system */ -#define SMEM_HOST_COUNT 20 +#define SMEM_HOST_COUNT 25 /** * struct smem_proc_comm - proc_comm communication struct (legacy) diff --git a/drivers/soc/qcom/smp2p.c b/drivers/soc/qcom/smp2p.c index 8c8878bc87f5..cb515c2340c1 100644 --- a/drivers/soc/qcom/smp2p.c +++ b/drivers/soc/qcom/smp2p.c @@ -575,7 +575,7 @@ static int qcom_smp2p_probe(struct platform_device *pdev) smp2p->mbox_client.knows_txdone = true; smp2p->mbox_chan = mbox_request_channel(&smp2p->mbox_client, 0); if (IS_ERR(smp2p->mbox_chan)) { - if (PTR_ERR(smp2p->mbox_chan) != -ENODEV) + if (PTR_ERR(smp2p->mbox_chan) != -ENOENT) return PTR_ERR(smp2p->mbox_chan); smp2p->mbox_chan = NULL; diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index 18d7f1be9093..8c4147737c35 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -444,6 +444,7 @@ static const struct soc_id soc_id[] = { { qcom_board_id(IPQ5302) }, { qcom_board_id(QCS8550) }, { qcom_board_id(QCM8550) }, + { qcom_board_id(SM8750) }, { qcom_board_id(IPQ5300) }, { qcom_board_id(IPQ5321) }, { qcom_board_id(IPQ5424) }, diff --git a/drivers/soc/renesas/Kconfig b/drivers/soc/renesas/Kconfig index 4990b85d7df7..fbc3b69d21a7 100644 --- a/drivers/soc/renesas/Kconfig +++ b/drivers/soc/renesas/Kconfig @@ -393,6 +393,13 @@ config ARCH_R9A09G047 help This enables support for the Renesas RZ/G3E SoC variants. +config ARCH_R9A09G056 + bool "ARM64 Platform support for RZ/V2N" + default y if ARCH_RENESAS + select SYS_R9A09G056 + help + This enables support for the Renesas RZ/V2N SoC variants. + config ARCH_R9A09G057 bool "ARM64 Platform support for RZ/V2H(P)" default y if ARCH_RENESAS @@ -439,6 +446,10 @@ config SYS_R9A09G047 bool "Renesas RZ/G3E System controller support" if COMPILE_TEST select SYSC_RZ +config SYS_R9A09G056 + bool "Renesas RZ/V2N System controller support" if COMPILE_TEST + select SYSC_RZ + config SYS_R9A09G057 bool "Renesas RZ/V2H System controller support" if COMPILE_TEST select SYSC_RZ diff --git a/drivers/soc/renesas/Makefile b/drivers/soc/renesas/Makefile index 81d4c5726e4c..3bdcc6a395d5 100644 --- a/drivers/soc/renesas/Makefile +++ b/drivers/soc/renesas/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_ARCH_R9A06G032) += r9a06g032-smp.o endif obj-$(CONFIG_SYSC_R9A08G045) += r9a08g045-sysc.o obj-$(CONFIG_SYS_R9A09G047) += r9a09g047-sys.o +obj-$(CONFIG_SYS_R9A09G056) += r9a09g056-sys.o obj-$(CONFIG_SYS_R9A09G057) += r9a09g057-sys.o # Family diff --git a/drivers/soc/renesas/r9a09g056-sys.c b/drivers/soc/renesas/r9a09g056-sys.c new file mode 100644 index 000000000000..3ad1422eba36 --- /dev/null +++ b/drivers/soc/renesas/r9a09g056-sys.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RZ/V2N System controller (SYS) driver + * + * Copyright (C) 2025 Renesas Electronics Corp. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/io.h> + +#include "rz-sysc.h" + +/* Register Offsets */ +#define SYS_LSI_MODE 0x300 +#define SYS_LSI_MODE_SEC_EN BIT(16) +/* + * BOOTPLLCA[1:0] + * [0,0] => 1.1GHZ + * [0,1] => 1.5GHZ + * [1,0] => 1.6GHZ + * [1,1] => 1.7GHZ + */ +#define SYS_LSI_MODE_STAT_BOOTPLLCA55 GENMASK(12, 11) +#define SYS_LSI_MODE_CA55_1_7GHZ 0x3 + +#define SYS_LSI_PRR 0x308 +#define SYS_LSI_PRR_GPU_DIS BIT(0) +#define SYS_LSI_PRR_ISP_DIS BIT(4) + +#define SYS_RZV2N_FEATURE_G31 BIT(0) +#define SYS_RZV2N_FEATURE_C55 BIT(1) +#define SYS_RZV2N_FEATURE_SEC BIT(2) + +static void rzv2n_sys_print_id(struct device *dev, + void __iomem *sysc_base, + struct soc_device_attribute *soc_dev_attr) +{ + u32 prr_val, mode_val; + u8 feature_flags; + + prr_val = readl(sysc_base + SYS_LSI_PRR); + mode_val = readl(sysc_base + SYS_LSI_MODE); + + /* Check GPU, ISP and Cryptographic configuration */ + feature_flags = !(prr_val & SYS_LSI_PRR_GPU_DIS) ? SYS_RZV2N_FEATURE_G31 : 0; + feature_flags |= !(prr_val & SYS_LSI_PRR_ISP_DIS) ? SYS_RZV2N_FEATURE_C55 : 0; + feature_flags |= (mode_val & SYS_LSI_MODE_SEC_EN) ? SYS_RZV2N_FEATURE_SEC : 0; + + dev_info(dev, "Detected Renesas %s %sn%d Rev %s%s%s%s%s\n", soc_dev_attr->family, + soc_dev_attr->soc_id, 41 + feature_flags, soc_dev_attr->revision, + feature_flags ? " with" : "", + feature_flags & SYS_RZV2N_FEATURE_G31 ? " GE3D (Mali-G31)" : "", + feature_flags & SYS_RZV2N_FEATURE_SEC ? " Cryptographic engine" : "", + feature_flags & SYS_RZV2N_FEATURE_C55 ? " ISP (Mali-C55)" : ""); + + /* Check CA55 PLL configuration */ + if (FIELD_GET(SYS_LSI_MODE_STAT_BOOTPLLCA55, mode_val) != SYS_LSI_MODE_CA55_1_7GHZ) + dev_warn(dev, "CA55 PLL is not set to 1.7GHz\n"); +} + +static const struct rz_sysc_soc_id_init_data rzv2n_sys_soc_id_init_data __initconst = { + .family = "RZ/V2N", + .id = 0x867d447, + .devid_offset = 0x304, + .revision_mask = GENMASK(31, 28), + .specific_id_mask = GENMASK(27, 0), + .print_id = rzv2n_sys_print_id, +}; + +const struct rz_sysc_init_data rzv2n_sys_init_data = { + .soc_id_init_data = &rzv2n_sys_soc_id_init_data, +}; diff --git a/drivers/soc/renesas/rz-sysc.c b/drivers/soc/renesas/rz-sysc.c index 14db508f669f..ffa65fb4dade 100644 --- a/drivers/soc/renesas/rz-sysc.c +++ b/drivers/soc/renesas/rz-sysc.c @@ -88,6 +88,9 @@ static const struct of_device_id rz_sysc_match[] = { #ifdef CONFIG_SYS_R9A09G047 { .compatible = "renesas,r9a09g047-sys", .data = &rzg3e_sys_init_data }, #endif +#ifdef CONFIG_SYS_R9A09G056 + { .compatible = "renesas,r9a09g056-sys", .data = &rzv2n_sys_init_data }, +#endif #ifdef CONFIG_SYS_R9A09G057 { .compatible = "renesas,r9a09g057-sys", .data = &rzv2h_sys_init_data }, #endif diff --git a/drivers/soc/renesas/rz-sysc.h b/drivers/soc/renesas/rz-sysc.h index aa83948c5117..56bc047a1bff 100644 --- a/drivers/soc/renesas/rz-sysc.h +++ b/drivers/soc/renesas/rz-sysc.h @@ -42,5 +42,6 @@ struct rz_sysc_init_data { extern const struct rz_sysc_init_data rzg3e_sys_init_data; extern const struct rz_sysc_init_data rzg3s_sysc_init_data; extern const struct rz_sysc_init_data rzv2h_sys_init_data; +extern const struct rz_sysc_init_data rzv2n_sys_init_data; #endif /* __SOC_RENESAS_RZ_SYSC_H__ */ diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c index c40313886a01..a77288f49d24 100644 --- a/drivers/soc/samsung/exynos-pmu.c +++ b/drivers/soc/samsung/exynos-pmu.c @@ -7,6 +7,7 @@ #include <linux/array_size.h> #include <linux/arm-smccc.h> +#include <linux/cpuhotplug.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/mfd/core.h> @@ -33,6 +34,7 @@ struct exynos_pmu_context { struct device *dev; const struct exynos_pmu_data *pmu_data; struct regmap *pmureg; + struct regmap *pmuintrgen; }; void __iomem *pmu_base_addr; @@ -222,7 +224,8 @@ static const struct regmap_config regmap_smccfg = { }; static const struct exynos_pmu_data gs101_pmu_data = { - .pmu_secure = true + .pmu_secure = true, + .pmu_cpuhp = true, }; /* @@ -326,6 +329,59 @@ struct regmap *exynos_get_pmu_regmap_by_phandle(struct device_node *np, } EXPORT_SYMBOL_GPL(exynos_get_pmu_regmap_by_phandle); +/* + * CPU_INFORM register hint values which are used by + * EL3 firmware (el3mon). + */ +#define CPU_INFORM_CLEAR 0 +#define CPU_INFORM_C2 1 + +static int gs101_cpuhp_pmu_online(unsigned int cpu) +{ + unsigned int cpuhint = smp_processor_id(); + u32 reg, mask; + + /* clear cpu inform hint */ + regmap_write(pmu_context->pmureg, GS101_CPU_INFORM(cpuhint), + CPU_INFORM_CLEAR); + + mask = BIT(cpu); + + regmap_update_bits(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_ENABLE, + mask, (0 << cpu)); + + regmap_read(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_UPEND, ®); + + regmap_write(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_CLEAR, + reg & mask); + + return 0; +} + +static int gs101_cpuhp_pmu_offline(unsigned int cpu) +{ + u32 reg, mask; + unsigned int cpuhint = smp_processor_id(); + + /* set cpu inform hint */ + regmap_write(pmu_context->pmureg, GS101_CPU_INFORM(cpuhint), + CPU_INFORM_C2); + + mask = BIT(cpu); + regmap_update_bits(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_ENABLE, + mask, BIT(cpu)); + + regmap_read(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_UPEND, ®); + regmap_write(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_CLEAR, + reg & mask); + + mask = (BIT(cpu + 8)); + regmap_read(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_UPEND, ®); + regmap_write(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_CLEAR, + reg & mask); + return 0; +} + static int exynos_pmu_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -378,6 +434,26 @@ static int exynos_pmu_probe(struct platform_device *pdev) pmu_context->pmureg = regmap; pmu_context->dev = dev; + if (pmu_context->pmu_data && pmu_context->pmu_data->pmu_cpuhp) { + pmu_context->pmuintrgen = syscon_regmap_lookup_by_phandle(dev->of_node, + "google,pmu-intr-gen-syscon"); + if (IS_ERR(pmu_context->pmuintrgen)) { + /* + * To maintain support for older DTs that didn't specify syscon phandle + * just issue a warning rather than fail to probe. + */ + dev_warn(&pdev->dev, "pmu-intr-gen syscon unavailable\n"); + } else { + cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, + "soc/exynos-pmu:prepare", + gs101_cpuhp_pmu_online, NULL); + + cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, + "soc/exynos-pmu:online", + NULL, gs101_cpuhp_pmu_offline); + } + } + if (pmu_context->pmu_data && pmu_context->pmu_data->pmu_init) pmu_context->pmu_data->pmu_init(); diff --git a/drivers/soc/samsung/exynos-pmu.h b/drivers/soc/samsung/exynos-pmu.h index 0a49a2c9a08e..0938bb4fe15f 100644 --- a/drivers/soc/samsung/exynos-pmu.h +++ b/drivers/soc/samsung/exynos-pmu.h @@ -22,6 +22,7 @@ struct exynos_pmu_data { const struct exynos_pmu_conf *pmu_config; const struct exynos_pmu_conf *pmu_config_extra; bool pmu_secure; + bool pmu_cpuhp; void (*pmu_init)(void); void (*powerdown_conf)(enum sys_powerdown); diff --git a/drivers/soc/sophgo/Kconfig b/drivers/soc/sophgo/Kconfig new file mode 100644 index 000000000000..45f78b270c91 --- /dev/null +++ b/drivers/soc/sophgo/Kconfig @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Sophgo SoC drivers +# + +if ARCH_SOPHGO || COMPILE_TEST +menu "Sophgo SoC drivers" + +config SOPHGO_CV1800_RTCSYS + tristate "Sophgo CV1800 RTC MFD" + select MFD_CORE + help + If you say yes here you get support the RTC MFD driver for Sophgo + CV1800 series SoC. The RTC module comprises a 32kHz oscillator, + Power-on-Reset (PoR) sub-module, HW state machine to control chip + power-on, power-off and reset. Furthermore, the 8051 subsystem is + located within RTCSYS including associated SRAM block. + + This driver can also be built as a module. If so, the module will be + called cv1800-rtcsys. + +config SOPHGO_SG2044_TOPSYS + tristate "Sophgo SG2044 TOP syscon driver" + select MFD_CORE + help + This is the core driver for the Sophgo SG2044 TOP system + controller device. This driver provide PLL clock device + for the SoC. + + This driver can also be built as a module. If so, the module + will be called sg2044-topsys. + +endmenu +endif diff --git a/drivers/soc/sophgo/Makefile b/drivers/soc/sophgo/Makefile new file mode 100644 index 000000000000..27f68df22c4d --- /dev/null +++ b/drivers/soc/sophgo/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_SOPHGO_CV1800_RTCSYS) += cv1800-rtcsys.o +obj-$(CONFIG_SOPHGO_SG2044_TOPSYS) += sg2044-topsys.o diff --git a/drivers/soc/sophgo/cv1800-rtcsys.c b/drivers/soc/sophgo/cv1800-rtcsys.c new file mode 100644 index 000000000000..fdae2e2a61c5 --- /dev/null +++ b/drivers/soc/sophgo/cv1800-rtcsys.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Sophgo CV1800 series SoC RTC subsystem + * + * The RTC module comprises a 32kHz oscillator, Power-on-Reset (PoR) sub-module, + * HW state machine to control chip power-on, power-off and reset. Furthermore, + * the 8051 subsystem is located within RTCSYS including associated SRAM block. + * + * Copyright (C) 2025 Alexander Sverdlin <alexander.sverdlin@gmail.com> + * + */ + +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/property.h> + +static struct resource cv1800_rtcsys_irq_resources[] = { + DEFINE_RES_IRQ_NAMED(0, "alarm"), +}; + +static const struct mfd_cell cv1800_rtcsys_subdev[] = { + { + .name = "cv1800b-rtc", + .num_resources = 1, + .resources = &cv1800_rtcsys_irq_resources[0], + }, +}; + +static int cv1800_rtcsys_probe(struct platform_device *pdev) +{ + int irq; + + irq = platform_get_irq_byname(pdev, "alarm"); + if (irq < 0) + return irq; + cv1800_rtcsys_irq_resources[0].start = irq; + cv1800_rtcsys_irq_resources[0].end = irq; + + return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO, + cv1800_rtcsys_subdev, + ARRAY_SIZE(cv1800_rtcsys_subdev), + NULL, 0, NULL); +} + +static const struct of_device_id cv1800_rtcsys_of_match[] = { + { .compatible = "sophgo,cv1800b-rtc" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cv1800_rtcsys_of_match); + +static struct platform_driver cv1800_rtcsys_mfd = { + .probe = cv1800_rtcsys_probe, + .driver = { + .name = "cv1800_rtcsys", + .of_match_table = cv1800_rtcsys_of_match, + }, +}; +module_platform_driver(cv1800_rtcsys_mfd); + +MODULE_AUTHOR("Alexander Sverdlin <alexander.sverdlin@gmail.com>"); +MODULE_DESCRIPTION("Sophgo CV1800 series SoC RTC subsystem driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/sophgo/sg2044-topsys.c b/drivers/soc/sophgo/sg2044-topsys.c new file mode 100644 index 000000000000..179f2620b2a9 --- /dev/null +++ b/drivers/soc/sophgo/sg2044-topsys.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sophgo SG2044 multi-function system controller driver + * + * Copyright (C) 2025 Inochi Amaoto <inochiama@gmail.com> + */ + +#include <linux/mfd/core.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/resource.h> + +static const struct mfd_cell sg2044_topsys_subdev[] = { + { + .name = "sg2044-pll", + }, +}; + +static int sg2044_topsys_probe(struct platform_device *pdev) +{ + return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO, + sg2044_topsys_subdev, + ARRAY_SIZE(sg2044_topsys_subdev), + NULL, 0, NULL); +} + +static const struct of_device_id sg2044_topsys_of_match[] = { + { .compatible = "sophgo,sg2044-top-syscon" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sg2044_topsys_of_match); + +static struct platform_driver sg2044_topsys_driver = { + .probe = sg2044_topsys_probe, + .driver = { + .name = "sg2044-topsys", + .of_match_table = sg2044_topsys_of_match, + }, +}; +module_platform_driver(sg2044_topsys_driver); + +MODULE_AUTHOR("Inochi Amaoto <inochiama@gmail.com>"); +MODULE_DESCRIPTION("Sophgo SG2044 multi-function system controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/ti/k3-ringacc.c b/drivers/soc/ti/k3-ringacc.c index 82a15cad1c6c..7602b8a909b0 100644 --- a/drivers/soc/ti/k3-ringacc.c +++ b/drivers/soc/ti/k3-ringacc.c @@ -1291,7 +1291,7 @@ struct k3_ringacc *of_k3_ringacc_get_by_phandle(struct device_node *np, mutex_lock(&k3_ringacc_list_lock); list_for_each_entry(entry, &k3_ringacc_list, list) - if (entry->dev->of_node == ringacc_np) { + if (device_match_of_node(entry->dev, ringacc_np)) { ringacc = entry; break; } diff --git a/drivers/soc/ti/k3-socinfo.c b/drivers/soc/ti/k3-socinfo.c index 704039eb3c07..d716be113c84 100644 --- a/drivers/soc/ti/k3-socinfo.c +++ b/drivers/soc/ti/k3-socinfo.c @@ -43,6 +43,7 @@ #define JTAG_ID_PARTNO_AM62AX 0xBB8D #define JTAG_ID_PARTNO_AM62PX 0xBB9D #define JTAG_ID_PARTNO_J722S 0xBBA0 +#define JTAG_ID_PARTNO_AM62LX 0xBBA7 static const struct k3_soc_id { unsigned int id; @@ -58,6 +59,7 @@ static const struct k3_soc_id { { JTAG_ID_PARTNO_AM62AX, "AM62AX" }, { JTAG_ID_PARTNO_AM62PX, "AM62PX" }, { JTAG_ID_PARTNO_J722S, "J722S" }, + { JTAG_ID_PARTNO_AM62LX, "AM62LX" }, }; static const char * const j721e_rev_string_map[] = { diff --git a/drivers/soc/ti/knav_qmss_queue.c b/drivers/soc/ti/knav_qmss_queue.c index ea52425864a9..6e56e7609ccd 100644 --- a/drivers/soc/ti/knav_qmss_queue.c +++ b/drivers/soc/ti/knav_qmss_queue.c @@ -252,8 +252,7 @@ static struct knav_queue *__knav_queue_open(struct knav_queue_inst *inst, return qh; err: - if (qh->stats) - free_percpu(qh->stats); + free_percpu(qh->stats); devm_kfree(inst->kdev->dev, qh); return ERR_PTR(ret); } diff --git a/drivers/soc/ti/wkup_m3_ipc.c b/drivers/soc/ti/wkup_m3_ipc.c index 79dde9a7ec63..5845fc652adc 100644 --- a/drivers/soc/ti/wkup_m3_ipc.c +++ b/drivers/soc/ti/wkup_m3_ipc.c @@ -644,11 +644,9 @@ static int wkup_m3_ipc_probe(struct platform_device *pdev) m3_ipc->mbox = mbox_request_channel(&m3_ipc->mbox_client, 0); - if (IS_ERR(m3_ipc->mbox)) { - dev_err(dev, "IPC Request for A8->M3 Channel failed! %ld\n", - PTR_ERR(m3_ipc->mbox)); - return PTR_ERR(m3_ipc->mbox); - } + if (IS_ERR(m3_ipc->mbox)) + return dev_err_probe(dev, PTR_ERR(m3_ipc->mbox), + "IPC Request for A8->M3 Channel failed!\n"); if (of_property_read_u32(dev->of_node, "ti,rproc", &rproc_phandle)) { dev_err(&pdev->dev, "could not get rproc phandle\n"); diff --git a/drivers/soc/vt8500/Kconfig b/drivers/soc/vt8500/Kconfig new file mode 100644 index 000000000000..b4cc0ba1128b --- /dev/null +++ b/drivers/soc/vt8500/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only + +if ARCH_VT8500 || COMPILE_TEST + +menu "VIA/WonderMedia SoC drivers" + +config WMT_SOCINFO + bool "VIA/WonderMedia SoC Information driver" + default ARCH_VT8500 + select SOC_BUS + help + Say yes to support decoding of VIA/WonderMedia system configuration + register information. This currently includes just the chip ID register + which helps identify the exact hardware revision of the SoC the kernel + is running on (to know if any revision-specific quirks are required) + +endmenu + +endif diff --git a/drivers/soc/vt8500/Makefile b/drivers/soc/vt8500/Makefile new file mode 100644 index 000000000000..05964c5f2890 --- /dev/null +++ b/drivers/soc/vt8500/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_WMT_SOCINFO) += wmt-socinfo.o diff --git a/drivers/soc/vt8500/wmt-socinfo.c b/drivers/soc/vt8500/wmt-socinfo.c new file mode 100644 index 000000000000..461f8c1ae56e --- /dev/null +++ b/drivers/soc/vt8500/wmt-socinfo.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2025 Alexey Charkov <alchark@gmail.com> + * Based on aspeed-socinfo.c + */ + +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/sys_soc.h> + +static const struct { + const char *name; + const u32 id; +} chip_id_table[] = { + /* VIA */ + { "VT8420", 0x3300 }, + { "VT8430", 0x3357 }, + { "VT8500", 0x3400 }, + + /* WonderMedia */ + { "WM8425", 0x3429 }, + { "WM8435", 0x3437 }, + { "WM8440", 0x3451 }, + { "WM8505", 0x3426 }, + { "WM8650", 0x3465 }, + { "WM8750", 0x3445 }, + { "WM8850", 0x3481 }, + { "WM8880", 0x3498 }, +}; + +static const char *sccid_to_name(u32 sccid) +{ + u32 id = sccid >> 16; + unsigned int i; + + for (i = 0 ; i < ARRAY_SIZE(chip_id_table) ; ++i) { + if (chip_id_table[i].id == id) + return chip_id_table[i].name; + } + + return "Unknown"; +} + +static int wmt_socinfo_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct soc_device_attribute *attrs; + struct soc_device *soc_dev; + char letter, digit; + void __iomem *reg; + u32 sccid; + + reg = devm_of_iomap(&pdev->dev, np, 0, NULL); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + sccid = readl(reg); + + attrs = devm_kzalloc(&pdev->dev, sizeof(*attrs), GFP_KERNEL); + if (!attrs) + return -ENOMEM; + + /* + * Machine: VIA APC Rock + * Family: WM8850 + * Revision: A2 + * SoC ID: raw silicon revision id (34810103 in hexadecimal) + */ + + attrs->family = sccid_to_name(sccid); + + letter = (sccid >> 8) & 0xf; + letter = (letter - 1) + 'A'; + digit = sccid & 0xff; + digit = (digit - 1) + '0'; + attrs->revision = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%c%c", letter, digit); + + attrs->soc_id = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%08x", sccid); + + if (!attrs->revision || !attrs->soc_id) + return -ENOMEM; + + soc_dev = soc_device_register(attrs); + if (IS_ERR(soc_dev)) + return PTR_ERR(soc_dev); + + dev_info(&pdev->dev, + "VIA/WonderMedia %s rev %s (%s)\n", + attrs->family, + attrs->revision, + attrs->soc_id); + + platform_set_drvdata(pdev, soc_dev); + return 0; +} + +static void wmt_socinfo_remove(struct platform_device *pdev) +{ + struct soc_device *soc_dev = platform_get_drvdata(pdev); + + soc_device_unregister(soc_dev); +} + +static const struct of_device_id wmt_socinfo_ids[] = { + { .compatible = "via,vt8500-scc-id" }, + { /* Sentinel */ }, +}; + +static struct platform_driver wmt_socinfo = { + .probe = wmt_socinfo_probe, + .remove = wmt_socinfo_remove, + .driver = { + .name = "wmt-socinfo", + .of_match_table = wmt_socinfo_ids, + }, +}; +module_platform_driver(wmt_socinfo); + +MODULE_AUTHOR("Alexey Charkov <alchark@gmail.com>"); +MODULE_DESCRIPTION("VIA/WonderMedia socinfo driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/tee/amdtee/core.c b/drivers/tee/amdtee/core.c index e487231d25dc..fb39d9a19c69 100644 --- a/drivers/tee/amdtee/core.c +++ b/drivers/tee/amdtee/core.c @@ -3,19 +3,22 @@ * Copyright 2019 Advanced Micro Devices, Inc. */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/errno.h> +#include <linux/device.h> +#include <linux/firmware.h> #include <linux/io.h> +#include <linux/mm.h> #include <linux/module.h> +#include <linux/psp-tee.h> #include <linux/slab.h> #include <linux/string.h> -#include <linux/device.h> #include <linux/tee_core.h> #include <linux/types.h> -#include <linux/mm.h> #include <linux/uaccess.h> -#include <linux/firmware.h> + #include "amdtee_private.h" -#include <linux/psp-tee.h> static struct amdtee_driver_data *drv_data; static DEFINE_MUTEX(session_list_mutex); @@ -458,7 +461,7 @@ static int __init amdtee_driver_init(void) rc = psp_check_tee_status(); if (rc) { - pr_err("amd-tee driver: tee not present\n"); + pr_err("tee not present\n"); return rc; } @@ -494,7 +497,6 @@ static int __init amdtee_driver_init(void) drv_data->amdtee = amdtee; - pr_info("amd-tee driver initialization successful\n"); return 0; err_device_unregister: @@ -510,7 +512,7 @@ err_kfree_drv_data: kfree(drv_data); drv_data = NULL; - pr_err("amd-tee driver initialization failed\n"); + pr_err("initialization failed\n"); return rc; } module_init(amdtee_driver_init); diff --git a/drivers/tee/optee/smc_abi.c b/drivers/tee/optee/smc_abi.c index f0c3ac1103bb..26f8f7bbbe56 100644 --- a/drivers/tee/optee/smc_abi.c +++ b/drivers/tee/optee/smc_abi.c @@ -1551,8 +1551,7 @@ fw_load: data_pa_high, data_pa_low, 0, 0, 0, &res); if (!rc) rc = res.a0; - if (fw) - release_firmware(fw); + release_firmware(fw); kfree(data_buf); if (!rc) { diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index d113679b1e2d..acc7998758ad 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -10,6 +10,7 @@ #include <linux/fs.h> #include <linux/idr.h> #include <linux/module.h> +#include <linux/overflow.h> #include <linux/slab.h> #include <linux/tee_core.h> #include <linux/uaccess.h> @@ -19,7 +20,7 @@ #define TEE_NUM_DEVICES 32 -#define TEE_IOCTL_PARAM_SIZE(x) (sizeof(struct tee_param) * (x)) +#define TEE_IOCTL_PARAM_SIZE(x) (size_mul(sizeof(struct tee_param), (x))) #define TEE_UUID_NS_NAME_SIZE 128 @@ -487,7 +488,7 @@ static int tee_ioctl_open_session(struct tee_context *ctx, if (copy_from_user(&arg, uarg, sizeof(arg))) return -EFAULT; - if (sizeof(arg) + TEE_IOCTL_PARAM_SIZE(arg.num_params) != buf.buf_len) + if (size_add(sizeof(arg), TEE_IOCTL_PARAM_SIZE(arg.num_params)) != buf.buf_len) return -EINVAL; if (arg.num_params) { @@ -565,7 +566,7 @@ static int tee_ioctl_invoke(struct tee_context *ctx, if (copy_from_user(&arg, uarg, sizeof(arg))) return -EFAULT; - if (sizeof(arg) + TEE_IOCTL_PARAM_SIZE(arg.num_params) != buf.buf_len) + if (size_add(sizeof(arg), TEE_IOCTL_PARAM_SIZE(arg.num_params)) != buf.buf_len) return -EINVAL; if (arg.num_params) { @@ -699,7 +700,7 @@ static int tee_ioctl_supp_recv(struct tee_context *ctx, if (get_user(num_params, &uarg->num_params)) return -EFAULT; - if (sizeof(*uarg) + TEE_IOCTL_PARAM_SIZE(num_params) != buf.buf_len) + if (size_add(sizeof(*uarg), TEE_IOCTL_PARAM_SIZE(num_params)) != buf.buf_len) return -EINVAL; params = kcalloc(num_params, sizeof(struct tee_param), GFP_KERNEL); @@ -798,7 +799,7 @@ static int tee_ioctl_supp_send(struct tee_context *ctx, get_user(num_params, &uarg->num_params)) return -EFAULT; - if (sizeof(*uarg) + TEE_IOCTL_PARAM_SIZE(num_params) > buf.buf_len) + if (size_add(sizeof(*uarg), TEE_IOCTL_PARAM_SIZE(num_params)) > buf.buf_len) return -EINVAL; params = kcalloc(num_params, sizeof(struct tee_param), GFP_KERNEL); diff --git a/drivers/video/console/vgacon.c b/drivers/video/console/vgacon.c index 37bd18730fe0..f9cdbf8c53e3 100644 --- a/drivers/video/console/vgacon.c +++ b/drivers/video/console/vgacon.c @@ -1168,7 +1168,7 @@ static bool vgacon_scroll(struct vc_data *c, unsigned int t, unsigned int b, c->vc_screenbuf_size - delta); c->vc_origin = vga_vram_end - c->vc_screenbuf_size; vga_rolled_over = 0; - } else + } else if (oldo - delta >= (unsigned long)c->vc_screenbuf) c->vc_origin -= delta; c->vc_scr_end = c->vc_origin + c->vc_screenbuf_size; scr_memsetw((u16 *) (c->vc_origin), c->vc_video_erase_char, diff --git a/drivers/video/fbdev/arkfb.c b/drivers/video/fbdev/arkfb.c index 082501feceb9..ec084323115f 100644 --- a/drivers/video/fbdev/arkfb.c +++ b/drivers/video/fbdev/arkfb.c @@ -431,9 +431,10 @@ static struct dac_ops ics5342_ops = { static struct dac_info * ics5342_init(dac_read_regs_t drr, dac_write_regs_t dwr, void *data) { - struct dac_info *info = kzalloc(sizeof(struct ics5342_info), GFP_KERNEL); + struct ics5342_info *ics_info = kzalloc(sizeof(struct ics5342_info), GFP_KERNEL); + struct dac_info *info = &ics_info->dac; - if (! info) + if (!ics_info) return NULL; info->dacops = &ics5342_ops; diff --git a/drivers/video/fbdev/carminefb.c b/drivers/video/fbdev/carminefb.c index e56065cdba97..2bdd67595891 100644 --- a/drivers/video/fbdev/carminefb.c +++ b/drivers/video/fbdev/carminefb.c @@ -649,13 +649,13 @@ static int carminefb_probe(struct pci_dev *dev, const struct pci_device_id *ent) * is required for that largest resolution to avoid remaps at run * time */ - if (carminefb_fix.smem_len > CARMINE_TOTAL_DIPLAY_MEM) - carminefb_fix.smem_len = CARMINE_TOTAL_DIPLAY_MEM; + if (carminefb_fix.smem_len > CARMINE_TOTAL_DISPLAY_MEM) + carminefb_fix.smem_len = CARMINE_TOTAL_DISPLAY_MEM; - else if (carminefb_fix.smem_len < CARMINE_TOTAL_DIPLAY_MEM) { + else if (carminefb_fix.smem_len < CARMINE_TOTAL_DISPLAY_MEM) { printk(KERN_ERR "carminefb: Memory bar is only %d bytes, %d " "are required.", carminefb_fix.smem_len, - CARMINE_TOTAL_DIPLAY_MEM); + CARMINE_TOTAL_DISPLAY_MEM); goto err_unmap_vregs; } diff --git a/drivers/video/fbdev/carminefb.h b/drivers/video/fbdev/carminefb.h index 297688eba469..c9825481d96b 100644 --- a/drivers/video/fbdev/carminefb.h +++ b/drivers/video/fbdev/carminefb.h @@ -7,7 +7,7 @@ #define MAX_DISPLAY 2 #define CARMINE_DISPLAY_MEM (800 * 600 * 4) -#define CARMINE_TOTAL_DIPLAY_MEM (CARMINE_DISPLAY_MEM * MAX_DISPLAY) +#define CARMINE_TOTAL_DISPLAY_MEM (CARMINE_DISPLAY_MEM * MAX_DISPLAY) #define CARMINE_USE_DISPLAY0 (1 << 0) #define CARMINE_USE_DISPLAY1 (1 << 1) diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c index ac3c99ed92d1..2df48037688d 100644 --- a/drivers/video/fbdev/core/fbcon.c +++ b/drivers/video/fbdev/core/fbcon.c @@ -117,9 +117,14 @@ static signed char con2fb_map_boot[MAX_NR_CONSOLES]; static struct fb_info *fbcon_info_from_console(int console) { + signed char fb; WARN_CONSOLE_UNLOCKED(); - return fbcon_registered_fb[con2fb_map[console]]; + fb = con2fb_map[console]; + if (fb < 0 || fb >= ARRAY_SIZE(fbcon_registered_fb)) + return NULL; + + return fbcon_registered_fb[fb]; } static int logo_lines; diff --git a/drivers/video/fbdev/core/fbcvt.c b/drivers/video/fbdev/core/fbcvt.c index 64843464c661..cd3821bd82e5 100644 --- a/drivers/video/fbdev/core/fbcvt.c +++ b/drivers/video/fbdev/core/fbcvt.c @@ -312,7 +312,7 @@ int fb_find_mode_cvt(struct fb_videomode *mode, int margins, int rb) cvt.f_refresh = cvt.refresh; cvt.interlace = 1; - if (!cvt.xres || !cvt.yres || !cvt.refresh) { + if (!cvt.xres || !cvt.yres || !cvt.refresh || cvt.f_refresh > INT_MAX) { printk(KERN_INFO "fbcvt: Invalid input parameters\n"); return 1; } diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c index 3c568cff2913..eca2498f2436 100644 --- a/drivers/video/fbdev/core/fbmem.c +++ b/drivers/video/fbdev/core/fbmem.c @@ -328,8 +328,10 @@ fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var) !list_empty(&info->modelist)) ret = fb_add_videomode(&mode, &info->modelist); - if (ret) + if (ret) { + info->var = old_var; return ret; + } event.info = info; event.data = &mode; @@ -388,7 +390,7 @@ static int fb_check_foreignness(struct fb_info *fi) static int do_register_framebuffer(struct fb_info *fb_info) { - int i; + int i, err = 0; struct fb_videomode mode; if (fb_check_foreignness(fb_info)) @@ -397,10 +399,18 @@ static int do_register_framebuffer(struct fb_info *fb_info) if (num_registered_fb == FB_MAX) return -ENXIO; - num_registered_fb++; for (i = 0 ; i < FB_MAX; i++) if (!registered_fb[i]) break; + + if (!fb_info->modelist.prev || !fb_info->modelist.next) + INIT_LIST_HEAD(&fb_info->modelist); + + fb_var_to_videomode(&mode, &fb_info->var); + err = fb_add_videomode(&mode, &fb_info->modelist); + if (err < 0) + return err; + fb_info->node = i; refcount_set(&fb_info->count, 1); mutex_init(&fb_info->lock); @@ -426,16 +436,12 @@ static int do_register_framebuffer(struct fb_info *fb_info) if (bitmap_empty(fb_info->pixmap.blit_y, FB_MAX_BLIT_HEIGHT)) bitmap_fill(fb_info->pixmap.blit_y, FB_MAX_BLIT_HEIGHT); - if (!fb_info->modelist.prev || !fb_info->modelist.next) - INIT_LIST_HEAD(&fb_info->modelist); - if (fb_info->skip_vt_switch) pm_vt_switch_required(fb_info->device, false); else pm_vt_switch_required(fb_info->device, true); - fb_var_to_videomode(&mode, &fb_info->var); - fb_add_videomode(&mode, &fb_info->modelist); + num_registered_fb++; registered_fb[i] = fb_info; #ifdef CONFIG_GUMSTIX_AM200EPD diff --git a/drivers/video/fbdev/nvidia/nvidia.c b/drivers/video/fbdev/nvidia/nvidia.c index 8900f181f195..cfaf9454014d 100644 --- a/drivers/video/fbdev/nvidia/nvidia.c +++ b/drivers/video/fbdev/nvidia/nvidia.c @@ -1484,7 +1484,7 @@ static int nvidiafb_setup(char *options) flatpanel = 1; } else if (!strncmp(this_opt, "hwcur", 5)) { hwcur = 1; - } else if (!strncmp(this_opt, "noaccel", 6)) { + } else if (!strncmp(this_opt, "noaccel", 7)) { noaccel = 1; } else if (!strncmp(this_opt, "noscale", 7)) { noscale = 1; diff --git a/drivers/video/fbdev/via/via-gpio.c b/drivers/video/fbdev/via/via-gpio.c index 9577c2cd52c7..27226a8f3f42 100644 --- a/drivers/video/fbdev/via/via-gpio.c +++ b/drivers/video/fbdev/via/via-gpio.c @@ -81,8 +81,7 @@ struct viafb_gpio_cfg { /* * GPIO access functions */ -static void via_gpio_set(struct gpio_chip *chip, unsigned int nr, - int value) +static int via_gpio_set(struct gpio_chip *chip, unsigned int nr, int value) { struct viafb_gpio_cfg *cfg = gpiochip_get_data(chip); u8 reg; @@ -99,13 +98,14 @@ static void via_gpio_set(struct gpio_chip *chip, unsigned int nr, reg &= ~(0x10 << gpio->vg_mask_shift); via_write_reg(VIASR, gpio->vg_port_index, reg); spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); + + return 0; } static int via_gpio_dir_out(struct gpio_chip *chip, unsigned int nr, int value) { - via_gpio_set(chip, nr, value); - return 0; + return via_gpio_set(chip, nr, value); } /* @@ -146,7 +146,7 @@ static struct viafb_gpio_cfg viafb_gpio_config = { .label = "VIAFB onboard GPIO", .owner = THIS_MODULE, .direction_output = via_gpio_dir_out, - .set = via_gpio_set, + .set_rv = via_gpio_set, .direction_input = via_gpio_dir_input, .get = via_gpio_get, .base = -1, diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 0d8d37f712e8..0c25b2ed44eb 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -804,6 +804,15 @@ config IMX7ULP_WDT To compile this driver as a module, choose M here: the module will be called imx7ulp_wdt. +config S32G_WDT + tristate "S32G Watchdog" + depends on ARCH_S32 || COMPILE_TEST + select WATCHDOG_CORE + help + This is the driver for the hardware watchdog on the NXP + S32G platforms. If you wish to have watchdog support + enabled, say Y, otherwise say N. + config DB500_WATCHDOG tristate "ST-Ericsson DB800 watchdog" depends on MFD_DB8500_PRCMU @@ -1001,7 +1010,7 @@ config STM32_WATCHDOG tristate "STM32 Independent WatchDoG (IWDG) support" depends on ARCH_STM32 || COMPILE_TEST select WATCHDOG_CORE - default y + default ARCH_STM32 help Say Y here to include support for the watchdog timer in stm32 SoCs. @@ -1363,6 +1372,17 @@ config INTEL_MID_WATCHDOG To compile this driver as a module, choose M here. +config INTEL_OC_WATCHDOG + tristate "Intel OC Watchdog" + depends on (X86 || COMPILE_TEST) && ACPI && HAS_IOPORT + select WATCHDOG_CORE + help + Hardware driver for Intel Over-Clocking watchdog present in + Platform Controller Hub (PCH) chipsets. + + To compile this driver as a module, choose M here: the + module will be called intel_oc_wdt. + config ITCO_WDT tristate "Intel TCO Timer/Watchdog" depends on X86 && PCI @@ -1869,7 +1889,7 @@ config OCTEON_WDT config MARVELL_GTI_WDT tristate "Marvell GTI Watchdog driver" depends on ARCH_THUNDER || (COMPILE_TEST && 64BIT) - default y + default ARCH_THUNDER select WATCHDOG_CORE help Marvell GTI hardware supports watchdog timer. First timeout @@ -2035,7 +2055,7 @@ config 8xxx_WDT config PIKA_WDT tristate "PIKA FPGA Watchdog" depends on WARP || (PPC64 && COMPILE_TEST) - default y + default WARP help This enables the watchdog in the PIKA FPGA. Currently used on the Warp platform. diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index c9482904bf87..bbd4d62d2cc3 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o obj-$(CONFIG_IMX_SC_WDT) += imx_sc_wdt.o obj-$(CONFIG_IMX7ULP_WDT) += imx7ulp_wdt.o +obj-$(CONFIG_S32G_WDT) += s32g_wdt.o obj-$(CONFIG_DB500_WATCHDOG) += db8500_wdt.o obj-$(CONFIG_RETU_WATCHDOG) += retu_wdt.o obj-$(CONFIG_BCM2835_WDT) += bcm2835_wdt.o @@ -150,6 +151,7 @@ obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o obj-$(CONFIG_MACHZ_WDT) += machzwd.o obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o +obj-$(CONFIG_INTEL_OC_WATCHDOG) += intel_oc_wdt.o obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o obj-$(CONFIG_NIC7018_WDT) += nic7018_wdt.o diff --git a/drivers/watchdog/apple_wdt.c b/drivers/watchdog/apple_wdt.c index 95d9e37df41c..66a158f67a71 100644 --- a/drivers/watchdog/apple_wdt.c +++ b/drivers/watchdog/apple_wdt.c @@ -95,9 +95,12 @@ static int apple_wdt_ping(struct watchdog_device *wdd) static int apple_wdt_set_timeout(struct watchdog_device *wdd, unsigned int s) { struct apple_wdt *wdt = to_apple_wdt(wdd); + u32 actual; writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME); - writel_relaxed(wdt->clk_rate * s, wdt->regs + APPLE_WDT_WD1_BITE_TIME); + + actual = min(s, wdd->max_hw_heartbeat_ms / 1000); + writel_relaxed(wdt->clk_rate * actual, wdt->regs + APPLE_WDT_WD1_BITE_TIME); wdd->timeout = s; @@ -177,7 +180,7 @@ static int apple_wdt_probe(struct platform_device *pdev) wdt->wdd.ops = &apple_wdt_ops; wdt->wdd.info = &apple_wdt_info; - wdt->wdd.max_timeout = U32_MAX / wdt->clk_rate; + wdt->wdd.max_hw_heartbeat_ms = U32_MAX / wdt->clk_rate * 1000; wdt->wdd.timeout = APPLE_WDT_TIMEOUT_DEFAULT; wdt_ctrl = readl_relaxed(wdt->regs + APPLE_WDT_WD1_CTRL); diff --git a/drivers/watchdog/arm_smc_wdt.c b/drivers/watchdog/arm_smc_wdt.c index 8f3d0c3a005f..bbba23ace7b8 100644 --- a/drivers/watchdog/arm_smc_wdt.c +++ b/drivers/watchdog/arm_smc_wdt.c @@ -46,6 +46,8 @@ static int smcwd_call(struct watchdog_device *wdd, enum smcwd_call call, return -ENODEV; if (res->a0 == PSCI_RET_INVALID_PARAMS) return -EINVAL; + if (res->a0 == PSCI_RET_DISABLED) + return -ENODATA; if (res->a0 != PSCI_RET_SUCCESS) return -EIO; return 0; @@ -131,10 +133,19 @@ static int smcwd_probe(struct platform_device *pdev) wdd->info = &smcwd_info; /* get_timeleft is optional */ - if (smcwd_call(wdd, SMCWD_GET_TIMELEFT, 0, NULL)) - wdd->ops = &smcwd_ops; - else + err = smcwd_call(wdd, SMCWD_GET_TIMELEFT, 0, NULL); + switch (err) { + case 0: + set_bit(WDOG_HW_RUNNING, &wdd->status); + fallthrough; + case -ENODATA: wdd->ops = &smcwd_timeleft_ops; + break; + default: + wdd->ops = &smcwd_ops; + break; + } + wdd->timeout = res.a2; wdd->max_timeout = res.a2; wdd->min_timeout = res.a1; diff --git a/drivers/watchdog/cros_ec_wdt.c b/drivers/watchdog/cros_ec_wdt.c index 716c23f4388c..9ffe7f505645 100644 --- a/drivers/watchdog/cros_ec_wdt.c +++ b/drivers/watchdog/cros_ec_wdt.c @@ -25,26 +25,22 @@ static int cros_ec_wdt_send_cmd(struct cros_ec_device *cros_ec, union cros_ec_wdt_data *arg) { int ret; - struct { - struct cros_ec_command msg; - union cros_ec_wdt_data data; - } __packed buf = { - .msg = { - .version = 0, - .command = EC_CMD_HANG_DETECT, - .insize = (arg->req.command == EC_HANG_DETECT_CMD_GET_STATUS) ? - sizeof(struct ec_response_hang_detect) : - 0, - .outsize = sizeof(struct ec_params_hang_detect), - }, - .data.req = arg->req - }; - - ret = cros_ec_cmd_xfer_status(cros_ec, &buf.msg); + DEFINE_RAW_FLEX(struct cros_ec_command, msg, data, + sizeof(union cros_ec_wdt_data)); + + msg->version = 0; + msg->command = EC_CMD_HANG_DETECT; + msg->insize = (arg->req.command == EC_HANG_DETECT_CMD_GET_STATUS) ? + sizeof(struct ec_response_hang_detect) : + 0; + msg->outsize = sizeof(struct ec_params_hang_detect); + *(struct ec_params_hang_detect *)msg->data = arg->req; + + ret = cros_ec_cmd_xfer_status(cros_ec, msg); if (ret < 0) return ret; - arg->resp = buf.data.resp; + arg->resp = *(struct ec_response_hang_detect *)msg->data; return 0; } diff --git a/drivers/watchdog/da9052_wdt.c b/drivers/watchdog/da9052_wdt.c index 77039f2f0be5..afb7887c3a1e 100644 --- a/drivers/watchdog/da9052_wdt.c +++ b/drivers/watchdog/da9052_wdt.c @@ -30,6 +30,18 @@ struct da9052_wdt_data { unsigned long jpast; }; +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static int timeout; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (default = " + __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")"); + static const struct { u8 reg_val; int time; /* Seconds */ @@ -168,10 +180,13 @@ static int da9052_wdt_probe(struct platform_device *pdev) da9052_wdt = &driver_data->wdt; da9052_wdt->timeout = DA9052_DEF_TIMEOUT; + da9052_wdt->min_hw_heartbeat_ms = DA9052_TWDMIN; da9052_wdt->info = &da9052_wdt_info; da9052_wdt->ops = &da9052_wdt_ops; da9052_wdt->parent = dev; watchdog_set_drvdata(da9052_wdt, driver_data); + watchdog_init_timeout(da9052_wdt, timeout, dev); + watchdog_set_nowayout(da9052_wdt, nowayout); if (da9052->fault_log & DA9052_FAULTLOG_TWDERROR) da9052_wdt->bootstatus |= WDIOF_CARDRESET; @@ -180,11 +195,15 @@ static int da9052_wdt_probe(struct platform_device *pdev) if (da9052->fault_log & DA9052_FAULTLOG_VDDFAULT) da9052_wdt->bootstatus |= WDIOF_POWERUNDER; - ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, - DA9052_CONTROLD_TWDSCALE, 0); - if (ret < 0) { - dev_err(dev, "Failed to disable watchdog bits, %d\n", ret); + ret = da9052_reg_read(da9052, DA9052_CONTROL_D_REG); + if (ret < 0) return ret; + + /* Check if FW enabled the watchdog */ + if (ret & DA9052_CONTROLD_TWDSCALE) { + /* Ensure proper initialization */ + da9052_wdt_start(da9052_wdt); + set_bit(WDOG_HW_RUNNING, &da9052_wdt->status); } return devm_watchdog_register_device(dev, &driver_data->wdt); diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c index 7672582fa407..9ab769aa0244 100644 --- a/drivers/watchdog/iTCO_wdt.c +++ b/drivers/watchdog/iTCO_wdt.c @@ -58,7 +58,6 @@ #include <linux/platform_device.h> /* For platform_driver framework */ #include <linux/pci.h> /* For pci functions */ #include <linux/ioport.h> /* For io-port access */ -#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ #include <linux/uaccess.h> /* For copy_to_user/put_user/... */ #include <linux/io.h> /* For inb/outb/... */ #include <linux/platform_data/itco_wdt.h> @@ -102,8 +101,6 @@ struct iTCO_wdt_private { * or memory-mapped PMC register bit 4 (TCO version 3). */ unsigned long __iomem *gcs_pmc; - /* the lock for io operations */ - spinlock_t io_lock; /* the PCI-device */ struct pci_dev *pci_dev; /* whether or not the watchdog has been suspended */ @@ -286,13 +283,10 @@ static int iTCO_wdt_start(struct watchdog_device *wd_dev) struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev); unsigned int val; - spin_lock(&p->io_lock); - iTCO_vendor_pre_start(p->smi_res, wd_dev->timeout); /* disable chipset's NO_REBOOT bit */ if (p->update_no_reboot_bit(p->no_reboot_priv, false)) { - spin_unlock(&p->io_lock); dev_err(wd_dev->parent, "failed to reset NO_REBOOT flag, reboot disabled by hardware/BIOS\n"); return -EIO; } @@ -309,7 +303,6 @@ static int iTCO_wdt_start(struct watchdog_device *wd_dev) val &= 0xf7ff; outw(val, TCO1_CNT(p)); val = inw(TCO1_CNT(p)); - spin_unlock(&p->io_lock); if (val & 0x0800) return -1; @@ -321,8 +314,6 @@ static int iTCO_wdt_stop(struct watchdog_device *wd_dev) struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev); unsigned int val; - spin_lock(&p->io_lock); - iTCO_vendor_pre_stop(p->smi_res); /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */ @@ -334,8 +325,6 @@ static int iTCO_wdt_stop(struct watchdog_device *wd_dev) /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ p->update_no_reboot_bit(p->no_reboot_priv, true); - spin_unlock(&p->io_lock); - if ((val & 0x0800) == 0) return -1; return 0; @@ -345,8 +334,6 @@ static int iTCO_wdt_ping(struct watchdog_device *wd_dev) { struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev); - spin_lock(&p->io_lock); - /* Reload the timer by writing to the TCO Timer Counter register */ if (p->iTCO_version >= 2) { outw(0x01, TCO_RLD(p)); @@ -358,7 +345,6 @@ static int iTCO_wdt_ping(struct watchdog_device *wd_dev) outb(0x01, TCO_RLD(p)); } - spin_unlock(&p->io_lock); return 0; } @@ -385,24 +371,20 @@ static int iTCO_wdt_set_timeout(struct watchdog_device *wd_dev, unsigned int t) /* Write new heartbeat to watchdog */ if (p->iTCO_version >= 2) { - spin_lock(&p->io_lock); val16 = inw(TCOv2_TMR(p)); val16 &= 0xfc00; val16 |= tmrval; outw(val16, TCOv2_TMR(p)); val16 = inw(TCOv2_TMR(p)); - spin_unlock(&p->io_lock); if ((val16 & 0x3ff) != tmrval) return -EINVAL; } else if (p->iTCO_version == 1) { - spin_lock(&p->io_lock); val8 = inb(TCOv1_TMR(p)); val8 &= 0xc0; val8 |= (tmrval & 0xff); outb(val8, TCOv1_TMR(p)); val8 = inb(TCOv1_TMR(p)); - spin_unlock(&p->io_lock); if ((val8 & 0x3f) != tmrval) return -EINVAL; @@ -421,19 +403,15 @@ static unsigned int iTCO_wdt_get_timeleft(struct watchdog_device *wd_dev) /* read the TCO Timer */ if (p->iTCO_version >= 2) { - spin_lock(&p->io_lock); val16 = inw(TCO_RLD(p)); val16 &= 0x3ff; - spin_unlock(&p->io_lock); time_left = ticks_to_seconds(p, val16); } else if (p->iTCO_version == 1) { - spin_lock(&p->io_lock); val8 = inb(TCO_RLD(p)); val8 &= 0x3f; if (!(inw(TCO1_STS(p)) & 0x0008)) val8 += (inb(TCOv1_TMR(p)) & 0x3f); - spin_unlock(&p->io_lock); time_left = ticks_to_seconds(p, val8); } @@ -493,8 +471,6 @@ static int iTCO_wdt_probe(struct platform_device *pdev) if (!p) return -ENOMEM; - spin_lock_init(&p->io_lock); - p->tco_res = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_IO_TCO); if (!p->tco_res) return -ENODEV; @@ -604,6 +580,7 @@ static int iTCO_wdt_probe(struct platform_device *pdev) iTCO_wdt_set_timeout(&p->wddev, WATCHDOG_TIMEOUT); dev_info(dev, "timeout value out of range, using %d\n", WATCHDOG_TIMEOUT); + heartbeat = WATCHDOG_TIMEOUT; } watchdog_stop_on_reboot(&p->wddev); diff --git a/drivers/watchdog/intel_oc_wdt.c b/drivers/watchdog/intel_oc_wdt.c new file mode 100644 index 000000000000..7c0551106981 --- /dev/null +++ b/drivers/watchdog/intel_oc_wdt.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel OC Watchdog driver + * + * Copyright (C) 2025, Siemens + * Author: Diogo Ivo <diogo.ivo@siemens.com> + */ + +#define DRV_NAME "intel_oc_wdt" + +#include <linux/acpi.h> +#include <linux/bits.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> + +#define INTEL_OC_WDT_TOV GENMASK(9, 0) +#define INTEL_OC_WDT_MIN_TOV 1 +#define INTEL_OC_WDT_MAX_TOV 1024 +#define INTEL_OC_WDT_DEF_TOV 60 + +/* + * One-time writable lock bit. If set forbids + * modification of itself, _TOV and _EN until + * next reboot. + */ +#define INTEL_OC_WDT_CTL_LCK BIT(12) + +#define INTEL_OC_WDT_EN BIT(14) +#define INTEL_OC_WDT_NO_ICCSURV_STS BIT(24) +#define INTEL_OC_WDT_ICCSURV_STS BIT(25) +#define INTEL_OC_WDT_RLD BIT(31) + +#define INTEL_OC_WDT_STS_BITS (INTEL_OC_WDT_NO_ICCSURV_STS | \ + INTEL_OC_WDT_ICCSURV_STS) + +#define INTEL_OC_WDT_CTRL_REG(wdt) ((wdt)->ctrl_res->start) + +struct intel_oc_wdt { + struct watchdog_device wdd; + struct resource *ctrl_res; + bool locked; +}; + +static int heartbeat; +module_param(heartbeat, uint, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. (default=" + __MODULE_STRING(WDT_HEARTBEAT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static int intel_oc_wdt_start(struct watchdog_device *wdd) +{ + struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd); + + if (oc_wdt->locked) + return 0; + + outl(inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) | INTEL_OC_WDT_EN, + INTEL_OC_WDT_CTRL_REG(oc_wdt)); + + return 0; +} + +static int intel_oc_wdt_stop(struct watchdog_device *wdd) +{ + struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd); + + outl(inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) & ~INTEL_OC_WDT_EN, + INTEL_OC_WDT_CTRL_REG(oc_wdt)); + + return 0; +} + +static int intel_oc_wdt_ping(struct watchdog_device *wdd) +{ + struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd); + + outl(inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) | INTEL_OC_WDT_RLD, + INTEL_OC_WDT_CTRL_REG(oc_wdt)); + + return 0; +} + +static int intel_oc_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int t) +{ + struct intel_oc_wdt *oc_wdt = watchdog_get_drvdata(wdd); + + outl((inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)) & ~INTEL_OC_WDT_TOV) | (t - 1), + INTEL_OC_WDT_CTRL_REG(oc_wdt)); + + wdd->timeout = t; + + return 0; +} + +static const struct watchdog_info intel_oc_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .identity = DRV_NAME, +}; + +static const struct watchdog_ops intel_oc_wdt_ops = { + .owner = THIS_MODULE, + .start = intel_oc_wdt_start, + .stop = intel_oc_wdt_stop, + .ping = intel_oc_wdt_ping, + .set_timeout = intel_oc_wdt_set_timeout, +}; + +static int intel_oc_wdt_setup(struct intel_oc_wdt *oc_wdt) +{ + struct watchdog_info *info; + unsigned long val; + + val = inl(INTEL_OC_WDT_CTRL_REG(oc_wdt)); + + if (val & INTEL_OC_WDT_STS_BITS) + oc_wdt->wdd.bootstatus |= WDIOF_CARDRESET; + + oc_wdt->locked = !!(val & INTEL_OC_WDT_CTL_LCK); + + if (val & INTEL_OC_WDT_EN) { + /* + * No need to issue a ping here to "commit" the new timeout + * value to hardware as the watchdog core schedules one + * immediately when registering the watchdog. + */ + set_bit(WDOG_HW_RUNNING, &oc_wdt->wdd.status); + + if (oc_wdt->locked) { + info = (struct watchdog_info *)&intel_oc_wdt_info; + /* + * Set nowayout unconditionally as we cannot stop + * the watchdog. + */ + nowayout = true; + /* + * If we are locked read the current timeout value + * and inform the core we can't change it. + */ + oc_wdt->wdd.timeout = (val & INTEL_OC_WDT_TOV) + 1; + info->options &= ~WDIOF_SETTIMEOUT; + + dev_info(oc_wdt->wdd.parent, + "Register access locked, heartbeat fixed at: %u s\n", + oc_wdt->wdd.timeout); + } + } else if (oc_wdt->locked) { + /* + * In case the watchdog is disabled and locked there + * is nothing we can do with it so just fail probing. + */ + return -EACCES; + } + + val &= ~INTEL_OC_WDT_TOV; + outl(val | (oc_wdt->wdd.timeout - 1), INTEL_OC_WDT_CTRL_REG(oc_wdt)); + + return 0; +} + +static int intel_oc_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_oc_wdt *oc_wdt; + struct watchdog_device *wdd; + int ret; + + oc_wdt = devm_kzalloc(&pdev->dev, sizeof(*oc_wdt), GFP_KERNEL); + if (!oc_wdt) + return -ENOMEM; + + oc_wdt->ctrl_res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!oc_wdt->ctrl_res) { + dev_err(&pdev->dev, "missing I/O resource\n"); + return -ENODEV; + } + + if (!devm_request_region(&pdev->dev, oc_wdt->ctrl_res->start, + resource_size(oc_wdt->ctrl_res), pdev->name)) { + dev_err(dev, "resource %pR already in use, device disabled\n", + oc_wdt->ctrl_res); + return -EBUSY; + } + + wdd = &oc_wdt->wdd; + wdd->min_timeout = INTEL_OC_WDT_MIN_TOV; + wdd->max_timeout = INTEL_OC_WDT_MAX_TOV; + wdd->timeout = INTEL_OC_WDT_DEF_TOV; + wdd->info = &intel_oc_wdt_info; + wdd->ops = &intel_oc_wdt_ops; + wdd->parent = dev; + + watchdog_init_timeout(wdd, heartbeat, dev); + + ret = intel_oc_wdt_setup(oc_wdt); + if (ret) + return ret; + + watchdog_set_drvdata(wdd, oc_wdt); + watchdog_set_nowayout(wdd, nowayout); + watchdog_stop_on_reboot(wdd); + watchdog_stop_on_unregister(wdd); + + return devm_watchdog_register_device(dev, wdd); +} + +static const struct acpi_device_id intel_oc_wdt_match[] = { + { "INT3F0D" }, + { "INTC1099" }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, intel_oc_wdt_match); + +static struct platform_driver intel_oc_wdt_platform_driver = { + .driver = { + .name = DRV_NAME, + .acpi_match_table = intel_oc_wdt_match, + }, + .probe = intel_oc_wdt_probe, +}; + +module_platform_driver(intel_oc_wdt_platform_driver); + +MODULE_AUTHOR("Diogo Ivo <diogo.ivo@siemens.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Intel OC Watchdog driver"); diff --git a/drivers/watchdog/lenovo_se30_wdt.c b/drivers/watchdog/lenovo_se30_wdt.c index 024b842499b3..1c73bb7eeeee 100644 --- a/drivers/watchdog/lenovo_se30_wdt.c +++ b/drivers/watchdog/lenovo_se30_wdt.c @@ -271,6 +271,8 @@ static int lenovo_se30_wdt_probe(struct platform_device *pdev) return -EBUSY; priv->shm_base_addr = devm_ioremap(dev, base_phys, SHM_WIN_SIZE); + if (!priv->shm_base_addr) + return -ENOMEM; priv->wdt_cfg.mod = WDT_MODULE; priv->wdt_cfg.idx = WDT_CFG_INDEX; diff --git a/drivers/watchdog/pcwd_usb.c b/drivers/watchdog/pcwd_usb.c index 132699e2f247..b636650b714b 100644 --- a/drivers/watchdog/pcwd_usb.c +++ b/drivers/watchdog/pcwd_usb.c @@ -579,7 +579,7 @@ static struct notifier_block usb_pcwd_notifier = { .notifier_call = usb_pcwd_notify_sys, }; -/** +/* * usb_pcwd_delete */ static inline void usb_pcwd_delete(struct usb_pcwd_private *usb_pcwd) @@ -590,7 +590,7 @@ static inline void usb_pcwd_delete(struct usb_pcwd_private *usb_pcwd) kfree(usb_pcwd); } -/** +/* * usb_pcwd_probe * * Called by the usb core when a new device is connected that it thinks @@ -758,7 +758,7 @@ error: } -/** +/* * usb_pcwd_disconnect * * Called by the usb core when the device is removed from the system. diff --git a/drivers/watchdog/pretimeout_noop.c b/drivers/watchdog/pretimeout_noop.c index 4799551dd784..74ec02b9ffca 100644 --- a/drivers/watchdog/pretimeout_noop.c +++ b/drivers/watchdog/pretimeout_noop.c @@ -11,7 +11,7 @@ /** * pretimeout_noop - No operation on watchdog pretimeout event - * @wdd - watchdog_device + * @wdd: watchdog_device * * This function prints a message about pretimeout to kernel log. */ diff --git a/drivers/watchdog/pretimeout_panic.c b/drivers/watchdog/pretimeout_panic.c index 2cc3c41d2be5..8c3ac674dc45 100644 --- a/drivers/watchdog/pretimeout_panic.c +++ b/drivers/watchdog/pretimeout_panic.c @@ -11,7 +11,7 @@ /** * pretimeout_panic - Panic on watchdog pretimeout event - * @wdd - watchdog_device + * @wdd: watchdog_device * * Panic, watchdog has not been fed till pretimeout event. */ diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c index 006f9c61aa64..dfaac5995c84 100644 --- a/drivers/watchdog/qcom-wdt.c +++ b/drivers/watchdog/qcom-wdt.c @@ -181,6 +181,12 @@ static const struct qcom_wdt_match_data match_data_apcs_tmr = { .max_tick_count = 0x10000000U, }; +static const struct qcom_wdt_match_data match_data_ipq5424 = { + .offset = reg_offset_data_kpss, + .pretimeout = true, + .max_tick_count = 0xFFFFFU, +}; + static const struct qcom_wdt_match_data match_data_kpss = { .offset = reg_offset_data_kpss, .pretimeout = true, @@ -322,6 +328,7 @@ static const struct dev_pm_ops qcom_wdt_pm_ops = { }; static const struct of_device_id qcom_wdt_of_table[] = { + { .compatible = "qcom,apss-wdt-ipq5424", .data = &match_data_ipq5424 }, { .compatible = "qcom,kpss-timer", .data = &match_data_apcs_tmr }, { .compatible = "qcom,scss-timer", .data = &match_data_apcs_tmr }, { .compatible = "qcom,kpss-wdt", .data = &match_data_kpss }, diff --git a/drivers/watchdog/s32g_wdt.c b/drivers/watchdog/s32g_wdt.c new file mode 100644 index 000000000000..ad55063060af --- /dev/null +++ b/drivers/watchdog/s32g_wdt.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Watchdog driver for S32G SoC + * + * Copyright 2017-2019, 2021-2025 NXP. + * + */ +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> + +#define DRIVER_NAME "s32g-swt" + +#define S32G_SWT_CR(__base) ((__base) + 0x00) /* Control Register offset */ +#define S32G_SWT_CR_SM (BIT(9) | BIT(10)) /* -> Service Mode */ +#define S32G_SWT_CR_STP BIT(2) /* -> Stop Mode Control */ +#define S32G_SWT_CR_FRZ BIT(1) /* -> Debug Mode Control */ +#define S32G_SWT_CR_WEN BIT(0) /* -> Watchdog Enable */ + +#define S32G_SWT_TO(__base) ((__base) + 0x08) /* Timeout Register offset */ + +#define S32G_SWT_SR(__base) ((__base) + 0x10) /* Service Register offset */ +#define S32G_WDT_SEQ1 0xA602 /* -> service sequence number 1 */ +#define S32G_WDT_SEQ2 0xB480 /* -> service sequence number 2 */ + +#define S32G_SWT_CO(__base) ((__base) + 0x14) /* Counter output register */ + +#define S32G_WDT_DEFAULT_TIMEOUT 30 + +struct s32g_wdt_device { + int rate; + void __iomem *base; + struct watchdog_device wdog; +}; + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static unsigned int timeout_param = S32G_WDT_DEFAULT_TIMEOUT; +module_param(timeout_param, uint, 0); +MODULE_PARM_DESC(timeout_param, "Watchdog timeout in seconds (default=" + __MODULE_STRING(S32G_WDT_DEFAULT_TIMEOUT) ")"); + +static bool early_enable; +module_param(early_enable, bool, 0); +MODULE_PARM_DESC(early_enable, + "Watchdog is started on module insertion (default=false)"); + +static const struct watchdog_info s32g_wdt_info = { + .identity = "s32g watchdog", + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | + WDIOC_GETTIMEOUT | WDIOC_GETTIMELEFT, +}; + +static struct s32g_wdt_device *wdd_to_s32g_wdt(struct watchdog_device *wdd) +{ + return container_of(wdd, struct s32g_wdt_device, wdog); +} + +static unsigned int wdog_sec_to_count(struct s32g_wdt_device *wdev, unsigned int timeout) +{ + return wdev->rate * timeout; +} + +static int s32g_wdt_ping(struct watchdog_device *wdog) +{ + struct s32g_wdt_device *wdev = wdd_to_s32g_wdt(wdog); + + writel(S32G_WDT_SEQ1, S32G_SWT_SR(wdev->base)); + writel(S32G_WDT_SEQ2, S32G_SWT_SR(wdev->base)); + + return 0; +} + +static int s32g_wdt_start(struct watchdog_device *wdog) +{ + struct s32g_wdt_device *wdev = wdd_to_s32g_wdt(wdog); + unsigned long val; + + val = readl(S32G_SWT_CR(wdev->base)); + + val |= S32G_SWT_CR_WEN; + + writel(val, S32G_SWT_CR(wdev->base)); + + return 0; +} + +static int s32g_wdt_stop(struct watchdog_device *wdog) +{ + struct s32g_wdt_device *wdev = wdd_to_s32g_wdt(wdog); + unsigned long val; + + val = readl(S32G_SWT_CR(wdev->base)); + + val &= ~S32G_SWT_CR_WEN; + + writel(val, S32G_SWT_CR(wdev->base)); + + return 0; +} + +static int s32g_wdt_set_timeout(struct watchdog_device *wdog, unsigned int timeout) +{ + struct s32g_wdt_device *wdev = wdd_to_s32g_wdt(wdog); + + writel(wdog_sec_to_count(wdev, timeout), S32G_SWT_TO(wdev->base)); + + wdog->timeout = timeout; + + /* + * Conforming to the documentation, the timeout counter is + * loaded when servicing is operated (aka ping) or when the + * counter is enabled. In case the watchdog is already started + * it must be stopped and started again to update the timeout + * register or a ping can be sent to refresh the counter. Here + * we choose to send a ping to the watchdog which is harmless + * if the watchdog is stopped. + */ + return s32g_wdt_ping(wdog); +} + +static unsigned int s32g_wdt_get_timeleft(struct watchdog_device *wdog) +{ + struct s32g_wdt_device *wdev = wdd_to_s32g_wdt(wdog); + unsigned long counter; + bool is_running; + + /* + * The counter output can be read only if the SWT is + * disabled. Given the latency between the internal counter + * and the counter output update, there can be very small + * difference. However, we can accept this matter of fact + * given the resolution is a second based unit for the output. + */ + is_running = watchdog_hw_running(wdog); + + if (is_running) + s32g_wdt_stop(wdog); + + counter = readl(S32G_SWT_CO(wdev->base)); + + if (is_running) + s32g_wdt_start(wdog); + + return counter / wdev->rate; +} + +static const struct watchdog_ops s32g_wdt_ops = { + .owner = THIS_MODULE, + .start = s32g_wdt_start, + .stop = s32g_wdt_stop, + .ping = s32g_wdt_ping, + .set_timeout = s32g_wdt_set_timeout, + .get_timeleft = s32g_wdt_get_timeleft, +}; + +static void s32g_wdt_init(struct s32g_wdt_device *wdev) +{ + unsigned long val; + + /* Set the watchdog's Time-Out value */ + val = wdog_sec_to_count(wdev, wdev->wdog.timeout); + + writel(val, S32G_SWT_TO(wdev->base)); + + /* + * Get the control register content. We are at init time, the + * watchdog should not be started. + */ + val = readl(S32G_SWT_CR(wdev->base)); + + /* + * We want to allow the watchdog timer to be stopped when + * device enters debug mode. + */ + val |= S32G_SWT_CR_FRZ; + + /* + * However, when the CPU is in WFI or suspend mode, the + * watchdog must continue. The documentation refers it as the + * stopped mode. + */ + val &= ~S32G_SWT_CR_STP; + + /* + * Use Fixed Service Sequence to ping the watchdog which is + * 0x00 configuration value for the service mode. It should be + * already set because it is the default value but we reset it + * in case. + */ + val &= ~S32G_SWT_CR_SM; + + writel(val, S32G_SWT_CR(wdev->base)); + + /* + * When the 'early_enable' option is set, we start the + * watchdog from the kernel. + */ + if (early_enable) { + s32g_wdt_start(&wdev->wdog); + set_bit(WDOG_HW_RUNNING, &wdev->wdog.status); + } +} + +static int s32g_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct clk *clk; + struct s32g_wdt_device *wdev; + struct watchdog_device *wdog; + int ret; + + wdev = devm_kzalloc(dev, sizeof(*wdev), GFP_KERNEL); + if (!wdev) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdev->base = devm_ioremap_resource(dev, res); + if (IS_ERR(wdev->base)) + return dev_err_probe(&pdev->dev, PTR_ERR(wdev->base), "Can not get resource\n"); + + clk = devm_clk_get_enabled(dev, "counter"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Can't get Watchdog clock\n"); + + wdev->rate = clk_get_rate(clk); + if (!wdev->rate) { + dev_err(dev, "Input clock rate is not valid\n"); + return -EINVAL; + } + + wdog = &wdev->wdog; + wdog->info = &s32g_wdt_info; + wdog->ops = &s32g_wdt_ops; + + /* + * The code converts the timeout into a counter a value, if + * the value is less than 0x100, then it is clamped by the SWT + * module, so it is safe to specify a zero value as the + * minimum timeout. + */ + wdog->min_timeout = 0; + + /* + * The counter register is a 32 bits long, so the maximum + * counter value is UINT_MAX and the timeout in second is the + * value divided by the rate. + * + * For instance, a rate of 51MHz lead to 84 seconds maximum + * timeout. + */ + wdog->max_timeout = UINT_MAX / wdev->rate; + + /* + * The module param and the DT 'timeout-sec' property will + * override the default value if they are specified. + */ + ret = watchdog_init_timeout(wdog, timeout_param, dev); + if (ret) + return ret; + + /* + * As soon as the watchdog is started, there is no way to stop + * it if the 'nowayout' option is set at boot time + */ + watchdog_set_nowayout(wdog, nowayout); + + /* + * The devm_ version of the watchdog_register_device() + * function will call watchdog_unregister_device() when the + * device is removed. + */ + watchdog_stop_on_unregister(wdog); + + s32g_wdt_init(wdev); + + ret = devm_watchdog_register_device(dev, wdog); + if (ret) + return dev_err_probe(dev, ret, "Cannot register watchdog device\n"); + + dev_info(dev, "S32G Watchdog Timer Registered, timeout=%ds, nowayout=%d, early_enable=%d\n", + wdog->timeout, nowayout, early_enable); + + return 0; +} + +static const struct of_device_id s32g_wdt_dt_ids[] = { + { .compatible = "nxp,s32g2-swt" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s32g_wdt_dt_ids); + +static struct platform_driver s32g_wdt_driver = { + .probe = s32g_wdt_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = s32g_wdt_dt_ids, + }, +}; + +module_platform_driver(s32g_wdt_driver); + +MODULE_AUTHOR("Daniel Lezcano <daniel.lezcano@linaro.org>"); +MODULE_DESCRIPTION("Watchdog driver for S32G SoC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index bdd81d8074b2..40901bdac426 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c @@ -82,6 +82,10 @@ #define GS_CLUSTER2_NONCPU_INT_EN 0x1644 #define GS_RST_STAT_REG_OFFSET 0x3B44 +#define EXYNOS990_CLUSTER2_NONCPU_OUT 0x1620 +#define EXYNOS990_CLUSTER2_NONCPU_INT_EN 0x1644 +#define EXYNOS990_CLUSTER2_WDTRESET_BIT 23 + /** * DOC: Quirk flags for different Samsung watchdog IP-cores * @@ -259,6 +263,32 @@ static const struct s3c2410_wdt_variant drv_data_exynos850_cl1 = { QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN, }; +static const struct s3c2410_wdt_variant drv_data_exynos990_cl0 = { + .mask_reset_reg = GS_CLUSTER0_NONCPU_INT_EN, + .mask_bit = 2, + .mask_reset_inv = true, + .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, + .rst_stat_bit = EXYNOS850_CLUSTER0_WDTRESET_BIT, + .cnt_en_reg = EXYNOSAUTOV920_CLUSTER0_NONCPU_OUT, + .cnt_en_bit = 7, + .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | + QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN | + QUIRK_HAS_DBGACK_BIT, +}; + +static const struct s3c2410_wdt_variant drv_data_exynos990_cl2 = { + .mask_reset_reg = EXYNOS990_CLUSTER2_NONCPU_INT_EN, + .mask_bit = 2, + .mask_reset_inv = true, + .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, + .rst_stat_bit = EXYNOS990_CLUSTER2_WDTRESET_BIT, + .cnt_en_reg = EXYNOS990_CLUSTER2_NONCPU_OUT, + .cnt_en_bit = 7, + .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | + QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN | + QUIRK_HAS_DBGACK_BIT, +}; + static const struct s3c2410_wdt_variant drv_data_exynosautov9_cl0 = { .mask_reset_reg = EXYNOS850_CLUSTER0_NONCPU_INT_EN, .mask_bit = 2, @@ -350,6 +380,8 @@ static const struct of_device_id s3c2410_wdt_match[] = { .data = &drv_data_exynos7 }, { .compatible = "samsung,exynos850-wdt", .data = &drv_data_exynos850_cl0 }, + { .compatible = "samsung,exynos990-wdt", + .data = &drv_data_exynos990_cl0 }, { .compatible = "samsung,exynosautov9-wdt", .data = &drv_data_exynosautov9_cl0 }, { .compatible = "samsung,exynosautov920-wdt", @@ -678,7 +710,8 @@ s3c2410_get_wdt_drv_data(struct platform_device *pdev, struct s3c2410_wdt *wdt) if (variant == &drv_data_exynos850_cl0 || variant == &drv_data_exynosautov9_cl0 || variant == &drv_data_gs101_cl0 || - variant == &drv_data_exynosautov920_cl0) { + variant == &drv_data_exynosautov920_cl0 || + variant == &drv_data_exynos990_cl0) { u32 index; int err; @@ -700,6 +733,10 @@ s3c2410_get_wdt_drv_data(struct platform_device *pdev, struct s3c2410_wdt *wdt) else if (variant == &drv_data_exynosautov920_cl0) variant = &drv_data_exynosautov920_cl1; break; + case 2: + if (variant == &drv_data_exynos990_cl0) + variant = &drv_data_exynos990_cl2; + break; default: return dev_err_probe(dev, -EINVAL, "wrong cluster index: %u\n", index); } diff --git a/drivers/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c index 8ad06b54c5ad..b356a272ff9a 100644 --- a/drivers/watchdog/stm32_iwdg.c +++ b/drivers/watchdog/stm32_iwdg.c @@ -291,7 +291,7 @@ static int stm32_iwdg_irq_init(struct platform_device *pdev, return 0; if (of_property_read_bool(np, "wakeup-source")) { - ret = device_init_wakeup(dev, true); + ret = devm_device_init_wakeup(dev); if (ret) return ret; diff --git a/drivers/watchdog/wdt_pci.c b/drivers/watchdog/wdt_pci.c index dc5f29560e9b..3918a600f2a0 100644 --- a/drivers/watchdog/wdt_pci.c +++ b/drivers/watchdog/wdt_pci.c @@ -264,7 +264,7 @@ static int wdtpci_get_status(int *status) return 0; } -/** +/* * wdtpci_get_temperature: * * Reports the temperature in degrees Fahrenheit. The API is in |