Commit 171cfeec authored by Olof Johansson's avatar Olof Johansson
Browse files

Merge tag 'samsung-drivers-5.5' of...

Merge tag 'samsung-drivers-5.5' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux into arm/drivers

Samsung soc drivers changes for v5.5

1. Minor fixes to Exynos Chipid driver.
2. Add Exynos Adaptive Supply Voltage driver allowing to adjust voltages
   used during CPU frequency scaling based on revision of SoC.  This
   also pulls dependency from PM/OPP tree - driver uses newly added
   dev_pm_opp_adjust_voltage() function.

* tag 'samsung-drivers-5.5' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux:
  soc: samsung: exynos-asv: Potential NULL dereference in exynos_asv_update_opps()
  soc: samsung: chipid: Drop "syscon" compatible requirement
  soc: samsung: Add Exynos Adaptive Supply Voltage driver
  PM / OPP: Support adjusting OPP voltages at runtime
  soc: samsung: chipid: Make exynos_chipid_early_init() static

Link: https://lore.kernel.org/r/20191104175902.12224-1-krzk@kernel.org


Signed-off-by: default avatarOlof Johansson <olof@lixom.net>
parents b4067b10 89e551e8
Loading
Loading
Loading
Loading
+69 −0
Original line number Diff line number Diff line
@@ -2112,6 +2112,75 @@ put_table:
	return r;
}

/**
 * dev_pm_opp_adjust_voltage() - helper to change the voltage of an OPP
 * @dev:		device for which we do this operation
 * @freq:		OPP frequency to adjust voltage of
 * @u_volt:		new OPP target voltage
 * @u_volt_min:		new OPP min voltage
 * @u_volt_max:		new OPP max voltage
 *
 * Return: -EINVAL for bad pointers, -ENOMEM if no memory available for the
 * copy operation, returns 0 if no modifcation was done OR modification was
 * successful.
 */
int dev_pm_opp_adjust_voltage(struct device *dev, unsigned long freq,
			      unsigned long u_volt, unsigned long u_volt_min,
			      unsigned long u_volt_max)

{
	struct opp_table *opp_table;
	struct dev_pm_opp *tmp_opp, *opp = ERR_PTR(-ENODEV);
	int r = 0;

	/* Find the opp_table */
	opp_table = _find_opp_table(dev);
	if (IS_ERR(opp_table)) {
		r = PTR_ERR(opp_table);
		dev_warn(dev, "%s: Device OPP not found (%d)\n", __func__, r);
		return r;
	}

	mutex_lock(&opp_table->lock);

	/* Do we have the frequency? */
	list_for_each_entry(tmp_opp, &opp_table->opp_list, node) {
		if (tmp_opp->rate == freq) {
			opp = tmp_opp;
			break;
		}
	}

	if (IS_ERR(opp)) {
		r = PTR_ERR(opp);
		goto adjust_unlock;
	}

	/* Is update really needed? */
	if (opp->supplies->u_volt == u_volt)
		goto adjust_unlock;

	opp->supplies->u_volt = u_volt;
	opp->supplies->u_volt_min = u_volt_min;
	opp->supplies->u_volt_max = u_volt_max;

	dev_pm_opp_get(opp);
	mutex_unlock(&opp_table->lock);

	/* Notify the voltage change of the OPP */
	blocking_notifier_call_chain(&opp_table->head, OPP_EVENT_ADJUST_VOLTAGE,
				     opp);

	dev_pm_opp_put(opp);
	goto adjust_put_table;

adjust_unlock:
	mutex_unlock(&opp_table->lock);
adjust_put_table:
	dev_pm_opp_put_opp_table(opp_table);
	return r;
}

