// SPDX-License-Identifier: GPL-2.0 // // System Control and Management Interface (SCMI) based regulator driver // // Copyright (C) 2020-2021 ARM Ltd. // // Implements a regulator driver on top of the SCMI Voltage Protocol. // // The ARM SCMI Protocol aims in general to hide as much as possible all the // underlying operational details while providing an abstracted interface for // its users to operate upon: as a consequence the resulting operational // capabilities and configurability of this regulator device are much more // limited than the ones usually available on a standard physical regulator. // // The supported SCMI regulator ops are restricted to the bare minimum: // // - 'status_ops': enable/disable/is_enabled // - 'voltage_ops': get_voltage_sel/set_voltage_sel // list_voltage/map_voltage // // Each SCMI regulator instance is associated, through the means of a proper DT // entry description, to a specific SCMI Voltage Domain. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include static const struct scmi_voltage_proto_ops *voltage_ops; struct scmi_regulator { u32 id; struct scmi_device *sdev; struct scmi_protocol_handle *ph; struct regulator_dev *rdev; struct device_node *of_node; struct regulator_desc desc; struct regulator_config conf; }; struct scmi_regulator_info { int num_doms; struct scmi_regulator **sregv; }; static int scmi_reg_enable(struct regulator_dev *rdev) { struct scmi_regulator *sreg = rdev_get_drvdata(rdev); return voltage_ops->config_set(sreg->ph, sreg->id, SCMI_VOLTAGE_ARCH_STATE_ON); } static int scmi_reg_disable(struct regulator_dev *rdev) { struct scmi_regulator *sreg = rdev_get_drvdata(rdev); return voltage_ops->config_set(sreg->ph, sreg->id, SCMI_VOLTAGE_ARCH_STATE_OFF); } static int scmi_reg_is_enabled(struct regulator_dev *rdev) { int ret; u32 config; struct scmi_regulator *sreg = rdev_get_drvdata(rdev); ret = voltage_ops->config_get(sreg->ph, sreg->id, &config); if (ret) { dev_err(&sreg->sdev->dev, "Error %d reading regulator %s status.\n", ret, sreg->desc.name); return ret; } return config & SCMI_VOLTAGE_ARCH_STATE_ON; } static int scmi_reg_get_voltage_sel(struct regulator_dev *rdev) { int ret; s32 volt_uV; struct scmi_regulator *sreg = rdev_get_drvdata(rdev); ret = voltage_ops->level_get(sreg->ph, sreg->id, &volt_uV); if (ret) return ret; return sreg->desc.ops->map_voltage(rdev, volt_uV, volt_uV); } static int scmi_reg_set_voltage_sel(struct regulator_dev *rdev, unsigned int selector) { s32 volt_uV; struct scmi_regulator *sreg = rdev_get_drvdata(rdev); volt_uV = sreg->desc.ops->list_voltage(rdev, selector); if (volt_uV <= 0) return -EINVAL; return voltage_ops->level_set(sreg->ph, sreg->id, 0x0, volt_uV); } static const struct regulator_ops scmi_reg_fixed_ops = { .enable = scmi_reg_enable, .disable = scmi_reg_disable, .is_enabled = scmi_reg_is_enabled, }; static const struct regulator_ops scmi_reg_linear_ops = { .enable = scmi_reg_enable, .disable = scmi_reg_disable, .is_enabled = scmi_reg_is_enabled, .get_voltage_sel = scmi_reg_get_voltage_sel, .set_voltage_sel = scmi_reg_set_voltage_sel, .list_voltage = regulator_list_voltage_linear, .map_voltage = regulator_map_voltage_linear, }; static const struct regulator_ops scmi_reg_discrete_ops = { .enable = scmi_reg_enable, .disable = scmi_reg_disable, .is_enabled = scmi_reg_is_enabled, .get_voltage_sel = scmi_reg_get_voltage_sel, .set_voltage_sel = scmi_reg_set_voltage_sel, .list_voltage = regulator_list_voltage_table, .map_voltage = regulator_map_voltage_iterate, }; static int scmi_config_linear_regulator_mappings(struct scmi_regulator *sreg, const struct scmi_voltage_info *vinfo) { s32 delta_uV; /* * Note that SCMI voltage domains describable by linear ranges * (segments) {low, high, step} are guaranteed to come in one single * triplet by the SCMI Voltage Domain protocol support itself. */ delta_uV = (vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_HIGH] - vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]); /* Rule out buggy negative-intervals answers from fw */ if (delta_uV < 0) { dev_err(&sreg->sdev->dev, "Invalid volt-range %d-%duV for domain %d\n", vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW], vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_HIGH], sreg->id); return -EINVAL; } if (!delta_uV) { /* Just one fixed voltage exposed by SCMI */ sreg->desc.fixed_uV = vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; sreg->desc.n_voltages = 1; sreg->desc.ops = &scmi_reg_fixed_ops; } else { /* One simple linear mapping. */ sreg->desc.min_uV = vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; sreg->desc.uV_step = vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_STEP]; sreg->desc.linear_min_sel = 0; sreg->desc.n_voltages = (delta_uV / sreg->desc.uV_step) + 1; sreg->desc.ops = &scmi_reg_linear_ops; } return 0; } static int scmi_config_discrete_regulator_mappings(struct scmi_regulator *sreg, const struct scmi_voltage_info *vinfo) { /* Discrete non linear levels are mapped to volt_table */ sreg->desc.n_voltages = vinfo->num_levels; if (sreg->desc.n_voltages > 1) { sreg->desc.volt_table = (const unsigned int *)vinfo->levels_uv; sreg->desc.ops = &scmi_reg_discrete_ops; } else { sreg->desc.fixed_uV = vinfo->levels_uv[0]; sreg->desc.ops = &scmi_reg_fixed_ops; } return 0; } static int scmi_regulator_common_init(struct scmi_regulator *sreg) { int ret; struct device *dev = &sreg->sdev->dev; const struct scmi_voltage_info *vinfo; vinfo = voltage_ops->info_get(sreg->ph, sreg->id); if (!vinfo) { dev_warn(dev, "Failure to get voltage domain %d\n", sreg->id); return -ENODEV; } /* * Regulator framework does not fully support negative voltages * so we discard any voltage domain reported as supporting negative * voltages: as a consequence each levels_uv entry is guaranteed to * be non-negative from here on. */ if (vinfo->negative_volts_allowed) { dev_warn(dev, "Negative voltages NOT supported...skip %s\n", sreg->of_node->full_name); return -EOPNOTSUPP; } sreg->desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s", vinfo->name); if (!sreg->desc.name) return -ENOMEM; sreg->desc.id = sreg->id; sreg->desc.type = REGULATOR_VOLTAGE; sreg->desc.owner = THIS_MODULE; sreg->desc.of_match_full_name = true; sreg->desc.of_match = sreg->of_node->full_name; sreg->desc.regulators_node = "regulators"; if (vinfo->segmented) ret = scmi_config_linear_regulator_mappings(sreg, vinfo); else ret = scmi_config_discrete_regulator_mappings(sreg, vinfo); if (ret) return ret; /* * Using the scmi device here to have DT searched from Voltage * protocol node down. */ sreg->conf.dev = dev; /* Store for later retrieval via rdev_get_drvdata() */ sreg->conf.driver_data = sreg; return 0; } static int process_scmi_regulator_of_node(struct scmi_device *sdev, struct scmi_protocol_handle *ph, struct device_node *np, struct scmi_regulator_info *rinfo) { u32 dom, ret; ret = of_property_read_u32(np, "reg", &dom); if (ret) return ret; if (dom >= rinfo->num_doms) return -ENODEV; if (rinfo->sregv[dom]) { dev_err(&sdev->dev, "SCMI Voltage Domain %d already in use. Skipping: %s\n", dom, np->full_name); return -EINVAL; } rinfo->sregv[dom] = devm_kzalloc(&sdev->dev, sizeof(struct scmi_regulator), GFP_KERNEL); if (!rinfo->sregv[dom]) return -ENOMEM; rinfo->sregv[dom]->id = dom; rinfo->sregv[dom]->sdev = sdev; rinfo->sregv[dom]->ph = ph; /* get hold of good nodes */ of_node_get(np); rinfo->sregv[dom]->of_node = np; dev_dbg(&sdev->dev, "Found SCMI Regulator entry -- OF node [%d] -> %s\n", dom, np->full_name); return 0; } static int scmi_regulator_probe(struct scmi_device *sdev) { int d, ret, num_doms; struct device_node *np, *child; const struct scmi_handle *handle = sdev->handle; struct scmi_regulator_info *rinfo; struct scmi_protocol_handle *ph; if (!handle) return -ENODEV; voltage_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_VOLTAGE, &ph); if (IS_ERR(voltage_ops)) return PTR_ERR(voltage_ops); num_doms = voltage_ops->num_domains_get(ph); if (!num_doms) return 0; if (num_doms < 0) { dev_err(&sdev->dev, "failed to get voltage domains - err:%d\n", num_doms); return num_doms; } rinfo = devm_kzalloc(&sdev->dev, sizeof(*rinfo), GFP_KERNEL); if (!rinfo) return -ENOMEM; /* Allocate pointers array for all possible domains */ rinfo->sregv = devm_kcalloc(&sdev->dev, num_doms, sizeof(void *), GFP_KERNEL); if (!rinfo->sregv) return -ENOMEM; rinfo->num_doms = num_doms; /* * Start collecting into rinfo->sregv possibly good SCMI Regulators as * described by a well-formed DT entry and associated with an existing * plausible SCMI Voltage Domain number, all belonging to this SCMI * platform instance node (handle->dev->of_node). */ of_node_get(handle->dev->of_node); np = of_find_node_by_name(handle->dev->of_node, "regulators"); for_each_child_of_node(np, child) { ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo); /* abort on any mem issue */ if (ret == -ENOMEM) { of_node_put(child); return ret; } } of_node_put(np); /* * Register a regulator for each valid regulator-DT-entry that we * can successfully reach via SCMI and has a valid associated voltage * domain. */ for (d = 0; d < num_doms; d++) { struct scmi_regulator *sreg = rinfo->sregv[d]; /* Skip empty slots */ if (!sreg) continue; ret = scmi_regulator_common_init(sreg); /* Skip invalid voltage domains */ if (ret) continue; sreg->rdev = devm_regulator_register(&sdev->dev, &sreg->desc, &sreg->conf); if (IS_ERR(sreg->rdev)) { sreg->rdev = NULL; continue; } dev_info(&sdev->dev, "Regulator %s registered for domain [%d]\n", sreg->desc.name, sreg->id); } dev_set_drvdata(&sdev->dev, rinfo); return 0; } static void scmi_regulator_remove(struct scmi_device *sdev) { int d; struct scmi_regulator_info *rinfo; rinfo = dev_get_drvdata(&sdev->dev); if (!rinfo) return; for (d = 0; d < rinfo->num_doms; d++) { if (!rinfo->sregv[d]) continue; of_node_put(rinfo->sregv[d]->of_node); } } static const struct scmi_device_id scmi_regulator_id_table[] = { { SCMI_PROTOCOL_VOLTAGE, "regulator" }, { }, }; MODULE_DEVICE_TABLE(scmi, scmi_regulator_id_table); static struct scmi_driver scmi_drv = { .name = "scmi-regulator", .probe = scmi_regulator_probe, .remove = scmi_regulator_remove, .id_table = scmi_regulator_id_table, }; module_scmi_driver(scmi_drv); MODULE_AUTHOR("Cristian Marussi "); MODULE_DESCRIPTION("ARM SCMI regulator driver"); MODULE_LICENSE("GPL v2");