audio-lnx: Initial change for techpack of audio drivers.

Add snapshot for audio drivers for SDM targets. The code is
migrated from msm-4.9 kernel at the below cutoff -

(74ff856e8d: "net: ipc_router: Add dynamic enable/disable
wakeup source feature")

This changes are done for new techpack addition
for audio kernel. Migrate all audio kernel drivers
to this techpack.

Change-Id: I33d580af3ba86a5cb777583efc5d4cdaf2882d93
Signed-off-by: Asish Bhattacharya <asishb@codeaurora.org>
This commit is contained in:
Asish Bhattacharya 2017-07-20 18:31:55 +05:30 • committed by Martin Fick
commit 8e2277f79f
283 changed files with 250571 additions and 0 deletions

23
Makefile Normal file
View file

@ -0,0 +1,23 @@
# auto-detect subdirs
ifeq ($(CONFIG_ARCH_SDM845), y)
include $(srctree)/techpack/audio/config/sdm845auto.conf
export
endif
# Use USERINCLUDE when you must reference the UAPI directories only.
USERINCLUDE += \
-I$(srctree)/techpack/audio/include/uapi \
# Use LINUXINCLUDE when you must reference the include/ directory.
# Needed to be compatible with the O= option
LINUXINCLUDE += \
-I$(srctree)/techpack/audio/include/uapi \
-I$(srctree)/techpack/audio/include
ifeq ($(CONFIG_ARCH_SDM845), y)
LINUXINCLUDE += \
-include $(srctree)/techpack/audio/config/sdm845autoconf.h
endif
obj-y += drivers/
obj-y += sound/

24
NOTICE Normal file
View file

@ -0,0 +1,24 @@
Copyright (c) 2009-2017, The Linux Foundation. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 and
only version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
________________________________________
Copyright (C) 2008 Google, Inc.
Copyright (C) 2008 HTC Corporation
Copyright (c) 2010-2017, The Linux Foundation. All rights reserved.
This software is licensed under the terms of the GNU General Public
License version 2, as published by the Free Software Foundation, and
may be copied, distributed, and modified under those terms.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

7
drivers/Makefile Normal file
View file

@ -0,0 +1,7 @@
obj-y += mfd/
obj-y += misc/
obj-y += soc/
obj-y += soundwire/
obj-y += base/
obj-y += pinctrl/

2
drivers/base/Makefile Normal file
View file

@ -0,0 +1,2 @@
obj-y += regmap/

View file

@ -0,0 +1,2 @@
obj-$(CONFIG_REGMAP_SWR) += regmap-swr.o

View file

@ -0,0 +1 @@
../../../../../drivers/base/regmap/internal.h

View file

@ -0,0 +1,216 @@
/*
* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/soundwire/soundwire.h>
#include <linux/module.h>
#include <linux/init.h>
#include "internal.h"
static int regmap_swr_gather_write(void *context,
const void *reg, size_t reg_size,
const void *val, size_t val_len)
{
struct device *dev = context;
struct swr_device *swr = to_swr_device(dev);
struct regmap *map = dev_get_regmap(dev, NULL);
size_t addr_bytes;
size_t val_bytes;
int i, ret = 0;
u16 reg_addr = 0;
u8 *value;
if (map == NULL) {
dev_err(dev, "%s: regmap is NULL\n", __func__);
return -EINVAL;
}
addr_bytes = map->format.reg_bytes;
if (swr == NULL) {
dev_err(dev, "%s: swr device is NULL\n", __func__);
return -EINVAL;
}
if (reg_size != addr_bytes) {
dev_err(dev, "%s: reg size %zd bytes not supported\n",
__func__, reg_size);
return -EINVAL;
}
reg_addr = *(u16 *)reg;
val_bytes = map->format.val_bytes;
/* val_len = val_bytes * val_count */
for (i = 0; i < (val_len / val_bytes); i++) {
value = (u8 *)val + (val_bytes * i);
ret = swr_write(swr, swr->dev_num, (reg_addr + i), value);
if (ret < 0) {
dev_err(dev, "%s: write reg 0x%x failed, err %d\n",
__func__, (reg_addr + i), ret);
break;
}
}
return ret;
}
static int regmap_swr_raw_multi_reg_write(void *context, const void *data,
size_t count)
{
struct device *dev = context;
struct swr_device *swr = to_swr_device(dev);
struct regmap *map = dev_get_regmap(dev, NULL);
size_t addr_bytes;
size_t val_bytes;
size_t pad_bytes;
size_t num_regs;
int i = 0;
int ret = 0;
u16 *reg;
u8 *val;
u8 *buf;
if (swr == NULL) {
dev_err(dev, "%s: swr device is NULL\n", __func__);
return -EINVAL;
}
if (map == NULL) {
dev_err(dev, "%s: regmap is NULL\n", __func__);
return -EINVAL;
}
addr_bytes = map->format.reg_bytes;
val_bytes = map->format.val_bytes;
pad_bytes = map->format.pad_bytes;
if (addr_bytes + val_bytes + pad_bytes == 0) {
dev_err(dev, "%s: sum of addr, value and pad is 0\n", __func__);
return -EINVAL;
}
num_regs = count / (addr_bytes + val_bytes + pad_bytes);
reg = kcalloc(num_regs, sizeof(u16), GFP_KERNEL);
if (!reg)
return -ENOMEM;
val = kcalloc(num_regs, sizeof(u8), GFP_KERNEL);
if (!val) {
ret = -ENOMEM;
goto mem_fail;
}
buf = (u8 *)data;
for (i = 0; i < num_regs; i++) {
reg[i] = *(u16 *)buf;
buf += (map->format.reg_bytes + map->format.pad_bytes);
val[i] = *buf;
buf += map->format.val_bytes;
}
ret = swr_bulk_write(swr, swr->dev_num, reg, val, num_regs);
if (ret)
dev_err(dev, "%s: multi reg write failed\n", __func__);
kfree(val);
mem_fail:
kfree(reg);
return ret;
}
static int regmap_swr_write(void *context, const void *data, size_t count)
{
struct device *dev = context;
struct regmap *map = dev_get_regmap(dev, NULL);
size_t addr_bytes;
size_t val_bytes;
size_t pad_bytes;
if (map == NULL) {
dev_err(dev, "%s: regmap is NULL\n", __func__);
return -EINVAL;
}
addr_bytes = map->format.reg_bytes;
val_bytes = map->format.val_bytes;
pad_bytes = map->format.pad_bytes;
WARN_ON(count < addr_bytes);
if (count > (addr_bytes + val_bytes + pad_bytes))
return regmap_swr_raw_multi_reg_write(context, data, count);
else
return regmap_swr_gather_write(context, data, addr_bytes,
(data + addr_bytes),
(count - addr_bytes));
}
static int regmap_swr_read(void *context,
const void *reg, size_t reg_size,
void *val, size_t val_size)
{
struct device *dev = context;
struct swr_device *swr = to_swr_device(dev);
struct regmap *map = dev_get_regmap(dev, NULL);
size_t addr_bytes;
int ret = 0;
u16 reg_addr = 0;
if (map == NULL) {
dev_err(dev, "%s: regmap is NULL\n", __func__);
return -EINVAL;
}
addr_bytes = map->format.reg_bytes;
if (swr == NULL) {
dev_err(dev, "%s: swr is NULL\n", __func__);
return -EINVAL;
}
if (reg_size != addr_bytes) {
dev_err(dev, "%s: register size %zd bytes not supported\n",
__func__, reg_size);
return -EINVAL;
}
reg_addr = *(u16 *)reg;
ret = swr_read(swr, swr->dev_num, reg_addr, val, val_size);
if (ret < 0)
dev_err(dev, "%s: codec reg 0x%x read failed %d\n",
__func__, reg_addr, ret);
return ret;
}
static struct regmap_bus regmap_swr = {
.write = regmap_swr_write,
.gather_write = regmap_swr_gather_write,
.read = regmap_swr_read,
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
.val_format_endian_default = REGMAP_ENDIAN_NATIVE,
};
struct regmap *__regmap_init_swr(struct swr_device *swr,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name)
{
return __regmap_init(&swr->dev, &regmap_swr, &swr->dev, config,
lock_key, lock_name);
}
EXPORT_SYMBOL(__regmap_init_swr);
struct regmap *__devm_regmap_init_swr(struct swr_device *swr,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name)
{
return __devm_regmap_init(&swr->dev, &regmap_swr, &swr->dev, config,
lock_key, lock_name);
}
EXPORT_SYMBOL(__devm_regmap_init_swr);
MODULE_LICENSE("GPL v2");

8
drivers/mfd/Makefile Normal file
View file

@ -0,0 +1,8 @@
wcd-core-objs := wcd9xxx-rst.o wcd9xxx-core-init.o \
wcd9xxx-core.o wcd9xxx-irq.o \
wcd9xxx-slimslave.o wcd9xxx-utils.o \
wcd934x-regmap.o wcd934x-tables.o \
wcd9335-regmap.o wcd9335-tables.o \
msm-cdc-pinctrl.o msm-cdc-supply.o
obj-$(CONFIG_WCD9XXX_CODEC_CORE) += wcd-core.o

View file

