Revision e2921f9f95f1c1355a39e54dc038ad95b6e032be authored by Linus Torvalds on 26 July 2019, 21:12:54 UTC, committed by Linus Torvalds on 26 July 2019, 21:12:54 UTC
Pull drm fixes from Daniel Vetter:
 "Dave seems to collect an entire streak of things happening, so again
  me typing pull summary.

  Nothing nefarious here, most of the fixes are for new stuff or things
  users won't see. The amd-display patches are a bit different, and very
  much look like they should have at least some cc: stable tags. Might
  be amd is a bit too comfortable with their internal tree and not
  enough looking at upstream. Dave&me are looking into this, in case
  something needs rectified with process here.

  Also no intel fixes pull, but intel CI is general become rather good,
  still I guess expect a notch more for -rc3.

  Summary:

  amdgpu:
   - fixes for (new in 5.3) hw support (vega20, navi)
   - disable RAS
   - lots of display fixes all over (audio, DSC, dongle, clock mgr)

  ttm:
   - fix dma_free_attrs calls to appease dma debugging

  msm:
   - fixes for dma-api, locking debug and compiler splats

  core:
   - fix cmdline mode to not apply rotation if not specified (new in 5.3)
   - compiler warn fix"

* tag 'drm-fixes-2019-07-26' of git://anongit.freedesktop.org/drm/drm: (46 commits)
  drm/amd/display: Set enabled to false at start of audio disable
  drm/amdgpu/smu: move fan rpm query into the asic specific code
  drm/amd/powerplay: custom peak clock freq for navi10
  drm: silence variable 'conn' set but not used
  drm/msm: stop abusing dma_map/unmap for cache
  drm/msm/dpu: Correct dpu encoder spinlock initialization
  drm/msm: correct NULL pointer dereference in context_init
  drm/amd/display: handle active dongle port type is DP++ or DP case
  drm/amd/display: do not read link setting if edp not connected
  drm/amd/display: Increase size of audios array
  drm/amd/display: drop ASSERT() if eDP panel is not connected
  drm/amd/display: Only enable audio if speaker allocation exists
  drm/amd/display: Fix dc_create failure handling and 666 color depths
  drm/amd/display: allocate 4 ddc engines for RV2
  drm/amd/display: put back front end initialization sequence
  drm/amd/display: Wait for flip to complete
  drm/amd/display: Change min_h_sync_width from 8 to 4
  drm/amd/display: use encoder's engine id to find matched free audio device
  drm/amd/display: fix DMCU hang when going into Modern Standby
  drm/amd/display: Disable Audio on reinitialize hardware
  ...
2 parent s 3ea54d9 + 4d5308e
Raw File
lochnagar-hwmon.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Lochnagar hardware monitoring features
 *
 * Copyright (c) 2016-2019 Cirrus Logic, Inc. and
 *                         Cirrus Logic International Semiconductor Ltd.
 *
 * Author: Lucas Tanure <tanureal@opensource.cirrus.com>
 */

#include <linux/delay.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/i2c.h>
#include <linux/math64.h>
#include <linux/mfd/lochnagar.h>
#include <linux/mfd/lochnagar2_regs.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>

#define LN2_MAX_NSAMPLE 1023
#define LN2_SAMPLE_US   1670

#define LN2_CURR_UNITS  1000
#define LN2_VOLT_UNITS  1000
#define LN2_TEMP_UNITS  1000
#define LN2_PWR_UNITS   1000000

static const char * const lochnagar_chan_names[] = {
	"DBVDD1",
	"1V8 DSP",
	"1V8 CDC",
	"VDDCORE DSP",
	"AVDD 1V8",
	"SYSVDD",
	"VDDCORE CDC",
	"MICVDD",
};

struct lochnagar_hwmon {
	struct regmap *regmap;

	long power_nsamples[ARRAY_SIZE(lochnagar_chan_names)];

