cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

crystal_cove_charger.c (4536B)


      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#define MCHGRIRQ_REG					0x17
     21
     22struct crystal_cove_charger_data {
     23	struct mutex buslock; /* irq_bus_lock */
     24	struct irq_chip irqchip;
     25	struct regmap *regmap;
     26	struct irq_domain *irq_domain;
     27	int irq;
     28	int charger_irq;
     29	u8 mask;
     30	u8 new_mask;
     31};
     32
     33static irqreturn_t crystal_cove_charger_irq(int irq, void *data)
     34{
     35	struct crystal_cove_charger_data *charger = data;
     36
     37	/* No need to read CHGRIRQ_REG as there is only 1 IRQ */
     38	handle_nested_irq(charger->charger_irq);
     39
     40	/* Ack CHGRIRQ 0 */
     41	regmap_write(charger->regmap, CHGRIRQ_REG, BIT(0));
     42
     43	return IRQ_HANDLED;
     44}
     45
     46static void crystal_cove_charger_irq_bus_lock(struct irq_data *data)
     47{
     48	struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
     49
     50	mutex_lock(&charger->buslock);
     51}
     52
     53static void crystal_cove_charger_irq_bus_sync_unlock(struct irq_data *data)
     54{
     55	struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
     56
     57	if (charger->mask != charger->new_mask) {
     58		regmap_write(charger->regmap, MCHGRIRQ_REG, charger->new_mask);
     59		charger->mask = charger->new_mask;
     60	}
     61
     62	mutex_unlock(&charger->buslock);
     63}
     64
     65static void crystal_cove_charger_irq_unmask(struct irq_data *data)
     66{
     67	struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
     68
     69	charger->new_mask &= ~BIT(data->hwirq);
     70}
     71
     72static void crystal_cove_charger_irq_mask(struct irq_data *data)
     73{
     74	struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
     75
     76	charger->new_mask |= BIT(data->hwirq);
     77}
     78
     79static void crystal_cove_charger_rm_irq_domain(void *data)
     80{
     81	struct crystal_cove_charger_data *charger = data;
     82
     83	irq_domain_remove(charger->irq_domain);
     84}
     85
     86static int crystal_cove_charger_probe(struct platform_device *pdev)
     87{
     88	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
     89	struct crystal_cove_charger_data *charger;
     90	int ret;
     91
     92	charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
     93	if (!charger)
     94		return -ENOMEM;
     95
     96	charger->regmap = pmic->regmap;
     97	mutex_init(&charger->buslock);
     98
     99	charger->irq = platform_get_irq(pdev, 0);
    100	if (charger->irq < 0)
    101		return charger->irq;
    102
    103	charger->irq_domain = irq_domain_create_linear(dev_fwnode(pdev->dev.parent), 1,
    104						       &irq_domain_simple_ops, NULL);
    105	if (!charger->irq_domain)
    106		return -ENOMEM;
    107
    108	/* Distuingish IRQ domain from others sharing (MFD) the same fwnode */
    109	irq_domain_update_bus_token(charger->irq_domain, DOMAIN_BUS_WAKEUP);
    110
    111	ret = devm_add_action_or_reset(&pdev->dev, crystal_cove_charger_rm_irq_domain, charger);
    112	if (ret)
    113		return ret;
    114
    115	charger->charger_irq = irq_create_mapping(charger->irq_domain, 0);
    116	if (!charger->charger_irq)
    117		return -ENOMEM;
    118
    119	charger->irqchip.name = KBUILD_MODNAME;
    120	charger->irqchip.irq_unmask = crystal_cove_charger_irq_unmask;
    121	charger->irqchip.irq_mask = crystal_cove_charger_irq_mask;
    122	charger->irqchip.irq_bus_lock = crystal_cove_charger_irq_bus_lock;
    123	charger->irqchip.irq_bus_sync_unlock = crystal_cove_charger_irq_bus_sync_unlock;
    124
    125	irq_set_chip_data(charger->charger_irq, charger);
    126	irq_set_chip_and_handler(charger->charger_irq, &charger->irqchip, handle_simple_irq);
    127	irq_set_nested_thread(charger->charger_irq, true);
    128	irq_set_noprobe(charger->charger_irq);
    129
    130	/* Mask the single 2nd level IRQ before enabling the 1st level IRQ */
    131	charger->mask = charger->new_mask = BIT(0);
    132	regmap_write(charger->regmap, MCHGRIRQ_REG, charger->mask);
    133
    134	ret = devm_request_threaded_irq(&pdev->dev, charger->irq, NULL,
    135					crystal_cove_charger_irq,
    136					IRQF_ONESHOT, KBUILD_MODNAME, charger);
    137	if (ret)
    138		return dev_err_probe(&pdev->dev, ret, "requesting irq\n");
    139
    140	return 0;
    141}
    142
    143static struct platform_driver crystal_cove_charger_driver = {
    144	.probe = crystal_cove_charger_probe,
    145	.driver = {
    146		.name = "crystal_cove_charger",
    147	},
    148};
    149module_platform_driver(crystal_cove_charger_driver);
    150
    151MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
    152MODULE_DESCRIPTION("Intel Bay Trail Crystal Cove external charger IRQ pass-through");
    153MODULE_LICENSE("GPL");