1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Driver for the external-charger IRQ pass-through function of the 4 * Intel Bay Trail Crystal Cove PMIC. 5 * 6 * Note this is NOT a power_supply class driver, it just deals with IRQ 7 * pass-through, this requires a separate driver because the PMIC's 8 * level 2 interrupt for this must be explicitly acked. 9 */ 10 11 #include <linux/interrupt.h> 12 #include <linux/irq.h> 13 #include <linux/irqdomain.h> 14 #include <linux/mfd/intel_soc_pmic.h> 15 #include <linux/module.h> 16 #include <linux/platform_device.h> 17 #include <linux/regmap.h> 18 19 #define CHGRIRQ_REG 0x0a 20 21 struct crystal_cove_charger_data { 22 struct mutex buslock; /* irq_bus_lock */ 23 struct irq_chip irqchip; 24 struct regmap *regmap; 25 struct irq_domain *irq_domain; 26 int irq; 27 int charger_irq; 28 bool irq_enabled; 29 bool irq_is_enabled; 30 }; 31 32 static irqreturn_t crystal_cove_charger_irq(int irq, void *data) 33 { 34 struct crystal_cove_charger_data *charger = data; 35 36 /* No need to read CHGRIRQ_REG as there is only 1 IRQ */ 37 handle_nested_irq(charger->charger_irq); 38 39 /* Ack CHGRIRQ 0 */ 40 regmap_write(charger->regmap, CHGRIRQ_REG, BIT(0)); 41 42 return IRQ_HANDLED; 43 } 44 45 static void crystal_cove_charger_irq_bus_lock(struct irq_data *data) 46 { 47 struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data); 48 49 mutex_lock(&charger->buslock); 50 } 51 52 static void crystal_cove_charger_irq_bus_sync_unlock(struct irq_data *data) 53 { 54 struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data); 55 56 if (charger->irq_is_enabled != charger->irq_enabled) { 57 if (charger->irq_enabled) 58 enable_irq(charger->irq); 59 else 60 disable_irq(charger->irq); 61 62 charger->irq_is_enabled = charger->irq_enabled; 63 } 64 65 mutex_unlock(&charger->buslock); 66 } 67 68 static void crystal_cove_charger_irq_unmask(struct irq_data *data) 69 { 70 struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data); 71 72 charger->irq_enabled = true; 73 } 74 75 static void crystal_cove_charger_irq_mask(struct irq_data *data) 76 { 77 struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data); 78 79 charger->irq_enabled = false; 80 } 81 82 static void crystal_cove_charger_rm_irq_domain(void *data) 83 { 84 struct crystal_cove_charger_data *charger = data; 85 86 irq_domain_remove(charger->irq_domain); 87 } 88 89 static int crystal_cove_charger_probe(struct platform_device *pdev) 90 { 91 struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); 92 struct crystal_cove_charger_data *charger; 93 int ret; 94 95 charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); 96 if (!charger) 97 return -ENOMEM; 98 99 charger->regmap = pmic->regmap; 100 mutex_init(&charger->buslock); 101 102 charger->irq = platform_get_irq(pdev, 0); 103 if (charger->irq < 0) 104 return charger->irq; 105 106 charger->irq_domain = irq_domain_create_linear(dev_fwnode(pdev->dev.parent), 1, 107 &irq_domain_simple_ops, NULL); 108 if (!charger->irq_domain) 109 return -ENOMEM; 110 111 /* Distuingish IRQ domain from others sharing (MFD) the same fwnode */ 112 irq_domain_update_bus_token(charger->irq_domain, DOMAIN_BUS_WAKEUP); 113 114 ret = devm_add_action_or_reset(&pdev->dev, crystal_cove_charger_rm_irq_domain, charger); 115 if (ret) 116 return ret; 117 118 charger->charger_irq = irq_create_mapping(charger->irq_domain, 0); 119 if (!charger->charger_irq) 120 return -ENOMEM; 121 122 charger->irqchip.name = KBUILD_MODNAME; 123 charger->irqchip.irq_unmask = crystal_cove_charger_irq_unmask; 124 charger->irqchip.irq_mask = crystal_cove_charger_irq_mask; 125 charger->irqchip.irq_bus_lock = crystal_cove_charger_irq_bus_lock; 126 charger->irqchip.irq_bus_sync_unlock = crystal_cove_charger_irq_bus_sync_unlock; 127 128 irq_set_chip_data(charger->charger_irq, charger); 129 irq_set_chip_and_handler(charger->charger_irq, &charger->irqchip, handle_simple_irq); 130 irq_set_nested_thread(charger->charger_irq, true); 131 irq_set_noprobe(charger->charger_irq); 132 133 ret = devm_request_threaded_irq(&pdev->dev, charger->irq, NULL, 134 crystal_cove_charger_irq, 135 IRQF_ONESHOT | IRQF_NO_AUTOEN, 136 KBUILD_MODNAME, charger); 137 if (ret) 138 return dev_err_probe(&pdev->dev, ret, "requesting irq\n"); 139 140 return 0; 141 } 142 143 static struct platform_driver crystal_cove_charger_driver = { 144 .probe = crystal_cove_charger_probe, 145 .driver = { 146 .name = "crystal_cove_charger", 147 }, 148 }; 149 module_platform_driver(crystal_cove_charger_driver); 150 151 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com"); 152 MODULE_DESCRIPTION("Intel Bay Trail Crystal Cove external charger IRQ pass-through"); 153 MODULE_LICENSE("GPL"); 154