blob: 402d97320e2a5e09e98818143b26b0fdab7b61d4 [file] [log] [blame]
/*
* 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 <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/firmware.h>
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <sound/soc.h>
#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);
}
of_property_read_u32(np, "ti,left-slot", &pTAS2770->mnLeftSlot);
if (rc) {
dev_err(pTAS2770->dev, "Looking up %s property in node %s failed %d\n",
"ti,left-slot", np->full_name, rc);
} else {
dev_dbg(pTAS2770->dev, "ti,left-slot=%d",
pTAS2770->mnLeftSlot);
}
of_property_read_u32(np, "ti,right-slot", &pTAS2770->mnRightSlot);
if (rc) {
dev_err(pTAS2770->dev, "Looking up %s property in node %s failed %d\n",
"ti,right-slot", np->full_name, rc);
} else {
dev_dbg(pTAS2770->dev, "ti,right-slot=%d",
pTAS2770->mnRightSlot);
}
of_property_read_u32(np, "ti,imon-slot-no", &pTAS2770->mnImon_slot_no);
if (rc) {
dev_err(pTAS2770->dev, "Looking up %s property in node %s failed %d\n",
"ti,imon-slot-no", np->full_name, rc);
} else {
dev_dbg(pTAS2770->dev, "ti,imon-slot-no=%d",
pTAS2770->mnImon_slot_no);
}
of_property_read_u32(np, "ti,vmon-slot-no", &pTAS2770->mnVmon_slot_no);
if (rc) {
dev_err(pTAS2770->dev, "Looking up %s property in node %s failed %d\n",
"ti,vmon-slot-no", np->full_name, rc);
} else {
dev_dbg(pTAS2770->dev, "ti,vmon-slot-no=%d",
pTAS2770->mnVmon_slot_no);
}
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;
pTAS2770->mnCh_size = 0;
pTAS2770->mnSlot_width = 0;
tas2770_hw_reset(pTAS2770);
regmap_write(pTAS2770->regmap, TAS2770_SoftwareReset,
TAS2770_SoftwareReset_SoftwareReset_Reset);
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