summaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/pmbus/aps-379.c
blob: 7d46cd647e20aab0135a8608c7642a6c0b15e5c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Hardware monitoring driver for Sony APS-379 Power Supplies
 *
 * Copyright 2026 Allied Telesis Labs
 */

#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pmbus.h>
#include "pmbus.h"

/*
 * The VOUT format used by the chip is linear11, not linear16. Provide a hard
 * coded VOUT_MODE that says VOUT is in linear mode with a fixed exponent of
 * 2^-4.
 */
#define APS_379_VOUT_MODE ((u8)(-4 & 0x1f))

static int aps_379_read_byte_data(struct i2c_client *client, int page, int reg)
{
	switch (reg) {
	case PMBUS_VOUT_MODE:
		return APS_379_VOUT_MODE;
	default:
		return -ENODATA;
	}
}

/*
 * The APS-379 uses linear11 format instead of linear16. We've reported the exponent
 * via the PMBUS_VOUT_MODE so we just return the mantissa here.
 */
static int aps_379_read_vout(struct i2c_client *client)
{
	int ret;

	ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_READ_VOUT);
	if (ret < 0)
		return ret;

	return clamp_val(sign_extend32(ret & 0x7ff, 10), 0, 0x3ff);
}

static int aps_379_read_word_data(struct i2c_client *client, int page, int phase, int reg)
{
	switch (reg) {
	case PMBUS_VOUT_UV_WARN_LIMIT:
	case PMBUS_VOUT_OV_WARN_LIMIT:
	case PMBUS_VOUT_UV_FAULT_LIMIT:
	case PMBUS_VOUT_OV_FAULT_LIMIT:
	case PMBUS_IOUT_OC_WARN_LIMIT:
	case PMBUS_IOUT_UC_FAULT_LIMIT:
	case PMBUS_UT_WARN_LIMIT:
	case PMBUS_UT_FAULT_LIMIT:
	case PMBUS_OT_WARN_LIMIT:
	case PMBUS_OT_FAULT_LIMIT:
	case PMBUS_PIN_OP_WARN_LIMIT:
	case PMBUS_POUT_OP_WARN_LIMIT:
	case PMBUS_MFR_IIN_MAX:
	case PMBUS_MFR_PIN_MAX:
	case PMBUS_MFR_VOUT_MIN:
	case PMBUS_MFR_VOUT_MAX:
	case PMBUS_MFR_IOUT_MAX:
	case PMBUS_MFR_POUT_MAX:
	case PMBUS_MFR_MAX_TEMP_1:
		/* These commands return data but it is invalid/un-documented */
		return -ENXIO;
	case PMBUS_IOUT_OC_FAULT_LIMIT:
		/*
		 * The standard requires this to be a value in Amps but it's
		 * actually a percentage of the rated output (123A for
		 * 110-240Vac, 110A for 90-100Vac) which we don't know. Ignore
		 * it rather than guessing.
		 */
		return -ENXIO;
	case PMBUS_READ_VOUT:
		return aps_379_read_vout(client);
	default:
		return -ENODATA;
	}
}

static struct pmbus_driver_info aps_379_info = {
	.pages = 1,
	.format[PSC_VOLTAGE_OUT] = linear,
	.format[PSC_CURRENT_OUT] = linear,
	.format[PSC_POWER] = linear,
	.format[PSC_TEMPERATURE] = linear,
	.format[PSC_FAN] = linear,
	.func[0] = PMBUS_HAVE_VOUT |
		PMBUS_HAVE_IOUT |
		PMBUS_HAVE_PIN | PMBUS_HAVE_POUT |
		PMBUS_HAVE_TEMP |
		PMBUS_HAVE_FAN12,
	.read_byte_data = aps_379_read_byte_data,
	.read_word_data = aps_379_read_word_data,
};

static const struct i2c_device_id aps_379_id[] = {
	{ "aps-379", 0 },
	{},
};
MODULE_DEVICE_TABLE(i2c, aps_379_id);

static int aps_379_probe(struct i2c_client *client)
{
	struct device *dev = &client->dev;
	u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = { 0 };
	int ret;

	if (!i2c_check_functionality(client->adapter,
				     I2C_FUNC_SMBUS_READ_BYTE_DATA
				     | I2C_FUNC_SMBUS_READ_WORD_DATA
				     | I2C_FUNC_SMBUS_READ_BLOCK_DATA))
		return -ENODEV;

	ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
	if (ret < 0) {
		dev_err(dev, "Failed to read Manufacturer Model\n");
		return ret;
	}

	if (strncasecmp(buf, aps_379_id[0].name, strlen(aps_379_id[0].name)) != 0) {
		buf[ret] = '\0';
		dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf);
		return -ENODEV;
	}

	return pmbus_do_probe(client, &aps_379_info);
}

static const struct of_device_id __maybe_unused aps_379_of_match[] = {
	{ .compatible = "sony,aps-379" },
	{},
};
MODULE_DEVICE_TABLE(of, aps_379_of_match);

static struct i2c_driver aps_379_driver = {
	.driver = {
		.name = "aps-379",
		.of_match_table = of_match_ptr(aps_379_of_match),
	},
	.probe = aps_379_probe,
	.id_table = aps_379_id,
};

module_i2c_driver(aps_379_driver);

MODULE_AUTHOR("Chris Packham");
MODULE_DESCRIPTION("PMBus driver for Sony APS-379");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("PMBUS");