Blame view

drivers/hwmon/thmc50.c 14 KB
add77c64c   Krzysztof Helt   hwmon: add suppor...
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
  /*
      thmc50.c - Part of lm_sensors, Linux kernel modules for hardware
               monitoring
      Copyright (C) 2007 Krzysztof Helt <krzysztof.h1@wp.pl>
      Based on 2.4 driver by Frodo Looijaard <frodol@dds.nl> and
      Philip Edelbrock <phil@netroedge.com>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation; either version 2 of the License, or
      (at your option) any later version.
  
      This program is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with this program; if not, write to the Free Software
      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
  
  #include <linux/module.h>
  #include <linux/init.h>
  #include <linux/slab.h>
  #include <linux/i2c.h>
  #include <linux/hwmon.h>
  #include <linux/hwmon-sysfs.h>
  #include <linux/err.h>
  #include <linux/mutex.h>
  
  MODULE_LICENSE("GPL");
  
  /* Addresses to scan */
25e9c86d5   Mark M. Hoffman   hwmon: normal_i2c...
35
  static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END };
add77c64c   Krzysztof Helt   hwmon: add suppor...
36
37
  
  /* Insmod parameters */
e5e9f44c2   Jean Delvare   i2c: Drop I2C_CLI...
38
  enum chips { thmc50, adm1022 };
f95f0b4ce   Jean Delvare   hwmon: (thmc50) S...
39
40
41
42
43
  
  static unsigned short adm1022_temp3[16];
  static unsigned int adm1022_temp3_num;
  module_param_array(adm1022_temp3, ushort, &adm1022_temp3_num, 0);
  MODULE_PARM_DESC(adm1022_temp3, "List of adapter,address pairs "
add77c64c   Krzysztof Helt   hwmon: add suppor...
44
45
46
47
48
49
50
51
52
  			"to enable 3rd temperature (ADM1022 only)");
  
  /* Many THMC50 constants specified below */
  
  /* The THMC50 registers */
  #define THMC50_REG_CONF				0x40
  #define THMC50_REG_COMPANY_ID			0x3E
  #define THMC50_REG_DIE_CODE			0x3F
  #define THMC50_REG_ANALOG_OUT			0x19
dcf3b5fb7   Krzysztof Helt   hwmon: (thmc50) a...
53
  /*
bba891c24   Krzysztof Helt   hwmon: (thmc50) F...
54
55
   * The mirror status register cannot be used as
   * reading it does not clear alarms.
dcf3b5fb7   Krzysztof Helt   hwmon: (thmc50) a...
56
   */
bba891c24   Krzysztof Helt   hwmon: (thmc50) F...
57
  #define THMC50_REG_INTR				0x41
add77c64c   Krzysztof Helt   hwmon: add suppor...
58

5910a9b2b   Tobias Klauser   hwmon: (thmc50) S...
59
60
61
  static const u8 THMC50_REG_TEMP[] = { 0x27, 0x26, 0x20 };
  static const u8 THMC50_REG_TEMP_MIN[] = { 0x3A, 0x38, 0x2C };
  static const u8 THMC50_REG_TEMP_MAX[] = { 0x39, 0x37, 0x2B };
84f768c16   Krzysztof Helt   hwmon: (thmc50) A...
62
63
  static const u8 THMC50_REG_TEMP_CRITICAL[] = { 0x13, 0x14, 0x14 };
  static const u8 THMC50_REG_TEMP_DEFAULT[] = { 0x17, 0x18, 0x18 };
add77c64c   Krzysztof Helt   hwmon: add suppor...
64
65
  
  #define THMC50_REG_CONF_nFANOFF			0x20
84f768c16   Krzysztof Helt   hwmon: (thmc50) A...
66
  #define THMC50_REG_CONF_PROGRAMMED		0x08