	/* Lock to ensure only a single sensor is read at a time */
	struct mutex sensor_lock;
};

enum lochnagar_measure_mode {
	LN2_CURR = 0,
	LN2_VOLT,
	LN2_TEMP,
};

/**
 * float_to_long - Convert ieee754 reading from hardware to an integer
 *
 * @data: Value read from the hardware
 * @precision: Units to multiply up to eg. 1000 = milli, 1000000 = micro
 *
 * Return: Converted integer reading
 *
 * Depending on the measurement type the hardware returns an ieee754
 * floating point value in either volts, amps or celsius. This function
 * will convert that into an integer in a smaller unit such as micro-amps
 * or milli-celsius. The hardware does not return NaN, so consideration of
 * that is not required.
 */
static long float_to_long(u32 data, u32 precision)
{
	u64 man = data & 0x007FFFFF;
	int exp = ((data & 0x7F800000) >> 23) - 127 - 23;
	bool negative = data & 0x80000000;
	long result;

	man = (man + (1 << 23)) * precision;

	if (fls64(man) + exp > (int)sizeof(long) * 8 - 1)
		result = LONG_MAX;
	else if (exp < 0)
		result = (man + (1ull << (-exp - 1))) >> -exp;
	else
		result = man << exp;

	return negative ? -result : result;
}

static int do_measurement(struct regmap *regmap, int chan,
			  enum lochnagar_measure_mode mode, int nsamples)
{
	unsigned int val;
	int ret;

	chan = 1 << (chan + LOCHNAGAR2_IMON_MEASURED_CHANNELS_SHIFT);

	ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL1,
			   LOCHNAGAR2_IMON_ENA_MASK | chan | mode);
	if (ret < 0)
		return ret;

	ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL2, nsamples);
	if (ret < 0)
		return ret;

	ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL3,
			   LOCHNAGAR2_IMON_CONFIGURE_MASK);
	if (ret < 0)
		return ret;

	ret =  regmap_read_poll_timeout(regmap, LOCHNAGAR2_IMON_CTRL3, val,
					val & LOCHNAGAR2_IMON_DONE_MASK,
					1000, 10000);
	if (ret < 0)
		return ret;

	ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL3,
			   LOCHNAGAR2_IMON_MEASURE_MASK);
	if (ret < 0)
		return ret;

	/*
	 * Actual measurement time is ~1.67mS per sample, approximate this
	 * with a 1.5mS per sample msleep and then poll for success up to
	 * ~0.17mS * 1023 (LN2_MAX_NSAMPLES). Normally for smaller values
	 * of nsamples the poll will complete on the first loop due to
	 * other latency in the system.
	 */
	msleep((nsamples * 3) / 2);

	ret =  regmap_read_poll_timeout(regmap, LOCHNAGAR2_IMON_CTRL3, val,
					val & LOCHNAGAR2_IMON_DONE_MASK,
					5000, 200000);
	if (ret < 0)
		return ret;

	return regmap_write(regmap, LOCHNAGAR2_IMON_CTRL3, 0);
}

static int request_data(struct regmap *regmap, int chan, u32 *data)
{
	unsigned int val;
	int ret;

	ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL4,
			   LOCHNAGAR2_IMON_DATA_REQ_MASK |
			   chan << LOCHNAGAR2_IMON_CH_SEL_SHIFT);
	if (ret < 0)
		return ret;

	ret =  regmap_read_poll_timeout(regmap, LOCHNAGAR2_IMON_CTRL4, val,
					val & LOCHNAGAR2_IMON_DATA_RDY_MASK,
					1000, 10000);
	if (ret < 0)
		return ret;

	ret = regmap_read(regmap, LOCHNAGAR2_IMON_DATA1, &val);
	if (ret < 0)
		return ret;

	*data = val << 16;

	ret = regmap_read(regmap, LOCHNAGAR2_IMON_DATA2, &val);
	if (ret < 0)
		return ret;

	*data |= val;

	return regmap_write(regmap, LOCHNAGAR2_IMON_CTRL4, 0);
}

