| /* |
| * 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 (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 (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__); |
| |
| 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; |
| |
| 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); |
| #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 (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__); |
| |
| 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; |
| } |
| |
| 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); |
| } |
| } |
| #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"); |
| |
| 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); |
| INIT_DELAYED_WORK(&p_tas2562->irq_work, irq_work_routine); |
| 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); |
| INIT_DELAYED_WORK(&p_tas2562->irq_work, irq_work_routine); |
| 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 |
| |
| 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__); |
| |
| #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 |