1469 lines
41 KiB
C
Executable File
1469 lines
41 KiB
C
Executable File
/*
|
|
* ALSA SoC Texas Instruments TAS2562 High Performance 4W Smart Amplifier
|
|
*
|
|
* 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_TAS2562_REGMAP
|
|
|
|
#define DEBUG 5
|
|
#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 <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/pm.h>
|
|
|
|
#include "tas2562.h"
|
|
#include "tas2562-codec.h"
|
|
#include "tas2562-misc.h"
|
|
#ifdef CONFIG_TAS25XX_ALGO
|
|
#include <sound/smart_amp.h>
|
|
#endif /*CONFIG_TAS25XX_ALGO*/
|
|
|
|
static char p_icn_threshold[] = {0x00, 0x01, 0x2f, 0x2c};
|
|
static char p_icn_hysteresis[] = {0x00, 0x01, 0x5d, 0xc0};
|
|
|
|
static int tas2562_regmap_write(struct tas2562_priv *p_tas2562,
|
|
unsigned int reg, unsigned int value)
|
|
{
|
|
int nResult = 0;
|
|
int retry_count = TAS2562_I2C_RETRY_COUNT;
|
|
|
|
if(p_tas2562->i2c_suspend)
|
|
return ERROR_I2C_SUSPEND;
|
|
|
|
while(retry_count--)
|
|
{
|
|
nResult = regmap_write(p_tas2562->regmap, reg,
|
|
value);
|
|
if (nResult >= 0)
|
|
break;
|
|
msleep(20);
|
|
}
|
|
if(retry_count == -1)
|
|
return ERROR_I2C_FAILED;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int tas2562_regmap_bulk_write(struct tas2562_priv *p_tas2562,
|
|
unsigned int reg, unsigned char *pData, unsigned int nLength)
|
|
{
|
|
int nResult = 0;
|
|
int retry_count = TAS2562_I2C_RETRY_COUNT;
|
|
|
|
if(p_tas2562->i2c_suspend)
|
|
return ERROR_I2C_SUSPEND;
|
|
|
|
while(retry_count --)
|
|
{
|
|
nResult = regmap_bulk_write(p_tas2562->regmap, reg,
|
|
pData, nLength);
|
|
if (nResult >= 0)
|
|
break;
|
|
msleep(20);
|
|
}
|
|
if(retry_count == -1)
|
|
return ERROR_I2C_FAILED;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int tas2562_regmap_read(struct tas2562_priv *p_tas2562,
|
|
unsigned int reg, unsigned int *value)
|
|
{
|
|
int nResult = 0;
|
|
int retry_count = TAS2562_I2C_RETRY_COUNT;
|
|
|
|
if(p_tas2562->i2c_suspend)
|
|
return ERROR_I2C_SUSPEND;
|
|
|
|
while(retry_count --)
|
|
{
|
|
nResult = regmap_read(p_tas2562->regmap, reg,
|
|
value);
|
|
if (nResult >= 0)
|
|
break;
|
|
msleep(20);
|
|
}
|
|
if(retry_count == -1)
|
|
return ERROR_I2C_FAILED;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int tas2562_regmap_bulk_read(struct tas2562_priv *p_tas2562,
|
|
unsigned int reg, unsigned char *pData, unsigned int nLength)
|
|
{
|
|
int nResult = 0;
|
|
int retry_count = TAS2562_I2C_RETRY_COUNT;
|
|
|
|
if(p_tas2562->i2c_suspend)
|
|
return ERROR_I2C_SUSPEND;
|
|
|
|
while(retry_count --)
|
|
{
|
|
nResult = regmap_bulk_read(p_tas2562->regmap, reg,
|
|
pData, nLength);
|
|
if (nResult >= 0)
|
|
break;
|
|
msleep(20);
|
|
}
|
|
if(retry_count == -1)
|
|
return ERROR_I2C_FAILED;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int tas2562_regmap_update_bits(struct tas2562_priv *p_tas2562,
|
|
unsigned int reg, unsigned int mask, unsigned int value)
|
|
{
|
|
int nResult = 0;
|
|
int retry_count = TAS2562_I2C_RETRY_COUNT;
|
|
|
|
if(p_tas2562->i2c_suspend)
|
|
return ERROR_I2C_SUSPEND;
|
|
|
|
while(retry_count--)
|
|
{
|
|
nResult = regmap_update_bits(p_tas2562->regmap, reg,
|
|
mask, value);
|
|
if (nResult >= 0)
|
|
break;
|
|
msleep(20);
|
|
}
|
|
if(retry_count == -1)
|
|
return ERROR_I2C_FAILED;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int tas2562_change_book_page(struct tas2562_priv *p_tas2562,
|
|
enum channel chn,
|
|
int book, int page)
|
|
{
|
|
int n_result = 0;
|
|
|
|
|
|
if ((chn&channel_left) || (p_tas2562->mn_channels == 1)) {
|
|
p_tas2562->client->addr = p_tas2562->mn_l_addr;
|
|
if (p_tas2562->mn_l_current_book != book) {
|
|
n_result = tas2562_regmap_write(p_tas2562,
|
|
TAS2562_BOOKCTL_PAGE, 0);
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
goto end;
|
|
}
|
|
p_tas2562->mn_l_current_page = 0;
|
|
n_result = tas2562_regmap_write(p_tas2562,
|
|
TAS2562_BOOKCTL_REG, book);
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
goto end;
|
|
}
|
|
p_tas2562->mn_l_current_book = book;
|
|
}
|
|
|
|
if (p_tas2562->mn_l_current_page != page) {
|
|
n_result = tas2562_regmap_write(p_tas2562,
|
|
TAS2562_BOOKCTL_PAGE, page);
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
goto end;
|
|
}
|
|
p_tas2562->mn_l_current_page = page;
|
|
}
|
|
}
|
|
|
|
if ((chn&channel_right) && (p_tas2562->mn_channels == 2)) {
|
|
p_tas2562->client->addr = p_tas2562->mn_r_addr;
|
|
if (p_tas2562->mn_r_current_book != book) {
|
|
n_result = tas2562_regmap_write(p_tas2562,
|
|
TAS2562_BOOKCTL_PAGE, 0);
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
goto end;
|
|
}
|
|
p_tas2562->mn_r_current_page = 0;
|
|
n_result = tas2562_regmap_write(p_tas2562,
|
|
TAS2562_BOOKCTL_REG, book);
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
goto end;
|
|
}
|
|
p_tas2562->mn_r_current_book = book;
|
|
}
|
|
|
|
if (p_tas2562->mn_r_current_page != page) {
|
|
n_result = tas2562_regmap_write(p_tas2562,
|
|
TAS2562_BOOKCTL_PAGE, page);
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
goto end;
|
|
}
|
|
p_tas2562->mn_r_current_page = page;
|
|
}
|
|
}
|
|
|
|
end:
|
|
return n_result;
|
|
}
|
|
|
|
static int tas2562_dev_read(struct tas2562_priv *p_tas2562,
|
|
enum channel chn,
|
|
unsigned int reg, unsigned int *pValue)
|
|
{
|
|
int n_result = 0;
|
|
|
|
mutex_lock(&p_tas2562->dev_lock);
|
|
|
|
n_result = tas2562_change_book_page(p_tas2562, chn,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg));
|
|
if (n_result < 0)
|
|
goto end;
|
|
|
|
if ((chn == channel_left) || (p_tas2562->mn_channels == 1))
|
|
p_tas2562->client->addr = p_tas2562->mn_l_addr;
|
|
else if (chn == channel_right)
|
|
p_tas2562->client->addr = p_tas2562->mn_r_addr;
|
|
else
|
|
dev_err(p_tas2562->dev, "%s, wrong channel number\n", __func__);
|
|
|
|
n_result = tas2562_regmap_read(p_tas2562,
|
|
TAS2562_PAGE_REG(reg), pValue);
|
|
if (n_result < 0)
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
else
|
|
dev_dbg(p_tas2562->dev,
|
|
"%s: chn:%x:BOOK:PAGE:REG %u:%u:%u,0x%x\n", __func__,
|
|
p_tas2562->client->addr, TAS2562_BOOK_ID(reg),
|
|
TAS2562_PAGE_ID(reg),
|
|
TAS2562_PAGE_REG(reg), *pValue);
|
|
|
|
end:
|
|
mutex_unlock(&p_tas2562->dev_lock);
|
|
return n_result;
|
|
}
|
|
|
|
static int tas2562_dev_write(struct tas2562_priv *p_tas2562, enum channel chn,
|
|
unsigned int reg, unsigned int value)
|
|
{
|
|
int n_result = 0;
|
|
|
|
mutex_lock(&p_tas2562->dev_lock);
|
|
|
|
n_result = tas2562_change_book_page(p_tas2562, chn,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg));
|
|
if (n_result < 0)
|
|
goto end;
|
|
|
|
if ((chn&channel_left) || (p_tas2562->mn_channels == 1)) {
|
|
p_tas2562->client->addr = p_tas2562->mn_l_addr;
|
|
|
|
n_result = tas2562_regmap_write(p_tas2562,
|
|
TAS2562_PAGE_REG(reg), value);
|
|
if (n_result < 0)
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
else
|
|
dev_dbg(p_tas2562->dev,
|
|
"%s: chn%x:BOOK:PAGE:REG %u:%u:%u, VAL: 0x%02x\n",
|
|
__func__, p_tas2562->client->addr,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg),
|
|
TAS2562_PAGE_REG(reg), value);
|
|
}
|
|
|
|
if ((chn&channel_right) && (p_tas2562->mn_channels == 2)) {
|
|
p_tas2562->client->addr = p_tas2562->mn_r_addr;
|
|
|
|
n_result = tas2562_regmap_write(p_tas2562,
|
|
TAS2562_PAGE_REG(reg),
|
|
value);
|
|
if (n_result < 0)
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
else
|
|
dev_dbg(p_tas2562->dev, "%s: chn%x:BOOK:PAGE:REG %u:%u:%u, VAL: 0x%02x\n",
|
|
__func__, p_tas2562->client->addr,
|
|
TAS2562_BOOK_ID(reg),
|
|
TAS2562_PAGE_ID(reg),
|
|
TAS2562_PAGE_REG(reg), value);
|
|
}
|
|
|
|
end:
|
|
mutex_unlock(&p_tas2562->dev_lock);
|
|
return n_result;
|
|
}
|
|
|
|
static int tas2562_dev_bulk_write(struct tas2562_priv *p_tas2562,
|
|
enum channel chn,
|
|
unsigned int reg, unsigned char *p_data, unsigned int n_length)
|
|
{
|
|
int n_result = 0;
|
|
|
|
mutex_lock(&p_tas2562->dev_lock);
|
|
|
|
n_result = tas2562_change_book_page(p_tas2562, chn,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg));
|
|
if (n_result < 0)
|
|
goto end;
|
|
|
|
if ((chn&channel_left) || (p_tas2562->mn_channels == 1)) {
|
|
p_tas2562->client->addr = p_tas2562->mn_l_addr;
|
|
n_result = tas2562_regmap_bulk_write(p_tas2562,
|
|
TAS2562_PAGE_REG(reg), p_data, n_length);
|
|
if (n_result < 0)
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
else
|
|
dev_dbg(p_tas2562->dev, "%s: chn%x:BOOK:PAGE:REG %u:%u:%u, len: 0x%02x\n",
|
|
__func__, p_tas2562->client->addr,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg),
|
|
TAS2562_PAGE_REG(reg), n_length);
|
|
}
|
|
|
|
if ((chn&channel_right) && (p_tas2562->mn_channels == 2)) {
|
|
p_tas2562->client->addr = p_tas2562->mn_r_addr;
|
|
n_result = tas2562_regmap_bulk_write(p_tas2562,
|
|
TAS2562_PAGE_REG(reg), p_data, n_length);
|
|
if (n_result < 0)
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
else
|
|
dev_dbg(p_tas2562->dev, "%s: %x:BOOK:PAGE:REG %u:%u:%u, len: 0x%02x\n",
|
|
__func__, p_tas2562->client->addr,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg),
|
|
TAS2562_PAGE_REG(reg), n_length);
|
|
}
|
|
|
|
end:
|
|
mutex_unlock(&p_tas2562->dev_lock);
|
|
return n_result;
|
|
}
|
|
|
|
static int tas2562_dev_bulk_read(struct tas2562_priv *p_tas2562,
|
|
enum channel chn,
|
|
unsigned int reg, unsigned char *p_data, unsigned int n_length)
|
|
{
|
|
int n_result = 0;
|
|
|
|
mutex_lock(&p_tas2562->dev_lock);
|
|
|
|
if ((chn == channel_left) || (p_tas2562->mn_channels == 1))
|
|
p_tas2562->client->addr = p_tas2562->mn_l_addr;
|
|
else if (chn == channel_right)
|
|
p_tas2562->client->addr = p_tas2562->mn_r_addr;
|
|
else
|
|
dev_err(p_tas2562->dev, "%s, wrong channel number\n", __func__);
|
|
|
|
n_result = tas2562_change_book_page(p_tas2562, chn,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg));
|
|
if (n_result < 0)
|
|
goto end;
|
|
|
|
n_result = tas2562_regmap_bulk_read(p_tas2562,
|
|
TAS2562_PAGE_REG(reg), p_data, n_length);
|
|
if (n_result < 0)
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
else
|
|
dev_dbg(p_tas2562->dev, "%s: chn%x:BOOK:PAGE:REG %u:%u:%u, len: 0x%02x\n",
|
|
__func__, p_tas2562->client->addr,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg),
|
|
TAS2562_PAGE_REG(reg), n_length);
|
|
end:
|
|
mutex_unlock(&p_tas2562->dev_lock);
|
|
return n_result;
|
|
}
|
|
|
|
static int tas2562_dev_update_bits(struct tas2562_priv *p_tas2562,
|
|
enum channel chn,
|
|
unsigned int reg, unsigned int mask, unsigned int value)
|
|
{
|
|
int n_result = 0;
|
|
|
|
mutex_lock(&p_tas2562->dev_lock);
|
|
n_result = tas2562_change_book_page(p_tas2562, chn,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg));
|
|
if (n_result < 0)
|
|
goto end;
|
|
|
|
if ((chn&channel_left) || (p_tas2562->mn_channels == 1)) {
|
|
p_tas2562->client->addr = p_tas2562->mn_l_addr;
|
|
n_result = tas2562_regmap_update_bits(p_tas2562,
|
|
TAS2562_PAGE_REG(reg), mask, value);
|
|
if (n_result < 0)
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
else
|
|
dev_dbg(p_tas2562->dev,
|
|
"%s: chn%x:BOOK:PAGE:REG %u:%u:%u, mask: 0x%x, val: 0x%x\n",
|
|
__func__, p_tas2562->client->addr,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg),
|
|
TAS2562_PAGE_REG(reg), mask, value);
|
|
}
|
|
|
|
if ((chn&channel_right) && (p_tas2562->mn_channels == 2)) {
|
|
p_tas2562->client->addr = p_tas2562->mn_r_addr;
|
|
n_result = tas2562_regmap_update_bits(p_tas2562,
|
|
TAS2562_PAGE_REG(reg), mask, value);
|
|
if (n_result < 0)
|
|
dev_err(p_tas2562->dev, "%s, ERROR, L=%d, E=%d\n",
|
|
__func__, __LINE__, n_result);
|
|
else
|
|
dev_dbg(p_tas2562->dev,
|
|
"%s:chn%x:BOOK:PAGE:REG %u:%u:%u,mask: 0x%x, val: 0x%x\n",
|
|
__func__, p_tas2562->client->addr,
|
|
TAS2562_BOOK_ID(reg), TAS2562_PAGE_ID(reg),
|
|
TAS2562_PAGE_REG(reg), mask, value);
|
|
}
|
|
|
|
end:
|
|
mutex_unlock(&p_tas2562->dev_lock);
|
|
return n_result;
|
|
}
|
|
|
|
static bool tas2562_volatile(struct device *dev, unsigned int reg)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static bool tas2562_writeable(struct device *dev, unsigned int reg)
|
|
{
|
|
return true;
|
|
}
|
|
static const struct regmap_config tas2562_i2c_regmap = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.writeable_reg = tas2562_writeable,
|
|
.volatile_reg = tas2562_volatile,
|
|
.cache_type = REGCACHE_NONE,
|
|
.max_register = 1 * 128,
|
|
};
|
|
|
|
|
|
static void tas2562_hw_reset(struct tas2562_priv *p_tas2562)
|
|
{
|
|
if (gpio_is_valid(p_tas2562->mn_reset_gpio)) {
|
|
gpio_direction_output(p_tas2562->mn_reset_gpio, 0);
|
|
|
|
if(p_tas2562->mn_channels != 1) {
|
|
dev_dbg(p_tas2562->dev, "Reset gpio: not mono case, resetting second gpio");
|
|
if(gpio_is_valid(p_tas2562->mn_reset_gpio2))
|
|
gpio_direction_output(p_tas2562->mn_reset_gpio2, 0);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "Reset gpio: mono case, not resetting second gpio");
|
|
}
|
|
msleep(20);
|
|
|
|
gpio_direction_output(p_tas2562->mn_reset_gpio, 1);
|
|
|
|
if(p_tas2562->mn_channels != 1) {
|
|
dev_dbg(p_tas2562->dev, "Reset gpio: not mono case, resetting second gpio");
|
|
if(gpio_is_valid(p_tas2562->mn_reset_gpio2))
|
|
gpio_direction_output(p_tas2562->mn_reset_gpio2, 1);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "Reset gpio: mono case, not resetting second gpio");
|
|
}
|
|
|
|
msleep(20);
|
|
}
|
|
dev_info(p_tas2562->dev, "reset gpio up !!\n");
|
|
|
|
p_tas2562->mn_l_current_book = -1;
|
|
p_tas2562->mn_l_current_page = -1;
|
|
p_tas2562->mn_r_current_book = -1;
|
|
p_tas2562->mn_r_current_page = -1;
|
|
}
|
|
|
|
void tas2562_enable_irq(struct tas2562_priv *p_tas2562, bool enable)
|
|
{
|
|
static int irq1_enabled = 0;
|
|
static int irq2_enabled = 0;
|
|
struct irq_desc *desc = NULL;
|
|
|
|
if (enable) {
|
|
if (p_tas2562->mb_irq_eable)
|
|
return;
|
|
|
|
if (gpio_is_valid(p_tas2562->mn_irq_gpio) && irq1_enabled == 0) {
|
|
desc = irq_to_desc(p_tas2562->mn_irq);
|
|
if (desc && desc->depth > 0) {
|
|
enable_irq(p_tas2562->mn_irq);
|
|
} else {
|
|
dev_info (p_tas2562->dev, "### irq already enabled");
|
|
}
|
|
irq1_enabled = 1;
|
|
}
|
|
if (p_tas2562->mn_channels == 2) {
|
|
if (gpio_is_valid(p_tas2562->mn_irq_gpio2) && irq2_enabled == 0) {
|
|
enable_irq(p_tas2562->mn_irq2);
|
|
irq2_enabled = 1;
|
|
}
|
|
}
|
|
p_tas2562->mb_irq_eable = true;
|
|
} else {
|
|
if (gpio_is_valid(p_tas2562->mn_irq_gpio) && irq1_enabled == 1) {
|
|
disable_irq_nosync(p_tas2562->mn_irq);
|
|
irq1_enabled = 0;
|
|
}
|
|
if (p_tas2562->mn_channels == 2) {
|
|
if (gpio_is_valid(p_tas2562->mn_irq_gpio2) && irq2_enabled == 1) {
|
|
disable_irq_nosync(p_tas2562->mn_irq2);
|
|
irq2_enabled = 0;
|
|
}
|
|
}
|
|
p_tas2562->mb_irq_eable = false;
|
|
}
|
|
}
|
|
|
|
static void irq_work_routine(struct work_struct *work)
|
|
{
|
|
struct tas2562_priv *p_tas2562 =
|
|
container_of(work, struct tas2562_priv, irq_work.work);
|
|
unsigned int nDevInt1Status = 0, nDevInt2Status = 0,
|
|
nDevInt3Status = 0, nDevInt4Status = 0;
|
|
int n_counter = 2;
|
|
int n_result = 0;
|
|
int irqreg;
|
|
enum channel chn;
|
|
|
|
dev_info(p_tas2562->dev, "%s\n", __func__);
|
|
#ifdef CONFIG_TAS2562_CODEC
|
|
mutex_lock(&p_tas2562->codec_lock);
|
|
#endif
|
|
tas2562_enable_irq(p_tas2562, false);
|
|
|
|
if (p_tas2562->mb_runtime_suspend) {
|
|
dev_info(p_tas2562->dev, "%s, Runtime Suspended\n", __func__);
|
|
goto end;
|
|
}
|
|
|
|
if (p_tas2562->mn_power_state == TAS2562_POWER_SHUTDOWN) {
|
|
dev_info(p_tas2562->dev, "%s, device not powered\n", __func__);
|
|
goto end;
|
|
}
|
|
|
|
n_result = p_tas2562->write(p_tas2562, channel_both,
|
|
TAS2562_INTERRUPTMASKREG0,
|
|
TAS2562_INTERRUPTMASKREG0_DISABLE);
|
|
n_result = p_tas2562->write(p_tas2562, channel_both,
|
|
TAS2562_INTERRUPTMASKREG1,
|
|
TAS2562_INTERRUPTMASKREG1_DISABLE);
|
|
|
|
if (n_result < 0)
|
|
goto reload;
|
|
|
|
if ((p_tas2562->spk_l_control == 1)
|
|
&& (p_tas2562->spk_r_control == 1)
|
|
&& (p_tas2562->mn_channels == 2))
|
|
chn = channel_both;
|
|
else if (p_tas2562->spk_l_control == 1)
|
|
chn = channel_left;
|
|
else if ((p_tas2562->spk_r_control == 1)
|
|
&& (p_tas2562->mn_channels == 2))
|
|
chn = channel_right;
|
|
else
|
|
chn = channel_left;
|
|
|
|
if (chn & channel_left)
|
|
n_result = p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_LATCHEDINTERRUPTREG0, &nDevInt1Status);
|
|
if (n_result >= 0)
|
|
n_result = p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_LATCHEDINTERRUPTREG1, &nDevInt2Status);
|
|
else
|
|
goto reload;
|
|
|
|
if (chn & channel_right)
|
|
n_result = p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_LATCHEDINTERRUPTREG0, &nDevInt3Status);
|
|
if (n_result >= 0)
|
|
n_result = p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_LATCHEDINTERRUPTREG1, &nDevInt4Status);
|
|
else
|
|
goto reload;
|
|
|
|
dev_dbg(p_tas2562->dev, "IRQ status : 0x%x, 0x%x, 0x%x, 0x%x\n",
|
|
nDevInt3Status, nDevInt4Status,
|
|
nDevInt3Status, nDevInt4Status);
|
|
|
|
if (((nDevInt1Status & 0x7) != 0)
|
|
|| ((nDevInt2Status & 0x0f) != 0) ||
|
|
((nDevInt3Status & 0x7) != 0)
|
|
|| ((nDevInt4Status & 0x0f) != 0)) {
|
|
/* in case of INT_CLK, INT_OC, INT_OT,
|
|
* INT_OVLT, INT_UVLT, INT_BO
|
|
*/
|
|
|
|
if ((nDevInt1Status &
|
|
TAS2562_LATCHEDINTERRUPTREG0_TDMCLOCKERRORSTICKY_INTERRUPT) ||
|
|
(nDevInt3Status &
|
|
TAS2562_LATCHEDINTERRUPTREG0_TDMCLOCKERRORSTICKY_INTERRUPT)) {
|
|
p_tas2562->mn_err_code |= ERROR_CLOCK;
|
|
dev_err(p_tas2562->dev, "TDM clock error!\n");
|
|
} else
|
|
p_tas2562->mn_err_code &= ~ERROR_OVER_CURRENT;
|
|
|
|
if ((nDevInt1Status &
|
|
TAS2562_LATCHEDINTERRUPTREG0_OCEFLAGSTICKY_INTERRUPT) ||
|
|
(nDevInt3Status &
|
|
TAS2562_LATCHEDINTERRUPTREG0_OCEFLAGSTICKY_INTERRUPT)) {
|
|
p_tas2562->mn_err_code |= ERROR_OVER_CURRENT;
|
|
dev_err(p_tas2562->dev, "SPK over current!\n");
|
|
} else
|
|
p_tas2562->mn_err_code &= ~ERROR_OVER_CURRENT;
|
|
|
|
if ((nDevInt1Status &
|
|
TAS2562_LATCHEDINTERRUPTREG0_OTEFLAGSTICKY_INTERRUPT) ||
|
|
(nDevInt3Status &
|
|
TAS2562_LATCHEDINTERRUPTREG0_OTEFLAGSTICKY_INTERRUPT)) {
|
|
p_tas2562->mn_err_code |= ERROR_DIE_OVERTEMP;
|
|
dev_err(p_tas2562->dev, "die over temperature!\n");
|
|
} else
|
|
p_tas2562->mn_err_code &= ~ERROR_DIE_OVERTEMP;
|
|
|
|
if ((nDevInt2Status &
|
|
TAS2562_LATCHEDINTERRUPTREG1_VBATOVLOSTICKY_INTERRUPT) ||
|
|
(nDevInt4Status &
|
|
TAS2562_LATCHEDINTERRUPTREG1_VBATOVLOSTICKY_INTERRUPT)) {
|
|
p_tas2562->mn_err_code |= ERROR_OVER_VOLTAGE;
|
|
dev_err(p_tas2562->dev, "SPK over voltage!\n");
|
|
} else
|
|
p_tas2562->mn_err_code &= ~ERROR_UNDER_VOLTAGE;
|
|
|
|
if ((nDevInt2Status &
|
|
TAS2562_LATCHEDINTERRUPTREG1_VBATUVLOSTICKY_INTERRUPT) ||
|
|
(nDevInt4Status &
|
|
TAS2562_LATCHEDINTERRUPTREG1_VBATUVLOSTICKY_INTERRUPT)) {
|
|
p_tas2562->mn_err_code |= ERROR_UNDER_VOLTAGE;
|
|
dev_err(p_tas2562->dev, "SPK under voltage!\n");
|
|
} else
|
|
p_tas2562->mn_err_code &= ~ERROR_UNDER_VOLTAGE;
|
|
|
|
if ((nDevInt2Status &
|
|
TAS2562_LATCHEDINTERRUPTREG1_BROWNOUTFLAGSTICKY_INTERRUPT) ||
|
|
(nDevInt4Status &
|
|
TAS2562_LATCHEDINTERRUPTREG1_BROWNOUTFLAGSTICKY_INTERRUPT)) {
|
|
p_tas2562->mn_err_code |= ERROR_BROWNOUT;
|
|
dev_err(p_tas2562->dev, "brownout!\n");
|
|
} else
|
|
p_tas2562->mn_err_code &= ~ERROR_BROWNOUT;
|
|
|
|
goto reload;
|
|
} else {
|
|
n_counter = 2;
|
|
|
|
while (n_counter > 0) {
|
|
if (chn & channel_left)
|
|
n_result = p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_POWERCONTROL, &nDevInt1Status);
|
|
if (n_result < 0)
|
|
goto reload;
|
|
if (chn & channel_right)
|
|
n_result = p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_POWERCONTROL, &nDevInt3Status);
|
|
if (n_result < 0)
|
|
goto reload;
|
|
|
|
if ((nDevInt1Status
|
|
& TAS2562_POWERCONTROL_OPERATIONALMODE10_MASK)
|
|
!= TAS2562_POWERCONTROL_OPERATIONALMODE10_SHUTDOWN) {
|
|
/* If only left should be power on */
|
|
if (chn == channel_left)
|
|
break;
|
|
/* If both should be power on */
|
|
if ((nDevInt3Status
|
|
& TAS2562_POWERCONTROL_OPERATIONALMODE10_MASK)
|
|
!=
|
|
TAS2562_POWERCONTROL_OPERATIONALMODE10_SHUTDOWN)
|
|
break;
|
|
}
|
|
/*If only right should be power on */
|
|
else if (chn == channel_right) {
|
|
if ((nDevInt3Status
|
|
& TAS2562_POWERCONTROL_OPERATIONALMODE10_MASK)
|
|
!=
|
|
TAS2562_POWERCONTROL_OPERATIONALMODE10_SHUTDOWN)
|
|
break;
|
|
}
|
|
|
|
p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_LATCHEDINTERRUPTREG0, &irqreg);
|
|
dev_info(p_tas2562->dev, "IRQ reg is: %s %d, %d\n",
|
|
__func__, irqreg, __LINE__);
|
|
p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_LATCHEDINTERRUPTREG0, &irqreg);
|
|
dev_info(p_tas2562->dev, "IRQ reg is: %s %d, %d\n",
|
|
__func__, irqreg, __LINE__);
|
|
if (p_tas2562->dac_mute == 0) {
|
|
n_result = p_tas2562->update_bits(p_tas2562,
|
|
chn, TAS2562_POWERCONTROL,
|
|
TAS2562_POWERCONTROL_OPERATIONALMODE10_MASK,
|
|
TAS2562_POWERCONTROL_OPERATIONALMODE10_ACTIVE);
|
|
if (n_result < 0)
|
|
goto reload;
|
|
} else {
|
|
n_result = p_tas2562->update_bits(p_tas2562,
|
|
chn, TAS2562_POWERCONTROL,
|
|
TAS2562_POWERCONTROL_OPERATIONALMODE10_MASK,
|
|
TAS2562_POWERCONTROL_OPERATIONALMODE10_MUTE);
|
|
if (n_result < 0)
|
|
goto reload;
|
|
}
|
|
|
|
dev_info(p_tas2562->dev, "set ICN to -80dB\n");
|
|
n_result = p_tas2562->bulk_write(p_tas2562, chn,
|
|
TAS2562_ICN_THRESHOLD_REG,
|
|
p_icn_threshold,
|
|
sizeof(p_icn_threshold));
|
|
n_result = p_tas2562->bulk_write(p_tas2562, chn,
|
|
TAS2562_ICN_HYSTERESIS_REG,
|
|
p_icn_hysteresis,
|
|
sizeof(p_icn_hysteresis));
|
|
|
|
p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_LATCHEDINTERRUPTREG0, &irqreg);
|
|
dev_info(p_tas2562->dev, "IRQ reg is: %s, %d, %d\n",
|
|
__func__, irqreg, __LINE__);
|
|
p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_LATCHEDINTERRUPTREG0, &irqreg);
|
|
dev_info(p_tas2562->dev, "IRQ reg is: %s %d, %d\n",
|
|
__func__, irqreg, __LINE__);
|
|
|
|
n_counter--;
|
|
if (n_counter > 0) {
|
|
/* in case check pow status just after power on TAS2562 */
|
|
dev_dbg(p_tas2562->dev, "PowSts B: 0x%x, check again after 10ms\n",
|
|
nDevInt1Status);
|
|
msleep(20);
|
|
}
|
|
}
|
|
|
|
if ((((nDevInt1Status
|
|
& TAS2562_POWERCONTROL_OPERATIONALMODE10_MASK)
|
|
== TAS2562_POWERCONTROL_OPERATIONALMODE10_SHUTDOWN)
|
|
&& (chn & channel_left))
|
|
|| (((nDevInt3Status
|
|
& TAS2562_POWERCONTROL_OPERATIONALMODE10_MASK)
|
|
== TAS2562_POWERCONTROL_OPERATIONALMODE10_SHUTDOWN)
|
|
&& (chn & channel_right))) {
|
|
dev_err(p_tas2562->dev, "%s, Critical ERROR REG[0x%x] = 0x%x\n",
|
|
__func__,
|
|
TAS2562_POWERCONTROL,
|
|
nDevInt1Status);
|
|
p_tas2562->mn_err_code |= ERROR_CLASSD_PWR;
|
|
goto reload;
|
|
}
|
|
p_tas2562->mn_err_code &= ~ERROR_CLASSD_PWR;
|
|
}
|
|
|
|
n_result = p_tas2562->write(p_tas2562, channel_left,
|
|
TAS2562_INTERRUPTMASKREG0, 0xf8);
|
|
if (n_result < 0)
|
|
goto reload;
|
|
|
|
n_result = p_tas2562->write(p_tas2562, channel_left,
|
|
TAS2562_INTERRUPTMASKREG1, 0xb1);
|
|
if (n_result < 0)
|
|
goto reload;
|
|
|
|
goto end;
|
|
|
|
reload:
|
|
/* hardware reset and reload */
|
|
tas2562_load_config(p_tas2562);
|
|
|
|
end:
|
|
tas2562_enable_irq(p_tas2562, true);
|
|
|
|
if (p_tas2562->mn_status_check) {
|
|
if (!hrtimer_active(&p_tas2562->mtimer)) {
|
|
u64 check_period = p_tas2562->mn_status_period * 1000;
|
|
dev_dbg(p_tas2562->dev, "%s, start timer\n", __func__);
|
|
hrtimer_start(&p_tas2562->mtimer,
|
|
ns_to_ktime((u64)check_period * NSEC_PER_MSEC), HRTIMER_MODE_REL);
|
|
}
|
|
}
|
|
#ifdef CONFIG_TAS2562_CODEC
|
|
mutex_unlock(&p_tas2562->codec_lock);
|
|
#endif
|
|
}
|
|
|
|
static void init_work_routine(struct work_struct *work)
|
|
{
|
|
struct tas2562_priv *p_tas2562 =
|
|
container_of(work, struct tas2562_priv, init_work.work);
|
|
int nResult = 0;
|
|
//int irqreg;
|
|
//dev_info(p_tas2562->dev, "%s\n", __func__);
|
|
#ifdef CONFIG_TAS2562_CODEC
|
|
mutex_lock(&p_tas2562->codec_lock);
|
|
#endif
|
|
|
|
p_tas2562->update_bits(p_tas2562, channel_both, TAS2562_POWERCONTROL,
|
|
TAS2562_POWERCONTROL_OPERATIONALMODE10_MASK,
|
|
TAS2562_POWERCONTROL_OPERATIONALMODE10_ACTIVE);
|
|
|
|
//dev_info(p_tas2562->dev, "set ICN to -80dB\n");
|
|
p_tas2562->bulk_write(p_tas2562, channel_both,
|
|
TAS2562_ICN_THRESHOLD_REG,
|
|
p_icn_threshold,
|
|
sizeof(p_icn_threshold));
|
|
p_tas2562->bulk_write(p_tas2562, channel_both,
|
|
TAS2562_ICN_HYSTERESIS_REG,
|
|
p_icn_hysteresis,
|
|
sizeof(p_icn_hysteresis));
|
|
|
|
nResult = gpio_get_value(p_tas2562->mn_irq_gpio);
|
|
//dev_info(p_tas2562->dev, "%s, irq GPIO state: %d\n", __func__, nResult);
|
|
|
|
#ifdef CONFIG_TAS2562_CODEC
|
|
mutex_unlock(&p_tas2562->codec_lock);
|
|
#endif
|
|
}
|
|
|
|
static irqreturn_t tas2562_irq_handler(int irq, void *dev_id)
|
|
{
|
|
struct tas2562_priv *p_tas2562 = (struct tas2562_priv *)dev_id;
|
|
|
|
/* get IRQ status after 100 ms */
|
|
schedule_delayed_work(&p_tas2562->irq_work, msecs_to_jiffies(100));
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int tas2562_runtime_suspend(struct tas2562_priv *p_tas2562)
|
|
{
|
|
dev_dbg(p_tas2562->dev, "%s\n", __func__);
|
|
|
|
p_tas2562->mb_runtime_suspend = true;
|
|
|
|
if (p_tas2562->mn_status_check) {
|
|
if (hrtimer_active(&p_tas2562->mtimer)) {
|
|
dev_dbg(p_tas2562->dev, "cancel timer\n");
|
|
hrtimer_cancel(&p_tas2562->mtimer);
|
|
}
|
|
|
|
if (delayed_work_pending(&p_tas2562->status_work)) {
|
|
dev_dbg(p_tas2562->dev, "cancel status work\n");
|
|
cancel_delayed_work_sync(&p_tas2562->status_work);
|
|
}
|
|
}
|
|
|
|
if (delayed_work_pending(&p_tas2562->irq_work)) {
|
|
dev_dbg(p_tas2562->dev, "cancel IRQ work\n");
|
|
cancel_delayed_work_sync(&p_tas2562->irq_work);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas2562_runtime_resume(struct tas2562_priv *p_tas2562)
|
|
{
|
|
dev_dbg(p_tas2562->dev, "%s\n", __func__);
|
|
|
|
if (p_tas2562->mn_status_check) {
|
|
if (p_tas2562->mb_power_up) {
|
|
u64 check_period = p_tas2562->mn_status_period * 1000;
|
|
if (!hrtimer_active(&p_tas2562->mtimer)) {
|
|
dev_dbg(p_tas2562->dev, "%s, start check timer\n", __func__);
|
|
hrtimer_start(&p_tas2562->mtimer,
|
|
ns_to_ktime((u64)check_period * NSEC_PER_MSEC),
|
|
HRTIMER_MODE_REL);
|
|
}
|
|
}
|
|
}
|
|
|
|
p_tas2562->mb_runtime_suspend = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas2562_pm_suspend(struct device *dev)
|
|
{
|
|
struct tas2562_priv *p_tas2562 = dev_get_drvdata(dev);
|
|
|
|
if(!p_tas2562){
|
|
dev_err(p_tas2562->dev, "drvdata is NULL\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&p_tas2562->codec_lock);
|
|
tas2562_runtime_suspend(p_tas2562);
|
|
mutex_unlock(&p_tas2562->codec_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int tas2562_pm_resume(struct device *dev)
|
|
{
|
|
struct tas2562_priv *p_tas2562 = dev_get_drvdata(dev);
|
|
|
|
if(!p_tas2562){
|
|
dev_err(p_tas2562->dev, "drvdata is NULL\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&p_tas2562->codec_lock);
|
|
tas2562_runtime_resume(p_tas2562);
|
|
mutex_unlock(&p_tas2562->codec_lock);
|
|
return 0;
|
|
}
|
|
|
|
/* Added for Mute Issue */
|
|
static void status_work_routine(struct work_struct *work)
|
|
{
|
|
struct tas2562_priv *p_tas2562 =
|
|
container_of(work, struct tas2562_priv, status_work.work);
|
|
unsigned int nDevInt0Status = 0, nDevInt1Status = 0, nDevInt2Status = 0,
|
|
nDevInt3Status = 0, nDevInt4Status = 0, nDevInt5Status = 0;
|
|
int n_result = 0;
|
|
enum channel chn;
|
|
|
|
#ifdef CONFIG_TAS2562_CODEC
|
|
mutex_lock(&p_tas2562->codec_lock);
|
|
#endif
|
|
|
|
if (p_tas2562->mb_runtime_suspend) {
|
|
dev_info(p_tas2562->dev, "%s, Runtime Suspended\n", __func__);
|
|
goto end;
|
|
}
|
|
|
|
if (p_tas2562->mn_power_state == TAS2562_POWER_SHUTDOWN) {
|
|
dev_info(p_tas2562->dev, "%s, device not powered\n", __func__);
|
|
goto end;
|
|
}
|
|
|
|
/* Read latched interrupt status registers */
|
|
if ((p_tas2562->spk_l_control == 1)
|
|
&& (p_tas2562->spk_r_control == 1)
|
|
&& (p_tas2562->mn_channels == 2))
|
|
chn = channel_both;
|
|
else if (p_tas2562->spk_l_control == 1)
|
|
chn = channel_left;
|
|
else if ((p_tas2562->spk_r_control == 1)
|
|
&& (p_tas2562->mn_channels == 2))
|
|
chn = channel_right;
|
|
else
|
|
chn = channel_left;
|
|
|
|
if (chn & channel_left) {
|
|
n_result = p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_LATCHEDINTERRUPTREG0, &nDevInt0Status);
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_LATCHEDINTERRUPTREG1, &nDevInt1Status);
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_LATCHEDINTERRUPTREG2, &nDevInt2Status);
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_LATCHEDINTERRUPTREG3, &nDevInt3Status);
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_LATCHEDINTERRUPTREG4, &nDevInt4Status);
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_LATCHEDINTERRUPTREG5, &nDevInt5Status);
|
|
|
|
dev_info(p_tas2562->dev, "%s: channel_left : 0x%x, 0x%x, 0x%x, 0x%x 0x%x 0x%x\n",
|
|
__func__,
|
|
nDevInt0Status, nDevInt1Status, nDevInt2Status,
|
|
nDevInt3Status, nDevInt4Status, nDevInt5Status);
|
|
}
|
|
|
|
if (chn & channel_right) {
|
|
/* Clear all values */
|
|
nDevInt0Status = 0;
|
|
nDevInt1Status = 0;
|
|
nDevInt2Status = 0;
|
|
nDevInt3Status = 0;
|
|
nDevInt4Status = 0;
|
|
nDevInt5Status = 0;
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_LATCHEDINTERRUPTREG0, &nDevInt0Status);
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_LATCHEDINTERRUPTREG1, &nDevInt1Status);
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_LATCHEDINTERRUPTREG2, &nDevInt2Status);
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_LATCHEDINTERRUPTREG3, &nDevInt3Status);
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_LATCHEDINTERRUPTREG4, &nDevInt4Status);
|
|
|
|
n_result = p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_LATCHEDINTERRUPTREG5, &nDevInt5Status);
|
|
|
|
dev_info(p_tas2562->dev, "%s: channel_right : 0x%x, 0x%x, 0x%x, 0x%x 0x%x 0x%x\n",
|
|
__func__,
|
|
nDevInt0Status, nDevInt1Status, nDevInt2Status,
|
|
nDevInt3Status, nDevInt4Status, nDevInt5Status);
|
|
}
|
|
|
|
/* Read Status register */
|
|
/* Clear register */
|
|
nDevInt0Status = 0;
|
|
if (chn & channel_left) {
|
|
n_result = p_tas2562->read(p_tas2562, channel_left,
|
|
TAS2562_INTSTATUS, &nDevInt0Status);
|
|
if (n_result < 0)
|
|
goto reload;
|
|
if (((nDevInt0Status & TAS2562_INTSTATUS_CLASSD) == 0) ||
|
|
((nDevInt0Status & TAS2562_INTSTATUS_DAC) == 0)) {
|
|
dev_err(p_tas2562->dev, "%s: Device ERROR!", __func__);
|
|
dev_err(p_tas2562->dev, "%s: channel_left : Status : 0x%x\n",
|
|
__func__, nDevInt0Status);
|
|
goto reload;
|
|
}
|
|
}
|
|
nDevInt0Status = 0;
|
|
if (chn & channel_right) {
|
|
n_result = p_tas2562->read(p_tas2562, channel_right,
|
|
TAS2562_INTSTATUS, &nDevInt0Status);
|
|
if (n_result < 0)
|
|
goto reload;
|
|
if (((nDevInt0Status & TAS2562_INTSTATUS_CLASSD) == 0) ||
|
|
((nDevInt0Status & TAS2562_INTSTATUS_DAC) == 0)) {
|
|
dev_info(p_tas2562->dev, "%s: channel_right : Status : 0x%x\n",
|
|
__func__, nDevInt0Status);
|
|
goto reload;
|
|
}
|
|
}
|
|
if (!hrtimer_active(&p_tas2562->mtimer)) {
|
|
u64 check_period = p_tas2562->mn_status_period * 1000;
|
|
dev_dbg(p_tas2562->dev, "%s, start timer\n", __func__);
|
|
hrtimer_start(&p_tas2562->mtimer,
|
|
ns_to_ktime((u64)check_period * NSEC_PER_MSEC), HRTIMER_MODE_REL);
|
|
}
|
|
goto end;
|
|
reload:
|
|
/* hardware reset and reload */
|
|
tas2562_reset_reload(p_tas2562);
|
|
end:
|
|
#ifdef CONFIG_TAS2562_CODEC
|
|
mutex_unlock(&p_tas2562->codec_lock);
|
|
#endif
|
|
}
|
|
|
|
static enum hrtimer_restart timer_func(struct hrtimer *timer)
|
|
{
|
|
struct tas2562_priv *p_tas2562 = container_of(timer,
|
|
struct tas2562_priv, mtimer);
|
|
|
|
if (p_tas2562->mb_power_up) {
|
|
if (!delayed_work_pending(&p_tas2562->status_work))
|
|
schedule_delayed_work(&p_tas2562->status_work,
|
|
msecs_to_jiffies(20));
|
|
}
|
|
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
static int tas2562_parse_dt(struct device *dev, struct tas2562_priv *p_tas2562)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
int rc = 0, ret = 0;
|
|
|
|
rc = of_property_read_u32(np, "ti,channels", &p_tas2562->mn_channels);
|
|
if (rc) {
|
|
dev_err(p_tas2562->dev, "Looking up %s property in node %s failed %d\n",
|
|
"ti,channels", np->full_name, rc);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "ti,channels=%d",
|
|
p_tas2562->mn_channels);
|
|
}
|
|
|
|
rc = of_property_read_u32(np, "ti,left-channel", &p_tas2562->mn_l_addr);
|
|
if (rc) {
|
|
dev_err(p_tas2562->dev, "Looking up %s property in node %s failed %d\n",
|
|
"ti,left-channel", np->full_name, rc);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "ti,left-channel=0x%x",
|
|
p_tas2562->mn_l_addr);
|
|
}
|
|
|
|
if(p_tas2562->mn_channels != 1) {
|
|
rc = of_property_read_u32(np, "ti,right-channel",
|
|
&p_tas2562->mn_r_addr);
|
|
if (rc) {
|
|
dev_err(p_tas2562->dev, "Looking up %s property in node %s failed %d\n",
|
|
"ti,right-channel", np->full_name, rc);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "ti,right-channel=0x%x",
|
|
p_tas2562->mn_r_addr);
|
|
}
|
|
}
|
|
|
|
p_tas2562->mn_reset_gpio = of_get_named_gpio(np, "ti,reset-gpio", 0);
|
|
if (!gpio_is_valid(p_tas2562->mn_reset_gpio)) {
|
|
dev_err(p_tas2562->dev, "Looking up %s property in node %s failed %d\n",
|
|
"ti,reset-gpio", np->full_name,
|
|
p_tas2562->mn_reset_gpio);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "ti,reset-gpio=%d",
|
|
p_tas2562->mn_reset_gpio);
|
|
}
|
|
|
|
if(p_tas2562->mn_channels != 1) {
|
|
p_tas2562->mn_reset_gpio2 = of_get_named_gpio(np, "ti,reset-gpio2", 0);
|
|
if (!gpio_is_valid(p_tas2562->mn_reset_gpio2)) {
|
|
dev_dbg(p_tas2562->dev, "Looking up %s property in node %s failed %d\n",
|
|
"ti,reset-gpio2", np->full_name,
|
|
p_tas2562->mn_reset_gpio2);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "ti,reset-gpio2=%d",
|
|
p_tas2562->mn_reset_gpio2);
|
|
}
|
|
}
|
|
|
|
p_tas2562->mn_irq_gpio = of_get_named_gpio(np, "ti,irq-gpio", 0);
|
|
if (!gpio_is_valid(p_tas2562->mn_irq_gpio)) {
|
|
dev_err(p_tas2562->dev, "Looking up %s property in node %s failed %d\n",
|
|
"ti,irq-gpio", np->full_name, p_tas2562->mn_irq_gpio);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "ti,irq-gpio=%d",
|
|
p_tas2562->mn_irq_gpio);
|
|
}
|
|
|
|
if(p_tas2562->mn_channels != 1) {
|
|
p_tas2562->mn_irq_gpio2 = of_get_named_gpio(np, "ti,irq-gpio2", 0);
|
|
if (!gpio_is_valid(p_tas2562->mn_irq_gpio2)) {
|
|
dev_dbg(p_tas2562->dev, "Looking up %s property in node %s failed %d\n",
|
|
"ti,irq-gpio2", np->full_name, p_tas2562->mn_irq_gpio2);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "ti,irq-gpio2=%d",
|
|
p_tas2562->mn_irq_gpio2);
|
|
}
|
|
}
|
|
|
|
/* Added for Boost Clock Source Change - Mute Issue */
|
|
rc = of_property_read_u32(np, "ti,bst-clk-src", &p_tas2562->mn_bst_clk_src);
|
|
if (rc) {
|
|
p_tas2562->mn_bst_clk_src = 0;
|
|
dev_err(p_tas2562->dev, "Looking up %s property in node %s failed %d\n",
|
|
"ti,bst-clk-src", np->full_name, rc);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "ti,bst-clk-src=0x%x",
|
|
p_tas2562->mn_bst_clk_src);
|
|
}
|
|
|
|
/* Added for Mute Issue */
|
|
rc = of_property_read_u32(np, "ti,status-check", &p_tas2562->mn_status_check);
|
|
if (rc) {
|
|
p_tas2562->mn_status_check = 0;
|
|
dev_err(p_tas2562->dev, "Looking up %s property in node %s failed %d\n",
|
|
"ti,status-check", np->full_name, rc);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "ti,status-check=0x%x",
|
|
p_tas2562->mn_status_check);
|
|
}
|
|
|
|
/* Added for Mute Issue */
|
|
rc = of_property_read_u32(np, "ti,status-period", &p_tas2562->mn_status_period);
|
|
if (rc) {
|
|
p_tas2562->mn_status_period = 1;
|
|
dev_err(p_tas2562->dev, "Looking up %s property in node %s failed %d\n",
|
|
"ti,status-period", np->full_name, rc);
|
|
} else {
|
|
dev_dbg(p_tas2562->dev, "ti,status-period=0x%x",
|
|
p_tas2562->mn_status_period);
|
|
}
|
|
|
|
#ifdef CONFIG_TAS25XX_ALGO
|
|
tas25xx_parse_algo_dt(np);
|
|
#endif /*CONFIG_TAS25XX_ALGO*/
|
|
return ret;
|
|
}
|
|
|
|
static int tas2562_i2c_probe(struct i2c_client *p_client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct tas2562_priv *p_tas2562;
|
|
int n_result;
|
|
|
|
dev_err(&p_client->dev, "Driver ID: %s\n", TAS2562_DRIVER_ID);
|
|
dev_info(&p_client->dev, "%s enter\n", __func__);
|
|
|
|
p_tas2562 = devm_kzalloc(&p_client->dev,
|
|
sizeof(struct tas2562_priv), GFP_KERNEL);
|
|
if (p_tas2562 == NULL) {
|
|
/* dev_err(&p_client->dev, "failed to get i2c device\n"); */
|
|
n_result = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
p_tas2562->client = p_client;
|
|
p_tas2562->dev = &p_client->dev;
|
|
i2c_set_clientdata(p_client, p_tas2562);
|
|
dev_set_drvdata(&p_client->dev, p_tas2562);
|
|
|
|
p_tas2562->regmap = devm_regmap_init_i2c(p_client, &tas2562_i2c_regmap);
|
|
if (IS_ERR(p_tas2562->regmap)) {
|
|
n_result = PTR_ERR(p_tas2562->regmap);
|
|
dev_err(&p_client->dev, "Failed to allocate register map: %d\n",
|
|
n_result);
|
|
goto err;
|
|
}
|
|
|
|
if (p_client->dev.of_node)
|
|
tas2562_parse_dt(&p_client->dev, p_tas2562);
|
|
|
|
if (gpio_is_valid(p_tas2562->mn_reset_gpio)) {
|
|
n_result = gpio_request(p_tas2562->mn_reset_gpio,
|
|
"TAS2562_RESET");
|
|
if (n_result) {
|
|
dev_err(p_tas2562->dev, "%s: Failed to request gpio %d\n",
|
|
__func__, p_tas2562->mn_reset_gpio);
|
|
n_result = -EINVAL;
|
|
goto err;
|
|
}
|
|
tas2562_hw_reset(p_tas2562);
|
|
}
|
|
|
|
if (gpio_is_valid(p_tas2562->mn_reset_gpio2) &&
|
|
(p_tas2562->mn_channels == 2)) {
|
|
n_result = gpio_request(p_tas2562->mn_reset_gpio2,
|
|
"TAS2562_RESET2");
|
|
if (n_result) {
|
|
dev_err(p_tas2562->dev, "%s: Failed to request gpio %d\n",
|
|
__func__, p_tas2562->mn_reset_gpio2);
|
|
n_result = -EINVAL;
|
|
goto err;
|
|
}
|
|
tas2562_hw_reset(p_tas2562);
|
|
}
|
|
|
|
p_tas2562->read = tas2562_dev_read;
|
|
p_tas2562->write = tas2562_dev_write;
|
|
p_tas2562->bulk_read = tas2562_dev_bulk_read;
|
|
p_tas2562->bulk_write = tas2562_dev_bulk_write;
|
|
p_tas2562->update_bits = tas2562_dev_update_bits;
|
|
p_tas2562->hw_reset = tas2562_hw_reset;
|
|
p_tas2562->enable_irq = tas2562_enable_irq;
|
|
#ifdef CODEC_PM
|
|
p_tas2562->runtime_suspend = tas2562_runtime_suspend;
|
|
p_tas2562->runtime_resume = tas2562_runtime_resume;
|
|
p_tas2562->mn_power_state = TAS2562_POWER_SHUTDOWN;
|
|
#endif
|
|
p_tas2562->mn_power_state = TAS2562_POWER_SHUTDOWN;
|
|
p_tas2562->spk_l_control = 1;
|
|
|
|
mutex_init(&p_tas2562->dev_lock);
|
|
|
|
dev_info(&p_client->dev, "Before SW reset\n");
|
|
/* Reset the chip */
|
|
n_result = tas2562_dev_write(p_tas2562, channel_both,
|
|
TAS2562_SOFTWARERESET, 0x01);
|
|
if (n_result < 0) {
|
|
dev_err(&p_client->dev, "I2c fail, %d\n", n_result);
|
|
goto err;
|
|
}
|
|
dev_info(&p_client->dev, "After SW reset\n");
|
|
|
|
/*Initialize IRQ-Work Routin irrespective or IRQ GPIO
|
|
*as it is used for PowerUp function also
|
|
*/
|
|
INIT_DELAYED_WORK(&p_tas2562->irq_work, irq_work_routine);
|
|
|
|
if (gpio_is_valid(p_tas2562->mn_irq_gpio)) {
|
|
n_result = gpio_request(p_tas2562->mn_irq_gpio, "TAS2562-IRQ");
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev, "%s: GPIO %d request error\n",
|
|
__func__, p_tas2562->mn_irq_gpio);
|
|
goto err;
|
|
}
|
|
gpio_direction_input(p_tas2562->mn_irq_gpio);
|
|
tas2562_dev_write(p_tas2562, channel_both,
|
|
TAS2562_MISCCONFIGURATIONREG0, 0xce);
|
|
|
|
p_tas2562->mn_irq = gpio_to_irq(p_tas2562->mn_irq_gpio);
|
|
dev_info(p_tas2562->dev, "irq = %d\n", p_tas2562->mn_irq);
|
|
n_result = request_threaded_irq(p_tas2562->mn_irq,
|
|
tas2562_irq_handler,
|
|
NULL, IRQF_TRIGGER_FALLING|IRQF_ONESHOT,
|
|
p_client->name, p_tas2562);
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev,
|
|
"request_irq failed, %d\n", n_result);
|
|
goto err;
|
|
}
|
|
disable_irq_nosync(p_tas2562->mn_irq);
|
|
}
|
|
if (gpio_is_valid(p_tas2562->mn_irq_gpio2) &&
|
|
(p_tas2562->mn_channels == 2)) {
|
|
n_result = gpio_request(p_tas2562->mn_irq_gpio2,
|
|
"TAS2562-IRQ2");
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev, "%s: GPIO %d request error\n",
|
|
__func__, p_tas2562->mn_irq_gpio2);
|
|
goto err;
|
|
}
|
|
gpio_direction_input(p_tas2562->mn_irq_gpio2);
|
|
tas2562_dev_write(p_tas2562, channel_both,
|
|
TAS2562_MISCCONFIGURATIONREG0, 0xce);
|
|
|
|
p_tas2562->mn_irq2 = gpio_to_irq(p_tas2562->mn_irq_gpio2);
|
|
dev_info(p_tas2562->dev, "irq = %d\n", p_tas2562->mn_irq2);
|
|
n_result = request_threaded_irq(p_tas2562->mn_irq2,
|
|
tas2562_irq_handler,
|
|
NULL, IRQF_TRIGGER_FALLING|IRQF_ONESHOT,
|
|
p_client->name, p_tas2562);
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev,
|
|
"request_irq failed, %d\n", n_result);
|
|
goto err;
|
|
}
|
|
disable_irq_nosync(p_tas2562->mn_irq2);
|
|
}
|
|
tas2562_enable_irq(p_tas2562, true);
|
|
INIT_DELAYED_WORK(&p_tas2562->init_work, init_work_routine);
|
|
|
|
#ifdef CONFIG_TAS2562_CODEC
|
|
mutex_init(&p_tas2562->codec_lock);
|
|
n_result = tas2562_register_codec(p_tas2562);
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev,
|
|
"register codec failed, %d\n", n_result);
|
|
goto err;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_TAS2562_MISC
|
|
mutex_init(&p_tas2562->file_lock);
|
|
n_result = tas2562_register_misc(p_tas2562);
|
|
if (n_result < 0) {
|
|
dev_err(p_tas2562->dev,
|
|
"register codec failed, %d\n", n_result);
|
|
goto err;
|
|
}
|
|
#endif
|
|
if (p_tas2562->mn_status_check) {
|
|
INIT_DELAYED_WORK(&p_tas2562->status_work, status_work_routine);
|
|
hrtimer_init(&p_tas2562->mtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
p_tas2562->mtimer.function = timer_func;
|
|
}
|
|
|
|
err:
|
|
return n_result;
|
|
}
|
|
|
|
static int tas2562_i2c_remove(struct i2c_client *p_client)
|
|
{
|
|
struct tas2562_priv *p_tas2562 = i2c_get_clientdata(p_client);
|
|
|
|
dev_info(p_tas2562->dev, "%s\n", __func__);
|
|
|
|
if (p_tas2562->mn_status_check) {
|
|
if (hrtimer_active(&p_tas2562->mtimer)) {
|
|
dev_dbg(p_tas2562->dev, "cancel timer\n");
|
|
hrtimer_cancel(&p_tas2562->mtimer);
|
|
}
|
|
|
|
if (delayed_work_pending(&p_tas2562->status_work)) {
|
|
dev_dbg(p_tas2562->dev, "cancel status work\n");
|
|
cancel_delayed_work_sync(&p_tas2562->status_work);
|
|
}
|
|
}
|
|
|
|
if (delayed_work_pending(&p_tas2562->irq_work)) {
|
|
dev_dbg(p_tas2562->dev, "cancel IRQ work\n");
|
|
cancel_delayed_work_sync(&p_tas2562->irq_work);
|
|
}
|
|
|
|
#ifdef CONFIG_TAS2562_CODEC
|
|
tas2562_deregister_codec(p_tas2562);
|
|
mutex_destroy(&p_tas2562->codec_lock);
|
|
#endif
|
|
|
|
#ifdef CONFIG_TAS2562_MISC
|
|
tas2562_deregister_misc(p_tas2562);
|
|
mutex_destroy(&p_tas2562->file_lock);
|
|
#endif
|
|
|
|
if (gpio_is_valid(p_tas2562->mn_reset_gpio))
|
|
gpio_free(p_tas2562->mn_reset_gpio);
|
|
if (gpio_is_valid(p_tas2562->mn_irq_gpio))
|
|
gpio_free(p_tas2562->mn_irq_gpio);
|
|
if (gpio_is_valid(p_tas2562->mn_reset_gpio2))
|
|
gpio_free(p_tas2562->mn_reset_gpio2);
|
|
if (gpio_is_valid(p_tas2562->mn_irq_gpio2))
|
|
gpio_free(p_tas2562->mn_irq_gpio2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct i2c_device_id tas2562_i2c_id[] = {
|
|
{ "tas2562", 0},
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, tas2562_i2c_id);
|
|
|
|
#if defined(CONFIG_OF)
|
|
static const struct of_device_id tas2562_of_match[] = {
|
|
{ .compatible = "ti,tas2562" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tas2562_of_match);
|
|
#endif
|
|
|
|
static const struct dev_pm_ops tas2562_pm_ops = {
|
|
.suspend = tas2562_pm_suspend,
|
|
.resume = tas2562_pm_resume
|
|
};
|
|
|
|
static struct i2c_driver tas2562_i2c_driver = {
|
|
.driver = {
|
|
.name = "tas2562",
|
|
.owner = THIS_MODULE,
|
|
#if defined(CONFIG_OF)
|
|
.of_match_table = of_match_ptr(tas2562_of_match),
|
|
#endif
|
|
.pm = &tas2562_pm_ops,
|
|
},
|
|
.probe = tas2562_i2c_probe,
|
|
.remove = tas2562_i2c_remove,
|
|
.id_table = tas2562_i2c_id,
|
|
};
|
|
|
|
module_i2c_driver(tas2562_i2c_driver);
|
|
|
|
MODULE_AUTHOR("Texas Instruments Inc.");
|
|
MODULE_DESCRIPTION("TAS2562 I2C Smart Amplifier driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
#endif
|