1761db353SHans de Goede // SPDX-License-Identifier: GPL-2.0+
2761db353SHans de Goede /*
3761db353SHans de Goede * Driver for the external-charger IRQ pass-through function of the
4761db353SHans de Goede * Intel Bay Trail Crystal Cove PMIC.
5761db353SHans de Goede *
6761db353SHans de Goede * Note this is NOT a power_supply class driver, it just deals with IRQ
7761db353SHans de Goede * pass-through, this requires a separate driver because the PMIC's
8761db353SHans de Goede * level 2 interrupt for this must be explicitly acked.
9761db353SHans de Goede */
10761db353SHans de Goede
11761db353SHans de Goede #include <linux/interrupt.h>
12761db353SHans de Goede #include <linux/irq.h>
13761db353SHans de Goede #include <linux/irqdomain.h>
14761db353SHans de Goede #include <linux/mfd/intel_soc_pmic.h>
15761db353SHans de Goede #include <linux/module.h>
16761db353SHans de Goede #include <linux/platform_device.h>
17761db353SHans de Goede #include <linux/regmap.h>
18761db353SHans de Goede
19761db353SHans de Goede #define CHGRIRQ_REG 0x0a
20*a29012abSHans de Goede #define MCHGRIRQ_REG 0x17
21761db353SHans de Goede
22761db353SHans de Goede struct crystal_cove_charger_data {
23761db353SHans de Goede struct mutex buslock; /* irq_bus_lock */
24761db353SHans de Goede struct irq_chip irqchip;
25761db353SHans de Goede struct regmap *regmap;
26761db353SHans de Goede struct irq_domain *irq_domain;
27761db353SHans de Goede int irq;
28761db353SHans de Goede int charger_irq;
29*a29012abSHans de Goede u8 mask;
30*a29012abSHans de Goede u8 new_mask;
31761db353SHans de Goede };
32761db353SHans de Goede
crystal_cove_charger_irq(int irq,void * data)33761db353SHans de Goede static irqreturn_t crystal_cove_charger_irq(int irq, void *data)
34761db353SHans de Goede {
35761db353SHans de Goede struct crystal_cove_charger_data *charger = data;
36761db353SHans de Goede
37761db353SHans de Goede /* No need to read CHGRIRQ_REG as there is only 1 IRQ */
38761db353SHans de Goede handle_nested_irq(charger->charger_irq);
39761db353SHans de Goede
40761db353SHans de Goede /* Ack CHGRIRQ 0 */
41761db353SHans de Goede regmap_write(charger->regmap, CHGRIRQ_REG, BIT(0));
42761db353SHans de Goede
43761db353SHans de Goede return IRQ_HANDLED;
44761db353SHans de Goede }
45761db353SHans de Goede
crystal_cove_charger_irq_bus_lock(struct irq_data * data)46761db353SHans de Goede static void crystal_cove_charger_irq_bus_lock(struct irq_data *data)
47761db353SHans de Goede {
48761db353SHans de Goede struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
49761db353SHans de Goede
50761db353SHans de Goede mutex_lock(&charger->buslock);
51761db353SHans de Goede }
52761db353SHans de Goede
crystal_cove_charger_irq_bus_sync_unlock(struct irq_data * data)53761db353SHans de Goede static void crystal_cove_charger_irq_bus_sync_unlock(struct irq_data *data)
54761db353SHans de Goede {
55761db353SHans de Goede struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
56761db353SHans de Goede
57*a29012abSHans de Goede if (charger->mask != charger->new_mask) {
58*a29012abSHans de Goede regmap_write(charger->regmap, MCHGRIRQ_REG, charger->new_mask);
59*a29012abSHans de Goede charger->mask = charger->new_mask;
60761db353SHans de Goede }
61761db353SHans de Goede
62761db353SHans de Goede mutex_unlock(&charger->buslock);
63761db353SHans de Goede }
64761db353SHans de Goede
crystal_cove_charger_irq_unmask(struct irq_data * data)65761db353SHans de Goede static void crystal_cove_charger_irq_unmask(struct irq_data *data)
66761db353SHans de Goede {
67761db353SHans de Goede struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
68761db353SHans de Goede
69*a29012abSHans de Goede charger->new_mask &= ~BIT(data->hwirq);
70761db353SHans de Goede }
71761db353SHans de Goede
crystal_cove_charger_irq_mask(struct irq_data * data)72761db353SHans de Goede static void crystal_cove_charger_irq_mask(struct irq_data *data)
73761db353SHans de Goede {
74761db353SHans de Goede struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
75761db353SHans de Goede
76*a29012abSHans de Goede charger->new_mask |= BIT(data->hwirq);
77761db353SHans de Goede }
78761db353SHans de Goede
crystal_cove_charger_rm_irq_domain(void * data)79761db353SHans de Goede static void crystal_cove_charger_rm_irq_domain(void *data)
80761db353SHans de Goede {
81761db353SHans de Goede struct crystal_cove_charger_data *charger = data;
82761db353SHans de Goede
83761db353SHans de Goede irq_domain_remove(charger->irq_domain);
84761db353SHans de Goede }
85761db353SHans de Goede
crystal_cove_charger_probe(struct platform_device * pdev)86761db353SHans de Goede static int crystal_cove_charger_probe(struct platform_device *pdev)
87761db353SHans de Goede {
88761db353SHans de Goede struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
89761db353SHans de Goede struct crystal_cove_charger_data *charger;
90761db353SHans de Goede int ret;
91761db353SHans de Goede
92761db353SHans de Goede charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
93761db353SHans de Goede if (!charger)
94761db353SHans de Goede return -ENOMEM;
95761db353SHans de Goede
96761db353SHans de Goede charger->regmap = pmic->regmap;
97761db353SHans de Goede mutex_init(&charger->buslock);
98761db353SHans de Goede
99761db353SHans de Goede charger->irq = platform_get_irq(pdev, 0);
100761db353SHans de Goede if (charger->irq < 0)
101761db353SHans de Goede return charger->irq;
102761db353SHans de Goede
103761db353SHans de Goede charger->irq_domain = irq_domain_create_linear(dev_fwnode(pdev->dev.parent), 1,
104761db353SHans de Goede &irq_domain_simple_ops, NULL);
105761db353SHans de Goede if (!charger->irq_domain)
106761db353SHans de Goede return -ENOMEM;
107761db353SHans de Goede
108761db353SHans de Goede /* Distuingish IRQ domain from others sharing (MFD) the same fwnode */
109761db353SHans de Goede irq_domain_update_bus_token(charger->irq_domain, DOMAIN_BUS_WAKEUP);
110761db353SHans de Goede
111761db353SHans de Goede ret = devm_add_action_or_reset(&pdev->dev, crystal_cove_charger_rm_irq_domain, charger);
112761db353SHans de Goede if (ret)
113761db353SHans de Goede return ret;
114761db353SHans de Goede
115761db353SHans de Goede charger->charger_irq = irq_create_mapping(charger->irq_domain, 0);
116761db353SHans de Goede if (!charger->charger_irq)
117761db353SHans de Goede return -ENOMEM;
118761db353SHans de Goede
119761db353SHans de Goede charger->irqchip.name = KBUILD_MODNAME;
120761db353SHans de Goede charger->irqchip.irq_unmask = crystal_cove_charger_irq_unmask;
121761db353SHans de Goede charger->irqchip.irq_mask = crystal_cove_charger_irq_mask;
122761db353SHans de Goede charger->irqchip.irq_bus_lock = crystal_cove_charger_irq_bus_lock;
123761db353SHans de Goede charger->irqchip.irq_bus_sync_unlock = crystal_cove_charger_irq_bus_sync_unlock;
124761db353SHans de Goede
125761db353SHans de Goede irq_set_chip_data(charger->charger_irq, charger);
126761db353SHans de Goede irq_set_chip_and_handler(charger->charger_irq, &charger->irqchip, handle_simple_irq);
127761db353SHans de Goede irq_set_nested_thread(charger->charger_irq, true);
128761db353SHans de Goede irq_set_noprobe(charger->charger_irq);
129761db353SHans de Goede
130*a29012abSHans de Goede /* Mask the single 2nd level IRQ before enabling the 1st level IRQ */
131*a29012abSHans de Goede charger->mask = charger->new_mask = BIT(0);
132*a29012abSHans de Goede regmap_write(charger->regmap, MCHGRIRQ_REG, charger->mask);
133*a29012abSHans de Goede
134761db353SHans de Goede ret = devm_request_threaded_irq(&pdev->dev, charger->irq, NULL,
135761db353SHans de Goede crystal_cove_charger_irq,
136*a29012abSHans de Goede IRQF_ONESHOT, KBUILD_MODNAME, charger);
137761db353SHans de Goede if (ret)
138761db353SHans de Goede return dev_err_probe(&pdev->dev, ret, "requesting irq\n");
139761db353SHans de Goede
140761db353SHans de Goede return 0;
141761db353SHans de Goede }
142761db353SHans de Goede
143761db353SHans de Goede static struct platform_driver crystal_cove_charger_driver = {
144761db353SHans de Goede .probe = crystal_cove_charger_probe,
145761db353SHans de Goede .driver = {
146761db353SHans de Goede .name = "crystal_cove_charger",
147761db353SHans de Goede },
148761db353SHans de Goede };
149761db353SHans de Goede module_platform_driver(crystal_cove_charger_driver);
150761db353SHans de Goede
151761db353SHans de Goede MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
152761db353SHans de Goede MODULE_DESCRIPTION("Intel Bay Trail Crystal Cove external charger IRQ pass-through");
153761db353SHans de Goede MODULE_LICENSE("GPL");
154