Blame view

drivers/mfd/pcf50633-core.c 7.91 KB
f52046b14   Balaji Rao   mfd: PCF50633 cor...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  /* NXP PCF50633 Power Management Unit (PMU) driver
   *
   * (C) 2006-2008 by Openmoko, Inc.
   * Author: Harald Welte <laforge@openmoko.org>
   * 	   Balaji Rao <balajirrao@openmoko.org>
   * All rights reserved.
   *
   *  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.
   *
   */
  
  #include <linux/kernel.h>
  #include <linux/device.h>
  #include <linux/sysfs.h>
f52046b14   Balaji Rao   mfd: PCF50633 cor...
18
19
20
21
22
23
  #include <linux/module.h>
  #include <linux/types.h>
  #include <linux/interrupt.h>
  #include <linux/workqueue.h>
  #include <linux/platform_device.h>
  #include <linux/i2c.h>
939941d44   Mark Brown   mfd: Convert pcf5...
24
  #include <linux/pm.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
25
  #include <linux/slab.h>
6e3ad1180   Mark Brown   mfd: Convert pcf5...
26
27
  #include <linux/regmap.h>
  #include <linux/err.h>
f52046b14   Balaji Rao   mfd: PCF50633 cor...
28
29
  
  #include <linux/mfd/pcf50633/core.h>
25985edce   Lucas De Marchi   Fix common misspe...
30
  /* Read a block of up to 32 regs  */
f52046b14   Balaji Rao   mfd: PCF50633 cor...
31
32
33
34
  int pcf50633_read_block(struct pcf50633 *pcf, u8 reg,
  					int nr_regs, u8 *data)
  {
  	int ret;
6e3ad1180   Mark Brown   mfd: Convert pcf5...
35
36
37
  	ret = regmap_raw_read(pcf->regmap, reg, data, nr_regs);
  	if (ret != 0)
  		return ret;
f52046b14   Balaji Rao   mfd: PCF50633 cor...
38

6e3ad1180   Mark Brown   mfd: Convert pcf5...
39
  	return nr_regs;
f52046b14   Balaji Rao   mfd: PCF50633 cor...
40
41
  }
  EXPORT_SYMBOL_GPL(pcf50633_read_block);
25985edce   Lucas De Marchi   Fix common misspe...
42
  /* Write a block of up to 32 regs  */