@ -0,0 +1,253 @@
/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/mfd/msm-cdc-pinctrl.h>
struct msm_cdc_pinctrl_info {
struct pinctrl *pinctrl;
struct pinctrl_state *pinctrl_active;
struct pinctrl_state *pinctrl_sleep;
int gpio;
bool state;
};
static struct msm_cdc_pinctrl_info *msm_cdc_pinctrl_get_gpiodata(
struct device_node *np)
{
struct platform_device *pdev;
struct msm_cdc_pinctrl_info *gpio_data;
if (!np) {
pr_err("%s: device node is null\n", __func__);
return NULL;
}
pdev = of_find_device_by_node(np);
if (!pdev) {
pr_err("%s: platform device not found!\n", __func__);
return NULL;
}
gpio_data = dev_get_drvdata(&pdev->dev);
if (!gpio_data)
dev_err(&pdev->dev, "%s: cannot find cdc gpio info\n",
__func__);
return gpio_data;
}
/*
* msm_cdc_get_gpio_state: select pinctrl sleep state
* @np: pointer to struct device_node
*
* Returns error code for failure and GPIO value on success
*/
int msm_cdc_get_gpio_state(struct device_node *np)
{
struct msm_cdc_pinctrl_info *gpio_data;
int value = -EINVAL;
gpio_data = msm_cdc_pinctrl_get_gpiodata(np);
if (!gpio_data)
return value;
if (gpio_is_valid(gpio_data->gpio))
value = gpio_get_value_cansleep(gpio_data->gpio);
return value;
}
EXPORT_SYMBOL(msm_cdc_get_gpio_state);
/*
* msm_cdc_pinctrl_select_sleep_state: select pinctrl sleep state
* @np: pointer to struct device_node
*
* Returns error code for failure
*/
int msm_cdc_pinctrl_select_sleep_state(struct device_node *np)
{
struct msm_cdc_pinctrl_info *gpio_data;
gpio_data = msm_cdc_pinctrl_get_gpiodata(np);
if (!gpio_data)
return -EINVAL;
if (!gpio_data->pinctrl_sleep) {
pr_err("%s: pinctrl sleep state is null\n", __func__);
return -EINVAL;
}
gpio_data->state = false;
return pinctrl_select_state(gpio_data->pinctrl,
gpio_data->pinctrl_sleep);
}
EXPORT_SYMBOL(msm_cdc_pinctrl_select_sleep_state);
/*
* msm_cdc_pinctrl_select_active_state: select pinctrl active state
* @np: pointer to struct device_node
*
* Returns error code for failure
*/
int msm_cdc_pinctrl_select_active_state(struct device_node *np)
{
struct msm_cdc_pinctrl_info *gpio_data;
gpio_data = msm_cdc_pinctrl_get_gpiodata(np);
if (!gpio_data)
return -EINVAL;
if (!gpio_data->pinctrl_active) {
pr_err("%s: pinctrl active state is null\n", __func__);
return -EINVAL;
}
gpio_data->state = true;
return pinctrl_select_state(gpio_data->pinctrl,
gpio_data->pinctrl_active);
}
EXPORT_SYMBOL(msm_cdc_pinctrl_select_active_state);
/*
* msm_cdc_pinctrl_get_state: get curren pinctrl state
* @np: pointer to struct device_node
*
* Returns 0 for sleep state, 1 for active state
*/
bool msm_cdc_pinctrl_get_state(struct device_node *np)
{
struct msm_cdc_pinctrl_info *gpio_data;
gpio_data = msm_cdc_pinctrl_get_gpiodata(np);
if (!gpio_data)
return -EINVAL;
return gpio_data->state;
}
EXPORT_SYMBOL(msm_cdc_pinctrl_get_state);
static int msm_cdc_pinctrl_probe(struct platform_device *pdev)
{
int ret = 0;
struct msm_cdc_pinctrl_info *gpio_data;
gpio_data = devm_kzalloc(&pdev->dev,
sizeof(struct msm_cdc_pinctrl_info),
GFP_KERNEL);
if (!gpio_data)
return -ENOMEM;
gpio_data->pinctrl = devm_pinctrl_get(&pdev->dev);
if (IS_ERR_OR_NULL(gpio_data->pinctrl)) {
dev_err(&pdev->dev, "%s: Cannot get cdc gpio pinctrl:%ld\n",
__func__, PTR_ERR(gpio_data->pinctrl));
ret = PTR_ERR(gpio_data->pinctrl);
goto err_pctrl_get;
}
gpio_data->pinctrl_active = pinctrl_lookup_state(
gpio_data->pinctrl, "aud_active");
if (IS_ERR_OR_NULL(gpio_data->pinctrl_active)) {
dev_err(&pdev->dev, "%s: Cannot get aud_active pinctrl state:%ld\n",
__func__, PTR_ERR(gpio_data->pinctrl_active));
ret = PTR_ERR(gpio_data->pinctrl_active);
goto err_lookup_state;
}
gpio_data->pinctrl_sleep = pinctrl_lookup_state(
gpio_data->pinctrl, "aud_sleep");
if (IS_ERR_OR_NULL(gpio_data->pinctrl_sleep)) {
dev_err(&pdev->dev, "%s: Cannot get aud_sleep pinctrl state:%ld\n",
__func__, PTR_ERR(gpio_data->pinctrl_sleep));
ret = PTR_ERR(gpio_data->pinctrl_sleep);
goto err_lookup_state;
}
/* skip setting to sleep state for LPI_TLMM GPIOs */
if (!of_property_read_bool(pdev->dev.of_node, "qcom,lpi-gpios")) {
/* Set pinctrl state to aud_sleep by default */
ret = pinctrl_select_state(gpio_data->pinctrl,
gpio_data->pinctrl_sleep);
if (ret)
dev_err(&pdev->dev, "%s: set cdc gpio sleep state fail: %d\n",
__func__, ret);
}
gpio_data->gpio = of_get_named_gpio(pdev->dev.of_node,
"qcom,cdc-rst-n-gpio", 0);
if (gpio_is_valid(gpio_data->gpio)) {
ret = gpio_request(gpio_data->gpio, "MSM_CDC_RESET");
if (ret) {
dev_err(&pdev->dev, "%s: Failed to request gpio %d\n",
__func__, gpio_data->gpio);
goto err_lookup_state;
}
}
dev_set_drvdata(&pdev->dev, gpio_data);
return 0;
err_lookup_state:
devm_pinctrl_put(gpio_data->pinctrl);
err_pctrl_get:
devm_kfree(&pdev->dev, gpio_data);
return ret;
}
static int msm_cdc_pinctrl_remove(struct platform_device *pdev)
{
struct msm_cdc_pinctrl_info *gpio_data;
gpio_data = dev_get_drvdata(&pdev->dev);
if (gpio_data && gpio_data->pinctrl)
devm_pinctrl_put(gpio_data->pinctrl);
devm_kfree(&pdev->dev, gpio_data);
return 0;
}
static const struct of_device_id msm_cdc_pinctrl_match[] = {
{.compatible = "qcom,msm-cdc-pinctrl"},
{}
};
static struct platform_driver msm_cdc_pinctrl_driver = {
.driver = {
.name = "msm-cdc-pinctrl",
.owner = THIS_MODULE,
.of_match_table = msm_cdc_pinctrl_match,
},
.probe = msm_cdc_pinctrl_probe,
.remove = msm_cdc_pinctrl_remove,
};
int msm_cdc_pinctrl_drv_init(void)
{
return platform_driver_register(&msm_cdc_pinctrl_driver);
}
void msm_cdc_pinctrl_drv_exit(void)
{
platform_driver_unregister(&msm_cdc_pinctrl_driver);
}
MODULE_DESCRIPTION("MSM CODEC pin control platform driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,456 @@
/*
* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/mfd/msm-cdc-supply.h>
#include <linux/regulator/consumer.h>
#define CODEC_DT_MAX_PROP_SIZE 40
static int msm_cdc_dt_parse_vreg_info(struct device *dev,
struct cdc_regulator *cdc_vreg,
const char *name, bool is_ond)
{
char prop_name[CODEC_DT_MAX_PROP_SIZE];
struct device_node *regulator_node = NULL;
const __be32 *prop;
int len, rc;
u32 prop_val;
/* Parse supply name */
snprintf(prop_name, CODEC_DT_MAX_PROP_SIZE, "%s-supply", name);
regulator_node = of_parse_phandle(dev->of_node, prop_name, 0);
if (!regulator_node) {
dev_err(dev, "%s: Looking up %s property in node %s failed",
__func__, prop_name, dev->of_node->full_name);
rc = -EINVAL;
goto done;
}
cdc_vreg->name = name;
cdc_vreg->ondemand = is_ond;
/* Parse supply - voltage */
snprintf(prop_name, CODEC_DT_MAX_PROP_SIZE, "qcom,%s-voltage", name);
prop = of_get_property(dev->of_node, prop_name, &len);
if (!prop || (len != (2 * sizeof(__be32)))) {
dev_err(dev, "%s: %s %s property\n", __func__,
prop ? "invalid format" : "no", prop_name);
rc = -EINVAL;
goto done;
} else {
cdc_vreg->min_uV = be32_to_cpup(&prop[0]);
cdc_vreg->max_uV = be32_to_cpup(&prop[1]);
}
/* Parse supply - current */
snprintf(prop_name, CODEC_DT_MAX_PROP_SIZE, "qcom,%s-current", name);
rc = of_property_read_u32(dev->of_node, prop_name, &prop_val);
if (rc) {
dev_err(dev, "%s: Looking up %s property in node %s failed",
__func__, prop_name, dev->of_node->full_name);
goto done;
}
cdc_vreg->optimum_uA = prop_val;
dev_info(dev, "%s: %s: vol=[%d %d]uV, curr=[%d]uA, ond %d\n",
__func__, cdc_vreg->name, cdc_vreg->min_uV, cdc_vreg->max_uV,
cdc_vreg->optimum_uA, cdc_vreg->ondemand);
done:
return rc;
}
static int msm_cdc_parse_supplies(struct device *dev,
struct cdc_regulator *cdc_reg,
const char *sup_list, int sup_cnt,
bool is_ond)
{
int idx, rc = 0;
const char *name = NULL;
for (idx = 0; idx < sup_cnt; idx++) {
rc = of_property_read_string_index(dev->of_node, sup_list, idx,
&name);
if (rc) {
dev_err(dev, "%s: read string %s[%d] error (%d)\n",
__func__, sup_list, idx, rc);
goto done;
}
dev_dbg(dev, "%s: Found cdc supply %s as part of %s\n",
__func__, name, sup_list);
rc = msm_cdc_dt_parse_vreg_info(dev, &cdc_reg[idx], name,
is_ond);
if (rc) {
dev_err(dev, "%s: parse %s vreg info failed (%d)\n",
__func__, name, rc);
goto done;
}
}
done:
return rc;
}
static int msm_cdc_check_supply_param(struct device *dev,
struct cdc_regulator *cdc_vreg,
int num_supplies)
{
if (!dev) {
pr_err("%s: device is NULL\n", __func__);
return -ENODEV;
}
if (!cdc_vreg || (num_supplies <= 0)) {
dev_err(dev, "%s: supply check failed: vreg: %pK, num_supplies: %d\n",
__func__, cdc_vreg, num_supplies);
return -EINVAL;
}
return 0;
}
/*
* msm_cdc_disable_static_supplies:
* Disable codec static supplies
*
* @dev: pointer to codec device
* @supplies: pointer to regulator bulk data
* @cdc_vreg: pointer to platform regulator data
* @num_supplies: number of supplies
*
* Return error code if supply disable is failed
*/
int msm_cdc_disable_static_supplies(struct device *dev,
struct regulator_bulk_data *supplies,
struct cdc_regulator *cdc_vreg,
int num_supplies)
{
int rc, i;
if ((!dev) || (!supplies) || (!cdc_vreg)) {
pr_err("%s: either dev or supplies or cdc_vreg is NULL\n",
__func__);
return -EINVAL;
}
/* input parameter validation */
rc = msm_cdc_check_supply_param(dev, cdc_vreg, num_supplies);
if (rc)
return rc;
for (i = 0; i < num_supplies; i++) {
if (cdc_vreg[i].ondemand)
continue;
rc = regulator_disable(supplies[i].consumer);
if (rc)
dev_err(dev, "%s: failed to disable supply %s, err:%d\n",
__func__, supplies[i].supply, rc);
else
dev_dbg(dev, "%s: disabled regulator %s\n",
__func__, supplies[i].supply);
}
return rc;
}
EXPORT_SYMBOL(msm_cdc_disable_static_supplies);
/*
* msm_cdc_release_supplies:
* Release codec power supplies
*
* @dev: pointer to codec device
* @supplies: pointer to regulator bulk data
* @cdc_vreg: pointer to platform regulator data
* @num_supplies: number of supplies
*
* Return error code if supply disable is failed
*/
int msm_cdc_release_supplies(struct device *dev,
struct regulator_bulk_data *supplies,
struct cdc_regulator *cdc_vreg,
int num_supplies)
{
int rc = 0;
int i;
if ((!dev) || (!supplies) || (!cdc_vreg)) {
pr_err("%s: either dev or supplies or cdc_vreg is NULL\n",
__func__);
return -EINVAL;
}
/* input parameter validation */
rc = msm_cdc_check_supply_param(dev, cdc_vreg, num_supplies);
if (rc)
return rc;
msm_cdc_disable_static_supplies(dev, supplies, cdc_vreg,
num_supplies);
for (i = 0; i < num_supplies; i++) {
if (regulator_count_voltages(supplies[i].consumer) < 0)
continue;
regulator_set_voltage(supplies[i].consumer, 0,
cdc_vreg[i].max_uV);
regulator_set_load(supplies[i].consumer, 0);
devm_regulator_put(supplies[i].consumer);
supplies[i].consumer = NULL;
}
devm_kfree(dev, supplies);
return rc;
}
EXPORT_SYMBOL(msm_cdc_release_supplies);
/*
* msm_cdc_enable_static_supplies:
* Enable codec static supplies
*
* @dev: pointer to codec device
* @supplies: pointer to regulator bulk data
* @cdc_vreg: pointer to platform regulator data
* @num_supplies: number of supplies
*
* Return error code if supply enable is failed
*/
int msm_cdc_enable_static_supplies(struct device *dev,
struct regulator_bulk_data *supplies,
struct cdc_regulator *cdc_vreg,
int num_supplies)
{
int rc, i;
if ((!dev) || (!supplies) || (!cdc_vreg)) {
pr_err("%s: either dev or supplies or cdc_vreg is NULL\n",
__func__);
return -EINVAL;
}
/* input parameter validation */
rc = msm_cdc_check_supply_param(dev, cdc_vreg, num_supplies);
if (rc)
return rc;
for (i = 0; i < num_supplies; i++) {
if (cdc_vreg[i].ondemand)
continue;
rc = regulator_enable(supplies[i].consumer);
if (rc) {
dev_err(dev, "%s: failed to enable supply %s, rc: %d\n",
__func__, supplies[i].supply, rc);
break;
}
}
while (rc && i--)
if (!cdc_vreg[i].ondemand)
regulator_disable(supplies[i].consumer);
return rc;
}
EXPORT_SYMBOL(msm_cdc_enable_static_supplies);
/*
* msm_cdc_init_supplies:
* Initialize codec static supplies with regulator get
*
* @dev: pointer to codec device
* @supplies: pointer to regulator bulk data
* @cdc_vreg: pointer to platform regulator data
* @num_supplies: number of supplies
*
* Return error code if supply init is failed
*/
int msm_cdc_init_supplies(struct device *dev,
struct regulator_bulk_data **supplies,
struct cdc_regulator *cdc_vreg,
int num_supplies)
{
struct regulator_bulk_data *vsup;
int rc;
int i;
if (!dev || !cdc_vreg) {
pr_err("%s: device pointer or dce_vreg is NULL\n",
__func__);
return -EINVAL;
}
/* input parameter validation */
rc = msm_cdc_check_supply_param(dev, cdc_vreg, num_supplies);
if (rc)
return rc;
vsup = devm_kcalloc(dev, num_supplies,
sizeof(struct regulator_bulk_data),
GFP_KERNEL);
if (!vsup)
return -ENOMEM;
for (i = 0; i < num_supplies; i++) {
if (!cdc_vreg[i].name) {
dev_err(dev, "%s: supply name not defined\n",
__func__);
rc = -EINVAL;
goto err_supply;
}
vsup[i].supply = cdc_vreg[i].name;
}
rc = devm_regulator_bulk_get(dev, num_supplies, vsup);
if (rc) {
dev_err(dev, "%s: failed to get supplies (%d)\n",
__func__, rc);
goto err_supply;
}
/* Set voltage and current on regulators */
for (i = 0; i < num_supplies; i++) {
if (regulator_count_voltages(vsup[i].consumer) < 0)
continue;
rc = regulator_set_voltage(vsup[i].consumer,
cdc_vreg[i].min_uV,
cdc_vreg[i].max_uV);
if (rc) {
dev_err(dev, "%s: set regulator voltage failed for %s, err:%d\n",
__func__, vsup[i].supply, rc);
goto err_set_supply;
}
rc = regulator_set_load(vsup[i].consumer,
cdc_vreg[i].optimum_uA);
if (rc < 0) {
dev_err(dev, "%s: set regulator optimum mode failed for %s, err:%d\n",
__func__, vsup[i].supply, rc);
goto err_set_supply;
}
}
*supplies = vsup;
return 0;
err_set_supply:
for (i = 0; i < num_supplies; i++)
devm_regulator_put(vsup[i].consumer);
err_supply:
devm_kfree(dev, vsup);
return rc;
}
EXPORT_SYMBOL(msm_cdc_init_supplies);
/*
* msm_cdc_get_power_supplies:
* Get codec power supplies from device tree.
* Allocate memory to hold regulator data for
* all power supplies.
*
* @dev: pointer to codec device
* @cdc_vreg: pointer to codec regulator
* @total_num_supplies: total number of supplies read from DT
*
* Return error code if supply disable is failed
*/
int msm_cdc_get_power_supplies(struct device *dev,
struct cdc_regulator **cdc_vreg,
int *total_num_supplies)
{
const char *static_prop_name = "qcom,cdc-static-supplies";
const char *ond_prop_name = "qcom,cdc-on-demand-supplies";
const char *cp_prop_name = "qcom,cdc-cp-supplies";
int static_sup_cnt = 0;
int ond_sup_cnt = 0;
int cp_sup_cnt = 0;
int num_supplies = 0;
struct cdc_regulator *cdc_reg;
int rc;
if (!dev) {
pr_err("%s: device pointer is NULL\n", __func__);
return -EINVAL;
}
static_sup_cnt = of_property_count_strings(dev->of_node,
static_prop_name);
if (static_sup_cnt < 0) {
dev_err(dev, "%s: Failed to get static supplies(%d)\n",
__func__, static_sup_cnt);
rc = static_sup_cnt;
goto err_supply_cnt;
}
ond_sup_cnt = of_property_count_strings(dev->of_node, ond_prop_name);
if (ond_sup_cnt < 0)
ond_sup_cnt = 0;
cp_sup_cnt = of_property_count_strings(dev->of_node,
cp_prop_name);
if (cp_sup_cnt < 0)
cp_sup_cnt = 0;
num_supplies = static_sup_cnt + ond_sup_cnt + cp_sup_cnt;
if (num_supplies <= 0) {
dev_err(dev, "%s: supply count is 0 or negative\n", __func__);
rc = -EINVAL;
goto err_supply_cnt;
}
cdc_reg = devm_kcalloc(dev, num_supplies,
sizeof(struct cdc_regulator),
GFP_KERNEL);
if (!cdc_reg) {
rc = -ENOMEM;
goto err_mem_alloc;
}
rc = msm_cdc_parse_supplies(dev, cdc_reg, static_prop_name,
static_sup_cnt, false);
if (rc) {
dev_err(dev, "%s: failed to parse static supplies(%d)\n",
__func__, rc);
goto err_sup;
}
rc = msm_cdc_parse_supplies(dev, &cdc_reg[static_sup_cnt],
ond_prop_name, ond_sup_cnt,
true);
if (rc) {
dev_err(dev, "%s: failed to parse demand supplies(%d)\n",
__func__, rc);
goto err_sup;
}
rc = msm_cdc_parse_supplies(dev,
&cdc_reg[static_sup_cnt + ond_sup_cnt],
cp_prop_name, cp_sup_cnt, true);
if (rc) {
dev_err(dev, "%s: failed to parse cp supplies(%d)\n",
__func__, rc);
goto err_sup;
}
*cdc_vreg = cdc_reg;
*total_num_supplies = num_supplies;
return 0;
err_sup:
devm_kfree(dev, cdc_reg);
err_supply_cnt:
err_mem_alloc:
return rc;
}
EXPORT_SYMBOL(msm_cdc_get_power_supplies);

