// SPDX-License-Identifier: GPL-2.0-only /* * Voltage regulators coupler for MediaTek SoCs * * Copyright (C) 2022 Collabora, Ltd. * Author: AngeloGioacchino Del Regno */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #define to_mediatek_coupler(x) container_of(x, struct mediatek_regulator_coupler, coupler) struct mediatek_regulator_coupler { struct regulator_coupler coupler; struct regulator_dev *vsram_rdev; }; /* * We currently support only couples of not more than two vregs and * modify the vsram voltage only when changing voltage of vgpu. * * This function is limited to the GPU<->SRAM voltages relationships. */ static int mediatek_regulator_balance_voltage(struct regulator_coupler *coupler, struct regulator_dev *rdev, suspend_state_t state) { struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler); int max_spread = rdev->constraints->max_spread[0]; int vsram_min_uV = mrc->vsram_rdev->constraints->min_uV; int vsram_max_uV = mrc->vsram_rdev->constraints->max_uV; int vsram_target_min_uV, vsram_target_max_uV; int min_uV = 0; int max_uV = INT_MAX; int ret; /* * If the target device is on, setting the SRAM voltage directly * is not supported as it scales through its coupled supply voltage. * * An exception is made in case the use_count is zero: this means * that this is the first time we power up the SRAM regulator, which * implies that the target device has yet to perform initialization * and setting a voltage at that time is harmless. */ if (rdev == mrc->vsram_rdev) { if (rdev->use_count == 0) return regulator_do_balance_voltage(rdev, state, true); return -EPERM; } ret = regulator_check_consumers(rdev, &min_uV, &max_uV, state); if (ret < 0) return ret; if (min_uV == 0) { ret = regulator_get_voltage_rdev(rdev); if (ret < 0) return ret; min_uV = ret; } ret = regulator_check_voltage(rdev, &min_uV, &max_uV); if (ret < 0) return ret; /* * If we're asked to set a voltage less than VSRAM min_uV, set * the minimum allowed voltage on VSRAM, as in this case it is * safe to ignore the max_spread parameter. */ vsram_target_min_uV = max(vsram_min_uV, min_uV + max_spread); vsram_target_max_uV = min(vsram_max_uV, vsram_target_min_uV + max_spread); /* Make sure we're not out of range */ vsram_target_min_uV = min(vsram_target_min_uV, vsram_max_uV); pr_debug("Setting voltage %d-%duV on %s (minuV %d)\n", vsram_target_min_uV, vsram_target_max_uV, rdev_get_name(mrc->vsram_rdev), min_uV); ret = regulator_set_voltage_rdev(mrc->vsram_rdev, vsram_target_min_uV, vsram_target_max_uV, state); if (ret) return ret; /* The sram voltage is now balanced: update the target vreg voltage */ return regulator_do_balance_voltage(rdev, state, true); } static int mediatek_regulator_attach(struct regulator_coupler *coupler, struct regulator_dev *rdev) { struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler); const char *rdev_name = rdev_get_name(rdev); /* * If we're getting a coupling of more than two regulators here and * this means that this is surely not a GPU<->SRAM couple: in that * case, we may want to use another coupler implementation, if any, * or the generic one: the regulator core will keep walking through * the list of couplers when any .attach_regulator() cb returns 1. */ if (rdev->coupling_desc.n_coupled > 2) return 1; if (strstr(rdev_name, "sram")) { if (mrc->vsram_rdev) return -EINVAL; mrc->vsram_rdev = rdev; } else if (!strstr(rdev_name, "vgpu") && !strstr(rdev_name, "Vgpu")) { return 1; } return 0; } static int mediatek_regulator_detach(struct regulator_coupler *coupler, struct regulator_dev *rdev) { struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler); if (rdev == mrc->vsram_rdev) mrc->vsram_rdev = NULL; return 0; } static struct mediatek_regulator_coupler mediatek_coupler = { .coupler = { .attach_regulator = mediatek_regulator_attach, .detach_regulator = mediatek_regulator_detach, .balance_voltage = mediatek_regulator_balance_voltage, }, }; static int mediatek_regulator_coupler_init(void) { if (!of_machine_is_compatible("mediatek,mt8183") && !of_machine_is_compatible("mediatek,mt8186") && !of_machine_is_compatible("mediatek,mt8192")) return 0; return regulator_coupler_register(&mediatek_coupler.coupler); } arch_initcall(mediatek_regulator_coupler_init); MODULE_AUTHOR("AngeloGioacchino Del Regno "); MODULE_DESCRIPTION("MediaTek Regulator Coupler driver"); MODULE_LICENSE("GPL");