summaryrefslogtreecommitdiff
path: root/drivers/hwmon/pmbus/dps920ab.c
blob: 04e0d598a6e54442e37292e7d5964a9d5ae59d14 (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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Driver for Delta DPS920AB PSU
 *
 * Copyright (C) 2021 Delta Networks, Inc.
 * Copyright (C) 2021 Sartura Ltd.
 */

#include <linux/debugfs.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
#include "pmbus.h"

struct dps920ab_data {
	char *mfr_model;
	char *mfr_id;
};

static int dps920ab_read_word_data(struct i2c_client *client, int page, int phase, int reg)
{
	/*
	 * This masks commands which are not supported.
	 * PSU advertises that all features are supported,
	 * in reality that unfortunately is not true.
	 * So enable only those that the datasheet confirms.
	 */
	switch (reg) {
	case PMBUS_FAN_COMMAND_1:
	case PMBUS_IOUT_OC_WARN_LIMIT:
	case PMBUS_STATUS_WORD:
	case PMBUS_READ_VIN:
	case PMBUS_READ_IIN:
	case PMBUS_READ_VOUT:
	case PMBUS_READ_IOUT:
	case PMBUS_READ_TEMPERATURE_1:
	case PMBUS_READ_TEMPERATURE_2:
	case PMBUS_READ_TEMPERATURE_3:
	case PMBUS_READ_FAN_SPEED_1:
	case PMBUS_READ_POUT:
	case PMBUS_READ_PIN:
	case PMBUS_MFR_VOUT_MIN:
	case PMBUS_MFR_VOUT_MAX:
	case PMBUS_MFR_IOUT_MAX:
	case PMBUS_MFR_POUT_MAX:
		return pmbus_read_word_data(client, page, phase, reg);
	default:
		return -ENXIO;
	}
}

static int dps920ab_write_word_data(struct i2c_client *client, int page, int reg,
				    u16 word)
{
	/*
	 * This masks commands which are not supported.
	 * PSU only has one R/W register and that is
	 * for the fan.
	 */
	switch (reg) {
	case PMBUS_FAN_COMMAND_1:
		return pmbus_write_word_data(client, page, reg, word);
	default:
		return -EACCES;
	}
}

static struct pmbus_driver_info dps920ab_info = {
	.pages = 1,

	.format[PSC_VOLTAGE_IN] = linear,
	.format[PSC_VOLTAGE_OUT] = linear,
	.format[PSC_CURRENT_IN] = linear,
	.format[PSC_CURRENT_OUT] = linear,
	.format[PSC_POWER] = linear,
	.format[PSC_FAN] = linear,
	.format[PSC_TEMPERATURE] = linear,

	.func[0] =
		PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN |
		PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT |
		PMBUS_HAVE_TEMP  | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 |
		PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 |
		PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
		PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
	.read_word_data = dps920ab_read_word_data,
	.write_word_data = dps920ab_write_word_data,
};

static int dps920ab_mfr_id_show(struct seq_file *s, void *data)
{
	struct dps920ab_data *priv = s->private;

	seq_printf(s, "%s\n", priv->mfr_id);

	return 0;
}

DEFINE_SHOW_ATTRIBUTE(dps920ab_mfr_id);

static int dps920ab_mfr_model_show(struct seq_file *s, void *data)
{
	struct dps920ab_data *priv = s->private;

	seq_printf(s, "%s\n", priv->mfr_model);

	return 0;
}

DEFINE_SHOW_ATTRIBUTE(dps920ab_mfr_model);

static void dps920ab_init_debugfs(struct dps920ab_data *data, struct i2c_client *client)
{
	struct dentry *debugfs_dir;
	struct dentry *root;

	root = pmbus_get_debugfs_dir(client);
	if (!root)
		return;

	debugfs_dir = debugfs_create_dir(client->name, root);

	debugfs_create_file("mfr_id",
			    0400,
			    debugfs_dir,
			    data,
			    &dps920ab_mfr_id_fops);

	debugfs_create_file("mfr_model",
			    0400,
			    debugfs_dir,
			    data,
			    &dps920ab_mfr_model_fops);
}

static int dps920ab_probe(struct i2c_client *client)
{
	u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
	struct dps920ab_data *data;
	int ret;

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

	ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
	if (ret < 0) {
		dev_err(&client->dev, "Failed to read Manufacturer ID\n");
		return ret;
	}

	buf[ret] = '\0';
	if (ret != 5 || strncmp(buf, "DELTA", 5)) {
		buf[ret] = '\0';
		dev_err(&client->dev, "Unsupported Manufacturer ID '%s'\n", buf);
		return -ENODEV;
	}
	data->mfr_id = devm_kstrdup(&client->dev, buf, GFP_KERNEL);
	if (!data->mfr_id)
		return -ENOMEM;

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

	buf[ret] = '\0';
	if (ret != 11 || strncmp(buf, "DPS-920AB", 9)) {
		dev_err(&client->dev, "Unsupported Manufacturer Model '%s'\n", buf);
		return -ENODEV;
	}
	data->mfr_model = devm_kstrdup(&client->dev, buf, GFP_KERNEL);
	if (!data->mfr_model)
		return -ENOMEM;

	ret = pmbus_do_probe(client, &dps920ab_info);
	if (ret)
		return ret;

	dps920ab_init_debugfs(data, client);

	return 0;
}

static const struct of_device_id __maybe_unused dps920ab_of_match[] = {
	{ .compatible = "delta,dps920ab", },
	{}
};

MODULE_DEVICE_TABLE(of, dps920ab_of_match);

static struct i2c_driver dps920ab_driver = {
	.driver = {
		   .name = "dps920ab",
		   .of_match_table = of_match_ptr(dps920ab_of_match),
	},
	.probe = dps920ab_probe,
};

module_i2c_driver(dps920ab_driver);

MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>");
MODULE_DESCRIPTION("PMBus driver for Delta DPS920AB PSU");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(PMBUS);