add77c64c   Krzysztof Helt   hwmon: add suppor...
67
68
69
  
  /* Each client has this additional data */
  struct thmc50_data {
1beeffe43   Tony Jones   hwmon: Convert fr...
70
  	struct device *hwmon_dev;
add77c64c   Krzysztof Helt   hwmon: add suppor...
71
72
73
74
75
76
77
78
79
80
81
  
  	struct mutex update_lock;
  	enum chips type;
  	unsigned long last_updated;	/* In jiffies */
  	char has_temp3;		/* !=0 if it is ADM1022 in temp3 mode */
  	char valid;		/* !=0 if following fields are valid */
  
  	/* Register values */
  	s8 temp_input[3];
  	s8 temp_max[3];
  	s8 temp_min[3];
84f768c16   Krzysztof Helt   hwmon: (thmc50) A...
82
  	s8 temp_critical[3];
add77c64c   Krzysztof Helt   hwmon: add suppor...
83
  	u8 analog_out;
dcf3b5fb7   Krzysztof Helt   hwmon: (thmc50) a...
84
  	u8 alarms;
add77c64c   Krzysztof Helt   hwmon: add suppor...
85
  };
310ec7921   Jean Delvare   i2c: Drop the kin...
86
  static int thmc50_detect(struct i2c_client *client,
ccf374883   Jean Delvare   hwmon: (thmc50) C...
87
88
89
90
  			 struct i2c_board_info *info);
  static int thmc50_probe(struct i2c_client *client,
  			const struct i2c_device_id *id);
  static int thmc50_remove(struct i2c_client *client);
add77c64c   Krzysztof Helt   hwmon: add suppor...
91
92
  static void thmc50_init_client(struct i2c_client *client);
  static struct thmc50_data *thmc50_update_device(struct device *dev);
ccf374883   Jean Delvare   hwmon: (thmc50) C...
93
94
95
96
97
98
  static const struct i2c_device_id thmc50_id[] = {
  	{ "adm1022", adm1022 },
  	{ "thmc50", thmc50 },
  	{ }
  };
  MODULE_DEVICE_TABLE(i2c, thmc50_id);
