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:
parent
5ff0cfa736
commit
8e2277f79f
283 changed files with 250571 additions and 0 deletions
23
Makefile
Normal file
23
Makefile
Normal 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
24
NOTICE
Normal 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
7
drivers/Makefile
Normal 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
2
drivers/base/Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
obj-y += regmap/
|
||||
2
drivers/base/regmap/Makefile
Normal file
2
drivers/base/regmap/Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
obj-$(CONFIG_REGMAP_SWR) += regmap-swr.o
|
||||
1
drivers/base/regmap/internal.h
Symbolic link
1
drivers/base/regmap/internal.h
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../../drivers/base/regmap/internal.h
|
||||
216
drivers/base/regmap/regmap-swr.c
Normal file
216
drivers/base/regmap/regmap-swr.c
Normal 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, ®map_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, ®map_swr, &swr->dev, config,
|
||||
lock_key, lock_name);
|
||||
}
|
||||
EXPORT_SYMBOL(__devm_regmap_init_swr);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
8
drivers/mfd/Makefile
Normal file
8
drivers/mfd/Makefile
Normal 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
|
||||
|
||||
253
drivers/mfd/msm-cdc-pinctrl.c
Normal file
253
drivers/mfd/msm-cdc-pinctrl.c
Normal 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");
|
||||
456
drivers/mfd/msm-cdc-supply.c
Normal file
456
drivers/mfd/msm-cdc-supply.c
Normal 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
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
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
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
2155
drivers/mfd/wcd934x-tables.c
Normal file
File diff suppressed because it is too large
Load diff
55
drivers/mfd/wcd9xxx-core-init.c
Normal file
55
drivers/mfd/wcd9xxx-core-init.c
Normal 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
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
853
drivers/mfd/wcd9xxx-irq.c
Normal 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 */
|
||||
68
drivers/mfd/wcd9xxx-regmap.h
Normal file
68
drivers/mfd/wcd9xxx-regmap.h
Normal 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
443
drivers/mfd/wcd9xxx-rst.c
Normal 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;
|
||||
}
|
||||
|
||||
584
drivers/mfd/wcd9xxx-slimslave.c
Normal file
584
drivers/mfd/wcd9xxx-slimslave.c
Normal 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
Loading…
Reference in a new issue