Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * Core support for ATC260x PMICs
  4 *
  5 * Copyright (C) 2019 Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
  6 * Copyright (C) 2020 Cristian Ciocaltea <cristian.ciocaltea@gmail.com>
  7 */
  8
  9#include <linux/interrupt.h>
 10#include <linux/mfd/atc260x/core.h>
 11#include <linux/mfd/core.h>
 12#include <linux/module.h>
 13#include <linux/of.h>
 14#include <linux/of_device.h>
 15#include <linux/regmap.h>
 16
 17#define ATC260X_CHIP_REV_MAX	31
 18
 19struct atc260x_init_regs {
 20	unsigned int cmu_devrst;
 21	unsigned int cmu_devrst_ints;
 22	unsigned int ints_msk;
 23	unsigned int pad_en;
 24	unsigned int pad_en_extirq;
 25};
 26
 27static void regmap_lock_mutex(void *__mutex)
 28{
 29	struct mutex *mutex = __mutex;
 30
 31	/*
 32	 * Using regmap within an atomic context (e.g. accessing a PMIC when
 33	 * powering system down) is normally allowed only if the regmap type
 34	 * is MMIO and the regcache type is either REGCACHE_NONE or
 35	 * REGCACHE_FLAT. For slow buses like I2C and SPI, the regmap is
 36	 * internally protected by a mutex which is acquired non-atomically.
 37	 *
 38	 * Let's improve this by using a customized locking scheme inspired
 39	 * from I2C atomic transfer. See i2c_in_atomic_xfer_mode() for a
 40	 * starting point.
 41	 */
 42	if (system_state > SYSTEM_RUNNING && irqs_disabled())
 43		mutex_trylock(mutex);
 44	else
 45		mutex_lock(mutex);
 46}
 47
 48static void regmap_unlock_mutex(void *__mutex)
 49{
 50	struct mutex *mutex = __mutex;
 51
 52	mutex_unlock(mutex);
 53}
 54
 55static const struct regmap_config atc2603c_regmap_config = {
 56	.reg_bits = 8,
 57	.val_bits = 16,
 58	.max_register = ATC2603C_SADDR,
 59	.cache_type = REGCACHE_NONE,
 60};
 61
 62static const struct regmap_config atc2609a_regmap_config = {
 63	.reg_bits = 8,
 64	.val_bits = 16,
 65	.max_register = ATC2609A_SADDR,
 66	.cache_type = REGCACHE_NONE,
 67};
 68
 69static const struct regmap_irq atc2603c_regmap_irqs[] = {
 70	REGMAP_IRQ_REG(ATC2603C_IRQ_AUDIO,	0, ATC2603C_INTS_MSK_AUDIO),
 71	REGMAP_IRQ_REG(ATC2603C_IRQ_OV,		0, ATC2603C_INTS_MSK_OV),
 72	REGMAP_IRQ_REG(ATC2603C_IRQ_OC,		0, ATC2603C_INTS_MSK_OC),
 73	REGMAP_IRQ_REG(ATC2603C_IRQ_OT,		0, ATC2603C_INTS_MSK_OT),
 74	REGMAP_IRQ_REG(ATC2603C_IRQ_UV,		0, ATC2603C_INTS_MSK_UV),
 75	REGMAP_IRQ_REG(ATC2603C_IRQ_ALARM,	0, ATC2603C_INTS_MSK_ALARM),
 76	REGMAP_IRQ_REG(ATC2603C_IRQ_ONOFF,	0, ATC2603C_INTS_MSK_ONOFF),
 77	REGMAP_IRQ_REG(ATC2603C_IRQ_SGPIO,	0, ATC2603C_INTS_MSK_SGPIO),
 78	REGMAP_IRQ_REG(ATC2603C_IRQ_IR,		0, ATC2603C_INTS_MSK_IR),
 79	REGMAP_IRQ_REG(ATC2603C_IRQ_REMCON,	0, ATC2603C_INTS_MSK_REMCON),
 80	REGMAP_IRQ_REG(ATC2603C_IRQ_POWER_IN,	0, ATC2603C_INTS_MSK_POWERIN),
 81};
 82
 83static const struct regmap_irq atc2609a_regmap_irqs[] = {
 84	REGMAP_IRQ_REG(ATC2609A_IRQ_AUDIO,	0, ATC2609A_INTS_MSK_AUDIO),
 85	REGMAP_IRQ_REG(ATC2609A_IRQ_OV,		0, ATC2609A_INTS_MSK_OV),
 86	REGMAP_IRQ_REG(ATC2609A_IRQ_OC,		0, ATC2609A_INTS_MSK_OC),
 87	REGMAP_IRQ_REG(ATC2609A_IRQ_OT,		0, ATC2609A_INTS_MSK_OT),
 88	REGMAP_IRQ_REG(ATC2609A_IRQ_UV,		0, ATC2609A_INTS_MSK_UV),
 89	REGMAP_IRQ_REG(ATC2609A_IRQ_ALARM,	0, ATC2609A_INTS_MSK_ALARM),
 90	REGMAP_IRQ_REG(ATC2609A_IRQ_ONOFF,	0, ATC2609A_INTS_MSK_ONOFF),
 91	REGMAP_IRQ_REG(ATC2609A_IRQ_WKUP,	0, ATC2609A_INTS_MSK_WKUP),
 92	REGMAP_IRQ_REG(ATC2609A_IRQ_IR,		0, ATC2609A_INTS_MSK_IR),
 93	REGMAP_IRQ_REG(ATC2609A_IRQ_REMCON,	0, ATC2609A_INTS_MSK_REMCON),
 94	REGMAP_IRQ_REG(ATC2609A_IRQ_POWER_IN,	0, ATC2609A_INTS_MSK_POWERIN),
 95};
 96
 97static const struct regmap_irq_chip atc2603c_regmap_irq_chip = {
 98	.name = "atc2603c",
 99	.irqs = atc2603c_regmap_irqs,
100	.num_irqs = ARRAY_SIZE(atc2603c_regmap_irqs),
101	.num_regs = 1,
102	.status_base = ATC2603C_INTS_PD,
103	.mask_base = ATC2603C_INTS_MSK,
104	.mask_invert = true,
105};
106
107static const struct regmap_irq_chip atc2609a_regmap_irq_chip = {
108	.name = "atc2609a",
109	.irqs = atc2609a_regmap_irqs,
110	.num_irqs = ARRAY_SIZE(atc2609a_regmap_irqs),
111	.num_regs = 1,
112	.status_base = ATC2609A_INTS_PD,
113	.mask_base = ATC2609A_INTS_MSK,
114	.mask_invert = true,
115};
116
117static const struct resource atc2603c_onkey_resources[] = {
118	DEFINE_RES_IRQ(ATC2603C_IRQ_ONOFF),
119};
120
121static const struct resource atc2609a_onkey_resources[] = {
122	DEFINE_RES_IRQ(ATC2609A_IRQ_ONOFF),
123};
124
125static const struct mfd_cell atc2603c_mfd_cells[] = {
126	{ .name = "atc260x-regulator" },
127	{ .name = "atc260x-pwrc" },
128	{
129		.name = "atc260x-onkey",
130		.num_resources = ARRAY_SIZE(atc2603c_onkey_resources),
131		.resources = atc2603c_onkey_resources,
132	},
133};
134
135static const struct mfd_cell atc2609a_mfd_cells[] = {
136	{ .name = "atc260x-regulator" },
137	{ .name = "atc260x-pwrc" },
138	{
139		.name = "atc260x-onkey",
140		.num_resources = ARRAY_SIZE(atc2609a_onkey_resources),
141		.resources = atc2609a_onkey_resources,
142	},
143};
144
145static const struct atc260x_init_regs atc2603c_init_regs = {
146	.cmu_devrst = ATC2603C_CMU_DEVRST,
147	.cmu_devrst_ints = ATC2603C_CMU_DEVRST_INTS,
148	.ints_msk = ATC2603C_INTS_MSK,
149	.pad_en = ATC2603C_PAD_EN,
150	.pad_en_extirq = ATC2603C_PAD_EN_EXTIRQ,
151};
152
153static const struct atc260x_init_regs atc2609a_init_regs = {
154	.cmu_devrst = ATC2609A_CMU_DEVRST,
155	.cmu_devrst_ints = ATC2609A_CMU_DEVRST_INTS,
156	.ints_msk = ATC2609A_INTS_MSK,
157	.pad_en = ATC2609A_PAD_EN,
158	.pad_en_extirq = ATC2609A_PAD_EN_EXTIRQ,
159};
160
161static void atc260x_cmu_reset(struct atc260x *atc260x)
162{
163	const struct atc260x_init_regs *regs = atc260x->init_regs;
164
165	/* Assert reset */
166	regmap_update_bits(atc260x->regmap, regs->cmu_devrst,
167			   regs->cmu_devrst_ints, ~regs->cmu_devrst_ints);
168
169	/* De-assert reset */
170	regmap_update_bits(atc260x->regmap, regs->cmu_devrst,
171			   regs->cmu_devrst_ints, regs->cmu_devrst_ints);
172}
173
174static void atc260x_dev_init(struct atc260x *atc260x)
175{
176	const struct atc260x_init_regs *regs = atc260x->init_regs;
177
178	/* Initialize interrupt block */
179	atc260x_cmu_reset(atc260x);
180
181	/* Disable all interrupt sources */
182	regmap_write(atc260x->regmap, regs->ints_msk, 0);
183
184	/* Enable EXTIRQ pad */
185	regmap_update_bits(atc260x->regmap, regs->pad_en,
186			   regs->pad_en_extirq, regs->pad_en_extirq);
187}
188
189/**
190 * atc260x_match_device(): Setup ATC260x variant related fields
191 *
192 * @atc260x: ATC260x device to setup (.dev field must be set)
193 * @regmap_cfg: regmap config associated with this ATC260x device
194 *
195 * This lets the ATC260x core configure the MFD cells and register maps
196 * for later use.
197 */
198int atc260x_match_device(struct atc260x *atc260x, struct regmap_config *regmap_cfg)
199{
200	struct device *dev = atc260x->dev;
201	const void *of_data;
202
203	of_data = of_device_get_match_data(dev);
204	if (!of_data)
205		return -ENODEV;
206
207	atc260x->ic_type = (unsigned long)of_data;
208
209	switch (atc260x->ic_type) {
210	case ATC2603C:
211		*regmap_cfg = atc2603c_regmap_config;
212		atc260x->regmap_irq_chip = &atc2603c_regmap_irq_chip;
213		atc260x->cells = atc2603c_mfd_cells;
214		atc260x->nr_cells = ARRAY_SIZE(atc2603c_mfd_cells);
215		atc260x->type_name = "atc2603c";
216		atc260x->rev_reg = ATC2603C_CHIP_VER;
217		atc260x->init_regs = &atc2603c_init_regs;
218		break;
219	case ATC2609A:
220		*regmap_cfg = atc2609a_regmap_config;
221		atc260x->regmap_irq_chip = &atc2609a_regmap_irq_chip;
222		atc260x->cells = atc2609a_mfd_cells;
223		atc260x->nr_cells = ARRAY_SIZE(atc2609a_mfd_cells);
224		atc260x->type_name = "atc2609a";
225		atc260x->rev_reg = ATC2609A_CHIP_VER;
226		atc260x->init_regs = &atc2609a_init_regs;
227		break;
228	default:
229		dev_err(dev, "Unsupported ATC260x device type: %u\n",
230			atc260x->ic_type);
231		return -EINVAL;
232	}
233
234	atc260x->regmap_mutex = devm_kzalloc(dev, sizeof(*atc260x->regmap_mutex),
235					     GFP_KERNEL);
236	if (!atc260x->regmap_mutex)
237		return -ENOMEM;
238
239	mutex_init(atc260x->regmap_mutex);
240
241	regmap_cfg->lock = regmap_lock_mutex,
242	regmap_cfg->unlock = regmap_unlock_mutex,
243	regmap_cfg->lock_arg = atc260x->regmap_mutex;
244
245	return 0;
246}
247EXPORT_SYMBOL_GPL(atc260x_match_device);
248
249/**
250 * atc260x_device_probe(): Probe a configured ATC260x device
251 *
252 * @atc260x: ATC260x device to probe (must be configured)
253 *
254 * This function lets the ATC260x core register the ATC260x MFD devices
255 * and IRQCHIP. The ATC260x device passed in must be fully configured
256 * with atc260x_match_device, its IRQ set, and regmap created.
257 */
258int atc260x_device_probe(struct atc260x *atc260x)
259{
260	struct device *dev = atc260x->dev;
261	unsigned int chip_rev;
262	int ret;
263
264	if (!atc260x->irq) {
265		dev_err(dev, "No interrupt support\n");
266		return -EINVAL;
267	}
268
269	/* Initialize the hardware */
270	atc260x_dev_init(atc260x);
271
272	ret = regmap_read(atc260x->regmap, atc260x->rev_reg, &chip_rev);
273	if (ret) {
274		dev_err(dev, "Failed to get chip revision\n");
275		return ret;
276	}
277
278	if (chip_rev > ATC260X_CHIP_REV_MAX) {
279		dev_err(dev, "Unknown chip revision: %u\n", chip_rev);
280		return -EINVAL;
281	}
282
283	atc260x->ic_ver = __ffs(chip_rev + 1U);
284
285	dev_info(dev, "Detected chip type %s rev.%c\n",
286		 atc260x->type_name, 'A' + atc260x->ic_ver);
287
288	ret = devm_regmap_add_irq_chip(dev, atc260x->regmap, atc260x->irq, IRQF_ONESHOT,
289				       -1, atc260x->regmap_irq_chip, &atc260x->irq_data);
290	if (ret) {
291		dev_err(dev, "Failed to add IRQ chip: %d\n", ret);
292		return ret;
293	}
294
295	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
296				   atc260x->cells, atc260x->nr_cells, NULL, 0,
297				   regmap_irq_get_domain(atc260x->irq_data));
298	if (ret) {
299		dev_err(dev, "Failed to add child devices: %d\n", ret);
300		regmap_del_irq_chip(atc260x->irq, atc260x->irq_data);
301	}
302
303	return ret;
304}
305EXPORT_SYMBOL_GPL(atc260x_device_probe);
306
307MODULE_DESCRIPTION("ATC260x PMICs Core support");
308MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
309MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>");
310MODULE_LICENSE("GPL");