summaryrefslogtreecommitdiff
path: root/drivers/mxc/pmic/core/mc34704.c
blob: f0ec05afe0abc7115f77947d77350f41c64a28ef (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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/*
 * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

/*!
 * @file pmic/core/mc34704.c
 * @brief This file contains MC34704 specific PMIC code.
 *
 * @ingroup PMIC_CORE
 */

/*
 * Includes
 */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/mfd/mc34704/core.h>
#include <linux/pmic_external.h>
#include <linux/pmic_status.h>

#include "pmic.h"

/*
 * Globals
 */
static pmic_version_t mxc_pmic_version = {
	.id = PMIC_MC34704,
	.revision = 0,
};
static unsigned int events_enabled;
unsigned int active_events[MAX_ACTIVE_EVENTS];
struct i2c_client *mc34704_client;
static void pmic_trigger_poll(void);

#define MAX_MC34704_REG 0x59
static unsigned int mc34704_reg_readonly[MAX_MC34704_REG / 32 + 1] = {
	(1 << 0x03) || (1 << 0x05) || (1 << 0x07) || (1 << 0x09) ||
	    (1 << 0x0B) || (1 << 0x0E) || (1 << 0x11) || (1 << 0x14) ||
	    (1 << 0x17) || (1 << 0x18),
	0,
};
static unsigned int mc34704_reg_written[MAX_MC34704_REG / 32 + 1];
static unsigned char mc34704_shadow_regs[MAX_MC34704_REG - 1];
#define IS_READONLY(r) ((1 << ((r) % 32)) & mc34704_reg_readonly[(r) / 32])
#define WAS_WRITTEN(r) ((1 << ((r) % 32)) & mc34704_reg_written[(r) / 32])
#define MARK_WRITTEN(r) do { \
	mc34704_reg_written[(r) / 32] |= (1 << ((r) % 32)); \
} while (0)

int pmic_read(int reg_nr, unsigned int *reg_val)
{
	int c;

	/*
	 * Use the shadow register if we've written to it
	 */
	if (WAS_WRITTEN(reg_nr)) {
		*reg_val = mc34704_shadow_regs[reg_nr];
		return PMIC_SUCCESS;
	}

	/*
	 * Otherwise, actually read the real register.
	 * Write-only registers will read as zero.
	 */
	c = i2c_smbus_read_byte_data(mc34704_client, reg_nr);
	if (c == -1) {
		pr_debug("mc34704: error reading register 0x%02x\n", reg_nr);
		return PMIC_ERROR;
	} else {
		*reg_val = c;
		return PMIC_SUCCESS;
	}
}

int pmic_write(int reg_nr, const unsigned int reg_val)
{
	int ret;

	ret = i2c_smbus_write_byte_data(mc34704_client, reg_nr, reg_val);
	if (ret == -1) {
		return PMIC_ERROR;
	} else {
		/*
		 * Update our software copy of the register since you
		 * can't read what you wrote.
		 */
		if (!IS_READONLY(reg_nr)) {
			mc34704_shadow_regs[reg_nr] = reg_val;
			MARK_WRITTEN(reg_nr);
		}
		return PMIC_SUCCESS;
	}
}

unsigned int pmic_get_active_events(unsigned int *active_events)
{
	unsigned int count = 0;
	unsigned int faults;
	int bit_set;

	/* Check for any relevant PMIC faults */
	pmic_read(REG_MC34704_FAULTS, &faults);
	faults &= events_enabled;

	/*
	 * Mask all active events, because there is no way to acknowledge
	 * or dismiss them in the PMIC -- they're sticky.
	 */
	events_enabled &= ~faults;

	/* Account for all unmasked faults */
	while (faults) {
		bit_set = ffs(faults) - 1;
		*(active_events + count) = bit_set;
		count++;
		faults ^= (1 << bit_set);
	}
	return count;
}

int pmic_event_unmask(type_event event)
{
	unsigned int event_bit = 0;
	unsigned int prior_events = events_enabled;

	event_bit = (1 << event);
	events_enabled |= event_bit;

	pr_debug("Enable Event : %d\n", event);

	/* start the polling task as needed */
	if (events_enabled && prior_events == 0)
		pmic_trigger_poll();

	return 0;
}

int pmic_event_mask(type_event event)
{
	unsigned int event_bit = 0;

	event_bit = (1 << event);
	events_enabled &= ~event_bit;

	pr_debug("Disable Event : %d\n", event);

	return 0;
}

