/* * ALSA SoC Texas Instruments TAS2770 20-W Digital Input Mono Class-D * Audio Amplifier with Speaker I/V Sense * * Copyright (C) 2016 Texas Instruments, Inc. * * Author: saiprasad * * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * */ #ifdef CONFIG_TAS2770_REGMAP #define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tas2770.h" #include "tas2770-codec.h" static const struct reg_default tas2770_reg_defaults[] = { { TAS2770_Page, 0x00 }, { TAS2770_SoftwareReset, 0x00 }, { TAS2770_PowerControl, 0x0e }, { TAS2770_PlaybackConfigurationReg0, 0x10 }, { TAS2770_PlaybackConfigurationReg1, 0x01 }, { TAS2770_PlaybackConfigurationReg2, 0x00 }, { TAS2770_MiscConfigurationReg0, 0x07 }, { TAS2770_TDMConfigurationReg1, 0x02 }, { TAS2770_TDMConfigurationReg2, 0x0a }, { TAS2770_TDMConfigurationReg3, 0x10 }, { TAS2770_InterruptMaskReg0, 0xfc }, { TAS2770_InterruptMaskReg1, 0xb1 }, { TAS2770_InterruptConfiguration, 0x05 }, { TAS2770_MiscIRQ, 0x81 }, { TAS2770_ClockConfiguration, 0x0c }, }; static bool tas2770_volatile(struct device *dev, unsigned int reg) { switch (reg) { case TAS2770_Page: /* regmap implementation requires this */ case TAS2770_SoftwareReset: /* always clears after write */ case TAS2770_BrownOutPreventionReg0:/* has a self clearing bit */ case TAS2770_LiveInterruptReg0: case TAS2770_LiveInterruptReg1: case TAS2770_LatchedInterruptReg0:/* Sticky interrupt flags */ case TAS2770_LatchedInterruptReg1:/* Sticky interrupt flags */ case TAS2770_VBATMSB: case TAS2770_VBATLSB: case TAS2770_TEMPMSB: case TAS2770_TEMPLSB: return true; } return false; } static bool tas2770_writeable(struct device *dev, unsigned int reg) { switch (reg) { case TAS2770_LiveInterruptReg0: case TAS2770_LiveInterruptReg1: case TAS2770_LatchedInterruptReg0: case TAS2770_LatchedInterruptReg1: case TAS2770_VBATMSB: case TAS2770_VBATLSB: case TAS2770_TEMPMSB: case TAS2770_TEMPLSB: case TAS2770_TDMClockdetectionmonitor: case TAS2770_RevisionandPGID: return false; } return true; } static const struct regmap_config tas2770_i2c_regmap = { .reg_bits = 8, .val_bits = 8, .writeable_reg = tas2770_writeable, .volatile_reg = tas2770_volatile, .reg_defaults = tas2770_reg_defaults, .num_reg_defaults = ARRAY_SIZE(tas2770_reg_defaults), .cache_type = REGCACHE_RBTREE, .max_register = 1 * 128, }; static void tas2770_hw_reset(struct tas2770_priv *pTAS2770) { if (gpio_is_valid(pTAS2770->mnResetGPIO)) { gpio_direction_output(pTAS2770->mnResetGPIO, 0); msleep(5); gpio_direction_output(pTAS2770->mnResetGPIO, 1); msleep(2); } pTAS2770->mnCurrentBook = -1; pTAS2770->mnCurrentPage = -1; } void tas2770_enableIRQ(struct tas2770_priv *pTAS2770, bool enable) { if (enable) { if (pTAS2770->mbIRQEnable) return; if (gpio_is_valid(pTAS2770->mnIRQGPIO)) enable_irq(pTAS2770->mnIRQ); schedule_delayed_work(&pTAS2770->irq_work, msecs_to_jiffies(10)); pTAS2770->mbIRQEnable = true; } else { if (!pTAS2770->mbIRQEnable) return; if (gpio_is_valid(pTAS2770->mnIRQGPIO)) disable_irq_nosync(pTAS2770->mnIRQ); pTAS2770->mbIRQEnable = false; } } static void irq_work_routine(struct work_struct *work) { struct tas2770_priv *pTAS2770 = container_of(work, struct tas2770_priv, irq_work.work); #ifdef CONFIG_TAS2770_CODEC mutex_lock(&pTAS2770->codec_lock); #endif if (pTAS2770->mbRuntimeSuspend) { dev_info(pTAS2770->dev, "%s, Runtime Suspended\n", __func__); goto end; } if (!pTAS2770->mbPowerUp) { dev_info(pTAS2770->dev, "%s, device not powered\n", __func__); goto end; } end: #ifdef CONFIG_TAS2770_CODEC mutex_unlock(&pTAS2770->codec_lock); #endif } static enum hrtimer_restart timer_func(struct hrtimer *timer) { struct tas2770_priv *pTAS2770 = container_of(timer, struct tas2770_priv, mtimer); if (pTAS2770->mbPowerUp) { if (!delayed_work_pending(&pTAS2770->irq_work)) schedule_delayed_work(&pTAS2770->irq_work, msecs_to_jiffies(20)); } return HRTIMER_NORESTART; } static irqreturn_t tas2770_irq_handler(int irq, void *dev_id) { struct tas2770_priv *pTAS2770 = (struct tas2770_priv *)dev_id; tas2770_enableIRQ(pTAS2770, false); /* get IRQ status after 100 ms */ if (!delayed_work_pending(&pTAS2770->irq_work)) schedule_delayed_work(&pTAS2770->irq_work, msecs_to_jiffies(100)); return IRQ_HANDLED; } static int tas2770_runtime_suspend(struct tas2770_priv *pTAS2770) { dev_dbg(pTAS2770->dev, "%s\n", __func__); pTAS2770->mbRuntimeSuspend = true; if (hrtimer_active(&pTAS2770->mtimer)) { dev_dbg(pTAS2770->dev, "cancel die temp timer\n"); hrtimer_cancel(&pTAS2770->mtimer); } if (delayed_work_pending(&pTAS2770->irq_work)) { dev_dbg(pTAS2770->dev, "cancel IRQ work\n"); cancel_delayed_work_sync(&pTAS2770->irq_work); } return 0; } #define CHECK_PERIOD 5000 /* 5 second */ static int tas2770_runtime_resume(struct tas2770_priv *pTAS2770) { dev_dbg(pTAS2770->dev, "%s\n", __func__); if (pTAS2770->mbPowerUp) { if (!hrtimer_active(&pTAS2770->mtimer)) { hrtimer_start(&pTAS2770->mtimer, ns_to_ktime((u64)CHECK_PERIOD * NSEC_PER_MSEC), HRTIMER_MODE_REL); } } pTAS2770->mbRuntimeSuspend = false; return 0; } static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *pTAS2770) { struct device_node *np = dev->of_node; int rc = 0, ret = 0; rc = of_property_read_u32(np, "ti,asi-format", &pTAS2770->mnASIFormat); if (rc) { dev_err(pTAS2770->dev, "Looking up %s property in node %s failed %d\n", "ti,asi-format", np->full_name, rc); } else { dev_dbg(pTAS2770->dev, "ti,asi-format=%d", pTAS2770->mnASIFormat); } pTAS2770->mnResetGPIO = of_get_named_gpio(np, "ti,reset-gpio", 0); if (!gpio_is_valid(pTAS2770->mnResetGPIO)) { dev_err(pTAS2770->dev, "Looking up %s property in node %s failed %d\n", "ti,reset-gpio", np->full_name, pTAS2770->mnResetGPIO); } else { dev_dbg(pTAS2770->dev, "ti,reset-gpio=%d", pTAS2770->mnResetGPIO); } pTAS2770->mnIRQGPIO = of_get_named_gpio(np, "ti,irq-gpio", 0); if (!gpio_is_valid(pTAS2770->mnIRQGPIO)) { dev_err(pTAS2770->dev, "Looking up %s property in node %s failed %d\n", "ti,irq-gpio", np->full_name, pTAS2770->mnIRQGPIO); } else { dev_dbg(pTAS2770->dev, "ti,irq-gpio=%d", pTAS2770->mnIRQGPIO); } return ret; } static int tas2770_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct tas2770_priv *pTAS2770; int nResult; dev_info(&client->dev, "%s enter\n", __func__); pTAS2770 = devm_kzalloc(&client->dev, sizeof(struct tas2770_priv), GFP_KERNEL); if (pTAS2770 == NULL) { nResult = -ENOMEM; goto end; } pTAS2770->dev = &client->dev; i2c_set_clientdata(client, pTAS2770); dev_set_drvdata(&client->dev, pTAS2770); pTAS2770->regmap = devm_regmap_init_i2c(client, &tas2770_i2c_regmap); if (IS_ERR(pTAS2770->regmap)) { nResult = PTR_ERR(pTAS2770->regmap); dev_err(&client->dev, "Failed to allocate register map: %d\n", nResult); goto end; } if (client->dev.of_node) tas2770_parse_dt(&client->dev, pTAS2770); if (gpio_is_valid(pTAS2770->mnResetGPIO)) { nResult = gpio_request(pTAS2770->mnResetGPIO, "TAS2770_RESET"); if (nResult) { dev_err(pTAS2770->dev, "%s: Failed to request gpio %d\n", __func__, pTAS2770->mnResetGPIO); nResult = -EINVAL; goto free_gpio; } } if (gpio_is_valid(pTAS2770->mnIRQGPIO)) { nResult = gpio_request(pTAS2770->mnIRQGPIO, "TAS2770-IRQ"); if (nResult < 0) { dev_err(pTAS2770->dev, "%s: GPIO %d request error\n", __func__, pTAS2770->mnIRQGPIO); goto free_gpio; } gpio_direction_input(pTAS2770->mnIRQGPIO); pTAS2770->mnIRQ = gpio_to_irq(pTAS2770->mnIRQGPIO); dev_dbg(pTAS2770->dev, "irq = %d\n", pTAS2770->mnIRQ); nResult = request_threaded_irq(pTAS2770->mnIRQ, tas2770_irq_handler, NULL, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, client->name, pTAS2770); if (nResult < 0) { dev_err(pTAS2770->dev, "request_irq failed, %d\n", nResult); goto free_gpio; } disable_irq_nosync(pTAS2770->mnIRQ); INIT_DELAYED_WORK(&pTAS2770->irq_work, irq_work_routine); } pTAS2770->hw_reset = tas2770_hw_reset; pTAS2770->enableIRQ = tas2770_enableIRQ; pTAS2770->runtime_suspend = tas2770_runtime_suspend; pTAS2770->runtime_resume = tas2770_runtime_resume; mutex_init(&pTAS2770->dev_lock); if (nResult < 0) goto destroy_mutex; #ifdef CONFIG_TAS2770_CODEC mutex_init(&pTAS2770->codec_lock); tas2770_register_codec(pTAS2770); #endif hrtimer_init(&pTAS2770->mtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); pTAS2770->mtimer.function = timer_func; destroy_mutex: if (nResult < 0) mutex_destroy(&pTAS2770->dev_lock); free_gpio: if (nResult < 0) { if (gpio_is_valid(pTAS2770->mnResetGPIO)) gpio_free(pTAS2770->mnResetGPIO); if (gpio_is_valid(pTAS2770->mnIRQGPIO)) gpio_free(pTAS2770->mnIRQGPIO); } end: return nResult; } static int tas2770_i2c_remove(struct i2c_client *client) { struct tas2770_priv *pTAS2770 = i2c_get_clientdata(client); dev_info(pTAS2770->dev, "%s\n", __func__); #ifdef CONFIG_TAS2770_CODEC tas2770_deregister_codec(pTAS2770); mutex_destroy(&pTAS2770->codec_lock); #endif if (gpio_is_valid(pTAS2770->mnResetGPIO)) gpio_free(pTAS2770->mnResetGPIO); if (gpio_is_valid(pTAS2770->mnIRQGPIO)) gpio_free(pTAS2770->mnIRQGPIO); return 0; } static const struct i2c_device_id tas2770_i2c_id[] = { { "tas2770", 0}, { } }; MODULE_DEVICE_TABLE(i2c, tas2770_i2c_id); #if defined(CONFIG_OF) static const struct of_device_id tas2770_of_match[] = { { .compatible = "ti,tas2770" }, {}, }; MODULE_DEVICE_TABLE(of, tas2770_of_match); #endif static struct i2c_driver tas2770_i2c_driver = { .driver = { .name = "tas2770", .owner = THIS_MODULE, #if defined(CONFIG_OF) .of_match_table = of_match_ptr(tas2770_of_match), #endif }, .probe = tas2770_i2c_probe, .remove = tas2770_i2c_remove, .id_table = tas2770_i2c_id, }; module_i2c_driver(tas2770_i2c_driver); MODULE_AUTHOR("Texas Instruments Inc."); MODULE_DESCRIPTION("TAS2770 I2C Smart Amplifier driver"); MODULE_LICENSE("GPL v2"); #endif