add77c64c   Krzysztof Helt   hwmon: add suppor...
99
  static struct i2c_driver thmc50_driver = {
ccf374883   Jean Delvare   hwmon: (thmc50) C...
100
  	.class = I2C_CLASS_HWMON,
add77c64c   Krzysztof Helt   hwmon: add suppor...
101
102
103
  	.driver = {
  		.name = "thmc50",
  	},
ccf374883   Jean Delvare   hwmon: (thmc50) C...
104
105
106
107
  	.probe = thmc50_probe,
  	.remove = thmc50_remove,
  	.id_table = thmc50_id,
  	.detect = thmc50_detect,
c3813d6af   Jean Delvare   i2c: Get rid of s...
108
  	.address_list = normal_i2c,
add77c64c   Krzysztof Helt   hwmon: add suppor...
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
207
208
209
210
  };
  
  static ssize_t show_analog_out(struct device *dev,
  			       struct device_attribute *attr, char *buf)
  {
  	struct thmc50_data *data = thmc50_update_device(dev);
  	return sprintf(buf, "%d
  ", data->analog_out);
  }
  
  static ssize_t set_analog_out(struct device *dev,
  			      struct device_attribute *attr,
  			      const char *buf, size_t count)
  {
  	struct i2c_client *client = to_i2c_client(dev);
  	struct thmc50_data *data = i2c_get_clientdata(client);
  	int tmp = simple_strtoul(buf, NULL, 10);
  	int config;
  
  	mutex_lock(&data->update_lock);
  	data->analog_out = SENSORS_LIMIT(tmp, 0, 255);
  	i2c_smbus_write_byte_data(client, THMC50_REG_ANALOG_OUT,
  				  data->analog_out);
  
  	config = i2c_smbus_read_byte_data(client, THMC50_REG_CONF);
  	if (data->analog_out == 0)
  		config &= ~THMC50_REG_CONF_nFANOFF;
  	else
  		config |= THMC50_REG_CONF_nFANOFF;
  	i2c_smbus_write_byte_data(client, THMC50_REG_CONF, config);
  
  	mutex_unlock(&data->update_lock);
  	return count;
  }
  
  /* There is only one PWM mode = DC */
  static ssize_t show_pwm_mode(struct device *dev, struct device_attribute *attr,
  			     char *buf)
  {
  	return sprintf(buf, "0
  ");
  }
  
  /* Temperatures */
  static ssize_t show_temp(struct device *dev, struct device_attribute *attr,
  			 char *buf)
  {
  	int nr = to_sensor_dev_attr(attr)->index;
  	struct thmc50_data *data = thmc50_update_device(dev);
  	return sprintf(buf, "%d
  ", data->temp_input[nr] * 1000);
  }
  
  static ssize_t show_temp_min(struct device *dev, struct device_attribute *attr,
  			     char *buf)
  {
  	int nr = to_sensor_dev_attr(attr)->index;
  	struct thmc50_data *data = thmc50_update_device(dev);
  	return sprintf(buf, "%d
  ", data->temp_min[nr] * 1000);
  }
  
  static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr,
  			    const char *buf, size_t count)
  {
  	int nr = to_sensor_dev_attr(attr)->index;
  	struct i2c_client *client = to_i2c_client(dev);
  	struct thmc50_data *data = i2c_get_clientdata(client);
  	int val = simple_strtol(buf, NULL, 10);
  
  	mutex_lock(&data->update_lock);
  	data->temp_min[nr] = SENSORS_LIMIT(val / 1000, -128, 127);
  	i2c_smbus_write_byte_data(client, THMC50_REG_TEMP_MIN[nr],
  				  data->temp_min[nr]);
  	mutex_unlock(&data->update_lock);
  	return count;
  }
  
  static ssize_t show_temp_max(struct device *dev, struct device_attribute *attr,
  			     char *buf)
  {
  	int nr = to_sensor_dev_attr(attr)->index;
  	struct thmc50_data *data = thmc50_update_device(dev);
  	return sprintf(buf, "%d
  ", data->temp_max[nr] * 1000);
  }
  
  static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr,
  			    const char *buf, size_t count)
  {
  	int nr = to_sensor_dev_attr(attr)->index;
  	struct i2c_client *client = to_i2c_client(dev);
  	struct thmc50_data *data = i2c_get_clientdata(client);
  	int val = simple_strtol(buf, NULL, 10);
  
  	mutex_lock(&data->update_lock);
  	data->temp_max[nr] = SENSORS_LIMIT(val / 1000, -128, 127);
  	i2c_smbus_write_byte_data(client, THMC50_REG_TEMP_MAX[nr],
  				  data->temp_max[nr]);
  	mutex_unlock(&data->update_lock);
  	return count;
  }
84f768c16   Krzysztof Helt   hwmon: (thmc50) A...
211
212
213
214
215
216
217
218
219
  static ssize_t show_temp_critical(struct device *dev,
  				  struct device_attribute *attr,
  				  char *buf)
  {
  	int nr = to_sensor_dev_attr(attr)->index;
  	struct thmc50_data *data = thmc50_update_device(dev);
  	return sprintf(buf, "%d
  ", data->temp_critical[nr] * 1000);
  }
dcf3b5fb7   Krzysztof Helt   hwmon: (thmc50) a...
220
221
222
223
224
225
226
227
228
  static ssize_t show_alarm(struct device *dev, struct device_attribute *attr,
  			  char *buf)
  {
  	int index = to_sensor_dev_attr(attr)->index;
  	struct thmc50_data *data = thmc50_update_device(dev);
  
  	return sprintf(buf, "%u
  ", (data->alarms >> index) & 1);
  }
add77c64c   Krzysztof Helt   hwmon: add suppor...
229
230
231
232
233
234
  #define temp_reg(offset)						\
  static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, show_temp,	\
  			NULL, offset - 1);				\
  static SENSOR_DEVICE_ATTR(temp##offset##_min, S_IRUGO | S_IWUSR,	\
  			show_temp_min, set_temp_min, offset - 1);	\
  static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR,	\
84f768c16   Krzysztof Helt   hwmon: (thmc50) A...
235
236
237
  			show_temp_max, set_temp_max, offset - 1);	\
  static SENSOR_DEVICE_ATTR(temp##offset##_crit, S_IRUGO,			\
  			show_temp_critical, NULL, offset - 1);
add77c64c   Krzysztof Helt   hwmon: add suppor...
238
239
240
241
  
  temp_reg(1);
  temp_reg(2);
  temp_reg(3);
dcf3b5fb7   Krzysztof Helt   hwmon: (thmc50) a...
242
243
244
245
246
  static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 0);
  static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 5);
  static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 1);
  static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 7);
  static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 2);