1611
drivers/mfd/wcd9335-regmap.c Normal file

File diff suppressed because it is too large Load diff

1325
drivers/mfd/wcd9335-tables.c Normal file

File diff suppressed because it is too large Load diff

1957
drivers/mfd/wcd934x-regmap.c Normal file

File diff suppressed because it is too large Load diff

2155
drivers/mfd/wcd934x-tables.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,55 @@
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/mfd/msm-cdc-pinctrl.h>
#include <linux/mfd/wcd9xxx/wcd9xxx-irq.h>
#include <linux/mfd/wcd9xxx/core.h>
#define NUM_DRIVERS_REG_RET 3
static int __init wcd9xxx_core_init(void)
{
int ret[NUM_DRIVERS_REG_RET] = {0};
int i = 0;
ret[0] = msm_cdc_pinctrl_drv_init();
if (ret[0])
pr_err("%s: Failed init pinctrl drv: %d\n", __func__, ret[0]);
ret[1] = wcd9xxx_irq_drv_init();
if (ret[1])
pr_err("%s: Failed init irq drv: %d\n", __func__, ret[1]);
ret[2] = wcd9xxx_init();
if (ret[2])
pr_err("%s: Failed wcd core drv: %d\n", __func__, ret[2]);
for (i = 0; i < NUM_DRIVERS_REG_RET; i++) {
if (ret[i])
return ret[i];
}
return 0;
}
module_init(wcd9xxx_core_init);
static void __exit wcd9xxx_core_exit(void)
{
wcd9xxx_exit();
wcd9xxx_irq_drv_exit();
msm_cdc_pinctrl_drv_exit();
}
module_exit(wcd9xxx_core_exit);
MODULE_DESCRIPTION("WCD9XXX CODEC core init driver");
MODULE_LICENSE("GPL v2");

1714
drivers/mfd/wcd9xxx-core.c Normal file

File diff suppressed because it is too large Load diff

853
drivers/mfd/wcd9xxx-irq.c Normal file
View file