f52046b14   Balaji Rao   mfd: PCF50633 cor...
43
44
45
46
  int pcf50633_write_block(struct pcf50633 *pcf , u8 reg,
  					int nr_regs, u8 *data)
  {
  	int ret;
6e3ad1180   Mark Brown   mfd: Convert pcf5...
47
48
49
  	ret = regmap_raw_write(pcf->regmap, reg, data, nr_regs);
  	if (ret != 0)
  		return ret;
f52046b14   Balaji Rao   mfd: PCF50633 cor...
50

6e3ad1180   Mark Brown   mfd: Convert pcf5...
51
  	return nr_regs;
f52046b14   Balaji Rao   mfd: PCF50633 cor...
52
53
54
55
56
  }
  EXPORT_SYMBOL_GPL(pcf50633_write_block);
  
  u8 pcf50633_reg_read(struct pcf50633 *pcf, u8 reg)
  {
6e3ad1180   Mark Brown   mfd: Convert pcf5...
57
58
  	unsigned int val;
  	int ret;
f52046b14   Balaji Rao   mfd: PCF50633 cor...
59

6e3ad1180   Mark Brown   mfd: Convert pcf5...
60
61
62
  	ret = regmap_read(pcf->regmap, reg, &val);
  	if (ret < 0)
  		return -1;
f52046b14   Balaji Rao   mfd: PCF50633 cor...
63
64
65
66
67
68
69
  
  	return val;
  }
  EXPORT_SYMBOL_GPL(pcf50633_reg_read);
  
  int pcf50633_reg_write(struct pcf50633 *pcf, u8 reg, u8 val)
  {
6e3ad1180   Mark Brown   mfd: Convert pcf5...
70
  	return regmap_write(pcf->regmap, reg, val);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
71
72
73
74
75
  }
  EXPORT_SYMBOL_GPL(pcf50633_reg_write);
  
  int pcf50633_reg_set_bit_mask(struct pcf50633 *pcf, u8 reg, u8 mask, u8 val)
  {
6e3ad1180   Mark Brown   mfd: Convert pcf5...
76
  	return regmap_update_bits(pcf->regmap, reg, mask, val);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
77
78
79
80
81
  }
  EXPORT_SYMBOL_GPL(pcf50633_reg_set_bit_mask);
  
  int pcf50633_reg_clear_bits(struct pcf50633 *pcf, u8 reg, u8 val)
  {
6e3ad1180   Mark Brown   mfd: Convert pcf5...
82
  	return regmap_update_bits(pcf->regmap, reg, val, 0);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
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
  }
  EXPORT_SYMBOL_GPL(pcf50633_reg_clear_bits);
  
  /* sysfs attributes */
  static ssize_t show_dump_regs(struct device *dev, struct device_attribute *attr,
  			    char *buf)
  {
  	struct pcf50633 *pcf = dev_get_drvdata(dev);
  	u8 dump[16];
  	int n, n1, idx = 0;
  	char *buf1 = buf;
  	static u8 address_no_read[] = { /* must be ascending */
  		PCF50633_REG_INT1,
  		PCF50633_REG_INT2,
  		PCF50633_REG_INT3,
  		PCF50633_REG_INT4,
  		PCF50633_REG_INT5,
  		0 /* terminator */
  	};
  
  	for (n = 0; n < 256; n += sizeof(dump)) {
  		for (n1 = 0; n1 < sizeof(dump); n1++)
  			if (n == address_no_read[idx]) {
  				idx++;
  				dump[n1] = 0x00;
  			} else
  				dump[n1] = pcf50633_reg_read(pcf, n + n1);
  
  		hex_dump_to_buffer(dump, sizeof(dump), 16, 1, buf1, 128, 0);
  		buf1 += strlen(buf1);
  		*buf1++ = '
  ';
  		*buf1 = '\0';
  	}
  
  	return buf1 - buf;
  }
  static DEVICE_ATTR(dump_regs, 0400, show_dump_regs, NULL);
  
  static ssize_t show_resume_reason(struct device *dev,
  				struct device_attribute *attr, char *buf)
  {
  	struct pcf50633 *pcf = dev_get_drvdata(dev);
  	int n;
  
  	n = sprintf(buf, "%02x%02x%02x%02x%02x
  ",
  				pcf->resume_reason[0],
  				pcf->resume_reason[1],
  				pcf->resume_reason[2],
  				pcf->resume_reason[3],
  				pcf->resume_reason[4]);
  
  	return n;
  }
  static DEVICE_ATTR(resume_reason, 0400, show_resume_reason, NULL);
  
  static struct attribute *pcf_sysfs_entries[] = {
  	&dev_attr_dump_regs.attr,
  	&dev_attr_resume_reason.attr,
  	NULL,
  };
  
  static struct attribute_group pcf_attr_group = {
  	.name	= NULL,			/* put in device directory */
  	.attrs	= pcf_sysfs_entries,
  };