static int read_sensor(struct device *dev, int chan,
		       enum lochnagar_measure_mode mode, int nsamples,
		       unsigned int precision, long *val)
{
	struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
	struct regmap *regmap = priv->regmap;
	u32 data;
	int ret;

	mutex_lock(&priv->sensor_lock);

	ret = do_measurement(regmap, chan, mode, nsamples);
	if (ret < 0) {
		dev_err(dev, "Failed to perform measurement: %d\n", ret);
		goto error;
	}

	ret = request_data(regmap, chan, &data);
	if (ret < 0) {
		dev_err(dev, "Failed to read measurement: %d\n", ret);
		goto error;
	}

	*val = float_to_long(data, precision);

error:
	mutex_unlock(&priv->sensor_lock);

	return ret;
}

static int read_power(struct device *dev, int chan, long *val)
{
	struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
	int nsamples = priv->power_nsamples[chan];
	u64 power;
	int ret;

	if (!strcmp("SYSVDD", lochnagar_chan_names[chan])) {
		power = 5 * LN2_PWR_UNITS;
	} else {
		ret = read_sensor(dev, chan, LN2_VOLT, 1, LN2_PWR_UNITS, val);
		if (ret < 0)
			return ret;

		power = abs(*val);
	}

	ret = read_sensor(dev, chan, LN2_CURR, nsamples, LN2_PWR_UNITS, val);
	if (ret < 0)
		return ret;

	power *= abs(*val);
	power = DIV_ROUND_CLOSEST_ULL(power, LN2_PWR_UNITS);

	if (power > LONG_MAX)
		*val = LONG_MAX;
	else
		*val = power;

	return 0;
}

static umode_t lochnagar_is_visible(const void *drvdata,
				    enum hwmon_sensor_types type,
				    u32 attr, int chan)
{
	switch (type) {
	case hwmon_in:
		if (!strcmp("SYSVDD", lochnagar_chan_names[chan]))
			return 0;
		break;
	case hwmon_power:
		if (attr == hwmon_power_average_interval)
			return 0644;
		break;
	default:
		break;
	}

	return 0444;
}

static int lochnagar_read(struct device *dev, enum hwmon_sensor_types type,
			  u32 attr, int chan, long *val)
{
	struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
	int interval;

	switch (type) {
	case hwmon_in:
		return read_sensor(dev, chan, LN2_VOLT, 1, LN2_VOLT_UNITS, val);
	case hwmon_curr:
		return read_sensor(dev, chan, LN2_CURR, 1, LN2_CURR_UNITS, val);
	case hwmon_temp:
		return read_sensor(dev, chan, LN2_TEMP, 1, LN2_TEMP_UNITS, val);
	case hwmon_power:
		switch (attr) {
		case hwmon_power_average:
			return read_power(dev, chan, val);
		case hwmon_power_average_interval:
			interval = priv->power_nsamples[chan] * LN2_SAMPLE_US;
			*val = DIV_ROUND_CLOSEST(interval, 1000);
			return 0;
		default:
			return -EOPNOTSUPP;
		}
	default:
		return -EOPNOTSUPP;
	}
}

static int lochnagar_read_string(struct device *dev,
				 enum hwmon_sensor_types type, u32 attr,
				 int chan, const char **str)
{
	switch (type) {
	case hwmon_in:
	case hwmon_curr:
	case hwmon_power:
		*str = lochnagar_chan_names[chan];
		return 0;
	default:
		return -EOPNOTSUPP;
	}
}