@ -0,0 +1,853 @@
/* Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <linux/mfd/core.h>
#include <linux/regmap.h>
#include <linux/mfd/wcd9xxx/core.h>
#include <linux/mfd/wcd9xxx/wcd9xxx_registers.h>
#include <linux/mfd/wcd9xxx/wcd9xxx-irq.h>
#include <linux/delay.h>
#include <linux/irqdomain.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/ratelimit.h>
#include <soc/qcom/pm.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#define BYTE_BIT_MASK(nr) (1UL << ((nr) % BITS_PER_BYTE))
#define BIT_BYTE(nr) ((nr) / BITS_PER_BYTE)
#define WCD9XXX_SYSTEM_RESUME_TIMEOUT_MS 100
#ifndef NO_IRQ
#define NO_IRQ (-1)
#endif
#ifdef CONFIG_OF
struct wcd9xxx_irq_drv_data {
struct irq_domain *domain;
int irq;
};
#endif
static int virq_to_phyirq(
struct wcd9xxx_core_resource *wcd9xxx_res, int virq);
static int phyirq_to_virq(
struct wcd9xxx_core_resource *wcd9xxx_res, int irq);
static unsigned int wcd9xxx_irq_get_upstream_irq(
struct wcd9xxx_core_resource *wcd9xxx_res);
static void wcd9xxx_irq_put_upstream_irq(
struct wcd9xxx_core_resource *wcd9xxx_res);
static int wcd9xxx_map_irq(
struct wcd9xxx_core_resource *wcd9xxx_res, int irq);
static void wcd9xxx_irq_lock(struct irq_data *data)
{
struct wcd9xxx_core_resource *wcd9xxx_res =
irq_data_get_irq_chip_data(data);
mutex_lock(&wcd9xxx_res->irq_lock);
}
static void wcd9xxx_irq_sync_unlock(struct irq_data *data)
{
struct wcd9xxx_core_resource *wcd9xxx_res =
irq_data_get_irq_chip_data(data);
int i;
if ((ARRAY_SIZE(wcd9xxx_res->irq_masks_cur) >
WCD9XXX_MAX_IRQ_REGS) ||
(ARRAY_SIZE(wcd9xxx_res->irq_masks_cache) >
WCD9XXX_MAX_IRQ_REGS)) {
pr_err("%s: Array Size out of bound\n", __func__);
return;
}
if (!wcd9xxx_res->wcd_core_regmap) {
pr_err("%s: Codec core regmap not defined\n",
__func__);
return;
}
for (i = 0; i < ARRAY_SIZE(wcd9xxx_res->irq_masks_cur); i++) {
/* If there's been a change in the mask write it back
* to the hardware.
*/
if (wcd9xxx_res->irq_masks_cur[i] !=
wcd9xxx_res->irq_masks_cache[i]) {
wcd9xxx_res->irq_masks_cache[i] =
wcd9xxx_res->irq_masks_cur[i];
regmap_write(wcd9xxx_res->wcd_core_regmap,
wcd9xxx_res->intr_reg[WCD9XXX_INTR_MASK_BASE] + i,
wcd9xxx_res->irq_masks_cur[i]);
}
}
mutex_unlock(&wcd9xxx_res->irq_lock);
}
static void wcd9xxx_irq_enable(struct irq_data *data)
{
struct wcd9xxx_core_resource *wcd9xxx_res =
irq_data_get_irq_chip_data(data);
int wcd9xxx_irq = virq_to_phyirq(wcd9xxx_res, data->irq);
int byte = BIT_BYTE(wcd9xxx_irq);
int size = ARRAY_SIZE(wcd9xxx_res->irq_masks_cur);
if ((byte < size) && (byte >= 0)) {
wcd9xxx_res->irq_masks_cur[byte] &=
~(BYTE_BIT_MASK(wcd9xxx_irq));
} else {
pr_err("%s: Array size is %d but index is %d: Out of range\n",
__func__, size, byte);
}
}
static void wcd9xxx_irq_disable(struct irq_data *data)
{
struct wcd9xxx_core_resource *wcd9xxx_res =
irq_data_get_irq_chip_data(data);
int wcd9xxx_irq = virq_to_phyirq(wcd9xxx_res, data->irq);
int byte = BIT_BYTE(wcd9xxx_irq);
int size = ARRAY_SIZE(wcd9xxx_res->irq_masks_cur);
if ((byte < size) && (byte >= 0)) {
wcd9xxx_res->irq_masks_cur[byte]
|= BYTE_BIT_MASK(wcd9xxx_irq);
} else {
pr_err("%s: Array size is %d but index is %d: Out of range\n",
__func__, size, byte);
}
}
static void wcd9xxx_irq_ack(struct irq_data *data)
{
int wcd9xxx_irq = 0;
struct wcd9xxx_core_resource *wcd9xxx_res =
irq_data_get_irq_chip_data(data);
if (wcd9xxx_res == NULL) {
pr_err("%s: wcd9xxx_res is NULL\n", __func__);
return;
}
wcd9xxx_irq = virq_to_phyirq(wcd9xxx_res, data->irq);
pr_debug("%s: IRQ_ACK called for WCD9XXX IRQ: %d\n",
__func__, wcd9xxx_irq);
}
static void wcd9xxx_irq_mask(struct irq_data *d)
{
/* do nothing but required as linux calls irq_mask without NULL check */
}
static struct irq_chip wcd9xxx_irq_chip = {
.name = "wcd9xxx",
.irq_bus_lock = wcd9xxx_irq_lock,
.irq_bus_sync_unlock = wcd9xxx_irq_sync_unlock,
.irq_disable = wcd9xxx_irq_disable,
.irq_enable = wcd9xxx_irq_enable,
.irq_mask = wcd9xxx_irq_mask,
.irq_ack = wcd9xxx_irq_ack,
};
bool wcd9xxx_lock_sleep(
struct wcd9xxx_core_resource *wcd9xxx_res)
{
enum wcd9xxx_pm_state os;
/*
* wcd9xxx_{lock/unlock}_sleep will be called by wcd9xxx_irq_thread
* and its subroutines only motly.
* but btn0_lpress_fn is not wcd9xxx_irq_thread's subroutine and
* It can race with wcd9xxx_irq_thread.
* So need to embrace wlock_holders with mutex.
*
* If system didn't resume, we can simply return false so codec driver's
* IRQ handler can return without handling IRQ.
* As interrupt line is still active, codec will have another IRQ to
* retry shortly.
*/
mutex_lock(&wcd9xxx_res->pm_lock);
if (wcd9xxx_res->wlock_holders++ == 0) {
pr_debug("%s: holding wake lock\n", __func__);
pm_qos_update_request(&wcd9xxx_res->pm_qos_req,
msm_cpuidle_get_deep_idle_latency());
pm_stay_awake(wcd9xxx_res->dev);
}
mutex_unlock(&wcd9xxx_res->pm_lock);
if (!wait_event_timeout(wcd9xxx_res->pm_wq,
((os = wcd9xxx_pm_cmpxchg(wcd9xxx_res,
WCD9XXX_PM_SLEEPABLE,
WCD9XXX_PM_AWAKE)) ==
WCD9XXX_PM_SLEEPABLE ||
(os == WCD9XXX_PM_AWAKE)),
msecs_to_jiffies(
WCD9XXX_SYSTEM_RESUME_TIMEOUT_MS))) {
pr_warn("%s: system didn't resume within %dms, s %d, w %d\n",
__func__,
WCD9XXX_SYSTEM_RESUME_TIMEOUT_MS, wcd9xxx_res->pm_state,
wcd9xxx_res->wlock_holders);
wcd9xxx_unlock_sleep(wcd9xxx_res);
return false;
}
wake_up_all(&wcd9xxx_res->pm_wq);
return true;
}
EXPORT_SYMBOL(wcd9xxx_lock_sleep);
void wcd9xxx_unlock_sleep(
struct wcd9xxx_core_resource *wcd9xxx_res)
{
mutex_lock(&wcd9xxx_res->pm_lock);
if (--wcd9xxx_res->wlock_holders == 0) {
pr_debug("%s: releasing wake lock pm_state %d -> %d\n",
__func__, wcd9xxx_res->pm_state, WCD9XXX_PM_SLEEPABLE);
/*
* if wcd9xxx_lock_sleep failed, pm_state would be still
* WCD9XXX_PM_ASLEEP, don't overwrite
*/
if (likely(wcd9xxx_res->pm_state == WCD9XXX_PM_AWAKE))
wcd9xxx_res->pm_state = WCD9XXX_PM_SLEEPABLE;
pm_qos_update_request(&wcd9xxx_res->pm_qos_req,
PM_QOS_DEFAULT_VALUE);
pm_relax(wcd9xxx_res->dev);
}
mutex_unlock(&wcd9xxx_res->pm_lock);
wake_up_all(&wcd9xxx_res->pm_wq);
}
EXPORT_SYMBOL(wcd9xxx_unlock_sleep);
void wcd9xxx_nested_irq_lock(struct wcd9xxx_core_resource *wcd9xxx_res)
{
mutex_lock(&wcd9xxx_res->nested_irq_lock);
}
void wcd9xxx_nested_irq_unlock(struct wcd9xxx_core_resource *wcd9xxx_res)
{
mutex_unlock(&wcd9xxx_res->nested_irq_lock);
}
static void wcd9xxx_irq_dispatch(struct wcd9xxx_core_resource *wcd9xxx_res,
struct intr_data *irqdata)
{
int irqbit = irqdata->intr_num;
if (!wcd9xxx_res->wcd_core_regmap) {
pr_err("%s: codec core regmap not defined\n",
__func__);
return;
}
if (irqdata->clear_first) {
wcd9xxx_nested_irq_lock(wcd9xxx_res);
regmap_write(wcd9xxx_res->wcd_core_regmap,
wcd9xxx_res->intr_reg[WCD9XXX_INTR_CLEAR_BASE] +
BIT_BYTE(irqbit),
BYTE_BIT_MASK(irqbit));
if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C)
regmap_write(wcd9xxx_res->wcd_core_regmap,
wcd9xxx_res->intr_reg[WCD9XXX_INTR_CLR_COMMIT],
0x02);
handle_nested_irq(phyirq_to_virq(wcd9xxx_res, irqbit));
wcd9xxx_nested_irq_unlock(wcd9xxx_res);
} else {
wcd9xxx_nested_irq_lock(wcd9xxx_res);
handle_nested_irq(phyirq_to_virq(wcd9xxx_res, irqbit));
regmap_write(wcd9xxx_res->wcd_core_regmap,
wcd9xxx_res->intr_reg[WCD9XXX_INTR_CLEAR_BASE] +
BIT_BYTE(irqbit),
BYTE_BIT_MASK(irqbit));
if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C)
regmap_write(wcd9xxx_res->wcd_core_regmap,
wcd9xxx_res->intr_reg[WCD9XXX_INTR_CLR_COMMIT],
0x02);
wcd9xxx_nested_irq_unlock(wcd9xxx_res);
}
}
static irqreturn_t wcd9xxx_irq_thread(int irq, void *data)
{
int ret;
int i;
struct intr_data irqdata;
char linebuf[128];
static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 1);
struct wcd9xxx_core_resource *wcd9xxx_res = data;
int num_irq_regs = wcd9xxx_res->num_irq_regs;
u8 status[4], status1[4] = {0}, unmask_status[4] = {0};
if (unlikely(wcd9xxx_lock_sleep(wcd9xxx_res) == false)) {
dev_err(wcd9xxx_res->dev, "Failed to hold suspend\n");
return IRQ_NONE;
}
if (!wcd9xxx_res->wcd_core_regmap) {
dev_err(wcd9xxx_res->dev,
"%s: Codec core regmap not supplied\n",
__func__);
goto err_disable_irq;
}
memset(status, 0, sizeof(status));
ret = regmap_bulk_read(wcd9xxx_res->wcd_core_regmap,
wcd9xxx_res->intr_reg[WCD9XXX_INTR_STATUS_BASE],
status, num_irq_regs);
if (ret < 0) {
dev_err(wcd9xxx_res->dev,
"Failed to read interrupt status: %d\n", ret);
goto err_disable_irq;
}
/*
* If status is 0 return without clearing.
* status contains: HW status - masked interrupts
* status1 contains: unhandled interrupts - masked interrupts
* unmasked_status contains: unhandled interrupts
*/
if (unlikely(!memcmp(status, status1, sizeof(status)))) {
pr_debug("%s: status is 0\n", __func__);
wcd9xxx_unlock_sleep(wcd9xxx_res);
return IRQ_HANDLED;
}
/*
* Copy status to unmask_status before masking, otherwise SW may miss
* to clear masked interrupt in corner case.
*/
memcpy(unmask_status, status, sizeof(unmask_status));
/* Apply masking */
for (i = 0; i < num_irq_regs; i++)
status[i] &= ~wcd9xxx_res->irq_masks_cur[i];
memcpy(status1, status, sizeof(status1));
/* Find out which interrupt was triggered and call that interrupt's
* handler function
*
* Since codec has only one hardware irq line which is shared by
* codec's different internal interrupts, so it's possible master irq
* handler dispatches multiple nested irq handlers after breaking
* order. Dispatch interrupts in the order that is maintained by
* the interrupt table.
*/
for (i = 0; i < wcd9xxx_res->intr_table_size; i++) {
irqdata = wcd9xxx_res->intr_table[i];
if (status[BIT_BYTE(irqdata.intr_num)] &
BYTE_BIT_MASK(irqdata.intr_num)) {
wcd9xxx_irq_dispatch(wcd9xxx_res, &irqdata);
status1[BIT_BYTE(irqdata.intr_num)] &=
~BYTE_BIT_MASK(irqdata.intr_num);
unmask_status[BIT_BYTE(irqdata.intr_num)] &=
~BYTE_BIT_MASK(irqdata.intr_num);
}
}
/*
* As a failsafe if unhandled irq is found, clear it to prevent
* interrupt storm.
* Note that we can say there was an unhandled irq only when no irq
* handled by nested irq handler since Taiko supports qdsp as irqs'
* destination for few irqs. Therefore driver shouldn't clear pending
* irqs when few handled while few others not.
*/
if (unlikely(!memcmp(status, status1, sizeof(status)))) {
if (__ratelimit(&ratelimit)) {
pr_warn("%s: Unhandled irq found\n", __func__);
hex_dump_to_buffer(status, sizeof(status), 16, 1,
linebuf, sizeof(linebuf), false);
pr_warn("%s: status0 : %s\n", __func__, linebuf);
hex_dump_to_buffer(status1, sizeof(status1), 16, 1,
linebuf, sizeof(linebuf), false);
pr_warn("%s: status1 : %s\n", __func__, linebuf);
}
/*
* unmask_status contains unhandled interrupts, hence clear all
* unhandled interrupts.
*/
ret = regmap_bulk_write(wcd9xxx_res->wcd_core_regmap,
wcd9xxx_res->intr_reg[WCD9XXX_INTR_CLEAR_BASE],
unmask_status, num_irq_regs);
if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C)
regmap_write(wcd9xxx_res->wcd_core_regmap,
wcd9xxx_res->intr_reg[WCD9XXX_INTR_CLR_COMMIT],
0x02);
}
wcd9xxx_unlock_sleep(wcd9xxx_res);
return IRQ_HANDLED;
err_disable_irq:
dev_err(wcd9xxx_res->dev,
"Disable irq %d\n", wcd9xxx_res->irq);
disable_irq_wake(wcd9xxx_res->irq);
disable_irq_nosync(wcd9xxx_res->irq);
wcd9xxx_unlock_sleep(wcd9xxx_res);
return IRQ_NONE;
}
/**
* wcd9xxx_free_irq
*
* @wcd9xxx_res: pointer to core resource
* irq: irq number
* @data: data pointer
*
*/
void wcd9xxx_free_irq(struct wcd9xxx_core_resource *wcd9xxx_res,
int irq, void *data)
{
free_irq(phyirq_to_virq(wcd9xxx_res, irq), data);
}
EXPORT_SYMBOL(wcd9xxx_free_irq);
/**
* wcd9xxx_enable_irq
*
* @wcd9xxx_res: pointer to core resource
* irq: irq number
*
*/
void wcd9xxx_enable_irq(struct wcd9xxx_core_resource *wcd9xxx_res, int irq)
{
if (wcd9xxx_res->irq)
enable_irq(phyirq_to_virq(wcd9xxx_res, irq));
}
EXPORT_SYMBOL(wcd9xxx_enable_irq);
/**
* wcd9xxx_disable_irq
*
* @wcd9xxx_res: pointer to core resource
* irq: irq number
*
*/
void wcd9xxx_disable_irq(struct wcd9xxx_core_resource *wcd9xxx_res, int irq)
{
if (wcd9xxx_res->irq)
disable_irq_nosync(phyirq_to_virq(wcd9xxx_res, irq));
}
EXPORT_SYMBOL(wcd9xxx_disable_irq);
/**
* wcd9xxx_disable_irq_sync
*
* @wcd9xxx_res: pointer to core resource
* irq: irq number
*
*/
void wcd9xxx_disable_irq_sync(
struct wcd9xxx_core_resource *wcd9xxx_res, int irq)
{
if (wcd9xxx_res->irq)
disable_irq(phyirq_to_virq(wcd9xxx_res, irq));
}
EXPORT_SYMBOL(wcd9xxx_disable_irq_sync);
static int wcd9xxx_irq_setup_downstream_irq(
struct wcd9xxx_core_resource *wcd9xxx_res)
{
int irq, virq, ret;
pr_debug("%s: enter\n", __func__);
for (irq = 0; irq < wcd9xxx_res->num_irqs; irq++) {
/* Map OF irq */
virq = wcd9xxx_map_irq(wcd9xxx_res, irq);
pr_debug("%s: irq %d -> %d\n", __func__, irq, virq);
if (virq == NO_IRQ) {
pr_err("%s, No interrupt specifier for irq %d\n",
__func__, irq);
return NO_IRQ;
}
ret = irq_set_chip_data(virq, wcd9xxx_res);
if (ret) {
pr_err("%s: Failed to configure irq %d (%d)\n",
__func__, irq, ret);
return ret;
}
if (wcd9xxx_res->irq_level_high[irq])
irq_set_chip_and_handler(virq, &wcd9xxx_irq_chip,
handle_level_irq);
else
irq_set_chip_and_handler(virq, &wcd9xxx_irq_chip,
handle_edge_irq);
irq_set_nested_thread(virq, 1);
}
pr_debug("%s: leave\n", __func__);
return 0;
}
/**
* wcd9xxx_irq_init
*
* @wcd9xxx_res: pointer to core resource
*
* Returns 0 on success, appropriate error code otherwise
*/
int wcd9xxx_irq_init(struct wcd9xxx_core_resource *wcd9xxx_res)
{
int i, ret;
u8 irq_level[wcd9xxx_res->num_irq_regs];
struct irq_domain *domain;
struct device_node *pnode;
mutex_init(&wcd9xxx_res->irq_lock);
mutex_init(&wcd9xxx_res->nested_irq_lock);
pnode = of_irq_find_parent(wcd9xxx_res->dev->of_node);
if (unlikely(!pnode))
return -EINVAL;
domain = irq_find_host(pnode);
if (unlikely(!domain))
return -EINVAL;
wcd9xxx_res->domain = domain;
wcd9xxx_res->irq = wcd9xxx_irq_get_upstream_irq(wcd9xxx_res);
if (!wcd9xxx_res->irq) {
pr_warn("%s: irq driver is not yet initialized\n", __func__);
mutex_destroy(&wcd9xxx_res->irq_lock);
mutex_destroy(&wcd9xxx_res->nested_irq_lock);
return -EPROBE_DEFER;
}
pr_debug("%s: probed irq %d\n", __func__, wcd9xxx_res->irq);
/* Setup downstream IRQs */
ret = wcd9xxx_irq_setup_downstream_irq(wcd9xxx_res);
if (ret) {
pr_err("%s: Failed to setup downstream IRQ\n", __func__);
wcd9xxx_irq_put_upstream_irq(wcd9xxx_res);
mutex_destroy(&wcd9xxx_res->irq_lock);
mutex_destroy(&wcd9xxx_res->nested_irq_lock);
return ret;
}
/* All other wcd9xxx interrupts are edge triggered */
wcd9xxx_res->irq_level_high[0] = true;
/* mask all the interrupts */
memset(irq_level, 0, wcd9xxx_res->num_irq_regs);
for (i = 0; i < wcd9xxx_res->num_irqs; i++) {
wcd9xxx_res->irq_masks_cur[BIT_BYTE(i)] |= BYTE_BIT_MASK(i);
wcd9xxx_res->irq_masks_cache[BIT_BYTE(i)] |= BYTE_BIT_MASK(i);
irq_level[BIT_BYTE(i)] |=
wcd9xxx_res->irq_level_high[i] << (i % BITS_PER_BYTE);
}
if (!wcd9xxx_res->wcd_core_regmap) {
dev_err(wcd9xxx_res->dev,
"%s: Codec core regmap not defined\n",
__func__);
ret = -EINVAL;
goto fail_irq_init;
}
for (i = 0; i < wcd9xxx_res->num_irq_regs; i++) {
/* Initialize interrupt mask and level registers */
regmap_write(wcd9xxx_res->wcd_core_regmap,
wcd9xxx_res->intr_reg[WCD9XXX_INTR_LEVEL_BASE] + i,
irq_level[i]);
regmap_write(wcd9xxx_res->wcd_core_regmap,
wcd9xxx_res->intr_reg[WCD9XXX_INTR_MASK_BASE] + i,
wcd9xxx_res->irq_masks_cur[i]);
}
ret = request_threaded_irq(wcd9xxx_res->irq, NULL, wcd9xxx_irq_thread,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"wcd9xxx", wcd9xxx_res);
if (ret != 0)
dev_err(wcd9xxx_res->dev, "Failed to request IRQ %d: %d\n",
wcd9xxx_res->irq, ret);
else {
ret = enable_irq_wake(wcd9xxx_res->irq);
if (ret)
dev_err(wcd9xxx_res->dev,
"Failed to set wake interrupt on IRQ %d: %d\n",
wcd9xxx_res->irq, ret);
if (ret)
free_irq(wcd9xxx_res->irq, wcd9xxx_res);
}
if (ret)
goto fail_irq_init;
return ret;
fail_irq_init:
dev_err(wcd9xxx_res->dev,
"%s: Failed to init wcd9xxx irq\n", __func__);
wcd9xxx_irq_put_upstream_irq(wcd9xxx_res);
mutex_destroy(&wcd9xxx_res->irq_lock);
mutex_destroy(&wcd9xxx_res->nested_irq_lock);
return ret;
}
EXPORT_SYMBOL(wcd9xxx_irq_init);
int wcd9xxx_request_irq(struct wcd9xxx_core_resource *wcd9xxx_res,
int irq, irq_handler_t handler,
const char *name, void *data)
{
int virq;
virq = phyirq_to_virq(wcd9xxx_res, irq);
return request_threaded_irq(virq, NULL, handler, IRQF_TRIGGER_RISING,
name, data);
}
EXPORT_SYMBOL(wcd9xxx_request_irq);
void wcd9xxx_irq_exit(struct wcd9xxx_core_resource *wcd9xxx_res)
{
dev_dbg(wcd9xxx_res->dev, "%s: Cleaning up irq %d\n", __func__,
wcd9xxx_res->irq);
if (wcd9xxx_res->irq) {
disable_irq_wake(wcd9xxx_res->irq);
free_irq(wcd9xxx_res->irq, wcd9xxx_res);
wcd9xxx_res->irq = 0;
wcd9xxx_irq_put_upstream_irq(wcd9xxx_res);
}
mutex_destroy(&wcd9xxx_res->irq_lock);
mutex_destroy(&wcd9xxx_res->nested_irq_lock);
}
#ifndef CONFIG_OF
static int phyirq_to_virq(
struct wcd9xxx_core_resource *wcd9xxx_res,
int offset)
{
return wcd9xxx_res->irq_base + offset;
}
static int virq_to_phyirq(
struct wcd9xxx_core_resource *wcd9xxx_res,
int virq)
{
return virq - wcd9xxx_res->irq_base;
}
static unsigned int wcd9xxx_irq_get_upstream_irq(
struct wcd9xxx_core_resource *wcd9xxx_res)
{
return wcd9xxx_res->irq;
}
static void wcd9xxx_irq_put_upstream_irq(
struct wcd9xxx_core_resource *wcd9xxx_res)
{
/* Do nothing */
}
static int wcd9xxx_map_irq(
struct wcd9xxx_core_resource *wcd9xxx_core_res, int irq)
{
return phyirq_to_virq(wcd9xxx_core_res, irq);
}
#else
static struct wcd9xxx_irq_drv_data *
wcd9xxx_irq_add_domain(struct device_node *node,
struct device_node *parent)
{
struct wcd9xxx_irq_drv_data *data = NULL;
pr_debug("%s: node %s, node parent %s\n", __func__,
node->name, node->parent->name);
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return NULL;
/*
* wcd9xxx_intc interrupt controller supports N to N irq mapping with
* single cell binding with irq numbers(offsets) only.
* Use irq_domain_simple_ops that has irq_domain_simple_map and
* irq_domain_xlate_onetwocell.
*/
data->domain = irq_domain_add_linear(node, WCD9XXX_MAX_NUM_IRQS,
&irq_domain_simple_ops, data);
if (!data->domain) {
kfree(data);
return NULL;
}
return data;
}
static struct wcd9xxx_irq_drv_data *
wcd9xxx_get_irq_drv_d(const struct wcd9xxx_core_resource *wcd9xxx_res)
{
struct irq_domain *domain;
domain = wcd9xxx_res->domain;
if (domain)
return domain->host_data;
else
return NULL;
}
static int phyirq_to_virq(struct wcd9xxx_core_resource *wcd9xxx_res, int offset)
{
struct wcd9xxx_irq_drv_data *data;
data = wcd9xxx_get_irq_drv_d(wcd9xxx_res);
if (!data) {
pr_warn("%s: not registered to interrupt controller\n",
__func__);
return -EINVAL;
}
return irq_linear_revmap(data->domain, offset);
}
static int virq_to_phyirq(struct wcd9xxx_core_resource *wcd9xxx_res, int virq)
{
struct irq_data *irq_data = irq_get_irq_data(virq);
if (unlikely(!irq_data)) {
pr_err("%s: irq_data is NULL", __func__);
return -EINVAL;
}
return irq_data->hwirq;
}
static unsigned int wcd9xxx_irq_get_upstream_irq(
struct wcd9xxx_core_resource *wcd9xxx_res)
{
struct wcd9xxx_irq_drv_data *data;
data = wcd9xxx_get_irq_drv_d(wcd9xxx_res);
if (!data) {
pr_err("%s: interrupt controller is not registered\n",
__func__);
return 0;
}
/* Make sure data is updated before return. */
rmb();
return data->irq;
}
static void wcd9xxx_irq_put_upstream_irq(
struct wcd9xxx_core_resource *wcd9xxx_res)
{
wcd9xxx_res->domain = NULL;
}
static int wcd9xxx_map_irq(struct wcd9xxx_core_resource *wcd9xxx_res, int irq)
{
return of_irq_to_resource(wcd9xxx_res->dev->of_node, irq, NULL);
}
static int wcd9xxx_irq_probe(struct platform_device *pdev)
{
int irq, dir_apps_irq = -EINVAL;
struct wcd9xxx_irq_drv_data *data;
struct device_node *node = pdev->dev.of_node;
int ret = -EINVAL;
irq = of_get_named_gpio(node, "qcom,gpio-connect", 0);
if (!gpio_is_valid(irq))
dir_apps_irq = platform_get_irq_byname(pdev, "wcd_irq");
if (!gpio_is_valid(irq) && dir_apps_irq < 0) {
dev_err(&pdev->dev, "TLMM connect gpio not found\n");
return -EPROBE_DEFER;
}
if (dir_apps_irq > 0) {
irq = dir_apps_irq;
} else {
irq = gpio_to_irq(irq);
if (irq < 0) {
dev_err(&pdev->dev, "Unable to configure irq\n");
return irq;
}
}
dev_dbg(&pdev->dev, "%s: virq = %d\n", __func__, irq);
data = wcd9xxx_irq_add_domain(node, node->parent);
if (!data) {
pr_err("%s: irq_add_domain failed\n", __func__);
return -EINVAL;
}
data->irq = irq;
/* Make sure irq is saved before return. */
wmb();
ret = 0;
return ret;
}
static int wcd9xxx_irq_remove(struct platform_device *pdev)
{
struct irq_domain *domain;
struct wcd9xxx_irq_drv_data *data;
domain = irq_find_host(pdev->dev.of_node);
if (unlikely(!domain)) {
pr_err("%s: domain is NULL", __func__);
return -EINVAL;
}
data = (struct wcd9xxx_irq_drv_data *)domain->host_data;
data->irq = 0;
/* Make sure irq variable is updated in data, before irq removal. */
wmb();
irq_domain_remove(data->domain);
kfree(data);
domain->host_data = NULL;
return 0;
}
static const struct of_device_id of_match[] = {
{ .compatible = "qcom,wcd9xxx-irq" },
{ }
};
static struct platform_driver wcd9xxx_irq_driver = {
.probe = wcd9xxx_irq_probe,
.remove = wcd9xxx_irq_remove,
.driver = {
.name = "wcd9xxx_intc",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_match),
},
};
int wcd9xxx_irq_drv_init(void)
{
return platform_driver_register(&wcd9xxx_irq_driver);
}
void wcd9xxx_irq_drv_exit(void)
{
platform_driver_unregister(&wcd9xxx_irq_driver);
}
#endif /* CONFIG_OF */

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _WCD9XXX_REGMAP_
#define _WCD9XXX_REGMAP_
#include <linux/regmap.h>
#include <linux/mfd/wcd9xxx/core.h>
typedef int (*regmap_patch_fptr)(struct regmap *, int);
extern struct regmap_config wcd934x_regmap_config;
extern int wcd934x_regmap_register_patch(struct regmap *regmap,
int version);
extern struct regmap_config wcd9335_regmap_config;
extern int wcd9335_regmap_register_patch(struct regmap *regmap,
int version);
static inline struct regmap_config *wcd9xxx_get_regmap_config(int type)
{
struct regmap_config *regmap_config;
switch (type) {
case WCD934X:
regmap_config = &wcd934x_regmap_config;
break;
case WCD9335:
regmap_config = &wcd9335_regmap_config;
break;
default:
regmap_config = NULL;
break;
};
return regmap_config;
}
static inline regmap_patch_fptr wcd9xxx_get_regmap_reg_patch(int type)
{
regmap_patch_fptr apply_patch;
switch (type) {
case WCD9335:
apply_patch = wcd9335_regmap_register_patch;
break;
case WCD934X:
apply_patch = wcd934x_regmap_register_patch;
break;
default:
apply_patch = NULL;
break;
}
return apply_patch;
}
#endif