f52046b14   Balaji Rao   mfd: PCF50633 cor...
150
151
152
153
  static void
  pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name,
  						struct platform_device **pdev)
  {
f52046b14   Balaji Rao   mfd: PCF50633 cor...
154
155
156
157
158
159
160
161
  	int ret;
  
  	*pdev = platform_device_alloc(name, -1);
  	if (!*pdev) {
  		dev_err(pcf->dev, "Falied to allocate %s
  ", name);
  		return;
  	}
f52046b14   Balaji Rao   mfd: PCF50633 cor...
162
163
164
165
166
167
168
169
170
171
  	(*pdev)->dev.parent = pcf->dev;
  
  	ret = platform_device_add(*pdev);
  	if (ret) {
  		dev_err(pcf->dev, "Failed to register %s: %d
  ", name, ret);
  		platform_device_put(*pdev);
  		*pdev = NULL;
  	}
  }
939941d44   Mark Brown   mfd: Convert pcf5...
172
173
  #ifdef CONFIG_PM_SLEEP
  static int pcf50633_suspend(struct device *dev)
f52046b14   Balaji Rao   mfd: PCF50633 cor...
174
  {
939941d44   Mark Brown   mfd: Convert pcf5...
175
176
  	struct i2c_client *client = to_i2c_client(dev);
  	struct pcf50633 *pcf = i2c_get_clientdata(client);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
177

380c09f64   Lars-Peter Clausen   mfd: Move pcf5063...
178
  	return pcf50633_irq_suspend(pcf);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
179
  }
939941d44   Mark Brown   mfd: Convert pcf5...
180
  static int pcf50633_resume(struct device *dev)
f52046b14   Balaji Rao   mfd: PCF50633 cor...
181
  {
939941d44   Mark Brown   mfd: Convert pcf5...
182
183
  	struct i2c_client *client = to_i2c_client(dev);
  	struct pcf50633 *pcf = i2c_get_clientdata(client);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
184

380c09f64   Lars-Peter Clausen   mfd: Move pcf5063...
185
  	return pcf50633_irq_resume(pcf);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
186
  }
f52046b14   Balaji Rao   mfd: PCF50633 cor...
187
  #endif
939941d44   Mark Brown   mfd: Convert pcf5...
188
  static SIMPLE_DEV_PM_OPS(pcf50633_pm, pcf50633_suspend, pcf50633_resume);
6e3ad1180   Mark Brown   mfd: Convert pcf5...
189
190
191
192
  static struct regmap_config pcf50633_regmap_config = {
  	.reg_bits = 8,
  	.val_bits = 8,
  };
f52046b14   Balaji Rao   mfd: PCF50633 cor...
193
194
195
196
197
  static int __devinit pcf50633_probe(struct i2c_client *client,
  				const struct i2c_device_id *ids)
  {
  	struct pcf50633 *pcf;
  	struct pcf50633_platform_data *pdata = client->dev.platform_data;
24213ae19   Lars-Peter Clausen   mfd: Cleanup pcf5...
198
  	int i, ret;
f52046b14   Balaji Rao   mfd: PCF50633 cor...
199
  	int version, variant;
24213ae19   Lars-Peter Clausen   mfd: Cleanup pcf5...
200
201
202
203
204
  	if (!client->irq) {
  		dev_err(&client->dev, "Missing IRQ
  ");
  		return -ENOENT;
  	}
f52046b14   Balaji Rao   mfd: PCF50633 cor...
205
206
207
208
209
210
211
  	pcf = kzalloc(sizeof(*pcf), GFP_KERNEL);
  	if (!pcf)
  		return -ENOMEM;
  
  	pcf->pdata = pdata;
  
  	mutex_init(&pcf->lock);
6e3ad1180   Mark Brown   mfd: Convert pcf5...
212
213
214
215
216
217
218
219
  	pcf->regmap = regmap_init_i2c(client, &pcf50633_regmap_config);
  	if (IS_ERR(pcf->regmap)) {
  		ret = PTR_ERR(pcf->regmap);
  		dev_err(pcf->dev, "Failed to allocate register map: %d
  ",
  			ret);
  		goto err_free;
  	}
f52046b14   Balaji Rao   mfd: PCF50633 cor...
220
221
  	i2c_set_clientdata(client, pcf);
  	pcf->dev = &client->dev;
f52046b14   Balaji Rao   mfd: PCF50633 cor...
222
223
224
225
226
227
228
  
  	version = pcf50633_reg_read(pcf, 0);
  	variant = pcf50633_reg_read(pcf, 1);
  	if (version < 0 || variant < 0) {
  		dev_err(pcf->dev, "Unable to probe pcf50633
  ");
  		ret = -ENODEV;
6e3ad1180   Mark Brown   mfd: Convert pcf5...
229
  		goto err_regmap;
f52046b14   Balaji Rao   mfd: PCF50633 cor...
230
231
232
233
234
  	}
  
  	dev_info(pcf->dev, "Probed device version %d variant %d
  ",
  							version, variant);
380c09f64   Lars-Peter Clausen   mfd: Move pcf5063...
235
  	pcf50633_irq_init(pcf, client->irq);
24213ae19   Lars-Peter Clausen   mfd: Cleanup pcf5...
236

f52046b14   Balaji Rao   mfd: PCF50633 cor...
237
238
239
240
241
242
243
244
245
  	/* Create sub devices */
  	pcf50633_client_dev_register(pcf, "pcf50633-input",
  						&pcf->input_pdev);
  	pcf50633_client_dev_register(pcf, "pcf50633-rtc",
  						&pcf->rtc_pdev);
  	pcf50633_client_dev_register(pcf, "pcf50633-mbc",
  						&pcf->mbc_pdev);
  	pcf50633_client_dev_register(pcf, "pcf50633-adc",
  						&pcf->adc_pdev);
f5bf403a9   Lars-Peter Clausen   backlight: pcf506...
246
247
  	pcf50633_client_dev_register(pcf, "pcf50633-backlight",
  						&pcf->bl_pdev);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
248
249
250
251
252
253
  
  	for (i = 0; i < PCF50633_NUM_REGULATORS; i++) {
  		struct platform_device *pdev;
  
  		pdev = platform_device_alloc("pcf50633-regltr", i);
  		if (!pdev) {
24213ae19   Lars-Peter Clausen   mfd: Cleanup pcf5...
254
255
  			dev_err(pcf->dev, "Cannot create regulator %d
  ", i);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
256
257
258
259
  			continue;
  		}
  
  		pdev->dev.parent = pcf->dev;
bbb2e496f   Lars-Peter Clausen   mfd: Use platform...
260
261
  		platform_device_add_data(pdev, &pdata->reg_init_data[i],
  					sizeof(pdata->reg_init_data[i]));
f52046b14   Balaji Rao   mfd: PCF50633 cor...
262
263
264
265
  		pcf->regulator_pdev[i] = pdev;
  
  		platform_device_add(pdev);
  	}
f52046b14   Balaji Rao   mfd: PCF50633 cor...
266
267
268
269
270
271
272
273
274
  	ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group);
  	if (ret)
  		dev_err(pcf->dev, "error creating sysfs entries
  ");
  
  	if (pdata->probe_done)
  		pdata->probe_done(pcf);
  
  	return 0;
6e3ad1180   Mark Brown   mfd: Convert pcf5...
275
276
  err_regmap:
  	regmap_exit(pcf->regmap);
24213ae19   Lars-Peter Clausen   mfd: Cleanup pcf5...
277
  err_free:
f52046b14   Balaji Rao   mfd: PCF50633 cor...
278
  	kfree(pcf);
24213ae19   Lars-Peter Clausen   mfd: Cleanup pcf5...
279

f52046b14   Balaji Rao   mfd: PCF50633 cor...
280
281
282
283
284
285
286
  	return ret;
  }
  
  static int __devexit pcf50633_remove(struct i2c_client *client)
  {
  	struct pcf50633 *pcf = i2c_get_clientdata(client);
  	int i;
8220fe4cb   Axel Lin   mfd: Fix resource...
287
  	sysfs_remove_group(&client->dev.kobj, &pcf_attr_group);
380c09f64   Lars-Peter Clausen   mfd: Move pcf5063...
288
  	pcf50633_irq_free(pcf);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
289
290
291
292
293
  
  	platform_device_unregister(pcf->input_pdev);
  	platform_device_unregister(pcf->rtc_pdev);
  	platform_device_unregister(pcf->mbc_pdev);
  	platform_device_unregister(pcf->adc_pdev);
8220fe4cb   Axel Lin   mfd: Fix resource...
294
  	platform_device_unregister(pcf->bl_pdev);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
295
296
297
  
  	for (i = 0; i < PCF50633_NUM_REGULATORS; i++)
  		platform_device_unregister(pcf->regulator_pdev[i]);
6e3ad1180   Mark Brown   mfd: Convert pcf5...
298
  	regmap_exit(pcf->regmap);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
299
300
301
302
  	kfree(pcf);
  
  	return 0;
  }
1206552b0   Axel Lin   mfd: Constify i2c...
303
  static const struct i2c_device_id pcf50633_id_table[] = {
f52046b14   Balaji Rao   mfd: PCF50633 cor...
304
  	{"pcf50633", 0x73},
8915e5402   Jean Delvare   mfd: terminate pc...
305
  	{/* end of list */}
f52046b14   Balaji Rao   mfd: PCF50633 cor...
306
  };
7679089de   Axel Lin   mfd: Add MODULE_D...
307
  MODULE_DEVICE_TABLE(i2c, pcf50633_id_table);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
308
309
310
311
  
  static struct i2c_driver pcf50633_driver = {
  	.driver = {
  		.name	= "pcf50633",
939941d44   Mark Brown   mfd: Convert pcf5...
312
  		.pm	= &pcf50633_pm,
f52046b14   Balaji Rao   mfd: PCF50633 cor...
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
  	},
  	.id_table = pcf50633_id_table,
  	.probe = pcf50633_probe,
  	.remove = __devexit_p(pcf50633_remove),
  };
  
  static int __init pcf50633_init(void)
  {
  	return i2c_add_driver(&pcf50633_driver);
  }
  
  static void __exit pcf50633_exit(void)
  {
  	i2c_del_driver(&pcf50633_driver);
  }
  
  MODULE_DESCRIPTION("I2C chip driver for NXP PCF50633 PMU");
  MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
  MODULE_LICENSE("GPL");
2021de874   Samuel Ortiz   mfd: early init f...
332
  subsys_initcall(pcf50633_init);
f52046b14   Balaji Rao   mfd: PCF50633 cor...
333
  module_exit(pcf50633_exit);