1 /* ADC MFD core driver for sunxi platforms 2 * 3 * Copyright (c) 2016 Quentin Schulz <quentin.schulz@free-electrons.com> 4 * 5 * This program is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 as published by 7 * the Free Software Foundation. 8 */ 9 10 #include <linux/interrupt.h> 11 #include <linux/kernel.h> 12 #include <linux/mfd/core.h> 13 #include <linux/module.h> 14 #include <linux/of_device.h> 15 #include <linux/of_irq.h> 16 #include <linux/regmap.h> 17 18 #include <linux/mfd/sun4i-gpadc.h> 19 20 #define ARCH_SUN4I_A10 0 21 #define ARCH_SUN5I_A13 1 22 #define ARCH_SUN6I_A31 2 23 24 static struct resource adc_resources[] = { 25 DEFINE_RES_IRQ_NAMED(SUN4I_GPADC_IRQ_FIFO_DATA, "FIFO_DATA_PENDING"), 26 DEFINE_RES_IRQ_NAMED(SUN4I_GPADC_IRQ_TEMP_DATA, "TEMP_DATA_PENDING"), 27 }; 28 29 static const struct regmap_irq sun4i_gpadc_regmap_irq[] = { 30 REGMAP_IRQ_REG(SUN4I_GPADC_IRQ_FIFO_DATA, 0, 31 SUN4I_GPADC_INT_FIFOC_TP_DATA_IRQ_EN), 32 REGMAP_IRQ_REG(SUN4I_GPADC_IRQ_TEMP_DATA, 0, 33 SUN4I_GPADC_INT_FIFOC_TEMP_IRQ_EN), 34 }; 35 36 static const struct regmap_irq_chip sun4i_gpadc_regmap_irq_chip = { 37 .name = "sun4i_gpadc_irq_chip", 38 .status_base = SUN4I_GPADC_INT_FIFOS, 39 .ack_base = SUN4I_GPADC_INT_FIFOS, 40 .mask_base = SUN4I_GPADC_INT_FIFOC, 41 .init_ack_masked = true, 42 .mask_invert = true, 43 .irqs = sun4i_gpadc_regmap_irq, 44 .num_irqs = ARRAY_SIZE(sun4i_gpadc_regmap_irq), 45 .num_regs = 1, 46 }; 47 48 static struct mfd_cell sun4i_gpadc_cells[] = { 49 { 50 .name = "sun4i-a10-gpadc-iio", 51 .resources = adc_resources, 52 .num_resources = ARRAY_SIZE(adc_resources), 53 }, 54 { .name = "iio_hwmon" } 55 }; 56 57 static struct mfd_cell sun5i_gpadc_cells[] = { 58 { 59 .name = "sun5i-a13-gpadc-iio", 60 .resources = adc_resources, 61 .num_resources = ARRAY_SIZE(adc_resources), 62 }, 63 { .name = "iio_hwmon" }, 64 }; 65 66 static struct mfd_cell sun6i_gpadc_cells[] = { 67 { 68 .name = "sun6i-a31-gpadc-iio", 69 .resources = adc_resources, 70 .num_resources = ARRAY_SIZE(adc_resources), 71 }, 72 { .name = "iio_hwmon" }, 73 }; 74 75 static const struct regmap_config sun4i_gpadc_regmap_config = { 76 .reg_bits = 32, 77 .val_bits = 32, 78 .reg_stride = 4, 79 .fast_io = true, 80 }; 81 82 static const struct of_device_id sun4i_gpadc_of_match[] = { 83 { 84 .compatible = "allwinner,sun4i-a10-ts", 85 .data = (void *)ARCH_SUN4I_A10, 86 }, { 87 .compatible = "allwinner,sun5i-a13-ts", 88 .data = (void *)ARCH_SUN5I_A13, 89 }, { 90 .compatible = "allwinner,sun6i-a31-ts", 91 .data = (void *)ARCH_SUN6I_A31, 92 }, { /* sentinel */ } 93 }; 94 95 MODULE_DEVICE_TABLE(of, sun4i_gpadc_of_match); 96 97 static int sun4i_gpadc_probe(struct platform_device *pdev) 98 { 99 struct sun4i_gpadc_dev *dev; 100 struct resource *mem; 101 const struct of_device_id *of_id; 102 const struct mfd_cell *cells; 103 unsigned int irq, size; 104 int ret; 105 106 of_id = of_match_node(sun4i_gpadc_of_match, pdev->dev.of_node); 107 if (!of_id) 108 return -EINVAL; 109 110 switch ((long)of_id->data) { 111 case ARCH_SUN4I_A10: 112 cells = sun4i_gpadc_cells; 113 size = ARRAY_SIZE(sun4i_gpadc_cells); 114 break; 115 case ARCH_SUN5I_A13: 116 cells = sun5i_gpadc_cells; 117 size = ARRAY_SIZE(sun5i_gpadc_cells); 118 break; 119 case ARCH_SUN6I_A31: 120 cells = sun6i_gpadc_cells; 121 size = ARRAY_SIZE(sun6i_gpadc_cells); 122 break; 123 default: 124 return -EINVAL; 125 } 126 127 dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); 128 if (!dev) 129 return -ENOMEM; 130 131 mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 132 dev->base = devm_ioremap_resource(&pdev->dev, mem); 133 if (IS_ERR(dev->base)) 134 return PTR_ERR(dev->base); 135 136 dev->dev = &pdev->dev; 137 dev_set_drvdata(dev->dev, dev); 138 139 dev->regmap = devm_regmap_init_mmio(dev->dev, dev->base, 140 &sun4i_gpadc_regmap_config); 141 if (IS_ERR(dev->regmap)) { 142 ret = PTR_ERR(dev->regmap); 143 dev_err(&pdev->dev, "failed to init regmap: %d\n", ret); 144 return ret; 145 } 146 147 /* Disable all interrupts */ 148 regmap_write(dev->regmap, SUN4I_GPADC_INT_FIFOC, 0); 149 150 irq = platform_get_irq(pdev, 0); 151 ret = devm_regmap_add_irq_chip(&pdev->dev, dev->regmap, irq, 152 IRQF_ONESHOT, 0, 153 &sun4i_gpadc_regmap_irq_chip, 154 &dev->regmap_irqc); 155 if (ret) { 156 dev_err(&pdev->dev, "failed to add irq chip: %d\n", ret); 157 return ret; 158 } 159 160 ret = devm_mfd_add_devices(dev->dev, 0, cells, size, NULL, 0, NULL); 161 if (ret) { 162 dev_err(&pdev->dev, "failed to add MFD devices: %d\n", ret); 163 return ret; 164 } 165 166 return 0; 167 } 168 169 static struct platform_driver sun4i_gpadc_driver = { 170 .driver = { 171 .name = "sun4i-gpadc", 172 .of_match_table = of_match_ptr(sun4i_gpadc_of_match), 173 }, 174 .probe = sun4i_gpadc_probe, 175 }; 176 177 module_platform_driver(sun4i_gpadc_driver); 178 179 MODULE_DESCRIPTION("Allwinner sunxi platforms' GPADC MFD core driver"); 180 MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>"); 181 MODULE_LICENSE("GPL v2"); 182