/*!
 * PMIC event polling task.  This task is called periodically to poll
 * for possible MC34704 events (No interrupt supplied by the hardware).
 */
static void pmic_event_task(struct work_struct *work);
DECLARE_DELAYED_WORK(pmic_ws, pmic_event_task);

static void pmic_trigger_poll(void)
{
	schedule_delayed_work(&pmic_ws, HZ / 10);
}

static void pmic_event_task(struct work_struct *work)
{
	unsigned int count = 0;
	int i;

	count = pmic_get_active_events(active_events);
	pr_debug("active events number %d\n", count);

	/* call handlers for all active events */
	for (i = 0; i < count; i++)
		pmic_event_callback(active_events[i]);

	/* re-trigger this task, but only if somebody is watching */
	if (events_enabled)
		pmic_trigger_poll();

	return;
}

pmic_version_t pmic_get_version(void)
{
	return mxc_pmic_version;
}
EXPORT_SYMBOL(pmic_get_version);

int __devinit pmic_init_registers(void)
{
	/*
	 * Set some registers to what they should be,
	 * if for no other reason than to initialize our
	 * software register copies.
	 */
	CHECK_ERROR(pmic_write(REG_MC34704_GENERAL2, 0x09));
	CHECK_ERROR(pmic_write(REG_MC34704_VGSET1, 0));
	CHECK_ERROR(pmic_write(REG_MC34704_REG2SET1, 0));
	CHECK_ERROR(pmic_write(REG_MC34704_REG3SET1, 0));
	CHECK_ERROR(pmic_write(REG_MC34704_REG4SET1, 0));
	CHECK_ERROR(pmic_write(REG_MC34704_REG5SET1, 0));

	return PMIC_SUCCESS;
}

static int __devinit is_chip_onboard(struct i2c_client *client)
{
	int val;

	/*
	 * This PMIC has no version or ID register, so just see
	 * if it ACK's and returns 0 on some write-only register as
	 * evidence of its presence.
	 */
	val = i2c_smbus_read_byte_data(client, REG_MC34704_GENERAL2);
	if (val != 0)
		return -1;

	return 0;
}

static int __devinit pmic_probe(struct i2c_client *client,
				const struct i2c_device_id *id)
{
	int ret = 0;
	struct mc34704 *mc34704;
	struct mc34704_platform_data *plat_data = client->dev.platform_data;

	if (!plat_data || !plat_data->init)
		return -ENODEV;

	ret = is_chip_onboard(client);

	if (ret == -1)
		return -ENODEV;

	mc34704 = kzalloc(sizeof(struct mc34704), GFP_KERNEL);
	if (mc34704 == NULL)
		return -ENOMEM;

	i2c_set_clientdata(client, mc34704);
	mc34704->dev = &client->dev;
	mc34704->i2c_client = client;

	mc34704_client = client;

	/* Initialize the PMIC event handling */
	pmic_event_list_init();

	/* Initialize PMI registers */
	if (pmic_init_registers() != PMIC_SUCCESS)
		return PMIC_ERROR;

	ret = plat_data->init(mc34704);
	if (ret != 0)
		return PMIC_ERROR;

	dev_info(&client->dev, "Loaded\n");

	return PMIC_SUCCESS;
}

static int pmic_remove(struct i2c_client *client)
{
	return 0;
}

static int pmic_suspend(struct i2c_client *client, pm_message_t state)
{
	return 0;
}

static int pmic_resume(struct i2c_client *client)
{
	return 0;
}

static const struct i2c_device_id mc34704_id[] = {
	{"mc34704", 0},
	{},
};

MODULE_DEVICE_TABLE(i2c, mc34704_id);

static struct i2c_driver pmic_driver = {
	.driver = {
		   .name = "mc34704",
		   .bus = NULL,
		   },
	.probe = pmic_probe,
	.remove = pmic_remove,
	.suspend = pmic_suspend,
	.resume = pmic_resume,
	.id_table = mc34704_id,
};

static int __init pmic_init(void)
{
	return i2c_add_driver(&pmic_driver);
}

static void __exit pmic_exit(void)
{
	i2c_del_driver(&pmic_driver);
}

/*
 * Module entry points
 */
subsys_initcall_sync(pmic_init);
module_exit(pmic_exit);

MODULE_DESCRIPTION("MC34704 PMIC driver");
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_LICENSE("GPL");