162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/***************************************************************************
362306a36Sopenharmony_ci *   Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com>           *
462306a36Sopenharmony_ci *                                                                         *
562306a36Sopenharmony_ci ***************************************************************************/
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/bits.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1262306a36Sopenharmony_ci#include <linux/init.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/jiffies.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/hwmon.h>
1762306a36Sopenharmony_ci#include <linux/err.h>
1862306a36Sopenharmony_ci#include <linux/mutex.h>
1962306a36Sopenharmony_ci#include "sch56xx-common.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define DRVNAME "sch5627"
2262306a36Sopenharmony_ci#define DEVNAME DRVNAME /* We only support one model */
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define SCH5627_HWMON_ID		0xa5
2562306a36Sopenharmony_ci#define SCH5627_COMPANY_ID		0x5c
2662306a36Sopenharmony_ci#define SCH5627_PRIMARY_ID		0xa0
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define SCH5627_REG_BUILD_CODE		0x39
2962306a36Sopenharmony_ci#define SCH5627_REG_BUILD_ID		0x3a
3062306a36Sopenharmony_ci#define SCH5627_REG_HWMON_ID		0x3c
3162306a36Sopenharmony_ci#define SCH5627_REG_HWMON_REV		0x3d
3262306a36Sopenharmony_ci#define SCH5627_REG_COMPANY_ID		0x3e
3362306a36Sopenharmony_ci#define SCH5627_REG_PRIMARY_ID		0x3f
3462306a36Sopenharmony_ci#define SCH5627_REG_CTRL		0x40
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#define SCH5627_CTRL_START		BIT(0)
3762306a36Sopenharmony_ci#define SCH5627_CTRL_LOCK		BIT(1)
3862306a36Sopenharmony_ci#define SCH5627_CTRL_VBAT		BIT(4)
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#define SCH5627_NO_TEMPS		8
4162306a36Sopenharmony_ci#define SCH5627_NO_FANS			4
4262306a36Sopenharmony_ci#define SCH5627_NO_IN			5
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic const u16 SCH5627_REG_TEMP_MSB[SCH5627_NO_TEMPS] = {
4562306a36Sopenharmony_ci	0x2B, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x180, 0x181 };
4662306a36Sopenharmony_cistatic const u16 SCH5627_REG_TEMP_LSN[SCH5627_NO_TEMPS] = {
4762306a36Sopenharmony_ci	0xE2, 0xE1, 0xE1, 0xE5, 0xE5, 0xE6, 0x182, 0x182 };
4862306a36Sopenharmony_cistatic const u16 SCH5627_REG_TEMP_HIGH_NIBBLE[SCH5627_NO_TEMPS] = {
4962306a36Sopenharmony_ci	0, 0, 1, 1, 0, 0, 0, 1 };
5062306a36Sopenharmony_cistatic const u16 SCH5627_REG_TEMP_HIGH[SCH5627_NO_TEMPS] = {
5162306a36Sopenharmony_ci	0x61, 0x57, 0x59, 0x5B, 0x5D, 0x5F, 0x184, 0x186 };
5262306a36Sopenharmony_cistatic const u16 SCH5627_REG_TEMP_ABS[SCH5627_NO_TEMPS] = {
5362306a36Sopenharmony_ci	0x9B, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x1A8, 0x1A9 };
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic const u16 SCH5627_REG_FAN[SCH5627_NO_FANS] = {
5662306a36Sopenharmony_ci	0x2C, 0x2E, 0x30, 0x32 };
5762306a36Sopenharmony_cistatic const u16 SCH5627_REG_FAN_MIN[SCH5627_NO_FANS] = {
5862306a36Sopenharmony_ci	0x62, 0x64, 0x66, 0x68 };
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic const u16 SCH5627_REG_PWM_MAP[SCH5627_NO_FANS] = {
6162306a36Sopenharmony_ci	0xA0, 0xA1, 0xA2, 0xA3 };
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic const u16 SCH5627_REG_IN_MSB[SCH5627_NO_IN] = {
6462306a36Sopenharmony_ci	0x22, 0x23, 0x24, 0x25, 0x189 };
6562306a36Sopenharmony_cistatic const u16 SCH5627_REG_IN_LSN[SCH5627_NO_IN] = {
6662306a36Sopenharmony_ci	0xE4, 0xE4, 0xE3, 0xE3, 0x18A };
6762306a36Sopenharmony_cistatic const u16 SCH5627_REG_IN_HIGH_NIBBLE[SCH5627_NO_IN] = {
6862306a36Sopenharmony_ci	1, 0, 1, 0, 1 };
6962306a36Sopenharmony_cistatic const u16 SCH5627_REG_IN_FACTOR[SCH5627_NO_IN] = {
7062306a36Sopenharmony_ci	10745, 3660, 9765, 10745, 3660 };
7162306a36Sopenharmony_cistatic const char * const SCH5627_IN_LABELS[SCH5627_NO_IN] = {
7262306a36Sopenharmony_ci	"VCC", "VTT", "VBAT", "VTR", "V_IN" };
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistruct sch5627_data {
7562306a36Sopenharmony_ci	unsigned short addr;
7662306a36Sopenharmony_ci	u8 control;
7762306a36Sopenharmony_ci	u8 temp_max[SCH5627_NO_TEMPS];
7862306a36Sopenharmony_ci	u8 temp_crit[SCH5627_NO_TEMPS];
7962306a36Sopenharmony_ci	u16 fan_min[SCH5627_NO_FANS];
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	struct mutex update_lock;
8262306a36Sopenharmony_ci	unsigned long last_battery;	/* In jiffies */
8362306a36Sopenharmony_ci	char temp_valid;		/* !=0 if following fields are valid */
8462306a36Sopenharmony_ci	char fan_valid;
8562306a36Sopenharmony_ci	char in_valid;
8662306a36Sopenharmony_ci	unsigned long temp_last_updated;	/* In jiffies */
8762306a36Sopenharmony_ci	unsigned long fan_last_updated;
8862306a36Sopenharmony_ci	unsigned long in_last_updated;
8962306a36Sopenharmony_ci	u16 temp[SCH5627_NO_TEMPS];
9062306a36Sopenharmony_ci	u16 fan[SCH5627_NO_FANS];
9162306a36Sopenharmony_ci	u16 in[SCH5627_NO_IN];
9262306a36Sopenharmony_ci};
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic int sch5627_update_temp(struct sch5627_data *data)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	int ret = 0;
9762306a36Sopenharmony_ci	int i, val;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	mutex_lock(&data->update_lock);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	/* Cache the values for 1 second */
10262306a36Sopenharmony_ci	if (time_after(jiffies, data->temp_last_updated + HZ) || !data->temp_valid) {
10362306a36Sopenharmony_ci		for (i = 0; i < SCH5627_NO_TEMPS; i++) {
10462306a36Sopenharmony_ci			val = sch56xx_read_virtual_reg12(data->addr, SCH5627_REG_TEMP_MSB[i],
10562306a36Sopenharmony_ci							 SCH5627_REG_TEMP_LSN[i],
10662306a36Sopenharmony_ci							 SCH5627_REG_TEMP_HIGH_NIBBLE[i]);
10762306a36Sopenharmony_ci			if (unlikely(val < 0)) {
10862306a36Sopenharmony_ci				ret = val;
10962306a36Sopenharmony_ci				goto abort;
11062306a36Sopenharmony_ci			}
11162306a36Sopenharmony_ci			data->temp[i] = val;
11262306a36Sopenharmony_ci		}
11362306a36Sopenharmony_ci		data->temp_last_updated = jiffies;
11462306a36Sopenharmony_ci		data->temp_valid = 1;
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ciabort:
11762306a36Sopenharmony_ci	mutex_unlock(&data->update_lock);
11862306a36Sopenharmony_ci	return ret;
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic int sch5627_update_fan(struct sch5627_data *data)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	int ret = 0;
12462306a36Sopenharmony_ci	int i, val;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	mutex_lock(&data->update_lock);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* Cache the values for 1 second */
12962306a36Sopenharmony_ci	if (time_after(jiffies, data->fan_last_updated + HZ) || !data->fan_valid) {
13062306a36Sopenharmony_ci		for (i = 0; i < SCH5627_NO_FANS; i++) {
13162306a36Sopenharmony_ci			val = sch56xx_read_virtual_reg16(data->addr, SCH5627_REG_FAN[i]);
13262306a36Sopenharmony_ci			if (unlikely(val < 0)) {
13362306a36Sopenharmony_ci				ret = val;
13462306a36Sopenharmony_ci				goto abort;
13562306a36Sopenharmony_ci			}
13662306a36Sopenharmony_ci			data->fan[i] = val;
13762306a36Sopenharmony_ci		}
13862306a36Sopenharmony_ci		data->fan_last_updated = jiffies;
13962306a36Sopenharmony_ci		data->fan_valid = 1;
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ciabort:
14262306a36Sopenharmony_ci	mutex_unlock(&data->update_lock);
14362306a36Sopenharmony_ci	return ret;
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cistatic int sch5627_update_in(struct sch5627_data *data)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	int ret = 0;
14962306a36Sopenharmony_ci	int i, val;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	mutex_lock(&data->update_lock);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	/* Trigger a Vbat voltage measurement every 5 minutes */
15462306a36Sopenharmony_ci	if (time_after(jiffies, data->last_battery + 300 * HZ)) {
15562306a36Sopenharmony_ci		sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL,
15662306a36Sopenharmony_ci					  data->control | SCH5627_CTRL_VBAT);
15762306a36Sopenharmony_ci		data->last_battery = jiffies;
15862306a36Sopenharmony_ci	}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	/* Cache the values for 1 second */
16162306a36Sopenharmony_ci	if (time_after(jiffies, data->in_last_updated + HZ) || !data->in_valid) {
16262306a36Sopenharmony_ci		for (i = 0; i < SCH5627_NO_IN; i++) {
16362306a36Sopenharmony_ci			val = sch56xx_read_virtual_reg12(data->addr, SCH5627_REG_IN_MSB[i],
16462306a36Sopenharmony_ci							 SCH5627_REG_IN_LSN[i],
16562306a36Sopenharmony_ci							 SCH5627_REG_IN_HIGH_NIBBLE[i]);
16662306a36Sopenharmony_ci			if (unlikely(val < 0)) {
16762306a36Sopenharmony_ci				ret = val;
16862306a36Sopenharmony_ci				goto abort;
16962306a36Sopenharmony_ci			}
17062306a36Sopenharmony_ci			data->in[i] = val;
17162306a36Sopenharmony_ci		}
17262306a36Sopenharmony_ci		data->in_last_updated = jiffies;
17362306a36Sopenharmony_ci		data->in_valid = 1;
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ciabort:
17662306a36Sopenharmony_ci	mutex_unlock(&data->update_lock);
17762306a36Sopenharmony_ci	return ret;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic int sch5627_read_limits(struct sch5627_data *data)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	int i, val;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	for (i = 0; i < SCH5627_NO_TEMPS; i++) {
18562306a36Sopenharmony_ci		/*
18662306a36Sopenharmony_ci		 * Note what SMSC calls ABS, is what lm_sensors calls max
18762306a36Sopenharmony_ci		 * (aka high), and HIGH is what lm_sensors calls crit.
18862306a36Sopenharmony_ci		 */
18962306a36Sopenharmony_ci		val = sch56xx_read_virtual_reg(data->addr,
19062306a36Sopenharmony_ci					       SCH5627_REG_TEMP_ABS[i]);
19162306a36Sopenharmony_ci		if (val < 0)
19262306a36Sopenharmony_ci			return val;
19362306a36Sopenharmony_ci		data->temp_max[i] = val;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci		val = sch56xx_read_virtual_reg(data->addr,
19662306a36Sopenharmony_ci					       SCH5627_REG_TEMP_HIGH[i]);
19762306a36Sopenharmony_ci		if (val < 0)
19862306a36Sopenharmony_ci			return val;
19962306a36Sopenharmony_ci		data->temp_crit[i] = val;
20062306a36Sopenharmony_ci	}
20162306a36Sopenharmony_ci	for (i = 0; i < SCH5627_NO_FANS; i++) {
20262306a36Sopenharmony_ci		val = sch56xx_read_virtual_reg16(data->addr,
20362306a36Sopenharmony_ci						 SCH5627_REG_FAN_MIN[i]);
20462306a36Sopenharmony_ci		if (val < 0)
20562306a36Sopenharmony_ci			return val;
20662306a36Sopenharmony_ci		data->fan_min[i] = val;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	return 0;
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic int reg_to_temp(u16 reg)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	return (reg * 625) / 10 - 64000;
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic int reg_to_temp_limit(u8 reg)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	return (reg - 64) * 1000;
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic int reg_to_rpm(u16 reg)
22362306a36Sopenharmony_ci{
22462306a36Sopenharmony_ci	if (reg == 0)
22562306a36Sopenharmony_ci		return -EIO;
22662306a36Sopenharmony_ci	if (reg == 0xffff)
22762306a36Sopenharmony_ci		return 0;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	return 5400540 / reg;
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic umode_t sch5627_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
23362306a36Sopenharmony_ci				  int channel)
23462306a36Sopenharmony_ci{
23562306a36Sopenharmony_ci	const struct sch5627_data *data = drvdata;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	/* Once the lock bit is set, the virtual registers become read-only
23862306a36Sopenharmony_ci	 * until the next power cycle.
23962306a36Sopenharmony_ci	 */
24062306a36Sopenharmony_ci	if (data->control & SCH5627_CTRL_LOCK)
24162306a36Sopenharmony_ci		return 0444;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	if (type == hwmon_pwm && attr == hwmon_pwm_auto_channels_temp)
24462306a36Sopenharmony_ci		return 0644;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	return 0444;
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic int sch5627_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
25062306a36Sopenharmony_ci			long *val)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct sch5627_data *data = dev_get_drvdata(dev);
25362306a36Sopenharmony_ci	int ret;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	switch (type) {
25662306a36Sopenharmony_ci	case hwmon_temp:
25762306a36Sopenharmony_ci		ret = sch5627_update_temp(data);
25862306a36Sopenharmony_ci		if (ret < 0)
25962306a36Sopenharmony_ci			return ret;
26062306a36Sopenharmony_ci		switch (attr) {
26162306a36Sopenharmony_ci		case hwmon_temp_input:
26262306a36Sopenharmony_ci			*val = reg_to_temp(data->temp[channel]);
26362306a36Sopenharmony_ci			return 0;
26462306a36Sopenharmony_ci		case hwmon_temp_max:
26562306a36Sopenharmony_ci			*val = reg_to_temp_limit(data->temp_max[channel]);
26662306a36Sopenharmony_ci			return 0;
26762306a36Sopenharmony_ci		case hwmon_temp_crit:
26862306a36Sopenharmony_ci			*val = reg_to_temp_limit(data->temp_crit[channel]);
26962306a36Sopenharmony_ci			return 0;
27062306a36Sopenharmony_ci		case hwmon_temp_fault:
27162306a36Sopenharmony_ci			*val = (data->temp[channel] == 0);
27262306a36Sopenharmony_ci			return 0;
27362306a36Sopenharmony_ci		default:
27462306a36Sopenharmony_ci			break;
27562306a36Sopenharmony_ci		}
27662306a36Sopenharmony_ci		break;
27762306a36Sopenharmony_ci	case hwmon_fan:
27862306a36Sopenharmony_ci		ret = sch5627_update_fan(data);
27962306a36Sopenharmony_ci		if (ret < 0)
28062306a36Sopenharmony_ci			return ret;
28162306a36Sopenharmony_ci		switch (attr) {
28262306a36Sopenharmony_ci		case hwmon_fan_input:
28362306a36Sopenharmony_ci			ret = reg_to_rpm(data->fan[channel]);
28462306a36Sopenharmony_ci			if (ret < 0)
28562306a36Sopenharmony_ci				return ret;
28662306a36Sopenharmony_ci			*val = ret;
28762306a36Sopenharmony_ci			return 0;
28862306a36Sopenharmony_ci		case hwmon_fan_min:
28962306a36Sopenharmony_ci			ret = reg_to_rpm(data->fan_min[channel]);
29062306a36Sopenharmony_ci			if (ret < 0)
29162306a36Sopenharmony_ci				return ret;
29262306a36Sopenharmony_ci			*val = ret;
29362306a36Sopenharmony_ci			return 0;
29462306a36Sopenharmony_ci		case hwmon_fan_fault:
29562306a36Sopenharmony_ci			*val = (data->fan[channel] == 0xffff);
29662306a36Sopenharmony_ci			return 0;
29762306a36Sopenharmony_ci		default:
29862306a36Sopenharmony_ci			break;
29962306a36Sopenharmony_ci		}
30062306a36Sopenharmony_ci		break;
30162306a36Sopenharmony_ci	case hwmon_pwm:
30262306a36Sopenharmony_ci		switch (attr) {
30362306a36Sopenharmony_ci		case hwmon_pwm_auto_channels_temp:
30462306a36Sopenharmony_ci			mutex_lock(&data->update_lock);
30562306a36Sopenharmony_ci			ret = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_PWM_MAP[channel]);
30662306a36Sopenharmony_ci			mutex_unlock(&data->update_lock);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci			if (ret < 0)
30962306a36Sopenharmony_ci				return ret;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci			*val = ret;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci			return 0;
31462306a36Sopenharmony_ci		default:
31562306a36Sopenharmony_ci			break;
31662306a36Sopenharmony_ci		}
31762306a36Sopenharmony_ci		break;
31862306a36Sopenharmony_ci	case hwmon_in:
31962306a36Sopenharmony_ci		ret = sch5627_update_in(data);
32062306a36Sopenharmony_ci		if (ret < 0)
32162306a36Sopenharmony_ci			return ret;
32262306a36Sopenharmony_ci		switch (attr) {
32362306a36Sopenharmony_ci		case hwmon_in_input:
32462306a36Sopenharmony_ci			*val = DIV_ROUND_CLOSEST(data->in[channel] * SCH5627_REG_IN_FACTOR[channel],
32562306a36Sopenharmony_ci						 10000);
32662306a36Sopenharmony_ci			return 0;
32762306a36Sopenharmony_ci		default:
32862306a36Sopenharmony_ci			break;
32962306a36Sopenharmony_ci		}
33062306a36Sopenharmony_ci		break;
33162306a36Sopenharmony_ci	default:
33262306a36Sopenharmony_ci		break;
33362306a36Sopenharmony_ci	}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	return -EOPNOTSUPP;
33662306a36Sopenharmony_ci}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_cistatic int sch5627_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
33962306a36Sopenharmony_ci			       int channel, const char **str)
34062306a36Sopenharmony_ci{
34162306a36Sopenharmony_ci	switch (type) {
34262306a36Sopenharmony_ci	case hwmon_in:
34362306a36Sopenharmony_ci		switch (attr) {
34462306a36Sopenharmony_ci		case hwmon_in_label:
34562306a36Sopenharmony_ci			*str = SCH5627_IN_LABELS[channel];
34662306a36Sopenharmony_ci			return 0;
34762306a36Sopenharmony_ci		default:
34862306a36Sopenharmony_ci			break;
34962306a36Sopenharmony_ci		}
35062306a36Sopenharmony_ci		break;
35162306a36Sopenharmony_ci	default:
35262306a36Sopenharmony_ci		break;
35362306a36Sopenharmony_ci	}
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	return -EOPNOTSUPP;
35662306a36Sopenharmony_ci}
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_cistatic int sch5627_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
35962306a36Sopenharmony_ci			 long val)
36062306a36Sopenharmony_ci{
36162306a36Sopenharmony_ci	struct sch5627_data *data = dev_get_drvdata(dev);
36262306a36Sopenharmony_ci	int ret;
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	switch (type) {
36562306a36Sopenharmony_ci	case hwmon_pwm:
36662306a36Sopenharmony_ci		switch (attr) {
36762306a36Sopenharmony_ci		case hwmon_pwm_auto_channels_temp:
36862306a36Sopenharmony_ci			/* registers are 8 bit wide */
36962306a36Sopenharmony_ci			if (val > U8_MAX || val < 0)
37062306a36Sopenharmony_ci				return -EINVAL;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci			mutex_lock(&data->update_lock);
37362306a36Sopenharmony_ci			ret = sch56xx_write_virtual_reg(data->addr, SCH5627_REG_PWM_MAP[channel],
37462306a36Sopenharmony_ci							val);
37562306a36Sopenharmony_ci			mutex_unlock(&data->update_lock);
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci			return ret;
37862306a36Sopenharmony_ci		default:
37962306a36Sopenharmony_ci			break;
38062306a36Sopenharmony_ci		}
38162306a36Sopenharmony_ci		break;
38262306a36Sopenharmony_ci	default:
38362306a36Sopenharmony_ci		break;
38462306a36Sopenharmony_ci	}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	return -EOPNOTSUPP;
38762306a36Sopenharmony_ci}
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_cistatic const struct hwmon_ops sch5627_ops = {
39062306a36Sopenharmony_ci	.is_visible = sch5627_is_visible,
39162306a36Sopenharmony_ci	.read = sch5627_read,
39262306a36Sopenharmony_ci	.read_string = sch5627_read_string,
39362306a36Sopenharmony_ci	.write = sch5627_write,
39462306a36Sopenharmony_ci};
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_cistatic const struct hwmon_channel_info * const sch5627_info[] = {
39762306a36Sopenharmony_ci	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
39862306a36Sopenharmony_ci	HWMON_CHANNEL_INFO(temp,
39962306a36Sopenharmony_ci			   HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT,
40062306a36Sopenharmony_ci			   HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT,
40162306a36Sopenharmony_ci			   HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT,
40262306a36Sopenharmony_ci			   HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT,
40362306a36Sopenharmony_ci			   HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT,
40462306a36Sopenharmony_ci			   HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT,
40562306a36Sopenharmony_ci			   HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT,
40662306a36Sopenharmony_ci			   HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT
40762306a36Sopenharmony_ci			   ),
40862306a36Sopenharmony_ci	HWMON_CHANNEL_INFO(fan,
40962306a36Sopenharmony_ci			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT,
41062306a36Sopenharmony_ci			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT,
41162306a36Sopenharmony_ci			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT,
41262306a36Sopenharmony_ci			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT
41362306a36Sopenharmony_ci			   ),
41462306a36Sopenharmony_ci	HWMON_CHANNEL_INFO(pwm,
41562306a36Sopenharmony_ci			   HWMON_PWM_AUTO_CHANNELS_TEMP,
41662306a36Sopenharmony_ci			   HWMON_PWM_AUTO_CHANNELS_TEMP,
41762306a36Sopenharmony_ci			   HWMON_PWM_AUTO_CHANNELS_TEMP,
41862306a36Sopenharmony_ci			   HWMON_PWM_AUTO_CHANNELS_TEMP
41962306a36Sopenharmony_ci			   ),
42062306a36Sopenharmony_ci	HWMON_CHANNEL_INFO(in,
42162306a36Sopenharmony_ci			   HWMON_I_INPUT | HWMON_I_LABEL,
42262306a36Sopenharmony_ci			   HWMON_I_INPUT | HWMON_I_LABEL,
42362306a36Sopenharmony_ci			   HWMON_I_INPUT | HWMON_I_LABEL,
42462306a36Sopenharmony_ci			   HWMON_I_INPUT | HWMON_I_LABEL,
42562306a36Sopenharmony_ci			   HWMON_I_INPUT
42662306a36Sopenharmony_ci			   ),
42762306a36Sopenharmony_ci	NULL
42862306a36Sopenharmony_ci};
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_cistatic const struct hwmon_chip_info sch5627_chip_info = {
43162306a36Sopenharmony_ci	.ops = &sch5627_ops,
43262306a36Sopenharmony_ci	.info = sch5627_info,
43362306a36Sopenharmony_ci};
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_cistatic int sch5627_probe(struct platform_device *pdev)
43662306a36Sopenharmony_ci{
43762306a36Sopenharmony_ci	struct sch5627_data *data;
43862306a36Sopenharmony_ci	struct device *hwmon_dev;
43962306a36Sopenharmony_ci	int err, build_code, build_id, hwmon_rev, val;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	data = devm_kzalloc(&pdev->dev, sizeof(struct sch5627_data),
44262306a36Sopenharmony_ci			    GFP_KERNEL);
44362306a36Sopenharmony_ci	if (!data)
44462306a36Sopenharmony_ci		return -ENOMEM;
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start;
44762306a36Sopenharmony_ci	mutex_init(&data->update_lock);
44862306a36Sopenharmony_ci	platform_set_drvdata(pdev, data);
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_HWMON_ID);
45162306a36Sopenharmony_ci	if (val < 0)
45262306a36Sopenharmony_ci		return val;
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	if (val != SCH5627_HWMON_ID) {
45562306a36Sopenharmony_ci		pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "hwmon",
45662306a36Sopenharmony_ci		       val, SCH5627_HWMON_ID);
45762306a36Sopenharmony_ci		return -ENODEV;
45862306a36Sopenharmony_ci	}
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_COMPANY_ID);
46162306a36Sopenharmony_ci	if (val < 0)
46262306a36Sopenharmony_ci		return val;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	if (val != SCH5627_COMPANY_ID) {
46562306a36Sopenharmony_ci		pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "company",
46662306a36Sopenharmony_ci		       val, SCH5627_COMPANY_ID);
46762306a36Sopenharmony_ci		return -ENODEV;
46862306a36Sopenharmony_ci	}
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_PRIMARY_ID);
47162306a36Sopenharmony_ci	if (val < 0)
47262306a36Sopenharmony_ci		return val;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	if (val != SCH5627_PRIMARY_ID) {
47562306a36Sopenharmony_ci		pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "primary",
47662306a36Sopenharmony_ci		       val, SCH5627_PRIMARY_ID);
47762306a36Sopenharmony_ci		return -ENODEV;
47862306a36Sopenharmony_ci	}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	build_code = sch56xx_read_virtual_reg(data->addr,
48162306a36Sopenharmony_ci					      SCH5627_REG_BUILD_CODE);
48262306a36Sopenharmony_ci	if (build_code < 0)
48362306a36Sopenharmony_ci		return build_code;
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	build_id = sch56xx_read_virtual_reg16(data->addr,
48662306a36Sopenharmony_ci					      SCH5627_REG_BUILD_ID);
48762306a36Sopenharmony_ci	if (build_id < 0)
48862306a36Sopenharmony_ci		return build_id;
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	hwmon_rev = sch56xx_read_virtual_reg(data->addr,
49162306a36Sopenharmony_ci					     SCH5627_REG_HWMON_REV);
49262306a36Sopenharmony_ci	if (hwmon_rev < 0)
49362306a36Sopenharmony_ci		return hwmon_rev;
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_CTRL);
49662306a36Sopenharmony_ci	if (val < 0)
49762306a36Sopenharmony_ci		return val;
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	data->control = val;
50062306a36Sopenharmony_ci	if (!(data->control & SCH5627_CTRL_START)) {
50162306a36Sopenharmony_ci		pr_err("hardware monitoring not enabled\n");
50262306a36Sopenharmony_ci		return -ENODEV;
50362306a36Sopenharmony_ci	}
50462306a36Sopenharmony_ci	/* Trigger a Vbat voltage measurement, so that we get a valid reading
50562306a36Sopenharmony_ci	   the first time we read Vbat */
50662306a36Sopenharmony_ci	sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL, data->control | SCH5627_CTRL_VBAT);
50762306a36Sopenharmony_ci	data->last_battery = jiffies;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	/*
51062306a36Sopenharmony_ci	 * Read limits, we do this only once as reading a register on
51162306a36Sopenharmony_ci	 * the sch5627 is quite expensive (and they don't change).
51262306a36Sopenharmony_ci	 */
51362306a36Sopenharmony_ci	err = sch5627_read_limits(data);
51462306a36Sopenharmony_ci	if (err)
51562306a36Sopenharmony_ci		return err;
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	pr_info("found %s chip at %#hx\n", DEVNAME, data->addr);
51862306a36Sopenharmony_ci	pr_info("firmware build: code 0x%02X, id 0x%04X, hwmon: rev 0x%02X\n",
51962306a36Sopenharmony_ci		build_code, build_id, hwmon_rev);
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, DEVNAME, data,
52262306a36Sopenharmony_ci							 &sch5627_chip_info, NULL);
52362306a36Sopenharmony_ci	if (IS_ERR(hwmon_dev))
52462306a36Sopenharmony_ci		return PTR_ERR(hwmon_dev);
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	/* Note failing to register the watchdog is not a fatal error */
52762306a36Sopenharmony_ci	sch56xx_watchdog_register(&pdev->dev, data->addr,
52862306a36Sopenharmony_ci				  (build_code << 24) | (build_id << 8) | hwmon_rev,
52962306a36Sopenharmony_ci				  &data->update_lock, 1);
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	return 0;
53262306a36Sopenharmony_ci}
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_cistatic const struct platform_device_id sch5627_device_id[] = {
53562306a36Sopenharmony_ci	{
53662306a36Sopenharmony_ci		.name = "sch5627",
53762306a36Sopenharmony_ci	},
53862306a36Sopenharmony_ci	{ }
53962306a36Sopenharmony_ci};
54062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(platform, sch5627_device_id);
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_cistatic struct platform_driver sch5627_driver = {
54362306a36Sopenharmony_ci	.driver = {
54462306a36Sopenharmony_ci		.name	= DRVNAME,
54562306a36Sopenharmony_ci	},
54662306a36Sopenharmony_ci	.probe		= sch5627_probe,
54762306a36Sopenharmony_ci	.id_table	= sch5627_device_id,
54862306a36Sopenharmony_ci};
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_cimodule_platform_driver(sch5627_driver);
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ciMODULE_DESCRIPTION("SMSC SCH5627 Hardware Monitoring Driver");
55362306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
55462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
555