add77c64c   Krzysztof Helt   hwmon: add suppor...
247
248
249
250
251
252
253
254
  static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_analog_out,
  			  set_analog_out, 0);
  static SENSOR_DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL, 0);
  
  static struct attribute *thmc50_attributes[] = {
  	&sensor_dev_attr_temp1_max.dev_attr.attr,
  	&sensor_dev_attr_temp1_min.dev_attr.attr,
  	&sensor_dev_attr_temp1_input.dev_attr.attr,
84f768c16   Krzysztof Helt   hwmon: (thmc50) A...
255
  	&sensor_dev_attr_temp1_crit.dev_attr.attr,
dcf3b5fb7   Krzysztof Helt   hwmon: (thmc50) a...
256
  	&sensor_dev_attr_temp1_alarm.dev_attr.attr,
add77c64c   Krzysztof Helt   hwmon: add suppor...
257
258
259
  	&sensor_dev_attr_temp2_max.dev_attr.attr,
  	&sensor_dev_attr_temp2_min.dev_attr.attr,
  	&sensor_dev_attr_temp2_input.dev_attr.attr,
84f768c16   Krzysztof Helt   hwmon: (thmc50) A...
260
  	&sensor_dev_attr_temp2_crit.dev_attr.attr,
dcf3b5fb7   Krzysztof Helt   hwmon: (thmc50) a...
261
262
  	&sensor_dev_attr_temp2_alarm.dev_attr.attr,
  	&sensor_dev_attr_temp2_fault.dev_attr.attr,
add77c64c   Krzysztof Helt   hwmon: add suppor...
263
264
265
266
267
268
269
270
271
272
  	&sensor_dev_attr_pwm1.dev_attr.attr,
  	&sensor_dev_attr_pwm1_mode.dev_attr.attr,
  	NULL
  };
  
  static const struct attribute_group thmc50_group = {
  	.attrs = thmc50_attributes,
  };
  
  /* for ADM1022 3rd temperature mode */
894c00cf3   Jean Delvare   hwmon: (thmc50) D...
273
  static struct attribute *temp3_attributes[] = {
add77c64c   Krzysztof Helt   hwmon: add suppor...
274
275
276
  	&sensor_dev_attr_temp3_max.dev_attr.attr,
  	&sensor_dev_attr_temp3_min.dev_attr.attr,
  	&sensor_dev_attr_temp3_input.dev_attr.attr,
84f768c16   Krzysztof Helt   hwmon: (thmc50) A...
277
  	&sensor_dev_attr_temp3_crit.dev_attr.attr,
dcf3b5fb7   Krzysztof Helt   hwmon: (thmc50) a...
278
279
  	&sensor_dev_attr_temp3_alarm.dev_attr.attr,
  	&sensor_dev_attr_temp3_fault.dev_attr.attr,
add77c64c   Krzysztof Helt   hwmon: add suppor...
280
281
  	NULL
  };
894c00cf3   Jean Delvare   hwmon: (thmc50) D...
282
283
  static const struct attribute_group temp3_group = {
  	.attrs = temp3_attributes,
add77c64c   Krzysztof Helt   hwmon: add suppor...
284
  };
