/* * tlv320aic325x-irq.c -- Interrupt controller support for * TI TLV320AIC3xxx family * * Author: Mukund Navada * Mehar Bajwa * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include struct aic325x_irq_data { int mask; int status; }; static struct aic325x_irq_data aic325x_irqs[] = { { .mask = AIC3XXX_HEADSET_IN_M, .status = AIC3XXX_HEADSET_PLUG_UNPLUG_INT, }, { .mask = AIC3XXX_BUTTON_PRESS_M, .status = AIC3XXX_BUTTON_PRESS_INT, }, { .mask = AIC3XXX_DAC_DRC_THRES_M, .status = AIC3XXX_LEFT_DRC_THRES_INT | AIC3XXX_RIGHT_DRC_THRES_INT, }, { .mask = AIC3XXX_AGC_NOISE_M, .status = AIC3XXX_LEFT_AGC_NOISE_INT | AIC3XXX_RIGHT_AGC_NOISE_INT, }, { .mask = AIC3XXX_OVER_CURRENT_M, .status = AIC3XXX_LEFT_OUTPUT_DRIVER_OVERCURRENT_INT | AIC3XXX_RIGHT_OUTPUT_DRIVER_OVERCURRENT_INT, }, { .mask = AIC3XXX_OVERFLOW_M, .status = AIC3XXX_LEFT_DAC_OVERFLOW_INT | AIC3XXX_RIGHT_DAC_OVERFLOW_INT | AIC3XXX_MINIDSP_D_BARREL_SHIFT_OVERFLOW_INT | AIC3XXX_LEFT_ADC_OVERFLOW_INT | AIC3XXX_RIGHT_ADC_OVERFLOW_INT | AIC3XXX_MINIDSP_D_BARREL_SHIFT_OVERFLOW_INT, }, { .mask = AIC3XXX_SPK_OVERCURRENT_M, .status = AIC3XXX_SPK_OVER_CURRENT_INT, }, }; static void aic325x_irq_lock(struct irq_data *data) { struct aic325x *aic325x = irq_data_get_irq_chip_data(data); mutex_lock(&aic325x->irq_lock); } static void aic325x_irq_sync_unlock(struct irq_data *data) { struct aic325x *aic325x = irq_data_get_irq_chip_data(data); /* write back to hardware any change in irq mask */ if (aic325x->irq_masks_cur != aic325x->irq_masks_cache) { aic325x->irq_masks_cache = aic325x->irq_masks_cur; aic325x_reg_write(aic325x, AIC3XXX_INT1_CNTL, aic325x->irq_masks_cur); } mutex_unlock(&aic325x->irq_lock); } static void aic325x_irq_enable(struct irq_data *data) { struct aic325x *aic325x = irq_data_get_irq_chip_data(data); struct aic325x_irq_data *irq_data = &aic325x_irqs[data->hwirq]; aic325x->irq_masks_cur |= irq_data->mask; } static void aic325x_irq_disable(struct irq_data *data) { struct aic325x *aic325x = irq_data_get_irq_chip_data(data); struct aic325x_irq_data *irq_data = &aic325x_irqs[data->hwirq]; aic325x->irq_masks_cur &= ~irq_data->mask; } static struct irq_chip aic325x_irq_chip = { .name = "tlv320aic325x", .irq_bus_lock = aic325x_irq_lock, .irq_bus_sync_unlock = aic325x_irq_sync_unlock, .irq_enable = aic325x_irq_enable, .irq_disable = aic325x_irq_disable }; static irqreturn_t aic325x_irq_thread(int irq, void *data) { struct aic325x *aic325x = data; u8 status[4]; /* Reading sticky bit registers acknowledges the interrupt to the device */ aic325x_bulk_read(aic325x, AIC3XXX_INT_STICKY_FLAG1, 4, status); /* report */ if (status[2] & aic325x_irqs[AIC3XXX_IRQ_HEADSET_DETECT].status) handle_nested_irq(aic325x->irq_base); if (status[2] & aic325x_irqs[AIC3XXX_IRQ_BUTTON_PRESS].status) handle_nested_irq(aic325x->irq_base + 1); if (status[2] & aic325x_irqs[AIC3XXX_IRQ_DAC_DRC].status) handle_nested_irq(aic325x->irq_base + 2); if (status[3] & aic325x_irqs[AIC3XXX_IRQ_AGC_NOISE].status) handle_nested_irq(aic325x->irq_base + 3); if (status[2] & aic325x_irqs[AIC3XXX_IRQ_OVER_CURRENT].status) handle_nested_irq(aic325x->irq_base + 4); if (status[0] & aic325x_irqs[AIC3XXX_IRQ_OVERFLOW_EVENT].status) handle_nested_irq(aic325x->irq_base + 5); if (status[3] & aic325x_irqs[AIC3XXX_IRQ_SPEAKER_OVER_TEMP].status) handle_nested_irq(aic325x->irq_base + 6); return IRQ_HANDLED; } static int aic325x_irq_map(struct irq_domain *h, unsigned int virq, irq_hw_number_t hw) { struct aic325x *aic325x = h->host_data; irq_set_chip_data(virq, aic325x); irq_set_chip_and_handler(virq, &aic325x_irq_chip, handle_edge_irq); irq_set_nested_thread(virq, 1); /* ARM needs us to explicitly flag the IRQ as valid * and will set them noprobe when we do so. */ #ifdef CONFIG_ARM set_irq_flags(virq, IRQF_VALID); #else irq_set_noprobe(virq); #endif return 0; } static const struct irq_domain_ops aic325x_domain_ops = { .map = aic325x_irq_map, .xlate = irq_domain_xlate_twocell, }; int aic325x_irq_init(struct aic325x *aic325x) { int ret; mutex_init(&aic325x->irq_lock); /* mask the individual interrupt sources */ aic325x->irq_masks_cur = 0x0; aic325x->irq_masks_cache = 0x0; aic325x_reg_write(aic325x, AIC3XXX_INT1_CNTL, 0x0); if (!aic325x->irq) { dev_warn(aic325x->dev, "no interrupt specified\n"); aic325x->irq_base = 0; return 0; } if (aic325x->irq_base) { aic325x->domain = irq_domain_add_legacy(aic325x->dev->of_node, ARRAY_SIZE(aic325x_irqs), aic325x->irq_base, 0, &aic325x_domain_ops, aic325x); } else { aic325x->domain = irq_domain_add_linear(aic325x->dev->of_node, ARRAY_SIZE(aic325x_irqs), &aic325x_domain_ops, aic325x); /* initiallizing irq_base from irq_domain*/ } if (!aic325x->domain) { dev_err(aic325x->dev, "Failed to create IRQ domain\n"); return -ENOMEM; } aic325x->irq_base = irq_create_mapping(aic325x->domain, 0); ret = request_threaded_irq(aic325x->irq, NULL, aic325x_irq_thread, IRQF_ONESHOT, "tlv320aic325x", aic325x); if (ret < 0) { dev_err(aic325x->dev, "failed to request IRQ %d: %d\n", aic325x->irq, ret); return ret; } irq_set_irq_type(aic325x->irq, IRQF_TRIGGER_RISING); return 0; } EXPORT_SYMBOL(aic325x_irq_init); void aic325x_irq_exit(struct aic325x *aic325x) { if (aic325x->irq) free_irq(aic325x->irq, aic325x); } EXPORT_SYMBOL(aic325x_irq_exit); MODULE_AUTHOR("Mukund navada "); MODULE_AUTHOR("Mehar Bajwa "); MODULE_DESCRIPTION("Interrupt controller support for TI TLV320AIC3XXX family"); MODULE_LICENSE("GPL");