brintos

brintos / linux-shallow public Read only

0
0
Text · 8.1 KiB · d8243b9 Raw
353 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Motorola CPCAP PMIC core driver4 *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/mod_devicetable.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	623#define CPCAP_NR_IRQ_CHIPS	324#define CPCAP_REGISTER_SIZE	425#define CPCAP_REGISTER_BITS	1626 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 third90 * 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		.clear_ack = 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		.clear_ack = 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		.clear_ack = 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 spi_device_id cpcap_spi_ids[] = {206	{ .name = "cpcap", },207	{ .name = "6556002", },208	{},209};210MODULE_DEVICE_TABLE(spi, cpcap_spi_ids);211 212static const struct regmap_config cpcap_regmap_config = {213	.reg_bits = 16,214	.reg_stride = 4,215	.pad_bits = 0,216	.val_bits = 16,217	.write_flag_mask = 0x8000,218	.max_register = CPCAP_REG_ST_TEST2,219	.cache_type = REGCACHE_NONE,220	.reg_format_endian = REGMAP_ENDIAN_LITTLE,221	.val_format_endian = REGMAP_ENDIAN_LITTLE,222};223 224static int cpcap_suspend(struct device *dev)225{226	struct spi_device *spi = to_spi_device(dev);227 228	disable_irq(spi->irq);229 230	return 0;231}232 233static int cpcap_resume(struct device *dev)234{235	struct spi_device *spi = to_spi_device(dev);236 237	enable_irq(spi->irq);238 239	return 0;240}241 242static DEFINE_SIMPLE_DEV_PM_OPS(cpcap_pm, cpcap_suspend, cpcap_resume);243 244static const struct mfd_cell cpcap_mfd_devices[] = {245	{246		.name          = "cpcap_adc",247		.of_compatible = "motorola,mapphone-cpcap-adc",248	}, {249		.name          = "cpcap_battery",250		.of_compatible = "motorola,cpcap-battery",251	}, {252		.name          = "cpcap-charger",253		.of_compatible = "motorola,mapphone-cpcap-charger",254	}, {255		.name          = "cpcap-regulator",256		.of_compatible = "motorola,mapphone-cpcap-regulator",257	}, {258		.name          = "cpcap-rtc",259		.of_compatible = "motorola,cpcap-rtc",260	}, {261		.name          = "cpcap-pwrbutton",262		.of_compatible = "motorola,cpcap-pwrbutton",263	}, {264		.name          = "cpcap-usb-phy",265		.of_compatible = "motorola,mapphone-cpcap-usb-phy",266	}, {267		.name          = "cpcap-led",268		.id            = 0,269		.of_compatible = "motorola,cpcap-led-red",270	}, {271		.name          = "cpcap-led",272		.id            = 1,273		.of_compatible = "motorola,cpcap-led-green",274	}, {275		.name          = "cpcap-led",276		.id            = 2,277		.of_compatible = "motorola,cpcap-led-blue",278	}, {279		.name          = "cpcap-led",280		.id            = 3,281		.of_compatible = "motorola,cpcap-led-adl",282	}, {283		.name          = "cpcap-led",284		.id            = 4,285		.of_compatible = "motorola,cpcap-led-cp",286	}, {287		.name          = "cpcap-codec",288	}289};290 291static int cpcap_probe(struct spi_device *spi)292{293	struct cpcap_ddata *cpcap;294	int ret;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	/* Parent SPI controller uses DMA, CPCAP and child devices do not */331	spi->dev.coherent_dma_mask = 0;332	spi->dev.dma_mask = &spi->dev.coherent_dma_mask;333 334	return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,335				    ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);336}337 338static struct spi_driver cpcap_driver = {339	.driver = {340		.name = "cpcap-core",341		.of_match_table = cpcap_of_match,342		.pm = pm_sleep_ptr(&cpcap_pm),343	},344	.probe = cpcap_probe,345	.id_table = cpcap_spi_ids,346};347module_spi_driver(cpcap_driver);348 349MODULE_ALIAS("platform:cpcap");350MODULE_DESCRIPTION("CPCAP driver");351MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");352MODULE_LICENSE("GPL v2");353