ccf374883   Jean Delvare   hwmon: (thmc50) C...
285
  /* Return 0 if detection is successful, -ENODEV otherwise */
310ec7921   Jean Delvare   i2c: Drop the kin...
286
  static int thmc50_detect(struct i2c_client *client,
ccf374883   Jean Delvare   hwmon: (thmc50) C...
287
  			 struct i2c_board_info *info)
add77c64c   Krzysztof Helt   hwmon: add suppor...
288
289
290
291
  {
  	unsigned company;
  	unsigned revision;
  	unsigned config;
ccf374883   Jean Delvare   hwmon: (thmc50) C...
292
  	struct i2c_adapter *adapter = client->adapter;
cc28a610d   Jean Delvare   hwmon: (thmc50) F...
293
  	const char *type_name;
add77c64c   Krzysztof Helt   hwmon: add suppor...
294
295
296
297
298
  
  	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
  		pr_debug("thmc50: detect failed, "
  			 "smbus byte data not supported!
  ");
ccf374883   Jean Delvare   hwmon: (thmc50) C...
299
  		return -ENODEV;
add77c64c   Krzysztof Helt   hwmon: add suppor...
300
  	}
add77c64c   Krzysztof Helt   hwmon: add suppor...
301
302
303
  	pr_debug("thmc50: Probing for THMC50 at 0x%2X on bus %d
  ",
  		 client->addr, i2c_adapter_id(client->adapter));
add77c64c   Krzysztof Helt   hwmon: add suppor...
304
305
306
  	company = i2c_smbus_read_byte_data(client, THMC50_REG_COMPANY_ID);
  	revision = i2c_smbus_read_byte_data(client, THMC50_REG_DIE_CODE);
  	config = i2c_smbus_read_byte_data(client, THMC50_REG_CONF);
52df6440a   Jean Delvare   hwmon: Clean up d...
307
308
  	if (revision < 0xc0 || (config & 0x10))
  		return -ENODEV;
add77c64c   Krzysztof Helt   hwmon: add suppor...
309

52df6440a   Jean Delvare   hwmon: Clean up d...
310
  	if (company == 0x41) {
add77c64c   Krzysztof Helt   hwmon: add suppor...
311
312
313
314
  		int id = i2c_adapter_id(client->adapter);
  		int i;
  
  		type_name = "adm1022";
add77c64c   Krzysztof Helt   hwmon: add suppor...
315
316
  		for (i = 0; i + 1 < adm1022_temp3_num; i += 2)
  			if (adm1022_temp3[i] == id &&
ccf374883   Jean Delvare   hwmon: (thmc50) C...
317
  			    adm1022_temp3[i + 1] == client->addr) {
add77c64c   Krzysztof Helt   hwmon: add suppor...
318
  				/* enable 2nd remote temp */
ccf374883   Jean Delvare   hwmon: (thmc50) C...
319
320
321
322
  				config |= (1 << 7);
  				i2c_smbus_write_byte_data(client,
  							  THMC50_REG_CONF,
  							  config);
add77c64c   Krzysztof Helt   hwmon: add suppor...
323
324
  				break;
  			}
52df6440a   Jean Delvare   hwmon: Clean up d...
325
  	} else if (company == 0x49) {
cc28a610d   Jean Delvare   hwmon: (thmc50) F...
326
  		type_name = "thmc50";
52df6440a   Jean Delvare   hwmon: Clean up d...
327
328
329
330
  	} else {
  		pr_debug("thmc50: Detection of THMC50/ADM1022 failed
  ");
  		return -ENODEV;
add77c64c   Krzysztof Helt   hwmon: add suppor...
331
  	}
52df6440a   Jean Delvare   hwmon: Clean up d...
332

cc28a610d   Jean Delvare   hwmon: (thmc50) F...
333
334
335
  	pr_debug("thmc50: Detected %s (version %x, revision %x)
  ",
  		 type_name, (revision >> 4) - 0xc, revision & 0xf);
add77c64c   Krzysztof Helt   hwmon: add suppor...
336

ccf374883   Jean Delvare   hwmon: (thmc50) C...
337
  	strlcpy(info->type, type_name, I2C_NAME_SIZE);
add77c64c   Krzysztof Helt   hwmon: add suppor...
338

ccf374883   Jean Delvare   hwmon: (thmc50) C...
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
  	return 0;
  }
  
  static int thmc50_probe(struct i2c_client *client,
  			const struct i2c_device_id *id)
  {
  	struct thmc50_data *data;
  	int err;
  
  	data = kzalloc(sizeof(struct thmc50_data), GFP_KERNEL);
  	if (!data) {
  		pr_debug("thmc50: detect failed, kzalloc failed!
  ");
  		err = -ENOMEM;
  		goto exit;
  	}
  
  	i2c_set_clientdata(client, data);
  	data->type = id->driver_data;
  	mutex_init(&data->update_lock);
add77c64c   Krzysztof Helt   hwmon: add suppor...
359
360
361
362
363
  
  	thmc50_init_client(client);
  
  	/* Register sysfs hooks */
  	if ((err = sysfs_create_group(&client->dev.kobj, &thmc50_group)))
ccf374883   Jean Delvare   hwmon: (thmc50) C...
364
  		goto exit_free;
add77c64c   Krzysztof Helt   hwmon: add suppor...
365
366
  
  	/* Register ADM1022 sysfs hooks */
894c00cf3   Jean Delvare   hwmon: (thmc50) D...
367
  	if (data->has_temp3)
add77c64c   Krzysztof Helt   hwmon: add suppor...
368
  		if ((err = sysfs_create_group(&client->dev.kobj,
894c00cf3   Jean Delvare   hwmon: (thmc50) D...
369
  					      &temp3_group)))
add77c64c   Krzysztof Helt   hwmon: add suppor...
370
371
372
  			goto exit_remove_sysfs_thmc50;
  
  	/* Register a new directory entry with module sensors */
1beeffe43   Tony Jones   hwmon: Convert fr...
373
374
375
  	data->hwmon_dev = hwmon_device_register(&client->dev);
  	if (IS_ERR(data->hwmon_dev)) {
  		err = PTR_ERR(data->hwmon_dev);
add77c64c   Krzysztof Helt   hwmon: add suppor...
376
377
378
379
380
381
  		goto exit_remove_sysfs;
  	}
  
  	return 0;
  
  exit_remove_sysfs:
894c00cf3   Jean Delvare   hwmon: (thmc50) D...
382
383
  	if (data->has_temp3)
  		sysfs_remove_group(&client->dev.kobj, &temp3_group);
add77c64c   Krzysztof Helt   hwmon: add suppor...
384
385
  exit_remove_sysfs_thmc50:
  	sysfs_remove_group(&client->dev.kobj, &thmc50_group);
add77c64c   Krzysztof Helt   hwmon: add suppor...
386
387
388
389
390
  exit_free:
  	kfree(data);
  exit:
  	return err;
  }
ccf374883   Jean Delvare   hwmon: (thmc50) C...
391
  static int thmc50_remove(struct i2c_client *client)
add77c64c   Krzysztof Helt   hwmon: add suppor...
392
393
  {
  	struct thmc50_data *data = i2c_get_clientdata(client);
add77c64c   Krzysztof Helt   hwmon: add suppor...
394

1beeffe43   Tony Jones   hwmon: Convert fr...
395
  	hwmon_device_unregister(data->hwmon_dev);
add77c64c   Krzysztof Helt   hwmon: add suppor...
396
  	sysfs_remove_group(&client->dev.kobj, &thmc50_group);
894c00cf3   Jean Delvare   hwmon: (thmc50) D...
397
398
  	if (data->has_temp3)
  		sysfs_remove_group(&client->dev.kobj, &temp3_group);
add77c64c   Krzysztof Helt   hwmon: add suppor...
399

add77c64c   Krzysztof Helt   hwmon: add suppor...
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
  	kfree(data);
  
  	return 0;
  }
  
  static void thmc50_init_client(struct i2c_client *client)
  {
  	struct thmc50_data *data = i2c_get_clientdata(client);
  	int config;
  
  	data->analog_out = i2c_smbus_read_byte_data(client,
  						    THMC50_REG_ANALOG_OUT);
  	/* set up to at least 1 */
  	if (data->analog_out == 0) {
  		data->analog_out = 1;
  		i2c_smbus_write_byte_data(client, THMC50_REG_ANALOG_OUT,
  					  data->analog_out);
  	}
  	config = i2c_smbus_read_byte_data(client, THMC50_REG_CONF);
  	config |= 0x1;	/* start the chip if it is in standby mode */
ccf374883   Jean Delvare   hwmon: (thmc50) C...
420
421
  	if (data->type == adm1022 && (config & (1 << 7)))
  		data->has_temp3 = 1;
add77c64c   Krzysztof Helt   hwmon: add suppor...
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
  	i2c_smbus_write_byte_data(client, THMC50_REG_CONF, config);
  }
  
  static struct thmc50_data *thmc50_update_device(struct device *dev)
  {
  	struct i2c_client *client = to_i2c_client(dev);
  	struct thmc50_data *data = i2c_get_clientdata(client);
  	int timeout = HZ / 5 + (data->type == thmc50 ? HZ : 0);
  
  	mutex_lock(&data->update_lock);
  
  	if (time_after(jiffies, data->last_updated + timeout)
  	    || !data->valid) {
  
  		int temps = data->has_temp3 ? 3 : 2;
  		int i;
84f768c16   Krzysztof Helt   hwmon: (thmc50) A...
438
439
440
  		int prog = i2c_smbus_read_byte_data(client, THMC50_REG_CONF);
  
  		prog &= THMC50_REG_CONF_PROGRAMMED;
add77c64c   Krzysztof Helt   hwmon: add suppor...
441
442
443
444
445
446
447
  		for (i = 0; i < temps; i++) {
  			data->temp_input[i] = i2c_smbus_read_byte_data(client,
  						THMC50_REG_TEMP[i]);
  			data->temp_max[i] = i2c_smbus_read_byte_data(client,
  						THMC50_REG_TEMP_MAX[i]);
  			data->temp_min[i] = i2c_smbus_read_byte_data(client,
  						THMC50_REG_TEMP_MIN[i]);
84f768c16   Krzysztof Helt   hwmon: (thmc50) A...
448
449
450
451
  			data->temp_critical[i] =
  				i2c_smbus_read_byte_data(client,
  					prog ? THMC50_REG_TEMP_CRITICAL[i]
  					     : THMC50_REG_TEMP_DEFAULT[i]);
add77c64c   Krzysztof Helt   hwmon: add suppor...
452
453
454
  		}
  		data->analog_out =
  		    i2c_smbus_read_byte_data(client, THMC50_REG_ANALOG_OUT);
dcf3b5fb7   Krzysztof Helt   hwmon: (thmc50) a...
455
  		data->alarms =
bba891c24   Krzysztof Helt   hwmon: (thmc50) F...
456
  		    i2c_smbus_read_byte_data(client, THMC50_REG_INTR);
add77c64c   Krzysztof Helt   hwmon: add suppor...
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
  		data->last_updated = jiffies;
  		data->valid = 1;
  	}
  
  	mutex_unlock(&data->update_lock);
  
  	return data;
  }
  
  static int __init sm_thmc50_init(void)
  {
  	return i2c_add_driver(&thmc50_driver);
  }
  
  static void __exit sm_thmc50_exit(void)
  {
  	i2c_del_driver(&thmc50_driver);
  }
  
  MODULE_AUTHOR("Krzysztof Helt <krzysztof.h1@wp.pl>");
  MODULE_DESCRIPTION("THMC50 driver");
  
  module_init(sm_thmc50_init);
  module_exit(sm_thmc50_exit);