Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Motorola CPCAP PMIC core driver
  4 *
  5 * Copyright (C) 2016 Tony Lindgren <tony@atomide.com>
  6 */
  7
  8#include <linux/device.h>
  9#include <linux/err.h>
 10#include <linux/interrupt.h>
 11#include <linux/irq.h>
 12#include <linux/kernel.h>
 13#include <linux/module.h>
 14#include <linux/of_device.h>
 15#include <linux/regmap.h>
 16#include <linux/sysfs.h>
 17
 18#include <linux/mfd/core.h>
 19#include <linux/mfd/motorola-cpcap.h>
 20#include <linux/spi/spi.h>
 21
 22#define CPCAP_NR_IRQ_REG_BANKS	6
 23#define CPCAP_NR_IRQ_CHIPS	3
 24#define CPCAP_REGISTER_SIZE	4
 25#define CPCAP_REGISTER_BITS	16
 26
 27struct cpcap_ddata {
 28	struct spi_device *spi;
 29	struct regmap_irq *irqs;
 30	struct regmap_irq_chip_data *irqdata[CPCAP_NR_IRQ_CHIPS];
 31	const struct regmap_config *regmap_conf;
 32	struct regmap *regmap;
 33};
 34
 35static int cpcap_sense_irq(struct regmap *regmap, int irq)
 36{
 37	int regnum = irq / CPCAP_REGISTER_BITS;
 38	int mask = BIT(irq % CPCAP_REGISTER_BITS);
 39	int reg = CPCAP_REG_INTS1 + (regnum * CPCAP_REGISTER_SIZE);
 40	int err, val;
 41
 42	if (reg < CPCAP_REG_INTS1 || reg > CPCAP_REG_INTS4)
 43		return -EINVAL;
 44
 45	err = regmap_read(regmap, reg, &val);
 46	if (err)
 47		return err;
 48
 49	return !!(val & mask);
 50}
 51
 52int cpcap_sense_virq(struct regmap *regmap, int virq)
 53{
 54	struct regmap_irq_chip_data *d = irq_get_chip_data(virq);
 55	int irq_base = regmap_irq_chip_get_base(d);
 56
 57	return cpcap_sense_irq(regmap, virq - irq_base);
 58}
 59EXPORT_SYMBOL_GPL(cpcap_sense_virq);
 60
 61static int cpcap_check_revision(struct cpcap_ddata *cpcap)
 62{
 63	u16 vendor, rev;
 64	int ret;
 65
 66	ret = cpcap_get_vendor(&cpcap->spi->dev, cpcap->regmap, &vendor);
 67	if (ret)
 68		return ret;
 69
 70	ret = cpcap_get_revision(&cpcap->spi->dev, cpcap->regmap, &rev);
 71	if (ret)
 72		return ret;
 73
 74	dev_info(&cpcap->spi->dev, "CPCAP vendor: %s rev: %i.%i (%x)\n",
 75		 vendor == CPCAP_VENDOR_ST ? "ST" : "TI",
 76		 CPCAP_REVISION_MAJOR(rev), CPCAP_REVISION_MINOR(rev),
 77		 rev);
 78
 79	if (rev < CPCAP_REVISION_2_1) {
 80		dev_info(&cpcap->spi->dev,
 81			 "Please add old CPCAP revision support as needed\n");
 82		return -ENODEV;
 83	}
 84
 85	return 0;
 86}
 87
 88/*
 89 * First two irq chips are the two private macro interrupt chips, the third
 90 * irq chip is for register banks 1 - 4 and is available for drivers to use.
 91 */
 92static struct regmap_irq_chip cpcap_irq_chip[CPCAP_NR_IRQ_CHIPS] = {
 93	{
 94		.name = "cpcap-m2",
 95		.num_regs = 1,
 96		.status_base = CPCAP_REG_MI1,
 97		.ack_base = CPCAP_REG_MI1,
 98		.mask_base = CPCAP_REG_MIM1,
 99		.use_ack = true,
100		.ack_invert = true,
101	},
102	{
103		.name = "cpcap-m2",
104		.num_regs = 1,
105		.status_base = CPCAP_REG_MI2,
106		.ack_base = CPCAP_REG_MI2,
107		.mask_base = CPCAP_REG_MIM2,
108		.use_ack = true,
109		.ack_invert = true,
110	},
111	{
112		.name = "cpcap1-4",
113		.num_regs = 4,
114		.status_base = CPCAP_REG_INT1,
115		.ack_base = CPCAP_REG_INT1,
116		.mask_base = CPCAP_REG_INTM1,
117		.use_ack = true,
118		.ack_invert = true,
119	},
120};
121
122static void cpcap_init_one_regmap_irq(struct cpcap_ddata *cpcap,
123				      struct regmap_irq *rirq,
124				      int irq_base, int irq)
125{
126	unsigned int reg_offset;
127	unsigned int bit, mask;
128
129	reg_offset = irq - irq_base;
130	reg_offset /= cpcap->regmap_conf->val_bits;
131	reg_offset *= cpcap->regmap_conf->reg_stride;
132
133	bit = irq % cpcap->regmap_conf->val_bits;
134	mask = (1 << bit);
135
136	rirq->reg_offset = reg_offset;
137	rirq->mask = mask;
138}
139
140static int cpcap_init_irq_chip(struct cpcap_ddata *cpcap, int irq_chip,
141			       int irq_start, int nr_irqs)
142{
143	struct regmap_irq_chip *chip = &cpcap_irq_chip[irq_chip];
144	int i, ret;
145
146	for (i = irq_start; i < irq_start + nr_irqs; i++) {
147		struct regmap_irq *rirq = &cpcap->irqs[i];
148
149		cpcap_init_one_regmap_irq(cpcap, rirq, irq_start, i);
150	}
151	chip->irqs = &cpcap->irqs[irq_start];
152	chip->num_irqs = nr_irqs;
153	chip->irq_drv_data = cpcap;
154
155	ret = devm_regmap_add_irq_chip(&cpcap->spi->dev, cpcap->regmap,
156				       cpcap->spi->irq,
157				       irq_get_trigger_type(cpcap->spi->irq) |
158				       IRQF_SHARED, -1,
159				       chip, &cpcap->irqdata[irq_chip]);
160	if (ret) {
161		dev_err(&cpcap->spi->dev, "could not add irq chip %i: %i\n",
162			irq_chip, ret);
163		return ret;
164	}
165
166	return 0;
167}
168
169static int cpcap_init_irq(struct cpcap_ddata *cpcap)
170{
171	int ret;
172
173	cpcap->irqs = devm_kzalloc(&cpcap->spi->dev,
174				   array3_size(sizeof(*cpcap->irqs),
175					       CPCAP_NR_IRQ_REG_BANKS,
176					       cpcap->regmap_conf->val_bits),
177				   GFP_KERNEL);
178	if (!cpcap->irqs)
179		return -ENOMEM;
180
181	ret = cpcap_init_irq_chip(cpcap, 0, 0, 16);
182	if (ret)
183		return ret;
184
185	ret = cpcap_init_irq_chip(cpcap, 1, 16, 16);
186	if (ret)
187		return ret;
188
189	ret = cpcap_init_irq_chip(cpcap, 2, 32, 64);
190	if (ret)
191		return ret;
192
193	enable_irq_wake(cpcap->spi->irq);
194
195	return 0;
196}
197
198static const struct of_device_id cpcap_of_match[] = {
199	{ .compatible = "motorola,cpcap", },
200	{ .compatible = "st,6556002", },
201	{},
202};
203MODULE_DEVICE_TABLE(of, cpcap_of_match);
204
205static const struct regmap_config cpcap_regmap_config = {
206	.reg_bits = 16,
207	.reg_stride = 4,
208	.pad_bits = 0,
209	.val_bits = 16,
210	.write_flag_mask = 0x8000,
211	.max_register = CPCAP_REG_ST_TEST2,
212	.cache_type = REGCACHE_NONE,
213	.reg_format_endian = REGMAP_ENDIAN_LITTLE,
214	.val_format_endian = REGMAP_ENDIAN_LITTLE,
215};
216
217#ifdef CONFIG_PM_SLEEP
218static int cpcap_suspend(struct device *dev)
219{
220	struct spi_device *spi = to_spi_device(dev);
221
222	disable_irq(spi->irq);
223
224	return 0;
225}
226
227static int cpcap_resume(struct device *dev)
228{
229	struct spi_device *spi = to_spi_device(dev);
230
231	enable_irq(spi->irq);
232
233	return 0;
234}
235#endif
236
237static SIMPLE_DEV_PM_OPS(cpcap_pm, cpcap_suspend, cpcap_resume);
238
239static const struct mfd_cell cpcap_mfd_devices[] = {
240	{
241		.name          = "cpcap_adc",
242		.of_compatible = "motorola,mapphone-cpcap-adc",
243	}, {
244		.name          = "cpcap_battery",
245		.of_compatible = "motorola,cpcap-battery",
246	}, {
247		.name          = "cpcap-charger",
248		.of_compatible = "motorola,mapphone-cpcap-charger",
249	}, {
250		.name          = "cpcap-regulator",
251		.of_compatible = "motorola,mapphone-cpcap-regulator",
252	}, {
253		.name          = "cpcap-rtc",
254		.of_compatible = "motorola,cpcap-rtc",
255	}, {
256		.name          = "cpcap-pwrbutton",
257		.of_compatible = "motorola,cpcap-pwrbutton",
258	}, {
259		.name          = "cpcap-usb-phy",
260		.of_compatible = "motorola,mapphone-cpcap-usb-phy",
261	}, {
262		.name          = "cpcap-led",
263		.id            = 0,
264		.of_compatible = "motorola,cpcap-led-red",
265	}, {
266		.name          = "cpcap-led",
267		.id            = 1,
268		.of_compatible = "motorola,cpcap-led-green",
269	}, {
270		.name          = "cpcap-led",
271		.id            = 2,
272		.of_compatible = "motorola,cpcap-led-blue",
273	}, {
274		.name          = "cpcap-led",
275		.id            = 3,
276		.of_compatible = "motorola,cpcap-led-adl",
277	}, {
278		.name          = "cpcap-led",
279		.id            = 4,
280		.of_compatible = "motorola,cpcap-led-cp",
281	}, {
282		.name          = "cpcap-codec",
283	}
284};
285
286static int cpcap_probe(struct spi_device *spi)
287{
288	const struct of_device_id *match;
289	struct cpcap_ddata *cpcap;
290	int ret;
291
292	match = of_match_device(of_match_ptr(cpcap_of_match), &spi->dev);
293	if (!match)
294		return -ENODEV;
295
296	cpcap = devm_kzalloc(&spi->dev, sizeof(*cpcap), GFP_KERNEL);
297	if (!cpcap)
298		return -ENOMEM;
299
300	cpcap->spi = spi;
301	spi_set_drvdata(spi, cpcap);
302
303	spi->bits_per_word = 16;
304	spi->mode = SPI_MODE_0 | SPI_CS_HIGH;
305
306	ret = spi_setup(spi);
307	if (ret)
308		return ret;
309
310	cpcap->regmap_conf = &cpcap_regmap_config;
311	cpcap->regmap = devm_regmap_init_spi(spi, &cpcap_regmap_config);
312	if (IS_ERR(cpcap->regmap)) {
313		ret = PTR_ERR(cpcap->regmap);
314		dev_err(&cpcap->spi->dev, "Failed to initialize regmap: %d\n",
315			ret);
316
317		return ret;
318	}
319
320	ret = cpcap_check_revision(cpcap);
321	if (ret) {
322		dev_err(&cpcap->spi->dev, "Failed to detect CPCAP: %i\n", ret);
323		return ret;
324	}
325
326	ret = cpcap_init_irq(cpcap);
327	if (ret)
328		return ret;
329
330	return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
331				    ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
332}
333
334static struct spi_driver cpcap_driver = {
335	.driver = {
336		.name = "cpcap-core",
337		.of_match_table = cpcap_of_match,
338		.pm = &cpcap_pm,
339	},
340	.probe = cpcap_probe,
341};
342module_spi_driver(cpcap_driver);
343
344MODULE_ALIAS("platform:cpcap");
345MODULE_DESCRIPTION("CPCAP driver");
346MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
347MODULE_LICENSE("GPL v2");