1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * ds620.c - Support for temperature sensor and thermostat DS620 |
4 | * |
5 | * Copyright (C) 2010, 2011 Roland Stigge <stigge@antcom.de> |
6 | * |
7 | * based on ds1621.c by Christian W. Zuckschwerdt <zany@triq.net> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/init.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/jiffies.h> |
14 | #include <linux/i2c.h> |
15 | #include <linux/hwmon.h> |
16 | #include <linux/hwmon-sysfs.h> |
17 | #include <linux/err.h> |
18 | #include <linux/mutex.h> |
19 | #include <linux/sysfs.h> |
20 | #include <linux/platform_data/ds620.h> |
21 | |
22 | /* |
23 | * Many DS620 constants specified below |
24 | * 15 14 13 12 11 10 09 08 |
25 | * |Done|NVB |THF |TLF |R1 |R0 |AUTOC|1SHOT| |
26 | * |
27 | * 07 06 05 04 03 02 01 00 |
28 | * |PO2 |PO1 |A2 |A1 |A0 | | | | |
29 | */ |
30 | #define DS620_REG_CONFIG_DONE 0x8000 |
31 | #define DS620_REG_CONFIG_NVB 0x4000 |
32 | #define DS620_REG_CONFIG_THF 0x2000 |
33 | #define DS620_REG_CONFIG_TLF 0x1000 |
34 | #define DS620_REG_CONFIG_R1 0x0800 |
35 | #define DS620_REG_CONFIG_R0 0x0400 |
36 | #define DS620_REG_CONFIG_AUTOC 0x0200 |
37 | #define DS620_REG_CONFIG_1SHOT 0x0100 |
38 | #define DS620_REG_CONFIG_PO2 0x0080 |
39 | #define DS620_REG_CONFIG_PO1 0x0040 |
40 | #define DS620_REG_CONFIG_A2 0x0020 |
41 | #define DS620_REG_CONFIG_A1 0x0010 |
42 | #define DS620_REG_CONFIG_A0 0x0008 |
43 | |
44 | /* The DS620 registers */ |
45 | static const u8 DS620_REG_TEMP[3] = { |
46 | 0xAA, /* input, word, RO */ |
47 | 0xA2, /* min, word, RW */ |
48 | 0xA0, /* max, word, RW */ |
49 | }; |
50 | |
51 | #define DS620_REG_CONF 0xAC /* word, RW */ |
52 | #define DS620_COM_START 0x51 /* no data */ |
53 | #define DS620_COM_STOP 0x22 /* no data */ |
54 | |
55 | /* Each client has this additional data */ |
56 | struct ds620_data { |
57 | struct i2c_client *client; |
58 | struct mutex update_lock; |
59 | bool valid; /* true if following fields are valid */ |
60 | unsigned long last_updated; /* In jiffies */ |
61 | |
62 | s16 temp[3]; /* Register values, word */ |
63 | }; |
64 | |
65 | static void ds620_init_client(struct i2c_client *client) |
66 | { |
67 | struct ds620_platform_data *ds620_info = dev_get_platdata(dev: &client->dev); |
68 | u16 conf, new_conf; |
69 | |
70 | new_conf = conf = |
71 | i2c_smbus_read_word_swapped(client, DS620_REG_CONF); |
72 | |
73 | /* switch to continuous conversion mode */ |
74 | new_conf &= ~DS620_REG_CONFIG_1SHOT; |
75 | /* already high at power-on, but don't trust the BIOS! */ |
76 | new_conf |= DS620_REG_CONFIG_PO2; |
77 | /* thermostat mode according to platform data */ |
78 | if (ds620_info && ds620_info->pomode == 1) |
79 | new_conf &= ~DS620_REG_CONFIG_PO1; /* PO_LOW */ |
80 | else if (ds620_info && ds620_info->pomode == 2) |
81 | new_conf |= DS620_REG_CONFIG_PO1; /* PO_HIGH */ |
82 | else |
83 | new_conf &= ~DS620_REG_CONFIG_PO2; /* always low */ |
84 | /* with highest precision */ |
85 | new_conf |= DS620_REG_CONFIG_R1 | DS620_REG_CONFIG_R0; |
86 | |
87 | if (conf != new_conf) |
88 | i2c_smbus_write_word_swapped(client, DS620_REG_CONF, value: new_conf); |
89 | |
90 | /* start conversion */ |
91 | i2c_smbus_write_byte(client, DS620_COM_START); |
92 | } |
93 | |
94 | static struct ds620_data *ds620_update_client(struct device *dev) |
95 | { |
96 | struct ds620_data *data = dev_get_drvdata(dev); |
97 | struct i2c_client *client = data->client; |
98 | struct ds620_data *ret = data; |
99 | |
100 | mutex_lock(&data->update_lock); |
101 | |
102 | if (time_after(jiffies, data->last_updated + HZ + HZ / 2) |
103 | || !data->valid) { |
104 | int i; |
105 | int res; |
106 | |
107 | dev_dbg(&client->dev, "Starting ds620 update\n" ); |
108 | |
109 | for (i = 0; i < ARRAY_SIZE(data->temp); i++) { |
110 | res = i2c_smbus_read_word_swapped(client, |
111 | command: DS620_REG_TEMP[i]); |
112 | if (res < 0) { |
113 | ret = ERR_PTR(error: res); |
114 | goto abort; |
115 | } |
116 | |
117 | data->temp[i] = res; |
118 | } |
119 | |
120 | data->last_updated = jiffies; |
121 | data->valid = true; |
122 | } |
123 | abort: |
124 | mutex_unlock(lock: &data->update_lock); |
125 | |
126 | return ret; |
127 | } |
128 | |
129 | static ssize_t temp_show(struct device *dev, struct device_attribute *da, |
130 | char *buf) |
131 | { |
132 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); |
133 | struct ds620_data *data = ds620_update_client(dev); |
134 | |
135 | if (IS_ERR(ptr: data)) |
136 | return PTR_ERR(ptr: data); |
137 | |
138 | return sprintf(buf, fmt: "%d\n" , ((data->temp[attr->index] / 8) * 625) / 10); |
139 | } |
140 | |
141 | static ssize_t temp_store(struct device *dev, struct device_attribute *da, |
142 | const char *buf, size_t count) |
143 | { |
144 | int res; |
145 | long val; |
146 | |
147 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); |
148 | struct ds620_data *data = dev_get_drvdata(dev); |
149 | struct i2c_client *client = data->client; |
150 | |
151 | res = kstrtol(s: buf, base: 10, res: &val); |
152 | |
153 | if (res) |
154 | return res; |
155 | |
156 | val = (clamp_val(val, -128000, 128000) * 10 / 625) * 8; |
157 | |
158 | mutex_lock(&data->update_lock); |
159 | data->temp[attr->index] = val; |
160 | i2c_smbus_write_word_swapped(client, command: DS620_REG_TEMP[attr->index], |
161 | value: data->temp[attr->index]); |
162 | mutex_unlock(lock: &data->update_lock); |
163 | return count; |
164 | } |
165 | |
166 | static ssize_t alarm_show(struct device *dev, struct device_attribute *da, |
167 | char *buf) |
168 | { |
169 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); |
170 | struct ds620_data *data = ds620_update_client(dev); |
171 | struct i2c_client *client; |
172 | u16 conf, new_conf; |
173 | int res; |
174 | |
175 | if (IS_ERR(ptr: data)) |
176 | return PTR_ERR(ptr: data); |
177 | |
178 | client = data->client; |
179 | |
180 | /* reset alarms if necessary */ |
181 | res = i2c_smbus_read_word_swapped(client, DS620_REG_CONF); |
182 | if (res < 0) |
183 | return res; |
184 | |
185 | new_conf = conf = res; |
186 | new_conf &= ~attr->index; |
187 | if (conf != new_conf) { |
188 | res = i2c_smbus_write_word_swapped(client, DS620_REG_CONF, |
189 | value: new_conf); |
190 | if (res < 0) |
191 | return res; |
192 | } |
193 | |
194 | return sprintf(buf, fmt: "%d\n" , !!(conf & attr->index)); |
195 | } |
196 | |
197 | static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0); |
198 | static SENSOR_DEVICE_ATTR_RW(temp1_min, temp, 1); |
199 | static SENSOR_DEVICE_ATTR_RW(temp1_max, temp, 2); |
200 | static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, alarm, DS620_REG_CONFIG_TLF); |
201 | static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, DS620_REG_CONFIG_THF); |
202 | |
203 | static struct attribute *ds620_attrs[] = { |
204 | &sensor_dev_attr_temp1_input.dev_attr.attr, |
205 | &sensor_dev_attr_temp1_min.dev_attr.attr, |
206 | &sensor_dev_attr_temp1_max.dev_attr.attr, |
207 | &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, |
208 | &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, |
209 | NULL |
210 | }; |
211 | |
212 | ATTRIBUTE_GROUPS(ds620); |
213 | |
214 | static int ds620_probe(struct i2c_client *client) |
215 | { |
216 | struct device *dev = &client->dev; |
217 | struct device *hwmon_dev; |
218 | struct ds620_data *data; |
219 | |
220 | data = devm_kzalloc(dev, size: sizeof(struct ds620_data), GFP_KERNEL); |
221 | if (!data) |
222 | return -ENOMEM; |
223 | |
224 | data->client = client; |
225 | mutex_init(&data->update_lock); |
226 | |
227 | /* Initialize the DS620 chip */ |
228 | ds620_init_client(client); |
229 | |
230 | hwmon_dev = devm_hwmon_device_register_with_groups(dev, name: client->name, |
231 | drvdata: data, groups: ds620_groups); |
232 | return PTR_ERR_OR_ZERO(ptr: hwmon_dev); |
233 | } |
234 | |
235 | static const struct i2c_device_id ds620_id[] = { |
236 | {"ds620" , 0}, |
237 | {} |
238 | }; |
239 | |
240 | MODULE_DEVICE_TABLE(i2c, ds620_id); |
241 | |
242 | /* This is the driver that will be inserted */ |
243 | static struct i2c_driver ds620_driver = { |
244 | .driver = { |
245 | .name = "ds620" , |
246 | }, |
247 | .probe = ds620_probe, |
248 | .id_table = ds620_id, |
249 | }; |
250 | |
251 | module_i2c_driver(ds620_driver); |
252 | |
253 | MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>" ); |
254 | MODULE_DESCRIPTION("DS620 driver" ); |
255 | MODULE_LICENSE("GPL" ); |
256 | |