| xj | b04a402 | 2021-11-25 15:01:52 +0800 | [diff] [blame] | 1 | /* | 
|  | 2 | * Copyright (C) 2014-2015 Pengutronix, Markus Pargmann <mpa@pengutronix.de> | 
|  | 3 | * | 
|  | 4 | * This program is free software; you can redistribute it and/or modify it under | 
|  | 5 | * the terms of the GNU General Public License version 2 as published by the | 
|  | 6 | * Free Software Foundation. | 
|  | 7 | */ | 
|  | 8 |  | 
|  | 9 | #include <linux/clk.h> | 
|  | 10 | #include <linux/interrupt.h> | 
|  | 11 | #include <linux/irqchip/chained_irq.h> | 
|  | 12 | #include <linux/irqdesc.h> | 
|  | 13 | #include <linux/irqdomain.h> | 
|  | 14 | #include <linux/irq.h> | 
|  | 15 | #include <linux/mfd/imx25-tsadc.h> | 
|  | 16 | #include <linux/module.h> | 
|  | 17 | #include <linux/of.h> | 
|  | 18 | #include <linux/of_platform.h> | 
|  | 19 | #include <linux/platform_device.h> | 
|  | 20 | #include <linux/regmap.h> | 
|  | 21 |  | 
|  | 22 | static struct regmap_config mx25_tsadc_regmap_config = { | 
|  | 23 | .fast_io = true, | 
|  | 24 | .max_register = 8, | 
|  | 25 | .reg_bits = 32, | 
|  | 26 | .val_bits = 32, | 
|  | 27 | .reg_stride = 4, | 
|  | 28 | }; | 
|  | 29 |  | 
|  | 30 | static void mx25_tsadc_irq_handler(struct irq_desc *desc) | 
|  | 31 | { | 
|  | 32 | struct mx25_tsadc *tsadc = irq_desc_get_handler_data(desc); | 
|  | 33 | struct irq_chip *chip = irq_desc_get_chip(desc); | 
|  | 34 | u32 status; | 
|  | 35 |  | 
|  | 36 | chained_irq_enter(chip, desc); | 
|  | 37 |  | 
|  | 38 | regmap_read(tsadc->regs, MX25_TSC_TGSR, &status); | 
|  | 39 |  | 
|  | 40 | if (status & MX25_TGSR_GCQ_INT) | 
|  | 41 | generic_handle_irq(irq_find_mapping(tsadc->domain, 1)); | 
|  | 42 |  | 
|  | 43 | if (status & MX25_TGSR_TCQ_INT) | 
|  | 44 | generic_handle_irq(irq_find_mapping(tsadc->domain, 0)); | 
|  | 45 |  | 
|  | 46 | chained_irq_exit(chip, desc); | 
|  | 47 | } | 
|  | 48 |  | 
|  | 49 | static int mx25_tsadc_domain_map(struct irq_domain *d, unsigned int irq, | 
|  | 50 | irq_hw_number_t hwirq) | 
|  | 51 | { | 
|  | 52 | struct mx25_tsadc *tsadc = d->host_data; | 
|  | 53 |  | 
|  | 54 | irq_set_chip_data(irq, tsadc); | 
|  | 55 | irq_set_chip_and_handler(irq, &dummy_irq_chip, | 
|  | 56 | handle_level_irq); | 
|  | 57 | irq_modify_status(irq, IRQ_NOREQUEST, IRQ_NOPROBE); | 
|  | 58 |  | 
|  | 59 | return 0; | 
|  | 60 | } | 
|  | 61 |  | 
|  | 62 | static const struct irq_domain_ops mx25_tsadc_domain_ops = { | 
|  | 63 | .map = mx25_tsadc_domain_map, | 
|  | 64 | .xlate = irq_domain_xlate_onecell, | 
|  | 65 | }; | 
|  | 66 |  | 
|  | 67 | static int mx25_tsadc_setup_irq(struct platform_device *pdev, | 
|  | 68 | struct mx25_tsadc *tsadc) | 
|  | 69 | { | 
|  | 70 | struct device *dev = &pdev->dev; | 
|  | 71 | struct device_node *np = dev->of_node; | 
|  | 72 | int irq; | 
|  | 73 |  | 
|  | 74 | irq = platform_get_irq(pdev, 0); | 
|  | 75 | if (irq <= 0) { | 
|  | 76 | dev_err(dev, "Failed to get irq\n"); | 
|  | 77 | return irq; | 
|  | 78 | } | 
|  | 79 |  | 
|  | 80 | tsadc->domain = irq_domain_add_simple(np, 2, 0, &mx25_tsadc_domain_ops, | 
|  | 81 | tsadc); | 
|  | 82 | if (!tsadc->domain) { | 
|  | 83 | dev_err(dev, "Failed to add irq domain\n"); | 
|  | 84 | return -ENOMEM; | 
|  | 85 | } | 
|  | 86 |  | 
|  | 87 | irq_set_chained_handler_and_data(irq, mx25_tsadc_irq_handler, tsadc); | 
|  | 88 |  | 
|  | 89 | return 0; | 
|  | 90 | } | 
|  | 91 |  | 
|  | 92 | static void mx25_tsadc_setup_clk(struct platform_device *pdev, | 
|  | 93 | struct mx25_tsadc *tsadc) | 
|  | 94 | { | 
|  | 95 | unsigned clk_div; | 
|  | 96 |  | 
|  | 97 | /* | 
|  | 98 | * According to the datasheet the ADC clock should never | 
|  | 99 | * exceed 1,75 MHz. Base clock is the IPG and the ADC unit uses | 
|  | 100 | * a funny clock divider. To keep the ADC conversion time constant | 
|  | 101 | * adapt the ADC internal clock divider to the IPG clock rate. | 
|  | 102 | */ | 
|  | 103 |  | 
|  | 104 | dev_dbg(&pdev->dev, "Found master clock at %lu Hz\n", | 
|  | 105 | clk_get_rate(tsadc->clk)); | 
|  | 106 |  | 
|  | 107 | clk_div = DIV_ROUND_UP(clk_get_rate(tsadc->clk), 1750000); | 
|  | 108 | dev_dbg(&pdev->dev, "Setting up ADC clock divider to %u\n", clk_div); | 
|  | 109 |  | 
|  | 110 | /* adc clock = IPG clock / (2 * div + 2) */ | 
|  | 111 | clk_div -= 2; | 
|  | 112 | clk_div /= 2; | 
|  | 113 |  | 
|  | 114 | /* | 
|  | 115 | * the ADC clock divider changes its behaviour when values below 4 | 
|  | 116 | * are used: it is fixed to "/ 10" in this case | 
|  | 117 | */ | 
|  | 118 | clk_div = max_t(unsigned, 4, clk_div); | 
|  | 119 |  | 
|  | 120 | dev_dbg(&pdev->dev, "Resulting ADC conversion clock at %lu Hz\n", | 
|  | 121 | clk_get_rate(tsadc->clk) / (2 * clk_div + 2)); | 
|  | 122 |  | 
|  | 123 | regmap_update_bits(tsadc->regs, MX25_TSC_TGCR, | 
|  | 124 | MX25_TGCR_ADCCLKCFG(0x1f), | 
|  | 125 | MX25_TGCR_ADCCLKCFG(clk_div)); | 
|  | 126 | } | 
|  | 127 |  | 
|  | 128 | static int mx25_tsadc_probe(struct platform_device *pdev) | 
|  | 129 | { | 
|  | 130 | struct device *dev = &pdev->dev; | 
|  | 131 | struct mx25_tsadc *tsadc; | 
|  | 132 | struct resource *res; | 
|  | 133 | int ret; | 
|  | 134 | void __iomem *iomem; | 
|  | 135 |  | 
|  | 136 | tsadc = devm_kzalloc(dev, sizeof(*tsadc), GFP_KERNEL); | 
|  | 137 | if (!tsadc) | 
|  | 138 | return -ENOMEM; | 
|  | 139 |  | 
|  | 140 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | 141 | iomem = devm_ioremap_resource(dev, res); | 
|  | 142 | if (IS_ERR(iomem)) | 
|  | 143 | return PTR_ERR(iomem); | 
|  | 144 |  | 
|  | 145 | tsadc->regs = devm_regmap_init_mmio(dev, iomem, | 
|  | 146 | &mx25_tsadc_regmap_config); | 
|  | 147 | if (IS_ERR(tsadc->regs)) { | 
|  | 148 | dev_err(dev, "Failed to initialize regmap\n"); | 
|  | 149 | return PTR_ERR(tsadc->regs); | 
|  | 150 | } | 
|  | 151 |  | 
|  | 152 | tsadc->clk = devm_clk_get(dev, "ipg"); | 
|  | 153 | if (IS_ERR(tsadc->clk)) { | 
|  | 154 | dev_err(dev, "Failed to get ipg clock\n"); | 
|  | 155 | return PTR_ERR(tsadc->clk); | 
|  | 156 | } | 
|  | 157 |  | 
|  | 158 | /* setup clock according to the datasheet */ | 
|  | 159 | mx25_tsadc_setup_clk(pdev, tsadc); | 
|  | 160 |  | 
|  | 161 | /* Enable clock and reset the component */ | 
|  | 162 | regmap_update_bits(tsadc->regs, MX25_TSC_TGCR, MX25_TGCR_CLK_EN, | 
|  | 163 | MX25_TGCR_CLK_EN); | 
|  | 164 | regmap_update_bits(tsadc->regs, MX25_TSC_TGCR, MX25_TGCR_TSC_RST, | 
|  | 165 | MX25_TGCR_TSC_RST); | 
|  | 166 |  | 
|  | 167 | /* Setup powersaving mode, but enable internal reference voltage */ | 
|  | 168 | regmap_update_bits(tsadc->regs, MX25_TSC_TGCR, MX25_TGCR_POWERMODE_MASK, | 
|  | 169 | MX25_TGCR_POWERMODE_SAVE); | 
|  | 170 | regmap_update_bits(tsadc->regs, MX25_TSC_TGCR, MX25_TGCR_INTREFEN, | 
|  | 171 | MX25_TGCR_INTREFEN); | 
|  | 172 |  | 
|  | 173 | ret = mx25_tsadc_setup_irq(pdev, tsadc); | 
|  | 174 | if (ret) | 
|  | 175 | return ret; | 
|  | 176 |  | 
|  | 177 | platform_set_drvdata(pdev, tsadc); | 
|  | 178 |  | 
|  | 179 | return devm_of_platform_populate(dev); | 
|  | 180 | } | 
|  | 181 |  | 
|  | 182 | static int mx25_tsadc_remove(struct platform_device *pdev) | 
|  | 183 | { | 
|  | 184 | struct mx25_tsadc *tsadc = platform_get_drvdata(pdev); | 
|  | 185 | int irq = platform_get_irq(pdev, 0); | 
|  | 186 |  | 
|  | 187 | if (irq) { | 
|  | 188 | irq_set_chained_handler_and_data(irq, NULL, NULL); | 
|  | 189 | irq_domain_remove(tsadc->domain); | 
|  | 190 | } | 
|  | 191 |  | 
|  | 192 | return 0; | 
|  | 193 | } | 
|  | 194 |  | 
|  | 195 | static const struct of_device_id mx25_tsadc_ids[] = { | 
|  | 196 | { .compatible = "fsl,imx25-tsadc" }, | 
|  | 197 | { /* Sentinel */ } | 
|  | 198 | }; | 
|  | 199 | MODULE_DEVICE_TABLE(of, mx25_tsadc_ids); | 
|  | 200 |  | 
|  | 201 | static struct platform_driver mx25_tsadc_driver = { | 
|  | 202 | .driver = { | 
|  | 203 | .name = "mx25-tsadc", | 
|  | 204 | .of_match_table = of_match_ptr(mx25_tsadc_ids), | 
|  | 205 | }, | 
|  | 206 | .probe = mx25_tsadc_probe, | 
|  | 207 | .remove = mx25_tsadc_remove, | 
|  | 208 | }; | 
|  | 209 | module_platform_driver(mx25_tsadc_driver); | 
|  | 210 |  | 
|  | 211 | MODULE_DESCRIPTION("MFD for ADC/TSC for Freescale mx25"); | 
|  | 212 | MODULE_AUTHOR("Markus Pargmann <mpa@pengutronix.de>"); | 
|  | 213 | MODULE_LICENSE("GPL v2"); | 
|  | 214 | MODULE_ALIAS("platform:mx25-tsadc"); |