/**
 * dev_pm_opp_enable() - Enable a specific OPP
 * @dev:	device for which we do this operation
+10 −0
Original line number Diff line number Diff line
@@ -7,6 +7,16 @@ menuconfig SOC_SAMSUNG

if SOC_SAMSUNG

config EXYNOS_ASV
	bool "Exynos Adaptive Supply Voltage support" if COMPILE_TEST
	depends on (ARCH_EXYNOS && EXYNOS_CHIPID) || COMPILE_TEST
	select EXYNOS_ASV_ARM if ARM && ARCH_EXYNOS

# There is no need to enable these drivers for ARMv8
config EXYNOS_ASV_ARM
	bool "Exynos ASV ARMv7-specific driver extensions" if COMPILE_TEST
	depends on EXYNOS_ASV

config EXYNOS_CHIPID
	bool "Exynos Chipid controller driver" if COMPILE_TEST
	depends on ARCH_EXYNOS || COMPILE_TEST
+3 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0

obj-$(CONFIG_EXYNOS_ASV)	+= exynos-asv.o
obj-$(CONFIG_EXYNOS_ASV_ARM)	+= exynos5422-asv.o

obj-$(CONFIG_EXYNOS_CHIPID)	+= exynos-chipid.o
obj-$(CONFIG_EXYNOS_PMU)	+= exynos-pmu.o

+177 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 Samsung Electronics Co., Ltd.
 *	      http://www.samsung.com/
 * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
 *
 * Samsung Exynos SoC Adaptive Supply Voltage support
 */

#include <linux/cpu.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/regmap.h>
#include <linux/soc/samsung/exynos-chipid.h>

#include "exynos-asv.h"
#include "exynos5422-asv.h"

#define MHZ 1000000U

static int exynos_asv_update_cpu_opps(struct exynos_asv *asv,
				      struct device *cpu)
{
	struct exynos_asv_subsys *subsys = NULL;
	struct dev_pm_opp *opp;
	unsigned int opp_freq;
	int i;

	for (i = 0; i < ARRAY_SIZE(asv->subsys); i++) {
		if (of_device_is_compatible(cpu->of_node,
					    asv->subsys[i].cpu_dt_compat)) {
			subsys = &asv->subsys[i];
			break;
		}
	}
	if (!subsys)
		return -EINVAL;

	for (i = 0; i < subsys->table.num_rows; i++) {
		unsigned int new_volt, volt;
		int ret;

		opp_freq = exynos_asv_opp_get_frequency(subsys, i);

		opp = dev_pm_opp_find_freq_exact(cpu, opp_freq * MHZ, true);
		if (IS_ERR(opp)) {
			dev_info(asv->dev, "cpu%d opp%d, freq: %u missing\n",
				 cpu->id, i, opp_freq);

			continue;
		}

		volt = dev_pm_opp_get_voltage(opp);
		new_volt = asv->opp_get_voltage(subsys, i, volt);
		dev_pm_opp_put(opp);

		if (new_volt == volt)
			continue;

		ret = dev_pm_opp_adjust_voltage(cpu, opp_freq * MHZ,
						new_volt, new_volt, new_volt);
		if (ret < 0)
			dev_err(asv->dev,
				"Failed to adjust OPP %u Hz/%u uV for cpu%d\n",
				opp_freq, new_volt, cpu->id);
		else
			dev_dbg(asv->dev,
				"Adjusted OPP %u Hz/%u -> %u uV, cpu%d\n",
				opp_freq, volt, new_volt, cpu->id);
	}

	return 0;
}

static int exynos_asv_update_opps(struct exynos_asv *asv)
{
	struct opp_table *last_opp_table = NULL;
	struct device *cpu;
	int ret, cpuid;

	for_each_possible_cpu(cpuid) {
		struct opp_table *opp_table;

		cpu = get_cpu_device(cpuid);
		if (!cpu)
			continue;

		opp_table = dev_pm_opp_get_opp_table(cpu);
		if (IS_ERR_OR_NULL(opp_table))
			continue;

		if (!last_opp_table || opp_table != last_opp_table) {
			last_opp_table = opp_table;

			ret = exynos_asv_update_cpu_opps(asv, cpu);
			if (ret < 0)
				dev_err(asv->dev, "Couldn't udate OPPs for cpu%d\n",
					cpuid);
		}

		dev_pm_opp_put_opp_table(opp_table);
	}

	return	0;
}

