/* * gk20a DVFS driver * * Copyright (c) 2013, NVIDIA Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include "clk_gk20a.h" #include "dev.h" #include "gk20a.h" #include "gk20a_dvfs.h" #include "nvhost_scale.h" #define KHZ 1000 #define GPU_SUPPLY_NAME "vddgpu" #define DEFAULT_STEP_UV 10000 /* * TODO: no thermal support yet, so just use the highest voltage in all * thermal ranges. */ #define SAFE_THERMAL_INDEX 0 static struct gk20a_dvfs gpu_dvfs; static struct gk20a_cvb_info gpu_cvb_info_table[] = { { .speedo_id = 0, .process_id = -1, .max_mv = 1200, .min_mv = 800, .speedo_scale = 100, .thermal_scale = 10, .voltage_scale = 1000, .cvb_table = { { 72, { 1209886, -36468, 515, 417, -13123, 203}, }, { 108, { 1130804, -27659, 296, 298, -10834, 221}, }, { 180, { 1162871, -27110, 247, 238, -10681, 268}, }, { 252, { 1220458, -28654, 247, 179, -10376, 298}, }, { 324, { 1280953, -30204, 247, 119, -9766, 304}, }, { 396, { 1344547, -31777, 247, 119, -8545, 292}, }, { 468, { 1420168, -34227, 269, 60, -7172, 256}, }, { 540, { 1490757, -35955, 274, 60, -5188, 197}, }, { 612, { 1599112, -42583, 398, 0, -1831, 119}, }, { 648, { 1366986, -16459, -274, 0, -3204, 72}, }, { 0, { }, }, }, .vmin_trips_table = { 20, }, .therm_floors_table = { 900, }, .vts_trips_table = { -10, 10, 30, 50, 70, }, }, { .speedo_id = 1, .process_id = -1, .max_mv = 1200, .min_mv = 800, .speedo_scale = 100, .thermal_scale = 10, .voltage_scale = 1000, .cvb_table = { { 72, { 1209886, -36468, 515, 417, -13123, 203}, }, { 108, { 1130804, -27659, 296, 298, -10834, 221}, }, { 180, { 1162871, -27110, 247, 238, -10681, 268}, }, { 252, { 1220458, -28654, 247, 179, -10376, 298}, }, { 324, { 1280953, -30204, 247, 119, -9766, 304}, }, { 396, { 1344547, -31777, 247, 119, -8545, 292}, }, { 468, { 1420168, -34227, 269, 60, -7172, 256}, }, { 540, { 1490757, -35955, 274, 60, -5188, 197}, }, { 612, { 1599112, -42583, 398, 0, -1831, 119}, }, { 648, { 1366986, -16459, -274, 0, -3204, 72}, }, { 684, { 1391884, -17078, -274, -60, -1526, 30}, }, { 708, { 1415522, -17497, -274, -60, -458, 0}, }, { 756, { 1464061, -18331, -274, -119, 1831, -72}, }, { 804, { 1524225, -20064, -254, -119, 4272, -155}, }, { 852, { 1608418, -21643, -269, 0, 763, -48}, }, { 0, { }, }, }, .vmin_trips_table = { 20, }, .therm_floors_table = { 900, }, .vts_trips_table = { -10, 10, 30, 50, 70, }, } }; static int gpu_millivolts[MAX_THERMAL_RANGES][MAX_DVFS_FREQS]; static struct tegra_cooling_device gpu_vmin_cdev = { .cdev_type = "gpu_cold", }; static struct tegra_cooling_device gpu_vts_cdev = { .cdev_type = "gpu_scaling", }; static struct gk20a_dvfs_rail gpu_rail = { .supply_name = GPU_SUPPLY_NAME, .max_uv = 1350000, .min_uv = 700000, .vts_cdev = &gpu_vts_cdev, .vmin_cdev = &gpu_vmin_cdev, }; static DEFINE_MUTEX(gk20a_dvfs_lock); static int gk20a_dvfs_init_pmic(struct platform_device *pdev, struct gk20a *g); /** * Validate thermal dvfs settings: * - trip-points are montonically increasing * - voltages in any temperature range are montonically increasing with * frequency (can go up/down across ranges at iso frequency) * - voltage for any frequency/thermal range combination must be within * rail minimum/maximum limits */ static int init_thermal_dvfs_trips(struct platform_device *pdev, int *therm_trips_table, struct gk20a_dvfs_rail *rail) { int i; if (!rail->vts_cdev) { WARN(1, "%s: missing thermal dvfs cooling device\n", rail->supply_name); return -ENOENT; } if (!rail->vts_cdev->trip_temperatures) { int *trips_t; trips_t = devm_kzalloc(&pdev->dev, sizeof(int) * MAX_THERMAL_LIMITS, GFP_KERNEL); if (!trips_t) { dev_err(&pdev->dev, "Failed to kzalloc trips table\n"); return -ENOMEM; } rail->vts_cdev->trip_temperatures = trips_t; } for (i = 0; i < MAX_THERMAL_LIMITS - 1; i++) { if (therm_trips_table[i] >= therm_trips_table[i+1]) break; } rail->vts_cdev->trip_temperatures_num = i + 1; memcpy(rail->vts_cdev->trip_temperatures, therm_trips_table, sizeof(int) * MAX_THERMAL_LIMITS); return 0; } /** * cvb_mv = ((c2 * speedo / s_scale + c1) * speedo / s_scale + c0) / v_scale */ static inline int get_cvb_voltage(int speedo, int s_scale, struct gk20a_cvb_parameters *cvb) { /* apply only speedo scale: output mv = cvb_mv * v_scale */ int mv; mv = DIV_ROUND_CLOSEST(cvb->c2 * speedo, s_scale); mv = DIV_ROUND_CLOSEST((mv + cvb->c1) * speedo, s_scale) + cvb->c0; return mv; } /** * cvb_t_mv = * ((c3 * speedo / s_scale + c4 + c5 * T / t_scale) * T / t_scale) / v_scale */ static inline int get_cvb_t_voltage(int speedo, int s_scale, int t, int t_scale, struct gk20a_cvb_parameters *cvb) { /* apply speedo & temperature scales: output mv = cvb_t_mv * v_scale */ int mv; mv = DIV_ROUND_CLOSEST(cvb->c3 * speedo, s_scale) + cvb->c4 + DIV_ROUND_CLOSEST(cvb->c5 * t, t_scale); mv = DIV_ROUND_CLOSEST(mv * t, t_scale); return mv; } static int round_cvb_voltage(int mv, int v_scale, int step_uv) { /* combined: apply voltage scale and round to cvb alignment step */ int uv = mv * 1000; int step = (step_uv ? : 1000) * v_scale; uv = DIV_ROUND_UP(uv, step) * step_uv; return uv / 1000; } /** * round_voltage - round the max and min voltage by rail step uv * * @uv: microvolts to round * @step_uv: step uv for the rail */ static int round_voltage(int mv, int step_uv, bool up) { int uv = mv * 1000; BUG_ON(!step_uv); uv = (uv + (up ? step_uv - 1 : 0)) / step_uv; return uv * step_uv / 1000; } static int validate_thermal_dvfs_voltages( int *therm_voltages, int freqs_num, int ranges_num, struct gk20a_dvfs *dvfs, struct gk20a_dvfs_rail *rail) { int *millivolts; int freq_idx, therm_idx; for (therm_idx = 0; therm_idx < ranges_num; therm_idx++) { millivolts = therm_voltages + therm_idx * MAX_DVFS_FREQS; for (freq_idx = 0; freq_idx < freqs_num; freq_idx++) { int mv = millivolts[freq_idx]; if ((mv > rail->max_uv / 1000) || (mv < rail->min_uv / 1000) || (freq_idx && (mv < millivolts[freq_idx - 1]))) { WARN(1, "gk20a_dvfs: invalid thermal dvfs entry %d(%d, %d)\n", mv, freq_idx, therm_idx); return -EINVAL; } } } dvfs->therm_dvfs = true; return 0; } static bool match_gpu_cvb_one(struct gk20a_cvb_info *info, int speedo_id, int process_id) { if ((info->process_id != -1 && info->process_id != process_id) || (info->speedo_id != -1 && info->speedo_id != speedo_id)) { pr_info("tegra124_dvfs: rejected gpu cvb speedo %d, process %d", info->speedo_id, info->process_id); return false; } return true; } static int gk20a_dvfs_rail_apply_limits(struct gk20a_dvfs_rail *rail, unsigned long rate, int millivolts) { int target_mv, min_mv, max_mv; int idx; int *dvfs_mv; unsigned long target_rate; min_mv = rail->min_uv / 1000; max_mv = rail->max_uv / 1000; target_mv = millivolts; if (rate) target_rate = rate; else target_rate = gpu_dvfs.cur_rate; if (rail->therm_mv_floors) { idx = rail->therm_floor_idx; if (idx < rail->therm_mv_floors_num) min_mv = rail->therm_mv_floors[idx]; } if (gpu_dvfs.therm_dvfs) { int i; idx = rail->therm_scale_idx; dvfs_mv = gpu_dvfs.millivolts + idx * MAX_DVFS_FREQS; for (i = 0; i < gpu_dvfs.num_freqs; i++) { if (!target_rate) goto no_rate; if (target_rate <= gpu_dvfs.freqs[i]) break; } if (i == gpu_dvfs.num_freqs) i--; max_mv = dvfs_mv[i]; } no_rate: if (target_mv < min_mv) target_mv = min_mv; else if (target_mv > max_mv) target_mv = max_mv; return target_mv; } static void gk20a_dvfs_rail_update_voltage(struct gk20a_dvfs_rail *rail) { int mv; mv = gk20a_dvfs_rail_apply_limits(gpu_dvfs.rail, 0, gpu_dvfs.cur_millivolts); if (mv != gpu_dvfs.cur_millivolts) { int max_mv, ret; max_mv = gpu_dvfs.max_millivolts; ret = regulator_set_voltage(gpu_dvfs.rail->reg, mv * 1000, max_mv * 1000); if (ret) pr_warn("Failed to set voltage to %duV\n", mv * 1000); else gpu_dvfs.cur_millivolts = mv; } } #ifdef CONFIG_THERMAL static int gk20a_dvfs_rail_get_vmin_cdev_max_state( struct thermal_cooling_device *cdev, unsigned long *max_state) { struct gk20a_dvfs_rail *rail = (struct gk20a_dvfs_rail *)cdev->devdata; *max_state = rail->vmin_cdev->trip_temperatures_num; return 0; } static int gk20a_dvfs_rail_get_vmin_cdev_cur_state( struct thermal_cooling_device *cdev, unsigned long *cur_state) { struct gk20a_dvfs_rail *rail = (struct gk20a_dvfs_rail *)cdev->devdata; *cur_state = rail->therm_floor_idx; return 0; } static int gk20a_dvfs_rail_set_vmin_cdev_state( struct thermal_cooling_device *cdev, unsigned long cur_state) { struct gk20a_dvfs_rail *rail = (struct gk20a_dvfs_rail *)cdev->devdata; if (IS_ERR(gpu_dvfs.rail->reg)) return -EINVAL; if (rail->therm_floor_idx == cur_state) return 0; rail->therm_floor_idx = cur_state; gk20a_dvfs_rail_update_voltage(rail); return 0; } static struct thermal_cooling_device_ops gk20a_dvfs_vmin_cooling_ops = { .get_max_state = gk20a_dvfs_rail_get_vmin_cdev_max_state, .get_cur_state = gk20a_dvfs_rail_get_vmin_cdev_cur_state, .set_cur_state = gk20a_dvfs_rail_set_vmin_cdev_state, }; static void gk20a_dvfs_rail_register_vmin_cdev(struct gk20a_dvfs_rail *rail) { if (!rail->vmin_cdev) return; rail->therm_floor_idx = 0; /* just report error - initialized for cold temperature, anyway */ if (IS_ERR(thermal_cooling_device_register( rail->vmin_cdev->cdev_type, (void *)rail, &gk20a_dvfs_vmin_cooling_ops))) pr_err("gk20a cooling device %s failed to register\n", rail->vmin_cdev->cdev_type); } static int gk20a_dvfs_rail_get_vts_cdev_max_state( struct thermal_cooling_device *cdev, unsigned long *max_state) { struct gk20a_dvfs_rail *rail = (struct gk20a_dvfs_rail *)cdev->devdata; *max_state = rail->vts_cdev->trip_temperatures_num; return 0; } static int gk20a_dvfs_rail_get_vts_cdev_cur_state( struct thermal_cooling_device *cdev, unsigned long *cur_state) { struct gk20a_dvfs_rail *rail = (struct gk20a_dvfs_rail *)cdev->devdata; *cur_state = rail->therm_scale_idx; return 0; } static int gk20a_dvfs_rail_set_vts_cdev_state( struct thermal_cooling_device *cdev, unsigned long cur_state) { struct gk20a_dvfs_rail *rail = (struct gk20a_dvfs_rail *)cdev->devdata; if (IS_ERR(gpu_dvfs.rail->reg)) return -EINVAL; if (rail->therm_scale_idx == cur_state) return 0; rail->therm_scale_idx = cur_state; gk20a_dvfs_rail_update_voltage(rail); return 0; } static struct thermal_cooling_device_ops gk20a_dvfs_vts_cooling_ops = { .get_max_state = gk20a_dvfs_rail_get_vts_cdev_max_state, .get_cur_state = gk20a_dvfs_rail_get_vts_cdev_cur_state, .set_cur_state = gk20a_dvfs_rail_set_vts_cdev_state, }; static void gk20a_dvfs_rail_register_vts_cdev(struct gk20a_dvfs_rail *rail) { if (!rail->vts_cdev) return; rail->therm_scale_idx = 0; /* just report error - initialized for cold temperature, anyway */ if (IS_ERR(thermal_cooling_device_register( rail->vts_cdev->cdev_type, (void *)rail, &gk20a_dvfs_vts_cooling_ops))) pr_err("gk20a cooling device %s failed to register\n", rail->vts_cdev->cdev_type); } #else static inline void gk20a_dvfs_rail_register_vmin_cdev(struct dvfs_rail *rail) { return; } static inline void gk20a_dvfs_rail_register_vts_cdev(struct dvfs_rail *rail) { return; } #endif struct tegra_cooling_device *tegra_get_gpu_vmin_cdev(void) { if (!gpu_rail.vmin_cdev) return NULL; if ((!gpu_rail.vmin_cdev->trip_temperatures) || (!gpu_rail.reg)) return ERR_PTR(-EPROBE_DEFER); return gpu_rail.vmin_cdev; } struct tegra_cooling_device *tegra_get_gpu_vts_cdev(void) { if (!gpu_rail.vts_cdev) return NULL; if ((!gpu_rail.vts_cdev->trip_temperatures) || (!gpu_rail.reg)) return ERR_PTR(-EPROBE_DEFER); return gpu_rail.vts_cdev; } static int get_gk20a_thermal_profile_size(int *trips_table, int *limits_table, struct gk20a_dvfs_rail *rail) { int i; for (i = 0; i < MAX_THERMAL_LIMITS - 1; i++) { if (!limits_table[i + 1]) break; if ((trips_table[i] >= trips_table[i + 1]) || (limits_table[i] < limits_table[i + 1])) { pr_warn("%s: not ordered profile\n", rail->supply_name); return -EINVAL; } } if ((limits_table[i] * 1000) < rail->min_uv) { pr_warn("%s: thermal profile below Vmin\n", rail->supply_name); return -EINVAL; } if ((limits_table[0] * 100) > rail->max_uv) { pr_warn("%s: thermal profile above Vmax\n", rail->supply_name); return -EINVAL; } return i + 1; } static void gk20a_dvfs_rail_init_vmin_thermal_profile( int *therm_trips_table, int *therm_floors_table, struct gk20a_dvfs_rail *rail) { int i = get_gk20a_thermal_profile_size(therm_trips_table, therm_floors_table, rail); if (i <= 0) { rail->vmin_cdev = NULL; pr_warn("%s: invalid Vmin thermal profile\n", rail->supply_name); return; } /* Install validated thermal floors */ rail->therm_mv_floors = therm_floors_table; rail->therm_mv_floors_num = i; /* Setup trip-points if applicable */ if (rail->vmin_cdev) { rail->vmin_cdev->trip_temperatures_num = i; rail->vmin_cdev->trip_temperatures = therm_trips_table; } } /** * set_gpu_dvfs_data - setup the global dvfs instance by cvb and rail info * * @pdev: the gk20a platform device instance * @info: cvb information with the dedicated speedo/process id * @dvfs: the global dvfs instance * * The caller must hold @gk20a_dvfs_lock. */ static int set_gpu_dvfs_data(struct platform_device *pdev, struct gk20a_cvb_info *info, struct gk20a_dvfs *dvfs) { int i, j, thermal_ranges, mv; struct gk20a_cvb_table *table = NULL; int speedo = tegra_get_gpu_speedo_value(); struct gk20a_dvfs_rail *rail = dvfs->rail; info->max_mv = round_voltage(info->max_mv, rail->step_uv, false); info->min_mv = round_voltage(info->min_mv, rail->step_uv, true); if (info->min_mv < rail->min_uv / 1000) { WARN(1, "the rounded voltage is below the minimum\n"); return -ENOENT; } /* * Init thermal trips, find number of thermal ranges; note that the * first trip-point is used for voltage calculations within the lowest * range, but should not be actually set. Hence, at least 2 trip-points * must be specified. */ if (init_thermal_dvfs_trips(pdev, info->vts_trips_table, rail)) return -ENOENT; thermal_ranges = rail->vts_cdev->trip_temperatures_num; rail->vts_cdev->trip_temperatures_num--; if (thermal_ranges < 2) WARN(1, "gk20a_dvfs: %d gpu trip: thermal dvfs is broken\n", thermal_ranges); /* * Use CVB table to fill in gpu dvfs frequencies and voltages. Each * CVB entry specifies gpu frequency and CVB coefficients to calculate * the respective voltage. */ for (i = 0; i < MAX_DVFS_FREQS; i++) { table = &info->cvb_table[i]; if (!table->freq) break; mv = get_cvb_voltage(speedo, info->speedo_scale, &table->cvb_param); for (j = 0; j < thermal_ranges; j++) { int mvj = mv; int t = rail->vts_cdev->trip_temperatures[j]; /* get thermal offset for this trip-point */ mvj += get_cvb_t_voltage(speedo, info->speedo_scale, t, info->thermal_scale, &table->cvb_param); mvj = round_cvb_voltage(mvj, info->voltage_scale, rail->step_uv); /* clip to minimum, abort if above maximum */ mvj = max(mvj, info->min_mv); if (mvj > info->max_mv) break; /* update voltage for adjacent ranges bounded by this trip-point (cvb & dvfs are transpose matrices) */ gpu_millivolts[j][i] = mvj; if (j && (gpu_millivolts[j-1][i] < mvj)) gpu_millivolts[j-1][i] = mvj; } /* Make sure all voltages for this frequency are below max */ if (j < thermal_ranges) break; /* fill in gpu dvfs tables */ dvfs->freqs[i] = table->freq; } /* * Table must not be empty, must have at least one entry in range, and * must specify monotonically increasing voltage on frequency dependency * in each temperature range. */ if (!i || validate_thermal_dvfs_voltages(&gpu_millivolts[0][0], i, thermal_ranges, dvfs, rail)) { WARN(1, "gk20a_dvfs: invalid gpu dvfs table\n"); return -ENOENT; } dvfs->millivolts = &gpu_millivolts[0][0]; /* Shift out the 1st trip-point */ for (j = 1; j < thermal_ranges; j++) rail->vts_cdev->trip_temperatures[j - 1] = rail->vts_cdev->trip_temperatures[j]; /* dvfs tables are successfully populated - fill in the gpu dvfs */ dvfs->num_freqs = i; dvfs->speedo_id = info->speedo_id; dvfs->process_id = info->process_id; dvfs->freqs_mult = KHZ; dvfs->rail->nominal_uv = info->max_mv; dvfs->max_millivolts = info->max_mv; gk20a_dvfs_rail_init_vmin_thermal_profile(info->vmin_trips_table, info->therm_floors_table, &gpu_rail); return 0; } /* * Public interfaces */ /** * gk20a_dvfs_adjust_voltage - adjust voltage according to the dvfs table * * @g: the gk20a instance * @rate: the target rate in MHz */ int gk20a_dvfs_adjust_voltage(struct gk20a *g, unsigned long rate) { int index; int uv, mv; int ret; if (!g || !gpu_dvfs.rail->reg) gk20a_dvfs_init_pmic(g->dev, g); for (index = 0; index < gpu_dvfs.num_freqs; index++) if (rate <= gpu_dvfs.freqs[index]) break; if (index == gpu_dvfs.num_freqs) uv = gpu_dvfs.max_millivolts * 1000; else uv = gpu_dvfs.millivolts[index] * 1000; mv = gk20a_dvfs_rail_apply_limits(gpu_dvfs.rail, rate, (uv / 1000)); uv = mv * 1000; dev_dbg(dev_from_gk20a(g), "%s - target rate=%luMHz, volt=%duv\n", __func__, rate, uv); ret = regulator_set_voltage(gpu_dvfs.rail->reg, uv, gpu_dvfs.max_millivolts * 1000); if (!ret) { gpu_dvfs.cur_millivolts = uv / 1000; gpu_dvfs.cur_rate = rate; } WARN(ret, "failed to set voltage to %duV\n", uv); return ret; } int gk20a_dvfs_get_freqs(struct gk20a *g, unsigned long **freqs, int *num_freqs) { if (!g || !freqs || !num_freqs) return -EINVAL; *freqs = gpu_dvfs.freqs; *num_freqs = gpu_dvfs.num_freqs; return 0; } /** * gk20a_dvfs_get_min_freq - get the minimal frequency in the dvfs table * * @g: the gk20a instance */ unsigned long gk20a_dvfs_get_min_freq(struct gk20a *g) { if (!g) { WARN_ON(1); return 0; } return gpu_dvfs.freqs[0]; } /** * gk20a_dvfs_get_max_freq - get the maximal frequency in the dvfs table * * @g: the gk20a instance */ unsigned long gk20a_dvfs_get_max_freq(struct gk20a *g) { if (!g || !gpu_dvfs.num_freqs) { WARN_ON(1); return 0; } return gpu_dvfs.freqs[gpu_dvfs.num_freqs - 1]; } /** * gk20a_dvfs_init_pmic - connect to regulator and setup the global @gpu_rail * * @pdev: the gk20a platform device instance * @g: the gk20a instance * * When this function gets called, it can't be guaranteed that the regulator * has finished its initialization. The devm_regulator_get() may not return * failure becasue we have dummy regulator support. So in order to not break * the pmic initialization here, we have to give a default step microvolt. * * This function should also function even if the gk20a dvfs is disabled. */ static int gk20a_dvfs_init_pmic(struct platform_device *pdev, struct gk20a *g) { struct regulator *reg; struct device *dev; unsigned int step; struct gk20a_dvfs_rail *rail; dev = &pdev->dev; dev_dbg(dev, "%s\n", __func__); rail = gpu_dvfs.rail; if (!rail || !rail->reg) { reg = devm_regulator_get_optional(dev, gpu_rail.supply_name); if (IS_ERR(reg)) dev_warn(dev, "failed to get supply %s\n", gpu_rail.supply_name); } if (!IS_ERR(reg)) { step = regulator_get_linear_step(reg); } else { dev_warn(dev, "step uv is not supported, assign a default %dmV\n", DEFAULT_STEP_UV / 1000); step = DEFAULT_STEP_UV; /* Keep reg as NULL and may initialize it again later */ reg = NULL; } mutex_lock(&gk20a_dvfs_lock); gpu_rail.dev = dev; gpu_rail.reg = reg; gpu_rail.step_uv = step; gpu_dvfs.rail = &gpu_rail; mutex_unlock(&gk20a_dvfs_lock); gk20a_dvfs_init_dvfs_table(pdev, g); return 0; } /** * gk20a_dvfs_enable_rail - power on the rail of gk20a * * @pdev: the platform device instance of gk20a * * This function should also function even if the gk20a dvfs is disabled. */ int gk20a_dvfs_enable_rail(struct platform_device *pdev, struct gk20a *g) { int ret = 0; struct gk20a_dvfs_rail *rail = gpu_dvfs.rail; dev_dbg(&pdev->dev, "%s\n", __func__); if (!rail || !rail->reg) { ret = gk20a_dvfs_init_pmic(pdev, g); if (ret) { dev_warn(&pdev->dev, "failed to initialize pmic when enabling gpu rail\n"); return ret; } rail = gpu_dvfs.rail; } if (!regulator_is_enabled(rail->reg)) return regulator_enable(rail->reg); return 0; } /** * gk20a_dvfs_disable_rail - power off the rail of gk20a * * @pdev: the platform device instance of gk20a * * This function should also function even if the gk20a dvfs is disabled. */ int gk20a_dvfs_disable_rail(struct platform_device *pdev, struct gk20a *g) { int ret = 0; struct gk20a_dvfs_rail *rail = gpu_dvfs.rail; dev_dbg(&pdev->dev, "%s\n", __func__); if (!rail || !rail->reg) { ret = gk20a_dvfs_init_pmic(pdev, g); if (ret) { dev_warn(&pdev->dev, "failed to initialize pmic when disabling gpu rail\n"); return ret; } rail = gpu_dvfs.rail; } if (regulator_is_enabled(rail->reg)) return regulator_disable(rail->reg); return 0; } /** * gk20a_dvfs_init_dvfs_table - initialize dvfs table * * @pdev: the gk20a platform device instance * @g: the gk20a instance */ int gk20a_dvfs_init_dvfs_table(struct platform_device *pdev, struct gk20a *g) { int gpu_speedo_id, gpu_process_id; int i, ret; gpu_speedo_id = tegra_get_gpu_speedo_id(); gpu_process_id = tegra_get_gpu_process_id(); for (ret = 0, i = 0; i < ARRAY_SIZE(gpu_cvb_info_table); i++) { struct gk20a_cvb_info *info = &gpu_cvb_info_table[i]; if (match_gpu_cvb_one(info, gpu_speedo_id, gpu_process_id)) { mutex_lock(&gk20a_dvfs_lock); ret = set_gpu_dvfs_data(pdev, info, &gpu_dvfs); mutex_unlock(&gk20a_dvfs_lock); break; } } if (i == ARRAY_SIZE(gpu_cvb_info_table) || ret) return -ENODEV; return 0; } /** * gk20a_dvfs_init - connect to the regulator and initialize the dvfs table * * @pdev: platform device to the gk20a * @g: the gk20a instance */ int gk20a_dvfs_init(struct platform_device *pdev, struct gk20a *g) { struct device *dev; int ret; if (!g) return -EINVAL; dev_dbg(&pdev->dev, "%s\n", __func__); dev = &pdev->dev; mutex_lock(&gk20a_dvfs_lock); gpu_dvfs.g = g; mutex_unlock(&gk20a_dvfs_lock); ret = gk20a_dvfs_init_pmic(pdev, g); if (ret) { dev_warn(&pdev->dev, "failed to initialize pmic\n"); return ret; } ret = gk20a_dvfs_init_dvfs_table(pdev, g); if (ret) { dev_warn(&pdev->dev, "failed to initialize dvfs table\n"); return ret; } gk20a_dvfs_rail_register_vmin_cdev(&gpu_rail); gk20a_dvfs_rail_register_vts_cdev(&gpu_rail); return 0; }