| // SPDX-License-Identifier: GPL-2.0-or-later OR BSD-3-Clause |
| /* |
| * Copyright (C) 2025, STMicroelectronics - All Rights Reserved |
| */ |
| |
| #define LOG_CATEGORY UCLASS_NOP |
| |
| #include <clk.h> |
| #include <dm.h> |
| #include <regmap.h> |
| #include <reset.h> |
| #include <syscon.h> |
| #include <asm/io.h> |
| #include <dm/device_compat.h> |
| #include <dm/device-internal.h> |
| #include <dm/lists.h> |
| #include <dm/of_addr.h> |
| #include <dm/of_access.h> |
| #include <linux/bitfield.h> |
| #include <linux/ioport.h> |
| #include <mach/rif.h> |
| |
| /* OCTOSPI control register */ |
| #define OCTOSPIM_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 |
| #define OMM_RESET_NB 3 |
| #define NSEC_PER_SEC 1000000000L |
| |
| struct stm32_omm_plat { |
| phys_addr_t regs_base; |
| struct regmap *syscfg_regmap; |
| struct clk clk[OMM_CLK_NB]; |
| struct reset_ctl reset_ctl[OMM_RESET_NB]; |
| resource_size_t mm_ospi2_size; |
| u32 mux; |
| u32 cssel_ovr; |
| u32 req2ack; |
| u32 amcr_base; |
| u32 amcr_mask; |
| unsigned long clk_rate_max; |
| u8 nb_child; |
| }; |
| |
| static int stm32_omm_set_amcr(struct udevice *dev, bool set) |
| { |
| struct stm32_omm_plat *plat = dev_get_plat(dev); |
| unsigned int amcr, read_amcr; |
| |
| amcr = plat->mm_ospi2_size / SZ_64M; |
| |
| if (set) |
| regmap_update_bits(plat->syscfg_regmap, plat->amcr_base, |
| plat->amcr_mask, amcr); |
| |
| /* read AMCR and check coherency with memory-map areas defined in DT */ |
| regmap_read(plat->syscfg_regmap, plat->amcr_base, &read_amcr); |
| read_amcr = read_amcr >> (ffs(plat->amcr_mask) - 1); |
| |
| if (amcr != read_amcr) { |
| dev_err(dev, "AMCR value not coherent with DT memory-map areas\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int stm32_omm_toggle_child_clock(struct udevice *dev, bool enable) |
| { |
| struct stm32_omm_plat *plat = dev_get_plat(dev); |
| int i, ret; |
| |
| for (i = 0; i < plat->nb_child; i++) { |
| if (enable) { |
| ret = clk_enable(&plat->clk[i + 1]); |
| if (ret) { |
| dev_err(dev, "Can not enable clock\n"); |
| goto clk_error; |
| } |
| } else { |
| clk_disable(&plat->clk[i + 1]); |
| } |
| } |
| |
| return 0; |
| |
| clk_error: |
| while (i--) |
| clk_disable(&plat->clk[i + 1]); |
| |
| return ret; |
| } |
| |
| static int stm32_omm_disable_child(struct udevice *dev) |
| { |
| struct stm32_omm_plat *plat = dev_get_plat(dev); |
| int ret; |
| u8 i; |
| |
| ret = stm32_omm_toggle_child_clock(dev, true); |
| if (ret) |
| return ret; |
| |
| for (i = 0; i < plat->nb_child; i++) { |
| /* reset OSPI to ensure CR_EN bit is set to 0 */ |
| reset_assert(&plat->reset_ctl[i + 1]); |
| udelay(2); |
| reset_deassert(&plat->reset_ctl[i + 1]); |
| } |
| |
| return stm32_omm_toggle_child_clock(dev, false); |
| } |
| |
| static int stm32_omm_configure(struct udevice *dev) |
| { |
| struct stm32_omm_plat *plat = dev_get_plat(dev); |
| int ret; |
| u32 mux = 0; |
| u32 cssel_ovr = 0; |
| u32 req2ack = 0; |
| |
| /* Ensure both OSPI instance are disabled before configuring OMM */ |
| ret = stm32_omm_disable_child(dev); |
| if (ret) |
| return ret; |
| |
| ret = clk_enable(&plat->clk[0]); |
| if (ret) { |
| dev_err(dev, "Failed to enable OMM clock (%d)\n", ret); |
| return ret; |
| } |
| |
| reset_assert(&plat->reset_ctl[0]); |
| udelay(2); |
| reset_deassert(&plat->reset_ctl[0]); |
| |
| if (plat->mux & CR_MUXEN) { |
| if (plat->req2ack) { |
| req2ack = DIV_ROUND_UP(plat->req2ack, |
| NSEC_PER_SEC / plat->clk_rate_max) - 1; |
| if (req2ack > 256) |
| req2ack = 256; |
| } |
| |
| req2ack = FIELD_PREP(CR_REQ2ACK_MASK, req2ack); |
| clrsetbits_le32(plat->regs_base + OCTOSPIM_CR, 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) |
| return ret; |
| } |
| |
| if (plat->cssel_ovr != 0xff) { |
| cssel_ovr = FIELD_PREP(CR_CSSEL_OVR_MASK, cssel_ovr); |
| cssel_ovr |= CR_CSSEL_OVR_EN; |
| clrsetbits_le32(plat->regs_base + OCTOSPIM_CR, CR_CSSEL_OVR_MASK, |
| cssel_ovr); |
| } |
| |
| mux = FIELD_PREP(CR_MUXENMODE_MASK, plat->mux); |
| clrsetbits_le32(plat->regs_base + OCTOSPIM_CR, CR_MUXENMODE_MASK, mux); |
| clk_disable(&plat->clk[0]); |
| |
| return stm32_omm_set_amcr(dev, true); |
| } |
| |
| static void stm32_omm_release_childs(ofnode *child_list, u8 nb_child) |
| { |
| u8 i; |
| |
| for (i = 0; i < nb_child; i++) |
| stm32_rifsc_release_access(child_list[i]); |
| } |
| |
| static int stm32_omm_probe(struct udevice *dev) |
| { |
| struct stm32_omm_plat *plat = dev_get_plat(dev); |
| ofnode child_list[OMM_CHILD_NB]; |
| ofnode child; |
| int ret; |
| u8 child_access_granted = 0; |
| bool child_access[OMM_CHILD_NB]; |
| |
| /* check child's access */ |
| for (child = ofnode_first_subnode(dev_ofnode(dev)); |
| ofnode_valid(child); |
| child = ofnode_next_subnode(child)) { |
| if (plat->nb_child > OMM_CHILD_NB) { |
| dev_err(dev, "Bad DT, found too much children\n"); |
| return -E2BIG; |
| } |
| |
| if (!ofnode_device_is_compatible(child, "st,stm32mp25-ospi")) |
| return -EINVAL; |
| |
| ret = stm32_rifsc_grant_access(child); |
| if (ret < 0 && ret != -EACCES) |
| return ret; |
| |
| child_access[plat->nb_child] = false; |
| if (!ret) { |
| child_access_granted++; |
| child_access[plat->nb_child] = true; |
| } |
| |
| child_list[plat->nb_child] = child; |
| plat->nb_child++; |
| } |
| |
| if (plat->nb_child != OMM_CHILD_NB) |
| return -EINVAL; |
| |
| /* check if OMM's resource access is granted */ |
| ret = stm32_rifsc_grant_access(dev_ofnode(dev)); |
| if (ret < 0 && ret != -EACCES) |
| goto end; |
| |
| /* All child's access are granted ? */ |
| if (!ret && child_access_granted == plat->nb_child) { |
| ret = stm32_omm_configure(dev); |
| if (ret) |
| goto end; |
| } 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); |
| } |
| |
| end: |
| stm32_omm_release_childs(child_list, plat->nb_child); |
| stm32_rifsc_release_access(dev_ofnode(dev)); |
| |
| return ret; |
| } |
| |
| static int stm32_omm_of_to_plat(struct udevice *dev) |
| { |
| struct stm32_omm_plat *plat = dev_get_plat(dev); |
| static const char * const clocks_name[] = {"omm", "ospi1", "ospi2"}; |
| static const char * const mm_name[] = { "ospi1", "ospi2" }; |
| static const char * const resets_name[] = {"omm", "ospi1", "ospi2"}; |
| struct resource res, res1, mm_res; |
| struct ofnode_phandle_args args; |
| struct udevice *child; |
| unsigned long clk_rate; |
| struct clk child_clk; |
| int ret, idx; |
| u8 i; |
| |
| plat->regs_base = dev_read_addr(dev); |
| if (plat->regs_base == FDT_ADDR_T_NONE) |
| return -EINVAL; |
| |
| ret = dev_read_resource_byname(dev, "memory_map", &mm_res); |
| if (ret) { |
| dev_err(dev, "can't get omm_mm mmap resource(ret = %d)!\n", ret); |
| return ret; |
| } |
| |
| for (i = 0; i < OMM_CLK_NB; i++) { |
| ret = clk_get_by_name(dev, clocks_name[i], &plat->clk[i]); |
| if (ret < 0) { |
| dev_err(dev, "Can't find I/O manager clock %s\n", clocks_name[i]); |
| return ret; |
| } |
| |
| ret = reset_get_by_name(dev, resets_name[i], &plat->reset_ctl[i]); |
| if (ret < 0) { |
| dev_err(dev, "Can't find I/O manager reset %s\n", resets_name[i]); |
| return ret; |
| } |
| } |
| |
| /* parse children's clock */ |
| plat->clk_rate_max = 0; |
| device_foreach_child(child, dev) { |
| ret = clk_get_by_index(child, 0, &child_clk); |
| if (ret) { |
| dev_err(dev, "Failed to get clock for %s\n", |
| dev_read_name(child)); |
| return ret; |
| } |
| |
| clk_rate = clk_get_rate(&child_clk); |
| if (!clk_rate) { |
| dev_err(dev, "Invalid clock rate\n"); |
| return -EINVAL; |
| } |
| |
| if (clk_rate > plat->clk_rate_max) |
| plat->clk_rate_max = clk_rate; |
| } |
| |
| plat->mux = dev_read_u32_default(dev, "st,omm-mux", 0); |
| plat->req2ack = dev_read_u32_default(dev, "st,omm-req2ack-ns", 0); |
| plat->cssel_ovr = dev_read_u32_default(dev, "st,omm-cssel-ovr", 0xff); |
| plat->mm_ospi2_size = 0; |
| |
| for (i = 0; i < 2; i++) { |
| idx = dev_read_stringlist_search(dev, "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; |
| |
| dev_read_phandle_with_args(dev, "memory-region", NULL, 0, idx, |
| &args); |
| ret = ofnode_read_resource(args.node, 0, &res); |
| if (ret) { |
| dev_err(dev, "unable to resolve memory region\n"); |
| return ret; |
| } |
| |
| /* check that memory region fits inside OMM memory map area */ |
| if (!resource_contains(&mm_res, &res)) { |
| dev_err(dev, "%s doesn't fit inside OMM memory map area\n", |
| mm_name[i]); |
| dev_err(dev, "[0x%llx-0x%llx] doesn't fit inside [0x%llx-0x%llx]\n", |
| res.start, res.end, |
| mm_res.start, mm_res.end); |
| |
| return -EFAULT; |
| } |
| |
| if (i == 1) { |
| plat->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, "[0x%llx-0x%llx] overlaps [0x%llx-0x%llx]\n", |
| res1.start, res1.end, res.start, res.end); |
| |
| return -EFAULT; |
| } |
| } |
| } |
| |
| plat->syscfg_regmap = syscon_regmap_lookup_by_phandle(dev, "st,syscfg-amcr"); |
| if (IS_ERR(plat->syscfg_regmap)) { |
| dev_err(dev, "Failed to get st,syscfg-amcr property\n"); |
| ret = PTR_ERR(plat->syscfg_regmap); |
| return ret; |
| } |
| |
| ret = dev_read_u32_index(dev, "st,syscfg-amcr", 1, &plat->amcr_base); |
| if (ret) { |
| dev_err(dev, "Failed to get st,syscfg-amcr base\n"); |
| return ret; |
| } |
| |
| ret = dev_read_u32_index(dev, "st,syscfg-amcr", 2, &plat->amcr_mask); |
| if (ret) { |
| dev_err(dev, "Failed to get st,syscfg-amcr mask\n"); |
| return ret; |
| } |
| |
| return 0; |
| }; |
| |
| static int stm32_omm_bind(struct udevice *dev) |
| { |
| int ret = 0, err = 0; |
| ofnode node; |
| |
| for (node = ofnode_first_subnode(dev_ofnode(dev)); |
| ofnode_valid(node); |
| node = ofnode_next_subnode(node)) { |
| const char *node_name = ofnode_get_name(node); |
| |
| if (!ofnode_is_enabled(node) || stm32_rifsc_grant_access(node)) { |
| dev_dbg(dev, "%s failed to bind\n", node_name); |
| continue; |
| } |
| |
| err = lists_bind_fdt(dev, node, NULL, NULL, |
| gd->flags & GD_FLG_RELOC ? false : true); |
| if (err && !ret) { |
| ret = err; |
| dev_dbg(dev, "%s: ret=%d\n", node_name, ret); |
| } |
| } |
| |
| if (ret) |
| dev_dbg(dev, "Some drivers failed to bind\n"); |
| |
| return ret; |
| } |
| |
| static const struct udevice_id stm32_omm_ids[] = { |
| { .compatible = "st,stm32mp25-omm", }, |
| {}, |
| }; |
| |
| U_BOOT_DRIVER(stm32_omm) = { |
| .name = "stm32_omm", |
| .id = UCLASS_NOP, |
| .probe = stm32_omm_probe, |
| .of_match = stm32_omm_ids, |
| .of_to_plat = stm32_omm_of_to_plat, |
| .plat_auto = sizeof(struct stm32_omm_plat), |
| .bind = stm32_omm_bind, |
| }; |