static int exynos_asv_probe(struct platform_device *pdev)
{
	int (*probe_func)(struct exynos_asv *asv);
	struct exynos_asv *asv;
	struct device *cpu_dev;
	u32 product_id = 0;
	int ret, i;

	cpu_dev = get_cpu_device(0);
	ret = dev_pm_opp_get_opp_count(cpu_dev);
	if (ret < 0)
		return -EPROBE_DEFER;

	asv = devm_kzalloc(&pdev->dev, sizeof(*asv), GFP_KERNEL);
	if (!asv)
		return -ENOMEM;

	asv->chipid_regmap = device_node_to_regmap(pdev->dev.of_node);
	if (IS_ERR(asv->chipid_regmap)) {
		dev_err(&pdev->dev, "Could not find syscon regmap\n");
		return PTR_ERR(asv->chipid_regmap);
	}

	regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_PRO_ID, &product_id);

	switch (product_id & EXYNOS_MASK) {
	case 0xE5422000:
		probe_func = exynos5422_asv_init;
		break;
	default:
		return -ENODEV;
	}

	ret = of_property_read_u32(pdev->dev.of_node, "samsung,asv-bin",
				   &asv->of_bin);
	if (ret < 0)
		asv->of_bin = -EINVAL;

	asv->dev = &pdev->dev;
	dev_set_drvdata(&pdev->dev, asv);

	for (i = 0; i < ARRAY_SIZE(asv->subsys); i++)
		asv->subsys[i].asv = asv;

	ret = probe_func(asv);
	if (ret < 0)
		return ret;

	return exynos_asv_update_opps(asv);
}

static const struct of_device_id exynos_asv_of_device_ids[] = {
	{ .compatible = "samsung,exynos4210-chipid" },
	{}
};

static struct platform_driver exynos_asv_driver = {
	.driver = {
		.name = "exynos-asv",
		.of_match_table = exynos_asv_of_device_ids,
	},
	.probe	= exynos_asv_probe,
};
module_platform_driver(exynos_asv_driver);
+71 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (c) 2019 Samsung Electronics Co., Ltd.
 *	      http://www.samsung.com/
 * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
 *
 * Samsung Exynos SoC Adaptive Supply Voltage support
 */
#ifndef __LINUX_SOC_EXYNOS_ASV_H
#define __LINUX_SOC_EXYNOS_ASV_H

struct regmap;

/* HPM, IDS values to select target group */
struct asv_limit_entry {
	unsigned int hpm;
	unsigned int ids;
};

struct exynos_asv_table {
	unsigned int num_rows;
	unsigned int num_cols;
	u32 *buf;
};

struct exynos_asv_subsys {
	struct exynos_asv *asv;
	const char *cpu_dt_compat;
	int id;
	struct exynos_asv_table table;

	unsigned int base_volt;
	unsigned int offset_volt_h;
	unsigned int offset_volt_l;
};

struct exynos_asv {
	struct device *dev;
	struct regmap *chipid_regmap;
	struct exynos_asv_subsys subsys[2];

	int (*opp_get_voltage)(const struct exynos_asv_subsys *subs,
			       int level, unsigned int voltage);
	unsigned int group;
	unsigned int table;

	/* True if SG fields from PKG_ID register should be used */
	bool use_sg;
	/* ASV bin read from DT */
	int of_bin;
};

static inline u32 __asv_get_table_entry(const struct exynos_asv_table *table,
					unsigned int row, unsigned int col)
{
	return table->buf[row * (table->num_cols) + col];
}

static inline u32 exynos_asv_opp_get_voltage(const struct exynos_asv_subsys *subsys,
					unsigned int level, unsigned int group)
{
	return __asv_get_table_entry(&subsys->table, level, group + 1);
}

static inline u32 exynos_asv_opp_get_frequency(const struct exynos_asv_subsys *subsys,
					unsigned int level)
{
	return __asv_get_table_entry(&subsys->table, level, 0);
}

#endif /* __LINUX_SOC_EXYNOS_ASV_H */
Loading