12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2380c09f6SLars-Peter Clausen /* NXP PCF50633 Power Management Unit (PMU) driver 3380c09f6SLars-Peter Clausen * 4380c09f6SLars-Peter Clausen * (C) 2006-2008 by Openmoko, Inc. 5380c09f6SLars-Peter Clausen * Author: Harald Welte <laforge@openmoko.org> 6380c09f6SLars-Peter Clausen * Balaji Rao <balajirrao@openmoko.org> 7380c09f6SLars-Peter Clausen * All rights reserved. 8380c09f6SLars-Peter Clausen */ 9380c09f6SLars-Peter Clausen 10380c09f6SLars-Peter Clausen #include <linux/interrupt.h> 11380c09f6SLars-Peter Clausen #include <linux/kernel.h> 12380c09f6SLars-Peter Clausen #include <linux/mutex.h> 135d4a357dSPaul Gortmaker #include <linux/export.h> 14380c09f6SLars-Peter Clausen #include <linux/slab.h> 15380c09f6SLars-Peter Clausen 16380c09f6SLars-Peter Clausen #include <linux/mfd/pcf50633/core.h> 17624e26e0SAxel Lin #include <linux/mfd/pcf50633/mbc.h> 18380c09f6SLars-Peter Clausen 19380c09f6SLars-Peter Clausen int pcf50633_register_irq(struct pcf50633 *pcf, int irq, 20380c09f6SLars-Peter Clausen void (*handler) (int, void *), void *data) 21380c09f6SLars-Peter Clausen { 22380c09f6SLars-Peter Clausen if (irq < 0 || irq >= PCF50633_NUM_IRQ || !handler) 23380c09f6SLars-Peter Clausen return -EINVAL; 24380c09f6SLars-Peter Clausen 25380c09f6SLars-Peter Clausen if (WARN_ON(pcf->irq_handler[irq].handler)) 26380c09f6SLars-Peter Clausen return -EBUSY; 27380c09f6SLars-Peter Clausen 28380c09f6SLars-Peter Clausen mutex_lock(&pcf->lock); 29380c09f6SLars-Peter Clausen pcf->irq_handler[irq].handler = handler; 30380c09f6SLars-Peter Clausen pcf->irq_handler[irq].data = data; 31380c09f6SLars-Peter Clausen mutex_unlock(&pcf->lock); 32380c09f6SLars-Peter Clausen 33380c09f6SLars-Peter Clausen return 0; 34380c09f6SLars-Peter Clausen } 35380c09f6SLars-Peter Clausen EXPORT_SYMBOL_GPL(pcf50633_register_irq); 36380c09f6SLars-Peter Clausen 37380c09f6SLars-Peter Clausen int pcf50633_free_irq(struct pcf50633 *pcf, int irq) 38380c09f6SLars-Peter Clausen { 39380c09f6SLars-Peter Clausen if (irq < 0 || irq >= PCF50633_NUM_IRQ) 40380c09f6SLars-Peter Clausen return -EINVAL; 41380c09f6SLars-Peter Clausen 42380c09f6SLars-Peter Clausen mutex_lock(&pcf->lock); 43380c09f6SLars-Peter Clausen pcf->irq_handler[irq].handler = NULL; 44380c09f6SLars-Peter Clausen mutex_unlock(&pcf->lock); 45380c09f6SLars-Peter Clausen 46380c09f6SLars-Peter Clausen return 0; 47380c09f6SLars-Peter Clausen } 48380c09f6SLars-Peter Clausen EXPORT_SYMBOL_GPL(pcf50633_free_irq); 49380c09f6SLars-Peter Clausen 50380c09f6SLars-Peter Clausen static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask) 51380c09f6SLars-Peter Clausen { 52380c09f6SLars-Peter Clausen u8 reg, bit; 53e7238fd2SJavier Martinez Canillas int idx; 54380c09f6SLars-Peter Clausen 55380c09f6SLars-Peter Clausen idx = irq >> 3; 56380c09f6SLars-Peter Clausen reg = PCF50633_REG_INT1M + idx; 57380c09f6SLars-Peter Clausen bit = 1 << (irq & 0x07); 58380c09f6SLars-Peter Clausen 59380c09f6SLars-Peter Clausen pcf50633_reg_set_bit_mask(pcf, reg, bit, mask ? bit : 0); 60380c09f6SLars-Peter Clausen 61380c09f6SLars-Peter Clausen mutex_lock(&pcf->lock); 62380c09f6SLars-Peter Clausen 63380c09f6SLars-Peter Clausen if (mask) 64380c09f6SLars-Peter Clausen pcf->mask_regs[idx] |= bit; 65380c09f6SLars-Peter Clausen else 66380c09f6SLars-Peter Clausen pcf->mask_regs[idx] &= ~bit; 67380c09f6SLars-Peter Clausen 68380c09f6SLars-Peter Clausen mutex_unlock(&pcf->lock); 69380c09f6SLars-Peter Clausen 70e7238fd2SJavier Martinez Canillas return 0; 71380c09f6SLars-Peter Clausen } 72380c09f6SLars-Peter Clausen 73380c09f6SLars-Peter Clausen int pcf50633_irq_mask(struct pcf50633 *pcf, int irq) 74380c09f6SLars-Peter Clausen { 75380c09f6SLars-Peter Clausen dev_dbg(pcf->dev, "Masking IRQ %d\n", irq); 76380c09f6SLars-Peter Clausen 77380c09f6SLars-Peter Clausen return __pcf50633_irq_mask_set(pcf, irq, 1); 78380c09f6SLars-Peter Clausen } 79380c09f6SLars-Peter Clausen EXPORT_SYMBOL_GPL(pcf50633_irq_mask); 80380c09f6SLars-Peter Clausen 81380c09f6SLars-Peter Clausen int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq) 82380c09f6SLars-Peter Clausen { 83380c09f6SLars-Peter Clausen dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq); 84380c09f6SLars-Peter Clausen 85380c09f6SLars-Peter Clausen return __pcf50633_irq_mask_set(pcf, irq, 0); 86380c09f6SLars-Peter Clausen } 87380c09f6SLars-Peter Clausen EXPORT_SYMBOL_GPL(pcf50633_irq_unmask); 88380c09f6SLars-Peter Clausen 89380c09f6SLars-Peter Clausen int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq) 90380c09f6SLars-Peter Clausen { 91380c09f6SLars-Peter Clausen u8 reg, bits; 92380c09f6SLars-Peter Clausen 93380c09f6SLars-Peter Clausen reg = irq >> 3; 94380c09f6SLars-Peter Clausen bits = 1 << (irq & 0x07); 95380c09f6SLars-Peter Clausen 96380c09f6SLars-Peter Clausen return pcf->mask_regs[reg] & bits; 97380c09f6SLars-Peter Clausen } 98380c09f6SLars-Peter Clausen EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get); 99380c09f6SLars-Peter Clausen 100380c09f6SLars-Peter Clausen static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq) 101380c09f6SLars-Peter Clausen { 102380c09f6SLars-Peter Clausen if (pcf->irq_handler[irq].handler) 103380c09f6SLars-Peter Clausen pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data); 104380c09f6SLars-Peter Clausen } 105380c09f6SLars-Peter Clausen 106380c09f6SLars-Peter Clausen /* Maximum amount of time ONKEY is held before emergency action is taken */ 107380c09f6SLars-Peter Clausen #define PCF50633_ONKEY1S_TIMEOUT 8 108380c09f6SLars-Peter Clausen 109380c09f6SLars-Peter Clausen static irqreturn_t pcf50633_irq(int irq, void *data) 110380c09f6SLars-Peter Clausen { 111380c09f6SLars-Peter Clausen struct pcf50633 *pcf = data; 112380c09f6SLars-Peter Clausen int ret, i, j; 113380c09f6SLars-Peter Clausen u8 pcf_int[5], chgstat; 114380c09f6SLars-Peter Clausen 115380c09f6SLars-Peter Clausen /* Read the 5 INT regs in one transaction */ 116380c09f6SLars-Peter Clausen ret = pcf50633_read_block(pcf, PCF50633_REG_INT1, 117380c09f6SLars-Peter Clausen ARRAY_SIZE(pcf_int), pcf_int); 118380c09f6SLars-Peter Clausen if (ret != ARRAY_SIZE(pcf_int)) { 119380c09f6SLars-Peter Clausen dev_err(pcf->dev, "Error reading INT registers\n"); 120380c09f6SLars-Peter Clausen 121380c09f6SLars-Peter Clausen /* 122380c09f6SLars-Peter Clausen * If this doesn't ACK the interrupt to the chip, we'll be 123380c09f6SLars-Peter Clausen * called once again as we're level triggered. 124380c09f6SLars-Peter Clausen */ 125380c09f6SLars-Peter Clausen goto out; 126380c09f6SLars-Peter Clausen } 127380c09f6SLars-Peter Clausen 128380c09f6SLars-Peter Clausen /* defeat 8s death from lowsys on A5 */ 129380c09f6SLars-Peter Clausen pcf50633_reg_write(pcf, PCF50633_REG_OOCSHDWN, 0x04); 130380c09f6SLars-Peter Clausen 131380c09f6SLars-Peter Clausen /* We immediately read the usb and adapter status. We thus make sure 132380c09f6SLars-Peter Clausen * only of USBINS/USBREM IRQ handlers are called */ 133380c09f6SLars-Peter Clausen if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) { 134380c09f6SLars-Peter Clausen chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); 135380c09f6SLars-Peter Clausen if (chgstat & (0x3 << 4)) 136380c09f6SLars-Peter Clausen pcf_int[0] &= ~PCF50633_INT1_USBREM; 137380c09f6SLars-Peter Clausen else 138380c09f6SLars-Peter Clausen pcf_int[0] &= ~PCF50633_INT1_USBINS; 139380c09f6SLars-Peter Clausen } 140380c09f6SLars-Peter Clausen 141380c09f6SLars-Peter Clausen /* Make sure only one of ADPINS or ADPREM is set */ 142380c09f6SLars-Peter Clausen if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) { 143380c09f6SLars-Peter Clausen chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); 144380c09f6SLars-Peter Clausen if (chgstat & (0x3 << 4)) 145380c09f6SLars-Peter Clausen pcf_int[0] &= ~PCF50633_INT1_ADPREM; 146380c09f6SLars-Peter Clausen else 147380c09f6SLars-Peter Clausen pcf_int[0] &= ~PCF50633_INT1_ADPINS; 148380c09f6SLars-Peter Clausen } 149380c09f6SLars-Peter Clausen 150380c09f6SLars-Peter Clausen dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x " 151380c09f6SLars-Peter Clausen "INT4=0x%02x INT5=0x%02x\n", pcf_int[0], 152380c09f6SLars-Peter Clausen pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]); 153380c09f6SLars-Peter Clausen 154380c09f6SLars-Peter Clausen /* Some revisions of the chip don't have a 8s standby mode on 155380c09f6SLars-Peter Clausen * ONKEY1S press. We try to manually do it in such cases. */ 156380c09f6SLars-Peter Clausen if ((pcf_int[0] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) { 157380c09f6SLars-Peter Clausen dev_info(pcf->dev, "ONKEY1S held for %d secs\n", 158380c09f6SLars-Peter Clausen pcf->onkey1s_held); 159380c09f6SLars-Peter Clausen if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT) 160380c09f6SLars-Peter Clausen if (pcf->pdata->force_shutdown) 161380c09f6SLars-Peter Clausen pcf->pdata->force_shutdown(pcf); 162380c09f6SLars-Peter Clausen } 163380c09f6SLars-Peter Clausen 164380c09f6SLars-Peter Clausen if (pcf_int[2] & PCF50633_INT3_ONKEY1S) { 165380c09f6SLars-Peter Clausen dev_info(pcf->dev, "ONKEY1S held\n"); 166380c09f6SLars-Peter Clausen pcf->onkey1s_held = 1 ; 167380c09f6SLars-Peter Clausen 168380c09f6SLars-Peter Clausen /* Unmask IRQ_SECOND */ 169380c09f6SLars-Peter Clausen pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M, 170380c09f6SLars-Peter Clausen PCF50633_INT1_SECOND); 171380c09f6SLars-Peter Clausen 172380c09f6SLars-Peter Clausen /* Unmask IRQ_ONKEYR */ 173380c09f6SLars-Peter Clausen pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M, 174380c09f6SLars-Peter Clausen PCF50633_INT2_ONKEYR); 175380c09f6SLars-Peter Clausen } 176380c09f6SLars-Peter Clausen 177380c09f6SLars-Peter Clausen if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) { 178380c09f6SLars-Peter Clausen pcf->onkey1s_held = 0; 179380c09f6SLars-Peter Clausen 180380c09f6SLars-Peter Clausen /* Mask SECOND and ONKEYR interrupts */ 181380c09f6SLars-Peter Clausen if (pcf->mask_regs[0] & PCF50633_INT1_SECOND) 182380c09f6SLars-Peter Clausen pcf50633_reg_set_bit_mask(pcf, 183380c09f6SLars-Peter Clausen PCF50633_REG_INT1M, 184380c09f6SLars-Peter Clausen PCF50633_INT1_SECOND, 185380c09f6SLars-Peter Clausen PCF50633_INT1_SECOND); 186380c09f6SLars-Peter Clausen 187380c09f6SLars-Peter Clausen if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR) 188380c09f6SLars-Peter Clausen pcf50633_reg_set_bit_mask(pcf, 189380c09f6SLars-Peter Clausen PCF50633_REG_INT2M, 190380c09f6SLars-Peter Clausen PCF50633_INT2_ONKEYR, 191380c09f6SLars-Peter Clausen PCF50633_INT2_ONKEYR); 192380c09f6SLars-Peter Clausen } 193380c09f6SLars-Peter Clausen 194380c09f6SLars-Peter Clausen /* Have we just resumed ? */ 195380c09f6SLars-Peter Clausen if (pcf->is_suspended) { 196380c09f6SLars-Peter Clausen pcf->is_suspended = 0; 197380c09f6SLars-Peter Clausen 198380c09f6SLars-Peter Clausen /* Set the resume reason filtering out non resumers */ 199380c09f6SLars-Peter Clausen for (i = 0; i < ARRAY_SIZE(pcf_int); i++) 200380c09f6SLars-Peter Clausen pcf->resume_reason[i] = pcf_int[i] & 201380c09f6SLars-Peter Clausen pcf->pdata->resumers[i]; 202380c09f6SLars-Peter Clausen 203380c09f6SLars-Peter Clausen /* Make sure we don't pass on any ONKEY events to 204380c09f6SLars-Peter Clausen * userspace now */ 205380c09f6SLars-Peter Clausen pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_ONKEYF); 206380c09f6SLars-Peter Clausen } 207380c09f6SLars-Peter Clausen 208380c09f6SLars-Peter Clausen for (i = 0; i < ARRAY_SIZE(pcf_int); i++) { 209380c09f6SLars-Peter Clausen /* Unset masked interrupts */ 210380c09f6SLars-Peter Clausen pcf_int[i] &= ~pcf->mask_regs[i]; 211380c09f6SLars-Peter Clausen 212380c09f6SLars-Peter Clausen for (j = 0; j < 8 ; j++) 213380c09f6SLars-Peter Clausen if (pcf_int[i] & (1 << j)) 214380c09f6SLars-Peter Clausen pcf50633_irq_call_handler(pcf, (i * 8) + j); 215380c09f6SLars-Peter Clausen } 216380c09f6SLars-Peter Clausen 217380c09f6SLars-Peter Clausen out: 218380c09f6SLars-Peter Clausen return IRQ_HANDLED; 219380c09f6SLars-Peter Clausen } 220380c09f6SLars-Peter Clausen 221380c09f6SLars-Peter Clausen #ifdef CONFIG_PM 222380c09f6SLars-Peter Clausen 223380c09f6SLars-Peter Clausen int pcf50633_irq_suspend(struct pcf50633 *pcf) 224380c09f6SLars-Peter Clausen { 225380c09f6SLars-Peter Clausen int ret; 226380c09f6SLars-Peter Clausen int i; 227380c09f6SLars-Peter Clausen u8 res[5]; 228380c09f6SLars-Peter Clausen 229380c09f6SLars-Peter Clausen 230380c09f6SLars-Peter Clausen /* Make sure our interrupt handlers are not called 231380c09f6SLars-Peter Clausen * henceforth */ 232380c09f6SLars-Peter Clausen disable_irq(pcf->irq); 233380c09f6SLars-Peter Clausen 234380c09f6SLars-Peter Clausen /* Save the masks */ 235380c09f6SLars-Peter Clausen ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M, 236380c09f6SLars-Peter Clausen ARRAY_SIZE(pcf->suspend_irq_masks), 237380c09f6SLars-Peter Clausen pcf->suspend_irq_masks); 238380c09f6SLars-Peter Clausen if (ret < 0) { 239380c09f6SLars-Peter Clausen dev_err(pcf->dev, "error saving irq masks\n"); 240380c09f6SLars-Peter Clausen goto out; 241380c09f6SLars-Peter Clausen } 242380c09f6SLars-Peter Clausen 243380c09f6SLars-Peter Clausen /* Write wakeup irq masks */ 244380c09f6SLars-Peter Clausen for (i = 0; i < ARRAY_SIZE(res); i++) 245380c09f6SLars-Peter Clausen res[i] = ~pcf->pdata->resumers[i]; 246380c09f6SLars-Peter Clausen 247380c09f6SLars-Peter Clausen ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, 248380c09f6SLars-Peter Clausen ARRAY_SIZE(res), &res[0]); 249380c09f6SLars-Peter Clausen if (ret < 0) { 250380c09f6SLars-Peter Clausen dev_err(pcf->dev, "error writing wakeup irq masks\n"); 251380c09f6SLars-Peter Clausen goto out; 252380c09f6SLars-Peter Clausen } 253380c09f6SLars-Peter Clausen 254380c09f6SLars-Peter Clausen pcf->is_suspended = 1; 255380c09f6SLars-Peter Clausen 256380c09f6SLars-Peter Clausen out: 257380c09f6SLars-Peter Clausen return ret; 258380c09f6SLars-Peter Clausen } 259380c09f6SLars-Peter Clausen 260380c09f6SLars-Peter Clausen int pcf50633_irq_resume(struct pcf50633 *pcf) 261380c09f6SLars-Peter Clausen { 262380c09f6SLars-Peter Clausen int ret; 263380c09f6SLars-Peter Clausen 264380c09f6SLars-Peter Clausen /* Write the saved mask registers */ 265380c09f6SLars-Peter Clausen ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, 266380c09f6SLars-Peter Clausen ARRAY_SIZE(pcf->suspend_irq_masks), 267380c09f6SLars-Peter Clausen pcf->suspend_irq_masks); 268380c09f6SLars-Peter Clausen if (ret < 0) 269380c09f6SLars-Peter Clausen dev_err(pcf->dev, "Error restoring saved suspend masks\n"); 270380c09f6SLars-Peter Clausen 271380c09f6SLars-Peter Clausen enable_irq(pcf->irq); 272380c09f6SLars-Peter Clausen 273380c09f6SLars-Peter Clausen return ret; 274380c09f6SLars-Peter Clausen } 275380c09f6SLars-Peter Clausen 276380c09f6SLars-Peter Clausen #endif 277380c09f6SLars-Peter Clausen 278380c09f6SLars-Peter Clausen int pcf50633_irq_init(struct pcf50633 *pcf, int irq) 279380c09f6SLars-Peter Clausen { 280380c09f6SLars-Peter Clausen int ret; 281380c09f6SLars-Peter Clausen 282380c09f6SLars-Peter Clausen pcf->irq = irq; 283380c09f6SLars-Peter Clausen 284380c09f6SLars-Peter Clausen /* Enable all interrupts except RTC SECOND */ 285380c09f6SLars-Peter Clausen pcf->mask_regs[0] = 0x80; 286380c09f6SLars-Peter Clausen pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]); 287380c09f6SLars-Peter Clausen pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00); 288380c09f6SLars-Peter Clausen pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00); 289380c09f6SLars-Peter Clausen pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00); 290380c09f6SLars-Peter Clausen pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00); 291380c09f6SLars-Peter Clausen 292380c09f6SLars-Peter Clausen ret = request_threaded_irq(irq, NULL, pcf50633_irq, 293380c09f6SLars-Peter Clausen IRQF_TRIGGER_LOW | IRQF_ONESHOT, 294380c09f6SLars-Peter Clausen "pcf50633", pcf); 295380c09f6SLars-Peter Clausen 296380c09f6SLars-Peter Clausen if (ret) 297380c09f6SLars-Peter Clausen dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); 298380c09f6SLars-Peter Clausen 299380c09f6SLars-Peter Clausen if (enable_irq_wake(irq) < 0) 300380c09f6SLars-Peter Clausen dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source" 301380c09f6SLars-Peter Clausen "in this hardware revision", irq); 302380c09f6SLars-Peter Clausen 303380c09f6SLars-Peter Clausen return ret; 304380c09f6SLars-Peter Clausen } 305380c09f6SLars-Peter Clausen 306380c09f6SLars-Peter Clausen void pcf50633_irq_free(struct pcf50633 *pcf) 307380c09f6SLars-Peter Clausen { 308380c09f6SLars-Peter Clausen free_irq(pcf->irq, pcf); 309380c09f6SLars-Peter Clausen } 310