443
drivers/mfd/wcd9xxx-rst.c Normal file
View file

@ -0,0 +1,443 @@
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/delay.h>
#include <linux/mfd/wcd9xxx/pdata.h>
#include <linux/mfd/wcd9xxx/core.h>
#include <linux/mfd/wcd9xxx/wcd9xxx-utils.h>
#include <linux/mfd/wcd9335/registers.h>
#include <linux/mfd/wcd934x/registers.h>
#include <linux/mfd/wcd9335/irq.h>
#include <linux/mfd/wcd934x/irq.h>
/* wcd9335 interrupt table */
static const struct intr_data wcd9335_intr_table[] = {
{WCD9XXX_IRQ_SLIMBUS, false},
{WCD9335_IRQ_MBHC_SW_DET, true},
{WCD9335_IRQ_MBHC_BUTTON_PRESS_DET, true},
{WCD9335_IRQ_MBHC_BUTTON_RELEASE_DET, true},
{WCD9335_IRQ_MBHC_ELECT_INS_REM_DET, true},
{WCD9335_IRQ_MBHC_ELECT_INS_REM_LEG_DET, true},
{WCD9335_IRQ_FLL_LOCK_LOSS, false},
{WCD9335_IRQ_HPH_PA_CNPL_COMPLETE, false},
{WCD9335_IRQ_HPH_PA_CNPR_COMPLETE, false},
{WCD9335_IRQ_EAR_PA_CNP_COMPLETE, false},
{WCD9335_IRQ_LINE_PA1_CNP_COMPLETE, false},
{WCD9335_IRQ_LINE_PA2_CNP_COMPLETE, false},
{WCD9335_IRQ_LINE_PA3_CNP_COMPLETE, false},
{WCD9335_IRQ_LINE_PA4_CNP_COMPLETE, false},
{WCD9335_IRQ_HPH_PA_OCPL_FAULT, false},
{WCD9335_IRQ_HPH_PA_OCPR_FAULT, false},
{WCD9335_IRQ_EAR_PA_OCP_FAULT, false},
{WCD9335_IRQ_SOUNDWIRE, false},
{WCD9335_IRQ_VDD_DIG_RAMP_COMPLETE, false},
{WCD9335_IRQ_RCO_ERROR, false},
{WCD9335_IRQ_SVA_ERROR, false},
{WCD9335_IRQ_MAD_AUDIO, false},
{WCD9335_IRQ_MAD_BEACON, false},
{WCD9335_IRQ_SVA_OUTBOX1, true},
{WCD9335_IRQ_SVA_OUTBOX2, true},
{WCD9335_IRQ_MAD_ULTRASOUND, false},
{WCD9335_IRQ_VBAT_ATTACK, false},
{WCD9335_IRQ_VBAT_RESTORE, false},
};
static const struct intr_data wcd934x_intr_table[] = {
{WCD9XXX_IRQ_SLIMBUS, false},
{WCD934X_IRQ_MBHC_SW_DET, true},
{WCD934X_IRQ_MBHC_BUTTON_PRESS_DET, true},
{WCD934X_IRQ_MBHC_BUTTON_RELEASE_DET, true},
{WCD934X_IRQ_MBHC_ELECT_INS_REM_DET, true},
{WCD934X_IRQ_MBHC_ELECT_INS_REM_LEG_DET, true},
{WCD934X_IRQ_MISC, false},
{WCD934X_IRQ_HPH_PA_CNPL_COMPLETE, false},
{WCD934X_IRQ_HPH_PA_CNPR_COMPLETE, false},
{WCD934X_IRQ_EAR_PA_CNP_COMPLETE, false},
{WCD934X_IRQ_LINE_PA1_CNP_COMPLETE, false},
{WCD934X_IRQ_LINE_PA2_CNP_COMPLETE, false},
{WCD934X_IRQ_SLNQ_ANALOG_ERROR, false},
{WCD934X_IRQ_RESERVED_3, false},
{WCD934X_IRQ_HPH_PA_OCPL_FAULT, false},
{WCD934X_IRQ_HPH_PA_OCPR_FAULT, false},
{WCD934X_IRQ_EAR_PA_OCP_FAULT, false},
{WCD934X_IRQ_SOUNDWIRE, false},
{WCD934X_IRQ_VDD_DIG_RAMP_COMPLETE, false},
{WCD934X_IRQ_RCO_ERROR, false},
{WCD934X_IRQ_CPE_ERROR, false},
{WCD934X_IRQ_MAD_AUDIO, false},
{WCD934X_IRQ_MAD_BEACON, false},
{WCD934X_IRQ_CPE1_INTR, true},
{WCD934X_IRQ_RESERVED_4, false},
{WCD934X_IRQ_MAD_ULTRASOUND, false},
{WCD934X_IRQ_VBAT_ATTACK, false},
{WCD934X_IRQ_VBAT_RESTORE, false},
};
/*
* wcd9335_bring_down: Bringdown WCD Codec
*
* @wcd9xxx: Pointer to wcd9xxx structure
*
* Returns 0 for success or negative error code for failure
*/
static int wcd9335_bring_down(struct wcd9xxx *wcd9xxx)
{
if (!wcd9xxx || !wcd9xxx->regmap)
return -EINVAL;
regmap_write(wcd9xxx->regmap, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
0x04);
return 0;
}
/*
* wcd9335_bring_up: Bringup WCD Codec
*
* @wcd9xxx: Pointer to the wcd9xxx structure
*
* Returns 0 for success or negative error code for failure
*/
static int wcd9335_bring_up(struct wcd9xxx *wcd9xxx)
{
int ret = 0;
int val, byte0;
struct regmap *wcd_regmap;
if (!wcd9xxx)
return -EINVAL;
if (!wcd9xxx->regmap) {
dev_err(wcd9xxx->dev, "%s: wcd9xxx regmap is null!\n",
__func__);
return -EINVAL;
}
wcd_regmap = wcd9xxx->regmap;
regmap_read(wcd_regmap, WCD9335_CHIP_TIER_CTRL_EFUSE_VAL_OUT0, &val);
regmap_read(wcd_regmap, WCD9335_CHIP_TIER_CTRL_CHIP_ID_BYTE0, &byte0);
if ((val < 0) || (byte0 < 0)) {
dev_err(wcd9xxx->dev, "%s: tasha codec version detection fail!\n",
__func__);
return -EINVAL;
}
if ((val & 0x80) && (byte0 == 0x0)) {
dev_info(wcd9xxx->dev, "%s: wcd9335 codec version is v1.1\n",
__func__);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_RST_CTL, 0x01);
regmap_write(wcd_regmap, WCD9335_SIDO_SIDO_CCL_2, 0xFC);
regmap_write(wcd_regmap, WCD9335_SIDO_SIDO_CCL_4, 0x21);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
0x5);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
0x7);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
0x3);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_RST_CTL, 0x3);
} else if (byte0 == 0x1) {
dev_info(wcd9xxx->dev, "%s: wcd9335 codec version is v2.0\n",
__func__);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_RST_CTL, 0x01);
regmap_write(wcd_regmap, WCD9335_SIDO_SIDO_TEST_2, 0x00);
regmap_write(wcd_regmap, WCD9335_SIDO_SIDO_CCL_8, 0x6F);
regmap_write(wcd_regmap, WCD9335_BIAS_VBG_FINE_ADJ, 0x65);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
0x5);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
0x7);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
0x3);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_RST_CTL, 0x3);
} else if ((byte0 == 0) && (!(val & 0x80))) {
dev_info(wcd9xxx->dev, "%s: wcd9335 codec version is v1.0\n",
__func__);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_RST_CTL, 0x01);
regmap_write(wcd_regmap, WCD9335_SIDO_SIDO_CCL_2, 0xFC);
regmap_write(wcd_regmap, WCD9335_SIDO_SIDO_CCL_4, 0x21);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
0x3);
regmap_write(wcd_regmap, WCD9335_CODEC_RPM_RST_CTL, 0x3);
} else {
dev_err(wcd9xxx->dev, "%s: tasha codec version unknown\n",
__func__);
ret = -EINVAL;
}
return ret;
}
/*
* wcd9335_get_cdc_info: Get codec specific information
*
* @wcd9xxx: pointer to wcd9xxx structure
* @wcd_type: pointer to wcd9xxx_codec_type structure
*
* Returns 0 for success or negative error code for failure
*/
static int wcd9335_get_cdc_info(struct wcd9xxx *wcd9xxx,
struct wcd9xxx_codec_type *wcd_type)
{
u16 id_minor, id_major;
struct regmap *wcd_regmap;
int rc, val, version = 0;
if (!wcd9xxx || !wcd_type)
return -EINVAL;
if (!wcd9xxx->regmap) {
dev_err(wcd9xxx->dev, "%s: wcd9xxx regmap is null!\n",
__func__);
return -EINVAL;
}
wcd_regmap = wcd9xxx->regmap;
rc = regmap_bulk_read(wcd_regmap, WCD9335_CHIP_TIER_CTRL_CHIP_ID_BYTE0,
(u8 *)&id_minor, sizeof(u16));
if (rc)
return -EINVAL;
rc = regmap_bulk_read(wcd_regmap, WCD9335_CHIP_TIER_CTRL_CHIP_ID_BYTE2,
(u8 *)&id_major, sizeof(u16));
if (rc)
return -EINVAL;
dev_info(wcd9xxx->dev, "%s: wcd9xxx chip id major 0x%x, minor 0x%x\n",
__func__, id_major, id_minor);
/* Version detection */
if (id_major == TASHA_MAJOR) {
regmap_read(wcd_regmap, WCD9335_CHIP_TIER_CTRL_EFUSE_VAL_OUT0,
&val);
version = ((u8)val & 0x80) >> 7;
} else if (id_major == TASHA2P0_MAJOR)
version = 2;
else
dev_err(wcd9xxx->dev, "%s: wcd9335 version unknown (major 0x%x, minor 0x%x)\n",
__func__, id_major, id_minor);
/* Fill codec type info */
wcd_type->id_major = id_major;
wcd_type->id_minor = id_minor;
wcd_type->num_irqs = WCD9335_NUM_IRQS;
wcd_type->version = version;
wcd_type->slim_slave_type = WCD9XXX_SLIM_SLAVE_ADDR_TYPE_1;
wcd_type->i2c_chip_status = 0x01;
wcd_type->intr_tbl = wcd9335_intr_table;
wcd_type->intr_tbl_size = ARRAY_SIZE(wcd9335_intr_table);
wcd_type->intr_reg[WCD9XXX_INTR_STATUS_BASE] =
WCD9335_INTR_PIN1_STATUS0;
wcd_type->intr_reg[WCD9XXX_INTR_CLEAR_BASE] =
WCD9335_INTR_PIN1_CLEAR0;
wcd_type->intr_reg[WCD9XXX_INTR_MASK_BASE] =
WCD9335_INTR_PIN1_MASK0;
wcd_type->intr_reg[WCD9XXX_INTR_LEVEL_BASE] =
WCD9335_INTR_LEVEL0;
wcd_type->intr_reg[WCD9XXX_INTR_CLR_COMMIT] =
WCD9335_INTR_CLR_COMMIT;
return rc;
}
/*
* wcd934x_bring_down: Bringdown WCD Codec
*
* @wcd9xxx: Pointer to wcd9xxx structure
*
* Returns 0 for success or negative error code for failure
*/
static int wcd934x_bring_down(struct wcd9xxx *wcd9xxx)
{
if (!wcd9xxx || !wcd9xxx->regmap)
return -EINVAL;
regmap_write(wcd9xxx->regmap, WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
0x04);
return 0;
}
/*
* wcd934x_bring_up: Bringup WCD Codec
*
* @wcd9xxx: Pointer to the wcd9xxx structure
*
* Returns 0 for success or negative error code for failure
*/
static int wcd934x_bring_up(struct wcd9xxx *wcd9xxx)
{
struct regmap *wcd_regmap;
if (!wcd9xxx)
return -EINVAL;
if (!wcd9xxx->regmap) {
dev_err(wcd9xxx->dev, "%s: wcd9xxx regmap is null!\n",
__func__);
return -EINVAL;
}
wcd_regmap = wcd9xxx->regmap;
regmap_write(wcd_regmap, WCD934X_CODEC_RPM_RST_CTL, 0x01);
regmap_write(wcd_regmap, WCD934X_SIDO_NEW_VOUT_A_STARTUP, 0x19);
regmap_write(wcd_regmap, WCD934X_SIDO_NEW_VOUT_D_STARTUP, 0x15);
/* Add 1msec delay for VOUT to settle */
usleep_range(1000, 1100);
regmap_write(wcd_regmap, WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL, 0x5);
regmap_write(wcd_regmap, WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL, 0x7);
regmap_write(wcd_regmap, WCD934X_CODEC_RPM_RST_CTL, 0x3);
regmap_write(wcd_regmap, WCD934X_CODEC_RPM_RST_CTL, 0x7);
regmap_write(wcd_regmap, WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL, 0x3);
return 0;
}
/*
* wcd934x_get_cdc_info: Get codec specific information
*
* @wcd9xxx: pointer to wcd9xxx structure
* @wcd_type: pointer to wcd9xxx_codec_type structure
*
* Returns 0 for success or negative error code for failure
*/
static int wcd934x_get_cdc_info(struct wcd9xxx *wcd9xxx,
struct wcd9xxx_codec_type *wcd_type)
{
u16 id_minor, id_major;
struct regmap *wcd_regmap;
int rc, version = -1;
if (!wcd9xxx || !wcd_type)
return -EINVAL;
if (!wcd9xxx->regmap) {
dev_err(wcd9xxx->dev, "%s: wcd9xxx regmap is null\n", __func__);
return -EINVAL;
}
wcd_regmap = wcd9xxx->regmap;
rc = regmap_bulk_read(wcd_regmap, WCD934X_CHIP_TIER_CTRL_CHIP_ID_BYTE0,
(u8 *)&id_minor, sizeof(u16));
if (rc)
return -EINVAL;
rc = regmap_bulk_read(wcd_regmap, WCD934X_CHIP_TIER_CTRL_CHIP_ID_BYTE2,
(u8 *)&id_major, sizeof(u16));
if (rc)
return -EINVAL;
dev_info(wcd9xxx->dev, "%s: wcd9xxx chip id major 0x%x, minor 0x%x\n",
__func__, id_major, id_minor);
if (id_major != TAVIL_MAJOR)
goto version_unknown;
/*
* As fine version info cannot be retrieved before tavil probe.
* Assign coarse versions for possible future use before tavil probe.
*/
if (id_minor == cpu_to_le16(0))
version = TAVIL_VERSION_1_0;
else if (id_minor == cpu_to_le16(0x01))
version = TAVIL_VERSION_1_1;
version_unknown:
if (version < 0)
dev_err(wcd9xxx->dev, "%s: wcd934x version unknown\n",
__func__);
/* Fill codec type info */
wcd_type->id_major = id_major;
wcd_type->id_minor = id_minor;
wcd_type->num_irqs = WCD934X_NUM_IRQS;
wcd_type->version = version;
wcd_type->slim_slave_type = WCD9XXX_SLIM_SLAVE_ADDR_TYPE_1;
wcd_type->i2c_chip_status = 0x01;
wcd_type->intr_tbl = wcd934x_intr_table;
wcd_type->intr_tbl_size = ARRAY_SIZE(wcd934x_intr_table);
wcd_type->intr_reg[WCD9XXX_INTR_STATUS_BASE] =
WCD934X_INTR_PIN1_STATUS0;
wcd_type->intr_reg[WCD9XXX_INTR_CLEAR_BASE] =
WCD934X_INTR_PIN1_CLEAR0;
wcd_type->intr_reg[WCD9XXX_INTR_MASK_BASE] =
WCD934X_INTR_PIN1_MASK0;
wcd_type->intr_reg[WCD9XXX_INTR_LEVEL_BASE] =
WCD934X_INTR_LEVEL0;
wcd_type->intr_reg[WCD9XXX_INTR_CLR_COMMIT] =
WCD934X_INTR_CLR_COMMIT;
return rc;
}
codec_bringdown_fn wcd9xxx_bringdown_fn(int type)
{
codec_bringdown_fn cdc_bdown_fn;
switch (type) {
case WCD934X:
cdc_bdown_fn = wcd934x_bring_down;
break;
case WCD9335:
cdc_bdown_fn = wcd9335_bring_down;
break;
default:
cdc_bdown_fn = NULL;
break;
}
return cdc_bdown_fn;
}
codec_bringup_fn wcd9xxx_bringup_fn(int type)
{
codec_bringup_fn cdc_bup_fn;
switch (type) {
case WCD934X:
cdc_bup_fn = wcd934x_bring_up;
break;
case WCD9335:
cdc_bup_fn = wcd9335_bring_up;
break;
default:
cdc_bup_fn = NULL;
break;
}
return cdc_bup_fn;
}
codec_type_fn wcd9xxx_get_codec_info_fn(int type)
{
codec_type_fn cdc_type_fn;
switch (type) {
case WCD934X:
cdc_type_fn = wcd934x_get_cdc_info;
break;
case WCD9335:
cdc_type_fn = wcd9335_get_cdc_info;
break;
default:
cdc_type_fn = NULL;
break;
}
return cdc_type_fn;
}

