summaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/ab8500.c
blob: 8b6a4f4c8774b10322ecfb56c6bbe992b693dab2 (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
207
/*
 * Copyright (C) ST-Ericsson 2010 - 2013
 * Author: Martin Persson <martin.persson@stericsson.com>
 *         Hongbo Zhang <hongbo.zhang@linaro.org>
 * License Terms: GNU General Public License v2
 *
 * When the AB8500 thermal warning temperature is reached (threshold cannot
 * be changed by SW), an interrupt is set, and if no further action is taken
 * within a certain time frame, kernel_power_off will be called.
 *
 * When AB8500 thermal shutdown temperature is reached a hardware shutdown of
 * the AB8500 will occur.
 */

#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/mfd/abx500.h>
#include <linux/mfd/abx500/ab8500-bm.h>
#include <linux/mfd/abx500/ab8500-gpadc.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power/ab8500.h>
#include <linux/reboot.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include "abx500.h"

#define DEFAULT_POWER_OFF_DELAY	(HZ * 10)
#define THERMAL_VCC		1800
#define PULL_UP_RESISTOR	47000
/* Number of monitored sensors should not greater than NUM_SENSORS */
#define NUM_MONITORED_SENSORS	4

struct ab8500_gpadc_cfg {
	const struct abx500_res_to_temp *temp_tbl;
	int tbl_sz;
	int vcc;
	int r_up;
};

struct ab8500_temp {
	struct ab8500_gpadc *gpadc;
	struct ab8500_btemp *btemp;
	struct delayed_work power_off_work;
	struct ab8500_gpadc_cfg cfg;
	struct abx500_temp *abx500_data;
};

/*
 * The hardware connection is like this:
 * VCC----[ R_up ]-----[ NTC ]----GND
 * where R_up is pull-up resistance, and GPADC measures voltage on NTC.
 * and res_to_temp table is strictly sorted by falling resistance values.
 */
static int ab8500_voltage_to_temp(struct ab8500_gpadc_cfg *cfg,
		int v_ntc, int *temp)
{
	int r_ntc, i = 0, tbl_sz = cfg->tbl_sz;
	const struct abx500_res_to_temp *tbl = cfg->temp_tbl;

	if (cfg->vcc < 0 || v_ntc >= cfg->vcc)
		return -EINVAL;

	r_ntc = v_ntc * cfg->r_up / (cfg->vcc - v_ntc);
	if (r_ntc > tbl[0].resist || r_ntc < tbl[tbl_sz - 1].resist)
		return -EINVAL;

	while (!(r_ntc <= tbl[i].resist && r_ntc > tbl[i + 1].resist) &&
			i < tbl_sz - 2)
		i++;

	/* return milli-Celsius */
	*temp = tbl[i].temp * 1000 + ((tbl[i + 1].temp - tbl[i].temp) * 1000 *
		(r_ntc - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist);

	return 0;
}

static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor, int *temp)
{
	int voltage, ret;
	struct ab8500_temp *ab8500_data = data->plat_data;

	if (sensor == BAT_CTRL) {
		*temp = ab8500_btemp_get_batctrl_temp(ab8500_data->btemp);
	} else if (sensor == BTEMP_BALL) {
		*temp = ab8500_btemp_get_temp(ab8500_data->btemp);
	} else {
		voltage = ab8500_gpadc_convert(ab8500_data->gpadc, sensor);
		if (voltage < 0)
			return voltage;

		ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp);
		if (ret < 0)
			return ret;
	}

	return 0;
}

static void ab8500_thermal_power_off(struct work_struct *work)
{
	struct ab8500_temp *ab8500_data = container_of(work,
				struct ab8500_temp, power_off_work.work);
	struct abx500_temp *abx500_data = ab8500_data->abx500_data;

	dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n");

	kernel_power_off();
}

static ssize_t ab8500_show_name(struct device *dev,
		struct device_attribute *devattr, char *buf)
{
	return sprintf(buf, "ab8500\n");
}

static ssize_t ab8500_show_label(struct device *dev,
		struct device_attribute *devattr, char *buf)
{
	char *label;
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
	int index = attr->index;

	switch (index) {
	case 1:
		label = "ext_adc1";
		break;
	case 2:
		label = "ext_adc2";
		break;
	case 3:
		label = "bat_temp";
		break;
	case 4:
		label = "bat_ctrl";
		break;
	default:
		return -EINVAL;
	}

	return sprintf(buf, "%s\n", label);
}

static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data)
{
	struct ab8500_temp *ab8500_data = data->plat_data;

	dev_warn(&data->pdev->dev, "Power off in %d s\n",
		 DEFAULT_POWER_OFF_DELAY / HZ);

	schedule_delayed_work(&ab8500_data->power_off_work,
		DEFAULT_POWER_OFF_DELAY);
	return 0;
}

int abx500_hwmon_init(struct abx500_temp *data)
{
	struct ab8500_temp *ab8500_data;

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

	ab8500_data->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
	if (IS_ERR(ab8500_data->gpadc))
		return PTR_ERR(ab8500_data->gpadc);

	ab8500_data->btemp = ab8500_btemp_get();
	if (IS_ERR(ab8500_data->btemp))
		return PTR_ERR(ab8500_data->btemp);

	INIT_DELAYED_WORK(&ab8500_data->power_off_work,
			  ab8500_thermal_power_off);

	ab8500_data->cfg.vcc = THERMAL_VCC;
	ab8500_data->cfg.r_up = PULL_UP_RESISTOR;
	ab8500_data->cfg.temp_tbl = ab8500_temp_tbl_a_thermistor;
	ab8500_data->cfg.tbl_sz = ab8500_temp_tbl_a_size;

	data->plat_data = ab8500_data;

	/*
	 * ADC_AUX1 and ADC_AUX2, connected to external NTC
	 * BTEMP_BALL and BAT_CTRL, fixed usage
	 */
	data->gpadc_addr[0] = ADC_AUX1;
	data->gpadc_addr[1] = ADC_AUX2;
	data->gpadc_addr[2] = BTEMP_BALL;
	data->gpadc_addr[3] = BAT_CTRL;
	data->monitored_sensors = NUM_MONITORED_SENSORS;

	data->ops.read_sensor = ab8500_read_sensor;
	data->ops.irq_handler = ab8500_temp_irq_handler;
	data->ops.show_name = ab8500_show_name;
	data->ops.show_label = ab8500_show_label;
	data->ops.is_visible = NULL;

	return 0;
}
EXPORT_SYMBOL(abx500_hwmon_init);

MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@linaro.org>");
MODULE_DESCRIPTION("AB8500 temperature driver");
MODULE_LICENSE("GPL");