static int lochnagar_write(struct device *dev, enum hwmon_sensor_types type,
			   u32 attr, int chan, long val)
{
	struct lochnagar_hwmon *priv = dev_get_drvdata(dev);

	if (type != hwmon_power || attr != hwmon_power_average_interval)
		return -EOPNOTSUPP;

	val = clamp_t(long, val, 1, (LN2_MAX_NSAMPLE * LN2_SAMPLE_US) / 1000);
	val = DIV_ROUND_CLOSEST(val * 1000, LN2_SAMPLE_US);

	priv->power_nsamples[chan] = val;

	return 0;
}

static const struct hwmon_ops lochnagar_ops = {
	.is_visible = lochnagar_is_visible,
	.read = lochnagar_read,
	.read_string = lochnagar_read_string,
	.write = lochnagar_write,
};

static const struct hwmon_channel_info *lochnagar_info[] = {
	HWMON_CHANNEL_INFO(temp,  HWMON_T_INPUT),
	HWMON_CHANNEL_INFO(in,    HWMON_I_INPUT | HWMON_I_LABEL,
				  HWMON_I_INPUT | HWMON_I_LABEL,
				  HWMON_I_INPUT | HWMON_I_LABEL,
				  HWMON_I_INPUT | HWMON_I_LABEL,
				  HWMON_I_INPUT | HWMON_I_LABEL,
				  HWMON_I_INPUT | HWMON_I_LABEL,
				  HWMON_I_INPUT | HWMON_I_LABEL,
				  HWMON_I_INPUT | HWMON_I_LABEL),
	HWMON_CHANNEL_INFO(curr,  HWMON_C_INPUT | HWMON_C_LABEL,
				  HWMON_C_INPUT | HWMON_C_LABEL,
				  HWMON_C_INPUT | HWMON_C_LABEL,
				  HWMON_C_INPUT | HWMON_C_LABEL,
				  HWMON_C_INPUT | HWMON_C_LABEL,
				  HWMON_C_INPUT | HWMON_C_LABEL,
				  HWMON_C_INPUT | HWMON_C_LABEL,
				  HWMON_C_INPUT | HWMON_C_LABEL),
	HWMON_CHANNEL_INFO(power, HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
				  HWMON_P_LABEL,
				  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
				  HWMON_P_LABEL,
				  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
				  HWMON_P_LABEL,
				  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
				  HWMON_P_LABEL,
				  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
				  HWMON_P_LABEL,
				  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
				  HWMON_P_LABEL,
				  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
				  HWMON_P_LABEL,
				  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
				  HWMON_P_LABEL),
	NULL
};

static const struct hwmon_chip_info lochnagar_chip_info = {
	.ops = &lochnagar_ops,
	.info = lochnagar_info,
};

static const struct of_device_id lochnagar_of_match[] = {
	{ .compatible = "cirrus,lochnagar2-hwmon" },
	{}
};
MODULE_DEVICE_TABLE(of, lochnagar_of_match);

static int lochnagar_hwmon_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device *hwmon_dev;
	struct lochnagar_hwmon *priv;
	int i;

	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	mutex_init(&priv->sensor_lock);

	priv->regmap = dev_get_regmap(dev->parent, NULL);
	if (!priv->regmap) {
		dev_err(dev, "No register map found\n");
		return -EINVAL;
	}

	for (i = 0; i < ARRAY_SIZE(priv->power_nsamples); i++)
		priv->power_nsamples[i] = 96;

	hwmon_dev = devm_hwmon_device_register_with_info(dev, "Lochnagar", priv,
							 &lochnagar_chip_info,
							 NULL);

	return PTR_ERR_OR_ZERO(hwmon_dev);
}

static struct platform_driver lochnagar_hwmon_driver = {
	.driver = {
		.name = "lochnagar-hwmon",
		.of_match_table = lochnagar_of_match,
	},
	.probe = lochnagar_hwmon_probe,
};
module_platform_driver(lochnagar_hwmon_driver);

MODULE_AUTHOR("Lucas Tanure <tanureal@opensource.cirrus.com>");
MODULE_DESCRIPTION("Lochnagar hardware monitoring features");
MODULE_LICENSE("GPL");
back to top