View file

@ -0,0 +1,584 @@
/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/mfd/wcd9xxx/wcd9xxx-slimslave.h>
#include <linux/mfd/wcd9xxx/wcd9xxx_registers.h>
struct wcd9xxx_slim_sch {
u16 rx_port_ch_reg_base;
u16 port_tx_cfg_reg_base;
u16 port_rx_cfg_reg_base;
};
static struct wcd9xxx_slim_sch sh_ch;
static int wcd9xxx_alloc_slim_sh_ch(struct wcd9xxx *wcd9xxx,
u8 wcd9xxx_pgd_la, u32 cnt,
struct wcd9xxx_ch *channels, u32 path);
static int wcd9xxx_dealloc_slim_sh_ch(struct slim_device *slim,
u32 cnt, struct wcd9xxx_ch *channels);
static int wcd9xxx_configure_ports(struct wcd9xxx *wcd9xxx)
{
if (wcd9xxx->codec_type->slim_slave_type ==
WCD9XXX_SLIM_SLAVE_ADDR_TYPE_0) {
sh_ch.rx_port_ch_reg_base = 0x180;
sh_ch.port_rx_cfg_reg_base = 0x040;
sh_ch.port_tx_cfg_reg_base = 0x040;
} else {
sh_ch.rx_port_ch_reg_base =
0x180 - (TAIKO_SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS * 4);
sh_ch.port_rx_cfg_reg_base =
0x040 - TAIKO_SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS;
sh_ch.port_tx_cfg_reg_base = 0x050;
}
return 0;
}
/**
* wcd9xxx_init_slimslave
*
* @wcd9xxx: pointer to wcd9xxx struct
* @wcd9xxx_pgd_la: pgd_la value
* @tx_num: tx number
* @rx_num: rx number
* @tx_slot: pointer to tx slot
* @rx_slot: pointer to rx slot
*
* Returns 0 on success, appropriate error code otherwise
*/
int wcd9xxx_init_slimslave(struct wcd9xxx *wcd9xxx, u8 wcd9xxx_pgd_la,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot)
{
int ret = 0;
int i;
ret = wcd9xxx_configure_ports(wcd9xxx);
if (ret) {
pr_err("%s: Failed to configure register address offset\n",
__func__);
goto err;
}
if (!rx_num || rx_num > wcd9xxx->num_rx_port) {
pr_err("%s: invalid rx num %d\n", __func__, rx_num);
return -EINVAL;
}
if (wcd9xxx->rx_chs) {
wcd9xxx->num_rx_port = rx_num;
for (i = 0; i < rx_num; i++) {
wcd9xxx->rx_chs[i].ch_num = rx_slot[i];
INIT_LIST_HEAD(&wcd9xxx->rx_chs[i].list);
}
ret = wcd9xxx_alloc_slim_sh_ch(wcd9xxx, wcd9xxx_pgd_la,
wcd9xxx->num_rx_port,
wcd9xxx->rx_chs,
SLIM_SINK);
if (ret) {
pr_err("%s: Failed to alloc %d rx slimbus channels\n",
__func__, wcd9xxx->num_rx_port);
kfree(wcd9xxx->rx_chs);
wcd9xxx->rx_chs = NULL;
wcd9xxx->num_rx_port = 0;
}
} else {
pr_err("Not able to allocate memory for %d slimbus rx ports\n",
wcd9xxx->num_rx_port);
}
if (!tx_num || tx_num > wcd9xxx->num_tx_port) {
pr_err("%s: invalid tx num %d\n", __func__, tx_num);
return -EINVAL;
}
if (wcd9xxx->tx_chs) {
wcd9xxx->num_tx_port = tx_num;
for (i = 0; i < tx_num; i++) {
wcd9xxx->tx_chs[i].ch_num = tx_slot[i];
INIT_LIST_HEAD(&wcd9xxx->tx_chs[i].list);
}
ret = wcd9xxx_alloc_slim_sh_ch(wcd9xxx, wcd9xxx_pgd_la,
wcd9xxx->num_tx_port,
wcd9xxx->tx_chs,
SLIM_SRC);
if (ret) {
pr_err("%s: Failed to alloc %d tx slimbus channels\n",
__func__, wcd9xxx->num_tx_port);
kfree(wcd9xxx->tx_chs);
wcd9xxx->tx_chs = NULL;
wcd9xxx->num_tx_port = 0;
}
} else {
pr_err("Not able to allocate memory for %d slimbus tx ports\n",
wcd9xxx->num_tx_port);
}
return 0;
err:
return ret;
}
EXPORT_SYMBOL(wcd9xxx_init_slimslave);
int wcd9xxx_deinit_slimslave(struct wcd9xxx *wcd9xxx)
{
if (wcd9xxx->num_rx_port) {
wcd9xxx_dealloc_slim_sh_ch(wcd9xxx->slim,
wcd9xxx->num_rx_port,
wcd9xxx->rx_chs);
wcd9xxx->num_rx_port = 0;
}
if (wcd9xxx->num_tx_port) {
wcd9xxx_dealloc_slim_sh_ch(wcd9xxx->slim,
wcd9xxx->num_tx_port,
wcd9xxx->tx_chs);
wcd9xxx->num_tx_port = 0;
}
return 0;
}
static int wcd9xxx_alloc_slim_sh_ch(struct wcd9xxx *wcd9xxx,
u8 wcd9xxx_pgd_la, u32 cnt,
struct wcd9xxx_ch *channels, u32 path)
{
int ret = 0;
u32 ch_idx;
/* The slimbus channel allocation seem take longer time
* so do the allocation up front to avoid delay in start of
* playback
*/
pr_debug("%s: pgd_la[%d]\n", __func__, wcd9xxx_pgd_la);
for (ch_idx = 0; ch_idx < cnt; ch_idx++) {
ret = slim_get_slaveport(wcd9xxx_pgd_la,
channels[ch_idx].port,
&channels[ch_idx].sph, path);
pr_debug("%s: pgd_la[%d] channels[%d].port[%d]\n"
"channels[%d].sph[%d] path[%d]\n",
__func__, wcd9xxx_pgd_la, ch_idx,
channels[ch_idx].port,
ch_idx, channels[ch_idx].sph, path);
if (ret < 0) {
pr_err("%s: slave port failure id[%d] ret[%d]\n",
__func__, channels[ch_idx].ch_num, ret);
goto err;
}
ret = slim_query_ch(wcd9xxx->slim,
channels[ch_idx].ch_num,
&channels[ch_idx].ch_h);
if (ret < 0) {
pr_err("%s: slim_query_ch failed ch-num[%d] ret[%d]\n",
__func__, channels[ch_idx].ch_num, ret);
goto err;
}
}
err:
return ret;
}
static int wcd9xxx_dealloc_slim_sh_ch(struct slim_device *slim,
u32 cnt, struct wcd9xxx_ch *channels)
{
int idx = 0;
int ret = 0;
/* slim_dealloc_ch */
for (idx = 0; idx < cnt; idx++) {
ret = slim_dealloc_ch(slim, channels[idx].ch_h);
if (ret < 0) {
pr_err("%s: slim_dealloc_ch fail ret[%d] ch_h[%d]\n",
__func__, ret, channels[idx].ch_h);
}
}
return ret;
}
/* Enable slimbus slave device for RX path */
int wcd9xxx_cfg_slim_sch_rx(struct wcd9xxx *wcd9xxx,
struct list_head *wcd9xxx_ch_list,
unsigned int rate, unsigned int bit_width,
u16 *grph)
{
u8 ch_cnt = 0;
u16 ch_h[SLIM_MAX_RX_PORTS] = {0};
u8 payload = 0;
u16 codec_port = 0;
int ret;
struct slim_ch prop;
struct wcd9xxx_ch *rx;
int size = ARRAY_SIZE(ch_h);
/* Configure slave interface device */
list_for_each_entry(rx, wcd9xxx_ch_list, list) {
payload |= 1 << rx->shift;
if (ch_cnt < size) {
ch_h[ch_cnt] = rx->ch_h;
ch_cnt++;
pr_debug("list ch->ch_h %d ch->sph %d\n",
rx->ch_h, rx->sph);
} else {
pr_err("%s: allocated channel number %u is out of max rangae %d\n",
__func__, ch_cnt,
size);
ret = EINVAL;
goto err;
}
}
pr_debug("%s: ch_cnt[%d] rate=%d WATER_MARK_VAL %d\n",
__func__, ch_cnt, rate, WATER_MARK_VAL);
/* slim_define_ch api */
prop.prot = SLIM_AUTO_ISO;
if ((rate == 44100) || (rate == 88200) || (rate == 176400) ||
(rate == 352800)) {
prop.baser = SLIM_RATE_11025HZ;
prop.ratem = (rate/11025);
} else {
prop.baser = SLIM_RATE_4000HZ;
prop.ratem = (rate/4000);
}
prop.dataf = SLIM_CH_DATAF_NOT_DEFINED;
prop.auxf = SLIM_CH_AUXF_NOT_APPLICABLE;
prop.sampleszbits = bit_width;
pr_debug("Before slim_define_ch:\n"
"ch_cnt %d,ch_h[0] %d ch_h[1] %d, grph %d\n",
ch_cnt, ch_h[0], ch_h[1], *grph);
ret = slim_define_ch(wcd9xxx->slim, &prop, ch_h, ch_cnt,
true, grph);
if (ret < 0) {
pr_err("%s: slim_define_ch failed ret[%d]\n",
__func__, ret);
goto err;
}
list_for_each_entry(rx, wcd9xxx_ch_list, list) {
codec_port = rx->port;
pr_debug("%s: codec_port %d rx 0x%p, payload %d\n"
"sh_ch.rx_port_ch_reg_base0 0x%x\n"
"sh_ch.port_rx_cfg_reg_base 0x%x\n",
__func__, codec_port, rx, payload,
sh_ch.rx_port_ch_reg_base,
sh_ch.port_rx_cfg_reg_base);
/* look for the valid port range and chose the
* payload accordingly
*/
/* write to interface device */
ret = wcd9xxx_interface_reg_write(wcd9xxx,
SB_PGD_RX_PORT_MULTI_CHANNEL_0(
sh_ch.rx_port_ch_reg_base, codec_port),
payload);
if (ret < 0) {
pr_err("%s:Intf-dev fail reg[%d] payload[%d] ret[%d]\n",
__func__,
SB_PGD_RX_PORT_MULTI_CHANNEL_0(
sh_ch.rx_port_ch_reg_base, codec_port),
payload, ret);
goto err;
}
/* configure the slave port for water mark and enable*/
ret = wcd9xxx_interface_reg_write(wcd9xxx,
SB_PGD_PORT_CFG_BYTE_ADDR(
sh_ch.port_rx_cfg_reg_base, codec_port),
WATER_MARK_VAL);
if (ret < 0) {
pr_err("%s:watermark set failure for port[%d] ret[%d]",
__func__, codec_port, ret);
}
ret = slim_connect_sink(wcd9xxx->slim, &rx->sph, 1, rx->ch_h);
if (ret < 0) {
pr_err("%s: slim_connect_sink failed ret[%d]\n",
__func__, ret);
goto err_close_slim_sch;
}
}
/* slim_control_ch */
ret = slim_control_ch(wcd9xxx->slim, *grph, SLIM_CH_ACTIVATE,
true);
if (ret < 0) {
pr_err("%s: slim_control_ch failed ret[%d]\n",
__func__, ret);
goto err_close_slim_sch;
}
return 0;
err_close_slim_sch:
/* release all acquired handles */
wcd9xxx_close_slim_sch_rx(wcd9xxx, wcd9xxx_ch_list, *grph);
err:
return ret;
}
EXPORT_SYMBOL(wcd9xxx_cfg_slim_sch_rx);
/* Enable slimbus slave device for RX path */
int wcd9xxx_cfg_slim_sch_tx(struct wcd9xxx *wcd9xxx,
struct list_head *wcd9xxx_ch_list,
unsigned int rate, unsigned int bit_width,
u16 *grph)
{
u16 ch_cnt = 0;
u16 payload = 0;
u16 ch_h[SLIM_MAX_TX_PORTS] = {0};
u16 codec_port;
int ret = 0;
struct wcd9xxx_ch *tx;
int size = ARRAY_SIZE(ch_h);
struct slim_ch prop;
list_for_each_entry(tx, wcd9xxx_ch_list, list) {
payload |= 1 << tx->shift;
if (ch_cnt < size) {
ch_h[ch_cnt] = tx->ch_h;
ch_cnt++;
} else {
pr_err("%s: allocated channel number %u is out of max rangae %d\n",
__func__, ch_cnt,
size);
ret = EINVAL;
goto err;
}
}
/* slim_define_ch api */
prop.prot = SLIM_AUTO_ISO;
prop.baser = SLIM_RATE_4000HZ;
prop.dataf = SLIM_CH_DATAF_NOT_DEFINED;
prop.auxf = SLIM_CH_AUXF_NOT_APPLICABLE;
prop.ratem = (rate/4000);
prop.sampleszbits = bit_width;
ret = slim_define_ch(wcd9xxx->slim, &prop, ch_h, ch_cnt,
true, grph);
if (ret < 0) {
pr_err("%s: slim_define_ch failed ret[%d]\n",
__func__, ret);
goto err;
}
pr_debug("%s: ch_cnt[%d] rate[%d] bitwidth[%u]\n", __func__, ch_cnt,
rate, bit_width);
list_for_each_entry(tx, wcd9xxx_ch_list, list) {
codec_port = tx->port;
pr_debug("%s: codec_port %d tx 0x%p, payload 0x%x\n",
__func__, codec_port, tx, payload);
/* write to interface device */
ret = wcd9xxx_interface_reg_write(wcd9xxx,
SB_PGD_TX_PORT_MULTI_CHANNEL_0(codec_port),
payload & 0x00FF);
if (ret < 0) {
pr_err("%s:Intf-dev fail reg[%d] payload[%d] ret[%d]\n",
__func__,
SB_PGD_TX_PORT_MULTI_CHANNEL_0(codec_port),
payload, ret);
goto err;
}
/* ports 8,9 */
ret = wcd9xxx_interface_reg_write(wcd9xxx,
SB_PGD_TX_PORT_MULTI_CHANNEL_1(codec_port),
(payload & 0xFF00)>>8);
if (ret < 0) {
pr_err("%s:Intf-dev fail reg[%d] payload[%d] ret[%d]\n",
__func__,
SB_PGD_TX_PORT_MULTI_CHANNEL_1(codec_port),
payload, ret);
goto err;
}
/* configure the slave port for water mark and enable*/
ret = wcd9xxx_interface_reg_write(wcd9xxx,
SB_PGD_PORT_CFG_BYTE_ADDR(
sh_ch.port_tx_cfg_reg_base, codec_port),
WATER_MARK_VAL);
if (ret < 0) {
pr_err("%s:watermark set failure for port[%d] ret[%d]",
__func__, codec_port, ret);
}
ret = slim_connect_src(wcd9xxx->slim, tx->sph, tx->ch_h);
if (ret < 0) {
pr_err("%s: slim_connect_src failed ret[%d]\n",
__func__, ret);
goto err;
}
}
/* slim_control_ch */
ret = slim_control_ch(wcd9xxx->slim, *grph, SLIM_CH_ACTIVATE,
true);
if (ret < 0) {
pr_err("%s: slim_control_ch failed ret[%d]\n",
__func__, ret);
goto err;
}
return 0;
err:
/* release all acquired handles */
wcd9xxx_close_slim_sch_tx(wcd9xxx, wcd9xxx_ch_list, *grph);
return ret;
}
EXPORT_SYMBOL(wcd9xxx_cfg_slim_sch_tx);
int wcd9xxx_close_slim_sch_rx(struct wcd9xxx *wcd9xxx,
struct list_head *wcd9xxx_ch_list, u16 grph)
{
u32 sph[SLIM_MAX_RX_PORTS] = {0};
int ch_cnt = 0;
int ret = 0;
struct wcd9xxx_ch *rx;
list_for_each_entry(rx, wcd9xxx_ch_list, list)
sph[ch_cnt++] = rx->sph;
pr_debug("%s ch_cht %d, sph[0] %d sph[1] %d\n", __func__, ch_cnt,
sph[0], sph[1]);
/* slim_control_ch (REMOVE) */
pr_debug("%s before slim_control_ch grph %d\n", __func__, grph);
ret = slim_control_ch(wcd9xxx->slim, grph, SLIM_CH_REMOVE, true);
if (ret < 0) {
pr_err("%s: slim_control_ch failed ret[%d]\n", __func__, ret);
goto err;
}
err:
return ret;
}
EXPORT_SYMBOL(wcd9xxx_close_slim_sch_rx);
int wcd9xxx_close_slim_sch_tx(struct wcd9xxx *wcd9xxx,
struct list_head *wcd9xxx_ch_list,
u16 grph)
{
u32 sph[SLIM_MAX_TX_PORTS] = {0};
int ret = 0;
int ch_cnt = 0;
struct wcd9xxx_ch *tx;
pr_debug("%s\n", __func__);
list_for_each_entry(tx, wcd9xxx_ch_list, list)
sph[ch_cnt++] = tx->sph;
pr_debug("%s ch_cht %d, sph[0] %d sph[1] %d\n",
__func__, ch_cnt, sph[0], sph[1]);
/* slim_control_ch (REMOVE) */
ret = slim_control_ch(wcd9xxx->slim, grph, SLIM_CH_REMOVE, true);
if (ret < 0) {
pr_err("%s: slim_control_ch failed ret[%d]\n",
__func__, ret);
goto err;
}
err:
return ret;
}
EXPORT_SYMBOL(wcd9xxx_close_slim_sch_tx);
int wcd9xxx_get_slave_port(unsigned int ch_num)
{
int ret = 0;
ret = (ch_num - BASE_CH_NUM);
pr_debug("%s: ch_num[%d] slave port[%d]\n", __func__, ch_num, ret);
if (ret < 0) {
pr_err("%s: Error:- Invalid slave port found = %d\n",
__func__, ret);
return -EINVAL;
}
return ret;
}
EXPORT_SYMBOL(wcd9xxx_get_slave_port);
int wcd9xxx_disconnect_port(struct wcd9xxx *wcd9xxx,
struct list_head *wcd9xxx_ch_list, u16 grph)
{
u32 sph[SLIM_MAX_TX_PORTS + SLIM_MAX_RX_PORTS] = {0};
int ch_cnt = 0;
int ret = 0;
struct wcd9xxx_ch *slim_ch;
list_for_each_entry(slim_ch, wcd9xxx_ch_list, list)
sph[ch_cnt++] = slim_ch->sph;
/* slim_disconnect_port */
ret = slim_disconnect_ports(wcd9xxx->slim, sph, ch_cnt);
if (ret < 0) {
pr_err("%s: slim_disconnect_ports failed ret[%d]\n",
__func__, ret);
}
return ret;
}
EXPORT_SYMBOL(wcd9xxx_disconnect_port);
/* This function is called with mutex acquired */
int wcd9xxx_rx_vport_validation(u32 port_id,
struct list_head *codec_dai_list)
{
struct wcd9xxx_ch *ch;
int ret = 0;
pr_debug("%s: port_id %u\n", __func__, port_id);
list_for_each_entry(ch,
codec_dai_list, list) {
pr_debug("%s: ch->port %u\n", __func__, ch->port);
if (ch->port == port_id) {
ret = -EINVAL;
break;
}
}
return ret;
}
EXPORT_SYMBOL(wcd9xxx_rx_vport_validation);
/* This function is called with mutex acquired */
int wcd9xxx_tx_vport_validation(u32 table, u32 port_id,
struct wcd9xxx_codec_dai_data *codec_dai,
u32 num_codec_dais)
{
struct wcd9xxx_ch *ch;
int ret = 0;
u32 index;
unsigned long vtable = table;
u32 size = sizeof(table) * BITS_PER_BYTE;
pr_debug("%s: vtable 0x%lx port_id %u size %d\n", __func__,
vtable, port_id, size);
for_each_set_bit(index, &vtable, size) {
if (index < num_codec_dais) {
list_for_each_entry(ch,
&codec_dai[index].wcd9xxx_ch_list,
list) {
pr_debug("%s: index %u ch->port %u vtable 0x%lx\n",
__func__, index, ch->port,
vtable);
if (ch->port == port_id) {
pr_err("%s: TX%u is used by AIF%u_CAP Mixer\n",
__func__, port_id + 1,
(index + 1)/2);
ret = -EINVAL;
break;
}
}
} else {
pr_err("%s: Invalid index %d of codec dai",
__func__, index);
ret = -EINVAL;
}
if (ret)
break;
}
return ret;
}
EXPORT_SYMBOL(wcd9xxx_tx_vport_validation);

Some files were not shown because too many files have changed in this diff Show more