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 - (74ff856e8d6: "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:
committed by
Martin Fick
parent
5ff0cfa736
commit
8e2277f79f
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);
|
||||
1198
drivers/mfd/wcd9xxx-utils.c
Normal file
1198
drivers/mfd/wcd9xxx-utils.c
Normal file
File diff suppressed because it is too large
Load Diff
5
drivers/misc/Makefile
Normal file
5
drivers/misc/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# Makefile for misc devices that really don't fit anywhere else.
|
||||
#
|
||||
|
||||
obj-y += qcom/
|
||||
19
drivers/misc/qcom/Kconfig
Normal file
19
drivers/misc/qcom/Kconfig
Normal file
@@ -0,0 +1,19 @@
|
||||
config MSM_QDSP6V2_CODECS
|
||||
bool "Audio QDSP6V2 APR support"
|
||||
select SND_SOC_QDSP6V2
|
||||
help
|
||||
Enable Audio codecs with APR IPC protocol support between
|
||||
application processor and QDSP6 for B-family. APR is
|
||||
used by audio driver to configure QDSP6's
|
||||
ASM, ADM and AFE.
|
||||
|
||||
config MSM_ULTRASOUND
|
||||
bool "QDSP6V2 HW Ultrasound support"
|
||||
select SND_SOC_QDSP6V2
|
||||
help
|
||||
Enable HW Ultrasound support in QDSP6V2.
|
||||
QDSP6V2 can support HW encoder & decoder and
|
||||
ultrasound processing. It will enable
|
||||
ultrasound data paths between
|
||||
HW and services, calculating input events
|
||||
upon the ultrasound data.
|
||||
1
drivers/misc/qcom/Makefile
Normal file
1
drivers/misc/qcom/Makefile
Normal file
@@ -0,0 +1 @@
|
||||
obj-y += qdsp6v2/
|
||||
6
drivers/misc/qcom/qdsp6v2/Makefile
Normal file
6
drivers/misc/qcom/qdsp6v2/Makefile
Normal file
@@ -0,0 +1,6 @@
|
||||
obj-$(CONFIG_MSM_QDSP6V2_CODECS) += aac_in.o qcelp_in.o evrc_in.o amrnb_in.o g711mlaw_in.o g711alaw_in.o audio_utils.o
|
||||
obj-$(CONFIG_MSM_QDSP6V2_CODECS) += audio_wma.o audio_wmapro.o audio_aac.o audio_multi_aac.o audio_alac.o audio_ape.o audio_utils_aio.o
|
||||
obj-$(CONFIG_MSM_QDSP6V2_CODECS) += q6audio_v2.o q6audio_v2_aio.o
|
||||
obj-$(CONFIG_MSM_QDSP6V2_CODECS) += audio_g711mlaw.o audio_g711alaw.o
|
||||
obj-$(CONFIG_MSM_QDSP6V2_CODECS) += audio_mp3.o audio_amrnb.o audio_amrwb.o audio_amrwbplus.o audio_evrc.o audio_qcelp.o amrwb_in.o audio_hwacc_effects.o
|
||||
obj-$(CONFIG_MSM_ULTRASOUND) += ultrasound/
|
||||
709
drivers/misc/qcom/qdsp6v2/aac_in.c
Normal file
709
drivers/misc/qcom/qdsp6v2/aac_in.c
Normal file
@@ -0,0 +1,709 @@
|
||||
/*
|
||||
* Copyright (c) 2010-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/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/msm_audio_aac.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include "audio_utils.h"
|
||||
|
||||
|
||||
/* Buffer with meta*/
|
||||
#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in))
|
||||
|
||||
/* Maximum 5 frames in buffer with meta */
|
||||
#define FRAME_SIZE (1 + ((1536+sizeof(struct meta_out_dsp)) * 5))
|
||||
|
||||
#define AAC_FORMAT_ADTS 65535
|
||||
|
||||
#define MAX_SAMPLE_RATE_384K 384000
|
||||
|
||||
static long aac_in_ioctl_shared(struct file *file, unsigned int cmd, void *arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
int cnt = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct msm_audio_aac_enc_config *enc_cfg;
|
||||
struct msm_audio_aac_config *aac_config;
|
||||
uint32_t aac_mode = AAC_ENC_MODE_AAC_LC;
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
aac_config = audio->codec_cfg;
|
||||
/* ENCODE CFG (after new set of API's are published )bharath*/
|
||||
pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__,
|
||||
audio->ac->session, audio->buf_alloc);
|
||||
if (audio->enabled == 1) {
|
||||
pr_info("%s:AUDIO_START already over\n", __func__);
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (audio->opened) {
|
||||
rc = audio_in_buf_alloc(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: buffer allocation failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
pr_debug("%s: starting in non_tunnel mode",
|
||||
__func__);
|
||||
rc = q6asm_open_read_write(audio->ac,
|
||||
FORMAT_MPEG4_AAC, FORMAT_LINEAR_PCM);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:open read write failed\n",
|
||||
__func__);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (audio->feedback == TUNNEL_MODE) {
|
||||
pr_debug("%s: starting in tunnel mode",
|
||||
__func__);
|
||||
rc = q6asm_open_read(audio->ac,
|
||||
FORMAT_MPEG4_AAC);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:open read failed\n",
|
||||
__func__);
|
||||
break;
|
||||
}
|
||||
}
|
||||
audio->stopped = 0;
|
||||
}
|
||||
|
||||
pr_debug("%s:sbr_ps_flag = %d, sbr_flag = %d\n", __func__,
|
||||
aac_config->sbr_ps_on_flag, aac_config->sbr_on_flag);
|
||||
if (aac_config->sbr_ps_on_flag)
|
||||
aac_mode = AAC_ENC_MODE_EAAC_P;
|
||||
else if (aac_config->sbr_on_flag)
|
||||
aac_mode = AAC_ENC_MODE_AAC_P;
|
||||
else
|
||||
aac_mode = AAC_ENC_MODE_AAC_LC;
|
||||
|
||||
rc = q6asm_enc_cfg_blk_aac(audio->ac,
|
||||
audio->buf_cfg.frames_per_buf,
|
||||
enc_cfg->sample_rate,
|
||||
enc_cfg->channels,
|
||||
enc_cfg->bit_rate,
|
||||
aac_mode,
|
||||
enc_cfg->stream_format);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: cmd media format block failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
rc = q6asm_media_format_block_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: media format block failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
rc = audio_in_enable(audio);
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s:session id %d: Audio Start procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
while (cnt++ < audio->str_cfg.buffer_count)
|
||||
q6asm_read(audio->ac);
|
||||
pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n",
|
||||
__func__, audio->ac->session, audio->enabled);
|
||||
break;
|
||||
}
|
||||
case AUDIO_STOP: {
|
||||
pr_debug("%s:session id %d: Rxed AUDIO_STOP\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = audio_in_disable(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Audio Stop procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AAC_ENC_CONFIG: {
|
||||
struct msm_audio_aac_enc_config *cfg;
|
||||
struct msm_audio_aac_enc_config *enc_cfg;
|
||||
|
||||
cfg = (struct msm_audio_aac_enc_config *)arg;
|
||||
if (cfg == NULL) {
|
||||
pr_err("%s: NULL config pointer for %s\n",
|
||||
__func__, "AUDIO_GET_AAC_CONFIG");
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
memset(cfg, 0, sizeof(*cfg));
|
||||
enc_cfg = audio->enc_cfg;
|
||||
if (enc_cfg->channels == CH_MODE_MONO)
|
||||
cfg->channels = 1;
|
||||
else
|
||||
cfg->channels = 2;
|
||||
|
||||
cfg->sample_rate = enc_cfg->sample_rate;
|
||||
cfg->bit_rate = enc_cfg->bit_rate;
|
||||
switch (enc_cfg->stream_format) {
|
||||
case 0x00:
|
||||
cfg->stream_format = AUDIO_AAC_FORMAT_ADTS;
|
||||
break;
|
||||
case 0x01:
|
||||
cfg->stream_format = AUDIO_AAC_FORMAT_LOAS;
|
||||
break;
|
||||
case 0x02:
|
||||
cfg->stream_format = AUDIO_AAC_FORMAT_ADIF;
|
||||
break;
|
||||
default:
|
||||
case 0x03:
|
||||
cfg->stream_format = AUDIO_AAC_FORMAT_RAW;
|
||||
}
|
||||
pr_debug("%s:session id %d: Get-aac-cfg: format=%d sr=%d bitrate=%d\n",
|
||||
__func__, audio->ac->session,
|
||||
cfg->stream_format, cfg->sample_rate, cfg->bit_rate);
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_ENC_CONFIG: {
|
||||
struct msm_audio_aac_enc_config *cfg;
|
||||
struct msm_audio_aac_enc_config *enc_cfg;
|
||||
uint32_t min_bitrate, max_bitrate;
|
||||
|
||||
cfg = (struct msm_audio_aac_enc_config *)arg;
|
||||
if (cfg == NULL) {
|
||||
pr_err("%s: NULL config pointer for %s\n",
|
||||
"AUDIO_SET_AAC_ENC_CONFIG", __func__);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
enc_cfg = audio->enc_cfg;
|
||||
pr_debug("%s:session id %d: Set-aac-cfg: stream=%d\n", __func__,
|
||||
audio->ac->session, cfg->stream_format);
|
||||
|
||||
switch (cfg->stream_format) {
|
||||
case AUDIO_AAC_FORMAT_ADTS:
|
||||
enc_cfg->stream_format = 0x00;
|
||||
break;
|
||||
case AUDIO_AAC_FORMAT_LOAS:
|
||||
enc_cfg->stream_format = 0x01;
|
||||
break;
|
||||
case AUDIO_AAC_FORMAT_ADIF:
|
||||
enc_cfg->stream_format = 0x02;
|
||||
break;
|
||||
case AUDIO_AAC_FORMAT_RAW:
|
||||
enc_cfg->stream_format = 0x03;
|
||||
break;
|
||||
default:
|
||||
pr_err("%s:session id %d: unsupported AAC format %d\n",
|
||||
__func__, audio->ac->session,
|
||||
cfg->stream_format);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cfg->channels == 1) {
|
||||
cfg->channels = CH_MODE_MONO;
|
||||
} else if (cfg->channels == 2) {
|
||||
cfg->channels = CH_MODE_STEREO;
|
||||
} else {
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cfg->sample_rate > MAX_SAMPLE_RATE_384K) {
|
||||
pr_err("%s: ERROR: invalid sample rate = %u",
|
||||
__func__, cfg->sample_rate);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
min_bitrate = ((cfg->sample_rate)*(cfg->channels))/2;
|
||||
/* This calculation should be based on AAC mode. But we cannot
|
||||
* get AAC mode in this setconfig. min_bitrate's logical max
|
||||
* value is 24000. So if min_bitrate is higher than 24000,
|
||||
* choose 24000.
|
||||
*/
|
||||
if (min_bitrate > 24000)
|
||||
min_bitrate = 24000;
|
||||
max_bitrate = 6*(cfg->sample_rate)*(cfg->channels);
|
||||
if (max_bitrate > 192000)
|
||||
max_bitrate = 192000;
|
||||
if ((cfg->bit_rate < min_bitrate) ||
|
||||
(cfg->bit_rate > max_bitrate)) {
|
||||
pr_err("%s: bitrate permissible: max=%d, min=%d\n",
|
||||
__func__, max_bitrate, min_bitrate);
|
||||
pr_err("%s: ERROR in setting bitrate = %d\n",
|
||||
__func__, cfg->bit_rate);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
enc_cfg->sample_rate = cfg->sample_rate;
|
||||
enc_cfg->channels = cfg->channels;
|
||||
enc_cfg->bit_rate = cfg->bit_rate;
|
||||
pr_debug("%s:session id %d: Set-aac-cfg:SR= 0x%x ch=0x%x bitrate=0x%x, format(adts/raw) = %d\n",
|
||||
__func__, audio->ac->session, enc_cfg->sample_rate,
|
||||
enc_cfg->channels, enc_cfg->bit_rate,
|
||||
enc_cfg->stream_format);
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_CONFIG: {
|
||||
struct msm_audio_aac_config *aac_cfg;
|
||||
struct msm_audio_aac_config *audio_aac_cfg;
|
||||
struct msm_audio_aac_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
audio_aac_cfg = audio->codec_cfg;
|
||||
aac_cfg = (struct msm_audio_aac_config *)arg;
|
||||
|
||||
if (aac_cfg == NULL) {
|
||||
pr_err("%s: NULL config pointer %s\n",
|
||||
__func__, "AUDIO_SET_AAC_CONFIG");
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
pr_debug("%s:session id %d: AUDIO_SET_AAC_CONFIG: sbr_flag = %d sbr_ps_flag = %d\n",
|
||||
__func__, audio->ac->session, aac_cfg->sbr_on_flag,
|
||||
aac_cfg->sbr_ps_on_flag);
|
||||
audio_aac_cfg->sbr_on_flag = aac_cfg->sbr_on_flag;
|
||||
audio_aac_cfg->sbr_ps_on_flag = aac_cfg->sbr_ps_on_flag;
|
||||
if ((audio_aac_cfg->sbr_on_flag == 1) ||
|
||||
(audio_aac_cfg->sbr_ps_on_flag == 1)) {
|
||||
if (enc_cfg->sample_rate < 24000) {
|
||||
pr_err("%s: ERROR in setting samplerate = %d\n",
|
||||
__func__, enc_cfg->sample_rate);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long aac_in_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = aac_in_ioctl_shared(file, cmd, NULL);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AAC_ENC_CONFIG: {
|
||||
struct msm_audio_aac_enc_config cfg;
|
||||
|
||||
rc = aac_in_ioctl_shared(file, cmd, &cfg);
|
||||
if (rc) {
|
||||
pr_err("%s:AUDIO_GET_AAC_ENC_CONFIG failed. rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AAC_ENC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_ENC_CONFIG: {
|
||||
struct msm_audio_aac_enc_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AAC_ENC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = aac_in_ioctl_shared(file, cmd, &cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_ENC_CONFIG failed. rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AAC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, &audio->codec_cfg,
|
||||
sizeof(struct msm_audio_aac_config))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AAC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_CONFIG: {
|
||||
struct msm_audio_aac_config aac_cfg;
|
||||
|
||||
if (copy_from_user(&aac_cfg, (void *)arg,
|
||||
sizeof(struct msm_audio_aac_config))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_SET_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = aac_in_ioctl_shared(file, cmd, &aac_cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_CONFIG failed. rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd=%d\n", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_aac_enc_config32 {
|
||||
u32 channels;
|
||||
u32 sample_rate;
|
||||
u32 bit_rate;
|
||||
u32 stream_format;
|
||||
};
|
||||
|
||||
struct msm_audio_aac_config32 {
|
||||
s16 format;
|
||||
u16 audio_object;
|
||||
u16 ep_config; /* 0 ~ 3 useful only obj = ERLC */
|
||||
u16 aac_section_data_resilience_flag;
|
||||
u16 aac_scalefactor_data_resilience_flag;
|
||||
u16 aac_spectral_data_resilience_flag;
|
||||
u16 sbr_on_flag;
|
||||
u16 sbr_ps_on_flag;
|
||||
u16 dual_mono_mode;
|
||||
u16 channel_configuration;
|
||||
u16 sample_rate;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_SET_AAC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_aac_config32),
|
||||
AUDIO_GET_AAC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_aac_config32),
|
||||
AUDIO_SET_AAC_ENC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+3), struct msm_audio_aac_enc_config32),
|
||||
AUDIO_GET_AAC_ENC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+4), struct msm_audio_aac_enc_config32)
|
||||
};
|
||||
|
||||
static long aac_in_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = aac_in_ioctl_shared(file, cmd, NULL);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AAC_ENC_CONFIG_32: {
|
||||
struct msm_audio_aac_enc_config cfg;
|
||||
struct msm_audio_aac_enc_config32 cfg_32;
|
||||
|
||||
memset(&cfg_32, 0, sizeof(cfg_32));
|
||||
|
||||
cmd = AUDIO_GET_AAC_ENC_CONFIG;
|
||||
rc = aac_in_ioctl_shared(file, cmd, &cfg);
|
||||
if (rc) {
|
||||
pr_err("%s:AUDIO_GET_AAC_ENC_CONFIG_32 failed. Rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
cfg_32.channels = cfg.channels;
|
||||
cfg_32.sample_rate = cfg.sample_rate;
|
||||
cfg_32.bit_rate = cfg.bit_rate;
|
||||
cfg_32.stream_format = cfg.stream_format;
|
||||
if (copy_to_user((void *)arg, &cfg_32, sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AAC_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_ENC_CONFIG_32: {
|
||||
struct msm_audio_aac_enc_config cfg;
|
||||
struct msm_audio_aac_enc_config32 cfg_32;
|
||||
|
||||
if (copy_from_user(&cfg_32, (void *)arg, sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_GET_AAC_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
cfg.channels = cfg_32.channels;
|
||||
cfg.sample_rate = cfg_32.sample_rate;
|
||||
cfg.bit_rate = cfg_32.bit_rate;
|
||||
cfg.stream_format = cfg_32.stream_format;
|
||||
/* The command should be converted from 32 bit to normal
|
||||
* before the shared ioctl is called as shared ioctl
|
||||
* can process only normal commands
|
||||
*/
|
||||
cmd = AUDIO_SET_AAC_ENC_CONFIG;
|
||||
rc = aac_in_ioctl_shared(file, cmd, &cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_ENC_CONFIG_32 failed. rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AAC_CONFIG_32: {
|
||||
struct msm_audio_aac_config *aac_config;
|
||||
struct msm_audio_aac_config32 aac_config_32;
|
||||
|
||||
aac_config = (struct msm_audio_aac_config *)audio->codec_cfg;
|
||||
aac_config_32.format = aac_config->format;
|
||||
aac_config_32.audio_object = aac_config->audio_object;
|
||||
aac_config_32.ep_config = aac_config->ep_config;
|
||||
aac_config_32.aac_section_data_resilience_flag =
|
||||
aac_config->aac_section_data_resilience_flag;
|
||||
aac_config_32.aac_scalefactor_data_resilience_flag =
|
||||
aac_config->aac_scalefactor_data_resilience_flag;
|
||||
aac_config_32.aac_spectral_data_resilience_flag =
|
||||
aac_config->aac_spectral_data_resilience_flag;
|
||||
aac_config_32.sbr_on_flag = aac_config->sbr_on_flag;
|
||||
aac_config_32.sbr_ps_on_flag = aac_config->sbr_ps_on_flag;
|
||||
aac_config_32.dual_mono_mode = aac_config->dual_mono_mode;
|
||||
aac_config_32.channel_configuration =
|
||||
aac_config->channel_configuration;
|
||||
aac_config_32.sample_rate = aac_config->sample_rate;
|
||||
|
||||
if (copy_to_user((void *)arg, &aac_config_32,
|
||||
sizeof(aac_config_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AAC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_CONFIG_32: {
|
||||
struct msm_audio_aac_config aac_cfg;
|
||||
struct msm_audio_aac_config32 aac_cfg_32;
|
||||
|
||||
if (copy_from_user(&aac_cfg_32, (void *)arg,
|
||||
sizeof(aac_cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AAC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
aac_cfg.format = aac_cfg_32.format;
|
||||
aac_cfg.audio_object = aac_cfg_32.audio_object;
|
||||
aac_cfg.ep_config = aac_cfg_32.ep_config;
|
||||
aac_cfg.aac_section_data_resilience_flag =
|
||||
aac_cfg_32.aac_section_data_resilience_flag;
|
||||
aac_cfg.aac_scalefactor_data_resilience_flag =
|
||||
aac_cfg_32.aac_scalefactor_data_resilience_flag;
|
||||
aac_cfg.aac_spectral_data_resilience_flag =
|
||||
aac_cfg_32.aac_spectral_data_resilience_flag;
|
||||
aac_cfg.sbr_on_flag = aac_cfg_32.sbr_on_flag;
|
||||
aac_cfg.sbr_ps_on_flag = aac_cfg_32.sbr_ps_on_flag;
|
||||
aac_cfg.dual_mono_mode = aac_cfg_32.dual_mono_mode;
|
||||
aac_cfg.channel_configuration =
|
||||
aac_cfg_32.channel_configuration;
|
||||
aac_cfg.sample_rate = aac_cfg_32.sample_rate;
|
||||
|
||||
cmd = AUDIO_SET_AAC_CONFIG;
|
||||
rc = aac_in_ioctl_shared(file, cmd, &aac_cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_CONFIG failed. Rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d\n", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define aac_in_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int aac_in_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_in *audio = NULL;
|
||||
struct msm_audio_aac_enc_config *enc_cfg;
|
||||
struct msm_audio_aac_config *aac_config;
|
||||
int rc = 0;
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Allocate memory for encoder config param */
|
||||
audio->enc_cfg = kzalloc(sizeof(struct msm_audio_aac_enc_config),
|
||||
GFP_KERNEL);
|
||||
if (audio->enc_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
enc_cfg = audio->enc_cfg;
|
||||
|
||||
audio->codec_cfg = kzalloc(sizeof(struct msm_audio_aac_config),
|
||||
GFP_KERNEL);
|
||||
if (audio->codec_cfg == NULL) {
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
aac_config = audio->codec_cfg;
|
||||
|
||||
mutex_init(&audio->lock);
|
||||
mutex_init(&audio->read_lock);
|
||||
mutex_init(&audio->write_lock);
|
||||
spin_lock_init(&audio->dsp_lock);
|
||||
init_waitqueue_head(&audio->read_wait);
|
||||
init_waitqueue_head(&audio->write_wait);
|
||||
|
||||
/* Settings will be re-config at AUDIO_SET_CONFIG,
|
||||
* but at least we need to have initial config
|
||||
*/
|
||||
audio->str_cfg.buffer_size = FRAME_SIZE;
|
||||
audio->str_cfg.buffer_count = FRAME_NUM;
|
||||
audio->min_frame_size = 1536;
|
||||
audio->max_frames_per_buf = 5;
|
||||
enc_cfg->sample_rate = 8000;
|
||||
enc_cfg->channels = 1;
|
||||
enc_cfg->bit_rate = 16000;
|
||||
enc_cfg->stream_format = 0x00;/* 0:ADTS, 3:RAW */
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->pcm_cfg.buffer_count = PCM_BUF_COUNT;
|
||||
audio->pcm_cfg.buffer_size = PCM_BUF_SIZE;
|
||||
aac_config->format = AUDIO_AAC_FORMAT_ADTS;
|
||||
aac_config->audio_object = AUDIO_AAC_OBJECT_LC;
|
||||
aac_config->sbr_on_flag = 0;
|
||||
aac_config->sbr_ps_on_flag = 0;
|
||||
aac_config->channel_configuration = 1;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_in_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("%s: Could not allocate memory for audio client\n",
|
||||
__func__);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
/* open aac encoder in tunnel mode */
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
|
||||
if ((file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_MPEG4_AAC,
|
||||
FORMAT_LINEAR_PCM);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: NT Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
pr_info("%s:session id %d: NT mode encoder success\n", __func__,
|
||||
audio->ac->session);
|
||||
} else if (!(file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
rc = q6asm_open_read(audio->ac, FORMAT_MPEG4_AAC);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Tunnel Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
/* register for tx overflow (valid for tunnel mode only) */
|
||||
rc = q6asm_reg_tx_overflow(audio->ac, 0x01);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: TX Overflow registration failed rc=%d\n",
|
||||
__func__,
|
||||
audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
pr_info("%s:session id %d: T mode encoder success\n", __func__,
|
||||
audio->ac->session);
|
||||
} else {
|
||||
pr_err("%s:session id %d: Unexpected mode\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
audio->opened = 1;
|
||||
audio->reset_event = false;
|
||||
atomic_set(&audio->in_count, PCM_BUF_COUNT);
|
||||
atomic_set(&audio->out_count, 0x00);
|
||||
audio->enc_compat_ioctl = aac_in_compat_ioctl;
|
||||
audio->enc_ioctl = aac_in_ioctl;
|
||||
file->private_data = audio;
|
||||
|
||||
pr_info("%s:session id %d: success\n", __func__, audio->ac->session);
|
||||
return 0;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_in_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = aac_in_open,
|
||||
.release = audio_in_release,
|
||||
.read = audio_in_read,
|
||||
.write = audio_in_write,
|
||||
.unlocked_ioctl = audio_in_ioctl,
|
||||
.compat_ioctl = audio_in_compat_ioctl
|
||||
};
|
||||
|
||||
struct miscdevice audio_aac_in_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_aac_in",
|
||||
.fops = &audio_in_fops,
|
||||
};
|
||||
|
||||
static int __init aac_in_init(void)
|
||||
{
|
||||
return misc_register(&audio_aac_in_misc);
|
||||
}
|
||||
device_initcall(aac_in_init);
|
||||
402
drivers/misc/qcom/qdsp6v2/amrnb_in.c
Normal file
402
drivers/misc/qcom/qdsp6v2/amrnb_in.c
Normal file
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2012, 2014, 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/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/msm_audio_amrnb.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include "audio_utils.h"
|
||||
|
||||
/* Buffer with meta*/
|
||||
#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in))
|
||||
|
||||
/* Maximum 10 frames in buffer with meta */
|
||||
#define FRAME_SIZE (1 + ((32+sizeof(struct meta_out_dsp)) * 10))
|
||||
|
||||
static long amrnb_in_ioctl_shared(struct file *file,
|
||||
unsigned int cmd, void *arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
int cnt = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct msm_audio_amrnb_enc_config_v2 *enc_cfg;
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__,
|
||||
audio->ac->session, audio->buf_alloc);
|
||||
if (audio->enabled == 1) {
|
||||
pr_info("%s:AUDIO_START already over\n", __func__);
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
rc = audio_in_buf_alloc(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: buffer allocation failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
|
||||
rc = q6asm_enc_cfg_blk_amrnb(audio->ac,
|
||||
audio->buf_cfg.frames_per_buf,
|
||||
enc_cfg->band_mode,
|
||||
enc_cfg->dtx_enable);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: cmd amrnb media format block failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
rc = q6asm_media_format_block_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: media format block failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pr_debug("%s:session id %d: AUDIO_START enable[%d]\n",
|
||||
__func__, audio->ac->session,
|
||||
audio->enabled);
|
||||
rc = audio_in_enable(audio);
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s:session id %d: Audio Start procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
while (cnt++ < audio->str_cfg.buffer_count)
|
||||
q6asm_read(audio->ac); /* Push buffer to DSP */
|
||||
rc = 0;
|
||||
pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n",
|
||||
__func__, audio->ac->session, audio->enabled);
|
||||
break;
|
||||
}
|
||||
case AUDIO_STOP: {
|
||||
pr_debug("%s:AUDIO_STOP\n", __func__);
|
||||
rc = audio_in_disable(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Audio Stop procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AMRNB_ENC_CONFIG_V2: {
|
||||
struct msm_audio_amrnb_enc_config_v2 *cfg;
|
||||
struct msm_audio_amrnb_enc_config_v2 *enc_cfg;
|
||||
|
||||
cfg = (struct msm_audio_amrnb_enc_config_v2 *)arg;
|
||||
if (cfg == NULL) {
|
||||
pr_err("%s: NULL config pointer for %s\n",
|
||||
__func__,
|
||||
"AUDIO_SET_AMRNB_ENC_CONFIG_V2");
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
if (cfg->band_mode > 8 ||
|
||||
cfg->band_mode < 1) {
|
||||
pr_err("%s:session id %d: invalid band mode\n",
|
||||
__func__, audio->ac->session);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
/* AMR NB encoder accepts values between 0-7
|
||||
* while openmax provides value between 1-8
|
||||
* as per spec
|
||||
*/
|
||||
enc_cfg->band_mode = (cfg->band_mode - 1);
|
||||
enc_cfg->dtx_enable = (cfg->dtx_enable ? 1 : 0);
|
||||
enc_cfg->frame_format = 0;
|
||||
pr_debug("%s:session id %d: band_mode = 0x%x dtx_enable=0x%x\n",
|
||||
__func__, audio->ac->session,
|
||||
enc_cfg->band_mode, enc_cfg->dtx_enable);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long amrnb_in_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = amrnb_in_ioctl_shared(file, cmd, NULL);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AMRNB_ENC_CONFIG_V2: {
|
||||
if (copy_to_user((void *)arg, audio->enc_cfg,
|
||||
sizeof(struct msm_audio_amrnb_enc_config_v2))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AMRNB_ENC_CONFIG_V2 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AMRNB_ENC_CONFIG_V2: {
|
||||
struct msm_audio_amrnb_enc_config_v2 cfg;
|
||||
|
||||
if (copy_from_user(&cfg, (void *) arg,
|
||||
sizeof(cfg))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AMRNB_ENC_CONFIG_V2 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = amrnb_in_ioctl_shared(file, cmd, &cfg);
|
||||
if (rc)
|
||||
pr_err("%s: AUDIO_SET_AMRNB_ENC_CONFIG_V2 failed. rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd=%d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_amrnb_enc_config_v2_32 {
|
||||
u32 band_mode;
|
||||
u32 dtx_enable;
|
||||
u32 frame_format;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_GET_AMRNB_ENC_CONFIG_V2_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+2),
|
||||
struct msm_audio_amrnb_enc_config_v2_32),
|
||||
AUDIO_SET_AMRNB_ENC_CONFIG_V2_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+3),
|
||||
struct msm_audio_amrnb_enc_config_v2_32)
|
||||
};
|
||||
|
||||
static long amrnb_in_compat_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = amrnb_in_ioctl_shared(file, cmd, NULL);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AMRNB_ENC_CONFIG_V2_32: {
|
||||
struct msm_audio_amrnb_enc_config_v2 *amrnb_config;
|
||||
struct msm_audio_amrnb_enc_config_v2_32 amrnb_config_32;
|
||||
|
||||
memset(&amrnb_config_32, 0, sizeof(amrnb_config_32));
|
||||
|
||||
amrnb_config =
|
||||
(struct msm_audio_amrnb_enc_config_v2 *)audio->enc_cfg;
|
||||
amrnb_config_32.band_mode = amrnb_config->band_mode;
|
||||
amrnb_config_32.dtx_enable = amrnb_config->dtx_enable;
|
||||
amrnb_config_32.frame_format = amrnb_config->frame_format;
|
||||
|
||||
if (copy_to_user((void *)arg, &amrnb_config_32,
|
||||
sizeof(amrnb_config_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AMRNB_ENC_CONFIG_V2_32 failed",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AMRNB_ENC_CONFIG_V2_32: {
|
||||
struct msm_audio_amrnb_enc_config_v2_32 cfg_32;
|
||||
|
||||
if (copy_from_user(&cfg_32, (void *) arg,
|
||||
sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AMRNB_ENC_CONFIG_V2_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
cmd = AUDIO_SET_AMRNB_ENC_CONFIG_V2;
|
||||
rc = amrnb_in_ioctl_shared(file, cmd, &cfg_32);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AMRNB_ENC_CONFIG_V2 failed rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define amrnb_in_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int amrnb_in_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_in *audio = NULL;
|
||||
struct msm_audio_amrnb_enc_config_v2 *enc_cfg;
|
||||
int rc = 0;
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Allocate memory for encoder config param */
|
||||
audio->enc_cfg = kzalloc(sizeof(struct msm_audio_amrnb_enc_config_v2),
|
||||
GFP_KERNEL);
|
||||
if (audio->enc_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
enc_cfg = audio->enc_cfg;
|
||||
|
||||
mutex_init(&audio->lock);
|
||||
mutex_init(&audio->read_lock);
|
||||
mutex_init(&audio->write_lock);
|
||||
spin_lock_init(&audio->dsp_lock);
|
||||
init_waitqueue_head(&audio->read_wait);
|
||||
init_waitqueue_head(&audio->write_wait);
|
||||
|
||||
/* Settings will be re-config at AUDIO_SET_CONFIG,
|
||||
* but at least we need to have initial config
|
||||
*/
|
||||
audio->str_cfg.buffer_size = FRAME_SIZE;
|
||||
audio->str_cfg.buffer_count = FRAME_NUM;
|
||||
audio->min_frame_size = 32;
|
||||
audio->max_frames_per_buf = 10;
|
||||
audio->pcm_cfg.buffer_size = PCM_BUF_SIZE;
|
||||
audio->pcm_cfg.buffer_count = PCM_BUF_COUNT;
|
||||
enc_cfg->band_mode = 7;
|
||||
enc_cfg->dtx_enable = 0;
|
||||
audio->pcm_cfg.channel_count = 1;
|
||||
audio->pcm_cfg.sample_rate = 8000;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_in_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("%s: Could not allocate memory for audio client\n",
|
||||
__func__);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* open amrnb encoder in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_AMRNB,
|
||||
FORMAT_LINEAR_PCM);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: NT mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
pr_info("%s:session id %d: NT mode encoder success\n",
|
||||
__func__, audio->ac->session);
|
||||
} else if (!(file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
rc = q6asm_open_read(audio->ac, FORMAT_AMRNB);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: T mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
/* register for tx overflow (valid for tunnel mode only) */
|
||||
rc = q6asm_reg_tx_overflow(audio->ac, 0x01);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: TX Overflow registration failed rc=%d\n",
|
||||
__func__, audio->ac->session,
|
||||
rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
pr_info("%s:session id %d: T mode encoder success\n",
|
||||
__func__, audio->ac->session);
|
||||
} else {
|
||||
pr_err("%s:session id %d: Unexpected mode\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
audio->opened = 1;
|
||||
atomic_set(&audio->in_count, PCM_BUF_COUNT);
|
||||
atomic_set(&audio->out_count, 0x00);
|
||||
audio->enc_compat_ioctl = amrnb_in_compat_ioctl;
|
||||
audio->enc_ioctl = amrnb_in_ioctl;
|
||||
file->private_data = audio;
|
||||
|
||||
pr_info("%s:session id %d: success\n", __func__, audio->ac->session);
|
||||
return 0;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_in_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = amrnb_in_open,
|
||||
.release = audio_in_release,
|
||||
.read = audio_in_read,
|
||||
.write = audio_in_write,
|
||||
.unlocked_ioctl = audio_in_ioctl,
|
||||
.compat_ioctl = audio_in_compat_ioctl
|
||||
};
|
||||
|
||||
struct miscdevice audio_amrnb_in_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_amrnb_in",
|
||||
.fops = &audio_in_fops,
|
||||
};
|
||||
|
||||
static int __init amrnb_in_init(void)
|
||||
{
|
||||
return misc_register(&audio_amrnb_in_misc);
|
||||
}
|
||||
|
||||
device_initcall(amrnb_in_init);
|
||||
400
drivers/misc/qcom/qdsp6v2/amrwb_in.c
Normal file
400
drivers/misc/qcom/qdsp6v2/amrwb_in.c
Normal file
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2012, 2014, 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/dma-mapping.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/msm_audio_amrwb.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include "audio_utils.h"
|
||||
|
||||
/* Buffer with meta*/
|
||||
#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in))
|
||||
|
||||
/* Maximum 10 frames in buffer with meta */
|
||||
#define FRAME_SIZE (1 + ((61+sizeof(struct meta_out_dsp)) * 10))
|
||||
|
||||
static long amrwb_in_ioctl_shared(struct file *file,
|
||||
unsigned int cmd, void *arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
int cnt = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct msm_audio_amrwb_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__,
|
||||
audio->ac->session, audio->buf_alloc);
|
||||
if (audio->enabled == 1) {
|
||||
pr_info("%s:AUDIO_START already over\n", __func__);
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
rc = audio_in_buf_alloc(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: buffer allocation failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
|
||||
rc = q6asm_enc_cfg_blk_amrwb(audio->ac,
|
||||
audio->buf_cfg.frames_per_buf,
|
||||
enc_cfg->band_mode,
|
||||
enc_cfg->dtx_enable);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: cmd amrwb media format block failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
rc = q6asm_media_format_block_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: media format block failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pr_debug("%s:session id %d: AUDIO_START enable[%d]\n",
|
||||
__func__, audio->ac->session,
|
||||
audio->enabled);
|
||||
rc = audio_in_enable(audio);
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s:session id %d: Audio Start procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
while (cnt++ < audio->str_cfg.buffer_count)
|
||||
q6asm_read(audio->ac); /* Push buffer to DSP */
|
||||
rc = 0;
|
||||
pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n",
|
||||
__func__, audio->ac->session, audio->enabled);
|
||||
break;
|
||||
}
|
||||
case AUDIO_STOP: {
|
||||
pr_debug("%s:AUDIO_STOP\n", __func__);
|
||||
rc = audio_in_disable(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Audio Stop procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AMRWB_ENC_CONFIG: {
|
||||
struct msm_audio_amrwb_enc_config *cfg;
|
||||
struct msm_audio_amrwb_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
cfg = (struct msm_audio_amrwb_enc_config *)arg;
|
||||
if (cfg == NULL) {
|
||||
pr_err("%s: NULL config pointer for %s\n",
|
||||
__func__, "AUDIO_SET_AMRWB_ENC_CONFIG");
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cfg->band_mode > 8) {
|
||||
pr_err("%s:session id %d: invalid band mode\n",
|
||||
__func__, audio->ac->session);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
/* ToDo: AMR WB encoder accepts values between 0-8
|
||||
* while openmax provides value between 9-17
|
||||
* as per spec
|
||||
*/
|
||||
enc_cfg->band_mode = cfg->band_mode;
|
||||
enc_cfg->dtx_enable = (cfg->dtx_enable ? 1 : 0);
|
||||
/* Currently DSP does not support different frameformat */
|
||||
enc_cfg->frame_format = 0;
|
||||
pr_debug("%s:session id %d: band_mode = 0x%x dtx_enable=0x%x\n",
|
||||
__func__, audio->ac->session,
|
||||
enc_cfg->band_mode, enc_cfg->dtx_enable);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long amrwb_in_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = amrwb_in_ioctl_shared(file, cmd, NULL);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AMRWB_ENC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->enc_cfg,
|
||||
sizeof(struct msm_audio_amrwb_enc_config)))
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AMRWB_ENC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AMRWB_ENC_CONFIG: {
|
||||
struct msm_audio_amrwb_enc_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg, (void *) arg,
|
||||
sizeof(cfg))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AMRWB_ENC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = amrwb_in_ioctl_shared(file, cmd, &cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_ENC_CONFIG failed. rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_amrwb_enc_config_32 {
|
||||
u32 band_mode;
|
||||
u32 dtx_enable;
|
||||
u32 frame_format;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_GET_AMRWB_ENC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0),
|
||||
struct msm_audio_amrwb_enc_config_32),
|
||||
AUDIO_SET_AMRWB_ENC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1),
|
||||
struct msm_audio_amrwb_enc_config_32)
|
||||
};
|
||||
|
||||
static long amrwb_in_compat_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = amrwb_in_ioctl_shared(file, cmd, NULL);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AMRWB_ENC_CONFIG_32: {
|
||||
struct msm_audio_amrwb_enc_config *amrwb_config;
|
||||
struct msm_audio_amrwb_enc_config_32 amrwb_config_32;
|
||||
|
||||
memset(&amrwb_config_32, 0, sizeof(amrwb_config_32));
|
||||
|
||||
amrwb_config =
|
||||
(struct msm_audio_amrwb_enc_config *)audio->enc_cfg;
|
||||
amrwb_config_32.band_mode = amrwb_config->band_mode;
|
||||
amrwb_config_32.dtx_enable = amrwb_config->dtx_enable;
|
||||
amrwb_config_32.frame_format = amrwb_config->frame_format;
|
||||
|
||||
if (copy_to_user((void *)arg, &amrwb_config_32,
|
||||
sizeof(struct msm_audio_amrwb_enc_config_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AMRWB_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AMRWB_ENC_CONFIG_32: {
|
||||
struct msm_audio_amrwb_enc_config cfg_32;
|
||||
|
||||
if (copy_from_user(&cfg_32, (void *) arg,
|
||||
sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AMRWB_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
cmd = AUDIO_SET_AMRWB_ENC_CONFIG;
|
||||
rc = amrwb_in_ioctl_shared(file, cmd, &cfg_32);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_ENC_CONFIG failed. rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define amrwb_in_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int amrwb_in_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_in *audio = NULL;
|
||||
struct msm_audio_amrwb_enc_config *enc_cfg;
|
||||
int rc = 0;
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Allocate memory for encoder config param */
|
||||
audio->enc_cfg = kzalloc(sizeof(struct msm_audio_amrwb_enc_config),
|
||||
GFP_KERNEL);
|
||||
if (audio->enc_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
enc_cfg = audio->enc_cfg;
|
||||
|
||||
mutex_init(&audio->lock);
|
||||
mutex_init(&audio->read_lock);
|
||||
mutex_init(&audio->write_lock);
|
||||
spin_lock_init(&audio->dsp_lock);
|
||||
init_waitqueue_head(&audio->read_wait);
|
||||
init_waitqueue_head(&audio->write_wait);
|
||||
|
||||
/* Settings will be re-config at AUDIO_SET_CONFIG,
|
||||
* but at least we need to have initial config
|
||||
*/
|
||||
audio->str_cfg.buffer_size = FRAME_SIZE;
|
||||
audio->str_cfg.buffer_count = FRAME_NUM;
|
||||
audio->min_frame_size = 32;
|
||||
audio->max_frames_per_buf = 10;
|
||||
audio->pcm_cfg.buffer_size = PCM_BUF_SIZE;
|
||||
audio->pcm_cfg.buffer_count = PCM_BUF_COUNT;
|
||||
enc_cfg->band_mode = 8;
|
||||
enc_cfg->dtx_enable = 0;
|
||||
audio->pcm_cfg.channel_count = 1;
|
||||
audio->pcm_cfg.sample_rate = 16000;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_in_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("%s:audio[%pK]: Could not allocate memory for audio client\n",
|
||||
__func__, audio);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* open amrwb encoder in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_AMRWB,
|
||||
FORMAT_LINEAR_PCM);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: NT mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
pr_info("%s:session id %d: NT mode encoder success\n",
|
||||
__func__, audio->ac->session);
|
||||
} else if (!(file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
rc = q6asm_open_read(audio->ac, FORMAT_AMRWB);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: T mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
/* register for tx overflow (valid for tunnel mode only) */
|
||||
rc = q6asm_reg_tx_overflow(audio->ac, 0x01);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: TX Overflow registration failed rc=%d\n",
|
||||
__func__, audio->ac->session,
|
||||
rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
pr_info("%s:session id %d: T mode encoder success\n",
|
||||
__func__, audio->ac->session);
|
||||
} else {
|
||||
pr_err("%s:session id %d: Unexpected mode\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
audio->opened = 1;
|
||||
atomic_set(&audio->in_count, PCM_BUF_COUNT);
|
||||
atomic_set(&audio->out_count, 0x00);
|
||||
audio->enc_compat_ioctl = amrwb_in_compat_ioctl;
|
||||
audio->enc_ioctl = amrwb_in_ioctl;
|
||||
file->private_data = audio;
|
||||
|
||||
pr_info("%s:session id %d: success\n", __func__, audio->ac->session);
|
||||
return 0;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_in_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = amrwb_in_open,
|
||||
.release = audio_in_release,
|
||||
.read = audio_in_read,
|
||||
.write = audio_in_write,
|
||||
.unlocked_ioctl = audio_in_ioctl,
|
||||
.compat_ioctl = audio_in_compat_ioctl
|
||||
};
|
||||
|
||||
struct miscdevice audio_amrwb_in_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_amrwb_in",
|
||||
.fops = &audio_in_fops,
|
||||
};
|
||||
|
||||
static int __init amrwb_in_init(void)
|
||||
{
|
||||
return misc_register(&audio_amrwb_in_misc);
|
||||
}
|
||||
|
||||
device_initcall(amrwb_in_init);
|
||||
474
drivers/misc/qcom/qdsp6v2/audio_aac.c
Normal file
474
drivers/misc/qcom/qdsp6v2/audio_aac.c
Normal file
@@ -0,0 +1,474 @@
|
||||
/* aac audio output device
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/msm_audio_aac.h>
|
||||
#include <linux/compat.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
#define AUDIO_AAC_DUAL_MONO_INVALID -1
|
||||
#define PCM_BUFSZ_MIN_AAC ((8*1024) + sizeof(struct dec_meta_out))
|
||||
|
||||
static struct miscdevice audio_aac_misc;
|
||||
static struct ws_mgr audio_aac_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static const struct file_operations audio_aac_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
#endif
|
||||
|
||||
static long audio_ioctl_shared(struct file *file, unsigned int cmd,
|
||||
void *arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct asm_aac_cfg aac_cfg;
|
||||
struct msm_audio_aac_config *aac_config;
|
||||
uint32_t sbr_ps = 0x00;
|
||||
|
||||
pr_debug("%s: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac, 0, 0);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* turn on both sbr and ps */
|
||||
rc = q6asm_enable_sbrps(audio->ac, sbr_ps);
|
||||
if (rc < 0)
|
||||
pr_err("sbr-ps enable failed\n");
|
||||
aac_config = (struct msm_audio_aac_config *)audio->codec_cfg;
|
||||
if (aac_config->sbr_ps_on_flag)
|
||||
aac_cfg.aot = AAC_ENC_MODE_EAAC_P;
|
||||
else if (aac_config->sbr_on_flag)
|
||||
aac_cfg.aot = AAC_ENC_MODE_AAC_P;
|
||||
else
|
||||
aac_cfg.aot = AAC_ENC_MODE_AAC_LC;
|
||||
|
||||
switch (aac_config->format) {
|
||||
case AUDIO_AAC_FORMAT_ADTS:
|
||||
aac_cfg.format = 0x00;
|
||||
break;
|
||||
case AUDIO_AAC_FORMAT_LOAS:
|
||||
aac_cfg.format = 0x01;
|
||||
break;
|
||||
case AUDIO_AAC_FORMAT_ADIF:
|
||||
aac_cfg.format = 0x02;
|
||||
break;
|
||||
default:
|
||||
case AUDIO_AAC_FORMAT_RAW:
|
||||
aac_cfg.format = 0x03;
|
||||
}
|
||||
aac_cfg.ep_config = aac_config->ep_config;
|
||||
aac_cfg.section_data_resilience =
|
||||
aac_config->aac_section_data_resilience_flag;
|
||||
aac_cfg.scalefactor_data_resilience =
|
||||
aac_config->aac_scalefactor_data_resilience_flag;
|
||||
aac_cfg.spectral_data_resilience =
|
||||
aac_config->aac_spectral_data_resilience_flag;
|
||||
aac_cfg.ch_cfg = audio->pcm_cfg.channel_count;
|
||||
if (audio->feedback == TUNNEL_MODE) {
|
||||
aac_cfg.sample_rate = aac_config->sample_rate;
|
||||
aac_cfg.ch_cfg = aac_config->channel_configuration;
|
||||
} else {
|
||||
aac_cfg.sample_rate = audio->pcm_cfg.sample_rate;
|
||||
aac_cfg.ch_cfg = audio->pcm_cfg.channel_count;
|
||||
}
|
||||
|
||||
pr_debug("%s:format=%x aot=%d ch=%d sr=%d\n",
|
||||
__func__, aac_cfg.format,
|
||||
aac_cfg.aot, aac_cfg.ch_cfg,
|
||||
aac_cfg.sample_rate);
|
||||
|
||||
/* Configure Media format block */
|
||||
rc = q6asm_media_format_block_aac(audio->ac, &aac_cfg);
|
||||
if (rc < 0) {
|
||||
pr_err("cmd media format block failed\n");
|
||||
break;
|
||||
}
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
rc = enable_volume_ramp(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: Failed to enable volume ramp\n",
|
||||
__func__);
|
||||
}
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_info("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__,
|
||||
audio->ac->session,
|
||||
audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_CONFIG: {
|
||||
struct msm_audio_aac_config *aac_config;
|
||||
uint16_t sce_left = 1, sce_right = 2;
|
||||
|
||||
pr_debug("%s: AUDIO_SET_AAC_CONFIG\n", __func__);
|
||||
aac_config = (struct msm_audio_aac_config *)arg;
|
||||
if (aac_config == NULL) {
|
||||
pr_err("%s: Invalid config pointer\n", __func__);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
memcpy(audio->codec_cfg, aac_config,
|
||||
sizeof(struct msm_audio_aac_config));
|
||||
/* PL_PR is 0 only need to check PL_SR */
|
||||
if (aac_config->dual_mono_mode >
|
||||
AUDIO_AAC_DUAL_MONO_PL_SR) {
|
||||
pr_err("%s:Invalid dual_mono mode =%d\n", __func__,
|
||||
aac_config->dual_mono_mode);
|
||||
} else {
|
||||
/* convert the data from user into sce_left
|
||||
* and sce_right based on the definitions
|
||||
*/
|
||||
pr_debug("%s: modify dual_mono mode =%d\n", __func__,
|
||||
aac_config->dual_mono_mode);
|
||||
switch (aac_config->dual_mono_mode) {
|
||||
case AUDIO_AAC_DUAL_MONO_PL_PR:
|
||||
sce_left = 1;
|
||||
sce_right = 1;
|
||||
break;
|
||||
case AUDIO_AAC_DUAL_MONO_SL_SR:
|
||||
sce_left = 2;
|
||||
sce_right = 2;
|
||||
break;
|
||||
case AUDIO_AAC_DUAL_MONO_SL_PR:
|
||||
sce_left = 2;
|
||||
sce_right = 1;
|
||||
break;
|
||||
case AUDIO_AAC_DUAL_MONO_PL_SR:
|
||||
default:
|
||||
sce_left = 1;
|
||||
sce_right = 2;
|
||||
break;
|
||||
}
|
||||
rc = q6asm_cfg_dual_mono_aac(audio->ac,
|
||||
sce_left, sce_right);
|
||||
if (rc < 0)
|
||||
pr_err("%s:asm cmd dualmono failed rc=%d\n",
|
||||
__func__, rc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AAC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->codec_cfg,
|
||||
sizeof(struct msm_audio_aac_config))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AAC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_CONFIG: {
|
||||
struct msm_audio_aac_config aac_config;
|
||||
|
||||
pr_debug("%s: AUDIO_SET_AAC_CONFIG\n", __func__);
|
||||
if (copy_from_user(&aac_config, (void *)arg,
|
||||
sizeof(aac_config))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AAC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = audio_ioctl_shared(file, cmd, &aac_config);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_CONFIG failed. Rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("%s[%pK]:Failed in utils_ioctl: %d\n",
|
||||
__func__, audio, rc);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_aac_config32 {
|
||||
s16 format;
|
||||
u16 audio_object;
|
||||
u16 ep_config; /* 0 ~ 3 useful only obj = ERLC */
|
||||
u16 aac_section_data_resilience_flag;
|
||||
u16 aac_scalefactor_data_resilience_flag;
|
||||
u16 aac_spectral_data_resilience_flag;
|
||||
u16 sbr_on_flag;
|
||||
u16 sbr_ps_on_flag;
|
||||
u16 dual_mono_mode;
|
||||
u16 channel_configuration;
|
||||
u16 sample_rate;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_SET_AAC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_aac_config32),
|
||||
AUDIO_GET_AAC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_aac_config32)
|
||||
};
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AAC_CONFIG_32: {
|
||||
struct msm_audio_aac_config *aac_config;
|
||||
struct msm_audio_aac_config32 aac_config_32;
|
||||
|
||||
aac_config = (struct msm_audio_aac_config *)audio->codec_cfg;
|
||||
aac_config_32.format = aac_config->format;
|
||||
aac_config_32.audio_object = aac_config->audio_object;
|
||||
aac_config_32.ep_config = aac_config->ep_config;
|
||||
aac_config_32.aac_section_data_resilience_flag =
|
||||
aac_config->aac_section_data_resilience_flag;
|
||||
aac_config_32.aac_scalefactor_data_resilience_flag =
|
||||
aac_config->aac_scalefactor_data_resilience_flag;
|
||||
aac_config_32.aac_spectral_data_resilience_flag =
|
||||
aac_config->aac_spectral_data_resilience_flag;
|
||||
aac_config_32.sbr_on_flag = aac_config->sbr_on_flag;
|
||||
aac_config_32.sbr_ps_on_flag = aac_config->sbr_ps_on_flag;
|
||||
aac_config_32.dual_mono_mode = aac_config->dual_mono_mode;
|
||||
aac_config_32.channel_configuration =
|
||||
aac_config->channel_configuration;
|
||||
aac_config_32.sample_rate = aac_config->sample_rate;
|
||||
|
||||
if (copy_to_user((void *)arg, &aac_config_32,
|
||||
sizeof(aac_config_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AAC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_CONFIG_32: {
|
||||
struct msm_audio_aac_config aac_config;
|
||||
struct msm_audio_aac_config32 aac_config_32;
|
||||
|
||||
pr_debug("%s: AUDIO_SET_AAC_CONFIG\n", __func__);
|
||||
if (copy_from_user(&aac_config_32, (void *)arg,
|
||||
sizeof(aac_config_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AAC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
aac_config.format = aac_config_32.format;
|
||||
aac_config.audio_object = aac_config_32.audio_object;
|
||||
aac_config.ep_config = aac_config_32.ep_config;
|
||||
aac_config.aac_section_data_resilience_flag =
|
||||
aac_config_32.aac_section_data_resilience_flag;
|
||||
aac_config.aac_scalefactor_data_resilience_flag =
|
||||
aac_config_32.aac_scalefactor_data_resilience_flag;
|
||||
aac_config.aac_spectral_data_resilience_flag =
|
||||
aac_config_32.aac_spectral_data_resilience_flag;
|
||||
aac_config.sbr_on_flag = aac_config_32.sbr_on_flag;
|
||||
aac_config.sbr_ps_on_flag = aac_config_32.sbr_ps_on_flag;
|
||||
aac_config.dual_mono_mode = aac_config_32.dual_mono_mode;
|
||||
aac_config.channel_configuration =
|
||||
aac_config_32.channel_configuration;
|
||||
aac_config.sample_rate = aac_config_32.sample_rate;
|
||||
|
||||
cmd = AUDIO_SET_AAC_CONFIG;
|
||||
rc = audio_ioctl_shared(file, cmd, &aac_config);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_CONFIG failed. Rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("%s[%pK]:Failed in utils_ioctl: %d\n",
|
||||
__func__, audio, rc);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define audio_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
struct msm_audio_aac_config *aac_config = NULL;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_aac_" + 5];
|
||||
#endif
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->codec_cfg = kzalloc(sizeof(struct msm_audio_aac_config),
|
||||
GFP_KERNEL);
|
||||
if (audio->codec_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
aac_config = audio->codec_cfg;
|
||||
|
||||
/* Settings will be re-config at AUDIO_SET_CONFIG,
|
||||
* but at least we need to have initial config
|
||||
*/
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN_AAC;
|
||||
audio->miscdevice = &audio_aac_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_aac_ws_mgr;
|
||||
aac_config->dual_mono_mode = AUDIO_AAC_DUAL_MONO_INVALID;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_MPEG4_AAC);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
/* open AAC decoder, expected frames is always 1
|
||||
* audio->buf_cfg.frames_per_buf = 0x01;
|
||||
*/
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_MPEG4_AAC);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
snprintf(name, sizeof(name), "msm_aac_%04x", audio->ac->session);
|
||||
audio->dentry = debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)audio,
|
||||
&audio_aac_debug_fops);
|
||||
|
||||
if (IS_ERR(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
#endif
|
||||
pr_info("%s:aacdec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_aac_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
.compat_ioctl = audio_compat_ioctl
|
||||
};
|
||||
|
||||
static struct miscdevice audio_aac_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_aac",
|
||||
.fops = &audio_aac_fops,
|
||||
};
|
||||
|
||||
static int __init audio_aac_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_aac_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_aac_misc.this_device, true);
|
||||
audio_aac_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_aac_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_aac_init);
|
||||
435
drivers/misc/qcom/qdsp6v2/audio_alac.c
Normal file
435
drivers/misc/qcom/qdsp6v2/audio_alac.c
Normal file
@@ -0,0 +1,435 @@
|
||||
/* 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/types.h>
|
||||
#include <linux/msm_audio_alac.h>
|
||||
#include <linux/compat.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_alac_misc;
|
||||
static struct ws_mgr audio_alac_ws_mgr;
|
||||
|
||||
static const struct file_operations audio_alac_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
|
||||
static struct dentry *config_debugfs_create_file(const char *name, void *data)
|
||||
{
|
||||
return debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)data, &audio_alac_debug_fops);
|
||||
}
|
||||
|
||||
static int alac_channel_map(u8 *channel_mapping, uint32_t channels);
|
||||
|
||||
static long audio_ioctl_shared(struct file *file, unsigned int cmd,
|
||||
void *arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct asm_alac_cfg alac_cfg;
|
||||
struct msm_audio_alac_config *alac_config;
|
||||
u8 channel_mapping[PCM_FORMAT_MAX_NUM_CHANNEL];
|
||||
|
||||
memset(channel_mapping, 0, PCM_FORMAT_MAX_NUM_CHANNEL);
|
||||
|
||||
if (alac_channel_map(channel_mapping,
|
||||
audio->pcm_cfg.channel_count)) {
|
||||
pr_err("%s: setting channel map failed %d\n",
|
||||
__func__, audio->pcm_cfg.channel_count);
|
||||
}
|
||||
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm_v2(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count,
|
||||
16, /*bits per sample*/
|
||||
false, false, channel_mapping);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
alac_config = (struct msm_audio_alac_config *)audio->codec_cfg;
|
||||
alac_cfg.frame_length = alac_config->frameLength;
|
||||
alac_cfg.compatible_version = alac_config->compatVersion;
|
||||
alac_cfg.bit_depth = alac_config->bitDepth;
|
||||
alac_cfg.pb = alac_config->pb;
|
||||
alac_cfg.mb = alac_config->mb;
|
||||
alac_cfg.kb = alac_config->kb;
|
||||
alac_cfg.num_channels = alac_config->channelCount;
|
||||
alac_cfg.max_run = alac_config->maxRun;
|
||||
alac_cfg.max_frame_bytes = alac_config->maxSize;
|
||||
alac_cfg.avg_bit_rate = alac_config->averageBitRate;
|
||||
alac_cfg.sample_rate = alac_config->sampleRate;
|
||||
alac_cfg.channel_layout_tag = alac_config->channelLayout;
|
||||
pr_debug("%s: frame_length %d compatible_version %d bit_depth %d pb %d mb %d kb %d num_channels %d max_run %d max_frame_bytes %d avg_bit_rate %d sample_rate %d channel_layout_tag %d\n",
|
||||
__func__, alac_config->frameLength,
|
||||
alac_config->compatVersion,
|
||||
alac_config->bitDepth, alac_config->pb,
|
||||
alac_config->mb, alac_config->kb,
|
||||
alac_config->channelCount, alac_config->maxRun,
|
||||
alac_config->maxSize,
|
||||
alac_config->averageBitRate,
|
||||
alac_config->sampleRate,
|
||||
alac_config->channelLayout);
|
||||
/* Configure Media format block */
|
||||
rc = q6asm_media_format_block_alac(audio->ac, &alac_cfg,
|
||||
audio->ac->stream_id);
|
||||
if (rc < 0) {
|
||||
pr_err("cmd media format block failed\n");
|
||||
break;
|
||||
}
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("AUDIO_START success enable[%d]\n", audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_ALAC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->codec_cfg,
|
||||
sizeof(struct msm_audio_alac_config))) {
|
||||
pr_err("%s:copy_to_user for AUDIO_GET_ALAC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_ALAC_CONFIG: {
|
||||
if (copy_from_user(audio->codec_cfg, (void *)arg,
|
||||
sizeof(struct msm_audio_alac_config))) {
|
||||
pr_err("%s:copy_from_user for AUDIO_SET_ALAC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("Failed in utils_ioctl: %d\n", rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_alac_config_32 {
|
||||
u32 frameLength;
|
||||
u8 compatVersion;
|
||||
u8 bitDepth;
|
||||
u8 pb;
|
||||
u8 mb;
|
||||
u8 kb;
|
||||
u8 channelCount;
|
||||
u16 maxRun;
|
||||
u32 maxSize;
|
||||
u32 averageBitRate;
|
||||
u32 sampleRate;
|
||||
u32 channelLayout;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_GET_ALAC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_alac_config_32),
|
||||
AUDIO_SET_ALAC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_alac_config_32)
|
||||
};
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_ALAC_CONFIG_32: {
|
||||
struct msm_audio_alac_config *alac_config;
|
||||
struct msm_audio_alac_config_32 alac_config_32;
|
||||
|
||||
memset(&alac_config_32, 0, sizeof(alac_config_32));
|
||||
|
||||
alac_config = (struct msm_audio_alac_config *)audio->codec_cfg;
|
||||
alac_config_32.frameLength = alac_config->frameLength;
|
||||
alac_config_32.compatVersion =
|
||||
alac_config->compatVersion;
|
||||
alac_config_32.bitDepth = alac_config->bitDepth;
|
||||
alac_config_32.pb = alac_config->pb;
|
||||
alac_config_32.mb = alac_config->mb;
|
||||
alac_config_32.kb = alac_config->kb;
|
||||
alac_config_32.channelCount = alac_config->channelCount;
|
||||
alac_config_32.maxRun = alac_config->maxRun;
|
||||
alac_config_32.maxSize = alac_config->maxSize;
|
||||
alac_config_32.averageBitRate = alac_config->averageBitRate;
|
||||
alac_config_32.sampleRate = alac_config->sampleRate;
|
||||
alac_config_32.channelLayout = alac_config->channelLayout;
|
||||
|
||||
if (copy_to_user((void *)arg, &alac_config_32,
|
||||
sizeof(alac_config_32))) {
|
||||
pr_err("%s: copy_to_user for GET_ALAC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_ALAC_CONFIG_32: {
|
||||
struct msm_audio_alac_config *alac_config;
|
||||
struct msm_audio_alac_config_32 alac_config_32;
|
||||
|
||||
if (copy_from_user(&alac_config_32, (void *)arg,
|
||||
sizeof(alac_config_32))) {
|
||||
pr_err("%s: copy_from_user for SET_ALAC_CONFIG_32 failed\n"
|
||||
, __func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
alac_config = (struct msm_audio_alac_config *)audio->codec_cfg;
|
||||
alac_config->frameLength = alac_config_32.frameLength;
|
||||
alac_config->compatVersion =
|
||||
alac_config_32.compatVersion;
|
||||
alac_config->bitDepth = alac_config_32.bitDepth;
|
||||
alac_config->pb = alac_config_32.pb;
|
||||
alac_config->mb = alac_config_32.mb;
|
||||
alac_config->kb = alac_config_32.kb;
|
||||
alac_config->channelCount = alac_config_32.channelCount;
|
||||
alac_config->maxRun = alac_config_32.maxRun;
|
||||
alac_config->maxSize = alac_config_32.maxSize;
|
||||
alac_config->averageBitRate = alac_config_32.averageBitRate;
|
||||
alac_config->sampleRate = alac_config_32.sampleRate;
|
||||
alac_config->channelLayout = alac_config_32.channelLayout;
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("Failed in utils_ioctl: %d\n", rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define audio_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_alac_" + 5];
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
if (!audio)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->codec_cfg = kzalloc(sizeof(struct msm_audio_alac_config),
|
||||
GFP_KERNEL);
|
||||
if (!audio->codec_cfg) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_alac_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_alac_ws_mgr;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_ALAC);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
/* open ALAC decoder, expected frames is always 1*/
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_ALAC);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), "msm_alac_%04x", audio->ac->session);
|
||||
audio->dentry = config_debugfs_create_file(name, (void *)audio);
|
||||
|
||||
if (IS_ERR_OR_NULL(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
pr_debug("%s:alacdec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int alac_channel_map(u8 *channel_mapping, uint32_t channels)
|
||||
{
|
||||
u8 *lchannel_mapping;
|
||||
|
||||
lchannel_mapping = channel_mapping;
|
||||
pr_debug("%s: channels passed: %d\n", __func__, channels);
|
||||
if (channels == 1) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
} else if (channels == 2) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FR;
|
||||
} else if (channels == 3) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
} else if (channels == 4) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_CS;
|
||||
} else if (channels == 5) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_RS;
|
||||
} else if (channels == 6) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_RS;
|
||||
lchannel_mapping[5] = PCM_CHANNEL_LFE;
|
||||
} else if (channels == 7) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_RS;
|
||||
lchannel_mapping[5] = PCM_CHANNEL_CS;
|
||||
lchannel_mapping[6] = PCM_CHANNEL_LFE;
|
||||
} else if (channels == 8) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FLC;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FRC;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[5] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[6] = PCM_CHANNEL_RS;
|
||||
lchannel_mapping[7] = PCM_CHANNEL_LFE;
|
||||
} else {
|
||||
pr_err("%s: ERROR.unsupported num_ch = %u\n",
|
||||
__func__, channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_alac_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
.compat_ioctl = audio_compat_ioctl
|
||||
};
|
||||
|
||||
static struct miscdevice audio_alac_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_alac",
|
||||
.fops = &audio_alac_fops,
|
||||
};
|
||||
|
||||
static int __init audio_alac_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_alac_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_alac_misc.this_device, true);
|
||||
audio_alac_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_alac_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_alac_init);
|
||||
226
drivers/misc/qcom/qdsp6v2/audio_amrnb.c
Normal file
226
drivers/misc/qcom/qdsp6v2/audio_amrnb.c
Normal file
@@ -0,0 +1,226 @@
|
||||
/* amrnb audio output device
|
||||
*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
* Copyright (C) 2008 HTC Corporation
|
||||
* Copyright (c) 2011-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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/compat.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_amrnb_misc;
|
||||
static struct ws_mgr audio_amrnb_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static const struct file_operations audio_amrnb_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
#endif
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("AUDIO_START success enable[%d]\n", audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: pcm output block config failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s: Audio Start procedure failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("AUDIO_START success enable[%d]\n", audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s[%pK]: Calling compat ioctl\n", __func__, audio);
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_amrnb_" + 5];
|
||||
#endif
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_amrnb_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_amrnb_ws_mgr;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_AMRNB);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_AMRNB);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
snprintf(name, sizeof(name), "msm_amrnb_%04x", audio->ac->session);
|
||||
audio->dentry = debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)audio,
|
||||
&audio_amrnb_debug_fops);
|
||||
|
||||
if (IS_ERR(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
#endif
|
||||
pr_info("%s:amrnb decoder open success, session_id = %d\n", __func__,
|
||||
audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_amrnb_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
.compat_ioctl = audio_compat_ioctl,
|
||||
};
|
||||
|
||||
static struct miscdevice audio_amrnb_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_amrnb",
|
||||
.fops = &audio_amrnb_fops,
|
||||
};
|
||||
|
||||
static int __init audio_amrnb_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_amrnb_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_amrnb_misc.this_device, true);
|
||||
audio_amrnb_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_amrnb_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_amrnb_init);
|
||||
231
drivers/misc/qcom/qdsp6v2/audio_amrwb.c
Normal file
231
drivers/misc/qcom/qdsp6v2/audio_amrwb.c
Normal file
@@ -0,0 +1,231 @@
|
||||
/* amrwb audio output device
|
||||
*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
* Copyright (C) 2008 HTC Corporation
|
||||
* Copyright (c) 2011-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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/compat.h>
|
||||
#include <linux/types.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_amrwb_misc;
|
||||
static struct ws_mgr audio_amrwb_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static const struct file_operations audio_amrwb_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
#endif
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__,
|
||||
audio->ac->session,
|
||||
audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: pcm output block config failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s: Audio Start procedure failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__,
|
||||
audio->ac->session,
|
||||
audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s[%pK]: Calling compat ioctl\n", __func__, audio);
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_amrwb_" + 5];
|
||||
#endif
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_amrwb_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_amrwb_ws_mgr;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_AMRWB);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_AMRWB);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
snprintf(name, sizeof(name), "msm_amrwb_%04x", audio->ac->session);
|
||||
audio->dentry = debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)audio,
|
||||
&audio_amrwb_debug_fops);
|
||||
|
||||
if (IS_ERR(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
#endif
|
||||
pr_info("%s: AMRWB dec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return 0;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_amrwb_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
.compat_ioctl = audio_compat_ioctl,
|
||||
};
|
||||
|
||||
static struct miscdevice audio_amrwb_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_amrwb",
|
||||
.fops = &audio_amrwb_fops,
|
||||
};
|
||||
|
||||
static int __init audio_amrwb_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_amrwb_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_amrwb_misc.this_device, true);
|
||||
audio_amrwb_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_amrwb_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_amrwb_init);
|
||||
397
drivers/misc/qcom/qdsp6v2/audio_amrwbplus.c
Normal file
397
drivers/misc/qcom/qdsp6v2/audio_amrwbplus.c
Normal file
@@ -0,0 +1,397 @@
|
||||
/* amr-wbplus audio output device
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#include <linux/msm_audio_amrwbplus.h>
|
||||
#include <linux/compat.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_amrwbplus_misc;
|
||||
static struct ws_mgr audio_amrwbplus_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static const struct file_operations audio_amrwbplus_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
static void config_debug_fs(struct q6audio_aio *audio)
|
||||
{
|
||||
if (audio != NULL) {
|
||||
char name[sizeof("msm_amrwbplus_") + 5];
|
||||
|
||||
snprintf(name, sizeof(name), "msm_amrwbplus_%04x",
|
||||
audio->ac->session);
|
||||
audio->dentry = debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)audio,
|
||||
&audio_amrwbplus_debug_fops);
|
||||
if (IS_ERR(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void config_debug_fs(struct q6audio_aio *audio)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static long audio_ioctl_shared(struct file *file, unsigned int cmd,
|
||||
void *arg)
|
||||
{
|
||||
struct asm_amrwbplus_cfg q6_amrwbplus_cfg;
|
||||
struct msm_audio_amrwbplus_config_v2 *amrwbplus_drv_config;
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
pr_err("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
amrwbplus_drv_config =
|
||||
(struct msm_audio_amrwbplus_config_v2 *)audio->codec_cfg;
|
||||
|
||||
q6_amrwbplus_cfg.size_bytes =
|
||||
amrwbplus_drv_config->size_bytes;
|
||||
q6_amrwbplus_cfg.version =
|
||||
amrwbplus_drv_config->version;
|
||||
q6_amrwbplus_cfg.num_channels =
|
||||
amrwbplus_drv_config->num_channels;
|
||||
q6_amrwbplus_cfg.amr_band_mode =
|
||||
amrwbplus_drv_config->amr_band_mode;
|
||||
q6_amrwbplus_cfg.amr_dtx_mode =
|
||||
amrwbplus_drv_config->amr_dtx_mode;
|
||||
q6_amrwbplus_cfg.amr_frame_fmt =
|
||||
amrwbplus_drv_config->amr_frame_fmt;
|
||||
q6_amrwbplus_cfg.amr_lsf_idx =
|
||||
amrwbplus_drv_config->amr_lsf_idx;
|
||||
|
||||
rc = q6asm_media_format_block_amrwbplus(audio->ac,
|
||||
&q6_amrwbplus_cfg);
|
||||
if (rc < 0) {
|
||||
pr_err("q6asm_media_format_block_amrwb+ failed...\n");
|
||||
break;
|
||||
}
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("%s:AUDIO_START sessionid[%d]enable[%d]\n", __func__,
|
||||
audio->ac->session,
|
||||
audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AMRWBPLUS_CONFIG_V2: {
|
||||
if ((audio) && (arg) && (audio->codec_cfg)) {
|
||||
if (copy_to_user((void *)arg, audio->codec_cfg,
|
||||
sizeof(struct msm_audio_amrwbplus_config_v2))) {
|
||||
rc = -EFAULT;
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AMRWBPLUS_CONFIG_V2 failed\n",
|
||||
__func__);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
pr_err("%s: wb+ config v2 invalid parameters\n"
|
||||
, __func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AMRWBPLUS_CONFIG_V2: {
|
||||
if ((audio) && (arg) && (audio->codec_cfg)) {
|
||||
if (copy_from_user(audio->codec_cfg, (void *)arg,
|
||||
sizeof(struct msm_audio_amrwbplus_config_v2))) {
|
||||
rc = -EFAULT;
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AMRWBPLUS_CONFIG_V2 failed\n",
|
||||
__func__);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
pr_err("%s: wb+ config invalid parameters\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_amrwbplus_config_v2_32 {
|
||||
u32 size_bytes;
|
||||
u32 version;
|
||||
u32 num_channels;
|
||||
u32 amr_band_mode;
|
||||
u32 amr_dtx_mode;
|
||||
u32 amr_frame_fmt;
|
||||
u32 amr_lsf_idx;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_GET_AMRWBPLUS_CONFIG_V2_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+2),
|
||||
struct msm_audio_amrwbplus_config_v2_32),
|
||||
AUDIO_SET_AMRWBPLUS_CONFIG_V2_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+3),
|
||||
struct msm_audio_amrwbplus_config_v2_32)
|
||||
};
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AMRWBPLUS_CONFIG_V2_32: {
|
||||
if (audio && arg && (audio->codec_cfg)) {
|
||||
struct msm_audio_amrwbplus_config_v2 *amrwbplus_config;
|
||||
struct msm_audio_amrwbplus_config_v2_32
|
||||
amrwbplus_config_32;
|
||||
|
||||
memset(&amrwbplus_config_32, 0,
|
||||
sizeof(amrwbplus_config_32));
|
||||
|
||||
amrwbplus_config =
|
||||
(struct msm_audio_amrwbplus_config_v2 *)
|
||||
audio->codec_cfg;
|
||||
amrwbplus_config_32.size_bytes =
|
||||
amrwbplus_config->size_bytes;
|
||||
amrwbplus_config_32.version =
|
||||
amrwbplus_config->version;
|
||||
amrwbplus_config_32.num_channels =
|
||||
amrwbplus_config->num_channels;
|
||||
amrwbplus_config_32.amr_band_mode =
|
||||
amrwbplus_config->amr_band_mode;
|
||||
amrwbplus_config_32.amr_dtx_mode =
|
||||
amrwbplus_config->amr_dtx_mode;
|
||||
amrwbplus_config_32.amr_frame_fmt =
|
||||
amrwbplus_config->amr_frame_fmt;
|
||||
amrwbplus_config_32.amr_lsf_idx =
|
||||
amrwbplus_config->amr_lsf_idx;
|
||||
|
||||
if (copy_to_user((void *)arg, &amrwbplus_config_32,
|
||||
sizeof(amrwbplus_config_32))) {
|
||||
rc = -EFAULT;
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AMRWBPLUS_CONFIG_V2_32 failed\n"
|
||||
, __func__);
|
||||
}
|
||||
} else {
|
||||
pr_err("%s: wb+ Get config v2 invalid parameters\n"
|
||||
, __func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AMRWBPLUS_CONFIG_V2_32: {
|
||||
if ((audio) && (arg) && (audio->codec_cfg)) {
|
||||
struct msm_audio_amrwbplus_config_v2 *amrwbplus_config;
|
||||
struct msm_audio_amrwbplus_config_v2_32
|
||||
amrwbplus_config_32;
|
||||
|
||||
if (copy_from_user(&amrwbplus_config_32, (void *)arg,
|
||||
sizeof(struct msm_audio_amrwbplus_config_v2_32))) {
|
||||
rc = -EFAULT;
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AMRWBPLUS_CONFIG_V2_32 failed\n"
|
||||
, __func__);
|
||||
break;
|
||||
}
|
||||
amrwbplus_config =
|
||||
(struct msm_audio_amrwbplus_config_v2 *)
|
||||
audio->codec_cfg;
|
||||
amrwbplus_config->size_bytes =
|
||||
amrwbplus_config_32.size_bytes;
|
||||
amrwbplus_config->version =
|
||||
amrwbplus_config_32.version;
|
||||
amrwbplus_config->num_channels =
|
||||
amrwbplus_config_32.num_channels;
|
||||
amrwbplus_config->amr_band_mode =
|
||||
amrwbplus_config_32.amr_band_mode;
|
||||
amrwbplus_config->amr_dtx_mode =
|
||||
amrwbplus_config_32.amr_dtx_mode;
|
||||
amrwbplus_config->amr_frame_fmt =
|
||||
amrwbplus_config_32.amr_frame_fmt;
|
||||
amrwbplus_config->amr_lsf_idx =
|
||||
amrwbplus_config_32.amr_lsf_idx;
|
||||
} else {
|
||||
pr_err("%s: wb+ config invalid parameters\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define audio_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->codec_cfg =
|
||||
kzalloc(sizeof(struct msm_audio_amrwbplus_config_v2), GFP_KERNEL);
|
||||
if (audio->codec_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_amrwbplus_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_amrwbplus_ws_mgr;
|
||||
|
||||
audio->ac =
|
||||
q6asm_audio_client_alloc((app_cb) q6_audio_cb, (void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_AMR_WB_PLUS);
|
||||
if (rc < 0) {
|
||||
pr_err("amrwbplus NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_AMR_WB_PLUS);
|
||||
if (rc < 0) {
|
||||
pr_err("wb+ T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("audio_amrwbplus Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
config_debug_fs(audio);
|
||||
pr_debug("%s: AMRWBPLUS dec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return 0;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_amrwbplus_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
.compat_ioctl = audio_compat_ioctl
|
||||
};
|
||||
|
||||
static struct miscdevice audio_amrwbplus_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_amrwbplus",
|
||||
.fops = &audio_amrwbplus_fops,
|
||||
};
|
||||
|
||||
static int __init audio_amrwbplus_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_amrwbplus_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_amrwbplus_misc.this_device, true);
|
||||
audio_amrwbplus_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_amrwbplus_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_amrwbplus_init);
|
||||
359
drivers/misc/qcom/qdsp6v2/audio_ape.c
Normal file
359
drivers/misc/qcom/qdsp6v2/audio_ape.c
Normal file
@@ -0,0 +1,359 @@
|
||||
/* 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/types.h>
|
||||
#include <linux/msm_audio_ape.h>
|
||||
#include <linux/compat.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_ape_misc;
|
||||
static struct ws_mgr audio_ape_ws_mgr;
|
||||
|
||||
static const struct file_operations audio_ape_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
static struct dentry *config_debugfs_create_file(const char *name, void *data)
|
||||
{
|
||||
return debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)data, &audio_ape_debug_fops);
|
||||
}
|
||||
|
||||
static long audio_ioctl_shared(struct file *file, unsigned int cmd,
|
||||
void *arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct asm_ape_cfg ape_cfg;
|
||||
struct msm_audio_ape_config *ape_config;
|
||||
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
ape_config = (struct msm_audio_ape_config *)audio->codec_cfg;
|
||||
ape_cfg.compatible_version = ape_config->compatibleVersion;
|
||||
ape_cfg.compression_level = ape_config->compressionLevel;
|
||||
ape_cfg.format_flags = ape_config->formatFlags;
|
||||
ape_cfg.blocks_per_frame = ape_config->blocksPerFrame;
|
||||
ape_cfg.final_frame_blocks = ape_config->finalFrameBlocks;
|
||||
ape_cfg.total_frames = ape_config->totalFrames;
|
||||
ape_cfg.bits_per_sample = ape_config->bitsPerSample;
|
||||
ape_cfg.num_channels = ape_config->numChannels;
|
||||
ape_cfg.sample_rate = ape_config->sampleRate;
|
||||
ape_cfg.seek_table_present = ape_config->seekTablePresent;
|
||||
pr_debug("%s: compatibleVersion %d compressionLevel %d formatFlags %d blocksPerFrame %d finalFrameBlocks %d totalFrames %d bitsPerSample %d numChannels %d sampleRate %d seekTablePresent %d\n",
|
||||
__func__, ape_config->compatibleVersion,
|
||||
ape_config->compressionLevel,
|
||||
ape_config->formatFlags,
|
||||
ape_config->blocksPerFrame,
|
||||
ape_config->finalFrameBlocks,
|
||||
ape_config->totalFrames,
|
||||
ape_config->bitsPerSample,
|
||||
ape_config->numChannels,
|
||||
ape_config->sampleRate,
|
||||
ape_config->seekTablePresent);
|
||||
/* Configure Media format block */
|
||||
rc = q6asm_media_format_block_ape(audio->ac, &ape_cfg,
|
||||
audio->ac->stream_id);
|
||||
if (rc < 0) {
|
||||
pr_err("cmd media format block failed\n");
|
||||
break;
|
||||
}
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("AUDIO_START success enable[%d]\n", audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_APE_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->codec_cfg,
|
||||
sizeof(struct msm_audio_ape_config))) {
|
||||
pr_err("%s:copy_to_user for AUDIO_GET_APE_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_APE_CONFIG: {
|
||||
if (copy_from_user(audio->codec_cfg, (void *)arg,
|
||||
sizeof(struct msm_audio_ape_config))) {
|
||||
pr_err("%s:copy_from_user for AUDIO_SET_APE_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("Failed in utils_ioctl: %d\n", rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_ape_config_32 {
|
||||
u16 compatibleVersion;
|
||||
u16 compressionLevel;
|
||||
u32 formatFlags;
|
||||
u32 blocksPerFrame;
|
||||
u32 finalFrameBlocks;
|
||||
u32 totalFrames;
|
||||
u16 bitsPerSample;
|
||||
u16 numChannels;
|
||||
u32 sampleRate;
|
||||
u32 seekTablePresent;
|
||||
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_GET_APE_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_ape_config_32),
|
||||
AUDIO_SET_APE_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_ape_config_32)
|
||||
};
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_APE_CONFIG_32: {
|
||||
struct msm_audio_ape_config *ape_config;
|
||||
struct msm_audio_ape_config_32 ape_config_32;
|
||||
|
||||
memset(&ape_config_32, 0, sizeof(ape_config_32));
|
||||
|
||||
ape_config = (struct msm_audio_ape_config *)audio->codec_cfg;
|
||||
ape_config_32.compatibleVersion = ape_config->compatibleVersion;
|
||||
ape_config_32.compressionLevel =
|
||||
ape_config->compressionLevel;
|
||||
ape_config_32.formatFlags = ape_config->formatFlags;
|
||||
ape_config_32.blocksPerFrame = ape_config->blocksPerFrame;
|
||||
ape_config_32.finalFrameBlocks = ape_config->finalFrameBlocks;
|
||||
ape_config_32.totalFrames = ape_config->totalFrames;
|
||||
ape_config_32.bitsPerSample = ape_config->bitsPerSample;
|
||||
ape_config_32.numChannels = ape_config->numChannels;
|
||||
ape_config_32.sampleRate = ape_config->sampleRate;
|
||||
ape_config_32.seekTablePresent = ape_config->seekTablePresent;
|
||||
|
||||
if (copy_to_user((void *)arg, &ape_config_32,
|
||||
sizeof(ape_config_32))) {
|
||||
pr_err("%s: copy_to_user for GET_APE_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_APE_CONFIG_32: {
|
||||
struct msm_audio_ape_config *ape_config;
|
||||
struct msm_audio_ape_config_32 ape_config_32;
|
||||
|
||||
if (copy_from_user(&ape_config_32, (void *)arg,
|
||||
sizeof(ape_config_32))) {
|
||||
pr_err("%s: copy_from_user for SET_APE_CONFIG_32 failed\n"
|
||||
, __func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
ape_config = (struct msm_audio_ape_config *)audio->codec_cfg;
|
||||
ape_config->compatibleVersion = ape_config_32.compatibleVersion;
|
||||
ape_config->compressionLevel =
|
||||
ape_config_32.compressionLevel;
|
||||
ape_config->formatFlags = ape_config_32.formatFlags;
|
||||
ape_config->blocksPerFrame = ape_config_32.blocksPerFrame;
|
||||
ape_config->finalFrameBlocks = ape_config_32.finalFrameBlocks;
|
||||
ape_config->totalFrames = ape_config_32.totalFrames;
|
||||
ape_config->bitsPerSample = ape_config_32.bitsPerSample;
|
||||
ape_config->numChannels = ape_config_32.numChannels;
|
||||
ape_config->sampleRate = ape_config_32.sampleRate;
|
||||
ape_config->seekTablePresent = ape_config_32.seekTablePresent;
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("Failed in utils_ioctl: %d\n", rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define audio_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_ape_" + 5];
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
if (!audio)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->codec_cfg = kzalloc(sizeof(struct msm_audio_ape_config),
|
||||
GFP_KERNEL);
|
||||
if (!audio->codec_cfg) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_ape_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_ape_ws_mgr;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_APE);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
/* open APE decoder, expected frames is always 1*/
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_APE);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), "msm_ape_%04x", audio->ac->session);
|
||||
audio->dentry = config_debugfs_create_file(name, (void *)audio);
|
||||
|
||||
if (IS_ERR_OR_NULL(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
pr_debug("%s:apedec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_ape_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
.compat_ioctl = audio_compat_ioctl
|
||||
};
|
||||
|
||||
static struct miscdevice audio_ape_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_ape",
|
||||
.fops = &audio_ape_fops,
|
||||
};
|
||||
|
||||
static int __init audio_ape_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_ape_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_ape_misc.this_device, true);
|
||||
audio_ape_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_ape_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_ape_init);
|
||||
184
drivers/misc/qcom/qdsp6v2/audio_evrc.c
Normal file
184
drivers/misc/qcom/qdsp6v2/audio_evrc.c
Normal file
@@ -0,0 +1,184 @@
|
||||
/* evrc audio output device
|
||||
*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
* Copyright (C) 2008 HTC Corporation
|
||||
* Copyright (c) 2011-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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_evrc_misc;
|
||||
static struct ws_mgr audio_evrc_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static const struct file_operations audio_evrc_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
#endif
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__,
|
||||
audio->ac->session,
|
||||
audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_evrc_" + 5];
|
||||
#endif
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Settings will be re-config at AUDIO_SET_CONFIG,
|
||||
* but at least we need to have initial config
|
||||
*/
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_evrc_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_evrc_ws_mgr;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_EVRC);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_EVRC);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
snprintf(name, sizeof(name), "msm_evrc_%04x", audio->ac->session);
|
||||
audio->dentry = debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)audio,
|
||||
&audio_evrc_debug_fops);
|
||||
|
||||
if (IS_ERR(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
#endif
|
||||
pr_info("%s:dec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_evrc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
};
|
||||
|
||||
static struct miscdevice audio_evrc_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_evrc",
|
||||
.fops = &audio_evrc_fops,
|
||||
};
|
||||
|
||||
static int __init audio_evrc_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_evrc_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_evrc_misc.this_device, true);
|
||||
audio_evrc_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_evrc_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_evrc_init);
|
||||
396
drivers/misc/qcom/qdsp6v2/audio_g711alaw.c
Normal file
396
drivers/misc/qcom/qdsp6v2/audio_g711alaw.c
Normal file
@@ -0,0 +1,396 @@
|
||||
/* 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/types.h>
|
||||
#include <linux/msm_audio_g711_dec.h>
|
||||
#include <linux/compat.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_g711alaw_misc;
|
||||
static struct ws_mgr audio_g711_ws_mgr;
|
||||
|
||||
static const struct file_operations audio_g711_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
|
||||
static struct dentry *config_debugfs_create_file(const char *name, void *data)
|
||||
{
|
||||
return debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)data, &audio_g711_debug_fops);
|
||||
}
|
||||
|
||||
static int g711_channel_map(u8 *channel_mapping, uint32_t channels);
|
||||
|
||||
static long audio_ioctl_shared(struct file *file, unsigned int cmd,
|
||||
void *arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct asm_g711_dec_cfg g711_dec_cfg;
|
||||
struct msm_audio_g711_dec_config *g711_dec_config;
|
||||
u8 channel_mapping[PCM_FORMAT_MAX_NUM_CHANNEL];
|
||||
|
||||
memset(channel_mapping, 0, PCM_FORMAT_MAX_NUM_CHANNEL);
|
||||
memset(&g711_dec_cfg, 0, sizeof(g711_dec_cfg));
|
||||
|
||||
if (g711_channel_map(channel_mapping,
|
||||
audio->pcm_cfg.channel_count)) {
|
||||
pr_err("%s: setting channel map failed %d\n",
|
||||
__func__, audio->pcm_cfg.channel_count);
|
||||
}
|
||||
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm_v2(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count,
|
||||
16, /*bits per sample*/
|
||||
false, false, channel_mapping);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: pcm output block config failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
g711_dec_config =
|
||||
(struct msm_audio_g711_dec_config *)audio->codec_cfg;
|
||||
g711_dec_cfg.sample_rate = g711_dec_config->sample_rate;
|
||||
/* Configure Media format block */
|
||||
rc = q6asm_media_format_block_g711(audio->ac, &g711_dec_cfg,
|
||||
audio->ac->stream_id);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: cmd media format block failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s: Audio Start procedure failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("%s: AUDIO_START success enable[%d]\n",
|
||||
__func__, audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_G711_DEC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->codec_cfg,
|
||||
sizeof(struct msm_audio_g711_dec_config))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_G711_DEC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_G711_DEC_CONFIG: {
|
||||
if (copy_from_user(audio->codec_cfg, (void *)arg,
|
||||
sizeof(struct msm_audio_g711_dec_config))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_G711_DEC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("%s: Failed in audio_aio_ioctl: %d cmd=%d\n",
|
||||
__func__, rc, cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_g711_dec_config_32 {
|
||||
u32 sample_rate;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_SET_G711_DEC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_g711_dec_config_32),
|
||||
AUDIO_GET_G711_DEC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_g711_dec_config_32)
|
||||
};
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_G711_DEC_CONFIG_32: {
|
||||
struct msm_audio_g711_dec_config *g711_dec_config;
|
||||
struct msm_audio_g711_dec_config_32 g711_dec_config_32;
|
||||
|
||||
memset(&g711_dec_config_32, 0, sizeof(g711_dec_config_32));
|
||||
|
||||
g711_dec_config =
|
||||
(struct msm_audio_g711_dec_config *)audio->codec_cfg;
|
||||
g711_dec_config_32.sample_rate = g711_dec_config->sample_rate;
|
||||
|
||||
if (copy_to_user((void *)arg, &g711_dec_config_32,
|
||||
sizeof(g711_dec_config_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_G711_DEC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_G711_DEC_CONFIG_32: {
|
||||
struct msm_audio_g711_dec_config *g711_dec_config;
|
||||
struct msm_audio_g711_dec_config_32 g711_dec_config_32;
|
||||
|
||||
memset(&g711_dec_config_32, 0, sizeof(g711_dec_config_32));
|
||||
|
||||
if (copy_from_user(&g711_dec_config_32, (void *)arg,
|
||||
sizeof(g711_dec_config_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_G711_DEC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
g711_dec_config =
|
||||
(struct msm_audio_g711_dec_config *)audio->codec_cfg;
|
||||
g711_dec_config->sample_rate = g711_dec_config_32.sample_rate;
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("%s: Failed in audio_aio_compat_ioctl: %d cmd=%d\n",
|
||||
__func__, rc, cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define audio_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_g711_" + 5];
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (!audio)
|
||||
return -ENOMEM;
|
||||
audio->codec_cfg = kzalloc(sizeof(struct msm_audio_g711_dec_config),
|
||||
GFP_KERNEL);
|
||||
if (!audio->codec_cfg) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_g711alaw_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_g711_ws_mgr;
|
||||
|
||||
init_waitqueue_head(&audio->event_wait);
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("%s: Could not allocate memory for audio client\n",
|
||||
__func__);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
/* open in T/NT mode */ /*foramt:G711_ALAW*/
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_G711_ALAW_FS);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: NT mode Open failed rc=%d\n", __func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
/* open G711 decoder, expected frames is always 1*/
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_G711_ALAW_FS);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: T mode Open failed rc=%d\n", __func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("%s: %d mode is not supported mode\n",
|
||||
__func__, file->f_mode);
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), "msm_g711_%04x", audio->ac->session);
|
||||
audio->dentry = config_debugfs_create_file(name, (void *)audio);
|
||||
|
||||
if (IS_ERR_OR_NULL(audio->dentry))
|
||||
pr_debug("%s: debugfs_create_file failed\n", __func__);
|
||||
pr_debug("%s: g711dec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int g711_channel_map(u8 *channel_mapping, uint32_t channels)
|
||||
{
|
||||
u8 *lchannel_mapping;
|
||||
|
||||
lchannel_mapping = channel_mapping;
|
||||
pr_debug("%s: channels passed: %d\n", __func__, channels);
|
||||
if (channels == 1) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
} else if (channels == 2) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FR;
|
||||
} else if (channels == 3) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
} else if (channels == 4) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_CS;
|
||||
} else if (channels == 5) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_RS;
|
||||
} else if (channels == 6) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_RS;
|
||||
lchannel_mapping[5] = PCM_CHANNEL_LFE;
|
||||
} else if (channels == 7) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_RS;
|
||||
lchannel_mapping[5] = PCM_CHANNEL_CS;
|
||||
lchannel_mapping[6] = PCM_CHANNEL_LFE;
|
||||
} else if (channels == 8) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FLC;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FRC;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[5] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[6] = PCM_CHANNEL_RS;
|
||||
lchannel_mapping[7] = PCM_CHANNEL_LFE;
|
||||
} else {
|
||||
pr_err("%s: ERROR.unsupported num_ch = %u\n",
|
||||
__func__, channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_g711_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.compat_ioctl = audio_compat_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
};
|
||||
|
||||
static struct miscdevice audio_g711alaw_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_g711alaw",
|
||||
.fops = &audio_g711_fops,
|
||||
};
|
||||
|
||||
static int __init audio_g711alaw_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_g711alaw_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_g711alaw_misc.this_device, true);
|
||||
audio_g711_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_g711_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
static void __exit audio_g711alaw_exit(void)
|
||||
{
|
||||
misc_deregister(&audio_g711alaw_misc);
|
||||
mutex_destroy(&audio_g711_ws_mgr.ws_lock);
|
||||
}
|
||||
|
||||
device_initcall(audio_g711alaw_init);
|
||||
__exitcall(audio_g711alaw_exit);
|
||||
396
drivers/misc/qcom/qdsp6v2/audio_g711mlaw.c
Normal file
396
drivers/misc/qcom/qdsp6v2/audio_g711mlaw.c
Normal file
@@ -0,0 +1,396 @@
|
||||
/* 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/types.h>
|
||||
#include <linux/msm_audio_g711_dec.h>
|
||||
#include <linux/compat.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_g711mlaw_misc;
|
||||
static struct ws_mgr audio_g711_ws_mgr;
|
||||
|
||||
static const struct file_operations audio_g711_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
|
||||
static struct dentry *config_debugfs_create_file(const char *name, void *data)
|
||||
{
|
||||
return debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)data, &audio_g711_debug_fops);
|
||||
}
|
||||
|
||||
static int g711_channel_map(u8 *channel_mapping, uint32_t channels);
|
||||
|
||||
static long audio_ioctl_shared(struct file *file, unsigned int cmd,
|
||||
void *arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct asm_g711_dec_cfg g711_dec_cfg;
|
||||
struct msm_audio_g711_dec_config *g711_dec_config;
|
||||
u8 channel_mapping[PCM_FORMAT_MAX_NUM_CHANNEL];
|
||||
|
||||
memset(channel_mapping, 0, PCM_FORMAT_MAX_NUM_CHANNEL);
|
||||
memset(&g711_dec_cfg, 0, sizeof(g711_dec_cfg));
|
||||
|
||||
if (g711_channel_map(channel_mapping,
|
||||
audio->pcm_cfg.channel_count)) {
|
||||
pr_err("%s: setting channel map failed %d\n",
|
||||
__func__, audio->pcm_cfg.channel_count);
|
||||
}
|
||||
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm_v2(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count,
|
||||
16, /*bits per sample*/
|
||||
false, false, channel_mapping);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: pcm output block config failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
g711_dec_config =
|
||||
(struct msm_audio_g711_dec_config *)audio->codec_cfg;
|
||||
g711_dec_cfg.sample_rate = g711_dec_config->sample_rate;
|
||||
/* Configure Media format block */
|
||||
rc = q6asm_media_format_block_g711(audio->ac, &g711_dec_cfg,
|
||||
audio->ac->stream_id);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: cmd media format block failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s: Audio Start procedure failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("%s: AUDIO_START success enable[%d]\n",
|
||||
__func__, audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_G711_DEC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->codec_cfg,
|
||||
sizeof(struct msm_audio_g711_dec_config))) {
|
||||
pr_err("%s: AUDIO_GET_G711_DEC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_G711_DEC_CONFIG: {
|
||||
if (copy_from_user(audio->codec_cfg, (void *)arg,
|
||||
sizeof(struct msm_audio_g711_dec_config))) {
|
||||
pr_err("%s: AUDIO_SET_G711_DEC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("%s: Failed in audio_aio_ioctl: %d cmd=%d\n",
|
||||
__func__, rc, cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_g711_dec_config_32 {
|
||||
u32 sample_rate;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_SET_G711_DEC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_g711_dec_config_32),
|
||||
AUDIO_GET_G711_DEC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_g711_dec_config_32)
|
||||
};
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_G711_DEC_CONFIG_32: {
|
||||
struct msm_audio_g711_dec_config *g711_dec_config;
|
||||
struct msm_audio_g711_dec_config_32 g711_dec_config_32;
|
||||
|
||||
memset(&g711_dec_config_32, 0, sizeof(g711_dec_config_32));
|
||||
|
||||
g711_dec_config =
|
||||
(struct msm_audio_g711_dec_config *)audio->codec_cfg;
|
||||
g711_dec_config_32.sample_rate = g711_dec_config->sample_rate;
|
||||
|
||||
if (copy_to_user((void *)arg, &g711_dec_config_32,
|
||||
sizeof(g711_dec_config_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_G711_DEC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_G711_DEC_CONFIG_32: {
|
||||
struct msm_audio_g711_dec_config *g711_dec_config;
|
||||
struct msm_audio_g711_dec_config_32 g711_dec_config_32;
|
||||
|
||||
memset(&g711_dec_config_32, 0, sizeof(g711_dec_config_32));
|
||||
|
||||
if (copy_from_user(&g711_dec_config_32, (void *)arg,
|
||||
sizeof(g711_dec_config_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_G711_DEC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
g711_dec_config =
|
||||
(struct msm_audio_g711_dec_config *)audio->codec_cfg;
|
||||
g711_dec_config->sample_rate = g711_dec_config_32.sample_rate;
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("%s: Failed in audio_aio_compat_ioctl: %d cmd=%d\n",
|
||||
__func__, rc, cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define audio_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_g711_" + 5];
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (!audio)
|
||||
return -ENOMEM;
|
||||
audio->codec_cfg = kzalloc(sizeof(struct msm_audio_g711_dec_config),
|
||||
GFP_KERNEL);
|
||||
if (!audio->codec_cfg) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_g711mlaw_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_g711_ws_mgr;
|
||||
|
||||
init_waitqueue_head(&audio->event_wait);
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("%s: Could not allocate memory for audio client\n",
|
||||
__func__);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
/* open in T/NT mode */ /*foramt:G711_ALAW*/
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_G711_MLAW_FS);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: NT mode Open failed rc=%d\n", __func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
/* open G711 decoder, expected frames is always 1*/
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_G711_MLAW_FS);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: T mode Open failed rc=%d\n", __func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("%s: %d mode is not supported\n", __func__,
|
||||
file->f_mode);
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), "msm_g711_%04x", audio->ac->session);
|
||||
audio->dentry = config_debugfs_create_file(name, (void *)audio);
|
||||
|
||||
if (IS_ERR_OR_NULL(audio->dentry))
|
||||
pr_debug("%s: debugfs_create_file failed\n", __func__);
|
||||
pr_debug("%s: g711dec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int g711_channel_map(u8 *channel_mapping, uint32_t channels)
|
||||
{
|
||||
u8 *lchannel_mapping;
|
||||
|
||||
lchannel_mapping = channel_mapping;
|
||||
pr_debug("%s: channels passed: %d\n", __func__, channels);
|
||||
if (channels == 1) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
} else if (channels == 2) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FR;
|
||||
} else if (channels == 3) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
} else if (channels == 4) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_CS;
|
||||
} else if (channels == 5) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_RS;
|
||||
} else if (channels == 6) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_RS;
|
||||
lchannel_mapping[5] = PCM_CHANNEL_LFE;
|
||||
} else if (channels == 7) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_RS;
|
||||
lchannel_mapping[5] = PCM_CHANNEL_CS;
|
||||
lchannel_mapping[6] = PCM_CHANNEL_LFE;
|
||||
} else if (channels == 8) {
|
||||
lchannel_mapping[0] = PCM_CHANNEL_FC;
|
||||
lchannel_mapping[1] = PCM_CHANNEL_FLC;
|
||||
lchannel_mapping[2] = PCM_CHANNEL_FRC;
|
||||
lchannel_mapping[3] = PCM_CHANNEL_FL;
|
||||
lchannel_mapping[4] = PCM_CHANNEL_FR;
|
||||
lchannel_mapping[5] = PCM_CHANNEL_LS;
|
||||
lchannel_mapping[6] = PCM_CHANNEL_RS;
|
||||
lchannel_mapping[7] = PCM_CHANNEL_LFE;
|
||||
} else {
|
||||
pr_err("%s: ERROR.unsupported num_ch = %u\n",
|
||||
__func__, channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_g711_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.compat_ioctl = audio_compat_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
};
|
||||
|
||||
static struct miscdevice audio_g711mlaw_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_g711mlaw",
|
||||
.fops = &audio_g711_fops,
|
||||
};
|
||||
|
||||
static int __init audio_g711mlaw_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_g711mlaw_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_g711mlaw_misc.this_device, true);
|
||||
audio_g711_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_g711_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit audio_g711mlaw_exit(void)
|
||||
{
|
||||
misc_deregister(&audio_g711mlaw_misc);
|
||||
mutex_destroy(&audio_g711_ws_mgr.ws_lock);
|
||||
}
|
||||
|
||||
device_initcall(audio_g711mlaw_init);
|
||||
__exitcall(audio_g711mlaw_exit);
|
||||
778
drivers/misc/qcom/qdsp6v2/audio_hwacc_effects.c
Normal file
778
drivers/misc/qcom/qdsp6v2/audio_hwacc_effects.c
Normal file
@@ -0,0 +1,778 @@
|
||||
/*
|
||||
* Copyright (c) 2014-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/msm_audio.h>
|
||||
#include <linux/compat.h>
|
||||
#include "q6audio_common.h"
|
||||
#include "audio_utils_aio.h"
|
||||
#include <sound/msm-audio-effects-q6-v2.h>
|
||||
|
||||
#define MAX_CHANNELS_SUPPORTED 8
|
||||
#define WAIT_TIMEDOUT_DURATION_SECS 1
|
||||
|
||||
struct q6audio_effects {
|
||||
wait_queue_head_t read_wait;
|
||||
wait_queue_head_t write_wait;
|
||||
|
||||
struct audio_client *ac;
|
||||
struct msm_hwacc_effects_config config;
|
||||
|
||||
struct mutex lock;
|
||||
|
||||
atomic_t in_count;
|
||||
atomic_t out_count;
|
||||
|
||||
int opened;
|
||||
int started;
|
||||
int buf_alloc;
|
||||
struct msm_nt_eff_all_config audio_effects;
|
||||
};
|
||||
|
||||
static void audio_effects_init_pp(struct audio_client *ac)
|
||||
{
|
||||
int ret = 0;
|
||||
struct asm_softvolume_params softvol = {
|
||||
.period = SOFT_VOLUME_PERIOD,
|
||||
.step = SOFT_VOLUME_STEP,
|
||||
.rampingcurve = SOFT_VOLUME_CURVE_LINEAR,
|
||||
};
|
||||
|
||||
if (!ac) {
|
||||
pr_err("%s: audio client null to init pp\n", __func__);
|
||||
return;
|
||||
}
|
||||
ret = q6asm_set_softvolume_v2(ac, &softvol,
|
||||
SOFT_VOLUME_INSTANCE_1);
|
||||
if (ret < 0)
|
||||
pr_err("%s: Send SoftVolume Param failed ret=%d\n",
|
||||
__func__, ret);
|
||||
}
|
||||
|
||||
static void audio_effects_deinit_pp(struct audio_client *ac)
|
||||
{
|
||||
if (!ac) {
|
||||
pr_err("%s: audio client null to deinit pp\n", __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_effects_event_handler(uint32_t opcode, uint32_t token,
|
||||
uint32_t *payload, void *priv)
|
||||
{
|
||||
struct q6audio_effects *effects;
|
||||
|
||||
if (!payload || !priv) {
|
||||
pr_err("%s: invalid data to handle events, payload: %pK, priv: %pK\n",
|
||||
__func__, payload, priv);
|
||||
return;
|
||||
}
|
||||
|
||||
effects = (struct q6audio_effects *)priv;
|
||||
switch (opcode) {
|
||||
case ASM_DATA_EVENT_WRITE_DONE_V2: {
|
||||
atomic_inc(&effects->out_count);
|
||||
wake_up(&effects->write_wait);
|
||||
break;
|
||||
}
|
||||
case ASM_DATA_EVENT_READ_DONE_V2: {
|
||||
atomic_inc(&effects->in_count);
|
||||
wake_up(&effects->read_wait);
|
||||
break;
|
||||
}
|
||||
case APR_BASIC_RSP_RESULT: {
|
||||
pr_debug("%s: APR_BASIC_RSP_RESULT Cmd[0x%x] Status[0x%x]\n",
|
||||
__func__, payload[0], payload[1]);
|
||||
switch (payload[0]) {
|
||||
case ASM_SESSION_CMD_RUN_V2:
|
||||
pr_debug("ASM_SESSION_CMD_RUN_V2\n");
|
||||
break;
|
||||
default:
|
||||
pr_debug("%s: Payload = [0x%x] stat[0x%x]\n",
|
||||
__func__, payload[0], payload[1]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s: Unhandled Event 0x%x token = 0x%x\n",
|
||||
__func__, opcode, token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int audio_effects_shared_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_effects *effects = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
pr_debug("%s: AUDIO_START\n", __func__);
|
||||
|
||||
mutex_lock(&effects->lock);
|
||||
|
||||
rc = q6asm_open_read_write_v2(effects->ac,
|
||||
FORMAT_LINEAR_PCM,
|
||||
FORMAT_MULTI_CHANNEL_LINEAR_PCM,
|
||||
effects->config.meta_mode_enabled,
|
||||
effects->config.output.bits_per_sample,
|
||||
true /*overwrite topology*/,
|
||||
ASM_STREAM_POSTPROC_TOPO_ID_HPX_MASTER);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: Open failed for hw accelerated effects:rc=%d\n",
|
||||
__func__, rc);
|
||||
rc = -EINVAL;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
effects->opened = 1;
|
||||
|
||||
pr_debug("%s: dec buf size: %d, num_buf: %d, enc buf size: %d, num_buf: %d\n",
|
||||
__func__, effects->config.output.buf_size,
|
||||
effects->config.output.num_buf,
|
||||
effects->config.input.buf_size,
|
||||
effects->config.input.num_buf);
|
||||
rc = q6asm_audio_client_buf_alloc_contiguous(IN, effects->ac,
|
||||
effects->config.output.buf_size,
|
||||
effects->config.output.num_buf);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: Write buffer Allocation failed rc = %d\n",
|
||||
__func__, rc);
|
||||
rc = -ENOMEM;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
atomic_set(&effects->in_count, effects->config.input.num_buf);
|
||||
rc = q6asm_audio_client_buf_alloc_contiguous(OUT, effects->ac,
|
||||
effects->config.input.buf_size,
|
||||
effects->config.input.num_buf);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: Read buffer Allocation failed rc = %d\n",
|
||||
__func__, rc);
|
||||
rc = -ENOMEM;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto readbuf_fail;
|
||||
}
|
||||
atomic_set(&effects->out_count, effects->config.output.num_buf);
|
||||
effects->buf_alloc = 1;
|
||||
|
||||
pr_debug("%s: enc: sample_rate: %d, num_channels: %d\n",
|
||||
__func__, effects->config.input.sample_rate,
|
||||
effects->config.input.num_channels);
|
||||
rc = q6asm_enc_cfg_blk_pcm(effects->ac,
|
||||
effects->config.input.sample_rate,
|
||||
effects->config.input.num_channels);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: pcm read block config failed\n", __func__);
|
||||
rc = -EINVAL;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto cfg_fail;
|
||||
}
|
||||
pr_debug("%s: dec: sample_rate: %d, num_channels: %d, bit_width: %d\n",
|
||||
__func__, effects->config.output.sample_rate,
|
||||
effects->config.output.num_channels,
|
||||
effects->config.output.bits_per_sample);
|
||||
rc = q6asm_media_format_block_pcm_format_support(
|
||||
effects->ac, effects->config.output.sample_rate,
|
||||
effects->config.output.num_channels,
|
||||
effects->config.output.bits_per_sample);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: pcm write format block config failed\n",
|
||||
__func__);
|
||||
rc = -EINVAL;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto cfg_fail;
|
||||
}
|
||||
|
||||
audio_effects_init_pp(effects->ac);
|
||||
|
||||
rc = q6asm_run(effects->ac, 0x00, 0x00, 0x00);
|
||||
if (!rc)
|
||||
effects->started = 1;
|
||||
else {
|
||||
effects->started = 0;
|
||||
pr_err("%s: ASM run state failed\n", __func__);
|
||||
}
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
case AUDIO_EFFECTS_WRITE: {
|
||||
char *bufptr = NULL;
|
||||
uint32_t idx = 0;
|
||||
uint32_t size = 0;
|
||||
|
||||
mutex_lock(&effects->lock);
|
||||
|
||||
if (!effects->started) {
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
|
||||
rc = wait_event_timeout(effects->write_wait,
|
||||
atomic_read(&effects->out_count),
|
||||
WAIT_TIMEDOUT_DURATION_SECS * HZ);
|
||||
if (!rc) {
|
||||
pr_err("%s: write wait_event_timeout\n", __func__);
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
if (!atomic_read(&effects->out_count)) {
|
||||
pr_err("%s: pcm stopped out_count 0\n", __func__);
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
|
||||
bufptr = q6asm_is_cpu_buf_avail(IN, effects->ac, &size, &idx);
|
||||
if (bufptr) {
|
||||
if ((effects->config.buf_cfg.output_len > size) ||
|
||||
copy_from_user(bufptr, (void *)arg,
|
||||
effects->config.buf_cfg.output_len)) {
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
rc = q6asm_write(effects->ac,
|
||||
effects->config.buf_cfg.output_len,
|
||||
0, 0, NO_TIMESTAMP);
|
||||
if (rc < 0) {
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
atomic_dec(&effects->out_count);
|
||||
} else {
|
||||
pr_err("%s: AUDIO_EFFECTS_WRITE: Buffer dropped\n",
|
||||
__func__);
|
||||
}
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
case AUDIO_EFFECTS_READ: {
|
||||
char *bufptr = NULL;
|
||||
uint32_t idx = 0;
|
||||
uint32_t size = 0;
|
||||
|
||||
mutex_lock(&effects->lock);
|
||||
|
||||
if (!effects->started) {
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
|
||||
atomic_set(&effects->in_count, 0);
|
||||
|
||||
q6asm_read_v2(effects->ac, effects->config.buf_cfg.input_len);
|
||||
/* Read might fail initially, don't error out */
|
||||
if (rc < 0)
|
||||
pr_err("%s: read failed\n", __func__);
|
||||
|
||||
rc = wait_event_timeout(effects->read_wait,
|
||||
atomic_read(&effects->in_count),
|
||||
WAIT_TIMEDOUT_DURATION_SECS * HZ);
|
||||
if (!rc) {
|
||||
pr_err("%s: read wait_event_timeout\n", __func__);
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
if (!atomic_read(&effects->in_count)) {
|
||||
pr_err("%s: pcm stopped in_count 0\n", __func__);
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
|
||||
bufptr = q6asm_is_cpu_buf_avail(OUT, effects->ac, &size, &idx);
|
||||
if (bufptr) {
|
||||
if (!((void *)arg)) {
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
if ((effects->config.buf_cfg.input_len > size) ||
|
||||
copy_to_user((void *)arg, bufptr,
|
||||
effects->config.buf_cfg.input_len)) {
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
goto ioctl_fail;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Invalid effects config module\n", __func__);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
ioctl_fail:
|
||||
return rc;
|
||||
readbuf_fail:
|
||||
q6asm_audio_client_buf_free_contiguous(IN,
|
||||
effects->ac);
|
||||
return rc;
|
||||
cfg_fail:
|
||||
q6asm_audio_client_buf_free_contiguous(IN,
|
||||
effects->ac);
|
||||
q6asm_audio_client_buf_free_contiguous(OUT,
|
||||
effects->ac);
|
||||
effects->buf_alloc = 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_effects_set_pp_param(struct q6audio_effects *effects,
|
||||
long *values)
|
||||
{
|
||||
int rc = 0;
|
||||
int effects_module = values[0];
|
||||
|
||||
switch (effects_module) {
|
||||
case VIRTUALIZER_MODULE:
|
||||
pr_debug("%s: VIRTUALIZER_MODULE\n", __func__);
|
||||
if (msm_audio_effects_is_effmodule_supp_in_top(
|
||||
effects_module, effects->ac->topology))
|
||||
msm_audio_effects_virtualizer_handler(
|
||||
effects->ac,
|
||||
&(effects->audio_effects.virtualizer),
|
||||
(long *)&values[1]);
|
||||
break;
|
||||
case REVERB_MODULE:
|
||||
pr_debug("%s: REVERB_MODULE\n", __func__);
|
||||
if (msm_audio_effects_is_effmodule_supp_in_top(
|
||||
effects_module, effects->ac->topology))
|
||||
msm_audio_effects_reverb_handler(effects->ac,
|
||||
&(effects->audio_effects.reverb),
|
||||
(long *)&values[1]);
|
||||
break;
|
||||
case BASS_BOOST_MODULE:
|
||||
pr_debug("%s: BASS_BOOST_MODULE\n", __func__);
|
||||
if (msm_audio_effects_is_effmodule_supp_in_top(
|
||||
effects_module, effects->ac->topology))
|
||||
msm_audio_effects_bass_boost_handler(
|
||||
effects->ac,
|
||||
&(effects->audio_effects.bass_boost),
|
||||
(long *)&values[1]);
|
||||
break;
|
||||
case PBE_MODULE:
|
||||
pr_debug("%s: PBE_MODULE\n", __func__);
|
||||
if (msm_audio_effects_is_effmodule_supp_in_top(
|
||||
effects_module, effects->ac->topology))
|
||||
msm_audio_effects_pbe_handler(
|
||||
effects->ac,
|
||||
&(effects->audio_effects.pbe),
|
||||
(long *)&values[1]);
|
||||
break;
|
||||
case EQ_MODULE:
|
||||
pr_debug("%s: EQ_MODULE\n", __func__);
|
||||
if (msm_audio_effects_is_effmodule_supp_in_top(
|
||||
effects_module, effects->ac->topology))
|
||||
msm_audio_effects_popless_eq_handler(
|
||||
effects->ac,
|
||||
&(effects->audio_effects.equalizer),
|
||||
(long *)&values[1]);
|
||||
break;
|
||||
case SOFT_VOLUME_MODULE:
|
||||
pr_debug("%s: SA PLUS VOLUME_MODULE\n", __func__);
|
||||
msm_audio_effects_volume_handler_v2(effects->ac,
|
||||
&(effects->audio_effects.saplus_vol),
|
||||
(long *)&values[1], SOFT_VOLUME_INSTANCE_1);
|
||||
break;
|
||||
case SOFT_VOLUME2_MODULE:
|
||||
pr_debug("%s: TOPOLOGY SWITCH VOLUME MODULE\n",
|
||||
__func__);
|
||||
if (msm_audio_effects_is_effmodule_supp_in_top(
|
||||
effects_module, effects->ac->topology))
|
||||
msm_audio_effects_volume_handler_v2(effects->ac,
|
||||
&(effects->audio_effects.topo_switch_vol),
|
||||
(long *)&values[1], SOFT_VOLUME_INSTANCE_2);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Invalid effects config module\n", __func__);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_effects_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_effects *effects = file->private_data;
|
||||
int rc = 0;
|
||||
long argvalues[MAX_PP_PARAMS_SZ] = {0};
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_SET_EFFECTS_CONFIG: {
|
||||
pr_debug("%s: AUDIO_SET_EFFECTS_CONFIG\n", __func__);
|
||||
mutex_lock(&effects->lock);
|
||||
memset(&effects->config, 0, sizeof(effects->config));
|
||||
if (copy_from_user(&effects->config, (void *)arg,
|
||||
sizeof(effects->config))) {
|
||||
pr_err("%s: copy from user for AUDIO_SET_EFFECTS_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
pr_debug("%s: write buf_size: %d, num_buf: %d, sample_rate: %d, channel: %d\n",
|
||||
__func__, effects->config.output.buf_size,
|
||||
effects->config.output.num_buf,
|
||||
effects->config.output.sample_rate,
|
||||
effects->config.output.num_channels);
|
||||
pr_debug("%s: read buf_size: %d, num_buf: %d, sample_rate: %d, channel: %d\n",
|
||||
__func__, effects->config.input.buf_size,
|
||||
effects->config.input.num_buf,
|
||||
effects->config.input.sample_rate,
|
||||
effects->config.input.num_channels);
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
case AUDIO_EFFECTS_SET_BUF_LEN: {
|
||||
mutex_lock(&effects->lock);
|
||||
if (copy_from_user(&effects->config.buf_cfg, (void *)arg,
|
||||
sizeof(effects->config.buf_cfg))) {
|
||||
pr_err("%s: copy from user for AUDIO_EFFECTS_SET_BUF_LEN failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
pr_debug("%s: write buf len: %d, read buf len: %d\n",
|
||||
__func__, effects->config.buf_cfg.output_len,
|
||||
effects->config.buf_cfg.input_len);
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
case AUDIO_EFFECTS_GET_BUF_AVAIL: {
|
||||
struct msm_hwacc_buf_avail buf_avail;
|
||||
|
||||
buf_avail.input_num_avail = atomic_read(&effects->in_count);
|
||||
buf_avail.output_num_avail = atomic_read(&effects->out_count);
|
||||
mutex_lock(&effects->lock);
|
||||
pr_debug("%s: write buf avail: %d, read buf avail: %d\n",
|
||||
__func__, buf_avail.output_num_avail,
|
||||
buf_avail.input_num_avail);
|
||||
if (copy_to_user((void *)arg, &buf_avail,
|
||||
sizeof(buf_avail))) {
|
||||
pr_err("%s: copy to user for AUDIO_EFFECTS_GET_NUM_BUF_AVAIL failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
case AUDIO_EFFECTS_SET_PP_PARAMS: {
|
||||
mutex_lock(&effects->lock);
|
||||
if (copy_from_user(argvalues, (void *)arg,
|
||||
MAX_PP_PARAMS_SZ*sizeof(long))) {
|
||||
pr_err("%s: copy from user for pp params failed\n",
|
||||
__func__);
|
||||
mutex_unlock(&effects->lock);
|
||||
return -EFAULT;
|
||||
}
|
||||
rc = audio_effects_set_pp_param(effects, argvalues);
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s: Calling shared ioctl\n", __func__);
|
||||
rc = audio_effects_shared_ioctl(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
if (rc)
|
||||
pr_err("%s: cmd 0x%x failed\n", __func__, cmd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_hwacc_data_config32 {
|
||||
__u32 buf_size;
|
||||
__u32 num_buf;
|
||||
__u32 num_channels;
|
||||
__u8 channel_map[MAX_CHANNELS_SUPPORTED];
|
||||
__u32 sample_rate;
|
||||
__u32 bits_per_sample;
|
||||
};
|
||||
|
||||
struct msm_hwacc_buf_cfg32 {
|
||||
__u32 input_len;
|
||||
__u32 output_len;
|
||||
};
|
||||
|
||||
struct msm_hwacc_buf_avail32 {
|
||||
__u32 input_num_avail;
|
||||
__u32 output_num_avail;
|
||||
};
|
||||
|
||||
struct msm_hwacc_effects_config32 {
|
||||
struct msm_hwacc_data_config32 input;
|
||||
struct msm_hwacc_data_config32 output;
|
||||
struct msm_hwacc_buf_cfg32 buf_cfg;
|
||||
__u32 meta_mode_enabled;
|
||||
__u32 overwrite_topology;
|
||||
__s32 topology;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_SET_EFFECTS_CONFIG32 = _IOW(AUDIO_IOCTL_MAGIC, 99,
|
||||
struct msm_hwacc_effects_config32),
|
||||
AUDIO_EFFECTS_SET_BUF_LEN32 = _IOW(AUDIO_IOCTL_MAGIC, 100,
|
||||
struct msm_hwacc_buf_cfg32),
|
||||
AUDIO_EFFECTS_GET_BUF_AVAIL32 = _IOW(AUDIO_IOCTL_MAGIC, 101,
|
||||
struct msm_hwacc_buf_avail32),
|
||||
AUDIO_EFFECTS_WRITE32 = _IOW(AUDIO_IOCTL_MAGIC, 102, compat_uptr_t),
|
||||
AUDIO_EFFECTS_READ32 = _IOWR(AUDIO_IOCTL_MAGIC, 103, compat_uptr_t),
|
||||
AUDIO_EFFECTS_SET_PP_PARAMS32 = _IOW(AUDIO_IOCTL_MAGIC, 104,
|
||||
compat_uptr_t),
|
||||
AUDIO_START32 = _IOW(AUDIO_IOCTL_MAGIC, 0, unsigned int),
|
||||
};
|
||||
|
||||
static long audio_effects_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_effects *effects = file->private_data;
|
||||
int rc = 0, i;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_SET_EFFECTS_CONFIG32: {
|
||||
struct msm_hwacc_effects_config32 config32;
|
||||
struct msm_hwacc_effects_config *config = &effects->config;
|
||||
|
||||
mutex_lock(&effects->lock);
|
||||
memset(&effects->config, 0, sizeof(effects->config));
|
||||
if (copy_from_user(&config32, (void *)arg,
|
||||
sizeof(config32))) {
|
||||
pr_err("%s: copy to user for AUDIO_SET_EFFECTS_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
config->input.buf_size = config32.input.buf_size;
|
||||
config->input.num_buf = config32.input.num_buf;
|
||||
config->input.num_channels = config32.input.num_channels;
|
||||
config->input.sample_rate = config32.input.sample_rate;
|
||||
config->input.bits_per_sample = config32.input.bits_per_sample;
|
||||
config->input.buf_size = config32.input.buf_size;
|
||||
for (i = 0; i < MAX_CHANNELS_SUPPORTED; i++)
|
||||
config->input.channel_map[i] =
|
||||
config32.input.channel_map[i];
|
||||
config->output.buf_size = config32.output.buf_size;
|
||||
config->output.num_buf = config32.output.num_buf;
|
||||
config->output.num_channels = config32.output.num_channels;
|
||||
config->output.sample_rate = config32.output.sample_rate;
|
||||
config->output.bits_per_sample =
|
||||
config32.output.bits_per_sample;
|
||||
config->output.buf_size = config32.output.buf_size;
|
||||
for (i = 0; i < MAX_CHANNELS_SUPPORTED; i++)
|
||||
config->output.channel_map[i] =
|
||||
config32.output.channel_map[i];
|
||||
config->buf_cfg.input_len = config32.buf_cfg.input_len;
|
||||
config->buf_cfg.output_len = config32.buf_cfg.output_len;
|
||||
config->meta_mode_enabled = config32.meta_mode_enabled;
|
||||
config->overwrite_topology = config32.overwrite_topology;
|
||||
config->topology = config32.topology;
|
||||
pr_debug("%s: write buf_size: %d, num_buf: %d, sample_rate: %d, channels: %d\n",
|
||||
__func__, effects->config.output.buf_size,
|
||||
effects->config.output.num_buf,
|
||||
effects->config.output.sample_rate,
|
||||
effects->config.output.num_channels);
|
||||
pr_debug("%s: read buf_size: %d, num_buf: %d, sample_rate: %d, channels: %d\n",
|
||||
__func__, effects->config.input.buf_size,
|
||||
effects->config.input.num_buf,
|
||||
effects->config.input.sample_rate,
|
||||
effects->config.input.num_channels);
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
case AUDIO_EFFECTS_SET_BUF_LEN32: {
|
||||
struct msm_hwacc_buf_cfg32 buf_cfg32;
|
||||
struct msm_hwacc_effects_config *config = &effects->config;
|
||||
|
||||
mutex_lock(&effects->lock);
|
||||
if (copy_from_user(&buf_cfg32, (void *)arg,
|
||||
sizeof(buf_cfg32))) {
|
||||
pr_err("%s: copy from user for AUDIO_EFFECTS_SET_BUF_LEN failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
config->buf_cfg.input_len = buf_cfg32.input_len;
|
||||
config->buf_cfg.output_len = buf_cfg32.output_len;
|
||||
pr_debug("%s: write buf len: %d, read buf len: %d\n",
|
||||
__func__, effects->config.buf_cfg.output_len,
|
||||
effects->config.buf_cfg.input_len);
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
case AUDIO_EFFECTS_GET_BUF_AVAIL32: {
|
||||
struct msm_hwacc_buf_avail32 buf_avail;
|
||||
|
||||
memset(&buf_avail, 0, sizeof(buf_avail));
|
||||
|
||||
mutex_lock(&effects->lock);
|
||||
buf_avail.input_num_avail = atomic_read(&effects->in_count);
|
||||
buf_avail.output_num_avail = atomic_read(&effects->out_count);
|
||||
pr_debug("%s: write buf avail: %d, read buf avail: %d\n",
|
||||
__func__, buf_avail.output_num_avail,
|
||||
buf_avail.input_num_avail);
|
||||
if (copy_to_user((void *)arg, &buf_avail,
|
||||
sizeof(buf_avail))) {
|
||||
pr_err("%s: copy to user for AUDIO_EFFECTS_GET_NUM_BUF_AVAIL failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
case AUDIO_EFFECTS_SET_PP_PARAMS32: {
|
||||
long argvalues[MAX_PP_PARAMS_SZ] = {0};
|
||||
int argvalues32[MAX_PP_PARAMS_SZ] = {0};
|
||||
|
||||
mutex_lock(&effects->lock);
|
||||
if (copy_from_user(argvalues32, (void *)arg,
|
||||
MAX_PP_PARAMS_SZ*sizeof(int))) {
|
||||
pr_err("%s: copy from user failed for pp params\n",
|
||||
__func__);
|
||||
mutex_unlock(&effects->lock);
|
||||
return -EFAULT;
|
||||
}
|
||||
for (i = 0; i < MAX_PP_PARAMS_SZ; i++)
|
||||
argvalues[i] = argvalues32[i];
|
||||
|
||||
rc = audio_effects_set_pp_param(effects, argvalues);
|
||||
mutex_unlock(&effects->lock);
|
||||
break;
|
||||
}
|
||||
case AUDIO_START32: {
|
||||
rc = audio_effects_shared_ioctl(file, AUDIO_START, arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_EFFECTS_WRITE32: {
|
||||
rc = audio_effects_shared_ioctl(file, AUDIO_EFFECTS_WRITE, arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_EFFECTS_READ32: {
|
||||
rc = audio_effects_shared_ioctl(file, AUDIO_EFFECTS_READ, arg);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s: unhandled ioctl\n", __func__);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int audio_effects_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_effects *effects = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
if (!effects) {
|
||||
pr_err("%s: effect is NULL\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (effects->opened) {
|
||||
rc = wait_event_timeout(effects->write_wait,
|
||||
atomic_read(&effects->out_count),
|
||||
WAIT_TIMEDOUT_DURATION_SECS * HZ);
|
||||
if (!rc)
|
||||
pr_err("%s: write wait_event_timeout failed\n",
|
||||
__func__);
|
||||
rc = wait_event_timeout(effects->read_wait,
|
||||
atomic_read(&effects->in_count),
|
||||
WAIT_TIMEDOUT_DURATION_SECS * HZ);
|
||||
if (!rc)
|
||||
pr_err("%s: read wait_event_timeout failed\n",
|
||||
__func__);
|
||||
rc = q6asm_cmd(effects->ac, CMD_CLOSE);
|
||||
if (rc < 0)
|
||||
pr_err("%s[%pK]:Failed to close the session rc=%d\n",
|
||||
__func__, effects, rc);
|
||||
effects->opened = 0;
|
||||
effects->started = 0;
|
||||
|
||||
audio_effects_deinit_pp(effects->ac);
|
||||
}
|
||||
|
||||
if (effects->buf_alloc) {
|
||||
q6asm_audio_client_buf_free_contiguous(IN, effects->ac);
|
||||
q6asm_audio_client_buf_free_contiguous(OUT, effects->ac);
|
||||
}
|
||||
q6asm_audio_client_free(effects->ac);
|
||||
|
||||
mutex_destroy(&effects->lock);
|
||||
kfree(effects);
|
||||
|
||||
pr_debug("%s: close session success\n", __func__);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int audio_effects_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_effects *effects;
|
||||
int rc = 0;
|
||||
|
||||
effects = kzalloc(sizeof(struct q6audio_effects), GFP_KERNEL);
|
||||
if (!effects)
|
||||
return -ENOMEM;
|
||||
|
||||
effects->ac = q6asm_audio_client_alloc(
|
||||
(app_cb)audio_effects_event_handler,
|
||||
(void *)effects);
|
||||
if (!effects->ac) {
|
||||
pr_err("%s: Could not allocate memory for audio client\n",
|
||||
__func__);
|
||||
kfree(effects);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
init_waitqueue_head(&effects->read_wait);
|
||||
init_waitqueue_head(&effects->write_wait);
|
||||
mutex_init(&effects->lock);
|
||||
|
||||
effects->opened = 0;
|
||||
effects->started = 0;
|
||||
effects->buf_alloc = 0;
|
||||
file->private_data = effects;
|
||||
pr_debug("%s: open session success\n", __func__);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_effects_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_effects_open,
|
||||
.release = audio_effects_release,
|
||||
.unlocked_ioctl = audio_effects_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = audio_effects_compat_ioctl,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct miscdevice audio_effects_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_hweffects",
|
||||
.fops = &audio_effects_fops,
|
||||
};
|
||||
|
||||
static int __init audio_effects_init(void)
|
||||
{
|
||||
return misc_register(&audio_effects_misc);
|
||||
}
|
||||
|
||||
device_initcall(audio_effects_init);
|
||||
MODULE_DESCRIPTION("Audio hardware accelerated effects driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
188
drivers/misc/qcom/qdsp6v2/audio_mp3.c
Normal file
188
drivers/misc/qcom/qdsp6v2/audio_mp3.c
Normal file
@@ -0,0 +1,188 @@
|
||||
/* mp3 audio output device
|
||||
*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
* Copyright (C) 2008 HTC Corporation
|
||||
* Copyright (c) 2011-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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_mp3_misc;
|
||||
static struct ws_mgr audio_mp3_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static const struct file_operations audio_mp3_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
#endif
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
rc = enable_volume_ramp(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: Failed to enable volume ramp\n",
|
||||
__func__);
|
||||
}
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_info("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__,
|
||||
audio->ac->session,
|
||||
audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_mp3_" + 5];
|
||||
#endif
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_mp3_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_mp3_ws_mgr;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_MP3);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
/* open MP3 decoder, expected frames is always 1
|
||||
* audio->buf_cfg.frames_per_buf = 0x01;
|
||||
*/
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_MP3);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
snprintf(name, sizeof(name), "msm_mp3_%04x", audio->ac->session);
|
||||
audio->dentry = debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)audio,
|
||||
&audio_mp3_debug_fops);
|
||||
|
||||
if (IS_ERR(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
#endif
|
||||
pr_info("%s:mp3dec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_mp3_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
};
|
||||
|
||||
static struct miscdevice audio_mp3_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_mp3",
|
||||
.fops = &audio_mp3_fops,
|
||||
};
|
||||
|
||||
static int __init audio_mp3_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_mp3_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_mp3_misc.this_device, true);
|
||||
audio_mp3_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_mp3_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_mp3_init);
|
||||
523
drivers/misc/qcom/qdsp6v2/audio_multi_aac.c
Normal file
523
drivers/misc/qcom/qdsp6v2/audio_multi_aac.c
Normal file
@@ -0,0 +1,523 @@
|
||||
/* aac audio output device
|
||||
*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
* Copyright (C) 2008 HTC Corporation
|
||||
* Copyright (c) 2011-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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/msm_audio_aac.h>
|
||||
#include <linux/compat.h>
|
||||
#include <soc/qcom/socinfo.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
#define AUDIO_AAC_DUAL_MONO_INVALID -1
|
||||
|
||||
|
||||
/* Default number of pre-allocated event packets */
|
||||
#define PCM_BUFSZ_MIN_AACM ((8*1024) + sizeof(struct dec_meta_out))
|
||||
static struct miscdevice audio_multiaac_misc;
|
||||
static struct ws_mgr audio_multiaac_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static const struct file_operations audio_aac_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
#endif
|
||||
|
||||
static long audio_ioctl_shared(struct file *file, unsigned int cmd,
|
||||
void *arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct asm_aac_cfg aac_cfg;
|
||||
struct msm_audio_aac_config *aac_config;
|
||||
uint32_t sbr_ps = 0x00;
|
||||
|
||||
aac_config = (struct msm_audio_aac_config *)audio->codec_cfg;
|
||||
if (audio->feedback == TUNNEL_MODE) {
|
||||
aac_cfg.sample_rate = aac_config->sample_rate;
|
||||
aac_cfg.ch_cfg = aac_config->channel_configuration;
|
||||
} else {
|
||||
aac_cfg.sample_rate = audio->pcm_cfg.sample_rate;
|
||||
aac_cfg.ch_cfg = audio->pcm_cfg.channel_count;
|
||||
}
|
||||
pr_debug("%s: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm_native(audio->ac,
|
||||
aac_cfg.sample_rate,
|
||||
aac_cfg.ch_cfg);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* turn on both sbr and ps */
|
||||
rc = q6asm_enable_sbrps(audio->ac, sbr_ps);
|
||||
if (rc < 0)
|
||||
pr_err("sbr-ps enable failed\n");
|
||||
if (aac_config->sbr_ps_on_flag)
|
||||
aac_cfg.aot = AAC_ENC_MODE_EAAC_P;
|
||||
else if (aac_config->sbr_on_flag)
|
||||
aac_cfg.aot = AAC_ENC_MODE_AAC_P;
|
||||
else
|
||||
aac_cfg.aot = AAC_ENC_MODE_AAC_LC;
|
||||
|
||||
switch (aac_config->format) {
|
||||
case AUDIO_AAC_FORMAT_ADTS:
|
||||
aac_cfg.format = 0x00;
|
||||
break;
|
||||
case AUDIO_AAC_FORMAT_LOAS:
|
||||
aac_cfg.format = 0x01;
|
||||
break;
|
||||
case AUDIO_AAC_FORMAT_ADIF:
|
||||
aac_cfg.format = 0x02;
|
||||
break;
|
||||
default:
|
||||
case AUDIO_AAC_FORMAT_RAW:
|
||||
aac_cfg.format = 0x03;
|
||||
}
|
||||
aac_cfg.ep_config = aac_config->ep_config;
|
||||
aac_cfg.section_data_resilience =
|
||||
aac_config->aac_section_data_resilience_flag;
|
||||
aac_cfg.scalefactor_data_resilience =
|
||||
aac_config->aac_scalefactor_data_resilience_flag;
|
||||
aac_cfg.spectral_data_resilience =
|
||||
aac_config->aac_spectral_data_resilience_flag;
|
||||
|
||||
pr_debug("%s:format=%x aot=%d ch=%d sr=%d\n",
|
||||
__func__, aac_cfg.format,
|
||||
aac_cfg.aot, aac_cfg.ch_cfg,
|
||||
aac_cfg.sample_rate);
|
||||
|
||||
/* Configure Media format block */
|
||||
rc = q6asm_media_format_block_multi_aac(audio->ac, &aac_cfg);
|
||||
if (rc < 0) {
|
||||
pr_err("cmd media format block failed\n");
|
||||
break;
|
||||
}
|
||||
rc = q6asm_set_encdec_chan_map(audio->ac, 2);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: cmd set encdec_chan_map failed\n",
|
||||
__func__);
|
||||
break;
|
||||
}
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_info("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__,
|
||||
audio->ac->session,
|
||||
audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_CONFIG: {
|
||||
struct msm_audio_aac_config *aac_config;
|
||||
uint16_t sce_left = 1, sce_right = 2;
|
||||
|
||||
if (arg == NULL) {
|
||||
pr_err("%s: NULL config pointer\n", __func__);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
memcpy(audio->codec_cfg, arg,
|
||||
sizeof(struct msm_audio_aac_config));
|
||||
aac_config = audio->codec_cfg;
|
||||
if (aac_config->dual_mono_mode >
|
||||
AUDIO_AAC_DUAL_MONO_PL_SR) {
|
||||
pr_err("%s:AUDIO_SET_AAC_CONFIG: Invalid dual_mono mode =%d\n",
|
||||
__func__, aac_config->dual_mono_mode);
|
||||
} else {
|
||||
/* convert the data from user into sce_left
|
||||
* and sce_right based on the definitions
|
||||
*/
|
||||
pr_debug("%s: AUDIO_SET_AAC_CONFIG: modify dual_mono mode =%d\n",
|
||||
__func__, aac_config->dual_mono_mode);
|
||||
switch (aac_config->dual_mono_mode) {
|
||||
case AUDIO_AAC_DUAL_MONO_PL_PR:
|
||||
sce_left = 1;
|
||||
sce_right = 1;
|
||||
break;
|
||||
case AUDIO_AAC_DUAL_MONO_SL_SR:
|
||||
sce_left = 2;
|
||||
sce_right = 2;
|
||||
break;
|
||||
case AUDIO_AAC_DUAL_MONO_SL_PR:
|
||||
sce_left = 2;
|
||||
sce_right = 1;
|
||||
break;
|
||||
case AUDIO_AAC_DUAL_MONO_PL_SR:
|
||||
default:
|
||||
sce_left = 1;
|
||||
sce_right = 2;
|
||||
break;
|
||||
}
|
||||
rc = q6asm_cfg_dual_mono_aac(audio->ac,
|
||||
sce_left, sce_right);
|
||||
if (rc < 0)
|
||||
pr_err("%s: asm cmd dualmono failed rc=%d\n",
|
||||
__func__, rc);
|
||||
} break;
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_MIX_CONFIG: {
|
||||
u32 *mix_coeff = (u32 *)arg;
|
||||
|
||||
if (!arg) {
|
||||
pr_err("%s: Invalid param for %s\n",
|
||||
__func__, "AUDIO_SET_AAC_MIX_CONFIG");
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
pr_debug("%s, AUDIO_SET_AAC_MIX_CONFIG", __func__);
|
||||
pr_debug("%s, value of coeff = %d",
|
||||
__func__, *mix_coeff);
|
||||
q6asm_cfg_aac_sel_mix_coef(audio->ac, *mix_coeff);
|
||||
if (rc < 0)
|
||||
pr_err("%s asm aac_sel_mix_coef failed rc=%d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AAC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->codec_cfg,
|
||||
sizeof(struct msm_audio_aac_config))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AAC_CONFIG failed\n"
|
||||
, __func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_CONFIG: {
|
||||
struct msm_audio_aac_config aac_config;
|
||||
|
||||
if (copy_from_user(&aac_config, (void *)arg,
|
||||
sizeof(aac_config))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AAC_CONFIG failed\n"
|
||||
, __func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
rc = audio_ioctl_shared(file, cmd, &aac_config);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_CONFIG failed. Rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_MIX_CONFIG: {
|
||||
u32 mix_config;
|
||||
|
||||
pr_debug("%s, AUDIO_SET_AAC_MIX_CONFIG", __func__);
|
||||
if (copy_from_user(&mix_config, (void *)arg,
|
||||
sizeof(u32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AAC_MIX_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = audio_ioctl_shared(file, cmd, &mix_config);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_CONFIG failed. Rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("Calling utils ioctl\n");
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_aac_config32 {
|
||||
s16 format;
|
||||
u16 audio_object;
|
||||
u16 ep_config; /* 0 ~ 3 useful only obj = ERLC */
|
||||
u16 aac_section_data_resilience_flag;
|
||||
u16 aac_scalefactor_data_resilience_flag;
|
||||
u16 aac_spectral_data_resilience_flag;
|
||||
u16 sbr_on_flag;
|
||||
u16 sbr_ps_on_flag;
|
||||
u16 dual_mono_mode;
|
||||
u16 channel_configuration;
|
||||
u16 sample_rate;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_SET_AAC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_aac_config32),
|
||||
AUDIO_GET_AAC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_aac_config32),
|
||||
};
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_AAC_CONFIG_32: {
|
||||
struct msm_audio_aac_config *aac_config;
|
||||
struct msm_audio_aac_config32 aac_config_32;
|
||||
|
||||
memset(&aac_config_32, 0, sizeof(aac_config_32));
|
||||
|
||||
aac_config = (struct msm_audio_aac_config *)audio->codec_cfg;
|
||||
aac_config_32.format = aac_config->format;
|
||||
aac_config_32.audio_object = aac_config->audio_object;
|
||||
aac_config_32.ep_config = aac_config->ep_config;
|
||||
aac_config_32.aac_section_data_resilience_flag =
|
||||
aac_config->aac_section_data_resilience_flag;
|
||||
aac_config_32.aac_scalefactor_data_resilience_flag =
|
||||
aac_config->aac_scalefactor_data_resilience_flag;
|
||||
aac_config_32.aac_spectral_data_resilience_flag =
|
||||
aac_config->aac_spectral_data_resilience_flag;
|
||||
aac_config_32.sbr_on_flag = aac_config->sbr_on_flag;
|
||||
aac_config_32.sbr_ps_on_flag = aac_config->sbr_ps_on_flag;
|
||||
aac_config_32.dual_mono_mode = aac_config->dual_mono_mode;
|
||||
aac_config_32.channel_configuration =
|
||||
aac_config->channel_configuration;
|
||||
aac_config_32.sample_rate = aac_config->sample_rate;
|
||||
|
||||
if (copy_to_user((void *)arg, &aac_config_32,
|
||||
sizeof(aac_config_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_AAC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_CONFIG_32: {
|
||||
struct msm_audio_aac_config aac_config;
|
||||
struct msm_audio_aac_config32 aac_config_32;
|
||||
|
||||
pr_debug("%s: AUDIO_SET_AAC_CONFIG\n", __func__);
|
||||
if (copy_from_user(&aac_config_32, (void *)arg,
|
||||
sizeof(aac_config_32))) {
|
||||
pr_err(
|
||||
"%s: copy_from_user for AUDIO_SET_AAC_CONFIG_32 failed",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
aac_config.format = aac_config_32.format;
|
||||
aac_config.audio_object = aac_config_32.audio_object;
|
||||
aac_config.ep_config = aac_config_32.ep_config;
|
||||
aac_config.aac_section_data_resilience_flag =
|
||||
aac_config_32.aac_section_data_resilience_flag;
|
||||
aac_config.aac_scalefactor_data_resilience_flag =
|
||||
aac_config_32.aac_scalefactor_data_resilience_flag;
|
||||
aac_config.aac_spectral_data_resilience_flag =
|
||||
aac_config_32.aac_spectral_data_resilience_flag;
|
||||
aac_config.sbr_on_flag = aac_config_32.sbr_on_flag;
|
||||
aac_config.sbr_ps_on_flag = aac_config_32.sbr_ps_on_flag;
|
||||
aac_config.dual_mono_mode = aac_config_32.dual_mono_mode;
|
||||
aac_config.channel_configuration =
|
||||
aac_config_32.channel_configuration;
|
||||
aac_config.sample_rate = aac_config_32.sample_rate;
|
||||
|
||||
cmd = AUDIO_SET_AAC_CONFIG;
|
||||
rc = audio_ioctl_shared(file, cmd, &aac_config);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_CONFIG failed. rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_AAC_MIX_CONFIG: {
|
||||
u32 mix_config;
|
||||
|
||||
pr_debug("%s, AUDIO_SET_AAC_MIX_CONFIG\n", __func__);
|
||||
if (copy_from_user(&mix_config, (void *)arg,
|
||||
sizeof(u32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_AAC_MIX_CONFIG failed\n"
|
||||
, __func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = audio_ioctl_shared(file, cmd, &mix_config);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_AAC_CONFIG failed. Rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("Calling utils ioctl\n");
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define audio_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
struct msm_audio_aac_config *aac_config = NULL;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_multi_aac_" + 5];
|
||||
#endif
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->codec_cfg = kzalloc(sizeof(struct msm_audio_aac_config),
|
||||
GFP_KERNEL);
|
||||
if (audio->codec_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
aac_config = audio->codec_cfg;
|
||||
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN_AACM;
|
||||
audio->miscdevice = &audio_multiaac_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_multiaac_ws_mgr;
|
||||
aac_config->dual_mono_mode = AUDIO_AAC_DUAL_MONO_INVALID;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_MPEG4_MULTI_AAC);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
/* open AAC decoder, expected frames is always 1
|
||||
* audio->buf_cfg.frames_per_buf = 0x01;
|
||||
*/
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_MPEG4_MULTI_AAC);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
snprintf(name, sizeof(name), "msm_multi_aac_%04x", audio->ac->session);
|
||||
audio->dentry = debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)audio,
|
||||
&audio_aac_debug_fops);
|
||||
|
||||
if (IS_ERR(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
#endif
|
||||
pr_info("%s:AAC 5.1 Decoder OPEN success mode[%d]session[%d]\n",
|
||||
__func__, audio->feedback, audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_aac_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
.compat_ioctl = audio_compat_ioctl
|
||||
};
|
||||
|
||||
static struct miscdevice audio_multiaac_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_multi_aac",
|
||||
.fops = &audio_aac_fops,
|
||||
};
|
||||
|
||||
static int __init audio_aac_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_multiaac_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_multiaac_misc.this_device, true);
|
||||
audio_multiaac_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_multiaac_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_aac_init);
|
||||
191
drivers/misc/qcom/qdsp6v2/audio_qcelp.c
Normal file
191
drivers/misc/qcom/qdsp6v2/audio_qcelp.c
Normal file
@@ -0,0 +1,191 @@
|
||||
/* qcelp(v13k) audio output device
|
||||
*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
* Copyright (C) 2008 HTC Corporation
|
||||
* Copyright (c) 2011-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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
#define FRAME_SIZE_DEC_QCELP ((32) + sizeof(struct dec_meta_in))
|
||||
|
||||
static struct miscdevice audio_qcelp_misc;
|
||||
static struct ws_mgr audio_qcelp_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static const struct file_operations audio_qcelp_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
#endif
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__,
|
||||
audio->ac->session,
|
||||
audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_qcelp_" + 5];
|
||||
#endif
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Settings will be re-config at AUDIO_SET_CONFIG,
|
||||
* but at least we need to have initial config
|
||||
*/
|
||||
audio->str_cfg.buffer_size = FRAME_SIZE_DEC_QCELP;
|
||||
audio->str_cfg.buffer_count = FRAME_NUM;
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->pcm_cfg.buffer_count = PCM_BUF_COUNT;
|
||||
audio->pcm_cfg.sample_rate = 8000;
|
||||
audio->pcm_cfg.channel_count = 1;
|
||||
audio->miscdevice = &audio_qcelp_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_qcelp_ws_mgr;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_V13K);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_V13K);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
snprintf(name, sizeof(name), "msm_qcelp_%04x", audio->ac->session);
|
||||
audio->dentry = debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)audio,
|
||||
&audio_qcelp_debug_fops);
|
||||
|
||||
if (IS_ERR(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
#endif
|
||||
pr_info("%s:dec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return 0;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_qcelp_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
};
|
||||
|
||||
static struct miscdevice audio_qcelp_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_qcelp",
|
||||
.fops = &audio_qcelp_fops,
|
||||
};
|
||||
|
||||
static int __init audio_qcelp_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_qcelp_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_qcelp_misc.this_device, true);
|
||||
audio_qcelp_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_qcelp_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_qcelp_init);
|
||||
954
drivers/misc/qcom/qdsp6v2/audio_utils.c
Normal file
954
drivers/misc/qcom/qdsp6v2/audio_utils.c
Normal file
@@ -0,0 +1,954 @@
|
||||
/* Copyright (c) 2010-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/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/compat.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include "audio_utils.h"
|
||||
|
||||
/*
|
||||
* Define maximum buffer size. Below values are chosen considering the higher
|
||||
* values used among all native drivers.
|
||||
*/
|
||||
#define MAX_FRAME_SIZE 1536
|
||||
#define MAX_FRAMES 5
|
||||
#define META_SIZE (sizeof(struct meta_out_dsp))
|
||||
#define MAX_BUFFER_SIZE (1 + ((MAX_FRAME_SIZE + META_SIZE) * MAX_FRAMES))
|
||||
|
||||
static int audio_in_pause(struct q6audio_in *audio)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = q6asm_cmd(audio->ac, CMD_PAUSE);
|
||||
if (rc < 0)
|
||||
pr_err("%s:session id %d: pause cmd failed rc=%d\n", __func__,
|
||||
audio->ac->session, rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int audio_in_flush(struct q6audio_in *audio)
|
||||
{
|
||||
int rc;
|
||||
|
||||
pr_debug("%s:session id %d: flush\n", __func__, audio->ac->session);
|
||||
/* Flush if session running */
|
||||
if (audio->enabled) {
|
||||
/* Implicitly issue a pause to the encoder before flushing */
|
||||
rc = audio_in_pause(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: pause cmd failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = q6asm_cmd(audio->ac, CMD_FLUSH);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: flush cmd failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
return rc;
|
||||
}
|
||||
/* 2nd arg: 0 -> run immediately
|
||||
* 3rd arg: 0 -> msw_ts,
|
||||
* 4th arg: 0 ->lsw_ts
|
||||
*/
|
||||
q6asm_run(audio->ac, 0x00, 0x00, 0x00);
|
||||
pr_debug("Rerun the session\n");
|
||||
}
|
||||
audio->rflush = 1;
|
||||
audio->wflush = 1;
|
||||
memset(audio->out_frame_info, 0, sizeof(audio->out_frame_info));
|
||||
wake_up(&audio->read_wait);
|
||||
/* get read_lock to ensure no more waiting read thread */
|
||||
mutex_lock(&audio->read_lock);
|
||||
audio->rflush = 0;
|
||||
mutex_unlock(&audio->read_lock);
|
||||
wake_up(&audio->write_wait);
|
||||
/* get write_lock to ensure no more waiting write thread */
|
||||
mutex_lock(&audio->write_lock);
|
||||
audio->wflush = 0;
|
||||
mutex_unlock(&audio->write_lock);
|
||||
pr_debug("%s:session id %d: in_bytes %d\n", __func__,
|
||||
audio->ac->session, atomic_read(&audio->in_bytes));
|
||||
pr_debug("%s:session id %d: in_samples %d\n", __func__,
|
||||
audio->ac->session, atomic_read(&audio->in_samples));
|
||||
atomic_set(&audio->in_bytes, 0);
|
||||
atomic_set(&audio->in_samples, 0);
|
||||
atomic_set(&audio->out_count, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* must be called with audio->lock held */
|
||||
int audio_in_enable(struct q6audio_in *audio)
|
||||
{
|
||||
if (audio->enabled)
|
||||
return 0;
|
||||
|
||||
/* 2nd arg: 0 -> run immediately
|
||||
* 3rd arg: 0 -> msw_ts,
|
||||
* 4th arg: 0 ->lsw_ts
|
||||
*/
|
||||
return q6asm_run(audio->ac, 0x00, 0x00, 0x00);
|
||||
}
|
||||
|
||||
/* must be called with audio->lock held */
|
||||
int audio_in_disable(struct q6audio_in *audio)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (!audio->stopped) {
|
||||
audio->enabled = 0;
|
||||
audio->opened = 0;
|
||||
pr_debug("%s:session id %d: inbytes[%d] insamples[%d]\n",
|
||||
__func__, audio->ac->session,
|
||||
atomic_read(&audio->in_bytes),
|
||||
atomic_read(&audio->in_samples));
|
||||
|
||||
rc = q6asm_cmd(audio->ac, CMD_CLOSE);
|
||||
if (rc < 0)
|
||||
pr_err("%s:session id %d: Failed to close the session rc=%d\n",
|
||||
__func__, audio->ac->session,
|
||||
rc);
|
||||
audio->stopped = 1;
|
||||
memset(audio->out_frame_info, 0,
|
||||
sizeof(audio->out_frame_info));
|
||||
wake_up(&audio->read_wait);
|
||||
wake_up(&audio->write_wait);
|
||||
}
|
||||
pr_debug("%s:session id %d: enabled[%d]\n", __func__,
|
||||
audio->ac->session, audio->enabled);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int audio_in_buf_alloc(struct q6audio_in *audio)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
switch (audio->buf_alloc) {
|
||||
case NO_BUF_ALLOC:
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
rc = q6asm_audio_client_buf_alloc(IN,
|
||||
audio->ac,
|
||||
ALIGN_BUF_SIZE(audio->pcm_cfg.buffer_size),
|
||||
audio->pcm_cfg.buffer_count);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Buffer Alloc failed\n",
|
||||
__func__,
|
||||
audio->ac->session);
|
||||
rc = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
audio->buf_alloc |= BUF_ALLOC_IN;
|
||||
}
|
||||
rc = q6asm_audio_client_buf_alloc(OUT, audio->ac,
|
||||
ALIGN_BUF_SIZE(audio->str_cfg.buffer_size),
|
||||
audio->str_cfg.buffer_count);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Buffer Alloc failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
audio->buf_alloc |= BUF_ALLOC_OUT;
|
||||
break;
|
||||
case BUF_ALLOC_IN:
|
||||
rc = q6asm_audio_client_buf_alloc(OUT, audio->ac,
|
||||
ALIGN_BUF_SIZE(audio->str_cfg.buffer_size),
|
||||
audio->str_cfg.buffer_count);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Buffer Alloc failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
audio->buf_alloc |= BUF_ALLOC_OUT;
|
||||
break;
|
||||
case BUF_ALLOC_OUT:
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
rc = q6asm_audio_client_buf_alloc(IN, audio->ac,
|
||||
ALIGN_BUF_SIZE(audio->pcm_cfg.buffer_size),
|
||||
audio->pcm_cfg.buffer_count);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Buffer Alloc failed\n",
|
||||
__func__,
|
||||
audio->ac->session);
|
||||
rc = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
audio->buf_alloc |= BUF_ALLOC_IN;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pr_debug("%s:session id %d: buf[%d]\n", __func__,
|
||||
audio->ac->session, audio->buf_alloc);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int audio_in_set_config(struct file *file,
|
||||
struct msm_audio_config *cfg)
|
||||
{
|
||||
int rc = 0;
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
|
||||
if (audio->feedback != NON_TUNNEL_MODE) {
|
||||
pr_err("%s:session id %d: Not sufficient permission to change the record mode\n",
|
||||
__func__, audio->ac->session);
|
||||
rc = -EACCES;
|
||||
goto ret;
|
||||
}
|
||||
if ((cfg->buffer_count > PCM_BUF_COUNT) ||
|
||||
(cfg->buffer_count == 1))
|
||||
cfg->buffer_count = PCM_BUF_COUNT;
|
||||
|
||||
audio->pcm_cfg.buffer_count = cfg->buffer_count;
|
||||
audio->pcm_cfg.buffer_size = cfg->buffer_size;
|
||||
audio->pcm_cfg.channel_count = cfg->channel_count;
|
||||
audio->pcm_cfg.sample_rate = cfg->sample_rate;
|
||||
if (audio->opened && audio->feedback == NON_TUNNEL_MODE) {
|
||||
rc = q6asm_audio_client_buf_alloc(IN, audio->ac,
|
||||
ALIGN_BUF_SIZE(audio->pcm_cfg.buffer_size),
|
||||
audio->pcm_cfg.buffer_count);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Buffer Alloc failed\n",
|
||||
__func__, audio->ac->session);
|
||||
rc = -ENOMEM;
|
||||
goto ret;
|
||||
}
|
||||
}
|
||||
audio->buf_alloc |= BUF_ALLOC_IN;
|
||||
rc = 0;
|
||||
pr_debug("%s:session id %d: AUDIO_SET_CONFIG %d %d\n", __func__,
|
||||
audio->ac->session, audio->pcm_cfg.buffer_count,
|
||||
audio->pcm_cfg.buffer_size);
|
||||
ret:
|
||||
return rc;
|
||||
}
|
||||
/* ------------------- device --------------------- */
|
||||
static long audio_in_ioctl_shared(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_FLUSH: {
|
||||
/* Make sure we're stopped and we wake any threads
|
||||
* that might be blocked holding the read_lock.
|
||||
* While audio->stopped read threads will always
|
||||
* exit immediately.
|
||||
*/
|
||||
rc = audio_in_flush(audio);
|
||||
if (rc < 0)
|
||||
pr_err("%s:session id %d: Flush Fail rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
else { /* Register back the flushed read buffer with DSP */
|
||||
int cnt = 0;
|
||||
|
||||
while (cnt++ < audio->str_cfg.buffer_count)
|
||||
q6asm_read(audio->ac); /* Push buffer to DSP */
|
||||
pr_debug("register the read buffer\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_PAUSE: {
|
||||
pr_debug("%s:session id %d: AUDIO_PAUSE\n", __func__,
|
||||
audio->ac->session);
|
||||
if (audio->enabled)
|
||||
audio_in_pause(audio);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_SESSION_ID: {
|
||||
if (copy_to_user((void *) arg, &audio->ac->session,
|
||||
sizeof(u16))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_SESSION_ID failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
long audio_in_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
if (cmd == AUDIO_GET_STATS) {
|
||||
struct msm_audio_stats stats;
|
||||
|
||||
memset(&stats, 0, sizeof(stats));
|
||||
stats.byte_count = atomic_read(&audio->in_bytes);
|
||||
stats.sample_count = atomic_read(&audio->in_samples);
|
||||
if (copy_to_user((void *) arg, &stats, sizeof(stats)))
|
||||
return -EFAULT;
|
||||
return rc;
|
||||
}
|
||||
|
||||
mutex_lock(&audio->lock);
|
||||
switch (cmd) {
|
||||
case AUDIO_FLUSH:
|
||||
case AUDIO_PAUSE:
|
||||
case AUDIO_GET_SESSION_ID:
|
||||
rc = audio_in_ioctl_shared(file, cmd, arg);
|
||||
break;
|
||||
case AUDIO_GET_STREAM_CONFIG: {
|
||||
struct msm_audio_stream_config cfg;
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.buffer_size = audio->str_cfg.buffer_size;
|
||||
cfg.buffer_count = audio->str_cfg.buffer_count;
|
||||
if (copy_to_user((void *)arg, &cfg, sizeof(cfg)))
|
||||
rc = -EFAULT;
|
||||
pr_debug("%s:session id %d: AUDIO_GET_STREAM_CONFIG %d %d\n",
|
||||
__func__, audio->ac->session, cfg.buffer_size,
|
||||
cfg.buffer_count);
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_STREAM_CONFIG: {
|
||||
struct msm_audio_stream_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_STREAM_CONFIG failed\n"
|
||||
, __func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
/* Minimum single frame size,
|
||||
* but with in maximum frames number
|
||||
*/
|
||||
if ((cfg.buffer_size < (audio->min_frame_size +
|
||||
sizeof(struct meta_out_dsp))) ||
|
||||
(cfg.buffer_count < FRAME_NUM)) {
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (cfg.buffer_size > MAX_BUFFER_SIZE) {
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
audio->str_cfg.buffer_size = cfg.buffer_size;
|
||||
audio->str_cfg.buffer_count = cfg.buffer_count;
|
||||
if (audio->opened) {
|
||||
rc = q6asm_audio_client_buf_alloc(OUT, audio->ac,
|
||||
ALIGN_BUF_SIZE(audio->str_cfg.buffer_size),
|
||||
audio->str_cfg.buffer_count);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: session id %d: Buffer Alloc failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
audio->buf_alloc |= BUF_ALLOC_OUT;
|
||||
rc = 0;
|
||||
pr_debug("%s:session id %d: AUDIO_SET_STREAM_CONFIG %d %d\n",
|
||||
__func__, audio->ac->session,
|
||||
audio->str_cfg.buffer_size,
|
||||
audio->str_cfg.buffer_count);
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_BUF_CFG: {
|
||||
struct msm_audio_buf_cfg cfg;
|
||||
|
||||
if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
if ((audio->feedback == NON_TUNNEL_MODE) &&
|
||||
!cfg.meta_info_enable) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Restrict the num of frames per buf to coincide with
|
||||
* default buf size
|
||||
*/
|
||||
if (cfg.frames_per_buf > audio->max_frames_per_buf) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
audio->buf_cfg.meta_info_enable = cfg.meta_info_enable;
|
||||
audio->buf_cfg.frames_per_buf = cfg.frames_per_buf;
|
||||
pr_debug("%s:session id %d: Set-buf-cfg: meta[%d] framesperbuf[%d]\n",
|
||||
__func__,
|
||||
audio->ac->session, cfg.meta_info_enable,
|
||||
cfg.frames_per_buf);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_BUF_CFG: {
|
||||
pr_debug("%s:session id %d: Get-buf-cfg: meta[%d] framesperbuf[%d]\n",
|
||||
__func__,
|
||||
audio->ac->session, audio->buf_cfg.meta_info_enable,
|
||||
audio->buf_cfg.frames_per_buf);
|
||||
|
||||
if (copy_to_user((void *)arg, &audio->buf_cfg,
|
||||
sizeof(struct msm_audio_buf_cfg)))
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_CONFIG: {
|
||||
if (copy_to_user((void *)arg, &audio->pcm_cfg,
|
||||
sizeof(struct msm_audio_config)))
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
|
||||
}
|
||||
case AUDIO_SET_CONFIG: {
|
||||
struct msm_audio_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = audio_in_set_config(file, &cfg);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
/* call codec specific ioctl */
|
||||
rc = audio->enc_ioctl(file, cmd, arg);
|
||||
}
|
||||
mutex_unlock(&audio->lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_stats32 {
|
||||
u32 byte_count;
|
||||
u32 sample_count;
|
||||
u32 unused[2];
|
||||
};
|
||||
|
||||
struct msm_audio_stream_config32 {
|
||||
u32 buffer_size;
|
||||
u32 buffer_count;
|
||||
};
|
||||
|
||||
struct msm_audio_config32 {
|
||||
u32 buffer_size;
|
||||
u32 buffer_count;
|
||||
u32 channel_count;
|
||||
u32 sample_rate;
|
||||
u32 type;
|
||||
u32 meta_field;
|
||||
u32 bits;
|
||||
u32 unused[3];
|
||||
};
|
||||
|
||||
struct msm_audio_buf_cfg32 {
|
||||
u32 meta_info_enable;
|
||||
u32 frames_per_buf;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_GET_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC, 3,
|
||||
struct msm_audio_config32),
|
||||
AUDIO_SET_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC, 4,
|
||||
struct msm_audio_config32),
|
||||
AUDIO_GET_STATS_32 = _IOR(AUDIO_IOCTL_MAGIC, 5,
|
||||
struct msm_audio_stats32),
|
||||
AUDIO_SET_STREAM_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC, 80,
|
||||
struct msm_audio_stream_config32),
|
||||
AUDIO_GET_STREAM_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC, 81,
|
||||
struct msm_audio_stream_config32),
|
||||
AUDIO_SET_BUF_CFG_32 = _IOW(AUDIO_IOCTL_MAGIC, 94,
|
||||
struct msm_audio_buf_cfg32),
|
||||
AUDIO_GET_BUF_CFG_32 = _IOW(AUDIO_IOCTL_MAGIC, 93,
|
||||
struct msm_audio_buf_cfg32),
|
||||
};
|
||||
|
||||
long audio_in_compat_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
if (cmd == AUDIO_GET_STATS_32) {
|
||||
struct msm_audio_stats32 stats_32;
|
||||
|
||||
memset(&stats_32, 0, sizeof(stats_32));
|
||||
stats_32.byte_count = atomic_read(&audio->in_bytes);
|
||||
stats_32.sample_count = atomic_read(&audio->in_samples);
|
||||
if (copy_to_user((void *) arg, &stats_32, sizeof(stats_32))) {
|
||||
pr_err("%s: copy_to_user failed for AUDIO_GET_STATS_32\n",
|
||||
__func__);
|
||||
return -EFAULT;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
mutex_lock(&audio->lock);
|
||||
switch (cmd) {
|
||||
case AUDIO_FLUSH:
|
||||
case AUDIO_PAUSE:
|
||||
case AUDIO_GET_SESSION_ID:
|
||||
rc = audio_in_ioctl_shared(file, cmd, arg);
|
||||
break;
|
||||
case AUDIO_GET_STREAM_CONFIG_32: {
|
||||
struct msm_audio_stream_config32 cfg_32;
|
||||
|
||||
memset(&cfg_32, 0, sizeof(cfg_32));
|
||||
cfg_32.buffer_size = audio->str_cfg.buffer_size;
|
||||
cfg_32.buffer_count = audio->str_cfg.buffer_count;
|
||||
if (copy_to_user((void *)arg, &cfg_32, sizeof(cfg_32))) {
|
||||
pr_err("%s: Copy to user failed\n", __func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
pr_debug("%s:session id %d: AUDIO_GET_STREAM_CONFIG %d %d\n",
|
||||
__func__, audio->ac->session,
|
||||
cfg_32.buffer_size,
|
||||
cfg_32.buffer_count);
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_STREAM_CONFIG_32: {
|
||||
struct msm_audio_stream_config32 cfg_32;
|
||||
struct msm_audio_stream_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg_32, (void *)arg, sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_STREAM_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
cfg.buffer_size = cfg_32.buffer_size;
|
||||
cfg.buffer_count = cfg_32.buffer_count;
|
||||
/* Minimum single frame size,
|
||||
* but with in maximum frames number
|
||||
*/
|
||||
if ((cfg.buffer_size < (audio->min_frame_size +
|
||||
sizeof(struct meta_out_dsp))) ||
|
||||
(cfg.buffer_count < FRAME_NUM)) {
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
audio->str_cfg.buffer_size = cfg.buffer_size;
|
||||
audio->str_cfg.buffer_count = cfg.buffer_count;
|
||||
if (audio->opened) {
|
||||
rc = q6asm_audio_client_buf_alloc(OUT, audio->ac,
|
||||
ALIGN_BUF_SIZE(audio->str_cfg.buffer_size),
|
||||
audio->str_cfg.buffer_count);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: session id %d:\n",
|
||||
__func__, audio->ac->session);
|
||||
pr_err("Buffer Alloc failed rc=%d\n", rc);
|
||||
rc = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
audio->buf_alloc |= BUF_ALLOC_OUT;
|
||||
pr_debug("%s:session id %d: AUDIO_SET_STREAM_CONFIG %d %d\n",
|
||||
__func__, audio->ac->session,
|
||||
audio->str_cfg.buffer_size,
|
||||
audio->str_cfg.buffer_count);
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_BUF_CFG_32: {
|
||||
struct msm_audio_buf_cfg32 cfg_32;
|
||||
struct msm_audio_buf_cfg cfg;
|
||||
|
||||
if (copy_from_user(&cfg_32, (void *)arg, sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_BUG_CFG_32 failed",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
cfg.meta_info_enable = cfg_32.meta_info_enable;
|
||||
cfg.frames_per_buf = cfg_32.frames_per_buf;
|
||||
|
||||
if ((audio->feedback == NON_TUNNEL_MODE) &&
|
||||
!cfg.meta_info_enable) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Restrict the num of frames per buf to coincide with
|
||||
* default buf size
|
||||
*/
|
||||
if (cfg.frames_per_buf > audio->max_frames_per_buf) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
audio->buf_cfg.meta_info_enable = cfg.meta_info_enable;
|
||||
audio->buf_cfg.frames_per_buf = cfg.frames_per_buf;
|
||||
pr_debug("%s:session id %d: Set-buf-cfg: meta[%d] framesperbuf[%d]\n",
|
||||
__func__, audio->ac->session, cfg.meta_info_enable,
|
||||
cfg.frames_per_buf);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_BUF_CFG_32: {
|
||||
struct msm_audio_buf_cfg32 cfg_32;
|
||||
|
||||
pr_debug("%s:session id %d: Get-buf-cfg: meta[%d] framesperbuf[%d]\n",
|
||||
__func__,
|
||||
audio->ac->session, audio->buf_cfg.meta_info_enable,
|
||||
audio->buf_cfg.frames_per_buf);
|
||||
cfg_32.meta_info_enable = audio->buf_cfg.meta_info_enable;
|
||||
cfg_32.frames_per_buf = audio->buf_cfg.frames_per_buf;
|
||||
|
||||
if (copy_to_user((void *)arg, &cfg_32,
|
||||
sizeof(struct msm_audio_buf_cfg32))) {
|
||||
pr_err("%s: Copy to user failed\n", __func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_CONFIG_32: {
|
||||
struct msm_audio_config32 cfg_32;
|
||||
|
||||
memset(&cfg_32, 0, sizeof(cfg_32));
|
||||
cfg_32.buffer_size = audio->pcm_cfg.buffer_size;
|
||||
cfg_32.buffer_count = audio->pcm_cfg.buffer_count;
|
||||
cfg_32.channel_count = audio->pcm_cfg.channel_count;
|
||||
cfg_32.sample_rate = audio->pcm_cfg.sample_rate;
|
||||
cfg_32.type = audio->pcm_cfg.type;
|
||||
cfg_32.meta_field = audio->pcm_cfg.meta_field;
|
||||
cfg_32.bits = audio->pcm_cfg.bits;
|
||||
|
||||
if (copy_to_user((void *)arg, &cfg_32,
|
||||
sizeof(struct msm_audio_config32))) {
|
||||
pr_err("%s: Copy to user failed\n", __func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_CONFIG_32: {
|
||||
struct msm_audio_config32 cfg_32;
|
||||
struct msm_audio_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg_32, (void *)arg, sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
cfg.buffer_size = cfg_32.buffer_size;
|
||||
cfg.buffer_count = cfg_32.buffer_count;
|
||||
cfg.channel_count = cfg_32.channel_count;
|
||||
cfg.sample_rate = cfg_32.sample_rate;
|
||||
cfg.type = cfg_32.type;
|
||||
cfg.meta_field = cfg_32.meta_field;
|
||||
cfg.bits = cfg_32.bits;
|
||||
rc = audio_in_set_config(file, &cfg);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
/* call codec specific ioctl */
|
||||
rc = audio->enc_compat_ioctl(file, cmd, arg);
|
||||
}
|
||||
mutex_unlock(&audio->lock);
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
ssize_t audio_in_read(struct file *file,
|
||||
char __user *buf,
|
||||
size_t count, loff_t *pos)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
const char __user *start = buf;
|
||||
unsigned char *data;
|
||||
uint32_t offset = 0;
|
||||
uint32_t size = 0;
|
||||
int rc = 0;
|
||||
uint32_t idx;
|
||||
struct meta_out_dsp meta;
|
||||
uint32_t bytes_to_copy = 0;
|
||||
uint32_t mfield_size = (audio->buf_cfg.meta_info_enable == 0) ? 0 :
|
||||
(sizeof(unsigned char) +
|
||||
(sizeof(struct meta_out_dsp)*(audio->buf_cfg.frames_per_buf)));
|
||||
|
||||
memset(&meta, 0, sizeof(meta));
|
||||
pr_debug("%s:session id %d: read - %zd\n", __func__, audio->ac->session,
|
||||
count);
|
||||
if (audio->reset_event)
|
||||
return -ENETRESET;
|
||||
|
||||
if (!audio->enabled)
|
||||
return -EFAULT;
|
||||
mutex_lock(&audio->read_lock);
|
||||
while (count > 0) {
|
||||
rc = wait_event_interruptible(
|
||||
audio->read_wait,
|
||||
((atomic_read(&audio->out_count) > 0) ||
|
||||
(audio->stopped) ||
|
||||
audio->rflush || audio->eos_rsp ||
|
||||
audio->event_abort));
|
||||
|
||||
if (audio->event_abort) {
|
||||
rc = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (rc < 0)
|
||||
break;
|
||||
|
||||
if ((audio->stopped && !(atomic_read(&audio->out_count))) ||
|
||||
audio->rflush) {
|
||||
pr_debug("%s:session id %d: driver in stop state or flush,No more buf to read",
|
||||
__func__,
|
||||
audio->ac->session);
|
||||
rc = 0;/* End of File */
|
||||
break;
|
||||
}
|
||||
if (!(atomic_read(&audio->out_count)) &&
|
||||
(audio->eos_rsp == 1) &&
|
||||
(count >= (sizeof(unsigned char) +
|
||||
sizeof(struct meta_out_dsp)))) {
|
||||
unsigned char num_of_frames;
|
||||
|
||||
pr_info("%s:session id %d: eos %d at output\n",
|
||||
__func__, audio->ac->session, audio->eos_rsp);
|
||||
if (buf != start)
|
||||
break;
|
||||
num_of_frames = 0xFF;
|
||||
if (copy_to_user(buf, &num_of_frames,
|
||||
sizeof(unsigned char))) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
buf += sizeof(unsigned char);
|
||||
meta.frame_size = 0xFFFF;
|
||||
meta.encoded_pcm_samples = 0xFFFF;
|
||||
meta.msw_ts = 0x00;
|
||||
meta.lsw_ts = 0x00;
|
||||
meta.nflags = AUD_EOS_SET;
|
||||
audio->eos_rsp = 0;
|
||||
if (copy_to_user(buf, &meta, sizeof(meta))) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
buf += sizeof(meta);
|
||||
break;
|
||||
}
|
||||
data = (unsigned char *)q6asm_is_cpu_buf_avail(OUT, audio->ac,
|
||||
&size, &idx);
|
||||
if ((count >= (size + mfield_size)) && data) {
|
||||
if (audio->buf_cfg.meta_info_enable) {
|
||||
if (copy_to_user(buf,
|
||||
&audio->out_frame_info[idx][0],
|
||||
sizeof(unsigned char))) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
bytes_to_copy =
|
||||
(size + audio->out_frame_info[idx][1]);
|
||||
/* Number of frames information copied */
|
||||
buf += sizeof(unsigned char);
|
||||
count -= sizeof(unsigned char);
|
||||
} else {
|
||||
offset = audio->out_frame_info[idx][1];
|
||||
bytes_to_copy = size;
|
||||
}
|
||||
|
||||
pr_debug("%s:session id %d: offset=%d nr of frames= %d\n",
|
||||
__func__, audio->ac->session,
|
||||
audio->out_frame_info[idx][1],
|
||||
audio->out_frame_info[idx][0]);
|
||||
|
||||
if (copy_to_user(buf, &data[offset], bytes_to_copy)) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
count -= bytes_to_copy;
|
||||
buf += bytes_to_copy;
|
||||
} else {
|
||||
pr_err("%s:session id %d: short read data[%pK] bytesavail[%d]bytesrequest[%zd]\n",
|
||||
__func__,
|
||||
audio->ac->session,
|
||||
data, size, count);
|
||||
}
|
||||
atomic_dec(&audio->out_count);
|
||||
q6asm_read(audio->ac);
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&audio->read_lock);
|
||||
|
||||
pr_debug("%s:session id %d: read: %zd bytes\n", __func__,
|
||||
audio->ac->session, (buf-start));
|
||||
if (buf > start)
|
||||
return buf - start;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int extract_meta_info(char *buf, unsigned long *msw_ts,
|
||||
unsigned long *lsw_ts, unsigned int *flags)
|
||||
{
|
||||
struct meta_in *meta = (struct meta_in *)buf;
|
||||
*msw_ts = meta->ntimestamp.highpart;
|
||||
*lsw_ts = meta->ntimestamp.lowpart;
|
||||
*flags = meta->nflags;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t audio_in_write(struct file *file,
|
||||
const char __user *buf,
|
||||
size_t count, loff_t *pos)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
const char __user *start = buf;
|
||||
size_t xfer = 0;
|
||||
char *cpy_ptr;
|
||||
int rc = 0;
|
||||
unsigned char *data;
|
||||
uint32_t size = 0;
|
||||
uint32_t idx = 0;
|
||||
uint32_t nflags = 0;
|
||||
unsigned long msw_ts = 0;
|
||||
unsigned long lsw_ts = 0;
|
||||
uint32_t mfield_size = (audio->buf_cfg.meta_info_enable == 0) ? 0 :
|
||||
sizeof(struct meta_in);
|
||||
|
||||
pr_debug("%s:session id %d: to write[%zd]\n", __func__,
|
||||
audio->ac->session, count);
|
||||
if (audio->reset_event)
|
||||
return -ENETRESET;
|
||||
|
||||
if (!audio->enabled)
|
||||
return -EFAULT;
|
||||
mutex_lock(&audio->write_lock);
|
||||
|
||||
while (count > 0) {
|
||||
rc = wait_event_interruptible(audio->write_wait,
|
||||
((atomic_read(&audio->in_count) > 0) ||
|
||||
(audio->stopped) ||
|
||||
(audio->wflush) || (audio->event_abort)));
|
||||
|
||||
if (audio->event_abort) {
|
||||
rc = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc < 0)
|
||||
break;
|
||||
if (audio->stopped || audio->wflush) {
|
||||
pr_debug("%s: session id %d: stop or flush\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = -EBUSY;
|
||||
break;
|
||||
}
|
||||
/* if no PCM data, might have only eos buffer
|
||||
* such case do not hold cpu buffer
|
||||
*/
|
||||
if ((buf == start) && (count == mfield_size)) {
|
||||
char eos_buf[sizeof(struct meta_in)];
|
||||
/* Processing beginning of user buffer */
|
||||
if (copy_from_user(eos_buf, buf, mfield_size)) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
/* Check if EOS flag is set and buffer has
|
||||
* contains just meta field
|
||||
*/
|
||||
extract_meta_info(eos_buf, &msw_ts, &lsw_ts,
|
||||
&nflags);
|
||||
buf += mfield_size;
|
||||
/* send the EOS and return */
|
||||
pr_debug("%s:session id %d: send EOS 0x%8x\n",
|
||||
__func__,
|
||||
audio->ac->session, nflags);
|
||||
break;
|
||||
}
|
||||
data = (unsigned char *)q6asm_is_cpu_buf_avail(IN, audio->ac,
|
||||
&size, &idx);
|
||||
if (!data) {
|
||||
pr_debug("%s:session id %d: No buf available\n",
|
||||
__func__, audio->ac->session);
|
||||
continue;
|
||||
}
|
||||
cpy_ptr = data;
|
||||
if (audio->buf_cfg.meta_info_enable) {
|
||||
if (buf == start) {
|
||||
/* Processing beginning of user buffer */
|
||||
if (copy_from_user(cpy_ptr, buf, mfield_size)) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
/* Check if EOS flag is set and buffer has
|
||||
* contains just meta field
|
||||
*/
|
||||
extract_meta_info(cpy_ptr, &msw_ts, &lsw_ts,
|
||||
&nflags);
|
||||
buf += mfield_size;
|
||||
count -= mfield_size;
|
||||
} else {
|
||||
pr_debug("%s:session id %d: continuous buffer\n",
|
||||
__func__, audio->ac->session);
|
||||
}
|
||||
}
|
||||
xfer = (count > (audio->pcm_cfg.buffer_size)) ?
|
||||
(audio->pcm_cfg.buffer_size) : count;
|
||||
|
||||
if (copy_from_user(cpy_ptr, buf, xfer)) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = q6asm_write(audio->ac, xfer, msw_ts, lsw_ts, 0x00);
|
||||
if (rc < 0) {
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
atomic_dec(&audio->in_count);
|
||||
count -= xfer;
|
||||
buf += xfer;
|
||||
}
|
||||
mutex_unlock(&audio->write_lock);
|
||||
pr_debug("%s:session id %d: eos_condition 0x%x buf[0x%pK] start[0x%pK]\n",
|
||||
__func__, audio->ac->session,
|
||||
nflags, buf, start);
|
||||
if (nflags & AUD_EOS_SET) {
|
||||
rc = q6asm_cmd(audio->ac, CMD_EOS);
|
||||
pr_info("%s:session id %d: eos %d at input\n", __func__,
|
||||
audio->ac->session, audio->eos_rsp);
|
||||
}
|
||||
pr_debug("%s:session id %d: Written %zd Avail Buf[%d]", __func__,
|
||||
audio->ac->session, (buf - start - mfield_size),
|
||||
atomic_read(&audio->in_count));
|
||||
if (!rc) {
|
||||
if (buf > start)
|
||||
return buf - start;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int audio_in_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
|
||||
pr_info("%s: session id %d\n", __func__, audio->ac->session);
|
||||
mutex_lock(&audio->lock);
|
||||
audio_in_disable(audio);
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
mutex_unlock(&audio->lock);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return 0;
|
||||
}
|
||||
114
drivers/misc/qcom/qdsp6v2/audio_utils.h
Normal file
114
drivers/misc/qcom/qdsp6v2/audio_utils.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/* Copyright (c) 2010-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/msm_audio.h>
|
||||
#include <linux/compat.h>
|
||||
#include "q6audio_common.h"
|
||||
|
||||
#define FRAME_NUM (8)
|
||||
|
||||
#define PCM_BUF_COUNT (2)
|
||||
|
||||
#define AUD_EOS_SET 0x01
|
||||
#define TUNNEL_MODE 0x0000
|
||||
#define NON_TUNNEL_MODE 0x0001
|
||||
|
||||
#define NO_BUF_ALLOC 0x00
|
||||
#define BUF_ALLOC_IN 0x01
|
||||
#define BUF_ALLOC_OUT 0x02
|
||||
#define BUF_ALLOC_INOUT 0x03
|
||||
#define ALIGN_BUF_SIZE(size) ((size + 4095) & (~4095))
|
||||
|
||||
struct timestamp {
|
||||
u32 lowpart;
|
||||
u32 highpart;
|
||||
} __packed;
|
||||
|
||||
struct meta_in {
|
||||
unsigned short offset;
|
||||
struct timestamp ntimestamp;
|
||||
unsigned int nflags;
|
||||
} __packed;
|
||||
|
||||
struct meta_out_dsp {
|
||||
u32 offset_to_frame;
|
||||
u32 frame_size;
|
||||
u32 encoded_pcm_samples;
|
||||
u32 msw_ts;
|
||||
u32 lsw_ts;
|
||||
u32 nflags;
|
||||
} __packed;
|
||||
|
||||
struct meta_out {
|
||||
unsigned char num_of_frames;
|
||||
struct meta_out_dsp meta_out_dsp[];
|
||||
} __packed;
|
||||
|
||||
struct q6audio_in {
|
||||
spinlock_t dsp_lock;
|
||||
atomic_t in_bytes;
|
||||
atomic_t in_samples;
|
||||
|
||||
struct mutex lock;
|
||||
struct mutex read_lock;
|
||||
struct mutex write_lock;
|
||||
wait_queue_head_t read_wait;
|
||||
wait_queue_head_t write_wait;
|
||||
|
||||
struct audio_client *ac;
|
||||
struct msm_audio_stream_config str_cfg;
|
||||
void *enc_cfg;
|
||||
struct msm_audio_buf_cfg buf_cfg;
|
||||
struct msm_audio_config pcm_cfg;
|
||||
void *codec_cfg;
|
||||
|
||||
/* number of buffers available to read/write */
|
||||
atomic_t in_count;
|
||||
atomic_t out_count;
|
||||
|
||||
/* first idx: num of frames per buf, second idx: offset to frame */
|
||||
uint32_t out_frame_info[FRAME_NUM][2];
|
||||
int eos_rsp;
|
||||
int opened;
|
||||
int enabled;
|
||||
int stopped;
|
||||
int event_abort;
|
||||
int feedback; /* Flag indicates whether used
|
||||
* in Non Tunnel mode
|
||||
*/
|
||||
int rflush;
|
||||
int wflush;
|
||||
int buf_alloc;
|
||||
uint16_t min_frame_size;
|
||||
uint16_t max_frames_per_buf;
|
||||
bool reset_event;
|
||||
long (*enc_ioctl)(struct file *, unsigned int, unsigned long);
|
||||
long (*enc_compat_ioctl)(struct file *, unsigned int, unsigned long);
|
||||
};
|
||||
|
||||
int audio_in_enable(struct q6audio_in *audio);
|
||||
int audio_in_disable(struct q6audio_in *audio);
|
||||
int audio_in_buf_alloc(struct q6audio_in *audio);
|
||||
long audio_in_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg);
|
||||
#ifdef CONFIG_COMPAT
|
||||
long audio_in_compat_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg);
|
||||
#else
|
||||
#define audio_in_compat_ioctl NULL
|
||||
#endif
|
||||
ssize_t audio_in_read(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *pos);
|
||||
ssize_t audio_in_write(struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *pos);
|
||||
int audio_in_release(struct inode *inode, struct file *file);
|
||||
int audio_in_set_config(struct file *file, struct msm_audio_config *cfg);
|
||||
2142
drivers/misc/qcom/qdsp6v2/audio_utils_aio.c
Normal file
2142
drivers/misc/qcom/qdsp6v2/audio_utils_aio.c
Normal file
File diff suppressed because it is too large
Load Diff
232
drivers/misc/qcom/qdsp6v2/audio_utils_aio.h
Normal file
232
drivers/misc/qcom/qdsp6v2/audio_utils_aio.h
Normal file
@@ -0,0 +1,232 @@
|
||||
/* Copyright (C) 2008 Google, Inc.
|
||||
* Copyright (C) 2008 HTC Corporation
|
||||
* Copyright (c) 2009-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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/wakelock.h>
|
||||
#include <linux/msm_audio.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/msm_ion.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include <linux/atomic.h>
|
||||
#include "q6audio_common.h"
|
||||
|
||||
#define TUNNEL_MODE 0x0000
|
||||
#define NON_TUNNEL_MODE 0x0001
|
||||
|
||||
#define ADRV_STATUS_AIO_INTF 0x00000001 /* AIO interface */
|
||||
#define ADRV_STATUS_FSYNC 0x00000008
|
||||
#define ADRV_STATUS_PAUSE 0x00000010
|
||||
#define AUDIO_DEC_EOS_SET 0x00000001
|
||||
#define AUDIO_DEC_EOF_SET 0x00000010
|
||||
#define AUDIO_EVENT_NUM 10
|
||||
|
||||
#define __CONTAINS(r, v, l) ({ \
|
||||
typeof(r) __r = r; \
|
||||
typeof(v) __v = v; \
|
||||
typeof(v) __e = __v + l; \
|
||||
int res = ((__v >= __r->vaddr) && \
|
||||
(__e <= __r->vaddr + __r->len)); \
|
||||
res; \
|
||||
})
|
||||
|
||||
#define CONTAINS(r1, r2) ({ \
|
||||
typeof(r2) __r2 = r2; \
|
||||
__CONTAINS(r1, __r2->vaddr, __r2->len); \
|
||||
})
|
||||
|
||||
#define IN_RANGE(r, v) ({ \
|
||||
typeof(r) __r = r; \
|
||||
typeof(v) __vv = v; \
|
||||
int res = ((__vv >= __r->vaddr) && \
|
||||
(__vv < (__r->vaddr + __r->len))); \
|
||||
res; \
|
||||
})
|
||||
|
||||
#define OVERLAPS(r1, r2) ({ \
|
||||
typeof(r1) __r1 = r1; \
|
||||
typeof(r2) __r2 = r2; \
|
||||
typeof(__r2->vaddr) __v = __r2->vaddr; \
|
||||
typeof(__v) __e = __v + __r2->len - 1; \
|
||||
int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \
|
||||
res; \
|
||||
})
|
||||
|
||||
struct timestamp {
|
||||
u32 lowpart;
|
||||
u32 highpart;
|
||||
} __packed;
|
||||
|
||||
struct meta_out_dsp {
|
||||
u32 offset_to_frame;
|
||||
u32 frame_size;
|
||||
u32 encoded_pcm_samples;
|
||||
u32 msw_ts;
|
||||
u32 lsw_ts;
|
||||
u32 nflags;
|
||||
} __packed;
|
||||
|
||||
struct dec_meta_in {
|
||||
unsigned char reserved[18];
|
||||
unsigned short offset;
|
||||
struct timestamp ntimestamp;
|
||||
unsigned int nflags;
|
||||
} __packed;
|
||||
|
||||
struct dec_meta_out {
|
||||
unsigned int reserved[7];
|
||||
unsigned int num_of_frames;
|
||||
struct meta_out_dsp meta_out_dsp[];
|
||||
} __packed;
|
||||
|
||||
/* General meta field to store meta info locally */
|
||||
union meta_data {
|
||||
struct dec_meta_out meta_out;
|
||||
struct dec_meta_in meta_in;
|
||||
} __packed;
|
||||
|
||||
/* per device wakeup source manager */
|
||||
struct ws_mgr {
|
||||
struct mutex ws_lock;
|
||||
uint32_t ref_cnt;
|
||||
};
|
||||
|
||||
#define PCM_BUF_COUNT (2)
|
||||
/* Buffer with meta */
|
||||
#define PCM_BUFSZ_MIN ((4*1024) + sizeof(struct dec_meta_out))
|
||||
|
||||
/* FRAME_NUM must be a power of two */
|
||||
#define FRAME_NUM (2)
|
||||
#define FRAME_SIZE ((4*1536) + sizeof(struct dec_meta_in))
|
||||
|
||||
struct audio_aio_ion_region {
|
||||
struct list_head list;
|
||||
struct ion_handle *handle;
|
||||
int fd;
|
||||
void *vaddr;
|
||||
phys_addr_t paddr;
|
||||
void *kvaddr;
|
||||
unsigned long len;
|
||||
unsigned int ref_cnt;
|
||||
};
|
||||
|
||||
struct audio_aio_event {
|
||||
struct list_head list;
|
||||
int event_type;
|
||||
union msm_audio_event_payload payload;
|
||||
};
|
||||
|
||||
struct audio_aio_buffer_node {
|
||||
struct list_head list;
|
||||
struct msm_audio_aio_buf buf;
|
||||
unsigned long paddr;
|
||||
uint32_t token;
|
||||
void *kvaddr;
|
||||
union meta_data meta_info;
|
||||
};
|
||||
|
||||
struct q6audio_aio;
|
||||
struct audio_aio_drv_operations {
|
||||
void (*out_flush)(struct q6audio_aio *);
|
||||
void (*in_flush)(struct q6audio_aio *);
|
||||
};
|
||||
|
||||
struct q6audio_aio {
|
||||
atomic_t in_bytes;
|
||||
atomic_t in_samples;
|
||||
|
||||
struct msm_audio_stream_config str_cfg;
|
||||
struct msm_audio_buf_cfg buf_cfg;
|
||||
struct msm_audio_config pcm_cfg;
|
||||
void *codec_cfg;
|
||||
|
||||
struct audio_client *ac;
|
||||
|
||||
struct mutex lock;
|
||||
struct mutex read_lock;
|
||||
struct mutex write_lock;
|
||||
struct mutex get_event_lock;
|
||||
wait_queue_head_t cmd_wait;
|
||||
wait_queue_head_t write_wait;
|
||||
wait_queue_head_t event_wait;
|
||||
spinlock_t dsp_lock;
|
||||
spinlock_t event_queue_lock;
|
||||
|
||||
struct miscdevice *miscdevice;
|
||||
uint32_t wakelock_voted;
|
||||
struct ws_mgr *audio_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
struct dentry *dentry;
|
||||
#endif
|
||||
struct list_head out_queue; /* queue to retain output buffers */
|
||||
struct list_head in_queue; /* queue to retain input buffers */
|
||||
struct list_head free_event_queue;
|
||||
struct list_head event_queue;
|
||||
struct list_head ion_region_queue; /* protected by lock */
|
||||
struct ion_client *client;
|
||||
struct audio_aio_drv_operations drv_ops;
|
||||
union msm_audio_event_payload eos_write_payload;
|
||||
uint32_t device_events;
|
||||
uint16_t volume;
|
||||
uint32_t drv_status;
|
||||
int event_abort;
|
||||
int eos_rsp;
|
||||
int eos_flag;
|
||||
int opened;
|
||||
int enabled;
|
||||
int stopped;
|
||||
int feedback;
|
||||
int rflush; /* Read flush */
|
||||
int wflush; /* Write flush */
|
||||
bool reset_event;
|
||||
long (*codec_ioctl)(struct file *, unsigned int, unsigned long);
|
||||
long (*codec_compat_ioctl)(struct file *, unsigned int, unsigned long);
|
||||
};
|
||||
|
||||
void audio_aio_async_write_ack(struct q6audio_aio *audio, uint32_t token,
|
||||
uint32_t *payload);
|
||||
|
||||
void audio_aio_async_read_ack(struct q6audio_aio *audio, uint32_t token,
|
||||
uint32_t *payload);
|
||||
|
||||
int insert_eos_buf(struct q6audio_aio *audio,
|
||||
struct audio_aio_buffer_node *buf_node);
|
||||
|
||||
void extract_meta_out_info(struct q6audio_aio *audio,
|
||||
struct audio_aio_buffer_node *buf_node, int dir);
|
||||
|
||||
int audio_aio_open(struct q6audio_aio *audio, struct file *file);
|
||||
int audio_aio_enable(struct q6audio_aio *audio);
|
||||
void audio_aio_post_event(struct q6audio_aio *audio, int type,
|
||||
union msm_audio_event_payload payload);
|
||||
int audio_aio_release(struct inode *inode, struct file *file);
|
||||
int audio_aio_fsync(struct file *file, loff_t start, loff_t end, int datasync);
|
||||
void audio_aio_async_out_flush(struct q6audio_aio *audio);
|
||||
void audio_aio_async_in_flush(struct q6audio_aio *audio);
|
||||
void audio_aio_ioport_reset(struct q6audio_aio *audio);
|
||||
int enable_volume_ramp(struct q6audio_aio *audio);
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
int audio_aio_debug_open(struct inode *inode, struct file *file);
|
||||
ssize_t audio_aio_debug_read(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *ppos);
|
||||
#endif
|
||||
345
drivers/misc/qcom/qdsp6v2/audio_wma.c
Normal file
345
drivers/misc/qcom/qdsp6v2/audio_wma.c
Normal file
@@ -0,0 +1,345 @@
|
||||
/* wma audio output device
|
||||
*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
* Copyright (C) 2008 HTC Corporation
|
||||
* Copyright (c) 2009-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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/msm_audio_wma.h>
|
||||
#include <linux/compat.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_wma_misc;
|
||||
static struct ws_mgr audio_wma_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static const struct file_operations audio_wma_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
#endif
|
||||
|
||||
static long audio_ioctl_shared(struct file *file, unsigned int cmd,
|
||||
void *arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct asm_wma_cfg wma_cfg;
|
||||
struct msm_audio_wma_config_v2 *wma_config;
|
||||
|
||||
pr_debug("%s[%pK]: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio, audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
wma_config = (struct msm_audio_wma_config_v2 *)audio->codec_cfg;
|
||||
wma_cfg.format_tag = wma_config->format_tag;
|
||||
wma_cfg.ch_cfg = wma_config->numchannels;
|
||||
wma_cfg.sample_rate = wma_config->samplingrate;
|
||||
wma_cfg.avg_bytes_per_sec = wma_config->avgbytespersecond;
|
||||
wma_cfg.block_align = wma_config->block_align;
|
||||
wma_cfg.valid_bits_per_sample =
|
||||
wma_config->validbitspersample;
|
||||
wma_cfg.ch_mask = wma_config->channelmask;
|
||||
wma_cfg.encode_opt = wma_config->encodeopt;
|
||||
/* Configure Media format block */
|
||||
rc = q6asm_media_format_block_wma(audio->ac, &wma_cfg,
|
||||
audio->ac->stream_id);
|
||||
if (rc < 0) {
|
||||
pr_err("cmd media format block failed\n");
|
||||
break;
|
||||
}
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("AUDIO_START success enable[%d]\n", audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_WMA_CONFIG_V2: {
|
||||
if (copy_to_user((void *)arg, audio->codec_cfg,
|
||||
sizeof(struct msm_audio_wma_config_v2))) {
|
||||
pr_err("%s:copy_to_user for AUDIO_SET_WMA_CONFIG_V2 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_WMA_CONFIG_V2: {
|
||||
if (copy_from_user(audio->codec_cfg, (void *)arg,
|
||||
sizeof(struct msm_audio_wma_config_v2))) {
|
||||
pr_err("%s:copy_from_user for AUDIO_SET_WMA_CONFIG_V2 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("Failed in utils_ioctl: %d\n", rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_wma_config_v2_32 {
|
||||
u16 format_tag;
|
||||
u16 numchannels;
|
||||
u32 samplingrate;
|
||||
u32 avgbytespersecond;
|
||||
u16 block_align;
|
||||
u16 validbitspersample;
|
||||
u32 channelmask;
|
||||
u16 encodeopt;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_GET_WMA_CONFIG_V2_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+2), struct msm_audio_wma_config_v2_32),
|
||||
AUDIO_SET_WMA_CONFIG_V2_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+3), struct msm_audio_wma_config_v2_32)
|
||||
};
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_WMA_CONFIG_V2_32: {
|
||||
struct msm_audio_wma_config_v2 *wma_config;
|
||||
struct msm_audio_wma_config_v2_32 wma_config_32;
|
||||
|
||||
memset(&wma_config_32, 0, sizeof(wma_config_32));
|
||||
|
||||
wma_config = (struct msm_audio_wma_config_v2 *)audio->codec_cfg;
|
||||
wma_config_32.format_tag = wma_config->format_tag;
|
||||
wma_config_32.numchannels = wma_config->numchannels;
|
||||
wma_config_32.samplingrate = wma_config->samplingrate;
|
||||
wma_config_32.avgbytespersecond = wma_config->avgbytespersecond;
|
||||
wma_config_32.block_align = wma_config->block_align;
|
||||
wma_config_32.validbitspersample =
|
||||
wma_config->validbitspersample;
|
||||
wma_config_32.channelmask = wma_config->channelmask;
|
||||
wma_config_32.encodeopt = wma_config->encodeopt;
|
||||
if (copy_to_user((void *)arg, &wma_config_32,
|
||||
sizeof(wma_config_32))) {
|
||||
pr_err("%s: copy_to_user for GET_WMA_CONFIG_V2_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_WMA_CONFIG_V2_32: {
|
||||
struct msm_audio_wma_config_v2 *wma_config;
|
||||
struct msm_audio_wma_config_v2_32 wma_config_32;
|
||||
|
||||
if (copy_from_user(&wma_config_32, (void *)arg,
|
||||
sizeof(wma_config_32))) {
|
||||
pr_err("%s: copy_from_user for SET_WMA_CONFIG_V2_32 failed\n"
|
||||
, __func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
wma_config = (struct msm_audio_wma_config_v2 *)audio->codec_cfg;
|
||||
wma_config->format_tag = wma_config_32.format_tag;
|
||||
wma_config->numchannels = wma_config_32.numchannels;
|
||||
wma_config->samplingrate = wma_config_32.samplingrate;
|
||||
wma_config->avgbytespersecond = wma_config_32.avgbytespersecond;
|
||||
wma_config->block_align = wma_config_32.block_align;
|
||||
wma_config->validbitspersample =
|
||||
wma_config_32.validbitspersample;
|
||||
wma_config->channelmask = wma_config_32.channelmask;
|
||||
wma_config->encodeopt = wma_config_32.encodeopt;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("Failed in utils_ioctl: %d\n", rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define audio_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_wma_" + 5];
|
||||
#endif
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->codec_cfg = kzalloc(sizeof(struct msm_audio_wma_config_v2),
|
||||
GFP_KERNEL);
|
||||
if (audio->codec_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_wma_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_wma_ws_mgr;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_WMA_V9);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
/* open WMA decoder, expected frames is always 1*/
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_WMA_V9);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
snprintf(name, sizeof(name), "msm_wma_%04x", audio->ac->session);
|
||||
audio->dentry = debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)audio,
|
||||
&audio_wma_debug_fops);
|
||||
|
||||
if (IS_ERR(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
#endif
|
||||
pr_info("%s:wmadec success mode[%d]session[%d]\n", __func__,
|
||||
audio->feedback,
|
||||
audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_wma_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
.compat_ioctl = audio_compat_ioctl
|
||||
};
|
||||
|
||||
static struct miscdevice audio_wma_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_wma",
|
||||
.fops = &audio_wma_fops,
|
||||
};
|
||||
|
||||
static int __init audio_wma_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_wma_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_wma_misc.this_device, true);
|
||||
audio_wma_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_wma_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_wma_init);
|
||||
418
drivers/misc/qcom/qdsp6v2/audio_wmapro.c
Normal file
418
drivers/misc/qcom/qdsp6v2/audio_wmapro.c
Normal file
@@ -0,0 +1,418 @@
|
||||
/* wmapro audio output device
|
||||
*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
* Copyright (C) 2008 HTC Corporation
|
||||
* Copyright (c) 2009-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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/msm_audio_wmapro.h>
|
||||
#include <linux/compat.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
static struct miscdevice audio_wmapro_misc;
|
||||
static struct ws_mgr audio_wmapro_ws_mgr;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static const struct file_operations audio_wmapro_debug_fops = {
|
||||
.read = audio_aio_debug_read,
|
||||
.open = audio_aio_debug_open,
|
||||
};
|
||||
#endif
|
||||
|
||||
static long audio_ioctl_shared(struct file *file, unsigned int cmd,
|
||||
void *arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct asm_wmapro_cfg wmapro_cfg;
|
||||
struct msm_audio_wmapro_config *wmapro_config;
|
||||
|
||||
pr_debug("%s: AUDIO_START session_id[%d]\n", __func__,
|
||||
audio->ac->session);
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
/* Configure PCM output block */
|
||||
rc = q6asm_enc_cfg_blk_pcm_v2(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count,
|
||||
16, /* bits per sample */
|
||||
true, /* use default channel map */
|
||||
true, /* use back channel map flavor */
|
||||
NULL);
|
||||
if (rc < 0) {
|
||||
pr_err("pcm output block config failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
wmapro_config = (struct msm_audio_wmapro_config *)
|
||||
audio->codec_cfg;
|
||||
if ((wmapro_config->formattag == 0x162) ||
|
||||
(wmapro_config->formattag == 0x163) ||
|
||||
(wmapro_config->formattag == 0x166) ||
|
||||
(wmapro_config->formattag == 0x167)) {
|
||||
wmapro_cfg.format_tag = wmapro_config->formattag;
|
||||
} else {
|
||||
pr_err("%s:AUDIO_START failed: formattag = %d\n",
|
||||
__func__, wmapro_config->formattag);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (wmapro_config->numchannels > 0) {
|
||||
wmapro_cfg.ch_cfg = wmapro_config->numchannels;
|
||||
} else {
|
||||
pr_err("%s:AUDIO_START failed: channels = %d\n",
|
||||
__func__, wmapro_config->numchannels);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (wmapro_config->samplingrate > 0) {
|
||||
wmapro_cfg.sample_rate = wmapro_config->samplingrate;
|
||||
} else {
|
||||
pr_err("%s:AUDIO_START failed: sample_rate = %d\n",
|
||||
__func__, wmapro_config->samplingrate);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
wmapro_cfg.avg_bytes_per_sec =
|
||||
wmapro_config->avgbytespersecond;
|
||||
if ((wmapro_config->asfpacketlength <= 13376) ||
|
||||
(wmapro_config->asfpacketlength > 0)) {
|
||||
wmapro_cfg.block_align =
|
||||
wmapro_config->asfpacketlength;
|
||||
} else {
|
||||
pr_err("%s:AUDIO_START failed: block_align = %d\n",
|
||||
__func__, wmapro_config->asfpacketlength);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if ((wmapro_config->validbitspersample == 16) ||
|
||||
(wmapro_config->validbitspersample == 24)) {
|
||||
wmapro_cfg.valid_bits_per_sample =
|
||||
wmapro_config->validbitspersample;
|
||||
} else {
|
||||
pr_err("%s:AUDIO_START failed: bitspersample = %d\n",
|
||||
__func__, wmapro_config->validbitspersample);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
wmapro_cfg.ch_mask = wmapro_config->channelmask;
|
||||
wmapro_cfg.encode_opt = wmapro_config->encodeopt;
|
||||
wmapro_cfg.adv_encode_opt =
|
||||
wmapro_config->advancedencodeopt;
|
||||
wmapro_cfg.adv_encode_opt2 =
|
||||
wmapro_config->advancedencodeopt2;
|
||||
/* Configure Media format block */
|
||||
rc = q6asm_media_format_block_wmapro(audio->ac, &wmapro_cfg,
|
||||
audio->ac->stream_id);
|
||||
if (rc < 0) {
|
||||
pr_err("cmd media format block failed\n");
|
||||
break;
|
||||
}
|
||||
rc = audio_aio_enable(audio);
|
||||
audio->eos_rsp = 0;
|
||||
audio->eos_flag = 0;
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("Audio Start procedure failed rc=%d\n", rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("AUDIO_START success enable[%d]\n", audio->enabled);
|
||||
if (audio->stopped == 1)
|
||||
audio->stopped = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd %d\n", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_GET_WMAPRO_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->codec_cfg,
|
||||
sizeof(struct msm_audio_wmapro_config))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_WMAPRO_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_WMAPRO_CONFIG: {
|
||||
if (copy_from_user(audio->codec_cfg, (void *)arg,
|
||||
sizeof(struct msm_audio_wmapro_config))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_WMAPRO_CONFIG_V2 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("Failed in utils_ioctl: %d\n", rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
|
||||
struct msm_audio_wmapro_config32 {
|
||||
u16 armdatareqthr;
|
||||
u8 validbitspersample;
|
||||
u8 numchannels;
|
||||
u16 formattag;
|
||||
u32 samplingrate;
|
||||
u32 avgbytespersecond;
|
||||
u16 asfpacketlength;
|
||||
u32 channelmask;
|
||||
u16 encodeopt;
|
||||
u16 advancedencodeopt;
|
||||
u32 advancedencodeopt2;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_GET_WMAPRO_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_wmapro_config32),
|
||||
AUDIO_SET_WMAPRO_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_wmapro_config32)
|
||||
};
|
||||
|
||||
static long audio_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct q6audio_aio *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_GET_WMAPRO_CONFIG_32: {
|
||||
struct msm_audio_wmapro_config *wmapro_config;
|
||||
struct msm_audio_wmapro_config32 wmapro_config_32;
|
||||
|
||||
memset(&wmapro_config_32, 0, sizeof(wmapro_config_32));
|
||||
|
||||
wmapro_config =
|
||||
(struct msm_audio_wmapro_config *)audio->codec_cfg;
|
||||
wmapro_config_32.armdatareqthr = wmapro_config->armdatareqthr;
|
||||
wmapro_config_32.validbitspersample =
|
||||
wmapro_config->validbitspersample;
|
||||
wmapro_config_32.numchannels = wmapro_config->numchannels;
|
||||
wmapro_config_32.formattag = wmapro_config->formattag;
|
||||
wmapro_config_32.samplingrate = wmapro_config->samplingrate;
|
||||
wmapro_config_32.avgbytespersecond =
|
||||
wmapro_config->avgbytespersecond;
|
||||
wmapro_config_32.asfpacketlength =
|
||||
wmapro_config->asfpacketlength;
|
||||
wmapro_config_32.channelmask = wmapro_config->channelmask;
|
||||
wmapro_config_32.encodeopt = wmapro_config->encodeopt;
|
||||
wmapro_config_32.advancedencodeopt =
|
||||
wmapro_config->advancedencodeopt;
|
||||
wmapro_config_32.advancedencodeopt2 =
|
||||
wmapro_config->advancedencodeopt2;
|
||||
|
||||
if (copy_to_user((void *)arg, &wmapro_config_32,
|
||||
sizeof(struct msm_audio_wmapro_config32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_WMAPRO_CONFIG_V2_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_WMAPRO_CONFIG_32: {
|
||||
struct msm_audio_wmapro_config *wmapro_config;
|
||||
struct msm_audio_wmapro_config32 wmapro_config_32;
|
||||
|
||||
if (copy_from_user(&wmapro_config_32, (void *)arg,
|
||||
sizeof(struct msm_audio_wmapro_config32))) {
|
||||
pr_err(
|
||||
"%s: copy_from_user for AUDIO_SET_WMAPRO_CONFG_V2_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
wmapro_config =
|
||||
(struct msm_audio_wmapro_config *)audio->codec_cfg;
|
||||
wmapro_config->armdatareqthr = wmapro_config_32.armdatareqthr;
|
||||
wmapro_config->validbitspersample =
|
||||
wmapro_config_32.validbitspersample;
|
||||
wmapro_config->numchannels = wmapro_config_32.numchannels;
|
||||
wmapro_config->formattag = wmapro_config_32.formattag;
|
||||
wmapro_config->samplingrate = wmapro_config_32.samplingrate;
|
||||
wmapro_config->avgbytespersecond =
|
||||
wmapro_config_32.avgbytespersecond;
|
||||
wmapro_config->asfpacketlength =
|
||||
wmapro_config_32.asfpacketlength;
|
||||
wmapro_config->channelmask = wmapro_config_32.channelmask;
|
||||
wmapro_config->encodeopt = wmapro_config_32.encodeopt;
|
||||
wmapro_config->advancedencodeopt =
|
||||
wmapro_config_32.advancedencodeopt;
|
||||
wmapro_config->advancedencodeopt2 =
|
||||
wmapro_config_32.advancedencodeopt2;
|
||||
break;
|
||||
}
|
||||
case AUDIO_START: {
|
||||
rc = audio_ioctl_shared(file, cmd, (void *)arg);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_debug("%s[%pK]: Calling utils ioctl\n", __func__, audio);
|
||||
rc = audio->codec_compat_ioctl(file, cmd, arg);
|
||||
if (rc)
|
||||
pr_err("Failed in utils_ioctl: %d\n", rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define audio_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int audio_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_aio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* 4 bytes represents decoder number, 1 byte for terminate string */
|
||||
char name[sizeof "msm_wmapro_" + 5];
|
||||
#endif
|
||||
audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
audio->codec_cfg = kzalloc(sizeof(struct msm_audio_wmapro_config),
|
||||
GFP_KERNEL);
|
||||
if (audio->codec_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
|
||||
audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
|
||||
audio->miscdevice = &audio_wmapro_misc;
|
||||
audio->wakelock_voted = false;
|
||||
audio->audio_ws_mgr = &audio_wmapro_ws_mgr;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("Could not allocate memory for audio client\n");
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = audio_aio_open(audio, file);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: audio_aio_open rc=%d\n",
|
||||
__func__, rc);
|
||||
goto fail;
|
||||
}
|
||||
/* open in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
|
||||
FORMAT_WMA_V10PRO);
|
||||
if (rc < 0) {
|
||||
pr_err("NT mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
/* open WMA decoder, expected frames is always 1*/
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
} else if ((file->f_mode & FMODE_WRITE) &&
|
||||
!(file->f_mode & FMODE_READ)) {
|
||||
rc = q6asm_open_write(audio->ac, FORMAT_WMA_V10PRO);
|
||||
if (rc < 0) {
|
||||
pr_err("T mode Open failed rc=%d\n", rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
audio->buf_cfg.meta_info_enable = 0x00;
|
||||
} else {
|
||||
pr_err("Not supported mode\n");
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
snprintf(name, sizeof(name), "msm_wmapro_%04x", audio->ac->session);
|
||||
audio->dentry = debugfs_create_file(name, S_IFREG | 0444,
|
||||
NULL, (void *)audio,
|
||||
&audio_wmapro_debug_fops);
|
||||
|
||||
if (IS_ERR(audio->dentry))
|
||||
pr_debug("debugfs_create_file failed\n");
|
||||
#endif
|
||||
pr_info("%s:wmapro decoder open success, session_id = %d\n", __func__,
|
||||
audio->ac->session);
|
||||
return rc;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->codec_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_wmapro_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = audio_open,
|
||||
.release = audio_aio_release,
|
||||
.unlocked_ioctl = audio_ioctl,
|
||||
.fsync = audio_aio_fsync,
|
||||
.compat_ioctl = audio_compat_ioctl
|
||||
};
|
||||
|
||||
static struct miscdevice audio_wmapro_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_wmapro",
|
||||
.fops = &audio_wmapro_fops,
|
||||
};
|
||||
|
||||
static int __init audio_wmapro_init(void)
|
||||
{
|
||||
int ret = misc_register(&audio_wmapro_misc);
|
||||
|
||||
if (ret == 0)
|
||||
device_init_wakeup(audio_wmapro_misc.this_device, true);
|
||||
audio_wmapro_ws_mgr.ref_cnt = 0;
|
||||
mutex_init(&audio_wmapro_ws_mgr.ws_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_initcall(audio_wmapro_init);
|
||||
410
drivers/misc/qcom/qdsp6v2/evrc_in.c
Normal file
410
drivers/misc/qcom/qdsp6v2/evrc_in.c
Normal file
@@ -0,0 +1,410 @@
|
||||
/* Copyright (c) 2010-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/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/msm_audio_qcp.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/compat.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include "audio_utils.h"
|
||||
|
||||
/* Buffer with meta*/
|
||||
#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in))
|
||||
|
||||
/* Maximum 10 frames in buffer with meta */
|
||||
#define FRAME_SIZE (1 + ((23+sizeof(struct meta_out_dsp)) * 10))
|
||||
|
||||
static long evrc_in_ioctl_shared(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
int cnt = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct msm_audio_evrc_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__,
|
||||
audio->ac->session, audio->buf_alloc);
|
||||
if (audio->enabled == 1) {
|
||||
pr_info("%s:AUDIO_START already over\n", __func__);
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
rc = audio_in_buf_alloc(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: buffer allocation failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
|
||||
/* rate_modulation_cmd set to zero
|
||||
* currently not configurable from user space
|
||||
*/
|
||||
rc = q6asm_enc_cfg_blk_evrc(audio->ac,
|
||||
audio->buf_cfg.frames_per_buf,
|
||||
enc_cfg->min_bit_rate,
|
||||
enc_cfg->max_bit_rate, 0);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: cmd evrc media format block failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
rc = q6asm_media_format_block_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: media format block failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pr_debug("%s:session id %d: AUDIO_START enable[%d]\n",
|
||||
__func__, audio->ac->session, audio->enabled);
|
||||
rc = audio_in_enable(audio);
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s:session id %d: Audio Start procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
while (cnt++ < audio->str_cfg.buffer_count)
|
||||
q6asm_read(audio->ac); /* Push buffer to DSP */
|
||||
rc = 0;
|
||||
pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n",
|
||||
__func__, audio->ac->session, audio->enabled);
|
||||
break;
|
||||
}
|
||||
case AUDIO_STOP: {
|
||||
pr_debug("%s:session id %d: AUDIO_STOP\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = audio_in_disable(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Audio Stop procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_EVRC_ENC_CONFIG: {
|
||||
struct msm_audio_evrc_enc_config *cfg;
|
||||
struct msm_audio_evrc_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
cfg = (struct msm_audio_evrc_enc_config *)arg;
|
||||
if (cfg == NULL) {
|
||||
pr_err("%s: NULL config pointer for %s\n",
|
||||
__func__, "AUDIO_SET_EVRC_ENC_CONFIG");
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (cfg->min_bit_rate > 4 ||
|
||||
cfg->min_bit_rate < 1 ||
|
||||
(cfg->min_bit_rate == 2)) {
|
||||
pr_err("%s:session id %d: invalid min bitrate\n",
|
||||
__func__, audio->ac->session);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (cfg->max_bit_rate > 4 ||
|
||||
cfg->max_bit_rate < 1 ||
|
||||
(cfg->max_bit_rate == 2)) {
|
||||
pr_err("%s:session id %d: invalid max bitrate\n",
|
||||
__func__, audio->ac->session);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
enc_cfg->min_bit_rate = cfg->min_bit_rate;
|
||||
enc_cfg->max_bit_rate = cfg->max_bit_rate;
|
||||
pr_debug("%s:session id %d: min_bit_rate= 0x%x max_bit_rate=0x%x\n",
|
||||
__func__,
|
||||
audio->ac->session, enc_cfg->min_bit_rate,
|
||||
enc_cfg->max_bit_rate);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long evrc_in_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = evrc_in_ioctl_shared(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_EVRC_ENC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->enc_cfg,
|
||||
sizeof(struct msm_audio_evrc_enc_config))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_EVRC_ENC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_EVRC_ENC_CONFIG: {
|
||||
struct msm_audio_evrc_enc_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg, (void *) arg,
|
||||
sizeof(struct msm_audio_evrc_enc_config))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_EVRC_ENC_CONFIG failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = evrc_in_ioctl_shared(file, cmd, (unsigned long)&cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_EVRC_ENC_CONFIG failed. rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_evrc_enc_config32 {
|
||||
u32 cdma_rate;
|
||||
u32 min_bit_rate;
|
||||
u32 max_bit_rate;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_SET_EVRC_ENC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
2, struct msm_audio_evrc_enc_config32),
|
||||
AUDIO_GET_EVRC_ENC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
3, struct msm_audio_evrc_enc_config32)
|
||||
};
|
||||
|
||||
static long evrc_in_compat_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = evrc_in_ioctl_shared(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_EVRC_ENC_CONFIG_32: {
|
||||
struct msm_audio_evrc_enc_config32 cfg_32;
|
||||
struct msm_audio_evrc_enc_config *enc_cfg;
|
||||
|
||||
memset(&cfg_32, 0, sizeof(cfg_32));
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
cfg_32.cdma_rate = enc_cfg->cdma_rate;
|
||||
cfg_32.min_bit_rate = enc_cfg->min_bit_rate;
|
||||
cfg_32.max_bit_rate = enc_cfg->max_bit_rate;
|
||||
|
||||
if (copy_to_user((void *)arg, &cfg_32,
|
||||
sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_EVRC_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_EVRC_ENC_CONFIG_32: {
|
||||
struct msm_audio_evrc_enc_config cfg;
|
||||
struct msm_audio_evrc_enc_config32 cfg_32;
|
||||
|
||||
if (copy_from_user(&cfg_32, (void *) arg,
|
||||
sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_EVRC_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
cfg.cdma_rate = cfg_32.cdma_rate;
|
||||
cfg.min_bit_rate = cfg_32.min_bit_rate;
|
||||
cfg.max_bit_rate = cfg_32.max_bit_rate;
|
||||
cmd = AUDIO_SET_EVRC_ENC_CONFIG;
|
||||
rc = evrc_in_ioctl_shared(file, cmd, (unsigned long)&cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_EVRC_ENC_CONFIG failed. rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define evrc_in_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int evrc_in_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_in *audio = NULL;
|
||||
struct msm_audio_evrc_enc_config *enc_cfg;
|
||||
int rc = 0;
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Allocate memory for encoder config param */
|
||||
audio->enc_cfg = kzalloc(sizeof(struct msm_audio_evrc_enc_config),
|
||||
GFP_KERNEL);
|
||||
if (audio->enc_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
enc_cfg = audio->enc_cfg;
|
||||
mutex_init(&audio->lock);
|
||||
mutex_init(&audio->read_lock);
|
||||
mutex_init(&audio->write_lock);
|
||||
spin_lock_init(&audio->dsp_lock);
|
||||
init_waitqueue_head(&audio->read_wait);
|
||||
init_waitqueue_head(&audio->write_wait);
|
||||
|
||||
/* Settings will be re-config at AUDIO_SET_CONFIG,
|
||||
* but at least we need to have initial config
|
||||
*/
|
||||
audio->str_cfg.buffer_size = FRAME_SIZE;
|
||||
audio->str_cfg.buffer_count = FRAME_NUM;
|
||||
audio->min_frame_size = 23;
|
||||
audio->max_frames_per_buf = 10;
|
||||
audio->pcm_cfg.buffer_size = PCM_BUF_SIZE;
|
||||
audio->pcm_cfg.buffer_count = PCM_BUF_COUNT;
|
||||
enc_cfg->min_bit_rate = 4;
|
||||
enc_cfg->max_bit_rate = 4;
|
||||
audio->pcm_cfg.channel_count = 1;
|
||||
audio->pcm_cfg.sample_rate = 8000;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->event_abort = 0;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_in_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("%s: Could not allocate memory for audio client\n",
|
||||
__func__);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* open evrc encoder in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_EVRC,
|
||||
FORMAT_LINEAR_PCM);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: NT mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
pr_info("%s:session id %d: NT mode encoder success\n",
|
||||
__func__, audio->ac->session);
|
||||
} else if (!(file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
rc = q6asm_open_read(audio->ac, FORMAT_EVRC);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: T mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
/* register for tx overflow (valid for tunnel mode only) */
|
||||
rc = q6asm_reg_tx_overflow(audio->ac, 0x01);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: TX Overflow registration failed rc=%d\n",
|
||||
__func__,
|
||||
audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
pr_info("%s:session id %d: T mode encoder success\n", __func__,
|
||||
audio->ac->session);
|
||||
} else {
|
||||
pr_err("%s:session id %d: Unexpected mode\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
audio->opened = 1;
|
||||
audio->reset_event = false;
|
||||
atomic_set(&audio->in_count, PCM_BUF_COUNT);
|
||||
atomic_set(&audio->out_count, 0x00);
|
||||
audio->enc_compat_ioctl = evrc_in_compat_ioctl;
|
||||
audio->enc_ioctl = evrc_in_ioctl;
|
||||
file->private_data = audio;
|
||||
|
||||
pr_info("%s:session id %d: success\n", __func__, audio->ac->session);
|
||||
return 0;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_in_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = evrc_in_open,
|
||||
.release = audio_in_release,
|
||||
.read = audio_in_read,
|
||||
.write = audio_in_write,
|
||||
.unlocked_ioctl = audio_in_ioctl,
|
||||
.compat_ioctl = audio_in_compat_ioctl
|
||||
};
|
||||
|
||||
struct miscdevice audio_evrc_in_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_evrc_in",
|
||||
.fops = &audio_in_fops,
|
||||
};
|
||||
|
||||
static int __init evrc_in_init(void)
|
||||
{
|
||||
return misc_register(&audio_evrc_in_misc);
|
||||
}
|
||||
|
||||
device_initcall(evrc_in_init);
|
||||
382
drivers/misc/qcom/qdsp6v2/g711alaw_in.c
Normal file
382
drivers/misc/qcom/qdsp6v2/g711alaw_in.c
Normal file
@@ -0,0 +1,382 @@
|
||||
/* Copyright (c) 2010-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/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/msm_audio_g711.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/compat.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include "audio_utils.h"
|
||||
|
||||
/* Buffer with meta*/
|
||||
#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in))
|
||||
|
||||
/* Maximum 10 frames in buffer with meta */
|
||||
#define FRAME_SIZE (1 + ((320+sizeof(struct meta_out_dsp)) * 10))
|
||||
static long g711_in_ioctl_shared(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
int cnt = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct msm_audio_g711_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = (struct msm_audio_g711_enc_config *)audio->enc_cfg;
|
||||
pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__,
|
||||
audio->ac->session, audio->buf_alloc);
|
||||
if (audio->enabled == 1) {
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
rc = audio_in_buf_alloc(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: buffer allocation failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("%s: sample rate %d", __func__, enc_cfg->sample_rate);
|
||||
rc = q6asm_enc_cfg_blk_g711(audio->ac,
|
||||
audio->buf_cfg.frames_per_buf,
|
||||
enc_cfg->sample_rate);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: cmd g711 media format block failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
rc = q6asm_media_format_block_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: media format block failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pr_debug("%s:session id %d: AUDIO_START enable[%d]\n", __func__,
|
||||
audio->ac->session, audio->enabled);
|
||||
rc = audio_in_enable(audio);
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s:session id %d: Audio Start procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
while (cnt++ < audio->str_cfg.buffer_count)
|
||||
q6asm_read(audio->ac); /* Push buffer to DSP */
|
||||
rc = 0;
|
||||
pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n",
|
||||
__func__, audio->ac->session, audio->enabled);
|
||||
break;
|
||||
}
|
||||
case AUDIO_STOP: {
|
||||
pr_debug("%s:session id %d: AUDIO_STOP\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = audio_in_disable(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Audio Stop procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session,
|
||||
rc);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_G711_ENC_CONFIG: {
|
||||
struct msm_audio_g711_enc_config *cfg;
|
||||
struct msm_audio_g711_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = (struct msm_audio_g711_enc_config *)audio->enc_cfg;
|
||||
|
||||
cfg = (struct msm_audio_g711_enc_config *)arg;
|
||||
if (cfg == NULL) {
|
||||
pr_err("%s: NULL config pointer\n", __func__);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (cfg->sample_rate != 8000 &&
|
||||
cfg->sample_rate != 16000) {
|
||||
pr_err("%s:session id %d: invalid sample rate\n",
|
||||
__func__, audio->ac->session);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
enc_cfg->sample_rate = cfg->sample_rate;
|
||||
pr_debug("%s:session id %d: sample_rate= 0x%x",
|
||||
__func__,
|
||||
audio->ac->session, enc_cfg->sample_rate);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -ENOIOCTLCMD;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long g711_in_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = g711_in_ioctl_shared(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_G711_ENC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->enc_cfg,
|
||||
sizeof(struct msm_audio_g711_enc_config))) {
|
||||
pr_err(
|
||||
"%s: copy_to_user for AUDIO_GET_g711_ENC_CONFIG failed",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_G711_ENC_CONFIG: {
|
||||
struct msm_audio_g711_enc_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg, (void *) arg,
|
||||
sizeof(cfg))) {
|
||||
pr_err(
|
||||
"%s: copy_from_user for AUDIO_GET_G711_ENC_CONFIG failed",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = g711_in_ioctl_shared(file, cmd, (unsigned long)&cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_GET_G711_ENC_CONFIG failed. Rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -ENOIOCTLCMD;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_g711_enc_config32 {
|
||||
uint32_t sample_rate;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_SET_G711_ENC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_g711_enc_config32),
|
||||
AUDIO_GET_G711_ENC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_g711_enc_config32)
|
||||
};
|
||||
|
||||
static long g711_in_compat_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = g711_in_ioctl_shared(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_G711_ENC_CONFIG_32: {
|
||||
struct msm_audio_g711_enc_config32 cfg_32;
|
||||
struct msm_audio_g711_enc_config32 *enc_cfg;
|
||||
|
||||
enc_cfg = (struct msm_audio_g711_enc_config32 *)audio->enc_cfg;
|
||||
cfg_32.sample_rate = enc_cfg->sample_rate;
|
||||
if (copy_to_user((void *)arg, &cfg_32,
|
||||
sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_G711_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_G711_ENC_CONFIG_32: {
|
||||
struct msm_audio_g711_enc_config32 cfg_32;
|
||||
struct msm_audio_g711_enc_config32 cfg;
|
||||
|
||||
if (copy_from_user(&cfg_32, (void *) arg,
|
||||
sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_G711_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
cfg.sample_rate = cfg_32.sample_rate;
|
||||
cmd = AUDIO_SET_G711_ENC_CONFIG;
|
||||
rc = g711_in_ioctl_shared(file, cmd, (unsigned long)&cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_G711_ENC_CONFIG failed. rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -ENOIOCTLCMD;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define g711_in_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int g711_in_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_in *audio = NULL;
|
||||
struct msm_audio_g711_enc_config *enc_cfg;
|
||||
int rc = 0;
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
/* Allocate memory for encoder config param */
|
||||
audio->enc_cfg = kzalloc(sizeof(struct msm_audio_g711_enc_config),
|
||||
GFP_KERNEL);
|
||||
if (audio->enc_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
enc_cfg = audio->enc_cfg;
|
||||
|
||||
mutex_init(&audio->lock);
|
||||
mutex_init(&audio->read_lock);
|
||||
mutex_init(&audio->write_lock);
|
||||
spin_lock_init(&audio->dsp_lock);
|
||||
init_waitqueue_head(&audio->read_wait);
|
||||
init_waitqueue_head(&audio->write_wait);
|
||||
|
||||
/*
|
||||
* Settings will be re-config at AUDIO_SET_CONFIG,
|
||||
* but at least we need to have initial config
|
||||
*/
|
||||
audio->str_cfg.buffer_size = FRAME_SIZE;
|
||||
audio->str_cfg.buffer_count = FRAME_NUM;
|
||||
audio->min_frame_size = 320;
|
||||
audio->max_frames_per_buf = 10;
|
||||
audio->pcm_cfg.buffer_size = PCM_BUF_SIZE;
|
||||
audio->pcm_cfg.buffer_count = PCM_BUF_COUNT;
|
||||
enc_cfg->sample_rate = 8000;
|
||||
audio->pcm_cfg.channel_count = 1;
|
||||
audio->pcm_cfg.sample_rate = 8000;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->event_abort = 0;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_in_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* open g711 encoder in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_G711_ALAW_FS,
|
||||
FORMAT_LINEAR_PCM);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: NT mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
} else if (!(file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
rc = q6asm_open_read(audio->ac, FORMAT_G711_ALAW_FS);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: T mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
/* register for tx overflow (valid for tunnel mode only) */
|
||||
rc = q6asm_reg_tx_overflow(audio->ac, 0x01);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: TX Overflow registration failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
pr_err("%s:session id %d: Unexpected mode\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
audio->opened = 1;
|
||||
audio->reset_event = false;
|
||||
atomic_set(&audio->in_count, PCM_BUF_COUNT);
|
||||
atomic_set(&audio->out_count, 0x00);
|
||||
audio->enc_compat_ioctl = g711_in_compat_ioctl;
|
||||
audio->enc_ioctl = g711_in_ioctl;
|
||||
file->private_data = audio;
|
||||
|
||||
pr_info("%s:session id %d: success\n", __func__, audio->ac->session);
|
||||
return 0;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_in_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = g711_in_open,
|
||||
.release = audio_in_release,
|
||||
.read = audio_in_read,
|
||||
.write = audio_in_write,
|
||||
.unlocked_ioctl = audio_in_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = audio_in_compat_ioctl,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct miscdevice audio_g711alaw_in_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_g711alaw_in",
|
||||
.fops = &audio_in_fops,
|
||||
};
|
||||
|
||||
static int __init g711alaw_in_init(void)
|
||||
{
|
||||
return misc_register(&audio_g711alaw_in_misc);
|
||||
}
|
||||
|
||||
device_initcall(g711alaw_in_init);
|
||||
385
drivers/misc/qcom/qdsp6v2/g711mlaw_in.c
Normal file
385
drivers/misc/qcom/qdsp6v2/g711mlaw_in.c
Normal file
@@ -0,0 +1,385 @@
|
||||
/* Copyright (c) 2010-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/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/msm_audio_g711.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/compat.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include "audio_utils.h"
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
#undef PROC_ADD
|
||||
#endif
|
||||
/* Buffer with meta*/
|
||||
#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in))
|
||||
|
||||
/* Maximum 10 frames in buffer with meta */
|
||||
#define FRAME_SIZE (1 + ((320+sizeof(struct meta_out_dsp)) * 10))
|
||||
static long g711_in_ioctl_shared(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
int cnt = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct msm_audio_g711_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = (struct msm_audio_g711_enc_config *)audio->enc_cfg;
|
||||
pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__,
|
||||
audio->ac->session, audio->buf_alloc);
|
||||
if (audio->enabled == 1) {
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
rc = audio_in_buf_alloc(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: buffer allocation failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
pr_debug("%s: sample rate %d", __func__, enc_cfg->sample_rate);
|
||||
rc = q6asm_enc_cfg_blk_g711(audio->ac,
|
||||
audio->buf_cfg.frames_per_buf,
|
||||
enc_cfg->sample_rate);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: cmd g711 media format block failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
rc = q6asm_media_format_block_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: media format block failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pr_debug("%s:session id %d: AUDIO_START enable[%d]\n", __func__,
|
||||
audio->ac->session, audio->enabled);
|
||||
rc = audio_in_enable(audio);
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s:session id %d: Audio Start procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
while (cnt++ < audio->str_cfg.buffer_count)
|
||||
q6asm_read(audio->ac); /* Push buffer to DSP */
|
||||
rc = 0;
|
||||
pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n",
|
||||
__func__, audio->ac->session, audio->enabled);
|
||||
break;
|
||||
}
|
||||
case AUDIO_STOP: {
|
||||
pr_debug("%s:session id %d: AUDIO_STOP\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = audio_in_disable(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Audio Stop procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session,
|
||||
rc);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_G711_ENC_CONFIG: {
|
||||
struct msm_audio_g711_enc_config *cfg;
|
||||
struct msm_audio_g711_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = (struct msm_audio_g711_enc_config *)audio->enc_cfg;
|
||||
|
||||
cfg = (struct msm_audio_g711_enc_config *)arg;
|
||||
if (cfg == NULL) {
|
||||
pr_err("%s: NULL config pointer\n", __func__);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (cfg->sample_rate != 8000 &&
|
||||
cfg->sample_rate != 16000) {
|
||||
pr_err("%s:session id %d: invalid sample rate\n",
|
||||
__func__, audio->ac->session);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
enc_cfg->sample_rate = cfg->sample_rate;
|
||||
pr_debug("%s:session id %d: sample_rate= 0x%x",
|
||||
__func__,
|
||||
audio->ac->session, enc_cfg->sample_rate);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -ENOIOCTLCMD;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long g711_in_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = g711_in_ioctl_shared(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_G711_ENC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->enc_cfg,
|
||||
sizeof(struct msm_audio_g711_enc_config))) {
|
||||
pr_err(
|
||||
"%s: copy_to_user for AUDIO_GET_g711_ENC_CONFIG failed",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_G711_ENC_CONFIG: {
|
||||
struct msm_audio_g711_enc_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg, (void *) arg,
|
||||
sizeof(cfg))) {
|
||||
pr_err(
|
||||
"%s: copy_from_user for AUDIO_GET_G711_ENC_CONFIG failed",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = g711_in_ioctl_shared(file, cmd, (unsigned long)&cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_GET_G711_ENC_CONFIG failed. Rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -ENOIOCTLCMD;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_g711_enc_config32 {
|
||||
uint32_t sample_rate;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_SET_G711_ENC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_g711_enc_config32),
|
||||
AUDIO_GET_G711_ENC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
(AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_g711_enc_config32)
|
||||
};
|
||||
|
||||
static long g711_in_compat_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = g711_in_ioctl_shared(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_G711_ENC_CONFIG_32: {
|
||||
struct msm_audio_g711_enc_config32 cfg_32;
|
||||
struct msm_audio_g711_enc_config32 *enc_cfg;
|
||||
|
||||
enc_cfg = (struct msm_audio_g711_enc_config32 *)audio->enc_cfg;
|
||||
cfg_32.sample_rate = enc_cfg->sample_rate;
|
||||
if (copy_to_user((void *)arg, &cfg_32,
|
||||
sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_G711_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_G711_ENC_CONFIG_32: {
|
||||
struct msm_audio_g711_enc_config32 cfg_32;
|
||||
struct msm_audio_g711_enc_config32 cfg;
|
||||
|
||||
if (copy_from_user(&cfg_32, (void *) arg,
|
||||
sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_G711_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
cfg.sample_rate = cfg_32.sample_rate;
|
||||
cmd = AUDIO_SET_G711_ENC_CONFIG;
|
||||
rc = g711_in_ioctl_shared(file, cmd, (unsigned long)&cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_G711_ENC_CONFIG failed. rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -ENOIOCTLCMD;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define g711_in_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int g711_in_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_in *audio = NULL;
|
||||
struct msm_audio_g711_enc_config *enc_cfg;
|
||||
int rc = 0;
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
/* Allocate memory for encoder config param */
|
||||
audio->enc_cfg = kzalloc(sizeof(struct msm_audio_g711_enc_config),
|
||||
GFP_KERNEL);
|
||||
if (audio->enc_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
enc_cfg = audio->enc_cfg;
|
||||
|
||||
mutex_init(&audio->lock);
|
||||
mutex_init(&audio->read_lock);
|
||||
mutex_init(&audio->write_lock);
|
||||
spin_lock_init(&audio->dsp_lock);
|
||||
init_waitqueue_head(&audio->read_wait);
|
||||
init_waitqueue_head(&audio->write_wait);
|
||||
|
||||
/*
|
||||
* Settings will be re-config at AUDIO_SET_CONFIG,
|
||||
* but at least we need to have initial config
|
||||
*/
|
||||
audio->str_cfg.buffer_size = FRAME_SIZE;
|
||||
audio->str_cfg.buffer_count = FRAME_NUM;
|
||||
audio->min_frame_size = 320;
|
||||
audio->max_frames_per_buf = 10;
|
||||
audio->pcm_cfg.buffer_size = PCM_BUF_SIZE;
|
||||
audio->pcm_cfg.buffer_count = PCM_BUF_COUNT;
|
||||
enc_cfg->sample_rate = 8000;
|
||||
audio->pcm_cfg.channel_count = 1;
|
||||
audio->pcm_cfg.sample_rate = 8000;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->event_abort = 0;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_in_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* open g711 encoder in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_G711_MLAW_FS,
|
||||
FORMAT_LINEAR_PCM);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: NT mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
} else if (!(file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
rc = q6asm_open_read(audio->ac, FORMAT_G711_MLAW_FS);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: T mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
/* register for tx overflow (valid for tunnel mode only) */
|
||||
rc = q6asm_reg_tx_overflow(audio->ac, 0x01);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: TX Overflow registration failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
pr_err("%s:session id %d: Unexpected mode\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
audio->opened = 1;
|
||||
audio->reset_event = false;
|
||||
atomic_set(&audio->in_count, PCM_BUF_COUNT);
|
||||
atomic_set(&audio->out_count, 0x00);
|
||||
audio->enc_compat_ioctl = g711_in_compat_ioctl;
|
||||
audio->enc_ioctl = g711_in_ioctl;
|
||||
file->private_data = audio;
|
||||
|
||||
pr_info("%s:session id %d: success\n", __func__, audio->ac->session);
|
||||
return 0;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_in_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = g711_in_open,
|
||||
.release = audio_in_release,
|
||||
.read = audio_in_read,
|
||||
.write = audio_in_write,
|
||||
.unlocked_ioctl = audio_in_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = audio_in_compat_ioctl,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct miscdevice audio_g711mlaw_in_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_g711mlaw_in",
|
||||
.fops = &audio_in_fops,
|
||||
};
|
||||
|
||||
static int __init g711mlaw_in_init(void)
|
||||
{
|
||||
return misc_register(&audio_g711mlaw_in_misc);
|
||||
}
|
||||
|
||||
device_initcall(g711mlaw_in_init);
|
||||
37
drivers/misc/qcom/qdsp6v2/q6audio_common.h
Normal file
37
drivers/misc/qcom/qdsp6v2/q6audio_common.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/* Copyright (c) 2012-2014, 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/* For Decoders */
|
||||
#ifndef __Q6_AUDIO_COMMON_H__
|
||||
#define __Q6_AUDIO_COMMON_H__
|
||||
|
||||
#include <sound/apr_audio-v2.h>
|
||||
#include <sound/q6asm-v2.h>
|
||||
|
||||
|
||||
void q6_audio_cb(uint32_t opcode, uint32_t token,
|
||||
uint32_t *payload, void *priv);
|
||||
|
||||
void audio_aio_cb(uint32_t opcode, uint32_t token,
|
||||
uint32_t *payload, void *audio);
|
||||
|
||||
|
||||
/* For Encoders */
|
||||
void q6asm_in_cb(uint32_t opcode, uint32_t token,
|
||||
uint32_t *payload, void *priv);
|
||||
|
||||
void audio_in_get_dsp_frames(void *audio,
|
||||
uint32_t token, uint32_t *payload);
|
||||
|
||||
#endif /*__Q6_AUDIO_COMMON_H__*/
|
||||
106
drivers/misc/qcom/qdsp6v2/q6audio_v2.c
Normal file
106
drivers/misc/qcom/qdsp6v2/q6audio_v2.c
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2013, 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/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include "audio_utils.h"
|
||||
|
||||
void q6asm_in_cb(uint32_t opcode, uint32_t token,
|
||||
uint32_t *payload, void *priv)
|
||||
{
|
||||
struct q6audio_in *audio = (struct q6audio_in *)priv;
|
||||
unsigned long flags;
|
||||
|
||||
pr_debug("%s:session id %d: opcode[0x%x]\n", __func__,
|
||||
audio->ac->session, opcode);
|
||||
|
||||
spin_lock_irqsave(&audio->dsp_lock, flags);
|
||||
switch (opcode) {
|
||||
case ASM_DATA_EVENT_READ_DONE_V2:
|
||||
audio_in_get_dsp_frames(audio, token, payload);
|
||||
break;
|
||||
case ASM_DATA_EVENT_WRITE_DONE_V2:
|
||||
atomic_inc(&audio->in_count);
|
||||
wake_up(&audio->write_wait);
|
||||
break;
|
||||
case ASM_DATA_EVENT_RENDERED_EOS:
|
||||
audio->eos_rsp = 1;
|
||||
wake_up(&audio->read_wait);
|
||||
break;
|
||||
case ASM_STREAM_CMDRSP_GET_PP_PARAMS_V2:
|
||||
break;
|
||||
case ASM_SESSION_EVENTX_OVERFLOW:
|
||||
pr_err("%s:session id %d: ASM_SESSION_EVENT_TX_OVERFLOW\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
case RESET_EVENTS:
|
||||
pr_debug("%s:received RESET EVENTS\n", __func__);
|
||||
audio->enabled = 0;
|
||||
audio->stopped = 1;
|
||||
audio->event_abort = 1;
|
||||
audio->reset_event = true;
|
||||
wake_up(&audio->read_wait);
|
||||
wake_up(&audio->write_wait);
|
||||
break;
|
||||
default:
|
||||
pr_debug("%s:session id %d: Ignore opcode[0x%x]\n", __func__,
|
||||
audio->ac->session, opcode);
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&audio->dsp_lock, flags);
|
||||
}
|
||||
|
||||
void audio_in_get_dsp_frames(void *priv,
|
||||
uint32_t token, uint32_t *payload)
|
||||
{
|
||||
struct q6audio_in *audio = (struct q6audio_in *)priv;
|
||||
uint32_t index;
|
||||
|
||||
index = q6asm_get_buf_index_from_token(token);
|
||||
pr_debug("%s:session id %d: index=%d nr frames=%d offset[%d]\n",
|
||||
__func__, audio->ac->session, token, payload[9],
|
||||
payload[5]);
|
||||
pr_debug("%s:session id %d: timemsw=%d lsw=%d\n", __func__,
|
||||
audio->ac->session, payload[7], payload[6]);
|
||||
pr_debug("%s:session id %d: uflags=0x%8x uid=0x%8x\n", __func__,
|
||||
audio->ac->session, payload[8], payload[10]);
|
||||
pr_debug("%s:session id %d: enc_framesotal_size=0x%8x\n", __func__,
|
||||
audio->ac->session, payload[4]);
|
||||
|
||||
/* Ensure the index is within max array size: FRAME_NUM */
|
||||
if (index >= FRAME_NUM) {
|
||||
pr_err("%s: Invalid index %d\n",
|
||||
__func__, index);
|
||||
return;
|
||||
}
|
||||
|
||||
audio->out_frame_info[index][0] = payload[9];
|
||||
audio->out_frame_info[index][1] = payload[5];
|
||||
|
||||
/* statistics of read */
|
||||
atomic_add(payload[4], &audio->in_bytes);
|
||||
atomic_add(payload[9], &audio->in_samples);
|
||||
|
||||
if (atomic_read(&audio->out_count) <= audio->str_cfg.buffer_count) {
|
||||
atomic_inc(&audio->out_count);
|
||||
wake_up(&audio->read_wait);
|
||||
}
|
||||
}
|
||||
223
drivers/misc/qcom/qdsp6v2/q6audio_v2_aio.c
Normal file
223
drivers/misc/qcom/qdsp6v2/q6audio_v2_aio.c
Normal file
@@ -0,0 +1,223 @@
|
||||
/* 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/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include "audio_utils_aio.h"
|
||||
|
||||
void q6_audio_cb(uint32_t opcode, uint32_t token,
|
||||
uint32_t *payload, void *priv)
|
||||
{
|
||||
struct q6audio_aio *audio = (struct q6audio_aio *)priv;
|
||||
|
||||
pr_debug("%s:opcode = %x token = 0x%x\n", __func__, opcode, token);
|
||||
switch (opcode) {
|
||||
case ASM_DATA_EVENT_WRITE_DONE_V2:
|
||||
case ASM_DATA_EVENT_READ_DONE_V2:
|
||||
case ASM_DATA_EVENT_RENDERED_EOS:
|
||||
case ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2:
|
||||
case ASM_STREAM_CMD_SET_ENCDEC_PARAM:
|
||||
case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY:
|
||||
case ASM_DATA_EVENT_ENC_SR_CM_CHANGE_NOTIFY:
|
||||
case RESET_EVENTS:
|
||||
audio_aio_cb(opcode, token, payload, audio);
|
||||
break;
|
||||
default:
|
||||
pr_debug("%s:Unhandled event = 0x%8x\n", __func__, opcode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void audio_aio_cb(uint32_t opcode, uint32_t token,
|
||||
uint32_t *payload, void *priv/*struct q6audio_aio *audio*/)
|
||||
{
|
||||
struct q6audio_aio *audio = (struct q6audio_aio *)priv;
|
||||
union msm_audio_event_payload e_payload;
|
||||
|
||||
switch (opcode) {
|
||||
case ASM_DATA_EVENT_WRITE_DONE_V2:
|
||||
pr_debug("%s[%pK]:ASM_DATA_EVENT_WRITE_DONE token = 0x%x\n",
|
||||
__func__, audio, token);
|
||||
audio_aio_async_write_ack(audio, token, payload);
|
||||
break;
|
||||
case ASM_DATA_EVENT_READ_DONE_V2:
|
||||
pr_debug("%s[%pK]:ASM_DATA_EVENT_READ_DONE token = 0x%x\n",
|
||||
__func__, audio, token);
|
||||
audio_aio_async_read_ack(audio, token, payload);
|
||||
break;
|
||||
case ASM_DATA_EVENT_RENDERED_EOS:
|
||||
/* EOS Handle */
|
||||
pr_debug("%s[%pK]:ASM_DATA_CMDRSP_EOS\n", __func__, audio);
|
||||
if (audio->feedback) { /* Non-Tunnel mode */
|
||||
audio->eos_rsp = 1;
|
||||
/* propagate input EOS i/p buffer,
|
||||
* after receiving DSP acknowledgment
|
||||
*/
|
||||
if (audio->eos_flag &&
|
||||
(audio->eos_write_payload.aio_buf.buf_addr)) {
|
||||
audio_aio_post_event(audio,
|
||||
AUDIO_EVENT_WRITE_DONE,
|
||||
audio->eos_write_payload);
|
||||
memset(&audio->eos_write_payload, 0,
|
||||
sizeof(union msm_audio_event_payload));
|
||||
audio->eos_flag = 0;
|
||||
}
|
||||
} else { /* Tunnel mode */
|
||||
audio->eos_rsp = 1;
|
||||
wake_up(&audio->write_wait);
|
||||
wake_up(&audio->cmd_wait);
|
||||
}
|
||||
break;
|
||||
case ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2:
|
||||
case ASM_STREAM_CMD_SET_ENCDEC_PARAM:
|
||||
pr_debug("%s[%pK]:payload0[%x] payloa1d[%x]opcode= 0x%x\n",
|
||||
__func__, audio, payload[0], payload[1], opcode);
|
||||
break;
|
||||
case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY:
|
||||
case ASM_DATA_EVENT_ENC_SR_CM_CHANGE_NOTIFY:
|
||||
pr_debug("%s[%pK]: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, payload[0]-sr = %d, payload[1]-chl = %d, payload[2] = %d, payload[3] = %d\n",
|
||||
__func__, audio, payload[0],
|
||||
payload[1], payload[2], payload[3]);
|
||||
|
||||
pr_debug("%s[%pK]: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, sr(prev) = %d, chl(prev) = %d,",
|
||||
__func__, audio, audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
|
||||
audio->pcm_cfg.sample_rate = payload[0];
|
||||
audio->pcm_cfg.channel_count = payload[1] & 0xFFFF;
|
||||
e_payload.stream_info.chan_info = audio->pcm_cfg.channel_count;
|
||||
e_payload.stream_info.sample_rate = audio->pcm_cfg.sample_rate;
|
||||
audio_aio_post_event(audio, AUDIO_EVENT_STREAM_INFO, e_payload);
|
||||
break;
|
||||
case RESET_EVENTS:
|
||||
pr_err("%s: Received opcode:0x%x\n", __func__, opcode);
|
||||
audio->stopped = 1;
|
||||
audio->reset_event = true;
|
||||
wake_up(&audio->event_wait);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void extract_meta_out_info(struct q6audio_aio *audio,
|
||||
struct audio_aio_buffer_node *buf_node, int dir)
|
||||
{
|
||||
struct dec_meta_out *meta_data = buf_node->kvaddr;
|
||||
uint32_t temp;
|
||||
|
||||
if (dir) { /* input buffer - Write */
|
||||
if (audio->buf_cfg.meta_info_enable)
|
||||
memcpy(&buf_node->meta_info.meta_in,
|
||||
(char *)buf_node->kvaddr, sizeof(struct dec_meta_in));
|
||||
else
|
||||
memset(&buf_node->meta_info.meta_in,
|
||||
0, sizeof(struct dec_meta_in));
|
||||
pr_debug("%s[%pK]:i/p: msw_ts %d lsw_ts %d nflags 0x%8x\n",
|
||||
__func__, audio,
|
||||
buf_node->meta_info.meta_in.ntimestamp.highpart,
|
||||
buf_node->meta_info.meta_in.ntimestamp.lowpart,
|
||||
buf_node->meta_info.meta_in.nflags);
|
||||
} else { /* output buffer - Read */
|
||||
memcpy((char *)buf_node->kvaddr,
|
||||
&buf_node->meta_info.meta_out,
|
||||
sizeof(struct dec_meta_out));
|
||||
meta_data->meta_out_dsp[0].nflags = 0x00000000;
|
||||
temp = meta_data->meta_out_dsp[0].msw_ts;
|
||||
meta_data->meta_out_dsp[0].msw_ts =
|
||||
meta_data->meta_out_dsp[0].lsw_ts;
|
||||
meta_data->meta_out_dsp[0].lsw_ts = temp;
|
||||
|
||||
pr_debug("%s[%pK]:o/p: msw_ts %d lsw_ts %d nflags 0x%8x, num_frames = %d\n",
|
||||
__func__, audio,
|
||||
((struct dec_meta_out *)buf_node->kvaddr)->
|
||||
meta_out_dsp[0].msw_ts,
|
||||
((struct dec_meta_out *)buf_node->kvaddr)->
|
||||
meta_out_dsp[0].lsw_ts,
|
||||
((struct dec_meta_out *)buf_node->kvaddr)->
|
||||
meta_out_dsp[0].nflags,
|
||||
((struct dec_meta_out *)buf_node->kvaddr)->num_of_frames);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read buffer from DSP / Handle Ack from DSP */
|
||||
void audio_aio_async_read_ack(struct q6audio_aio *audio, uint32_t token,
|
||||
uint32_t *payload)
|
||||
{
|
||||
unsigned long flags;
|
||||
union msm_audio_event_payload event_payload;
|
||||
struct audio_aio_buffer_node *filled_buf;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
/* No active flush in progress */
|
||||
if (audio->rflush)
|
||||
return;
|
||||
|
||||
/* Statistics of read */
|
||||
atomic_add(payload[4], &audio->in_bytes);
|
||||
atomic_add(payload[9], &audio->in_samples);
|
||||
|
||||
spin_lock_irqsave(&audio->dsp_lock, flags);
|
||||
if (list_empty(&audio->in_queue)) {
|
||||
spin_unlock_irqrestore(&audio->dsp_lock, flags);
|
||||
pr_warn("%s unexpected ack from dsp\n", __func__);
|
||||
return;
|
||||
}
|
||||
filled_buf = list_first_entry(&audio->in_queue,
|
||||
struct audio_aio_buffer_node, list);
|
||||
|
||||
pr_debug("%s token: 0x[%x], filled_buf->token: 0x[%x]",
|
||||
__func__, token, filled_buf->token);
|
||||
if (token == (filled_buf->token)) {
|
||||
list_del(&filled_buf->list);
|
||||
spin_unlock_irqrestore(&audio->dsp_lock, flags);
|
||||
event_payload.aio_buf = filled_buf->buf;
|
||||
/* Read done Buffer due to flush/normal condition
|
||||
* after EOS event, so append EOS buffer
|
||||
*/
|
||||
if (audio->eos_rsp == 0x1) {
|
||||
event_payload.aio_buf.data_len =
|
||||
insert_eos_buf(audio, filled_buf);
|
||||
/* Reset flag back to indicate eos intimated */
|
||||
audio->eos_rsp = 0;
|
||||
} else {
|
||||
filled_buf->meta_info.meta_out.num_of_frames
|
||||
= payload[9];
|
||||
event_payload.aio_buf.data_len = payload[4]
|
||||
+ payload[5] + sizeof(struct dec_meta_out);
|
||||
pr_debug("%s[%pK]:nr of frames 0x%8x len=%d\n",
|
||||
__func__, audio,
|
||||
filled_buf->meta_info.meta_out.num_of_frames,
|
||||
event_payload.aio_buf.data_len);
|
||||
extract_meta_out_info(audio, filled_buf, 0);
|
||||
audio->eos_rsp = 0;
|
||||
}
|
||||
pr_debug("%s, posting read done to the app here\n", __func__);
|
||||
audio_aio_post_event(audio, AUDIO_EVENT_READ_DONE,
|
||||
event_payload);
|
||||
kfree(filled_buf);
|
||||
} else {
|
||||
pr_err("%s[%pK]:expected=%x ret=%x\n",
|
||||
__func__, audio, filled_buf->token, token);
|
||||
spin_unlock_irqrestore(&audio->dsp_lock, flags);
|
||||
}
|
||||
}
|
||||
410
drivers/misc/qcom/qdsp6v2/qcelp_in.c
Normal file
410
drivers/misc/qcom/qdsp6v2/qcelp_in.c
Normal file
@@ -0,0 +1,410 @@
|
||||
/* Copyright (c) 2010-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/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/msm_audio_qcp.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/compat.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include "audio_utils.h"
|
||||
|
||||
/* Buffer with meta*/
|
||||
#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in))
|
||||
|
||||
/* Maximum 10 frames in buffer with meta */
|
||||
#define FRAME_SIZE (1 + ((35+sizeof(struct meta_out_dsp)) * 10))
|
||||
|
||||
static long qcelp_in_ioctl_shared(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
int cnt = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START: {
|
||||
struct msm_audio_qcelp_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__,
|
||||
audio->ac->session, audio->buf_alloc);
|
||||
if (audio->enabled == 1) {
|
||||
pr_info("%s:AUDIO_START already over\n", __func__);
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
rc = audio_in_buf_alloc(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: buffer allocation failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
|
||||
/* reduced_rate_level, rate_modulation_cmd set to zero
|
||||
* currently not configurable from user space
|
||||
*/
|
||||
rc = q6asm_enc_cfg_blk_qcelp(audio->ac,
|
||||
audio->buf_cfg.frames_per_buf,
|
||||
enc_cfg->min_bit_rate,
|
||||
enc_cfg->max_bit_rate, 0, 0);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: cmd qcelp media format block failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
if (audio->feedback == NON_TUNNEL_MODE) {
|
||||
rc = q6asm_media_format_block_pcm(audio->ac,
|
||||
audio->pcm_cfg.sample_rate,
|
||||
audio->pcm_cfg.channel_count);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: media format block failed\n",
|
||||
__func__, audio->ac->session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pr_debug("%s:session id %d: AUDIO_START enable[%d]\n", __func__,
|
||||
audio->ac->session, audio->enabled);
|
||||
rc = audio_in_enable(audio);
|
||||
if (!rc) {
|
||||
audio->enabled = 1;
|
||||
} else {
|
||||
audio->enabled = 0;
|
||||
pr_err("%s:session id %d: Audio Start procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
break;
|
||||
}
|
||||
while (cnt++ < audio->str_cfg.buffer_count)
|
||||
q6asm_read(audio->ac); /* Push buffer to DSP */
|
||||
rc = 0;
|
||||
pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n",
|
||||
__func__, audio->ac->session, audio->enabled);
|
||||
break;
|
||||
}
|
||||
case AUDIO_STOP: {
|
||||
pr_debug("%s:session id %d: AUDIO_STOP\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = audio_in_disable(audio);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: Audio Stop procedure failed rc=%d\n",
|
||||
__func__, audio->ac->session,
|
||||
rc);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_QCELP_ENC_CONFIG: {
|
||||
struct msm_audio_qcelp_enc_config *cfg;
|
||||
struct msm_audio_qcelp_enc_config *enc_cfg;
|
||||
|
||||
enc_cfg = audio->enc_cfg;
|
||||
cfg = (struct msm_audio_qcelp_enc_config *)arg;
|
||||
if (cfg == NULL) {
|
||||
pr_err("%s: NULL config pointer\n", __func__);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (cfg->min_bit_rate > 4 ||
|
||||
cfg->min_bit_rate < 1) {
|
||||
pr_err("%s:session id %d: invalid min bitrate\n",
|
||||
__func__, audio->ac->session);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (cfg->max_bit_rate > 4 ||
|
||||
cfg->max_bit_rate < 1) {
|
||||
pr_err("%s:session id %d: invalid max bitrate\n",
|
||||
__func__, audio->ac->session);
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
enc_cfg->cdma_rate = cfg->cdma_rate;
|
||||
enc_cfg->min_bit_rate = cfg->min_bit_rate;
|
||||
enc_cfg->max_bit_rate = cfg->max_bit_rate;
|
||||
pr_debug("%s:session id %d: min_bit_rate= 0x%x max_bit_rate=0x%x\n",
|
||||
__func__,
|
||||
audio->ac->session, enc_cfg->min_bit_rate,
|
||||
enc_cfg->max_bit_rate);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long qcelp_in_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = qcelp_in_ioctl_shared(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_QCELP_ENC_CONFIG: {
|
||||
if (copy_to_user((void *)arg, audio->enc_cfg,
|
||||
sizeof(struct msm_audio_qcelp_enc_config))) {
|
||||
pr_err(
|
||||
"%s: copy_to_user for AUDIO_GET_QCELP_ENC_CONFIG failed",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_QCELP_ENC_CONFIG: {
|
||||
struct msm_audio_qcelp_enc_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg, (void *) arg,
|
||||
sizeof(cfg))) {
|
||||
pr_err(
|
||||
"%s: copy_from_user for AUDIO_SET_QCELP_ENC_CONFIG failed",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rc = qcelp_in_ioctl_shared(file, cmd, (unsigned long)&cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_QCELP_ENC_CONFIG failed. Rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct msm_audio_qcelp_enc_config32 {
|
||||
u32 cdma_rate;
|
||||
u32 min_bit_rate;
|
||||
u32 max_bit_rate;
|
||||
};
|
||||
|
||||
enum {
|
||||
AUDIO_SET_QCELP_ENC_CONFIG_32 = _IOW(AUDIO_IOCTL_MAGIC,
|
||||
0, struct msm_audio_qcelp_enc_config32),
|
||||
AUDIO_GET_QCELP_ENC_CONFIG_32 = _IOR(AUDIO_IOCTL_MAGIC,
|
||||
1, struct msm_audio_qcelp_enc_config32)
|
||||
};
|
||||
|
||||
static long qcelp_in_compat_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct q6audio_in *audio = file->private_data;
|
||||
int rc = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case AUDIO_START:
|
||||
case AUDIO_STOP: {
|
||||
rc = qcelp_in_ioctl_shared(file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
case AUDIO_GET_QCELP_ENC_CONFIG_32: {
|
||||
struct msm_audio_qcelp_enc_config32 cfg_32;
|
||||
struct msm_audio_qcelp_enc_config *enc_cfg;
|
||||
|
||||
memset(&cfg_32, 0, sizeof(cfg_32));
|
||||
|
||||
enc_cfg = (struct msm_audio_qcelp_enc_config *)audio->enc_cfg;
|
||||
cfg_32.cdma_rate = enc_cfg->cdma_rate;
|
||||
cfg_32.min_bit_rate = enc_cfg->min_bit_rate;
|
||||
cfg_32.max_bit_rate = enc_cfg->max_bit_rate;
|
||||
if (copy_to_user((void *)arg, &cfg_32,
|
||||
sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_to_user for AUDIO_GET_QCELP_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_SET_QCELP_ENC_CONFIG_32: {
|
||||
struct msm_audio_qcelp_enc_config32 cfg_32;
|
||||
struct msm_audio_qcelp_enc_config cfg;
|
||||
|
||||
if (copy_from_user(&cfg_32, (void *) arg,
|
||||
sizeof(cfg_32))) {
|
||||
pr_err("%s: copy_from_user for AUDIO_SET_QCELP_ENC_CONFIG_32 failed\n",
|
||||
__func__);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
cfg.cdma_rate = cfg_32.cdma_rate;
|
||||
cfg.min_bit_rate = cfg_32.min_bit_rate;
|
||||
cfg.max_bit_rate = cfg_32.max_bit_rate;
|
||||
cmd = AUDIO_SET_QCELP_ENC_CONFIG;
|
||||
rc = qcelp_in_ioctl_shared(file, cmd, (unsigned long)&cfg);
|
||||
if (rc)
|
||||
pr_err("%s:AUDIO_SET_QCELP_ENC_CONFIG failed. rc= %d\n",
|
||||
__func__, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("%s: Unknown ioctl cmd = %d", __func__, cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
#define qcelp_in_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
static int qcelp_in_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct q6audio_in *audio = NULL;
|
||||
struct msm_audio_qcelp_enc_config *enc_cfg;
|
||||
int rc = 0;
|
||||
|
||||
audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL);
|
||||
|
||||
if (audio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Allocate memory for encoder config param */
|
||||
audio->enc_cfg = kzalloc(sizeof(struct msm_audio_qcelp_enc_config),
|
||||
GFP_KERNEL);
|
||||
if (audio->enc_cfg == NULL) {
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
enc_cfg = audio->enc_cfg;
|
||||
|
||||
mutex_init(&audio->lock);
|
||||
mutex_init(&audio->read_lock);
|
||||
mutex_init(&audio->write_lock);
|
||||
spin_lock_init(&audio->dsp_lock);
|
||||
init_waitqueue_head(&audio->read_wait);
|
||||
init_waitqueue_head(&audio->write_wait);
|
||||
|
||||
/* Settings will be re-config at AUDIO_SET_CONFIG,
|
||||
* but at least we need to have initial config
|
||||
*/
|
||||
audio->str_cfg.buffer_size = FRAME_SIZE;
|
||||
audio->str_cfg.buffer_count = FRAME_NUM;
|
||||
audio->min_frame_size = 35;
|
||||
audio->max_frames_per_buf = 10;
|
||||
audio->pcm_cfg.buffer_size = PCM_BUF_SIZE;
|
||||
audio->pcm_cfg.buffer_count = PCM_BUF_COUNT;
|
||||
enc_cfg->min_bit_rate = 4;
|
||||
enc_cfg->max_bit_rate = 4;
|
||||
audio->pcm_cfg.channel_count = 1;
|
||||
audio->pcm_cfg.sample_rate = 8000;
|
||||
audio->buf_cfg.meta_info_enable = 0x01;
|
||||
audio->buf_cfg.frames_per_buf = 0x01;
|
||||
audio->event_abort = 0;
|
||||
|
||||
audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_in_cb,
|
||||
(void *)audio);
|
||||
|
||||
if (!audio->ac) {
|
||||
pr_err("%s: Could not allocate memory for audio client\n",
|
||||
__func__);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* open qcelp encoder in T/NT mode */
|
||||
if ((file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = NON_TUNNEL_MODE;
|
||||
rc = q6asm_open_read_write(audio->ac, FORMAT_V13K,
|
||||
FORMAT_LINEAR_PCM);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: NT mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
pr_info("%s:session id %d: NT mode encoder success\n", __func__,
|
||||
audio->ac->session);
|
||||
} else if (!(file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_mode & FMODE_READ)) {
|
||||
audio->feedback = TUNNEL_MODE;
|
||||
rc = q6asm_open_read(audio->ac, FORMAT_V13K);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: T mode Open failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
/* register for tx overflow (valid for tunnel mode only) */
|
||||
rc = q6asm_reg_tx_overflow(audio->ac, 0x01);
|
||||
if (rc < 0) {
|
||||
pr_err("%s:session id %d: TX Overflow registration failed rc=%d\n",
|
||||
__func__, audio->ac->session, rc);
|
||||
rc = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
pr_info("%s:session id %d: T mode encoder success\n", __func__,
|
||||
audio->ac->session);
|
||||
} else {
|
||||
pr_err("%s:session id %d: Unexpected mode\n", __func__,
|
||||
audio->ac->session);
|
||||
rc = -EACCES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
audio->opened = 1;
|
||||
audio->reset_event = false;
|
||||
atomic_set(&audio->in_count, PCM_BUF_COUNT);
|
||||
atomic_set(&audio->out_count, 0x00);
|
||||
audio->enc_compat_ioctl = qcelp_in_compat_ioctl;
|
||||
audio->enc_ioctl = qcelp_in_ioctl;
|
||||
file->private_data = audio;
|
||||
|
||||
pr_info("%s:session id %d: success\n", __func__, audio->ac->session);
|
||||
return 0;
|
||||
fail:
|
||||
q6asm_audio_client_free(audio->ac);
|
||||
kfree(audio->enc_cfg);
|
||||
kfree(audio);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations audio_in_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = qcelp_in_open,
|
||||
.release = audio_in_release,
|
||||
.read = audio_in_read,
|
||||
.write = audio_in_write,
|
||||
.unlocked_ioctl = audio_in_ioctl,
|
||||
.compat_ioctl = audio_in_compat_ioctl
|
||||
};
|
||||
|
||||
struct miscdevice audio_qcelp_in_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "msm_qcelp_in",
|
||||
.fops = &audio_in_fops,
|
||||
};
|
||||
|
||||
static int __init qcelp_in_init(void)
|
||||
{
|
||||
return misc_register(&audio_qcelp_in_misc);
|
||||
}
|
||||
|
||||
device_initcall(qcelp_in_init);
|
||||
2
drivers/misc/qcom/qdsp6v2/ultrasound/Makefile
Normal file
2
drivers/misc/qcom/qdsp6v2/ultrasound/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
ccflags-y := -I$(src)/..
|
||||
obj-$(CONFIG_MSM_ULTRASOUND) += usf.o usfcdev.o q6usm.o
|
||||
1467
drivers/misc/qcom/qdsp6v2/ultrasound/q6usm.c
Normal file
1467
drivers/misc/qcom/qdsp6v2/ultrasound/q6usm.c
Normal file
File diff suppressed because it is too large
Load Diff
130
drivers/misc/qcom/qdsp6v2/ultrasound/q6usm.h
Normal file
130
drivers/misc/qcom/qdsp6v2/ultrasound/q6usm.h
Normal file
@@ -0,0 +1,130 @@
|
||||
/* Copyright (c) 2011-2014, 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 __Q6_USM_H__
|
||||
#define __Q6_USM_H__
|
||||
|
||||
#include <linux/qdsp6v2/apr_us.h>
|
||||
|
||||
#define Q6USM_EVENT_UNDEF 0
|
||||
#define Q6USM_EVENT_READ_DONE 1
|
||||
#define Q6USM_EVENT_WRITE_DONE 2
|
||||
#define Q6USM_EVENT_SIGNAL_DETECT_RESULT 3
|
||||
|
||||
/* cyclic buffer with 1 gap support */
|
||||
#define USM_MIN_BUF_CNT 3
|
||||
|
||||
#define FORMAT_USPS_EPOS 0x00000000
|
||||
#define FORMAT_USRAW 0x00000001
|
||||
#define FORMAT_USPROX 0x00000002
|
||||
#define FORMAT_USGES_SYNC 0x00000003
|
||||
#define FORMAT_USRAW_SYNC 0x00000004
|
||||
#define INVALID_FORMAT 0xffffffff
|
||||
|
||||
#define IN 0x000
|
||||
#define OUT 0x001
|
||||
|
||||
#define USM_WRONG_TOKEN 0xffffffff
|
||||
#define USM_UNDEF_TOKEN 0xfffffffe
|
||||
|
||||
#define CMD_CLOSE 0x0004
|
||||
|
||||
/* bit 0:1 represents priority of stream */
|
||||
#define STREAM_PRIORITY_NORMAL 0x0000
|
||||
#define STREAM_PRIORITY_LOW 0x0001
|
||||
#define STREAM_PRIORITY_HIGH 0x0002
|
||||
|
||||
/* bit 4 represents META enable of encoded data buffer */
|
||||
#define BUFFER_META_ENABLE 0x0010
|
||||
|
||||
struct us_port_data {
|
||||
dma_addr_t phys;
|
||||
/* cyclic region of buffers with 1 gap */
|
||||
void *data;
|
||||
/* number of buffers in the region */
|
||||
uint32_t buf_cnt;
|
||||
/* size of buffer */
|
||||
size_t buf_size;
|
||||
/* write index */
|
||||
uint32_t dsp_buf;
|
||||
/* read index */
|
||||
uint32_t cpu_buf;
|
||||
/* expected token from dsp */
|
||||
uint32_t expected_token;
|
||||
/* read or write locks */
|
||||
struct mutex lock;
|
||||
spinlock_t dsp_lock;
|
||||
/* ION memory handle */
|
||||
struct ion_handle *handle;
|
||||
/* ION memory client */
|
||||
struct ion_client *client;
|
||||
/* extended parameters, related to q6 variants */
|
||||
void *ext;
|
||||
/* physical address of parameter buffer */
|
||||
dma_addr_t param_phys;
|
||||
/* buffer which stores the parameter data */
|
||||
void *param_buf;
|
||||
/* size of parameter buffer */
|
||||
uint32_t param_buf_size;
|
||||
/* parameter buffer memory handle */
|
||||
void *param_buf_mem_handle;
|
||||
/* ION memory handle for parameter buffer */
|
||||
struct ion_handle *param_handle;
|
||||
/* ION memory client for parameter buffer */
|
||||
struct ion_client *param_client;
|
||||
};
|
||||
|
||||
struct us_client {
|
||||
int session;
|
||||
/* idx:1 out port, 0: in port*/
|
||||
struct us_port_data port[2];
|
||||
|
||||
struct apr_svc *apr;
|
||||
struct mutex cmd_lock;
|
||||
|
||||
atomic_t cmd_state;
|
||||
atomic_t eos_state;
|
||||
wait_queue_head_t cmd_wait;
|
||||
|
||||
void (*cb)(uint32_t, uint32_t, uint32_t *, void *);
|
||||
void *priv;
|
||||
};
|
||||
|
||||
int q6usm_run(struct us_client *usc, uint32_t flags,
|
||||
uint32_t msw_ts, uint32_t lsw_ts);
|
||||
int q6usm_cmd(struct us_client *usc, int cmd);
|
||||
int q6usm_us_client_buf_alloc(unsigned int dir, struct us_client *usc,
|
||||
unsigned int bufsz, unsigned int bufcnt);
|
||||
int q6usm_us_param_buf_alloc(unsigned int dir, struct us_client *usc,
|
||||
unsigned int bufsz);
|
||||
int q6usm_enc_cfg_blk(struct us_client *usc, struct us_encdec_cfg *us_cfg);
|
||||
int q6usm_dec_cfg_blk(struct us_client *usc, struct us_encdec_cfg *us_cfg);
|
||||
int q6usm_read(struct us_client *usc, uint32_t read_ind);
|
||||
struct us_client *q6usm_us_client_alloc(
|
||||
void (*cb)(uint32_t, uint32_t, uint32_t *, void *),
|
||||
void *priv);
|
||||
int q6usm_open_read(struct us_client *usc, uint32_t format);
|
||||
void q6usm_us_client_free(struct us_client *usc);
|
||||
uint32_t q6usm_get_virtual_address(int dir, struct us_client *usc,
|
||||
struct vm_area_struct *vms);
|
||||
int q6usm_open_write(struct us_client *usc, uint32_t format);
|
||||
int q6usm_write(struct us_client *usc, uint32_t write_ind);
|
||||
bool q6usm_is_write_buf_full(struct us_client *usc, uint32_t *free_region);
|
||||
int q6usm_set_us_detection(struct us_client *usc,
|
||||
struct usm_session_cmd_detect_info *detect_info,
|
||||
uint16_t detect_info_size);
|
||||
int q6usm_set_us_stream_param(int dir, struct us_client *usc,
|
||||
uint32_t module_id, uint32_t param_id, uint32_t buf_size);
|
||||
int q6usm_get_us_stream_param(int dir, struct us_client *usc,
|
||||
uint32_t module_id, uint32_t param_id, uint32_t buf_size);
|
||||
|
||||
#endif /* __Q6_USM_H__ */
|
||||
2465
drivers/misc/qcom/qdsp6v2/ultrasound/usf.c
Normal file
2465
drivers/misc/qcom/qdsp6v2/ultrasound/usf.c
Normal file
File diff suppressed because it is too large
Load Diff
422
drivers/misc/qcom/qdsp6v2/ultrasound/usfcdev.c
Normal file
422
drivers/misc/qcom/qdsp6v2/ultrasound/usfcdev.c
Normal file
@@ -0,0 +1,422 @@
|
||||
/* Copyright (c) 2012-2013, 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/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/input/mt.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include "usfcdev.h"
|
||||
|
||||
#define UNDEF_ID 0xffffffff
|
||||
#define SLOT_CMD_ID 0
|
||||
#define MAX_RETRIES 10
|
||||
|
||||
enum usdev_event_status {
|
||||
USFCDEV_EVENT_ENABLED,
|
||||
USFCDEV_EVENT_DISABLING,
|
||||
USFCDEV_EVENT_DISABLED,
|
||||
};
|
||||
|
||||
struct usfcdev_event {
|
||||
bool (*match_cb)(uint16_t, struct input_dev *dev);
|
||||
bool registered_event;
|
||||
bool interleaved;
|
||||
enum usdev_event_status event_status;
|
||||
};
|
||||
static struct usfcdev_event s_usfcdev_events[MAX_EVENT_TYPE_NUM];
|
||||
|
||||
struct usfcdev_input_command {
|
||||
unsigned int type;
|
||||
unsigned int code;
|
||||
unsigned int value;
|
||||
};
|
||||
|
||||
static long s_usf_pid;
|
||||
|
||||
static bool usfcdev_filter(struct input_handle *handle,
|
||||
unsigned int type, unsigned int code, int value);
|
||||
static bool usfcdev_match(struct input_handler *handler,
|
||||
struct input_dev *dev);
|
||||
static int usfcdev_connect(struct input_handler *handler,
|
||||
struct input_dev *dev,
|
||||
const struct input_device_id *id);
|
||||
static void usfcdev_disconnect(struct input_handle *handle);
|
||||
|
||||
static const struct input_device_id usfc_tsc_ids[] = {
|
||||
{
|
||||
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
|
||||
INPUT_DEVICE_ID_MATCH_KEYBIT |
|
||||
INPUT_DEVICE_ID_MATCH_ABSBIT,
|
||||
.evbit = { BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY) },
|
||||
.keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
|
||||
/* assumption: ABS_X & ABS_Y are in the same long */
|
||||
.absbit = { [BIT_WORD(ABS_X)] = BIT_MASK(ABS_X) |
|
||||
BIT_MASK(ABS_Y) },
|
||||
},
|
||||
{
|
||||
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
|
||||
INPUT_DEVICE_ID_MATCH_KEYBIT |
|
||||
INPUT_DEVICE_ID_MATCH_ABSBIT,
|
||||
.evbit = { BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY) },
|
||||
.keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
|
||||
/* assumption: MT_.._X & MT_.._Y are in the same long */
|
||||
.absbit = { [BIT_WORD(ABS_MT_POSITION_X)] =
|
||||
BIT_MASK(ABS_MT_POSITION_X) |
|
||||
BIT_MASK(ABS_MT_POSITION_Y) },
|
||||
},
|
||||
{ } /* Terminating entry */
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(input, usfc_tsc_ids);
|
||||
|
||||
static struct input_handler s_usfc_handlers[MAX_EVENT_TYPE_NUM] = {
|
||||
{ /* TSC handler */
|
||||
.filter = usfcdev_filter,
|
||||
.match = usfcdev_match,
|
||||
.connect = usfcdev_connect,
|
||||
.disconnect = usfcdev_disconnect,
|
||||
/* .minor can be used as index in the container, */
|
||||
/* because .fops isn't supported */
|
||||
.minor = TSC_EVENT_TYPE_IND,
|
||||
.name = "usfc_tsc_handler",
|
||||
.id_table = usfc_tsc_ids,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* For each event type, there are a number conflicting devices (handles)
|
||||
* The first registered device (primary) is real TSC device; it's mandatory
|
||||
* Optionally, later registered devices are simulated ones.
|
||||
* They are dynamically managed
|
||||
* The primary device's handles are stored in the below static array
|
||||
*/
|
||||
static struct input_handle s_usfc_primary_handles[MAX_EVENT_TYPE_NUM] = {
|
||||
{ /* TSC handle */
|
||||
.handler = &s_usfc_handlers[TSC_EVENT_TYPE_IND],
|
||||
.name = "usfc_tsc_handle",
|
||||
},
|
||||
};
|
||||
|
||||
static struct usfcdev_input_command initial_clear_cmds[] = {
|
||||
{EV_ABS, ABS_PRESSURE, 0},
|
||||
{EV_KEY, BTN_TOUCH, 0},
|
||||
};
|
||||
|
||||
static struct usfcdev_input_command slot_clear_cmds[] = {
|
||||
{EV_ABS, ABS_MT_SLOT, 0},
|
||||
{EV_ABS, ABS_MT_TRACKING_ID, UNDEF_ID},
|
||||
};
|
||||
|
||||
static struct usfcdev_input_command no_filter_cmds[] = {
|
||||
{EV_ABS, ABS_MT_SLOT, 0},
|
||||
{EV_ABS, ABS_MT_TRACKING_ID, UNDEF_ID},
|
||||
{EV_SYN, SYN_REPORT, 0},
|
||||
};
|
||||
|
||||
static bool usfcdev_match(struct input_handler *handler, struct input_dev *dev)
|
||||
{
|
||||
bool rc = false;
|
||||
int ind = handler->minor;
|
||||
|
||||
pr_debug("%s: name=[%s]; ind=%d\n", __func__, dev->name, ind);
|
||||
|
||||
if (s_usfcdev_events[ind].registered_event &&
|
||||
s_usfcdev_events[ind].match_cb) {
|
||||
rc = (*s_usfcdev_events[ind].match_cb)((uint16_t)ind, dev);
|
||||
pr_debug("%s: [%s]; rc=%d\n", __func__, dev->name, rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int usfcdev_connect(struct input_handler *handler, struct input_dev *dev,
|
||||
const struct input_device_id *id)
|
||||
{
|
||||
int ret = 0;
|
||||
uint16_t ind = handler->minor;
|
||||
struct input_handle *usfc_handle = NULL;
|
||||
|
||||
if (s_usfc_primary_handles[ind].dev == NULL) {
|
||||
pr_debug("%s: primary device; ind=%d\n",
|
||||
__func__,
|
||||
ind);
|
||||
usfc_handle = &s_usfc_primary_handles[ind];
|
||||
} else {
|
||||
pr_debug("%s: secondary device; ind=%d\n",
|
||||
__func__,
|
||||
ind);
|
||||
usfc_handle = kzalloc(sizeof(struct input_handle),
|
||||
GFP_KERNEL);
|
||||
if (!usfc_handle)
|
||||
return -ENOMEM;
|
||||
|
||||
usfc_handle->handler = &s_usfc_handlers[ind];
|
||||
usfc_handle->name = s_usfc_primary_handles[ind].name;
|
||||
}
|
||||
usfc_handle->dev = dev;
|
||||
ret = input_register_handle(usfc_handle);
|
||||
pr_debug("%s: name=[%s]; ind=%d; dev=0x%pK\n",
|
||||
__func__,
|
||||
dev->name,
|
||||
ind,
|
||||
usfc_handle->dev);
|
||||
if (ret)
|
||||
pr_err("%s: input_register_handle[%d] failed: ret=%d\n",
|
||||
__func__,
|
||||
ind,
|
||||
ret);
|
||||
else {
|
||||
ret = input_open_device(usfc_handle);
|
||||
if (ret) {
|
||||
pr_err("%s: input_open_device[%d] failed: ret=%d\n",
|
||||
__func__,
|
||||
ind,
|
||||
ret);
|
||||
input_unregister_handle(usfc_handle);
|
||||
} else
|
||||
pr_debug("%s: device[%d] is opened\n",
|
||||
__func__,
|
||||
ind);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usfcdev_disconnect(struct input_handle *handle)
|
||||
{
|
||||
int ind = handle->handler->minor;
|
||||
|
||||
input_close_device(handle);
|
||||
input_unregister_handle(handle);
|
||||
pr_debug("%s: handle[%d], name=[%s] is disconnected\n",
|
||||
__func__,
|
||||
ind,
|
||||
handle->dev->name);
|
||||
if (s_usfc_primary_handles[ind].dev == handle->dev)
|
||||
s_usfc_primary_handles[ind].dev = NULL;
|
||||
else
|
||||
kfree(handle);
|
||||
}
|
||||
|
||||
static bool usfcdev_filter(struct input_handle *handle,
|
||||
unsigned int type, unsigned int code, int value)
|
||||
{
|
||||
uint16_t i = 0;
|
||||
uint16_t ind = (uint16_t)handle->handler->minor;
|
||||
bool rc = (s_usfcdev_events[ind].event_status != USFCDEV_EVENT_ENABLED);
|
||||
|
||||
if (s_usf_pid == sys_getpid()) {
|
||||
/* Pass events from usfcdev driver */
|
||||
rc = false;
|
||||
pr_debug("%s: event_type=%d; type=%d; code=%d; val=%d",
|
||||
__func__,
|
||||
ind,
|
||||
type,
|
||||
code,
|
||||
value);
|
||||
} else if (s_usfcdev_events[ind].event_status ==
|
||||
USFCDEV_EVENT_DISABLING) {
|
||||
uint32_t u_value = value;
|
||||
|
||||
s_usfcdev_events[ind].interleaved = true;
|
||||
/* Pass events for freeing slots from TSC driver */
|
||||
for (i = 0; i < ARRAY_SIZE(no_filter_cmds); ++i) {
|
||||
if ((no_filter_cmds[i].type == type) &&
|
||||
(no_filter_cmds[i].code == code) &&
|
||||
(no_filter_cmds[i].value <= u_value)) {
|
||||
rc = false;
|
||||
pr_debug("%s: no_filter_cmds[%d]; %d",
|
||||
__func__,
|
||||
i,
|
||||
no_filter_cmds[i].value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool usfcdev_register(
|
||||
uint16_t event_type_ind,
|
||||
bool (*match_cb)(uint16_t, struct input_dev *dev))
|
||||
{
|
||||
int ret = 0;
|
||||
bool rc = false;
|
||||
|
||||
if ((event_type_ind >= MAX_EVENT_TYPE_NUM) || !match_cb) {
|
||||
pr_err("%s: wrong input: event_type_ind=%d; match_cb=0x%pK\n",
|
||||
__func__,
|
||||
event_type_ind,
|
||||
match_cb);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s_usfcdev_events[event_type_ind].registered_event) {
|
||||
pr_info("%s: handler[%d] was already registered\n",
|
||||
__func__,
|
||||
event_type_ind);
|
||||
return true;
|
||||
}
|
||||
|
||||
s_usfcdev_events[event_type_ind].registered_event = true;
|
||||
s_usfcdev_events[event_type_ind].match_cb = match_cb;
|
||||
s_usfcdev_events[event_type_ind].event_status = USFCDEV_EVENT_ENABLED;
|
||||
ret = input_register_handler(&s_usfc_handlers[event_type_ind]);
|
||||
if (!ret) {
|
||||
rc = true;
|
||||
pr_debug("%s: handler[%d] was registered\n",
|
||||
__func__,
|
||||
event_type_ind);
|
||||
} else {
|
||||
s_usfcdev_events[event_type_ind].registered_event = false;
|
||||
s_usfcdev_events[event_type_ind].match_cb = NULL;
|
||||
pr_err("%s: handler[%d] registration failed: ret=%d\n",
|
||||
__func__,
|
||||
event_type_ind,
|
||||
ret);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void usfcdev_unregister(uint16_t event_type_ind)
|
||||
{
|
||||
if (event_type_ind >= MAX_EVENT_TYPE_NUM) {
|
||||
pr_err("%s: wrong input: event_type_ind=%d\n",
|
||||
__func__,
|
||||
event_type_ind);
|
||||
return;
|
||||
}
|
||||
if (s_usfcdev_events[event_type_ind].registered_event) {
|
||||
input_unregister_handler(&s_usfc_handlers[event_type_ind]);
|
||||
pr_debug("%s: handler[%d] was unregistered\n",
|
||||
__func__,
|
||||
event_type_ind);
|
||||
s_usfcdev_events[event_type_ind].registered_event = false;
|
||||
s_usfcdev_events[event_type_ind].match_cb = NULL;
|
||||
s_usfcdev_events[event_type_ind].event_status =
|
||||
USFCDEV_EVENT_ENABLED;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static inline void usfcdev_send_cmd(
|
||||
struct input_dev *dev,
|
||||
struct usfcdev_input_command cmd)
|
||||
{
|
||||
input_event(dev, cmd.type, cmd.code, cmd.value);
|
||||
}
|
||||
|
||||
static void usfcdev_clean_dev(uint16_t event_type_ind)
|
||||
{
|
||||
struct input_dev *dev = NULL;
|
||||
int i;
|
||||
int j;
|
||||
int retries = 0;
|
||||
|
||||
if (event_type_ind >= MAX_EVENT_TYPE_NUM) {
|
||||
pr_err("%s: wrong input: event_type_ind=%d\n",
|
||||
__func__,
|
||||
event_type_ind);
|
||||
return;
|
||||
}
|
||||
/* Only primary device must exist */
|
||||
dev = s_usfc_primary_handles[event_type_ind].dev;
|
||||
if (dev == NULL) {
|
||||
pr_err("%s: NULL primary device\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(initial_clear_cmds); i++)
|
||||
usfcdev_send_cmd(dev, initial_clear_cmds[i]);
|
||||
input_sync(dev);
|
||||
|
||||
/* Send commands to free all slots */
|
||||
for (i = 0; i < dev->mt->num_slots; i++) {
|
||||
s_usfcdev_events[event_type_ind].interleaved = false;
|
||||
if (input_mt_get_value(&dev->mt->slots[i],
|
||||
ABS_MT_TRACKING_ID) < 0) {
|
||||
pr_debug("%s: skipping slot %d",
|
||||
__func__, i);
|
||||
continue;
|
||||
}
|
||||
slot_clear_cmds[SLOT_CMD_ID].value = i;
|
||||
for (j = 0; j < ARRAY_SIZE(slot_clear_cmds); j++)
|
||||
usfcdev_send_cmd(dev, slot_clear_cmds[j]);
|
||||
|
||||
if (s_usfcdev_events[event_type_ind].interleaved) {
|
||||
pr_debug("%s: interleaved(%d): slot(%d)",
|
||||
__func__, i, dev->mt->slot);
|
||||
if (retries++ < MAX_RETRIES) {
|
||||
--i;
|
||||
continue;
|
||||
}
|
||||
pr_warn("%s: index(%d) reached max retires",
|
||||
__func__, i);
|
||||
}
|
||||
|
||||
retries = 0;
|
||||
input_sync(dev);
|
||||
}
|
||||
}
|
||||
|
||||
bool usfcdev_set_filter(uint16_t event_type_ind, bool filter)
|
||||
{
|
||||
bool rc = true;
|
||||
|
||||
if (event_type_ind >= MAX_EVENT_TYPE_NUM) {
|
||||
pr_err("%s: wrong input: event_type_ind=%d\n",
|
||||
__func__,
|
||||
event_type_ind);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s_usfcdev_events[event_type_ind].registered_event) {
|
||||
|
||||
pr_debug("%s: event_type[%d]; filter=%d\n",
|
||||
__func__,
|
||||
event_type_ind,
|
||||
filter
|
||||
);
|
||||
if (filter) {
|
||||
s_usfcdev_events[event_type_ind].event_status =
|
||||
USFCDEV_EVENT_DISABLING;
|
||||
s_usf_pid = sys_getpid();
|
||||
usfcdev_clean_dev(event_type_ind);
|
||||
s_usfcdev_events[event_type_ind].event_status =
|
||||
USFCDEV_EVENT_DISABLED;
|
||||
} else
|
||||
s_usfcdev_events[event_type_ind].event_status =
|
||||
USFCDEV_EVENT_ENABLED;
|
||||
} else {
|
||||
pr_err("%s: event_type[%d] isn't registered\n",
|
||||
__func__,
|
||||
event_type_ind);
|
||||
rc = false;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __init usfcdev_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
device_initcall(usfcdev_init);
|
||||
|
||||
MODULE_DESCRIPTION("Handle of events from devices, conflicting with USF");
|
||||
28
drivers/misc/qcom/qdsp6v2/ultrasound/usfcdev.h
Normal file
28
drivers/misc/qcom/qdsp6v2/ultrasound/usfcdev.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/* Copyright (c) 2012, 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 __USFCDEV_H__
|
||||
#define __USFCDEV_H__
|
||||
|
||||
#include <linux/input.h>
|
||||
|
||||
/* TSC event type index in the containers of the handlers & handles */
|
||||
#define TSC_EVENT_TYPE_IND 0
|
||||
/* Number of supported event types to be filtered */
|
||||
#define MAX_EVENT_TYPE_NUM 1
|
||||
|
||||
bool usfcdev_register(
|
||||
uint16_t event_type_ind,
|
||||
bool (*match_cb)(uint16_t, struct input_dev *dev));
|
||||
void usfcdev_unregister(uint16_t event_type_ind);
|
||||
bool usfcdev_set_filter(uint16_t event_type_ind, bool filter);
|
||||
#endif /* __USFCDEV_H__ */
|
||||
2
drivers/pinctrl/Makefile
Normal file
2
drivers/pinctrl/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
obj-y += qcom/
|
||||
1
drivers/pinctrl/core.h
Symbolic link
1
drivers/pinctrl/core.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../drivers/pinctrl/core.h
|
||||
1
drivers/pinctrl/pinctrl-utils.h
Symbolic link
1
drivers/pinctrl/pinctrl-utils.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../drivers/pinctrl/pinctrl-utils.h
|
||||
3
drivers/pinctrl/qcom/Makefile
Normal file
3
drivers/pinctrl/qcom/Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
obj-$(CONFIG_PINCTRL_WCD) += pinctrl-wcd.o
|
||||
obj-$(CONFIG_PINCTRL_LPI) += pinctrl-lpi.o
|
||||
647
drivers/pinctrl/qcom/pinctrl-lpi.c
Normal file
647
drivers/pinctrl/qcom/pinctrl-lpi.c
Normal file
@@ -0,0 +1,647 @@
|
||||
/*
|
||||
* 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/gpio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pinctrl/pinconf-generic.h>
|
||||
#include <linux/pinctrl/pinconf.h>
|
||||
#include <linux/pinctrl/pinmux.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/qdsp6v2/audio_notifier.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "../core.h"
|
||||
#include "../pinctrl-utils.h"
|
||||
|
||||
#define LPI_ADDRESS_SIZE 0xC000
|
||||
|
||||
#define LPI_GPIO_REG_VAL_CTL 0x00
|
||||
#define LPI_GPIO_REG_DIR_CTL 0x04
|
||||
|
||||
#define LPI_GPIO_REG_PULL_SHIFT 0x0
|
||||
#define LPI_GPIO_REG_PULL_MASK 0x3
|
||||
|
||||
#define LPI_GPIO_REG_FUNCTION_SHIFT 0x2
|
||||
#define LPI_GPIO_REG_FUNCTION_MASK 0x3C
|
||||
|
||||
#define LPI_GPIO_REG_OUT_STRENGTH_SHIFT 0x6
|
||||
#define LPI_GPIO_REG_OUT_STRENGTH_MASK 0x1C0
|
||||
|
||||
#define LPI_GPIO_REG_OE_SHIFT 0x9
|
||||
#define LPI_GPIO_REG_OE_MASK 0x200
|
||||
|
||||
#define LPI_GPIO_REG_DIR_SHIFT 0x1
|
||||
#define LPI_GPIO_REG_DIR_MASK 0x2
|
||||
|
||||
#define LPI_GPIO_BIAS_DISABLE 0x0
|
||||
#define LPI_GPIO_PULL_DOWN 0x1
|
||||
#define LPI_GPIO_KEEPER 0x2
|
||||
#define LPI_GPIO_PULL_UP 0x3
|
||||
|
||||
#define LPI_GPIO_FUNC_GPIO "gpio"
|
||||
#define LPI_GPIO_FUNC_FUNC1 "func1"
|
||||
#define LPI_GPIO_FUNC_FUNC2 "func2"
|
||||
#define LPI_GPIO_FUNC_FUNC3 "func3"
|
||||
#define LPI_GPIO_FUNC_FUNC4 "func4"
|
||||
#define LPI_GPIO_FUNC_FUNC5 "func5"
|
||||
|
||||
static bool lpi_dev_up;
|
||||
|
||||
/* The index of each function in lpi_gpio_functions[] array */
|
||||
enum lpi_gpio_func_index {
|
||||
LPI_GPIO_FUNC_INDEX_GPIO = 0x00,
|
||||
LPI_GPIO_FUNC_INDEX_FUNC1 = 0x01,
|
||||
LPI_GPIO_FUNC_INDEX_FUNC2 = 0x02,
|
||||
LPI_GPIO_FUNC_INDEX_FUNC3 = 0x03,
|
||||
LPI_GPIO_FUNC_INDEX_FUNC4 = 0x04,
|
||||
LPI_GPIO_FUNC_INDEX_FUNC5 = 0x05,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct lpi_gpio_pad - keep current GPIO settings
|
||||
* @offset: Nth GPIO in supported GPIOs.
|
||||
* @output_enabled: Set to true if GPIO output logic is enabled.
|
||||
* @value: value of a pin
|
||||
* @base: Address base of LPI GPIO PAD.
|
||||
* @pullup: Constant current which flow through GPIO output buffer.
|
||||
* @strength: No, Low, Medium, High
|
||||
* @function: See lpi_gpio_functions[]
|
||||
*/
|
||||
struct lpi_gpio_pad {
|
||||
u16 offset;
|
||||
bool output_enabled;
|
||||
bool value;
|
||||
char __iomem *base;
|
||||
unsigned int pullup;
|
||||
unsigned int strength;
|
||||
unsigned int function;
|
||||
};
|
||||
|
||||
struct lpi_gpio_state {
|
||||
struct device *dev;
|
||||
struct pinctrl_dev *ctrl;
|
||||
struct gpio_chip chip;
|
||||
char __iomem *base;
|
||||
};
|
||||
|
||||
static const char *const lpi_gpio_groups[] = {
|
||||
"gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7",
|
||||
"gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14",
|
||||
"gpio15", "gpio16", "gpio17", "gpio18", "gpio19", "gpio20", "gpio21",
|
||||
"gpio22", "gpio23", "gpio24", "gpio25", "gpio26", "gpio27", "gpio28",
|
||||
"gpio29", "gpio30", "gpio31",
|
||||
};
|
||||
|
||||
static const u32 lpi_offset[] = {
|
||||
0x00000000,
|
||||
0x00001000,
|
||||
0x00002000,
|
||||
0x00002010,
|
||||
0x00003000,
|
||||
0x00003010,
|
||||
0x00004000,
|
||||
0x00004010,
|
||||
0x00005000,
|
||||
0x00005010,
|
||||
0x00005020,
|
||||
0x00005030,
|
||||
0x00006000,
|
||||
0x00006010,
|
||||
0x00007000,
|
||||
0x00007010,
|
||||
0x00005040,
|
||||
0x00005050,
|
||||
0x00008000,
|
||||
0x00008010,
|
||||
0x00008020,
|
||||
0x00008030,
|
||||
0x00008040,
|
||||
0x00008050,
|
||||
0x00008060,
|
||||
0x00008070,
|
||||
0x00009000,
|
||||
0x00009010,
|
||||
0x0000A000,
|
||||
0x0000A010,
|
||||
0x0000B000,
|
||||
0x0000B010,
|
||||
};
|
||||
|
||||
static const char *const lpi_gpio_functions[] = {
|
||||
[LPI_GPIO_FUNC_INDEX_GPIO] = LPI_GPIO_FUNC_GPIO,
|
||||
[LPI_GPIO_FUNC_INDEX_FUNC1] = LPI_GPIO_FUNC_FUNC1,
|
||||
[LPI_GPIO_FUNC_INDEX_FUNC2] = LPI_GPIO_FUNC_FUNC2,
|
||||
[LPI_GPIO_FUNC_INDEX_FUNC3] = LPI_GPIO_FUNC_FUNC3,
|
||||
[LPI_GPIO_FUNC_INDEX_FUNC4] = LPI_GPIO_FUNC_FUNC4,
|
||||
[LPI_GPIO_FUNC_INDEX_FUNC5] = LPI_GPIO_FUNC_FUNC5,
|
||||
};
|
||||
|
||||
static int lpi_gpio_read(struct lpi_gpio_pad *pad, unsigned int addr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!lpi_dev_up) {
|
||||
pr_err_ratelimited("%s: ADSP is down due to SSR, return\n",
|
||||
__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = ioread32(pad->base + pad->offset + addr);
|
||||
if (ret < 0)
|
||||
pr_err("%s: read 0x%x failed\n", __func__, addr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpi_gpio_write(struct lpi_gpio_pad *pad, unsigned int addr,
|
||||
unsigned int val)
|
||||
{
|
||||
if (!lpi_dev_up) {
|
||||
pr_err_ratelimited("%s: ADSP is down due to SSR, return\n",
|
||||
__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
iowrite32(val, pad->base + pad->offset + addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpi_gpio_get_groups_count(struct pinctrl_dev *pctldev)
|
||||
{
|
||||
/* Every PIN is a group */
|
||||
return pctldev->desc->npins;
|
||||
}
|
||||
|
||||
static const char *lpi_gpio_get_group_name(struct pinctrl_dev *pctldev,
|
||||
unsigned int pin)
|
||||
{
|
||||
return pctldev->desc->pins[pin].name;
|
||||
}
|
||||
|
||||
static int lpi_gpio_get_group_pins(struct pinctrl_dev *pctldev,
|
||||
unsigned int pin,
|
||||
const unsigned int **pins,
|
||||
unsigned int *num_pins)
|
||||
{
|
||||
*pins = &pctldev->desc->pins[pin].number;
|
||||
*num_pins = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pinctrl_ops lpi_gpio_pinctrl_ops = {
|
||||
.get_groups_count = lpi_gpio_get_groups_count,
|
||||
.get_group_name = lpi_gpio_get_group_name,
|
||||
.get_group_pins = lpi_gpio_get_group_pins,
|
||||
.dt_node_to_map = pinconf_generic_dt_node_to_map_group,
|
||||
.dt_free_map = pinctrl_utils_free_map,
|
||||
};
|
||||
|
||||
static int lpi_gpio_get_functions_count(struct pinctrl_dev *pctldev)
|
||||
{
|
||||
return ARRAY_SIZE(lpi_gpio_functions);
|
||||
}
|
||||
|
||||
static const char *lpi_gpio_get_function_name(struct pinctrl_dev *pctldev,
|
||||
unsigned int function)
|
||||
{
|
||||
return lpi_gpio_functions[function];
|
||||
}
|
||||
|
||||
static int lpi_gpio_get_function_groups(struct pinctrl_dev *pctldev,
|
||||
unsigned int function,
|
||||
const char *const **groups,
|
||||
unsigned *const num_qgroups)
|
||||
{
|
||||
*groups = lpi_gpio_groups;
|
||||
*num_qgroups = pctldev->desc->npins;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpi_gpio_set_mux(struct pinctrl_dev *pctldev, unsigned int function,
|
||||
unsigned int pin)
|
||||
{
|
||||
struct lpi_gpio_pad *pad;
|
||||
unsigned int val;
|
||||
|
||||
pad = pctldev->desc->pins[pin].drv_data;
|
||||
|
||||
pad->function = function;
|
||||
|
||||
val = lpi_gpio_read(pad, LPI_GPIO_REG_VAL_CTL);
|
||||
val &= ~(LPI_GPIO_REG_FUNCTION_MASK);
|
||||
val |= pad->function << LPI_GPIO_REG_FUNCTION_SHIFT;
|
||||
lpi_gpio_write(pad, LPI_GPIO_REG_VAL_CTL, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pinmux_ops lpi_gpio_pinmux_ops = {
|
||||
.get_functions_count = lpi_gpio_get_functions_count,
|
||||
.get_function_name = lpi_gpio_get_function_name,
|
||||
.get_function_groups = lpi_gpio_get_function_groups,
|
||||
.set_mux = lpi_gpio_set_mux,
|
||||
};
|
||||
|
||||
static int lpi_config_get(struct pinctrl_dev *pctldev,
|
||||
unsigned int pin, unsigned long *config)
|
||||
{
|
||||
unsigned int param = pinconf_to_config_param(*config);
|
||||
struct lpi_gpio_pad *pad;
|
||||
unsigned int arg;
|
||||
|
||||
pad = pctldev->desc->pins[pin].drv_data;
|
||||
|
||||
switch (param) {
|
||||
case PIN_CONFIG_BIAS_DISABLE:
|
||||
arg = pad->pullup = LPI_GPIO_BIAS_DISABLE;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_PULL_DOWN:
|
||||
arg = pad->pullup == LPI_GPIO_PULL_DOWN;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_BUS_HOLD:
|
||||
arg = pad->pullup = LPI_GPIO_KEEPER;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_PULL_UP:
|
||||
arg = pad->pullup == LPI_GPIO_PULL_UP;
|
||||
break;
|
||||
case PIN_CONFIG_INPUT_ENABLE:
|
||||
case PIN_CONFIG_OUTPUT:
|
||||
arg = pad->output_enabled;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*config = pinconf_to_config_packed(param, arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int lpi_drive_to_regval(u32 arg)
|
||||
{
|
||||
return (arg/2 - 1);
|
||||
}
|
||||
|
||||
static int lpi_config_set(struct pinctrl_dev *pctldev, unsigned int pin,
|
||||
unsigned long *configs, unsigned int nconfs)
|
||||
{
|
||||
struct lpi_gpio_pad *pad;
|
||||
unsigned int param, arg;
|
||||
int i, ret = 0, val;
|
||||
|
||||
pad = pctldev->desc->pins[pin].drv_data;
|
||||
|
||||
for (i = 0; i < nconfs; i++) {
|
||||
param = pinconf_to_config_param(configs[i]);
|
||||
arg = pinconf_to_config_argument(configs[i]);
|
||||
|
||||
dev_dbg(pctldev->dev, "%s: param: %d arg: %d pin: %d\n",
|
||||
__func__, param, arg, pin);
|
||||
|
||||
switch (param) {
|
||||
case PIN_CONFIG_BIAS_DISABLE:
|
||||
pad->pullup = LPI_GPIO_BIAS_DISABLE;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_PULL_DOWN:
|
||||
pad->pullup = LPI_GPIO_PULL_DOWN;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_BUS_HOLD:
|
||||
pad->pullup = LPI_GPIO_KEEPER;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_PULL_UP:
|
||||
pad->pullup = LPI_GPIO_PULL_UP;
|
||||
break;
|
||||
case PIN_CONFIG_INPUT_ENABLE:
|
||||
pad->output_enabled = false;
|
||||
break;
|
||||
case PIN_CONFIG_OUTPUT:
|
||||
pad->output_enabled = true;
|
||||
pad->value = arg;
|
||||
break;
|
||||
case PIN_CONFIG_DRIVE_STRENGTH:
|
||||
pad->strength = arg;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
val = lpi_gpio_read(pad, LPI_GPIO_REG_VAL_CTL);
|
||||
val &= ~(LPI_GPIO_REG_PULL_MASK | LPI_GPIO_REG_OUT_STRENGTH_MASK |
|
||||
LPI_GPIO_REG_OE_MASK);
|
||||
val |= pad->pullup << LPI_GPIO_REG_PULL_SHIFT;
|
||||
val |= lpi_drive_to_regval(pad->strength) <<
|
||||
LPI_GPIO_REG_OUT_STRENGTH_SHIFT;
|
||||
if (pad->output_enabled)
|
||||
val |= pad->value << LPI_GPIO_REG_OE_SHIFT;
|
||||
|
||||
lpi_gpio_write(pad, LPI_GPIO_REG_VAL_CTL, val);
|
||||
lpi_gpio_write(pad, LPI_GPIO_REG_DIR_CTL,
|
||||
pad->output_enabled << LPI_GPIO_REG_DIR_SHIFT);
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct pinconf_ops lpi_gpio_pinconf_ops = {
|
||||
.is_generic = true,
|
||||
.pin_config_group_get = lpi_config_get,
|
||||
.pin_config_group_set = lpi_config_set,
|
||||
};
|
||||
|
||||
static int lpi_gpio_direction_input(struct gpio_chip *chip, unsigned int pin)
|
||||
{
|
||||
struct lpi_gpio_state *state = gpiochip_get_data(chip);
|
||||
unsigned long config;
|
||||
|
||||
config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1);
|
||||
|
||||
return lpi_config_set(state->ctrl, pin, &config, 1);
|
||||
}
|
||||
|
||||
static int lpi_gpio_direction_output(struct gpio_chip *chip,
|
||||
unsigned int pin, int val)
|
||||
{
|
||||
struct lpi_gpio_state *state = gpiochip_get_data(chip);
|
||||
unsigned long config;
|
||||
|
||||
config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val);
|
||||
|
||||
return lpi_config_set(state->ctrl, pin, &config, 1);
|
||||
}
|
||||
|
||||
static int lpi_gpio_get(struct gpio_chip *chip, unsigned int pin)
|
||||
{
|
||||
struct lpi_gpio_state *state = gpiochip_get_data(chip);
|
||||
struct lpi_gpio_pad *pad;
|
||||
int value;
|
||||
|
||||
pad = state->ctrl->desc->pins[pin].drv_data;
|
||||
|
||||
value = lpi_gpio_read(pad, LPI_GPIO_REG_VAL_CTL);
|
||||
return value;
|
||||
}
|
||||
|
||||
static void lpi_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
|
||||
{
|
||||
struct lpi_gpio_state *state = gpiochip_get_data(chip);
|
||||
unsigned long config;
|
||||
|
||||
config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value);
|
||||
|
||||
lpi_config_set(state->ctrl, pin, &config, 1);
|
||||
}
|
||||
|
||||
static int lpi_notifier_service_cb(struct notifier_block *this,
|
||||
unsigned long opcode, void *ptr)
|
||||
{
|
||||
static bool initial_boot = true;
|
||||
|
||||
pr_debug("%s: Service opcode 0x%lx\n", __func__, opcode);
|
||||
|
||||
switch (opcode) {
|
||||
case AUDIO_NOTIFIER_SERVICE_DOWN:
|
||||
if (initial_boot) {
|
||||
initial_boot = false;
|
||||
break;
|
||||
}
|
||||
lpi_dev_up = false;
|
||||
break;
|
||||
case AUDIO_NOTIFIER_SERVICE_UP:
|
||||
if (initial_boot)
|
||||
initial_boot = false;
|
||||
lpi_dev_up = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block service_nb = {
|
||||
.notifier_call = lpi_notifier_service_cb,
|
||||
.priority = -INT_MAX,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
static unsigned int lpi_regval_to_drive(u32 val)
|
||||
{
|
||||
return (val + 1) * 2;
|
||||
}
|
||||
|
||||
static void lpi_gpio_dbg_show_one(struct seq_file *s,
|
||||
struct pinctrl_dev *pctldev,
|
||||
struct gpio_chip *chip,
|
||||
unsigned int offset,
|
||||
unsigned int gpio)
|
||||
{
|
||||
struct pinctrl_pin_desc pindesc;
|
||||
struct lpi_gpio_pad *pad;
|
||||
unsigned int func;
|
||||
int is_out;
|
||||
int drive;
|
||||
int pull;
|
||||
u32 ctl_reg;
|
||||
|
||||
static const char * const pulls[] = {
|
||||
"no pull",
|
||||
"pull down",
|
||||
"keeper",
|
||||
"pull up"
|
||||
};
|
||||
|
||||
pctldev = pctldev ? : to_gpio_state(chip)->ctrl;
|
||||
pindesc = pctldev->desc->pins[offset];
|
||||
pad = pctldev->desc->pins[offset].drv_data;
|
||||
ctl_reg = lpi_gpio_read(pad, LPI_GPIO_REG_DIR_CTL);
|
||||
is_out = (ctl_reg & LPI_GPIO_REG_DIR_MASK) >> LPI_GPIO_REG_DIR_SHIFT;
|
||||
ctl_reg = lpi_gpio_read(pad, LPI_GPIO_REG_VAL_CTL);
|
||||
|
||||
func = (ctl_reg & LPI_GPIO_REG_FUNCTION_MASK) >>
|
||||
LPI_GPIO_REG_FUNCTION_SHIFT;
|
||||
drive = (ctl_reg & LPI_GPIO_REG_OUT_STRENGTH_MASK) >>
|
||||
LPI_GPIO_REG_OUT_STRENGTH_SHIFT;
|
||||
pull = (ctl_reg & LPI_GPIO_REG_PULL_MASK) >> LPI_GPIO_REG_PULL_SHIFT;
|
||||
|
||||
seq_printf(s, " %-8s: %-3s %d",
|
||||
pindesc.name, is_out ? "out" : "in", func);
|
||||
seq_printf(s, " %dmA", lpi_regval_to_drive(drive));
|
||||
seq_printf(s, " %s", pulls[pull]);
|
||||
}
|
||||
|
||||
static void lpi_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
|
||||
{
|
||||
unsigned int gpio = chip->base;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < chip->ngpio; i++, gpio++) {
|
||||
lpi_gpio_dbg_show_one(s, NULL, chip, i, gpio);
|
||||
seq_puts(s, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
#define lpi_gpio_dbg_show NULL
|
||||
#endif
|
||||
|
||||
static const struct gpio_chip lpi_gpio_template = {
|
||||
.direction_input = lpi_gpio_direction_input,
|
||||
.direction_output = lpi_gpio_direction_output,
|
||||
.get = lpi_gpio_get,
|
||||
.set = lpi_gpio_set,
|
||||
.request = gpiochip_generic_request,
|
||||
.free = gpiochip_generic_free,
|
||||
.dbg_show = lpi_gpio_dbg_show,
|
||||
};
|
||||
|
||||
static int lpi_pinctrl_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct pinctrl_pin_desc *pindesc;
|
||||
struct pinctrl_desc *pctrldesc;
|
||||
struct lpi_gpio_pad *pad, *pads;
|
||||
struct lpi_gpio_state *state;
|
||||
int ret, npins, i;
|
||||
char __iomem *lpi_base;
|
||||
u32 reg;
|
||||
|
||||
ret = of_property_read_u32(dev->of_node, "reg", ®);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "missing base address\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(dev->of_node, "qcom,num-gpios", &npins);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
WARN_ON(npins > ARRAY_SIZE(lpi_gpio_groups));
|
||||
|
||||
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, state);
|
||||
|
||||
state->dev = &pdev->dev;
|
||||
|
||||
pindesc = devm_kcalloc(dev, npins, sizeof(*pindesc), GFP_KERNEL);
|
||||
if (!pindesc)
|
||||
return -ENOMEM;
|
||||
|
||||
pads = devm_kcalloc(dev, npins, sizeof(*pads), GFP_KERNEL);
|
||||
if (!pads)
|
||||
return -ENOMEM;
|
||||
|
||||
pctrldesc = devm_kzalloc(dev, sizeof(*pctrldesc), GFP_KERNEL);
|
||||
if (!pctrldesc)
|
||||
return -ENOMEM;
|
||||
|
||||
pctrldesc->pctlops = &lpi_gpio_pinctrl_ops;
|
||||
pctrldesc->pmxops = &lpi_gpio_pinmux_ops;
|
||||
pctrldesc->confops = &lpi_gpio_pinconf_ops;
|
||||
pctrldesc->owner = THIS_MODULE;
|
||||
pctrldesc->name = dev_name(dev);
|
||||
pctrldesc->pins = pindesc;
|
||||
pctrldesc->npins = npins;
|
||||
|
||||
lpi_base = devm_ioremap(dev, reg, LPI_ADDRESS_SIZE);
|
||||
if (lpi_base == NULL) {
|
||||
dev_err(dev, "%s devm_ioremap failed\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
state->base = lpi_base;
|
||||
|
||||
for (i = 0; i < npins; i++, pindesc++) {
|
||||
pad = &pads[i];
|
||||
pindesc->drv_data = pad;
|
||||
pindesc->number = i;
|
||||
pindesc->name = lpi_gpio_groups[i];
|
||||
|
||||
pad->base = lpi_base;
|
||||
pad->offset = lpi_offset[i];
|
||||
}
|
||||
|
||||
state->chip = lpi_gpio_template;
|
||||
state->chip.parent = dev;
|
||||
state->chip.base = -1;
|
||||
state->chip.ngpio = npins;
|
||||
state->chip.label = dev_name(dev);
|
||||
state->chip.of_gpio_n_cells = 2;
|
||||
state->chip.can_sleep = false;
|
||||
|
||||
state->ctrl = devm_pinctrl_register(dev, pctrldesc, state);
|
||||
if (IS_ERR(state->ctrl))
|
||||
return PTR_ERR(state->ctrl);
|
||||
|
||||
ret = gpiochip_add_data(&state->chip, state);
|
||||
if (ret) {
|
||||
dev_err(state->dev, "can't add gpio chip\n");
|
||||
goto err_chip;
|
||||
}
|
||||
|
||||
ret = gpiochip_add_pin_range(&state->chip, dev_name(dev), 0, 0, npins);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to add pin range\n");
|
||||
goto err_range;
|
||||
}
|
||||
|
||||
lpi_dev_up = true;
|
||||
ret = audio_notifier_register("lpi_tlmm", AUDIO_NOTIFIER_ADSP_DOMAIN,
|
||||
&service_nb);
|
||||
if (ret < 0) {
|
||||
pr_err("%s: Audio notifier register failed ret = %d\n",
|
||||
__func__, ret);
|
||||
goto err_range;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_range:
|
||||
gpiochip_remove(&state->chip);
|
||||
err_chip:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpi_pinctrl_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct lpi_gpio_state *state = platform_get_drvdata(pdev);
|
||||
|
||||
gpiochip_remove(&state->chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id lpi_pinctrl_of_match[] = {
|
||||
{ .compatible = "qcom,lpi-pinctrl" }, /* Generic */
|
||||
{ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, lpi_pinctrl_of_match);
|
||||
|
||||
static struct platform_driver lpi_pinctrl_driver = {
|
||||
.driver = {
|
||||
.name = "qcom-lpi-pinctrl",
|
||||
.of_match_table = lpi_pinctrl_of_match,
|
||||
},
|
||||
.probe = lpi_pinctrl_probe,
|
||||
.remove = lpi_pinctrl_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(lpi_pinctrl_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QTI LPI GPIO pin control driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
435
drivers/pinctrl/qcom/pinctrl-wcd.c
Normal file
435
drivers/pinctrl/qcom/pinctrl-wcd.c
Normal file
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* 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/gpio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pinctrl/pinconf-generic.h>
|
||||
#include <linux/pinctrl/pinconf.h>
|
||||
#include <linux/pinctrl/pinmux.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/mfd/wcd934x/registers.h>
|
||||
|
||||
#include "../core.h"
|
||||
#include "../pinctrl-utils.h"
|
||||
|
||||
#define WCD_REG_DIR_CTL WCD934X_CHIP_TIER_CTRL_GPIO_CTL_OE
|
||||
#define WCD_REG_VAL_CTL WCD934X_CHIP_TIER_CTRL_GPIO_CTL_DATA
|
||||
#define WCD_GPIO_PULL_UP 1
|
||||
#define WCD_GPIO_PULL_DOWN 2
|
||||
#define WCD_GPIO_BIAS_DISABLE 3
|
||||
#define WCD_GPIO_STRING_LEN 20
|
||||
|
||||
/**
|
||||
* struct wcd_gpio_pad - keep current GPIO settings
|
||||
* @offset: offset of gpio.
|
||||
* @is_valid: Set to false, when GPIO in high Z state.
|
||||
* @value: value of a pin
|
||||
* @output_enabled: Set to true if GPIO is output and false if it is input
|
||||
* @pullup: Constant current which flow through GPIO output buffer.
|
||||
* @strength: Drive strength of a pin
|
||||
*/
|
||||
struct wcd_gpio_pad {
|
||||
u16 offset;
|
||||
bool is_valid;
|
||||
bool value;
|
||||
bool output_enabled;
|
||||
unsigned int pullup;
|
||||
unsigned int strength;
|
||||
};
|
||||
|
||||
struct wcd_gpio_priv {
|
||||
struct device *dev;
|
||||
struct regmap *map;
|
||||
struct pinctrl_dev *ctrl;
|
||||
struct gpio_chip chip;
|
||||
};
|
||||
|
||||
static int wcd_gpio_read(struct wcd_gpio_priv *priv_data,
|
||||
struct wcd_gpio_pad *pad, unsigned int addr)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(priv_data->map, addr, &val);
|
||||
if (ret < 0)
|
||||
dev_err(priv_data->dev, "%s: read 0x%x failed\n",
|
||||
__func__, addr);
|
||||
else
|
||||
ret = (val >> pad->offset);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wcd_gpio_write(struct wcd_gpio_priv *priv_data,
|
||||
struct wcd_gpio_pad *pad, unsigned int addr,
|
||||
unsigned int val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_update_bits(priv_data->map, addr, (1 << pad->offset),
|
||||
val << pad->offset);
|
||||
if (ret < 0)
|
||||
dev_err(priv_data->dev, "write 0x%x failed\n", addr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wcd_get_groups_count(struct pinctrl_dev *pctldev)
|
||||
{
|
||||
return pctldev->desc->npins;
|
||||
}
|
||||
|
||||
static const char *wcd_get_group_name(struct pinctrl_dev *pctldev,
|
||||
unsigned int pin)
|
||||
{
|
||||
return pctldev->desc->pins[pin].name;
|
||||
}
|
||||
|
||||
static int wcd_get_group_pins(struct pinctrl_dev *pctldev, unsigned int pin,
|
||||
const unsigned int **pins, unsigned int *num_pins)
|
||||
{
|
||||
*pins = &pctldev->desc->pins[pin].number;
|
||||
*num_pins = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pinctrl_ops wcd_pinctrl_ops = {
|
||||
.get_groups_count = wcd_get_groups_count,
|
||||
.get_group_name = wcd_get_group_name,
|
||||
.get_group_pins = wcd_get_group_pins,
|
||||
.dt_node_to_map = pinconf_generic_dt_node_to_map_group,
|
||||
.dt_free_map = pinctrl_utils_free_map,
|
||||
};
|
||||
|
||||
static int wcd_config_get(struct pinctrl_dev *pctldev,
|
||||
unsigned int pin, unsigned long *config)
|
||||
{
|
||||
unsigned int param = pinconf_to_config_param(*config);
|
||||
struct wcd_gpio_pad *pad;
|
||||
unsigned int arg;
|
||||
|
||||
pad = pctldev->desc->pins[pin].drv_data;
|
||||
|
||||
switch (param) {
|
||||
case PIN_CONFIG_BIAS_PULL_DOWN:
|
||||
arg = pad->pullup == WCD_GPIO_PULL_DOWN;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_DISABLE:
|
||||
arg = pad->pullup = WCD_GPIO_BIAS_DISABLE;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_PULL_UP:
|
||||
arg = pad->pullup == WCD_GPIO_PULL_UP;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
|
||||
arg = !pad->is_valid;
|
||||
break;
|
||||
case PIN_CONFIG_INPUT_ENABLE:
|
||||
arg = pad->output_enabled;
|
||||
break;
|
||||
case PIN_CONFIG_OUTPUT:
|
||||
arg = pad->value;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*config = pinconf_to_config_packed(param, arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wcd_config_set(struct pinctrl_dev *pctldev, unsigned int pin,
|
||||
unsigned long *configs, unsigned int nconfs)
|
||||
{
|
||||
struct wcd_gpio_priv *priv_data = pinctrl_dev_get_drvdata(pctldev);
|
||||
struct wcd_gpio_pad *pad;
|
||||
unsigned int param, arg;
|
||||
int i, ret;
|
||||
|
||||
pad = pctldev->desc->pins[pin].drv_data;
|
||||
|
||||
for (i = 0; i < nconfs; i++) {
|
||||
param = pinconf_to_config_param(configs[i]);
|
||||
arg = pinconf_to_config_argument(configs[i]);
|
||||
|
||||
dev_dbg(priv_data->dev, "%s: param: %d arg: %d",
|
||||
__func__, param, arg);
|
||||
|
||||
switch (param) {
|
||||
case PIN_CONFIG_BIAS_DISABLE:
|
||||
pad->pullup = WCD_GPIO_BIAS_DISABLE;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_PULL_UP:
|
||||
pad->pullup = WCD_GPIO_PULL_UP;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_PULL_DOWN:
|
||||
pad->pullup = WCD_GPIO_PULL_DOWN;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
|
||||
pad->is_valid = false;
|
||||
break;
|
||||
case PIN_CONFIG_INPUT_ENABLE:
|
||||
pad->output_enabled = false;
|
||||
break;
|
||||
case PIN_CONFIG_OUTPUT:
|
||||
pad->output_enabled = true;
|
||||
pad->value = arg;
|
||||
break;
|
||||
case PIN_CONFIG_DRIVE_STRENGTH:
|
||||
pad->strength = arg;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (pad->output_enabled) {
|
||||
ret = wcd_gpio_write(priv_data, pad, WCD_REG_DIR_CTL,
|
||||
pad->output_enabled);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
ret = wcd_gpio_write(priv_data, pad, WCD_REG_VAL_CTL,
|
||||
pad->value);
|
||||
} else
|
||||
ret = wcd_gpio_write(priv_data, pad, WCD_REG_DIR_CTL,
|
||||
pad->output_enabled);
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct pinconf_ops wcd_pinconf_ops = {
|
||||
.is_generic = true,
|
||||
.pin_config_group_get = wcd_config_get,
|
||||
.pin_config_group_set = wcd_config_set,
|
||||
};
|
||||
|
||||
static int wcd_gpio_direction_input(struct gpio_chip *chip, unsigned int pin)
|
||||
{
|
||||
struct wcd_gpio_priv *priv_data = gpiochip_get_data(chip);
|
||||
unsigned long config;
|
||||
|
||||
config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1);
|
||||
|
||||
return wcd_config_set(priv_data->ctrl, pin, &config, 1);
|
||||
}
|
||||
|
||||
static int wcd_gpio_direction_output(struct gpio_chip *chip,
|
||||
unsigned int pin, int val)
|
||||
{
|
||||
struct wcd_gpio_priv *priv_data = gpiochip_get_data(chip);
|
||||
unsigned long config;
|
||||
|
||||
config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val);
|
||||
|
||||
return wcd_config_set(priv_data->ctrl, pin, &config, 1);
|
||||
}
|
||||
|
||||
static int wcd_gpio_get(struct gpio_chip *chip, unsigned int pin)
|
||||
{
|
||||
struct wcd_gpio_priv *priv_data = gpiochip_get_data(chip);
|
||||
struct wcd_gpio_pad *pad;
|
||||
int value;
|
||||
|
||||
pad = priv_data->ctrl->desc->pins[pin].drv_data;
|
||||
|
||||
if (!pad->is_valid)
|
||||
return -EINVAL;
|
||||
|
||||
value = wcd_gpio_read(priv_data, pad, WCD_REG_VAL_CTL);
|
||||
return value;
|
||||
}
|
||||
|
||||
static void wcd_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
|
||||
{
|
||||
struct wcd_gpio_priv *priv_data = gpiochip_get_data(chip);
|
||||
unsigned long config;
|
||||
|
||||
config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value);
|
||||
|
||||
wcd_config_set(priv_data->ctrl, pin, &config, 1);
|
||||
}
|
||||
|
||||
static const struct gpio_chip wcd_gpio_chip = {
|
||||
.direction_input = wcd_gpio_direction_input,
|
||||
.direction_output = wcd_gpio_direction_output,
|
||||
.get = wcd_gpio_get,
|
||||
.set = wcd_gpio_set,
|
||||
};
|
||||
|
||||
static int wcd_pinctrl_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct pinctrl_pin_desc *pindesc;
|
||||
struct pinctrl_desc *pctrldesc;
|
||||
struct wcd_gpio_pad *pad, *pads;
|
||||
struct wcd_gpio_priv *priv_data;
|
||||
int ret, i, j;
|
||||
u32 npins;
|
||||
char **name;
|
||||
|
||||
ret = of_property_read_u32(dev->of_node, "qcom,num-gpios", &npins);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s: Looking up %s property in node %s failed\n",
|
||||
__func__, "qcom,num-gpios", dev->of_node->full_name);
|
||||
ret = -EINVAL;
|
||||
goto err_priv_alloc;
|
||||
}
|
||||
if (!npins) {
|
||||
dev_err(dev, "%s: no.of pins are 0\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto err_priv_alloc;
|
||||
}
|
||||
|
||||
priv_data = devm_kzalloc(dev, sizeof(*priv_data), GFP_KERNEL);
|
||||
if (!priv_data) {
|
||||
ret = -ENOMEM;
|
||||
goto err_priv_alloc;
|
||||
}
|
||||
|
||||
priv_data->dev = dev;
|
||||
priv_data->map = dev_get_regmap(dev->parent, NULL);
|
||||
if (!priv_data->map) {
|
||||
dev_err(dev, "%s: failed to get regmap\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto err_regmap;
|
||||
}
|
||||
|
||||
pindesc = devm_kcalloc(dev, npins, sizeof(*pindesc), GFP_KERNEL);
|
||||
if (!pindesc) {
|
||||
ret = -ENOMEM;
|
||||
goto err_pinsec_alloc;
|
||||
}
|
||||
|
||||
pads = devm_kcalloc(dev, npins, sizeof(*pads), GFP_KERNEL);
|
||||
if (!pads) {
|
||||
ret = -ENOMEM;
|
||||
goto err_pads_alloc;
|
||||
}
|
||||
|
||||
pctrldesc = devm_kzalloc(dev, sizeof(*pctrldesc), GFP_KERNEL);
|
||||
if (!pctrldesc) {
|
||||
ret = -ENOMEM;
|
||||
goto err_pinctrl_alloc;
|
||||
}
|
||||
|
||||
pctrldesc->pctlops = &wcd_pinctrl_ops;
|
||||
pctrldesc->confops = &wcd_pinconf_ops;
|
||||
pctrldesc->owner = THIS_MODULE;
|
||||
pctrldesc->name = dev_name(dev);
|
||||
pctrldesc->pins = pindesc;
|
||||
pctrldesc->npins = npins;
|
||||
|
||||
name = devm_kcalloc(dev, npins, sizeof(char *), GFP_KERNEL);
|
||||
if (!name) {
|
||||
ret = -ENOMEM;
|
||||
goto err_name_alloc;
|
||||
}
|
||||
for (i = 0; i < npins; i++, pindesc++) {
|
||||
name[i] = devm_kzalloc(dev, sizeof(char) * WCD_GPIO_STRING_LEN,
|
||||
GFP_KERNEL);
|
||||
if (!name[i]) {
|
||||
ret = -ENOMEM;
|
||||
goto err_pin;
|
||||
}
|
||||
pad = &pads[i];
|
||||
pindesc->drv_data = pad;
|
||||
pindesc->number = i;
|
||||
snprintf(name[i], (WCD_GPIO_STRING_LEN - 1), "gpio%d", (i+1));
|
||||
pindesc->name = name[i];
|
||||
pad->offset = i;
|
||||
pad->is_valid = true;
|
||||
}
|
||||
|
||||
priv_data->chip = wcd_gpio_chip;
|
||||
priv_data->chip.parent = dev;
|
||||
priv_data->chip.base = -1;
|
||||
priv_data->chip.ngpio = npins;
|
||||
priv_data->chip.label = dev_name(dev);
|
||||
priv_data->chip.of_gpio_n_cells = 2;
|
||||
priv_data->chip.can_sleep = false;
|
||||
|
||||
priv_data->ctrl = devm_pinctrl_register(dev, pctrldesc, priv_data);
|
||||
if (IS_ERR(priv_data->ctrl)) {
|
||||
dev_err(dev, "%s: failed to register to pinctrl\n", __func__);
|
||||
ret = PTR_ERR(priv_data->ctrl);
|
||||
goto err_pin;
|
||||
}
|
||||
|
||||
ret = gpiochip_add_data(&priv_data->chip, priv_data);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s: can't add gpio chip\n", __func__);
|
||||
goto err_pin;
|
||||
}
|
||||
|
||||
ret = gpiochip_add_pin_range(&priv_data->chip, dev_name(dev), 0, 0,
|
||||
npins);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s: failed to add pin range\n", __func__);
|
||||
goto err_range;
|
||||
}
|
||||
platform_set_drvdata(pdev, priv_data);
|
||||
|
||||
return 0;
|
||||
|
||||
err_range:
|
||||
gpiochip_remove(&priv_data->chip);
|
||||
err_pin:
|
||||
for (j = 0; j < i; j++)
|
||||
devm_kfree(dev, name[j]);
|
||||
devm_kfree(dev, name);
|
||||
err_name_alloc:
|
||||
devm_kfree(dev, pctrldesc);
|
||||
err_pinctrl_alloc:
|
||||
devm_kfree(dev, pads);
|
||||
err_pads_alloc:
|
||||
devm_kfree(dev, pindesc);
|
||||
err_pinsec_alloc:
|
||||
err_regmap:
|
||||
devm_kfree(dev, priv_data);
|
||||
err_priv_alloc:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wcd_pinctrl_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct wcd_gpio_priv *priv_data = platform_get_drvdata(pdev);
|
||||
|
||||
gpiochip_remove(&priv_data->chip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id wcd_pinctrl_of_match[] = {
|
||||
{ .compatible = "qcom,wcd-pinctrl" },
|
||||
{ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, wcd_pinctrl_of_match);
|
||||
|
||||
static struct platform_driver wcd_pinctrl_driver = {
|
||||
.driver = {
|
||||
.name = "qcom-wcd-pinctrl",
|
||||
.of_match_table = wcd_pinctrl_of_match,
|
||||
},
|
||||
.probe = wcd_pinctrl_probe,
|
||||
.remove = wcd_pinctrl_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(wcd_pinctrl_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Qualcomm Technologies, Inc WCD GPIO pin control driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
5
drivers/soc/Makefile
Normal file
5
drivers/soc/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# Makefile for the Linux Kernel SOC specific device drivers.
|
||||
#
|
||||
|
||||
obj-y += qcom/
|
||||
1
drivers/soc/qcom/Makefile
Normal file
1
drivers/soc/qcom/Makefile
Normal file
@@ -0,0 +1 @@
|
||||
obj-y += qdsp6v2/
|
||||
8
drivers/soc/qcom/qdsp6v2/Makefile
Normal file
8
drivers/soc/qcom/qdsp6v2/Makefile
Normal file
@@ -0,0 +1,8 @@
|
||||
obj-$(CONFIG_MSM_QDSP6_APRV2_GLINK) += apr.o apr_v2.o apr_tal_glink.o
|
||||
obj-$(CONFIG_MSM_QDSP6_APRV3_GLINK) += apr.o apr_v3.o apr_tal_glink.o
|
||||
obj-$(CONFIG_SND_SOC_MSM_QDSP6V2_INTF) += msm_audio_ion.o
|
||||
obj-$(CONFIG_MSM_ADSP_LOADER) += adsp-loader.o
|
||||
obj-$(CONFIG_MSM_QDSP6_SSR) += audio_ssr.o
|
||||
obj-$(CONFIG_MSM_QDSP6_PDR) += audio_pdr.o
|
||||
obj-$(CONFIG_MSM_QDSP6_NOTIFIER) += audio_notifier.o
|
||||
obj-$(CONFIG_MSM_CDSP_LOADER) += cdsp-loader.o
|
||||
316
drivers/soc/qcom/qdsp6v2/adsp-loader.c
Normal file
316
drivers/soc/qcom/qdsp6v2/adsp-loader.c
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2014, 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/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/qdsp6v2/apr.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <soc/qcom/subsystem_restart.h>
|
||||
|
||||
#define Q6_PIL_GET_DELAY_MS 100
|
||||
#define BOOT_CMD 1
|
||||
#define IMAGE_UNLOAD_CMD 0
|
||||
|
||||
static ssize_t adsp_boot_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
|
||||
struct adsp_loader_private {
|
||||
void *pil_h;
|
||||
struct kobject *boot_adsp_obj;
|
||||
struct attribute_group *attr_group;
|
||||
};
|
||||
|
||||
static struct kobj_attribute adsp_boot_attribute =
|
||||
__ATTR(boot, 0220, NULL, adsp_boot_store);
|
||||
|
||||
static struct attribute *attrs[] = {
|
||||
&adsp_boot_attribute.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct work_struct adsp_ldr_work;
|
||||
static struct platform_device *adsp_private;
|
||||
static void adsp_loader_unload(struct platform_device *pdev);
|
||||
|
||||
static void adsp_load_fw(struct work_struct *adsp_ldr_work)
|
||||
{
|
||||
struct platform_device *pdev = adsp_private;
|
||||
struct adsp_loader_private *priv = NULL;
|
||||
|
||||
const char *adsp_dt = "qcom,adsp-state";
|
||||
int rc = 0;
|
||||
u32 adsp_state;
|
||||
const char *img_name;
|
||||
|
||||
if (!pdev) {
|
||||
dev_err(&pdev->dev, "%s: Platform device null\n", __func__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!pdev->dev.of_node) {
|
||||
dev_err(&pdev->dev,
|
||||
"%s: Device tree information missing\n", __func__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(pdev->dev.of_node, adsp_dt, &adsp_state);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev,
|
||||
"%s: ADSP state = %x\n", __func__, adsp_state);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rc = of_property_read_string(pdev->dev.of_node,
|
||||
"qcom,proc-img-to-load",
|
||||
&img_name);
|
||||
|
||||
if (rc) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"%s: loading default image ADSP\n", __func__);
|
||||
goto load_adsp;
|
||||
}
|
||||
if (!strcmp(img_name, "modem")) {
|
||||
/* adsp_state always returns "0". So load modem image based on
|
||||
* apr_modem_state to prevent loading of image twice
|
||||
*/
|
||||
adsp_state = apr_get_modem_state();
|
||||
if (adsp_state == APR_SUBSYS_DOWN) {
|
||||
priv = platform_get_drvdata(pdev);
|
||||
if (!priv) {
|
||||
dev_err(&pdev->dev,
|
||||
" %s: Private data get failed\n", __func__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->pil_h = subsystem_get("modem");
|
||||
if (IS_ERR(priv->pil_h)) {
|
||||
dev_err(&pdev->dev, "%s: pil get failed,\n",
|
||||
__func__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Set the state of the ADSP in APR driver */
|
||||
apr_set_modem_state(APR_SUBSYS_LOADED);
|
||||
} else if (adsp_state == APR_SUBSYS_LOADED) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"%s: MDSP state = %x\n", __func__, adsp_state);
|
||||
}
|
||||
|
||||
dev_dbg(&pdev->dev, "%s: Q6/MDSP image is loaded\n", __func__);
|
||||
return;
|
||||
}
|
||||
load_adsp:
|
||||
{
|
||||
adsp_state = apr_get_q6_state();
|
||||
if (adsp_state == APR_SUBSYS_DOWN) {
|
||||
priv = platform_get_drvdata(pdev);
|
||||
if (!priv) {
|
||||
dev_err(&pdev->dev,
|
||||
" %s: Private data get failed\n", __func__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->pil_h = subsystem_get("adsp");
|
||||
if (IS_ERR(priv->pil_h)) {
|
||||
dev_err(&pdev->dev, "%s: pil get failed,\n",
|
||||
__func__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Set the state of the ADSP in APR driver */
|
||||
apr_set_q6_state(APR_SUBSYS_LOADED);
|
||||
} else if (adsp_state == APR_SUBSYS_LOADED) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"%s: ADSP state = %x\n", __func__, adsp_state);
|
||||
}
|
||||
|
||||
dev_dbg(&pdev->dev, "%s: Q6/ADSP image is loaded\n", __func__);
|
||||
return;
|
||||
}
|
||||
fail:
|
||||
dev_err(&pdev->dev, "%s: Q6 image loading failed\n", __func__);
|
||||
}
|
||||
|
||||
static void adsp_loader_do(struct platform_device *pdev)
|
||||
{
|
||||
schedule_work(&adsp_ldr_work);
|
||||
}
|
||||
|
||||
static ssize_t adsp_boot_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
int boot = 0;
|
||||
|
||||
if (sscanf(buf, "%du", &boot) != 1) {
|
||||
pr_err("%s: failed to read boot info from string\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (boot == BOOT_CMD) {
|
||||
pr_debug("%s: going to call adsp_loader_do\n", __func__);
|
||||
adsp_loader_do(adsp_private);
|
||||
} else if (boot == IMAGE_UNLOAD_CMD) {
|
||||
pr_debug("%s: going to call adsp_unloader\n", __func__);
|
||||
adsp_loader_unload(adsp_private);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void adsp_loader_unload(struct platform_device *pdev)
|
||||
{
|
||||
struct adsp_loader_private *priv = NULL;
|
||||
|
||||
priv = platform_get_drvdata(pdev);
|
||||
|
||||
if (!priv)
|
||||
return;
|
||||
|
||||
if (priv->pil_h) {
|
||||
dev_dbg(&pdev->dev, "%s: calling subsystem put\n", __func__);
|
||||
subsystem_put(priv->pil_h);
|
||||
priv->pil_h = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int adsp_loader_init_sysfs(struct platform_device *pdev)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
struct adsp_loader_private *priv = NULL;
|
||||
|
||||
adsp_private = NULL;
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
ret = -ENOMEM;
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
priv->pil_h = NULL;
|
||||
priv->boot_adsp_obj = NULL;
|
||||
priv->attr_group = devm_kzalloc(&pdev->dev,
|
||||
sizeof(*(priv->attr_group)),
|
||||
GFP_KERNEL);
|
||||
if (!priv->attr_group) {
|
||||
ret = -ENOMEM;
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
priv->attr_group->attrs = attrs;
|
||||
|
||||
priv->boot_adsp_obj = kobject_create_and_add("boot_adsp", kernel_kobj);
|
||||
if (!priv->boot_adsp_obj) {
|
||||
dev_err(&pdev->dev, "%s: sysfs create and add failed\n",
|
||||
__func__);
|
||||
ret = -ENOMEM;
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
ret = sysfs_create_group(priv->boot_adsp_obj, priv->attr_group);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s: sysfs create group failed %d\n",
|
||||
__func__, ret);
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
adsp_private = pdev;
|
||||
|
||||
return 0;
|
||||
|
||||
error_return:
|
||||
|
||||
if (priv->boot_adsp_obj) {
|
||||
kobject_del(priv->boot_adsp_obj);
|
||||
priv->boot_adsp_obj = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adsp_loader_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct adsp_loader_private *priv = NULL;
|
||||
|
||||
priv = platform_get_drvdata(pdev);
|
||||
|
||||
if (!priv)
|
||||
return 0;
|
||||
|
||||
if (priv->pil_h) {
|
||||
subsystem_put(priv->pil_h);
|
||||
priv->pil_h = NULL;
|
||||
}
|
||||
|
||||
if (priv->boot_adsp_obj) {
|
||||
sysfs_remove_group(priv->boot_adsp_obj, priv->attr_group);
|
||||
kobject_del(priv->boot_adsp_obj);
|
||||
priv->boot_adsp_obj = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adsp_loader_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = adsp_loader_init_sysfs(pdev);
|
||||
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev, "%s: Error in initing sysfs\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
INIT_WORK(&adsp_ldr_work, adsp_load_fw);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id adsp_loader_dt_match[] = {
|
||||
{ .compatible = "qcom,adsp-loader" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adsp_loader_dt_match);
|
||||
|
||||
static struct platform_driver adsp_loader_driver = {
|
||||
.driver = {
|
||||
.name = "adsp-loader",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = adsp_loader_dt_match,
|
||||
},
|
||||
.probe = adsp_loader_probe,
|
||||
.remove = adsp_loader_remove,
|
||||
};
|
||||
|
||||
static int __init adsp_loader_init(void)
|
||||
{
|
||||
return platform_driver_register(&adsp_loader_driver);
|
||||
}
|
||||
module_init(adsp_loader_init);
|
||||
|
||||
static void __exit adsp_loader_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&adsp_loader_driver);
|
||||
}
|
||||
module_exit(adsp_loader_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ADSP Loader module");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
976
drivers/soc/qcom/qdsp6v2/apr.c
Normal file
976
drivers/soc/qcom/qdsp6v2/apr.c
Normal file
@@ -0,0 +1,976 @@
|
||||
/* Copyright (c) 2010-2014, 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/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <soc/qcom/subsystem_restart.h>
|
||||
#include <soc/qcom/scm.h>
|
||||
#include <sound/apr_audio-v2.h>
|
||||
#include <linux/qdsp6v2/apr.h>
|
||||
#include <linux/qdsp6v2/apr_tal.h>
|
||||
#include <linux/qdsp6v2/dsp_debug.h>
|
||||
#include <linux/qdsp6v2/audio_notifier.h>
|
||||
#include <linux/ipc_logging.h>
|
||||
|
||||
#define APR_PKT_IPC_LOG_PAGE_CNT 2
|
||||
|
||||
static struct apr_q6 q6;
|
||||
static struct apr_client client[APR_DEST_MAX][APR_CLIENT_MAX];
|
||||
static void *apr_pkt_ctx;
|
||||
static wait_queue_head_t dsp_wait;
|
||||
static wait_queue_head_t modem_wait;
|
||||
static bool is_modem_up;
|
||||
static bool is_initial_boot;
|
||||
/* Subsystem restart: QDSP6 data, functions */
|
||||
static struct workqueue_struct *apr_reset_workqueue;
|
||||
static void apr_reset_deregister(struct work_struct *work);
|
||||
static void dispatch_event(unsigned long code, uint16_t proc);
|
||||
struct apr_reset_work {
|
||||
void *handle;
|
||||
struct work_struct work;
|
||||
};
|
||||
|
||||
static bool apr_cf_debug;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static struct dentry *debugfs_apr_debug;
|
||||
static ssize_t apr_debug_write(struct file *filp, const char __user *ubuf,
|
||||
size_t cnt, loff_t *ppos)
|
||||
{
|
||||
char cmd;
|
||||
|
||||
if (copy_from_user(&cmd, ubuf, 1))
|
||||
return -EFAULT;
|
||||
|
||||
apr_cf_debug = (cmd == '1') ? true : false;
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static const struct file_operations apr_debug_ops = {
|
||||
.write = apr_debug_write,
|
||||
};
|
||||
#endif
|
||||
|
||||
#define APR_PKT_INFO(x...) \
|
||||
do { \
|
||||
if (apr_pkt_ctx) \
|
||||
ipc_log_string(apr_pkt_ctx, "<APR>: "x); \
|
||||
} while (0)
|
||||
|
||||
|
||||
struct apr_svc_table {
|
||||
char name[64];
|
||||
int idx;
|
||||
int id;
|
||||
int client_id;
|
||||
};
|
||||
|
||||
static const struct apr_svc_table svc_tbl_qdsp6[] = {
|
||||
{
|
||||
.name = "AFE",
|
||||
.idx = 0,
|
||||
.id = APR_SVC_AFE,
|
||||
.client_id = APR_CLIENT_AUDIO,
|
||||
},
|
||||
{
|
||||
.name = "ASM",
|
||||
.idx = 1,
|
||||
.id = APR_SVC_ASM,
|
||||
.client_id = APR_CLIENT_AUDIO,
|
||||
},
|
||||
{
|
||||
.name = "ADM",
|
||||
.idx = 2,
|
||||
.id = APR_SVC_ADM,
|
||||
.client_id = APR_CLIENT_AUDIO,
|
||||
},
|
||||
{
|
||||
.name = "CORE",
|
||||
.idx = 3,
|
||||
.id = APR_SVC_ADSP_CORE,
|
||||
.client_id = APR_CLIENT_AUDIO,
|
||||
},
|
||||
{
|
||||
.name = "TEST",
|
||||
.idx = 4,
|
||||
.id = APR_SVC_TEST_CLIENT,
|
||||
.client_id = APR_CLIENT_AUDIO,
|
||||
},
|
||||
{
|
||||
.name = "MVM",
|
||||
.idx = 5,
|
||||
.id = APR_SVC_ADSP_MVM,
|
||||
.client_id = APR_CLIENT_AUDIO,
|
||||
},
|
||||
{
|
||||
.name = "CVS",
|
||||
.idx = 6,
|
||||
.id = APR_SVC_ADSP_CVS,
|
||||
.client_id = APR_CLIENT_AUDIO,
|
||||
},
|
||||
{
|
||||
.name = "CVP",
|
||||
.idx = 7,
|
||||
.id = APR_SVC_ADSP_CVP,
|
||||
.client_id = APR_CLIENT_AUDIO,
|
||||
},
|
||||
{
|
||||
.name = "USM",
|
||||
.idx = 8,
|
||||
.id = APR_SVC_USM,
|
||||
.client_id = APR_CLIENT_AUDIO,
|
||||
},
|
||||
{
|
||||
.name = "VIDC",
|
||||
.idx = 9,
|
||||
.id = APR_SVC_VIDC,
|
||||
},
|
||||
{
|
||||
.name = "LSM",
|
||||
.idx = 10,
|
||||
.id = APR_SVC_LSM,
|
||||
.client_id = APR_CLIENT_AUDIO,
|
||||
},
|
||||
};
|
||||
|
||||
static struct apr_svc_table svc_tbl_voice[] = {
|
||||
{
|
||||
.name = "VSM",
|
||||
.idx = 0,
|
||||
.id = APR_SVC_VSM,
|
||||
.client_id = APR_CLIENT_VOICE,
|
||||
},
|
||||
{
|
||||
.name = "VPM",
|
||||
.idx = 1,
|
||||
.id = APR_SVC_VPM,
|
||||
.client_id = APR_CLIENT_VOICE,
|
||||
},
|
||||
{
|
||||
.name = "MVS",
|
||||
.idx = 2,
|
||||
.id = APR_SVC_MVS,
|
||||
.client_id = APR_CLIENT_VOICE,
|
||||
},
|
||||
{
|
||||
.name = "MVM",
|
||||
.idx = 3,
|
||||
.id = APR_SVC_MVM,
|
||||
.client_id = APR_CLIENT_VOICE,
|
||||
},
|
||||
{
|
||||
.name = "CVS",
|
||||
.idx = 4,
|
||||
.id = APR_SVC_CVS,
|
||||
.client_id = APR_CLIENT_VOICE,
|
||||
},
|
||||
{
|
||||
.name = "CVP",
|
||||
.idx = 5,
|
||||
.id = APR_SVC_CVP,
|
||||
.client_id = APR_CLIENT_VOICE,
|
||||
},
|
||||
{
|
||||
.name = "SRD",
|
||||
.idx = 6,
|
||||
.id = APR_SVC_SRD,
|
||||
.client_id = APR_CLIENT_VOICE,
|
||||
},
|
||||
{
|
||||
.name = "TEST",
|
||||
.idx = 7,
|
||||
.id = APR_SVC_TEST_CLIENT,
|
||||
.client_id = APR_CLIENT_VOICE,
|
||||
},
|
||||
};
|
||||
|
||||
enum apr_subsys_state apr_get_modem_state(void)
|
||||
{
|
||||
return atomic_read(&q6.modem_state);
|
||||
}
|
||||
|
||||
void apr_set_modem_state(enum apr_subsys_state state)
|
||||
{
|
||||
atomic_set(&q6.modem_state, state);
|
||||
}
|
||||
|
||||
enum apr_subsys_state apr_cmpxchg_modem_state(enum apr_subsys_state prev,
|
||||
enum apr_subsys_state new)
|
||||
{
|
||||
return atomic_cmpxchg(&q6.modem_state, prev, new);
|
||||
}
|
||||
|
||||
static void apr_modem_down(unsigned long opcode)
|
||||
{
|
||||
apr_set_modem_state(APR_SUBSYS_DOWN);
|
||||
dispatch_event(opcode, APR_DEST_MODEM);
|
||||
}
|
||||
|
||||
static void apr_modem_up(void)
|
||||
{
|
||||
if (apr_cmpxchg_modem_state(APR_SUBSYS_DOWN, APR_SUBSYS_UP) ==
|
||||
APR_SUBSYS_DOWN)
|
||||
wake_up(&modem_wait);
|
||||
is_modem_up = 1;
|
||||
}
|
||||
|
||||
enum apr_subsys_state apr_get_q6_state(void)
|
||||
{
|
||||
return atomic_read(&q6.q6_state);
|
||||
}
|
||||
EXPORT_SYMBOL(apr_get_q6_state);
|
||||
|
||||
int apr_set_q6_state(enum apr_subsys_state state)
|
||||
{
|
||||
pr_debug("%s: setting adsp state %d\n", __func__, state);
|
||||
if (state < APR_SUBSYS_DOWN || state > APR_SUBSYS_LOADED)
|
||||
return -EINVAL;
|
||||
atomic_set(&q6.q6_state, state);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(apr_set_q6_state);
|
||||
|
||||
enum apr_subsys_state apr_cmpxchg_q6_state(enum apr_subsys_state prev,
|
||||
enum apr_subsys_state new)
|
||||
{
|
||||
return atomic_cmpxchg(&q6.q6_state, prev, new);
|
||||
}
|
||||
|
||||
static void apr_adsp_down(unsigned long opcode)
|
||||
{
|
||||
apr_set_q6_state(APR_SUBSYS_DOWN);
|
||||
dispatch_event(opcode, APR_DEST_QDSP6);
|
||||
}
|
||||
|
||||
static void apr_adsp_up(void)
|
||||
{
|
||||
if (apr_cmpxchg_q6_state(APR_SUBSYS_DOWN, APR_SUBSYS_LOADED) ==
|
||||
APR_SUBSYS_DOWN)
|
||||
wake_up(&dsp_wait);
|
||||
}
|
||||
|
||||
int apr_wait_for_device_up(int dest_id)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
if (dest_id == APR_DEST_MODEM)
|
||||
rc = wait_event_interruptible_timeout(modem_wait,
|
||||
(apr_get_modem_state() == APR_SUBSYS_UP),
|
||||
(1 * HZ));
|
||||
else if (dest_id == APR_DEST_QDSP6)
|
||||
rc = wait_event_interruptible_timeout(dsp_wait,
|
||||
(apr_get_q6_state() == APR_SUBSYS_UP),
|
||||
(1 * HZ));
|
||||
else
|
||||
pr_err("%s: unknown dest_id %d\n", __func__, dest_id);
|
||||
/* returns left time */
|
||||
return rc;
|
||||
}
|
||||
|
||||
int apr_load_adsp_image(void)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
mutex_lock(&q6.lock);
|
||||
if (apr_get_q6_state() == APR_SUBSYS_UP) {
|
||||
q6.pil = subsystem_get("adsp");
|
||||
if (IS_ERR(q6.pil)) {
|
||||
rc = PTR_ERR(q6.pil);
|
||||
pr_err("APR: Unable to load q6 image, error:%d\n", rc);
|
||||
} else {
|
||||
apr_set_q6_state(APR_SUBSYS_LOADED);
|
||||
pr_debug("APR: Image is loaded, stated\n");
|
||||
}
|
||||
} else if (apr_get_q6_state() == APR_SUBSYS_LOADED) {
|
||||
pr_debug("APR: q6 image already loaded\n");
|
||||
} else {
|
||||
pr_debug("APR: cannot load state %d\n", apr_get_q6_state());
|
||||
}
|
||||
mutex_unlock(&q6.lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct apr_client *apr_get_client(int dest_id, int client_id)
|
||||
{
|
||||
return &client[dest_id][client_id];
|
||||
}
|
||||
|
||||
int apr_send_pkt(void *handle, uint32_t *buf)
|
||||
{
|
||||
struct apr_svc *svc = handle;
|
||||
struct apr_client *clnt;
|
||||
struct apr_hdr *hdr;
|
||||
uint16_t dest_id;
|
||||
uint16_t client_id;
|
||||
uint16_t w_len;
|
||||
int rc;
|
||||
unsigned long flags;
|
||||
|
||||
if (!handle || !buf) {
|
||||
pr_err("APR: Wrong parameters\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (svc->need_reset) {
|
||||
pr_err("apr: send_pkt service need reset\n");
|
||||
return -ENETRESET;
|
||||
}
|
||||
|
||||
if ((svc->dest_id == APR_DEST_QDSP6) &&
|
||||
(apr_get_q6_state() != APR_SUBSYS_LOADED)) {
|
||||
pr_err("%s: Still dsp is not Up\n", __func__);
|
||||
return -ENETRESET;
|
||||
} else if ((svc->dest_id == APR_DEST_MODEM) &&
|
||||
(apr_get_modem_state() == APR_SUBSYS_DOWN)) {
|
||||
pr_err("apr: Still Modem is not Up\n");
|
||||
return -ENETRESET;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&svc->w_lock, flags);
|
||||
dest_id = svc->dest_id;
|
||||
client_id = svc->client_id;
|
||||
clnt = &client[dest_id][client_id];
|
||||
|
||||
if (!client[dest_id][client_id].handle) {
|
||||
pr_err("APR: Still service is not yet opened\n");
|
||||
spin_unlock_irqrestore(&svc->w_lock, flags);
|
||||
return -EINVAL;
|
||||
}
|
||||
hdr = (struct apr_hdr *)buf;
|
||||
|
||||
hdr->src_domain = APR_DOMAIN_APPS;
|
||||
hdr->src_svc = svc->id;
|
||||
hdr->dest_domain = svc->dest_domain;
|
||||
hdr->dest_svc = svc->id;
|
||||
|
||||
if (unlikely(apr_cf_debug)) {
|
||||
APR_PKT_INFO(
|
||||
"Tx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X]",
|
||||
(hdr->src_domain << 8) | hdr->src_svc,
|
||||
(hdr->dest_domain << 8) | hdr->dest_svc, hdr->opcode,
|
||||
hdr->token);
|
||||
}
|
||||
|
||||
rc = apr_tal_write(clnt->handle, buf,
|
||||
(struct apr_pkt_priv *)&svc->pkt_owner,
|
||||
hdr->pkt_size);
|
||||
if (rc >= 0) {
|
||||
w_len = rc;
|
||||
if (w_len != hdr->pkt_size) {
|
||||
pr_err("%s: Unable to write whole APR pkt successfully: %d\n",
|
||||
__func__, rc);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
} else {
|
||||
pr_err("%s: Write APR pkt failed with error %d\n",
|
||||
__func__, rc);
|
||||
}
|
||||
spin_unlock_irqrestore(&svc->w_lock, flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int apr_pkt_config(void *handle, struct apr_pkt_cfg *cfg)
|
||||
{
|
||||
struct apr_svc *svc = (struct apr_svc *)handle;
|
||||
uint16_t dest_id;
|
||||
uint16_t client_id;
|
||||
struct apr_client *clnt;
|
||||
|
||||
if (!handle) {
|
||||
pr_err("%s: Invalid handle\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (svc->need_reset) {
|
||||
pr_err("%s: service need reset\n", __func__);
|
||||
return -ENETRESET;
|
||||
}
|
||||
|
||||
svc->pkt_owner = cfg->pkt_owner;
|
||||
dest_id = svc->dest_id;
|
||||
client_id = svc->client_id;
|
||||
clnt = &client[dest_id][client_id];
|
||||
|
||||
return apr_tal_rx_intents_config(clnt->handle,
|
||||
cfg->intents.num_of_intents, cfg->intents.size);
|
||||
}
|
||||
|
||||
struct apr_svc *apr_register(char *dest, char *svc_name, apr_fn svc_fn,
|
||||
uint32_t src_port, void *priv)
|
||||
{
|
||||
struct apr_client *clnt;
|
||||
int client_id = 0;
|
||||
int svc_idx = 0;
|
||||
int svc_id = 0;
|
||||
int dest_id = 0;
|
||||
int domain_id = 0;
|
||||
int temp_port = 0;
|
||||
struct apr_svc *svc = NULL;
|
||||
int rc = 0;
|
||||
bool can_open_channel = true;
|
||||
|
||||
if (!dest || !svc_name || !svc_fn)
|
||||
return NULL;
|
||||
|
||||
if (!strcmp(dest, "ADSP"))
|
||||
domain_id = APR_DOMAIN_ADSP;
|
||||
else if (!strcmp(dest, "MODEM")) {
|
||||
/* Don't request for SMD channels if destination is MODEM,
|
||||
* as these channels are no longer used and these clients
|
||||
* are to listen only for MODEM SSR events
|
||||
*/
|
||||
can_open_channel = false;
|
||||
domain_id = APR_DOMAIN_MODEM;
|
||||
} else {
|
||||
pr_err("APR: wrong destination\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
dest_id = apr_get_dest_id(dest);
|
||||
|
||||
if (dest_id == APR_DEST_QDSP6) {
|
||||
if (apr_get_q6_state() != APR_SUBSYS_LOADED) {
|
||||
pr_err("%s: adsp not up\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
pr_debug("%s: adsp Up\n", __func__);
|
||||
} else if (dest_id == APR_DEST_MODEM) {
|
||||
if (apr_get_modem_state() == APR_SUBSYS_DOWN) {
|
||||
if (is_modem_up) {
|
||||
pr_err("%s: modem shutdown due to SSR, ret",
|
||||
__func__);
|
||||
return NULL;
|
||||
}
|
||||
pr_debug("%s: Wait for modem to bootup\n", __func__);
|
||||
rc = apr_wait_for_device_up(APR_DEST_MODEM);
|
||||
if (rc == 0) {
|
||||
pr_err("%s: Modem is not Up\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
pr_debug("%s: modem Up\n", __func__);
|
||||
}
|
||||
|
||||
if (apr_get_svc(svc_name, domain_id, &client_id, &svc_idx, &svc_id)) {
|
||||
pr_err("%s: apr_get_svc failed\n", __func__);
|
||||
goto done;
|
||||
}
|
||||
|
||||
clnt = &client[dest_id][client_id];
|
||||
mutex_lock(&clnt->m_lock);
|
||||
if (!clnt->handle && can_open_channel) {
|
||||
clnt->handle = apr_tal_open(client_id, dest_id,
|
||||
APR_DL_SMD, apr_cb_func, NULL);
|
||||
if (!clnt->handle) {
|
||||
svc = NULL;
|
||||
pr_err("APR: Unable to open handle\n");
|
||||
mutex_unlock(&clnt->m_lock);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&clnt->m_lock);
|
||||
svc = &clnt->svc[svc_idx];
|
||||
mutex_lock(&svc->m_lock);
|
||||
clnt->id = client_id;
|
||||
if (svc->need_reset) {
|
||||
mutex_unlock(&svc->m_lock);
|
||||
pr_err("APR: Service needs reset\n");
|
||||
goto done;
|
||||
}
|
||||
svc->id = svc_id;
|
||||
svc->dest_id = dest_id;
|
||||
svc->client_id = client_id;
|
||||
svc->dest_domain = domain_id;
|
||||
svc->pkt_owner = APR_PKT_OWNER_DRIVER;
|
||||
|
||||
if (src_port != 0xFFFFFFFF) {
|
||||
temp_port = ((src_port >> 8) * 8) + (src_port & 0xFF);
|
||||
pr_debug("port = %d t_port = %d\n", src_port, temp_port);
|
||||
if (temp_port >= APR_MAX_PORTS || temp_port < 0) {
|
||||
pr_err("APR: temp_port out of bounds\n");
|
||||
mutex_unlock(&svc->m_lock);
|
||||
return NULL;
|
||||
}
|
||||
if (!svc->svc_cnt)
|
||||
clnt->svc_cnt++;
|
||||
svc->port_cnt++;
|
||||
svc->port_fn[temp_port] = svc_fn;
|
||||
svc->port_priv[temp_port] = priv;
|
||||
svc->svc_cnt++;
|
||||
} else {
|
||||
if (!svc->fn) {
|
||||
if (!svc->svc_cnt)
|
||||
clnt->svc_cnt++;
|
||||
svc->fn = svc_fn;
|
||||
svc->priv = priv;
|
||||
svc->svc_cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&svc->m_lock);
|
||||
done:
|
||||
return svc;
|
||||
}
|
||||
|
||||
|
||||
void apr_cb_func(void *buf, int len, void *priv)
|
||||
{
|
||||
struct apr_client_data data;
|
||||
struct apr_client *apr_client;
|
||||
struct apr_svc *c_svc;
|
||||
struct apr_hdr *hdr;
|
||||
uint16_t hdr_size;
|
||||
uint16_t msg_type;
|
||||
uint16_t ver;
|
||||
uint16_t src;
|
||||
uint16_t svc;
|
||||
uint16_t clnt;
|
||||
int i;
|
||||
int temp_port = 0;
|
||||
uint32_t *ptr;
|
||||
|
||||
pr_debug("APR2: len = %d\n", len);
|
||||
ptr = buf;
|
||||
pr_debug("\n*****************\n");
|
||||
for (i = 0; i < len/4; i++)
|
||||
pr_debug("%x ", ptr[i]);
|
||||
pr_debug("\n");
|
||||
pr_debug("\n*****************\n");
|
||||
|
||||
if (!buf || len <= APR_HDR_SIZE) {
|
||||
pr_err("APR: Improper apr pkt received:%pK %d\n", buf, len);
|
||||
return;
|
||||
}
|
||||
hdr = buf;
|
||||
|
||||
ver = hdr->hdr_field;
|
||||
ver = (ver & 0x000F);
|
||||
if (ver > APR_PKT_VER + 1) {
|
||||
pr_err("APR: Wrong version: %d\n", ver);
|
||||
return;
|
||||
}
|
||||
|
||||
hdr_size = hdr->hdr_field;
|
||||
hdr_size = ((hdr_size & 0x00F0) >> 0x4) * 4;
|
||||
if (hdr_size < APR_HDR_SIZE) {
|
||||
pr_err("APR: Wrong hdr size:%d\n", hdr_size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hdr->pkt_size < APR_HDR_SIZE) {
|
||||
pr_err("APR: Wrong paket size\n");
|
||||
return;
|
||||
}
|
||||
msg_type = hdr->hdr_field;
|
||||
msg_type = (msg_type >> 0x08) & 0x0003;
|
||||
if (msg_type >= APR_MSG_TYPE_MAX && msg_type != APR_BASIC_RSP_RESULT) {
|
||||
pr_err("APR: Wrong message type: %d\n", msg_type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hdr->src_domain >= APR_DOMAIN_MAX ||
|
||||
hdr->dest_domain >= APR_DOMAIN_MAX ||
|
||||
hdr->src_svc >= APR_SVC_MAX ||
|
||||
hdr->dest_svc >= APR_SVC_MAX) {
|
||||
pr_err("APR: Wrong APR header\n");
|
||||
return;
|
||||
}
|
||||
|
||||
svc = hdr->dest_svc;
|
||||
if (hdr->src_domain == APR_DOMAIN_MODEM) {
|
||||
if (svc == APR_SVC_MVS || svc == APR_SVC_MVM ||
|
||||
svc == APR_SVC_CVS || svc == APR_SVC_CVP ||
|
||||
svc == APR_SVC_TEST_CLIENT)
|
||||
clnt = APR_CLIENT_VOICE;
|
||||
else {
|
||||
pr_err("APR: Wrong svc :%d\n", svc);
|
||||
return;
|
||||
}
|
||||
} else if (hdr->src_domain == APR_DOMAIN_ADSP) {
|
||||
if (svc == APR_SVC_AFE || svc == APR_SVC_ASM ||
|
||||
svc == APR_SVC_VSM || svc == APR_SVC_VPM ||
|
||||
svc == APR_SVC_ADM || svc == APR_SVC_ADSP_CORE ||
|
||||
svc == APR_SVC_USM ||
|
||||
svc == APR_SVC_TEST_CLIENT || svc == APR_SVC_ADSP_MVM ||
|
||||
svc == APR_SVC_ADSP_CVS || svc == APR_SVC_ADSP_CVP ||
|
||||
svc == APR_SVC_LSM)
|
||||
clnt = APR_CLIENT_AUDIO;
|
||||
else if (svc == APR_SVC_VIDC)
|
||||
clnt = APR_CLIENT_AUDIO;
|
||||
else {
|
||||
pr_err("APR: Wrong svc :%d\n", svc);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
pr_err("APR: Pkt from wrong source: %d\n", hdr->src_domain);
|
||||
return;
|
||||
}
|
||||
|
||||
src = apr_get_data_src(hdr);
|
||||
if (src == APR_DEST_MAX)
|
||||
return;
|
||||
|
||||
pr_debug("src =%d clnt = %d\n", src, clnt);
|
||||
apr_client = &client[src][clnt];
|
||||
for (i = 0; i < APR_SVC_MAX; i++)
|
||||
if (apr_client->svc[i].id == svc) {
|
||||
pr_debug("%d\n", apr_client->svc[i].id);
|
||||
c_svc = &apr_client->svc[i];
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == APR_SVC_MAX) {
|
||||
pr_err("APR: service is not registered\n");
|
||||
return;
|
||||
}
|
||||
pr_debug("svc_idx = %d\n", i);
|
||||
pr_debug("%x %x %x %pK %pK\n", c_svc->id, c_svc->dest_id,
|
||||
c_svc->client_id, c_svc->fn, c_svc->priv);
|
||||
data.payload_size = hdr->pkt_size - hdr_size;
|
||||
data.opcode = hdr->opcode;
|
||||
data.src = src;
|
||||
data.src_port = hdr->src_port;
|
||||
data.dest_port = hdr->dest_port;
|
||||
data.token = hdr->token;
|
||||
data.msg_type = msg_type;
|
||||
data.payload = NULL;
|
||||
if (data.payload_size > 0)
|
||||
data.payload = (char *)hdr + hdr_size;
|
||||
|
||||
if (unlikely(apr_cf_debug)) {
|
||||
if (hdr->opcode == APR_BASIC_RSP_RESULT && data.payload) {
|
||||
uint32_t *ptr = data.payload;
|
||||
|
||||
APR_PKT_INFO(
|
||||
"Rx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X] rc[0x%X]",
|
||||
(hdr->src_domain << 8) | hdr->src_svc,
|
||||
(hdr->dest_domain << 8) | hdr->dest_svc,
|
||||
hdr->opcode, hdr->token, ptr[1]);
|
||||
} else {
|
||||
APR_PKT_INFO(
|
||||
"Rx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X]",
|
||||
(hdr->src_domain << 8) | hdr->src_svc,
|
||||
(hdr->dest_domain << 8) | hdr->dest_svc, hdr->opcode,
|
||||
hdr->token);
|
||||
}
|
||||
}
|
||||
|
||||
temp_port = ((data.dest_port >> 8) * 8) + (data.dest_port & 0xFF);
|
||||
pr_debug("port = %d t_port = %d\n", data.src_port, temp_port);
|
||||
if (c_svc->port_cnt && c_svc->port_fn[temp_port])
|
||||
c_svc->port_fn[temp_port](&data, c_svc->port_priv[temp_port]);
|
||||
else if (c_svc->fn)
|
||||
c_svc->fn(&data, c_svc->priv);
|
||||
else
|
||||
pr_err("APR: Rxed a packet for NULL callback\n");
|
||||
}
|
||||
|
||||
int apr_get_svc(const char *svc_name, int domain_id, int *client_id,
|
||||
int *svc_idx, int *svc_id)
|
||||
{
|
||||
int i;
|
||||
int size;
|
||||
struct apr_svc_table *tbl;
|
||||
int ret = 0;
|
||||
|
||||
if (domain_id == APR_DOMAIN_ADSP) {
|
||||
tbl = (struct apr_svc_table *)&svc_tbl_qdsp6;
|
||||
size = ARRAY_SIZE(svc_tbl_qdsp6);
|
||||
} else {
|
||||
tbl = (struct apr_svc_table *)&svc_tbl_voice;
|
||||
size = ARRAY_SIZE(svc_tbl_voice);
|
||||
}
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
if (!strcmp(svc_name, tbl[i].name)) {
|
||||
*client_id = tbl[i].client_id;
|
||||
*svc_idx = tbl[i].idx;
|
||||
*svc_id = tbl[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("%s: svc_name = %s c_id = %d domain_id = %d\n",
|
||||
__func__, svc_name, *client_id, domain_id);
|
||||
if (i == size) {
|
||||
pr_err("%s: APR: Wrong svc name %s\n", __func__, svc_name);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void apr_reset_deregister(struct work_struct *work)
|
||||
{
|
||||
struct apr_svc *handle = NULL;
|
||||
struct apr_reset_work *apr_reset =
|
||||
container_of(work, struct apr_reset_work, work);
|
||||
|
||||
handle = apr_reset->handle;
|
||||
pr_debug("%s:handle[%pK]\n", __func__, handle);
|
||||
apr_deregister(handle);
|
||||
kfree(apr_reset);
|
||||
}
|
||||
|
||||
int apr_deregister(void *handle)
|
||||
{
|
||||
struct apr_svc *svc = handle;
|
||||
struct apr_client *clnt;
|
||||
uint16_t dest_id;
|
||||
uint16_t client_id;
|
||||
|
||||
if (!handle)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&svc->m_lock);
|
||||
if (!svc->svc_cnt) {
|
||||
pr_err("%s: svc already deregistered. svc = %pK\n",
|
||||
__func__, svc);
|
||||
mutex_unlock(&svc->m_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dest_id = svc->dest_id;
|
||||
client_id = svc->client_id;
|
||||
clnt = &client[dest_id][client_id];
|
||||
|
||||
if (svc->svc_cnt > 0) {
|
||||
if (svc->port_cnt)
|
||||
svc->port_cnt--;
|
||||
svc->svc_cnt--;
|
||||
if (!svc->svc_cnt) {
|
||||
client[dest_id][client_id].svc_cnt--;
|
||||
pr_debug("%s: service is reset %pK\n", __func__, svc);
|
||||
}
|
||||
}
|
||||
|
||||
if (!svc->svc_cnt) {
|
||||
svc->priv = NULL;
|
||||
svc->id = 0;
|
||||
svc->fn = NULL;
|
||||
svc->dest_id = 0;
|
||||
svc->client_id = 0;
|
||||
svc->need_reset = 0x0;
|
||||
}
|
||||
if (client[dest_id][client_id].handle &&
|
||||
!client[dest_id][client_id].svc_cnt) {
|
||||
apr_tal_close(client[dest_id][client_id].handle);
|
||||
client[dest_id][client_id].handle = NULL;
|
||||
}
|
||||
mutex_unlock(&svc->m_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void apr_reset(void *handle)
|
||||
{
|
||||
struct apr_reset_work *apr_reset_worker = NULL;
|
||||
|
||||
if (!handle)
|
||||
return;
|
||||
pr_debug("%s: handle[%pK]\n", __func__, handle);
|
||||
|
||||
if (apr_reset_workqueue == NULL) {
|
||||
pr_err("%s: apr_reset_workqueue is NULL\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
apr_reset_worker = kzalloc(sizeof(struct apr_reset_work),
|
||||
GFP_ATOMIC);
|
||||
|
||||
if (apr_reset_worker == NULL) {
|
||||
pr_err("%s: mem failure\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
apr_reset_worker->handle = handle;
|
||||
INIT_WORK(&apr_reset_worker->work, apr_reset_deregister);
|
||||
queue_work(apr_reset_workqueue, &apr_reset_worker->work);
|
||||
}
|
||||
|
||||
/* Dispatch the Reset events to Modem and audio clients */
|
||||
static void dispatch_event(unsigned long code, uint16_t proc)
|
||||
{
|
||||
struct apr_client *apr_client;
|
||||
struct apr_client_data data;
|
||||
struct apr_svc *svc;
|
||||
uint16_t clnt;
|
||||
int i, j;
|
||||
|
||||
data.opcode = RESET_EVENTS;
|
||||
data.reset_event = code;
|
||||
|
||||
/* Service domain can be different from the processor */
|
||||
data.reset_proc = apr_get_reset_domain(proc);
|
||||
|
||||
clnt = APR_CLIENT_AUDIO;
|
||||
apr_client = &client[proc][clnt];
|
||||
for (i = 0; i < APR_SVC_MAX; i++) {
|
||||
mutex_lock(&apr_client->svc[i].m_lock);
|
||||
if (apr_client->svc[i].fn) {
|
||||
apr_client->svc[i].need_reset = 0x1;
|
||||
apr_client->svc[i].fn(&data, apr_client->svc[i].priv);
|
||||
}
|
||||
if (apr_client->svc[i].port_cnt) {
|
||||
svc = &(apr_client->svc[i]);
|
||||
svc->need_reset = 0x1;
|
||||
for (j = 0; j < APR_MAX_PORTS; j++)
|
||||
if (svc->port_fn[j])
|
||||
svc->port_fn[j](&data,
|
||||
svc->port_priv[j]);
|
||||
}
|
||||
mutex_unlock(&apr_client->svc[i].m_lock);
|
||||
}
|
||||
|
||||
clnt = APR_CLIENT_VOICE;
|
||||
apr_client = &client[proc][clnt];
|
||||
for (i = 0; i < APR_SVC_MAX; i++) {
|
||||
mutex_lock(&apr_client->svc[i].m_lock);
|
||||
if (apr_client->svc[i].fn) {
|
||||
apr_client->svc[i].need_reset = 0x1;
|
||||
apr_client->svc[i].fn(&data, apr_client->svc[i].priv);
|
||||
}
|
||||
if (apr_client->svc[i].port_cnt) {
|
||||
svc = &(apr_client->svc[i]);
|
||||
svc->need_reset = 0x1;
|
||||
for (j = 0; j < APR_MAX_PORTS; j++)
|
||||
if (svc->port_fn[j])
|
||||
svc->port_fn[j](&data,
|
||||
svc->port_priv[j]);
|
||||
}
|
||||
mutex_unlock(&apr_client->svc[i].m_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static int apr_notifier_service_cb(struct notifier_block *this,
|
||||
unsigned long opcode, void *data)
|
||||
{
|
||||
struct audio_notifier_cb_data *cb_data = data;
|
||||
|
||||
if (cb_data == NULL) {
|
||||
pr_err("%s: Callback data is NULL!\n", __func__);
|
||||
goto done;
|
||||
}
|
||||
|
||||
pr_debug("%s: Service opcode 0x%lx, domain %d\n",
|
||||
__func__, opcode, cb_data->domain);
|
||||
|
||||
switch (opcode) {
|
||||
case AUDIO_NOTIFIER_SERVICE_DOWN:
|
||||
/*
|
||||
* Use flag to ignore down notifications during
|
||||
* initial boot. There is no benefit from error
|
||||
* recovery notifications during initial boot
|
||||
* up since everything is expected to be down.
|
||||
*/
|
||||
if (is_initial_boot) {
|
||||
is_initial_boot = false;
|
||||
break;
|
||||
}
|
||||
if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN)
|
||||
apr_modem_down(opcode);
|
||||
else
|
||||
apr_adsp_down(opcode);
|
||||
break;
|
||||
case AUDIO_NOTIFIER_SERVICE_UP:
|
||||
is_initial_boot = false;
|
||||
if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN)
|
||||
apr_modem_up();
|
||||
else
|
||||
apr_adsp_up();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
done:
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block adsp_service_nb = {
|
||||
.notifier_call = apr_notifier_service_cb,
|
||||
.priority = 0,
|
||||
};
|
||||
|
||||
static struct notifier_block modem_service_nb = {
|
||||
.notifier_call = apr_notifier_service_cb,
|
||||
.priority = 0,
|
||||
};
|
||||
|
||||
static int __init apr_init(void)
|
||||
{
|
||||
int i, j, k;
|
||||
|
||||
for (i = 0; i < APR_DEST_MAX; i++)
|
||||
for (j = 0; j < APR_CLIENT_MAX; j++) {
|
||||
mutex_init(&client[i][j].m_lock);
|
||||
for (k = 0; k < APR_SVC_MAX; k++) {
|
||||
mutex_init(&client[i][j].svc[k].m_lock);
|
||||
spin_lock_init(&client[i][j].svc[k].w_lock);
|
||||
}
|
||||
}
|
||||
apr_set_subsys_state();
|
||||
mutex_init(&q6.lock);
|
||||
apr_reset_workqueue = create_singlethread_workqueue("apr_driver");
|
||||
if (!apr_reset_workqueue)
|
||||
return -ENOMEM;
|
||||
|
||||
apr_pkt_ctx = ipc_log_context_create(APR_PKT_IPC_LOG_PAGE_CNT,
|
||||
"apr", 0);
|
||||
if (!apr_pkt_ctx)
|
||||
pr_err("%s: Unable to create ipc log context\n", __func__);
|
||||
|
||||
is_initial_boot = true;
|
||||
subsys_notif_register("apr_adsp", AUDIO_NOTIFIER_ADSP_DOMAIN,
|
||||
&adsp_service_nb);
|
||||
subsys_notif_register("apr_modem", AUDIO_NOTIFIER_MODEM_DOMAIN,
|
||||
&modem_service_nb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
device_initcall(apr_init);
|
||||
|
||||
static int __init apr_late_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
init_waitqueue_head(&dsp_wait);
|
||||
init_waitqueue_head(&modem_wait);
|
||||
|
||||
return ret;
|
||||
}
|
||||
late_initcall(apr_late_init);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int __init apr_debug_init(void)
|
||||
{
|
||||
debugfs_apr_debug = debugfs_create_file("msm_apr_debug",
|
||||
S_IFREG | 0444, NULL, NULL,
|
||||
&apr_debug_ops);
|
||||
return 0;
|
||||
}
|
||||
device_initcall(apr_debug_init);
|
||||
#endif
|
||||
451
drivers/soc/qcom/qdsp6v2/apr_tal_glink.c
Normal file
451
drivers/soc/qcom/qdsp6v2/apr_tal_glink.c
Normal file
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
* 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/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <soc/qcom/glink.h>
|
||||
#include <linux/qdsp6v2/apr_tal.h>
|
||||
|
||||
#define APR_MAXIMUM_NUM_OF_RETRIES 2
|
||||
|
||||
struct apr_tx_buf {
|
||||
struct apr_pkt_priv pkt_priv;
|
||||
char buf[APR_MAX_BUF];
|
||||
};
|
||||
|
||||
struct link_state {
|
||||
uint32_t dest;
|
||||
void *handle;
|
||||
enum glink_link_state link_state;
|
||||
wait_queue_head_t wait;
|
||||
};
|
||||
|
||||
static struct link_state link_state[APR_DEST_MAX];
|
||||
|
||||
static char *svc_names[APR_DEST_MAX][APR_CLIENT_MAX] = {
|
||||
{
|
||||
"apr_audio_svc",
|
||||
"apr_voice_svc",
|
||||
},
|
||||
{
|
||||
"apr_audio_svc",
|
||||
"apr_voice_svc",
|
||||
},
|
||||
};
|
||||
|
||||
static struct apr_svc_ch_dev
|
||||
apr_svc_ch[APR_DL_MAX][APR_DEST_MAX][APR_CLIENT_MAX];
|
||||
|
||||
static struct apr_tx_buf *apr_alloc_buf(int len)
|
||||
{
|
||||
|
||||
if (len > APR_MAX_BUF) {
|
||||
pr_err("%s: buf too large [%d]\n", __func__, len);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
return kzalloc(sizeof(struct apr_tx_buf), GFP_ATOMIC);
|
||||
}
|
||||
|
||||
static void apr_free_buf(const void *ptr)
|
||||
{
|
||||
|
||||
struct apr_pkt_priv *apr_pkt_priv = (struct apr_pkt_priv *)ptr;
|
||||
struct apr_tx_buf *tx_buf;
|
||||
|
||||
if (!apr_pkt_priv) {
|
||||
pr_err("%s: Invalid apr_pkt_priv\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (apr_pkt_priv->pkt_owner == APR_PKT_OWNER_DRIVER) {
|
||||
tx_buf = container_of((void *)apr_pkt_priv,
|
||||
struct apr_tx_buf, pkt_priv);
|
||||
pr_debug("%s: Freeing buffer %pK", __func__, tx_buf);
|
||||
kfree(tx_buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int __apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data,
|
||||
struct apr_pkt_priv *pkt_priv, int len)
|
||||
{
|
||||
int rc = 0;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&apr_ch->w_lock, flags);
|
||||
rc = glink_tx(apr_ch->handle, pkt_priv, data, len, GLINK_TX_ATOMIC);
|
||||
spin_unlock_irqrestore(&apr_ch->w_lock, flags);
|
||||
|
||||
if (rc)
|
||||
pr_err("%s: glink_tx failed, rc[%d]\n", __func__, rc);
|
||||
else
|
||||
rc = len;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data,
|
||||
struct apr_pkt_priv *pkt_priv, int len)
|
||||
{
|
||||
int rc = 0, retries = 0;
|
||||
void *pkt_data = NULL;
|
||||
struct apr_tx_buf *tx_buf = NULL;
|
||||
struct apr_pkt_priv *pkt_priv_ptr = pkt_priv;
|
||||
|
||||
if (!apr_ch->handle || !pkt_priv)
|
||||
return -EINVAL;
|
||||
|
||||
if (pkt_priv->pkt_owner == APR_PKT_OWNER_DRIVER) {
|
||||
tx_buf = apr_alloc_buf(len);
|
||||
if (IS_ERR_OR_NULL(tx_buf)) {
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
memcpy(tx_buf->buf, data, len);
|
||||
memcpy(&tx_buf->pkt_priv, pkt_priv, sizeof(tx_buf->pkt_priv));
|
||||
pkt_priv_ptr = &tx_buf->pkt_priv;
|
||||
pkt_data = tx_buf->buf;
|
||||
} else {
|
||||
pkt_data = data;
|
||||
}
|
||||
|
||||
do {
|
||||
if (rc == -EAGAIN)
|
||||
udelay(50);
|
||||
|
||||
rc = __apr_tal_write(apr_ch, pkt_data, pkt_priv_ptr, len);
|
||||
} while (rc == -EAGAIN && retries++ < APR_MAXIMUM_NUM_OF_RETRIES);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("%s: Unable to send the packet, rc:%d\n", __func__, rc);
|
||||
if (pkt_priv->pkt_owner == APR_PKT_OWNER_DRIVER)
|
||||
kfree(tx_buf);
|
||||
}
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
void apr_tal_notify_rx(void *handle, const void *priv, const void *pkt_priv,
|
||||
const void *ptr, size_t size)
|
||||
{
|
||||
struct apr_svc_ch_dev *apr_ch = (struct apr_svc_ch_dev *)priv;
|
||||
unsigned long flags;
|
||||
|
||||
if (!apr_ch || !ptr) {
|
||||
pr_err("%s: Invalid apr_ch or ptr\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
pr_debug("%s: Rx packet received\n", __func__);
|
||||
|
||||
spin_lock_irqsave(&apr_ch->r_lock, flags);
|
||||
if (apr_ch->func)
|
||||
apr_ch->func((void *)ptr, size, (void *)pkt_priv);
|
||||
spin_unlock_irqrestore(&apr_ch->r_lock, flags);
|
||||
glink_rx_done(apr_ch->handle, ptr, true);
|
||||
}
|
||||
|
||||
static void apr_tal_notify_tx_abort(void *handle, const void *priv,
|
||||
const void *pkt_priv)
|
||||
{
|
||||
pr_debug("%s: tx_abort received for pkt_priv:%pK\n",
|
||||
__func__, pkt_priv);
|
||||
apr_free_buf(pkt_priv);
|
||||
}
|
||||
|
||||
void apr_tal_notify_tx_done(void *handle, const void *priv,
|
||||
const void *pkt_priv, const void *ptr)
|
||||
{
|
||||
pr_debug("%s: tx_done received for pkt_priv:%pK\n",
|
||||
__func__, pkt_priv);
|
||||
apr_free_buf(pkt_priv);
|
||||
}
|
||||
|
||||
bool apr_tal_notify_rx_intent_req(void *handle, const void *priv,
|
||||
size_t req_size)
|
||||
{
|
||||
struct apr_svc_ch_dev *apr_ch = (struct apr_svc_ch_dev *)priv;
|
||||
|
||||
if (!apr_ch) {
|
||||
pr_err("%s: Invalid apr_ch\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
pr_err("%s: No rx intents queued, unable to receive\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void apr_tal_notify_remote_rx_intent(void *handle, const void *priv,
|
||||
size_t size)
|
||||
{
|
||||
struct apr_svc_ch_dev *apr_ch = (struct apr_svc_ch_dev *)priv;
|
||||
|
||||
if (!apr_ch) {
|
||||
pr_err("%s: Invalid apr_ch\n", __func__);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* This is to make sure that the far end has queued at least one intent
|
||||
* before we attmpt any IPC. A simple bool flag is used here instead of
|
||||
* a counter, as the far end is required to guarantee intent
|
||||
* availability for all use cases once the channel is fully opened.
|
||||
*/
|
||||
pr_debug("%s: remote queued an intent\n", __func__);
|
||||
apr_ch->if_remote_intent_ready = true;
|
||||
wake_up(&apr_ch->wait);
|
||||
}
|
||||
|
||||
void apr_tal_notify_state(void *handle, const void *priv, unsigned int event)
|
||||
{
|
||||
struct apr_svc_ch_dev *apr_ch = (struct apr_svc_ch_dev *)priv;
|
||||
|
||||
if (!apr_ch) {
|
||||
pr_err("%s: Invalid apr_ch\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
apr_ch->channel_state = event;
|
||||
pr_info("%s: Channel state[%d]\n", __func__, event);
|
||||
|
||||
if (event == GLINK_CONNECTED)
|
||||
wake_up(&apr_ch->wait);
|
||||
}
|
||||
|
||||
int apr_tal_rx_intents_config(struct apr_svc_ch_dev *apr_ch,
|
||||
int num_of_intents, uint32_t size)
|
||||
{
|
||||
int i;
|
||||
int rc;
|
||||
|
||||
if (!apr_ch || !num_of_intents || !size) {
|
||||
pr_err("%s: Invalid parameter\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_of_intents; i++) {
|
||||
rc = glink_queue_rx_intent(apr_ch->handle, apr_ch, size);
|
||||
if (rc) {
|
||||
pr_err("%s: Failed to queue rx intent, iteration[%d]\n",
|
||||
__func__, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct apr_svc_ch_dev *apr_tal_open(uint32_t clnt, uint32_t dest, uint32_t dl,
|
||||
apr_svc_cb_fn func, void *priv)
|
||||
{
|
||||
int rc;
|
||||
struct glink_open_config open_cfg;
|
||||
struct apr_svc_ch_dev *apr_ch;
|
||||
|
||||
if ((clnt >= APR_CLIENT_MAX) || (dest >= APR_DEST_MAX) ||
|
||||
(dl >= APR_DL_MAX)) {
|
||||
pr_err("%s: Invalid params, clnt:%d, dest:%d, dl:%d\n",
|
||||
__func__, clnt, dest, dl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
apr_ch = &apr_svc_ch[dl][dest][clnt];
|
||||
mutex_lock(&apr_ch->m_lock);
|
||||
if (apr_ch->handle) {
|
||||
pr_err("%s: This channel is already opened\n", __func__);
|
||||
rc = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (link_state[dest].link_state != GLINK_LINK_STATE_UP) {
|
||||
rc = wait_event_timeout(link_state[dest].wait,
|
||||
link_state[dest].link_state == GLINK_LINK_STATE_UP,
|
||||
msecs_to_jiffies(APR_OPEN_TIMEOUT_MS));
|
||||
if (rc == 0) {
|
||||
pr_err("%s: Open timeout, dest:%d\n", __func__, dest);
|
||||
rc = -ETIMEDOUT;
|
||||
goto unlock;
|
||||
}
|
||||
pr_debug("%s: Wakeup done, dest:%d\n", __func__, dest);
|
||||
}
|
||||
|
||||
memset(&open_cfg, 0, sizeof(struct glink_open_config));
|
||||
open_cfg.options = GLINK_OPT_INITIAL_XPORT;
|
||||
if (dest == APR_DEST_MODEM)
|
||||
open_cfg.edge = "mpss";
|
||||
else
|
||||
open_cfg.edge = "lpass";
|
||||
|
||||
open_cfg.name = svc_names[dest][clnt];
|
||||
open_cfg.notify_rx = apr_tal_notify_rx;
|
||||
open_cfg.notify_tx_done = apr_tal_notify_tx_done;
|
||||
open_cfg.notify_state = apr_tal_notify_state;
|
||||
open_cfg.notify_rx_intent_req = apr_tal_notify_rx_intent_req;
|
||||
open_cfg.notify_remote_rx_intent = apr_tal_notify_remote_rx_intent;
|
||||
open_cfg.notify_tx_abort = apr_tal_notify_tx_abort;
|
||||
open_cfg.priv = apr_ch;
|
||||
open_cfg.transport = "smem";
|
||||
|
||||
apr_ch->channel_state = GLINK_REMOTE_DISCONNECTED;
|
||||
apr_ch->handle = glink_open(&open_cfg);
|
||||
if (IS_ERR_OR_NULL(apr_ch->handle)) {
|
||||
pr_err("%s: glink_open failed %s\n", __func__,
|
||||
svc_names[dest][clnt]);
|
||||
apr_ch->handle = NULL;
|
||||
rc = -EINVAL;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
rc = wait_event_timeout(apr_ch->wait,
|
||||
(apr_ch->channel_state == GLINK_CONNECTED), 5 * HZ);
|
||||
if (rc == 0) {
|
||||
pr_err("%s: TIMEOUT for OPEN event\n", __func__);
|
||||
rc = -ETIMEDOUT;
|
||||
goto close_link;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remote intent is not required for GLINK <--> SMD IPC, so this is
|
||||
* designed not to fail the open call.
|
||||
*/
|
||||
rc = wait_event_timeout(apr_ch->wait,
|
||||
apr_ch->if_remote_intent_ready, 5 * HZ);
|
||||
if (rc == 0)
|
||||
pr_err("%s: TIMEOUT for remote intent readiness\n", __func__);
|
||||
|
||||
rc = apr_tal_rx_intents_config(apr_ch, APR_DEFAULT_NUM_OF_INTENTS,
|
||||
APR_MAX_BUF);
|
||||
if (rc) {
|
||||
pr_err("%s: Unable to queue intents\n", __func__);
|
||||
goto close_link;
|
||||
}
|
||||
|
||||
apr_ch->func = func;
|
||||
apr_ch->priv = priv;
|
||||
|
||||
close_link:
|
||||
if (rc) {
|
||||
glink_close(apr_ch->handle);
|
||||
apr_ch->handle = NULL;
|
||||
}
|
||||
unlock:
|
||||
mutex_unlock(&apr_ch->m_lock);
|
||||
|
||||
return rc ? NULL : apr_ch;
|
||||
}
|
||||
|
||||
int apr_tal_close(struct apr_svc_ch_dev *apr_ch)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!apr_ch || !apr_ch->handle) {
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
mutex_lock(&apr_ch->m_lock);
|
||||
rc = glink_close(apr_ch->handle);
|
||||
apr_ch->handle = NULL;
|
||||
apr_ch->func = NULL;
|
||||
apr_ch->priv = NULL;
|
||||
apr_ch->if_remote_intent_ready = false;
|
||||
mutex_unlock(&apr_ch->m_lock);
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void apr_tal_link_state_cb(struct glink_link_state_cb_info *cb_info,
|
||||
void *priv)
|
||||
{
|
||||
uint32_t dest;
|
||||
|
||||
if (!cb_info) {
|
||||
pr_err("%s: Invalid cb_info\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(cb_info->edge, "mpss"))
|
||||
dest = APR_DEST_MODEM;
|
||||
else if (!strcmp(cb_info->edge, "lpass"))
|
||||
dest = APR_DEST_QDSP6;
|
||||
else {
|
||||
pr_err("%s:Unknown edge[%s]\n", __func__, cb_info->edge);
|
||||
return;
|
||||
}
|
||||
|
||||
pr_info("%s: edge[%s] link state[%d]\n", __func__, cb_info->edge,
|
||||
cb_info->link_state);
|
||||
|
||||
link_state[dest].link_state = cb_info->link_state;
|
||||
if (link_state[dest].link_state == GLINK_LINK_STATE_UP)
|
||||
wake_up(&link_state[dest].wait);
|
||||
}
|
||||
|
||||
static struct glink_link_info mpss_link_info = {
|
||||
.transport = "smem",
|
||||
.edge = "mpss",
|
||||
.glink_link_state_notif_cb = apr_tal_link_state_cb,
|
||||
};
|
||||
|
||||
static struct glink_link_info lpass_link_info = {
|
||||
.transport = "smem",
|
||||
.edge = "lpass",
|
||||
.glink_link_state_notif_cb = apr_tal_link_state_cb,
|
||||
};
|
||||
|
||||
static int __init apr_tal_init(void)
|
||||
{
|
||||
int i, j, k;
|
||||
|
||||
for (i = 0; i < APR_DL_MAX; i++) {
|
||||
for (j = 0; j < APR_DEST_MAX; j++) {
|
||||
for (k = 0; k < APR_CLIENT_MAX; k++) {
|
||||
init_waitqueue_head(&apr_svc_ch[i][j][k].wait);
|
||||
spin_lock_init(&apr_svc_ch[i][j][k].w_lock);
|
||||
spin_lock_init(&apr_svc_ch[i][j][k].r_lock);
|
||||
mutex_init(&apr_svc_ch[i][j][k].m_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < APR_DEST_MAX; i++)
|
||||
init_waitqueue_head(&link_state[i].wait);
|
||||
|
||||
link_state[APR_DEST_MODEM].link_state = GLINK_LINK_STATE_DOWN;
|
||||
link_state[APR_DEST_MODEM].handle =
|
||||
glink_register_link_state_cb(&mpss_link_info, NULL);
|
||||
if (!link_state[APR_DEST_MODEM].handle)
|
||||
pr_err("%s: Unable to register mpss link state\n", __func__);
|
||||
|
||||
link_state[APR_DEST_QDSP6].link_state = GLINK_LINK_STATE_DOWN;
|
||||
link_state[APR_DEST_QDSP6].handle =
|
||||
glink_register_link_state_cb(&lpass_link_info, NULL);
|
||||
if (!link_state[APR_DEST_QDSP6].handle)
|
||||
pr_err("%s: Unable to register lpass link state\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
device_initcall(apr_tal_init);
|
||||
67
drivers/soc/qcom/qdsp6v2/apr_v2.c
Normal file
67
drivers/soc/qcom/qdsp6v2/apr_v2.c
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/qdsp6v2/apr.h>
|
||||
#include <linux/qdsp6v2/apr_tal.h>
|
||||
#include <linux/qdsp6v2/dsp_debug.h>
|
||||
#include <linux/qdsp6v2/audio_notifier.h>
|
||||
|
||||
enum apr_subsys_state apr_get_subsys_state(void)
|
||||
{
|
||||
return apr_get_q6_state();
|
||||
}
|
||||
|
||||
void apr_set_subsys_state(void)
|
||||
{
|
||||
apr_set_q6_state(APR_SUBSYS_DOWN);
|
||||
apr_set_modem_state(APR_SUBSYS_UP);
|
||||
}
|
||||
|
||||
uint16_t apr_get_data_src(struct apr_hdr *hdr)
|
||||
{
|
||||
if (hdr->src_domain == APR_DOMAIN_MODEM)
|
||||
return APR_DEST_MODEM;
|
||||
else if (hdr->src_domain == APR_DOMAIN_ADSP)
|
||||
return APR_DEST_QDSP6;
|
||||
|
||||
pr_err("APR: Pkt from wrong source: %d\n", hdr->src_domain);
|
||||
return APR_DEST_MAX; /*RETURN INVALID VALUE*/
|
||||
}
|
||||
|
||||
int apr_get_dest_id(char *dest)
|
||||
{
|
||||
if (!strcmp(dest, "ADSP"))
|
||||
return APR_DEST_QDSP6;
|
||||
else
|
||||
return APR_DEST_MODEM;
|
||||
}
|
||||
|
||||
void subsys_notif_register(char *client_name, int domain,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = audio_notifier_register(client_name, domain, nb);
|
||||
if (ret < 0)
|
||||
pr_err("%s: Audio notifier register failed for domain %d ret = %d\n",
|
||||
__func__, domain, ret);
|
||||
}
|
||||
|
||||
uint16_t apr_get_reset_domain(uint16_t proc)
|
||||
{
|
||||
return proc;
|
||||
}
|
||||
65
drivers/soc/qcom/qdsp6v2/apr_v3.c
Normal file
65
drivers/soc/qcom/qdsp6v2/apr_v3.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016, 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/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/qdsp6v2/apr.h>
|
||||
#include <linux/qdsp6v2/apr_tal.h>
|
||||
#include <linux/qdsp6v2/dsp_debug.h>
|
||||
#include <linux/qdsp6v2/audio_notifier.h>
|
||||
|
||||
#define DEST_ID APR_DEST_MODEM
|
||||
|
||||
enum apr_subsys_state apr_get_subsys_state(void)
|
||||
{
|
||||
return apr_get_modem_state();
|
||||
}
|
||||
|
||||
void apr_set_subsys_state(void)
|
||||
{
|
||||
apr_set_modem_state(APR_SUBSYS_DOWN);
|
||||
}
|
||||
|
||||
uint16_t apr_get_data_src(struct apr_hdr *hdr)
|
||||
{
|
||||
return DEST_ID;
|
||||
}
|
||||
|
||||
int apr_get_dest_id(char *dest)
|
||||
{
|
||||
return DEST_ID;
|
||||
}
|
||||
|
||||
void subsys_notif_register(char *client_name, int domain,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (domain != AUDIO_NOTIFIER_MODEM_DOMAIN) {
|
||||
pr_debug("%s: Unused domain %d not registering with notifier\n",
|
||||
__func__, domain);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = audio_notifier_register(client_name, domain, nb);
|
||||
if (ret < 0)
|
||||
pr_err("%s: Audio notifier register failed for domain %d ret = %d\n",
|
||||
__func__, domain, ret);
|
||||
}
|
||||
|
||||
uint16_t apr_get_reset_domain(uint16_t proc)
|
||||
{
|
||||
return APR_DEST_QDSP6;
|
||||
}
|
||||
636
drivers/soc/qcom/qdsp6v2/audio_notifier.c
Normal file
636
drivers/soc/qcom/qdsp6v2/audio_notifier.c
Normal file
@@ -0,0 +1,636 @@
|
||||
/* 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/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/qdsp6v2/audio_pdr.h>
|
||||
#include <linux/qdsp6v2/audio_ssr.h>
|
||||
#include <linux/qdsp6v2/audio_notifier.h>
|
||||
#include <soc/qcom/scm.h>
|
||||
#include <soc/qcom/subsystem_notif.h>
|
||||
#include <soc/qcom/service-notifier.h>
|
||||
|
||||
/* Audio states internal to notifier. Client */
|
||||
/* used states defined in audio_notifier.h */
|
||||
/* for AUDIO_NOTIFIER_SERVICE_DOWN & UP */
|
||||
#define NO_SERVICE -2
|
||||
#define UNINIT_SERVICE -1
|
||||
|
||||
/*
|
||||
* Used for each client registered with audio notifier
|
||||
*/
|
||||
struct client_data {
|
||||
struct list_head list;
|
||||
/* Notifier block given by client */
|
||||
struct notifier_block *nb;
|
||||
char client_name[20];
|
||||
int service;
|
||||
int domain;
|
||||
};
|
||||
|
||||
/*
|
||||
* Used for each service and domain combination
|
||||
* Tracks information specific to the underlying
|
||||
* service.
|
||||
*/
|
||||
struct service_info {
|
||||
const char name[20];
|
||||
int domain_id;
|
||||
int state;
|
||||
void *handle;
|
||||
/* Notifier block registered to service */
|
||||
struct notifier_block *nb;
|
||||
/* Used to determine when to register and deregister service */
|
||||
int num_of_clients;
|
||||
/* List of all clients registered to the service and domain */
|
||||
struct srcu_notifier_head client_nb_list;
|
||||
};
|
||||
|
||||
static int audio_notifer_ssr_adsp_cb(struct notifier_block *this,
|
||||
unsigned long opcode, void *data);
|
||||
static int audio_notifer_ssr_modem_cb(struct notifier_block *this,
|
||||
unsigned long opcode, void *data);
|
||||
static int audio_notifer_pdr_adsp_cb(struct notifier_block *this,
|
||||
unsigned long opcode, void *data);
|
||||
|
||||
static struct notifier_block notifier_ssr_adsp_nb = {
|
||||
.notifier_call = audio_notifer_ssr_adsp_cb,
|
||||
.priority = 0,
|
||||
};
|
||||
|
||||
static struct notifier_block notifier_ssr_modem_nb = {
|
||||
.notifier_call = audio_notifer_ssr_modem_cb,
|
||||
.priority = 0,
|
||||
};
|
||||
|
||||
static struct notifier_block notifier_pdr_adsp_nb = {
|
||||
.notifier_call = audio_notifer_pdr_adsp_cb,
|
||||
.priority = 0,
|
||||
};
|
||||
|
||||
static struct service_info service_data[AUDIO_NOTIFIER_MAX_SERVICES]
|
||||
[AUDIO_NOTIFIER_MAX_DOMAINS] = {
|
||||
|
||||
{{
|
||||
.name = "SSR_ADSP",
|
||||
.domain_id = AUDIO_SSR_DOMAIN_ADSP,
|
||||
.state = AUDIO_NOTIFIER_SERVICE_DOWN,
|
||||
.nb = ¬ifier_ssr_adsp_nb
|
||||
},
|
||||
{
|
||||
.name = "SSR_MODEM",
|
||||
.domain_id = AUDIO_SSR_DOMAIN_MODEM,
|
||||
.state = AUDIO_NOTIFIER_SERVICE_DOWN,
|
||||
.nb = ¬ifier_ssr_modem_nb
|
||||
} },
|
||||
|
||||
{{
|
||||
.name = "PDR_ADSP",
|
||||
.domain_id = AUDIO_PDR_DOMAIN_ADSP,
|
||||
.state = UNINIT_SERVICE,
|
||||
.nb = ¬ifier_pdr_adsp_nb
|
||||
},
|
||||
{ /* PDR MODEM service not enabled */
|
||||
.name = "INVALID",
|
||||
.state = NO_SERVICE,
|
||||
.nb = NULL
|
||||
} }
|
||||
};
|
||||
|
||||
/* Master list of all audio notifier clients */
|
||||
struct list_head client_list;
|
||||
struct mutex notifier_mutex;
|
||||
|
||||
static int audio_notifer_get_default_service(int domain)
|
||||
{
|
||||
int service = NO_SERVICE;
|
||||
|
||||
/* initial service to connect per domain */
|
||||
switch (domain) {
|
||||
case AUDIO_NOTIFIER_ADSP_DOMAIN:
|
||||
service = AUDIO_NOTIFIER_PDR_SERVICE;
|
||||
break;
|
||||
case AUDIO_NOTIFIER_MODEM_DOMAIN:
|
||||
service = AUDIO_NOTIFIER_SSR_SERVICE;
|
||||
break;
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
static void audio_notifer_disable_service(int service)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < AUDIO_NOTIFIER_MAX_DOMAINS; i++)
|
||||
service_data[service][i].state = NO_SERVICE;
|
||||
}
|
||||
|
||||
static bool audio_notifer_is_service_enabled(int service)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < AUDIO_NOTIFIER_MAX_DOMAINS; i++)
|
||||
if (service_data[service][i].state != NO_SERVICE)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void audio_notifer_init_service(int service)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < AUDIO_NOTIFIER_MAX_DOMAINS; i++) {
|
||||
if (service_data[service][i].state == UNINIT_SERVICE)
|
||||
service_data[service][i].state =
|
||||
AUDIO_NOTIFIER_SERVICE_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static int audio_notifer_reg_service(int service, int domain)
|
||||
{
|
||||
void *handle;
|
||||
int ret = 0;
|
||||
int curr_state = AUDIO_NOTIFIER_SERVICE_DOWN;
|
||||
|
||||
switch (service) {
|
||||
case AUDIO_NOTIFIER_SSR_SERVICE:
|
||||
handle = audio_ssr_register(
|
||||
service_data[service][domain].domain_id,
|
||||
service_data[service][domain].nb);
|
||||
break;
|
||||
case AUDIO_NOTIFIER_PDR_SERVICE:
|
||||
handle = audio_pdr_service_register(
|
||||
service_data[service][domain].domain_id,
|
||||
service_data[service][domain].nb, &curr_state);
|
||||
|
||||
if (curr_state == SERVREG_NOTIF_SERVICE_STATE_UP_V01)
|
||||
curr_state = AUDIO_NOTIFIER_SERVICE_UP;
|
||||
else
|
||||
curr_state = AUDIO_NOTIFIER_SERVICE_DOWN;
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Invalid service %d\n",
|
||||
__func__, service);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
if (IS_ERR_OR_NULL(handle)) {
|
||||
pr_err("%s: handle is incorrect for service %s\n",
|
||||
__func__, service_data[service][domain].name);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
service_data[service][domain].state = curr_state;
|
||||
service_data[service][domain].handle = handle;
|
||||
|
||||
pr_info("%s: service %s is in use\n",
|
||||
__func__, service_data[service][domain].name);
|
||||
pr_debug("%s: service %s has current state %d, handle 0x%pK\n",
|
||||
__func__, service_data[service][domain].name,
|
||||
service_data[service][domain].state,
|
||||
service_data[service][domain].handle);
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int audio_notifer_dereg_service(int service, int domain)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (service) {
|
||||
case AUDIO_NOTIFIER_SSR_SERVICE:
|
||||
ret = audio_ssr_deregister(
|
||||
service_data[service][domain].handle,
|
||||
service_data[service][domain].nb);
|
||||
break;
|
||||
case AUDIO_NOTIFIER_PDR_SERVICE:
|
||||
ret = audio_pdr_service_deregister(
|
||||
service_data[service][domain].handle,
|
||||
service_data[service][domain].nb);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Invalid service %d\n",
|
||||
__func__, service);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
if (ret < 0) {
|
||||
pr_err("%s: deregister failed for service %s, ret %d\n",
|
||||
__func__, service_data[service][domain].name, ret);
|
||||
goto done;
|
||||
}
|
||||
|
||||
pr_debug("%s: service %s with handle 0x%pK deregistered\n",
|
||||
__func__, service_data[service][domain].name,
|
||||
service_data[service][domain].handle);
|
||||
|
||||
service_data[service][domain].state = AUDIO_NOTIFIER_SERVICE_DOWN;
|
||||
service_data[service][domain].handle = NULL;
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int audio_notifer_reg_client_service(struct client_data *client_data,
|
||||
int service)
|
||||
{
|
||||
int ret = 0;
|
||||
int domain = client_data->domain;
|
||||
struct audio_notifier_cb_data data;
|
||||
|
||||
switch (service) {
|
||||
case AUDIO_NOTIFIER_SSR_SERVICE:
|
||||
case AUDIO_NOTIFIER_PDR_SERVICE:
|
||||
if (service_data[service][domain].num_of_clients == 0)
|
||||
ret = audio_notifer_reg_service(service, domain);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Invalid service for client %s, service %d, domain %d\n",
|
||||
__func__, client_data->client_name, service, domain);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
pr_err("%s: service registration failed on service %s for client %s\n",
|
||||
__func__, service_data[service][domain].name,
|
||||
client_data->client_name);
|
||||
goto done;
|
||||
}
|
||||
|
||||
client_data->service = service;
|
||||
srcu_notifier_chain_register(
|
||||
&service_data[service][domain].client_nb_list,
|
||||
client_data->nb);
|
||||
service_data[service][domain].num_of_clients++;
|
||||
|
||||
pr_debug("%s: registered client %s on service %s, current state 0x%x\n",
|
||||
__func__, client_data->client_name,
|
||||
service_data[service][domain].name,
|
||||
service_data[service][domain].state);
|
||||
|
||||
/*
|
||||
* PDR registration returns current state
|
||||
* Force callback of client with current state for PDR
|
||||
*/
|
||||
if (client_data->service == AUDIO_NOTIFIER_PDR_SERVICE) {
|
||||
data.service = service;
|
||||
data.domain = domain;
|
||||
(void)client_data->nb->notifier_call(client_data->nb,
|
||||
service_data[service][domain].state, &data);
|
||||
}
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int audio_notifer_reg_client(struct client_data *client_data)
|
||||
{
|
||||
int ret = 0;
|
||||
int service;
|
||||
int domain = client_data->domain;
|
||||
|
||||
service = audio_notifer_get_default_service(domain);
|
||||
if (service < 0) {
|
||||
pr_err("%s: service %d is incorrect\n", __func__, service);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Search through services to find a valid one to register client on. */
|
||||
for (; service >= 0; service--) {
|
||||
/* If a service is not initialized, wait for it to come up. */
|
||||
if (service_data[service][domain].state == UNINIT_SERVICE)
|
||||
goto done;
|
||||
/* Skip unsupported service and domain combinations. */
|
||||
if (service_data[service][domain].state < 0)
|
||||
continue;
|
||||
/* Only register clients who have not acquired a service. */
|
||||
if (client_data->service != NO_SERVICE)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Only register clients, who have not acquired a service, on
|
||||
* the best available service for their domain. Uninitialized
|
||||
* services will try to register all of their clients after
|
||||
* they initialize correctly or will disable their service and
|
||||
* register clients on the next best avaialable service.
|
||||
*/
|
||||
pr_debug("%s: register client %s on service %s",
|
||||
__func__, client_data->client_name,
|
||||
service_data[service][domain].name);
|
||||
|
||||
ret = audio_notifer_reg_client_service(client_data, service);
|
||||
if (ret < 0)
|
||||
pr_err("%s: client %s failed to register on service %s",
|
||||
__func__, client_data->client_name,
|
||||
service_data[service][domain].name);
|
||||
}
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int audio_notifer_dereg_client(struct client_data *client_data)
|
||||
{
|
||||
int ret = 0;
|
||||
int service = client_data->service;
|
||||
int domain = client_data->domain;
|
||||
|
||||
switch (client_data->service) {
|
||||
case AUDIO_NOTIFIER_SSR_SERVICE:
|
||||
case AUDIO_NOTIFIER_PDR_SERVICE:
|
||||
if (service_data[service][domain].num_of_clients == 1)
|
||||
ret = audio_notifer_dereg_service(service, domain);
|
||||
break;
|
||||
case NO_SERVICE:
|
||||
goto done;
|
||||
default:
|
||||
pr_err("%s: Invalid service for client %s, service %d\n",
|
||||
__func__, client_data->client_name,
|
||||
client_data->service);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
pr_err("%s: deregister failed for client %s on service %s, ret %d\n",
|
||||
__func__, client_data->client_name,
|
||||
service_data[service][domain].name, ret);
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = srcu_notifier_chain_unregister(&service_data[service][domain].
|
||||
client_nb_list, client_data->nb);
|
||||
if (ret < 0) {
|
||||
pr_err("%s: srcu_notifier_chain_unregister failed, ret %d\n",
|
||||
__func__, ret);
|
||||
goto done;
|
||||
}
|
||||
|
||||
pr_debug("%s: deregistered client %s on service %s\n",
|
||||
__func__, client_data->client_name,
|
||||
service_data[service][domain].name);
|
||||
|
||||
client_data->service = NO_SERVICE;
|
||||
if (service_data[service][domain].num_of_clients > 0)
|
||||
service_data[service][domain].num_of_clients--;
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void audio_notifer_reg_all_clients(void)
|
||||
{
|
||||
struct list_head *ptr, *next;
|
||||
struct client_data *client_data;
|
||||
int ret;
|
||||
|
||||
list_for_each_safe(ptr, next, &client_list) {
|
||||
client_data = list_entry(ptr, struct client_data, list);
|
||||
|
||||
ret = audio_notifer_reg_client(client_data);
|
||||
if (ret < 0)
|
||||
pr_err("%s: audio_notifer_reg_client failed for client %s, ret %d\n",
|
||||
__func__, client_data->client_name,
|
||||
ret);
|
||||
}
|
||||
}
|
||||
|
||||
static int audio_notifer_pdr_callback(struct notifier_block *this,
|
||||
unsigned long opcode, void *data)
|
||||
{
|
||||
pr_debug("%s: Audio PDR framework state 0x%lx\n",
|
||||
__func__, opcode);
|
||||
mutex_lock(¬ifier_mutex);
|
||||
if (opcode == AUDIO_PDR_FRAMEWORK_DOWN)
|
||||
audio_notifer_disable_service(AUDIO_NOTIFIER_PDR_SERVICE);
|
||||
else
|
||||
audio_notifer_init_service(AUDIO_NOTIFIER_PDR_SERVICE);
|
||||
|
||||
audio_notifer_reg_all_clients();
|
||||
mutex_unlock(¬ifier_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct notifier_block pdr_nb = {
|
||||
.notifier_call = audio_notifer_pdr_callback,
|
||||
.priority = 0,
|
||||
};
|
||||
|
||||
static int audio_notifer_convert_opcode(unsigned long opcode,
|
||||
unsigned long *notifier_opcode)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (opcode) {
|
||||
case SUBSYS_BEFORE_SHUTDOWN:
|
||||
case SERVREG_NOTIF_SERVICE_STATE_DOWN_V01:
|
||||
*notifier_opcode = AUDIO_NOTIFIER_SERVICE_DOWN;
|
||||
break;
|
||||
case SUBSYS_AFTER_POWERUP:
|
||||
case SERVREG_NOTIF_SERVICE_STATE_UP_V01:
|
||||
*notifier_opcode = AUDIO_NOTIFIER_SERVICE_UP;
|
||||
break;
|
||||
default:
|
||||
pr_debug("%s: Unused opcode 0x%lx\n", __func__, opcode);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int audio_notifer_service_cb(unsigned long opcode,
|
||||
int service, int domain)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long notifier_opcode;
|
||||
struct audio_notifier_cb_data data;
|
||||
|
||||
if (audio_notifer_convert_opcode(opcode, ¬ifier_opcode) < 0)
|
||||
goto done;
|
||||
|
||||
data.service = service;
|
||||
data.domain = domain;
|
||||
|
||||
pr_debug("%s: service %s, opcode 0x%lx\n",
|
||||
__func__, service_data[service][domain].name, notifier_opcode);
|
||||
|
||||
mutex_lock(¬ifier_mutex);
|
||||
|
||||
service_data[service][domain].state = notifier_opcode;
|
||||
ret = srcu_notifier_call_chain(&service_data[service][domain].
|
||||
client_nb_list, notifier_opcode, &data);
|
||||
if (ret < 0)
|
||||
pr_err("%s: srcu_notifier_call_chain returned %d, service %s, opcode 0x%lx\n",
|
||||
__func__, ret, service_data[service][domain].name,
|
||||
notifier_opcode);
|
||||
|
||||
mutex_unlock(¬ifier_mutex);
|
||||
done:
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int audio_notifer_pdr_adsp_cb(struct notifier_block *this,
|
||||
unsigned long opcode, void *data)
|
||||
{
|
||||
return audio_notifer_service_cb(opcode,
|
||||
AUDIO_NOTIFIER_PDR_SERVICE,
|
||||
AUDIO_NOTIFIER_ADSP_DOMAIN);
|
||||
}
|
||||
|
||||
static int audio_notifer_ssr_adsp_cb(struct notifier_block *this,
|
||||
unsigned long opcode, void *data)
|
||||
{
|
||||
if (opcode == SUBSYS_BEFORE_SHUTDOWN)
|
||||
audio_ssr_send_nmi(data);
|
||||
|
||||
return audio_notifer_service_cb(opcode,
|
||||
AUDIO_NOTIFIER_SSR_SERVICE,
|
||||
AUDIO_NOTIFIER_ADSP_DOMAIN);
|
||||
}
|
||||
|
||||
static int audio_notifer_ssr_modem_cb(struct notifier_block *this,
|
||||
unsigned long opcode, void *data)
|
||||
{
|
||||
return audio_notifer_service_cb(opcode,
|
||||
AUDIO_NOTIFIER_SSR_SERVICE,
|
||||
AUDIO_NOTIFIER_MODEM_DOMAIN);
|
||||
}
|
||||
|
||||
int audio_notifier_deregister(char *client_name)
|
||||
{
|
||||
int ret = 0;
|
||||
int ret2;
|
||||
struct list_head *ptr, *next;
|
||||
struct client_data *client_data;
|
||||
|
||||
if (client_name == NULL) {
|
||||
pr_err("%s: client_name is NULL\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
mutex_lock(¬ifier_mutex);
|
||||
list_for_each_safe(ptr, next, &client_list) {
|
||||
client_data = list_entry(ptr, struct client_data, list);
|
||||
if (!strcmp(client_name, client_data->client_name)) {
|
||||
ret2 = audio_notifer_dereg_client(client_data);
|
||||
if (ret2 < 0) {
|
||||
pr_err("%s: audio_notifer_dereg_client failed, ret %d\n, service %d, domain %d",
|
||||
__func__, ret2, client_data->service,
|
||||
client_data->domain);
|
||||
ret = ret2;
|
||||
continue;
|
||||
}
|
||||
list_del(&client_data->list);
|
||||
kfree(client_data);
|
||||
}
|
||||
}
|
||||
mutex_unlock(¬ifier_mutex);
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(audio_notifier_deregister);
|
||||
|
||||
int audio_notifier_register(char *client_name, int domain,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
struct client_data *client_data;
|
||||
|
||||
if (client_name == NULL) {
|
||||
pr_err("%s: client_name is NULL\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
} else if (nb == NULL) {
|
||||
pr_err("%s: Notifier block is NULL\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
client_data = kmalloc(sizeof(*client_data), GFP_KERNEL);
|
||||
if (client_data == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
INIT_LIST_HEAD(&client_data->list);
|
||||
client_data->nb = nb;
|
||||
strlcpy(client_data->client_name, client_name,
|
||||
sizeof(client_data->client_name));
|
||||
client_data->service = NO_SERVICE;
|
||||
client_data->domain = domain;
|
||||
|
||||
mutex_lock(¬ifier_mutex);
|
||||
ret = audio_notifer_reg_client(client_data);
|
||||
if (ret < 0) {
|
||||
mutex_unlock(¬ifier_mutex);
|
||||
pr_err("%s: audio_notifer_reg_client for client %s failed ret = %d\n",
|
||||
__func__, client_data->client_name,
|
||||
ret);
|
||||
kfree(client_data);
|
||||
goto done;
|
||||
}
|
||||
list_add_tail(&client_data->list, &client_list);
|
||||
mutex_unlock(¬ifier_mutex);
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(audio_notifier_register);
|
||||
|
||||
static int __init audio_notifier_subsys_init(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
mutex_init(¬ifier_mutex);
|
||||
INIT_LIST_HEAD(&client_list);
|
||||
for (i = 0; i < AUDIO_NOTIFIER_MAX_SERVICES; i++) {
|
||||
for (j = 0; j < AUDIO_NOTIFIER_MAX_DOMAINS; j++) {
|
||||
if (service_data[i][j].state <= NO_SERVICE)
|
||||
continue;
|
||||
|
||||
srcu_init_notifier_head(
|
||||
&service_data[i][j].client_nb_list);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
subsys_initcall(audio_notifier_subsys_init);
|
||||
|
||||
static int __init audio_notifier_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = audio_pdr_register(&pdr_nb);
|
||||
if (ret < 0) {
|
||||
pr_debug("%s: PDR register failed, ret = %d, disable service\n",
|
||||
__func__, ret);
|
||||
audio_notifer_disable_service(AUDIO_NOTIFIER_PDR_SERVICE);
|
||||
}
|
||||
|
||||
/* Do not return error since PDR enablement is not critical */
|
||||
return 0;
|
||||
}
|
||||
module_init(audio_notifier_init);
|
||||
|
||||
static int __init audio_notifier_late_init(void)
|
||||
{
|
||||
/*
|
||||
* If pdr registration failed, register clients on next service
|
||||
* Do in late init to ensure that SSR subsystem is initialized
|
||||
*/
|
||||
mutex_lock(¬ifier_mutex);
|
||||
if (!audio_notifer_is_service_enabled(AUDIO_NOTIFIER_PDR_SERVICE))
|
||||
audio_notifer_reg_all_clients();
|
||||
|
||||
mutex_unlock(¬ifier_mutex);
|
||||
return 0;
|
||||
}
|
||||
late_initcall(audio_notifier_late_init);
|
||||
147
drivers/soc/qcom/qdsp6v2/audio_pdr.c
Normal file
147
drivers/soc/qcom/qdsp6v2/audio_pdr.c
Normal file
@@ -0,0 +1,147 @@
|
||||
/* 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/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/qdsp6v2/audio_pdr.h>
|
||||
#include <soc/qcom/service-locator.h>
|
||||
#include <soc/qcom/service-notifier.h>
|
||||
|
||||
static struct pd_qmi_client_data audio_pdr_services[AUDIO_PDR_DOMAIN_MAX] = {
|
||||
{ /* AUDIO_PDR_DOMAIN_ADSP */
|
||||
.client_name = "audio_pdr_adsp",
|
||||
.service_name = "avs/audio"
|
||||
}
|
||||
};
|
||||
|
||||
struct srcu_notifier_head audio_pdr_cb_list;
|
||||
|
||||
static int audio_pdr_locator_callback(struct notifier_block *this,
|
||||
unsigned long opcode, void *data)
|
||||
{
|
||||
unsigned long pdr_state = AUDIO_PDR_FRAMEWORK_DOWN;
|
||||
|
||||
if (opcode == LOCATOR_DOWN) {
|
||||
pr_debug("%s: Service %s is down!", __func__,
|
||||
audio_pdr_services[AUDIO_PDR_DOMAIN_ADSP].
|
||||
service_name);
|
||||
goto done;
|
||||
}
|
||||
|
||||
memcpy(&audio_pdr_services, data,
|
||||
sizeof(audio_pdr_services[AUDIO_PDR_DOMAIN_ADSP]));
|
||||
if (audio_pdr_services[AUDIO_PDR_DOMAIN_ADSP].total_domains == 1) {
|
||||
pr_debug("%s: Service %s, returned total domains %d, ",
|
||||
__func__,
|
||||
audio_pdr_services[AUDIO_PDR_DOMAIN_ADSP].service_name,
|
||||
audio_pdr_services[AUDIO_PDR_DOMAIN_ADSP].
|
||||
total_domains);
|
||||
pdr_state = AUDIO_PDR_FRAMEWORK_UP;
|
||||
goto done;
|
||||
} else
|
||||
pr_err("%s: Service %s returned invalid total domains %d",
|
||||
__func__,
|
||||
audio_pdr_services[AUDIO_PDR_DOMAIN_ADSP].service_name,
|
||||
audio_pdr_services[AUDIO_PDR_DOMAIN_ADSP].
|
||||
total_domains);
|
||||
done:
|
||||
srcu_notifier_call_chain(&audio_pdr_cb_list, pdr_state, NULL);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block audio_pdr_locator_nb = {
|
||||
.notifier_call = audio_pdr_locator_callback,
|
||||
.priority = 0,
|
||||
};
|
||||
|
||||
int audio_pdr_register(struct notifier_block *nb)
|
||||
{
|
||||
if (nb == NULL) {
|
||||
pr_err("%s: Notifier block is NULL\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
return srcu_notifier_chain_register(&audio_pdr_cb_list, nb);
|
||||
}
|
||||
EXPORT_SYMBOL(audio_pdr_register);
|
||||
|
||||
void *audio_pdr_service_register(int domain_id,
|
||||
struct notifier_block *nb, int *curr_state)
|
||||
{
|
||||
void *handle;
|
||||
|
||||
if ((domain_id < 0) ||
|
||||
(domain_id >= AUDIO_PDR_DOMAIN_MAX)) {
|
||||
pr_err("%s: Invalid service ID %d\n", __func__, domain_id);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
handle = service_notif_register_notifier(
|
||||
audio_pdr_services[domain_id].domain_list[0].name,
|
||||
audio_pdr_services[domain_id].domain_list[0].instance_id,
|
||||
nb, curr_state);
|
||||
if (IS_ERR_OR_NULL(handle)) {
|
||||
pr_err("%s: Failed to register for service %s, instance %d\n",
|
||||
__func__,
|
||||
audio_pdr_services[domain_id].domain_list[0].name,
|
||||
audio_pdr_services[domain_id].domain_list[0].
|
||||
instance_id);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
EXPORT_SYMBOL(audio_pdr_service_register);
|
||||
|
||||
int audio_pdr_service_deregister(void *service_handle,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (service_handle == NULL) {
|
||||
pr_err("%s: service handle is NULL\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = service_notif_unregister_notifier(
|
||||
service_handle, nb);
|
||||
if (ret < 0)
|
||||
pr_err("%s: Failed to deregister service ret %d\n",
|
||||
__func__, ret);
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(audio_pdr_service_deregister);
|
||||
|
||||
static int __init audio_pdr_subsys_init(void)
|
||||
{
|
||||
srcu_init_notifier_head(&audio_pdr_cb_list);
|
||||
return 0;
|
||||
}
|
||||
subsys_initcall(audio_pdr_subsys_init);
|
||||
|
||||
static int __init audio_pdr_late_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = get_service_location(
|
||||
audio_pdr_services[AUDIO_PDR_DOMAIN_ADSP].client_name,
|
||||
audio_pdr_services[AUDIO_PDR_DOMAIN_ADSP].service_name,
|
||||
&audio_pdr_locator_nb);
|
||||
if (ret < 0) {
|
||||
pr_err("%s get_service_location failed ret %d\n",
|
||||
__func__, ret);
|
||||
srcu_notifier_call_chain(&audio_pdr_cb_list,
|
||||
AUDIO_PDR_FRAMEWORK_DOWN, NULL);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
late_initcall(audio_pdr_late_init);
|
||||
66
drivers/soc/qcom/qdsp6v2/audio_ssr.c
Normal file
66
drivers/soc/qcom/qdsp6v2/audio_ssr.c
Normal file
@@ -0,0 +1,66 @@
|
||||
/* Copyright (c) 2016, 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/qdsp6v2/audio_ssr.h>
|
||||
#include <soc/qcom/scm.h>
|
||||
#include <soc/qcom/subsystem_restart.h>
|
||||
#include <soc/qcom/subsystem_notif.h>
|
||||
|
||||
#define SCM_Q6_NMI_CMD 0x1
|
||||
|
||||
static char *audio_ssr_domains[] = {
|
||||
"adsp",
|
||||
"modem"
|
||||
};
|
||||
|
||||
void *audio_ssr_register(int domain_id, struct notifier_block *nb)
|
||||
{
|
||||
if ((domain_id < 0) ||
|
||||
(domain_id >= AUDIO_SSR_DOMAIN_MAX)) {
|
||||
pr_err("%s: Invalid service ID %d\n", __func__, domain_id);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
return subsys_notif_register_notifier(
|
||||
audio_ssr_domains[domain_id], nb);
|
||||
}
|
||||
EXPORT_SYMBOL(audio_ssr_register);
|
||||
|
||||
int audio_ssr_deregister(void *handle, struct notifier_block *nb)
|
||||
{
|
||||
return subsys_notif_unregister_notifier(handle, nb);
|
||||
}
|
||||
EXPORT_SYMBOL(audio_ssr_deregister);
|
||||
|
||||
void audio_ssr_send_nmi(void *ssr_cb_data)
|
||||
{
|
||||
struct notif_data *data = (struct notif_data *)ssr_cb_data;
|
||||
struct scm_desc desc;
|
||||
|
||||
if (data && data->crashed) {
|
||||
/* Send NMI to QDSP6 via an SCM call. */
|
||||
if (!is_scm_armv8()) {
|
||||
scm_call_atomic1(SCM_SVC_UTIL,
|
||||
SCM_Q6_NMI_CMD, 0x1);
|
||||
} else {
|
||||
desc.args[0] = 0x1;
|
||||
desc.arginfo = SCM_ARGS(1);
|
||||
scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_UTIL,
|
||||
SCM_Q6_NMI_CMD), &desc);
|
||||
}
|
||||
/* The write should go through before q6 is shutdown */
|
||||
mb();
|
||||
pr_debug("%s: Q6 NMI was sent.\n", __func__);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(audio_ssr_send_nmi);
|
||||
280
drivers/soc/qcom/qdsp6v2/cdsp-loader.c
Normal file
280
drivers/soc/qcom/qdsp6v2/cdsp-loader.c
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2014, 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/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <soc/qcom/subsystem_restart.h>
|
||||
|
||||
#define BOOT_CMD 1
|
||||
#define IMAGE_UNLOAD_CMD 0
|
||||
|
||||
#define CDSP_SUBSYS_DOWN 0
|
||||
#define CDSP_SUBSYS_LOADED 1
|
||||
|
||||
static ssize_t cdsp_boot_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
|
||||
struct cdsp_loader_private {
|
||||
void *pil_h;
|
||||
struct kobject *boot_cdsp_obj;
|
||||
struct attribute_group *attr_group;
|
||||
};
|
||||
|
||||
static struct kobj_attribute cdsp_boot_attribute =
|
||||
__ATTR(boot, 0220, NULL, cdsp_boot_store);
|
||||
|
||||
static struct attribute *attrs[] = {
|
||||
&cdsp_boot_attribute.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static u32 cdsp_state = CDSP_SUBSYS_DOWN;
|
||||
static struct platform_device *cdsp_private;
|
||||
static struct work_struct cdsp_ldr_work;
|
||||
static void cdsp_loader_unload(struct platform_device *pdev);
|
||||
|
||||
static void cdsp_load_fw(struct work_struct *cdsp_ldr_work)
|
||||
{
|
||||
struct platform_device *pdev = cdsp_private;
|
||||
struct cdsp_loader_private *priv = NULL;
|
||||
|
||||
int rc = 0;
|
||||
const char *img_name;
|
||||
|
||||
if (!pdev) {
|
||||
dev_err(&pdev->dev, "%s: Platform device null\n", __func__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!pdev->dev.of_node) {
|
||||
dev_err(&pdev->dev,
|
||||
"%s: Device tree information missing\n", __func__);
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rc = of_property_read_string(pdev->dev.of_node,
|
||||
"qcom,proc-img-to-load",
|
||||
&img_name);
|
||||
if (rc)
|
||||
goto fail;
|
||||
|
||||
if (!strcmp(img_name, "cdsp")) {
|
||||
/* cdsp_state always returns "0".*/
|
||||
if (cdsp_state == CDSP_SUBSYS_DOWN) {
|
||||
priv = platform_get_drvdata(pdev);
|
||||
if (!priv) {
|
||||
dev_err(&pdev->dev,
|
||||
" %s: Private data get failed\n", __func__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->pil_h = subsystem_get("cdsp");
|
||||
if (IS_ERR(priv->pil_h)) {
|
||||
dev_err(&pdev->dev, "%s: pil get failed,\n",
|
||||
__func__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Set the state of the CDSP.*/
|
||||
cdsp_state = CDSP_SUBSYS_LOADED;
|
||||
} else if (cdsp_state == CDSP_SUBSYS_LOADED) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"%s: CDSP state = %x\n", __func__, cdsp_state);
|
||||
}
|
||||
|
||||
dev_dbg(&pdev->dev, "%s: CDSP image is loaded\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
fail:
|
||||
dev_err(&pdev->dev, "%s: CDSP image loading failed\n", __func__);
|
||||
}
|
||||
|
||||
static void cdsp_loader_do(struct platform_device *pdev)
|
||||
{
|
||||
schedule_work(&cdsp_ldr_work);
|
||||
}
|
||||
|
||||
static ssize_t cdsp_boot_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
int boot = 0, ret = 0;
|
||||
|
||||
ret = sscanf(buf, "%du", &boot);
|
||||
|
||||
if (ret != 1)
|
||||
pr_debug("%s: invalid arguments for cdsp_loader.\n", __func__);
|
||||
|
||||
if (boot == BOOT_CMD) {
|
||||
pr_debug("%s: going to call cdsp_loader_do\n", __func__);
|
||||
cdsp_loader_do(cdsp_private);
|
||||
} else if (boot == IMAGE_UNLOAD_CMD) {
|
||||
pr_debug("%s: going to call cdsp_unloader\n", __func__);
|
||||
cdsp_loader_unload(cdsp_private);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void cdsp_loader_unload(struct platform_device *pdev)
|
||||
{
|
||||
struct cdsp_loader_private *priv = NULL;
|
||||
|
||||
priv = platform_get_drvdata(pdev);
|
||||
|
||||
if (!priv)
|
||||
return;
|
||||
|
||||
if (priv->pil_h) {
|
||||
dev_dbg(&pdev->dev, "%s: calling subsystem put\n", __func__);
|
||||
subsystem_put(priv->pil_h);
|
||||
priv->pil_h = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int cdsp_loader_init_sysfs(struct platform_device *pdev)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
struct cdsp_loader_private *priv = NULL;
|
||||
|
||||
cdsp_private = NULL;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
ret = -ENOMEM;
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
priv->pil_h = NULL;
|
||||
priv->boot_cdsp_obj = NULL;
|
||||
priv->attr_group = devm_kzalloc(&pdev->dev,
|
||||
sizeof(*(priv->attr_group)),
|
||||
GFP_KERNEL);
|
||||
if (!priv->attr_group) {
|
||||
dev_err(&pdev->dev, "%s: malloc attr_group failed\n",
|
||||
__func__);
|
||||
ret = -ENOMEM;
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
priv->attr_group->attrs = attrs;
|
||||
|
||||
priv->boot_cdsp_obj = kobject_create_and_add("boot_cdsp", kernel_kobj);
|
||||
if (!priv->boot_cdsp_obj) {
|
||||
dev_err(&pdev->dev, "%s: sysfs create and add failed\n",
|
||||
__func__);
|
||||
ret = -ENOMEM;
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
ret = sysfs_create_group(priv->boot_cdsp_obj, priv->attr_group);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s: sysfs create group failed %d\n",
|
||||
__func__, ret);
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
cdsp_private = pdev;
|
||||
|
||||
return 0;
|
||||
|
||||
error_return:
|
||||
|
||||
if (priv->boot_cdsp_obj) {
|
||||
kobject_del(priv->boot_cdsp_obj);
|
||||
priv->boot_cdsp_obj = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cdsp_loader_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cdsp_loader_private *priv = NULL;
|
||||
|
||||
priv = platform_get_drvdata(pdev);
|
||||
|
||||
if (!priv)
|
||||
return 0;
|
||||
|
||||
if (priv->pil_h) {
|
||||
subsystem_put(priv->pil_h);
|
||||
priv->pil_h = NULL;
|
||||
}
|
||||
|
||||
if (priv->boot_cdsp_obj) {
|
||||
sysfs_remove_group(priv->boot_cdsp_obj, priv->attr_group);
|
||||
kobject_del(priv->boot_cdsp_obj);
|
||||
priv->boot_cdsp_obj = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdsp_loader_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = cdsp_loader_init_sysfs(pdev);
|
||||
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev, "%s: Error in initing sysfs\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
INIT_WORK(&cdsp_ldr_work, cdsp_load_fw);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id cdsp_loader_dt_match[] = {
|
||||
{ .compatible = "qcom,cdsp-loader" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, cdsp_loader_dt_match);
|
||||
|
||||
static struct platform_driver cdsp_loader_driver = {
|
||||
.driver = {
|
||||
.name = "cdsp-loader",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = cdsp_loader_dt_match,
|
||||
},
|
||||
.probe = cdsp_loader_probe,
|
||||
.remove = cdsp_loader_remove,
|
||||
};
|
||||
|
||||
static int __init cdsp_loader_init(void)
|
||||
{
|
||||
return platform_driver_register(&cdsp_loader_driver);
|
||||
}
|
||||
module_init(cdsp_loader_init);
|
||||
|
||||
static void __exit cdsp_loader_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&cdsp_loader_driver);
|
||||
}
|
||||
module_exit(cdsp_loader_exit);
|
||||
|
||||
MODULE_DESCRIPTION("CDSP Loader module");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
853
drivers/soc/qcom/qdsp6v2/msm_audio_ion.c
Normal file
853
drivers/soc/qcom/qdsp6v2/msm_audio_ion.c
Normal file
@@ -0,0 +1,853 @@
|
||||
/*
|
||||
* Copyright (c) 2013-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/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/qdsp6v2/apr.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/msm_audio_ion.h>
|
||||
#include <linux/export.h>
|
||||
#include <asm/dma-iommu.h>
|
||||
|
||||
#define MSM_AUDIO_ION_PROBED (1 << 0)
|
||||
|
||||
#define MSM_AUDIO_ION_PHYS_ADDR(alloc_data) \
|
||||
alloc_data->table->sgl->dma_address
|
||||
|
||||
#define MSM_AUDIO_ION_VA_START 0x10000000
|
||||
#define MSM_AUDIO_ION_VA_LEN 0x0FFFFFFF
|
||||
|
||||
#define MSM_AUDIO_SMMU_SID_OFFSET 32
|
||||
|
||||
struct msm_audio_ion_private {
|
||||
bool smmu_enabled;
|
||||
bool audioheap_enabled;
|
||||
struct device *cb_dev;
|
||||
struct dma_iommu_mapping *mapping;
|
||||
u8 device_status;
|
||||
struct list_head alloc_list;
|
||||
struct mutex list_mutex;
|
||||
u64 smmu_sid_bits;
|
||||
u32 smmu_version;
|
||||
};
|
||||
|
||||
struct msm_audio_alloc_data {
|
||||
struct ion_client *client;
|
||||
struct ion_handle *handle;
|
||||
size_t len;
|
||||
struct dma_buf *dma_buf;
|
||||
struct dma_buf_attachment *attach;
|
||||
struct sg_table *table;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
static struct msm_audio_ion_private msm_audio_ion_data = {0,};
|
||||
|
||||
static int msm_audio_ion_get_phys(struct ion_client *client,
|
||||
struct ion_handle *handle,
|
||||
ion_phys_addr_t *addr, size_t *len);
|
||||
|
||||
static int msm_audio_dma_buf_map(struct ion_client *client,
|
||||
struct ion_handle *handle,
|
||||
ion_phys_addr_t *addr, size_t *len);
|
||||
|
||||
static int msm_audio_dma_buf_unmap(struct ion_client *client,
|
||||
struct ion_handle *handle);
|
||||
|
||||
static void msm_audio_ion_add_allocation(
|
||||
struct msm_audio_ion_private *msm_audio_ion_data,
|
||||
struct msm_audio_alloc_data *alloc_data)
|
||||
{
|
||||
/*
|
||||
* Since these APIs can be invoked by multiple
|
||||
* clients, there is need to make sure the list
|
||||
* of allocations is always protected
|
||||
*/
|
||||
mutex_lock(&(msm_audio_ion_data->list_mutex));
|
||||
list_add_tail(&(alloc_data->list),
|
||||
&(msm_audio_ion_data->alloc_list));
|
||||
mutex_unlock(&(msm_audio_ion_data->list_mutex));
|
||||
}
|
||||
|
||||
int msm_audio_ion_alloc(const char *name, struct ion_client **client,
|
||||
struct ion_handle **handle, size_t bufsz,
|
||||
ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr)
|
||||
{
|
||||
int rc = -EINVAL;
|
||||
unsigned long err_ion_ptr = 0;
|
||||
|
||||
if ((msm_audio_ion_data.smmu_enabled == true) &&
|
||||
!(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) {
|
||||
pr_debug("%s:probe is not done, deferred\n", __func__);
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
if (!name || !client || !handle || !paddr || !vaddr
|
||||
|| !bufsz || !pa_len) {
|
||||
pr_err("%s: Invalid params\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
*client = msm_audio_ion_client_create(name);
|
||||
if (IS_ERR_OR_NULL((void *)(*client))) {
|
||||
pr_err("%s: ION create client for AUDIO failed\n", __func__);
|
||||
goto err;
|
||||
}
|
||||
|
||||
*handle = ion_alloc(*client, bufsz, SZ_4K,
|
||||
ION_HEAP(ION_AUDIO_HEAP_ID), 0);
|
||||
if (IS_ERR_OR_NULL((void *) (*handle))) {
|
||||
if (msm_audio_ion_data.smmu_enabled == true) {
|
||||
pr_debug("system heap is used");
|
||||
msm_audio_ion_data.audioheap_enabled = 0;
|
||||
*handle = ion_alloc(*client, bufsz, SZ_4K,
|
||||
ION_HEAP(ION_SYSTEM_HEAP_ID), 0);
|
||||
}
|
||||
if (IS_ERR_OR_NULL((void *) (*handle))) {
|
||||
if (IS_ERR((void *)(*handle)))
|
||||
err_ion_ptr = PTR_ERR((int *)(*handle));
|
||||
pr_err("%s:ION alloc fail err ptr=%ld, smmu_enabled=%d\n",
|
||||
__func__, err_ion_ptr, msm_audio_ion_data.smmu_enabled);
|
||||
rc = -ENOMEM;
|
||||
goto err_ion_client;
|
||||
}
|
||||
} else {
|
||||
pr_debug("audio heap is used");
|
||||
msm_audio_ion_data.audioheap_enabled = 1;
|
||||
}
|
||||
|
||||
rc = msm_audio_ion_get_phys(*client, *handle, paddr, pa_len);
|
||||
if (rc) {
|
||||
pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n",
|
||||
__func__, rc);
|
||||
goto err_ion_handle;
|
||||
}
|
||||
|
||||
*vaddr = ion_map_kernel(*client, *handle);
|
||||
if (IS_ERR_OR_NULL((void *)*vaddr)) {
|
||||
pr_err("%s: ION memory mapping for AUDIO failed\n", __func__);
|
||||
goto err_ion_handle;
|
||||
}
|
||||
pr_debug("%s: mapped address = %pK, size=%zd\n", __func__,
|
||||
*vaddr, bufsz);
|
||||
|
||||
if (bufsz != 0) {
|
||||
pr_debug("%s: memset to 0 %pK %zd\n", __func__, *vaddr, bufsz);
|
||||
memset((void *)*vaddr, 0, bufsz);
|
||||
}
|
||||
|
||||
return rc;
|
||||
|
||||
err_ion_handle:
|
||||
ion_free(*client, *handle);
|
||||
err_ion_client:
|
||||
msm_audio_ion_client_destroy(*client);
|
||||
*handle = NULL;
|
||||
*client = NULL;
|
||||
err:
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_audio_ion_alloc);
|
||||
|
||||
int msm_audio_ion_import(const char *name, struct ion_client **client,
|
||||
struct ion_handle **handle, int fd,
|
||||
unsigned long *ionflag, size_t bufsz,
|
||||
ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if ((msm_audio_ion_data.smmu_enabled == true) &&
|
||||
!(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) {
|
||||
pr_debug("%s:probe is not done, deferred\n", __func__);
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
if (!name || !client || !handle || !paddr || !vaddr || !pa_len) {
|
||||
pr_err("%s: Invalid params\n", __func__);
|
||||
rc = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
*client = msm_audio_ion_client_create(name);
|
||||
if (IS_ERR_OR_NULL((void *)(*client))) {
|
||||
pr_err("%s: ION create client for AUDIO failed\n", __func__);
|
||||
rc = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* name should be audio_acdb_client or Audio_Dec_Client,
|
||||
* bufsz should be 0 and fd shouldn't be 0 as of now
|
||||
*/
|
||||
*handle = ion_import_dma_buf_fd(*client, fd);
|
||||
pr_debug("%s: DMA Buf name=%s, fd=%d handle=%pK\n", __func__,
|
||||
name, fd, *handle);
|
||||
if (IS_ERR_OR_NULL((void *) (*handle))) {
|
||||
pr_err("%s: ion import dma buffer failed\n",
|
||||
__func__);
|
||||
rc = -EINVAL;
|
||||
goto err_destroy_client;
|
||||
}
|
||||
|
||||
if (ionflag != NULL) {
|
||||
rc = ion_handle_get_flags(*client, *handle, ionflag);
|
||||
if (rc) {
|
||||
pr_err("%s: could not get flags for the handle\n",
|
||||
__func__);
|
||||
goto err_ion_handle;
|
||||
}
|
||||
}
|
||||
|
||||
rc = msm_audio_ion_get_phys(*client, *handle, paddr, pa_len);
|
||||
if (rc) {
|
||||
pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n",
|
||||
__func__, rc);
|
||||
goto err_ion_handle;
|
||||
}
|
||||
|
||||
*vaddr = ion_map_kernel(*client, *handle);
|
||||
if (IS_ERR_OR_NULL((void *)*vaddr)) {
|
||||
pr_err("%s: ION memory mapping for AUDIO failed\n", __func__);
|
||||
rc = -ENOMEM;
|
||||
goto err_ion_handle;
|
||||
}
|
||||
pr_debug("%s: mapped address = %pK, size=%zd\n", __func__,
|
||||
*vaddr, bufsz);
|
||||
|
||||
return 0;
|
||||
|
||||
err_ion_handle:
|
||||
ion_free(*client, *handle);
|
||||
err_destroy_client:
|
||||
msm_audio_ion_client_destroy(*client);
|
||||
*client = NULL;
|
||||
*handle = NULL;
|
||||
err:
|
||||
return rc;
|
||||
}
|
||||
|
||||
int msm_audio_ion_free(struct ion_client *client, struct ion_handle *handle)
|
||||
{
|
||||
if (!client || !handle) {
|
||||
pr_err("%s Invalid params\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (msm_audio_ion_data.smmu_enabled)
|
||||
msm_audio_dma_buf_unmap(client, handle);
|
||||
|
||||
ion_unmap_kernel(client, handle);
|
||||
|
||||
ion_free(client, handle);
|
||||
msm_audio_ion_client_destroy(client);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_audio_ion_free);
|
||||
|
||||
int msm_audio_ion_mmap(struct audio_buffer *ab,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct sg_table *table;
|
||||
unsigned long addr = vma->vm_start;
|
||||
unsigned long offset = vma->vm_pgoff * PAGE_SIZE;
|
||||
struct scatterlist *sg;
|
||||
unsigned int i;
|
||||
struct page *page;
|
||||
int ret;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
table = ion_sg_table(ab->client, ab->handle);
|
||||
|
||||
if (IS_ERR(table)) {
|
||||
pr_err("%s: Unable to get sg_table from ion: %ld\n",
|
||||
__func__, PTR_ERR(table));
|
||||
return PTR_ERR(table);
|
||||
} else if (!table) {
|
||||
pr_err("%s: sg_list is NULL\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* uncached */
|
||||
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
|
||||
|
||||
/* We need to check if a page is associated with this sg list because:
|
||||
* If the allocation came from a carveout we currently don't have
|
||||
* pages associated with carved out memory. This might change in the
|
||||
* future and we can remove this check and the else statement.
|
||||
*/
|
||||
page = sg_page(table->sgl);
|
||||
if (page) {
|
||||
pr_debug("%s: page is NOT null\n", __func__);
|
||||
for_each_sg(table->sgl, sg, table->nents, i) {
|
||||
unsigned long remainder = vma->vm_end - addr;
|
||||
unsigned long len = sg->length;
|
||||
|
||||
page = sg_page(sg);
|
||||
|
||||
if (offset >= len) {
|
||||
offset -= len;
|
||||
continue;
|
||||
} else if (offset) {
|
||||
page += offset / PAGE_SIZE;
|
||||
len -= offset;
|
||||
offset = 0;
|
||||
}
|
||||
len = min(len, remainder);
|
||||
pr_debug("vma=%pK, addr=%x len=%ld vm_start=%x vm_end=%x vm_page_prot=%lu\n",
|
||||
vma, (unsigned int)addr, len,
|
||||
(unsigned int)vma->vm_start,
|
||||
(unsigned int)vma->vm_end,
|
||||
(unsigned long)vma->vm_page_prot.pgprot);
|
||||
remap_pfn_range(vma, addr, page_to_pfn(page), len,
|
||||
vma->vm_page_prot);
|
||||
addr += len;
|
||||
if (addr >= vma->vm_end)
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
ion_phys_addr_t phys_addr;
|
||||
size_t phys_len;
|
||||
size_t va_len = 0;
|
||||
|
||||
pr_debug("%s: page is NULL\n", __func__);
|
||||
ret = ion_phys(ab->client, ab->handle, &phys_addr, &phys_len);
|
||||
if (ret) {
|
||||
pr_err("%s: Unable to get phys address from ION buffer: %d\n"
|
||||
, __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
pr_debug("phys=%pKK len=%zd\n", &phys_addr, phys_len);
|
||||
pr_debug("vma=%pK, vm_start=%x vm_end=%x vm_pgoff=%ld vm_page_prot=%lu\n",
|
||||
vma, (unsigned int)vma->vm_start,
|
||||
(unsigned int)vma->vm_end, vma->vm_pgoff,
|
||||
(unsigned long)vma->vm_page_prot.pgprot);
|
||||
va_len = vma->vm_end - vma->vm_start;
|
||||
if ((offset > phys_len) || (va_len > phys_len-offset)) {
|
||||
pr_err("wrong offset size %ld, lens= %zd, va_len=%zd\n",
|
||||
offset, phys_len, va_len);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = remap_pfn_range(vma, vma->vm_start,
|
||||
__phys_to_pfn(phys_addr) + vma->vm_pgoff,
|
||||
vma->vm_end - vma->vm_start,
|
||||
vma->vm_page_prot);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool msm_audio_ion_is_smmu_available(void)
|
||||
{
|
||||
return msm_audio_ion_data.smmu_enabled;
|
||||
}
|
||||
|
||||
/* move to static section again */
|
||||
struct ion_client *msm_audio_ion_client_create(const char *name)
|
||||
{
|
||||
struct ion_client *pclient = NULL;
|
||||
|
||||
pclient = msm_ion_client_create(name);
|
||||
return pclient;
|
||||
}
|
||||
|
||||
|
||||
void msm_audio_ion_client_destroy(struct ion_client *client)
|
||||
{
|
||||
pr_debug("%s: client = %pK smmu_enabled = %d\n", __func__,
|
||||
client, msm_audio_ion_data.smmu_enabled);
|
||||
|
||||
ion_client_destroy(client);
|
||||
}
|
||||
|
||||
int msm_audio_ion_import_legacy(const char *name, struct ion_client *client,
|
||||
struct ion_handle **handle, int fd,
|
||||
unsigned long *ionflag, size_t bufsz,
|
||||
ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (!name || !client || !handle || !paddr || !vaddr || !pa_len) {
|
||||
pr_err("%s: Invalid params\n", __func__);
|
||||
rc = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
/* client is already created for legacy and given
|
||||
* name should be audio_acdb_client or Audio_Dec_Client,
|
||||
* bufsz should be 0 and fd shouldn't be 0 as of now
|
||||
*/
|
||||
*handle = ion_import_dma_buf_fd(client, fd);
|
||||
pr_debug("%s: DMA Buf name=%s, fd=%d handle=%pK\n", __func__,
|
||||
name, fd, *handle);
|
||||
if (IS_ERR_OR_NULL((void *)(*handle))) {
|
||||
pr_err("%s: ion import dma buffer failed\n",
|
||||
__func__);
|
||||
rc = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ionflag != NULL) {
|
||||
rc = ion_handle_get_flags(client, *handle, ionflag);
|
||||
if (rc) {
|
||||
pr_err("%s: could not get flags for the handle\n",
|
||||
__func__);
|
||||
rc = -EINVAL;
|
||||
goto err_ion_handle;
|
||||
}
|
||||
}
|
||||
|
||||
rc = msm_audio_ion_get_phys(client, *handle, paddr, pa_len);
|
||||
if (rc) {
|
||||
pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n",
|
||||
__func__, rc);
|
||||
rc = -EINVAL;
|
||||
goto err_ion_handle;
|
||||
}
|
||||
|
||||
/*Need to add condition SMMU enable or not */
|
||||
*vaddr = ion_map_kernel(client, *handle);
|
||||
if (IS_ERR_OR_NULL((void *)*vaddr)) {
|
||||
pr_err("%s: ION memory mapping for AUDIO failed\n", __func__);
|
||||
rc = -EINVAL;
|
||||
goto err_ion_handle;
|
||||
}
|
||||
|
||||
if (bufsz != 0)
|
||||
memset((void *)*vaddr, 0, bufsz);
|
||||
|
||||
return 0;
|
||||
|
||||
err_ion_handle:
|
||||
ion_free(client, *handle);
|
||||
err:
|
||||
return rc;
|
||||
}
|
||||
|
||||
int msm_audio_ion_free_legacy(struct ion_client *client,
|
||||
struct ion_handle *handle)
|
||||
{
|
||||
if (msm_audio_ion_data.smmu_enabled)
|
||||
msm_audio_dma_buf_unmap(client, handle);
|
||||
|
||||
ion_unmap_kernel(client, handle);
|
||||
|
||||
ion_free(client, handle);
|
||||
/* no client_destrody in legacy*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msm_audio_ion_cache_operations(struct audio_buffer *abuff, int cache_op)
|
||||
{
|
||||
unsigned long ionflag = 0;
|
||||
int rc = 0;
|
||||
int msm_cache_ops = 0;
|
||||
|
||||
if (!abuff) {
|
||||
pr_err("%s: Invalid params: %pK\n", __func__, abuff);
|
||||
return -EINVAL;
|
||||
}
|
||||
rc = ion_handle_get_flags(abuff->client, abuff->handle,
|
||||
&ionflag);
|
||||
if (rc) {
|
||||
pr_err("ion_handle_get_flags failed: %d\n", rc);
|
||||
goto cache_op_failed;
|
||||
}
|
||||
|
||||
/* has to be CACHED */
|
||||
if (ION_IS_CACHED(ionflag)) {
|
||||
/* ION_IOC_INV_CACHES or ION_IOC_CLEAN_CACHES */
|
||||
msm_cache_ops = cache_op;
|
||||
rc = msm_ion_do_cache_op(abuff->client,
|
||||
abuff->handle,
|
||||
(unsigned long *) abuff->data,
|
||||
(unsigned long)abuff->size,
|
||||
msm_cache_ops);
|
||||
if (rc) {
|
||||
pr_err("cache operation failed %d\n", rc);
|
||||
goto cache_op_failed;
|
||||
}
|
||||
}
|
||||
cache_op_failed:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static int msm_audio_dma_buf_map(struct ion_client *client,
|
||||
struct ion_handle *handle,
|
||||
ion_phys_addr_t *addr, size_t *len)
|
||||
{
|
||||
|
||||
struct msm_audio_alloc_data *alloc_data;
|
||||
struct device *cb_dev;
|
||||
int rc = 0;
|
||||
|
||||
cb_dev = msm_audio_ion_data.cb_dev;
|
||||
|
||||
/* Data required per buffer mapping */
|
||||
alloc_data = kzalloc(sizeof(*alloc_data), GFP_KERNEL);
|
||||
if (!alloc_data)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Get the ION handle size */
|
||||
ion_handle_get_size(client, handle, len);
|
||||
|
||||
alloc_data->client = client;
|
||||
alloc_data->handle = handle;
|
||||
alloc_data->len = *len;
|
||||
|
||||
/* Get the dma_buf handle from ion_handle */
|
||||
alloc_data->dma_buf = ion_share_dma_buf(client, handle);
|
||||
if (IS_ERR(alloc_data->dma_buf)) {
|
||||
rc = PTR_ERR(alloc_data->dma_buf);
|
||||
dev_err(cb_dev,
|
||||
"%s: Fail to get dma_buf handle, rc = %d\n",
|
||||
__func__, rc);
|
||||
goto err_dma_buf;
|
||||
}
|
||||
|
||||
/* Attach the dma_buf to context bank device */
|
||||
alloc_data->attach = dma_buf_attach(alloc_data->dma_buf,
|
||||
cb_dev);
|
||||
if (IS_ERR(alloc_data->attach)) {
|
||||
rc = PTR_ERR(alloc_data->attach);
|
||||
dev_err(cb_dev,
|
||||
"%s: Fail to attach dma_buf to CB, rc = %d\n",
|
||||
__func__, rc);
|
||||
goto err_attach;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the scatter-gather list.
|
||||
* There is no info as this is a write buffer or
|
||||
* read buffer, hence the request is bi-directional
|
||||
* to accommodate both read and write mappings.
|
||||
*/
|
||||
alloc_data->table = dma_buf_map_attachment(alloc_data->attach,
|
||||
DMA_BIDIRECTIONAL);
|
||||
if (IS_ERR(alloc_data->table)) {
|
||||
rc = PTR_ERR(alloc_data->table);
|
||||
dev_err(cb_dev,
|
||||
"%s: Fail to map attachment, rc = %d\n",
|
||||
__func__, rc);
|
||||
goto err_map_attach;
|
||||
}
|
||||
|
||||
rc = dma_map_sg(cb_dev, alloc_data->table->sgl,
|
||||
alloc_data->table->nents,
|
||||
DMA_BIDIRECTIONAL);
|
||||
if (rc != alloc_data->table->nents) {
|
||||
dev_err(cb_dev,
|
||||
"%s: Fail to map SG, rc = %d, nents = %d\n",
|
||||
__func__, rc, alloc_data->table->nents);
|
||||
goto err_map_sg;
|
||||
}
|
||||
/* Make sure not to return rc from dma_map_sg, as it can be nonzero */
|
||||
rc = 0;
|
||||
|
||||
/* physical address from mapping */
|
||||
*addr = MSM_AUDIO_ION_PHYS_ADDR(alloc_data);
|
||||
|
||||
msm_audio_ion_add_allocation(&msm_audio_ion_data,
|
||||
alloc_data);
|
||||
return rc;
|
||||
|
||||
err_map_sg:
|
||||
dma_buf_unmap_attachment(alloc_data->attach,
|
||||
alloc_data->table,
|
||||
DMA_BIDIRECTIONAL);
|
||||
err_map_attach:
|
||||
dma_buf_detach(alloc_data->dma_buf,
|
||||
alloc_data->attach);
|
||||
err_attach:
|
||||
dma_buf_put(alloc_data->dma_buf);
|
||||
|
||||
err_dma_buf:
|
||||
kfree(alloc_data);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int msm_audio_dma_buf_unmap(struct ion_client *client,
|
||||
struct ion_handle *handle)
|
||||
{
|
||||
int rc = 0;
|
||||
struct msm_audio_alloc_data *alloc_data = NULL;
|
||||
struct list_head *ptr, *next;
|
||||
struct device *cb_dev = msm_audio_ion_data.cb_dev;
|
||||
bool found = false;
|
||||
|
||||
/*
|
||||
* Though list_for_each_safe is delete safe, lock
|
||||
* should be explicitly acquired to avoid race condition
|
||||
* on adding elements to the list.
|
||||
*/
|
||||
mutex_lock(&(msm_audio_ion_data.list_mutex));
|
||||
list_for_each_safe(ptr, next,
|
||||
&(msm_audio_ion_data.alloc_list)) {
|
||||
|
||||
alloc_data = list_entry(ptr, struct msm_audio_alloc_data,
|
||||
list);
|
||||
|
||||
if (alloc_data->handle == handle &&
|
||||
alloc_data->client == client) {
|
||||
found = true;
|
||||
dma_unmap_sg(cb_dev,
|
||||
alloc_data->table->sgl,
|
||||
alloc_data->table->nents,
|
||||
DMA_BIDIRECTIONAL);
|
||||
|
||||
dma_buf_unmap_attachment(alloc_data->attach,
|
||||
alloc_data->table,
|
||||
DMA_BIDIRECTIONAL);
|
||||
|
||||
dma_buf_detach(alloc_data->dma_buf,
|
||||
alloc_data->attach);
|
||||
|
||||
dma_buf_put(alloc_data->dma_buf);
|
||||
|
||||
list_del(&(alloc_data->list));
|
||||
kfree(alloc_data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&(msm_audio_ion_data.list_mutex));
|
||||
|
||||
if (!found) {
|
||||
dev_err(cb_dev,
|
||||
"%s: cannot find allocation, ion_handle %pK, ion_client %pK",
|
||||
__func__, handle, client);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int msm_audio_ion_get_phys(struct ion_client *client,
|
||||
struct ion_handle *handle,
|
||||
ion_phys_addr_t *addr, size_t *len)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
pr_debug("%s: smmu_enabled = %d\n", __func__,
|
||||
msm_audio_ion_data.smmu_enabled);
|
||||
|
||||
if (msm_audio_ion_data.smmu_enabled) {
|
||||
rc = msm_audio_dma_buf_map(client, handle, addr, len);
|
||||
if (rc) {
|
||||
pr_err("%s: failed to map DMA buf, err = %d\n",
|
||||
__func__, rc);
|
||||
goto err;
|
||||
}
|
||||
/* Append the SMMU SID information to the IOVA address */
|
||||
*addr |= msm_audio_ion_data.smmu_sid_bits;
|
||||
} else {
|
||||
rc = ion_phys(client, handle, addr, len);
|
||||
}
|
||||
|
||||
pr_debug("phys=%pK, len=%zd, rc=%d\n", &(*addr), *len, rc);
|
||||
err:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int msm_audio_smmu_init(struct device *dev)
|
||||
{
|
||||
struct dma_iommu_mapping *mapping;
|
||||
int ret;
|
||||
|
||||
mapping = arm_iommu_create_mapping(&platform_bus_type,
|
||||
MSM_AUDIO_ION_VA_START,
|
||||
MSM_AUDIO_ION_VA_LEN);
|
||||
if (IS_ERR(mapping))
|
||||
return PTR_ERR(mapping);
|
||||
|
||||
ret = arm_iommu_attach_device(dev, mapping);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s: Attach failed, err = %d\n",
|
||||
__func__, ret);
|
||||
goto fail_attach;
|
||||
}
|
||||
|
||||
msm_audio_ion_data.cb_dev = dev;
|
||||
msm_audio_ion_data.mapping = mapping;
|
||||
INIT_LIST_HEAD(&msm_audio_ion_data.alloc_list);
|
||||
mutex_init(&(msm_audio_ion_data.list_mutex));
|
||||
|
||||
return 0;
|
||||
|
||||
fail_attach:
|
||||
arm_iommu_release_mapping(mapping);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id msm_audio_ion_dt_match[] = {
|
||||
{ .compatible = "qcom,msm-audio-ion" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, msm_audio_ion_dt_match);
|
||||
|
||||
|
||||
u32 msm_audio_ion_get_smmu_sid_mode32(void)
|
||||
{
|
||||
if (msm_audio_ion_data.smmu_enabled)
|
||||
return upper_32_bits(msm_audio_ion_data.smmu_sid_bits);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 msm_audio_populate_upper_32_bits(ion_phys_addr_t pa)
|
||||
{
|
||||
if (sizeof(ion_phys_addr_t) == sizeof(u32))
|
||||
return msm_audio_ion_get_smmu_sid_mode32();
|
||||
else
|
||||
return upper_32_bits(pa);
|
||||
}
|
||||
|
||||
static int msm_audio_ion_probe(struct platform_device *pdev)
|
||||
{
|
||||
int rc = 0;
|
||||
const char *msm_audio_ion_dt = "qcom,smmu-enabled";
|
||||
const char *msm_audio_ion_smmu = "qcom,smmu-version";
|
||||
const char *msm_audio_ion_smmu_sid_mask = "qcom,smmu-sid-mask";
|
||||
bool smmu_enabled;
|
||||
enum apr_subsys_state q6_state;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
if (dev->of_node == NULL) {
|
||||
dev_err(dev,
|
||||
"%s: device tree is not found\n",
|
||||
__func__);
|
||||
msm_audio_ion_data.smmu_enabled = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
smmu_enabled = of_property_read_bool(dev->of_node,
|
||||
msm_audio_ion_dt);
|
||||
msm_audio_ion_data.smmu_enabled = smmu_enabled;
|
||||
|
||||
if (smmu_enabled) {
|
||||
rc = of_property_read_u32(dev->of_node,
|
||||
msm_audio_ion_smmu,
|
||||
&msm_audio_ion_data.smmu_version);
|
||||
if (rc) {
|
||||
dev_err(dev,
|
||||
"%s: qcom,smmu_version missing in DT node\n",
|
||||
__func__);
|
||||
return rc;
|
||||
}
|
||||
dev_dbg(dev, "%s: SMMU version is (%d)", __func__,
|
||||
msm_audio_ion_data.smmu_version);
|
||||
q6_state = apr_get_q6_state();
|
||||
if (q6_state == APR_SUBSYS_DOWN) {
|
||||
dev_dbg(dev,
|
||||
"defering %s, adsp_state %d\n",
|
||||
__func__, q6_state);
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
dev_dbg(dev, "%s: adsp is ready\n", __func__);
|
||||
}
|
||||
|
||||
dev_dbg(dev, "%s: SMMU is %s\n", __func__,
|
||||
(smmu_enabled) ? "Enabled" : "Disabled");
|
||||
|
||||
if (smmu_enabled) {
|
||||
u64 smmu_sid = 0;
|
||||
u64 smmu_sid_mask = 0;
|
||||
struct of_phandle_args iommuspec;
|
||||
|
||||
/* Get SMMU SID information from Devicetree */
|
||||
rc = of_property_read_u64(dev->of_node,
|
||||
msm_audio_ion_smmu_sid_mask,
|
||||
&smmu_sid_mask);
|
||||
if (rc) {
|
||||
dev_err(dev,
|
||||
"%s: qcom,smmu-sid-mask missing in DT node, using default\n",
|
||||
__func__);
|
||||
smmu_sid_mask = 0xFFFFFFFFFFFFFFFF;
|
||||
}
|
||||
rc = of_parse_phandle_with_args(dev->of_node, "iommus",
|
||||
"#iommu-cells", 0, &iommuspec);
|
||||
if (rc)
|
||||
dev_err(dev, "%s: could not get smmu SID, ret = %d\n",
|
||||
__func__, rc);
|
||||
else
|
||||
smmu_sid = (iommuspec.args[0] & smmu_sid_mask);
|
||||
|
||||
msm_audio_ion_data.smmu_sid_bits =
|
||||
smmu_sid << MSM_AUDIO_SMMU_SID_OFFSET;
|
||||
|
||||
if (msm_audio_ion_data.smmu_version == 0x2) {
|
||||
rc = msm_audio_smmu_init(dev);
|
||||
} else {
|
||||
dev_err(dev, "%s: smmu version invalid %d\n",
|
||||
__func__, msm_audio_ion_data.smmu_version);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
if (rc)
|
||||
dev_err(dev, "%s: smmu init failed, err = %d\n",
|
||||
__func__, rc);
|
||||
}
|
||||
|
||||
if (!rc)
|
||||
msm_audio_ion_data.device_status |= MSM_AUDIO_ION_PROBED;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int msm_audio_ion_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct dma_iommu_mapping *mapping;
|
||||
struct device *audio_cb_dev;
|
||||
|
||||
mapping = msm_audio_ion_data.mapping;
|
||||
audio_cb_dev = msm_audio_ion_data.cb_dev;
|
||||
|
||||
if (audio_cb_dev && mapping) {
|
||||
arm_iommu_detach_device(audio_cb_dev);
|
||||
arm_iommu_release_mapping(mapping);
|
||||
}
|
||||
|
||||
msm_audio_ion_data.smmu_enabled = 0;
|
||||
msm_audio_ion_data.device_status = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver msm_audio_ion_driver = {
|
||||
.driver = {
|
||||
.name = "msm-audio-ion",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = msm_audio_ion_dt_match,
|
||||
},
|
||||
.probe = msm_audio_ion_probe,
|
||||
.remove = msm_audio_ion_remove,
|
||||
};
|
||||
|
||||
static int __init msm_audio_ion_init(void)
|
||||
{
|
||||
return platform_driver_register(&msm_audio_ion_driver);
|
||||
}
|
||||
module_init(msm_audio_ion_init);
|
||||
|
||||
static void __exit msm_audio_ion_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&msm_audio_ion_driver);
|
||||
}
|
||||
module_exit(msm_audio_ion_exit);
|
||||
|
||||
MODULE_DESCRIPTION("MSM Audio ION module");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
17
drivers/soundwire/Kconfig
Normal file
17
drivers/soundwire/Kconfig
Normal file
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# SOUNDWIRE driver configuration
|
||||
#
|
||||
menuconfig SOUNDWIRE
|
||||
bool "Soundwire support"
|
||||
help
|
||||
Soundwire is a two wire interface for audio to connect
|
||||
simple peripheral components in mobile devices.
|
||||
|
||||
if SOUNDWIRE
|
||||
config SOUNDWIRE_WCD_CTRL
|
||||
depends on WCD9XXX_CODEC_CORE
|
||||
tristate "QTI WCD CODEC Soundwire controller"
|
||||
default n
|
||||
help
|
||||
Select driver for QTI's Soundwire Master Component.
|
||||
endif
|
||||
5
drivers/soundwire/Makefile
Normal file
5
drivers/soundwire/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# Makefile for kernel soundwire framework.
|
||||
#
|
||||
obj-$(CONFIG_SOUNDWIRE) += soundwire.o
|
||||
obj-$(CONFIG_SOUNDWIRE_WCD_CTRL) += swr-wcd-ctrl.o
|
||||
1032
drivers/soundwire/soundwire.c
Normal file
1032
drivers/soundwire/soundwire.c
Normal file
File diff suppressed because it is too large
Load Diff
1880
drivers/soundwire/swr-wcd-ctrl.c
Normal file
1880
drivers/soundwire/swr-wcd-ctrl.c
Normal file
File diff suppressed because it is too large
Load Diff
106
drivers/soundwire/swr-wcd-ctrl.h
Normal file
106
drivers/soundwire/swr-wcd-ctrl.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/* 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 _SWR_WCD_CTRL_H
|
||||
#define _SWR_WCD_CTRL_H
|
||||
#include <linux/module.h>
|
||||
#include <linux/soundwire/swr-wcd.h>
|
||||
|
||||
#define SWR_MAX_ROW 0 /* Rows = 48 */
|
||||
#define SWR_MAX_COL 7 /* Cols = 16 */
|
||||
#define SWR_MIN_COL 0 /* Cols = 2 */
|
||||
|
||||
#define SWR_WCD_NAME "swr-wcd"
|
||||
|
||||
#define SWR_MSTR_PORT_LEN 8 /* Number of master ports */
|
||||
|
||||
#define SWRM_VERSION_1_0 0x01010000
|
||||
#define SWRM_VERSION_1_2 0x01030000
|
||||
#define SWRM_VERSION_1_3 0x01040000
|
||||
|
||||
enum {
|
||||
SWR_MSTR_PAUSE,
|
||||
SWR_MSTR_RESUME,
|
||||
SWR_MSTR_UP,
|
||||
SWR_MSTR_DOWN,
|
||||
};
|
||||
|
||||
enum {
|
||||
SWR_IRQ_FREE,
|
||||
SWR_IRQ_REGISTER,
|
||||
};
|
||||
|
||||
enum {
|
||||
SWR_DAC_PORT,
|
||||
SWR_COMP_PORT,
|
||||
SWR_BOOST_PORT,
|
||||
SWR_VISENSE_PORT,
|
||||
};
|
||||
|
||||
struct usecase {
|
||||
u8 num_port;
|
||||
u8 num_ch;
|
||||
u32 chrate;
|
||||
};
|
||||
|
||||
struct port_params {
|
||||
u8 si;
|
||||
u8 off1;
|
||||
u8 off2;
|
||||
};
|
||||
|
||||
struct swrm_mports {
|
||||
struct list_head list;
|
||||
u8 id;
|
||||
};
|
||||
|
||||
struct swr_ctrl_platform_data {
|
||||
void *handle; /* holds priv data */
|
||||
int (*read)(void *handle, int reg);
|
||||
int (*write)(void *handle, int reg, int val);
|
||||
int (*bulk_write)(void *handle, u32 *reg, u32 *val, size_t len);
|
||||
int (*clk)(void *handle, bool enable);
|
||||
int (*reg_irq)(void *handle, irqreturn_t(*irq_handler)(int irq,
|
||||
void *data), void *swr_handle, int type);
|
||||
};
|
||||
|
||||
struct swr_mstr_ctrl {
|
||||
struct swr_master master;
|
||||
struct device *dev;
|
||||
struct resource *supplies;
|
||||
struct clk *mclk;
|
||||
struct completion reset;
|
||||
struct completion broadcast;
|
||||
struct mutex mlock;
|
||||
struct mutex reslock;
|
||||
u8 rcmd_id;
|
||||
u8 wcmd_id;
|
||||
void *handle; /* SWR Master handle from client for read and writes */
|
||||
int (*read)(void *handle, int reg);
|
||||
int (*write)(void *handle, int reg, int val);
|
||||
int (*bulk_write)(void *handle, u32 *reg, u32 *val, size_t len);
|
||||
int (*clk)(void *handle, bool enable);
|
||||
int (*reg_irq)(void *handle, irqreturn_t(*irq_handler)(int irq,
|
||||
void *data), void *swr_handle, int type);
|
||||
int irq;
|
||||
int version;
|
||||
int num_enum_slaves;
|
||||
int slave_status;
|
||||
struct swr_mstr_port *mstr_port;
|
||||
struct list_head mport_list;
|
||||
int state;
|
||||
struct platform_device *pdev;
|
||||
int num_rx_chs;
|
||||
u8 num_cfg_devs;
|
||||
};
|
||||
|
||||
#endif /* _SWR_WCD_CTRL_H */
|
||||
204
drivers/soundwire/swrm_registers.h
Normal file
204
drivers/soundwire/swrm_registers.h
Normal file
@@ -0,0 +1,204 @@
|
||||
/* 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 _SWRM_REGISTERS_H
|
||||
#define _SWRM_REGISTERS_H
|
||||
|
||||
#define SWRM_BASE_ADDRESS 0x00
|
||||
|
||||
#define SWRM_COMP_HW_VERSION SWRM_BASE_ADDRESS
|
||||
#define SWRM_COMP_CFG_ADDR (SWRM_BASE_ADDRESS+0x00000004)
|
||||
#define SWRM_COMP_CFG_RMSK 0x3
|
||||
#define SWRM_COMP_CFG_IRQ_LEVEL_OR_PULSE_BMSK 0x2
|
||||
#define SWRM_COMP_CFG_IRQ_LEVEL_OR_PULSE_SHFT 0x1
|
||||
#define SWRM_COMP_CFG_ENABLE_BMSK 0x1
|
||||
#define SWRM_COMP_CFG_ENABLE_SHFT 0x0
|
||||
|
||||
#define SWRM_COMP_SW_RESET (SWRM_BASE_ADDRESS+0x00000008)
|
||||
|
||||
#define SWRM_COMP_PARAMS (SWRM_BASE_ADDRESS+0x100)
|
||||
#define SWRM_COMP_PARAMS_DOUT_PORTS_MASK 0x0000001F
|
||||
#define SWRM_COMP_PARAMS_DIN_PORTS_MASK 0x000003E0
|
||||
#define SWRM_COMP_PARAMS_WR_FIFO_DEPTH 0x00007C00
|
||||
#define SWRM_COMP_PARAMS_RD_FIFO_DEPTH 0x000F8000
|
||||
#define SWRM_COMP_PARAMS_AUTO_ENUM_SLAVES 0x00F00000
|
||||
#define SWRM_COMP_PARAMS_DATA_LANES 0x07000000
|
||||
|
||||
|
||||
#define SWRM_INTERRUPT_STATUS (SWRM_BASE_ADDRESS+0x00000200)
|
||||
#define SWRM_INTERRUPT_STATUS_RMSK 0x1FFFD
|
||||
|
||||
#define SWRM_INTERRUPT_STATUS_SLAVE_PEND_IRQ 0x1
|
||||
#define SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED 0x2
|
||||
#define SWRM_INTERRUPT_STATUS_CHANGE_ENUM_SLAVE_STATUS 0x4
|
||||
#define SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET 0x8
|
||||
#define SWRM_INTERRUPT_STATUS_RD_FIFO_OVERFLOW 0x10
|
||||
#define SWRM_INTERRUPT_STATUS_RD_FIFO_UNDERFLOW 0x20
|
||||
#define SWRM_INTERRUPT_STATUS_WR_CMD_FIFO_OVERFLOW 0x40
|
||||
#define SWRM_INTERRUPT_STATUS_CMD_ERROR 0x80
|
||||
#define SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION 0x100
|
||||
#define SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH 0x200
|
||||
#define SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED 0x400
|
||||
#define SWRM_INTERRUPT_STATUS_NEW_SLAVE_AUTO_ENUM_FINISHED 0x800
|
||||
#define SWRM_INTERRUPT_STATUS_AUTO_ENUM_FAILED 0x1000
|
||||
#define SWRM_INTERRUPT_STATUS_AUTO_ENUM_TABLE_IS_FULL 0x2000
|
||||
#define SWRM_INTERRUPT_STATUS_BUS_RESET_FINISHED 0x4000
|
||||
#define SWRM_INTERRUPT_STATUS_CLK_STOP_FINISHED 0x8000
|
||||
#define SWRM_INTERRUPT_STATUS_ERROR_PORT_TEST 0x10000
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_ADDR (SWRM_BASE_ADDRESS+0x00000204)
|
||||
#define SWRM_INTERRUPT_MASK_RMSK 0x1FFFF
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_SLAVE_PEND_IRQ_BMSK 0x1
|
||||
#define SWRM_INTERRUPT_MASK_SLAVE_PEND_IRQ_SHFT 0x0
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_NEW_SLAVE_ATTACHED_BMSK 0x2
|
||||
#define SWRM_INTERRUPT_MASK_NEW_SLAVE_ATTACHED_SHFT 0x1
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_CHANGE_ENUM_SLAVE_STATUS_BMSK 0x4
|
||||
#define SWRM_INTERRUPT_MASK_CHANGE_ENUM_SLAVE_STATUS_SHFT 0x2
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_MASTER_CLASH_DET_BMSK 0x8
|
||||
#define SWRM_INTERRUPT_MASK_MASTER_CLASH_DET_SHFT 0x3
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_RD_FIFO_OVERFLOW_BMSK 0x10
|
||||
#define SWRM_INTERRUPT_MASK_RD_FIFO_OVERFLOW_SHFT 0x4
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_RD_FIFO_UNDERFLOW_BMSK 0x20
|
||||
#define SWRM_INTERRUPT_MASK_RD_FIFO_UNDERFLOW_SHFT 0x5
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_WR_CMD_FIFO_OVERFLOW_BMSK 0x40
|
||||
#define SWRM_INTERRUPT_MASK_WR_CMD_FIFO_OVERFLOW_SHFT 0x6
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_CMD_ERROR_BMSK 0x80
|
||||
#define SWRM_INTERRUPT_MASK_CMD_ERROR_SHFT 0x7
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_DOUT_PORT_COLLISION_BMSK 0x100
|
||||
#define SWRM_INTERRUPT_MASK_DOUT_PORT_COLLISION_SHFT 0x8
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_READ_EN_RD_VALID_MISMATCH_BMSK 0x200
|
||||
#define SWRM_INTERRUPT_MASK_READ_EN_RD_VALID_MISMATCH_SHFT 0x9
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_SPECIAL_CMD_ID_FINISHED_BMSK 0x400
|
||||
#define SWRM_INTERRUPT_MASK_SPECIAL_CMD_ID_FINISHED_SHFT 0xA
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_NEW_SLAVE_AUTO_ENUM_FINISHED_BMSK 0x800
|
||||
#define SWRM_INTERRUPT_MASK_NEW_SLAVE_AUTO_ENUM_FINISHED_SHFT 0xB
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_AUTO_ENUM_FAILED_BMSK 0x1000
|
||||
#define SWRM_INTERRUPT_MASK_AUTO_ENUM_FAILED_SHFT 0xC
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_AUTO_ENUM_TABLE_IS_FULL_BMSK 0x2000
|
||||
#define SWRM_INTERRUPT_MASK_AUTO_ENUM_TABLE_IS_FULL_SHFT 0xD
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_BUS_RESET_FINISHED_BMSK 0x4000
|
||||
#define SWRM_INTERRUPT_MASK_BUS_RESET_FINISHED_SHFT 0xE
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_CLK_STOP_FINISHED_BMSK 0x8000
|
||||
#define SWRM_INTERRUPT_MASK_CLK_STOP_FINISHED_SHFT 0xF
|
||||
|
||||
#define SWRM_INTERRUPT_MASK_ERROR_PORT_TEST_BMSK 0x10000
|
||||
#define SWRM_INTERRUPT_MASK_ERROR_PORT_TEST_SHFT 0x10
|
||||
|
||||
#define SWRM_INTERRUPT_MAX 0x11
|
||||
|
||||
#define SWRM_INTERRUPT_CLEAR (SWRM_BASE_ADDRESS+0x00000208)
|
||||
|
||||
#define SWRM_CMD_FIFO_WR_CMD (SWRM_BASE_ADDRESS + 0x00000300)
|
||||
#define SWRM_CMD_FIFO_WR_CMD_MASK 0xFFFFFFFF
|
||||
#define SWRM_CMD_FIFO_RD_CMD (SWRM_BASE_ADDRESS + 0x00000304)
|
||||
#define SWRM_CMD_FIFO_RD_CMD_MASK 0xFFFFFFF
|
||||
#define SWRM_CMD_FIFO_CMD (SWRM_BASE_ADDRESS + 0x00000308)
|
||||
#define SWRM_CMD_FIFO_STATUS (SWRM_BASE_ADDRESS + 0x0000030C)
|
||||
|
||||
#define SWRM_CMD_FIFO_STATUS_WR_CMD_FIFO_CNT_MASK 0x1F00
|
||||
#define SWRM_CMD_FIFO_STATUS_RD_CMD_FIFO_CNT_MASK 0x7C00000
|
||||
|
||||
#define SWRM_CMD_FIFO_CFG_ADDR (SWRM_BASE_ADDRESS+0x00000314)
|
||||
#define SWRM_CMD_FIFO_CFG_NUM_OF_CMD_RETRY_BMSK 0x7
|
||||
#define SWRM_CMD_FIFO_CFG_NUM_OF_CMD_RETRY_SHFT 0x0
|
||||
|
||||
#define SWRM_CMD_FIFO_RD_FIFO_ADDR (SWRM_BASE_ADDRESS + 0x00000318)
|
||||
|
||||
#define SWRM_ENUMERATOR_CFG_ADDR (SWRM_BASE_ADDRESS+0x00000500)
|
||||
#define SWRM_ENUMERATOR_CFG_AUTO_ENUM_EN_BMSK 0x1
|
||||
#define SWRM_ENUMERATOR_CFG_AUTO_ENUM_EN_SHFT 0x0
|
||||
|
||||
#define SWRM_ENUMERATOR_SLAVE_DEV_ID_1(m) (SWRM_BASE_ADDRESS+0x530+0x8*m)
|
||||
#define SWRM_ENUMERATOR_SLAVE_DEV_ID_2(m) (SWRM_BASE_ADDRESS+0x534+0x8*m)
|
||||
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_ADDR(m) (SWRM_BASE_ADDRESS+0x101C+0x40*m)
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_RMSK 0x00ff07ff
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_SHFT 0
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_SSP_PERIOD_BMSK 0xff0000
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_SSP_PERIOD_SHFT 16
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_PHASE_BMSK 0xf800
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_PHASE_SHFT 11
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_CLK_DIV_VALUE_BMSK 0x700
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_CLK_DIV_VALUE_SHFT 8
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK 0xF8
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT 3
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK 0x7
|
||||
#define SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT 0
|
||||
|
||||
#define SWRM_MCP_BUS_CTRL_ADDR (SWRM_BASE_ADDRESS+0x00001044)
|
||||
#define SWRM_MCP_BUS_CTRL_BUS_RESET_BMSK 0x1
|
||||
#define SWRM_MCP_BUS_CTRL_BUS_RESET_SHFT 0x0
|
||||
#define SWRM_MCP_BUS_CTRL_CLK_START_BMSK 0x2
|
||||
#define SWRM_MCP_BUS_CTRL_CLK_START_SHFT 0x1
|
||||
|
||||
#define SWRM_MCP_CFG_ADDR (SWRM_BASE_ADDRESS+0x00001048)
|
||||
#define SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK 0x3E0000
|
||||
#define SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_SHFT 0x11
|
||||
#define SWRM_MCP_CFG_BUS_CLK_PAUSE_BMSK 0x02
|
||||
|
||||
#define SWRM_MCP_STATUS (SWRM_BASE_ADDRESS+0x104C)
|
||||
#define SWRM_MCP_STATUS_BANK_NUM_MASK 0x01
|
||||
|
||||
#define SWRM_MCP_SLV_STATUS (SWRM_BASE_ADDRESS+0x1090)
|
||||
#define SWRM_MCP_SLV_STATUS_MASK 0x03
|
||||
|
||||
#define SWRM_DP_PORT_CTRL_BANK(n, m) (SWRM_BASE_ADDRESS + \
|
||||
0x00001124 + \
|
||||
0x100*(n-1) + \
|
||||
0x40*m)
|
||||
#define SWRM_DP_PORT_CTRL_BANK_MASK 0xFFFFFFFF
|
||||
#define SWRM_DP_PORT_CTRL_EN_CHAN_MASK 0xFF000000
|
||||
#define SWRM_DP_PORT_CTRL_EN_CHAN_SHFT 0x18
|
||||
#define SWRM_DP_PORT_CTRL_OFFSET2_SHFT 0x10
|
||||
#define SWRM_DP_PORT_CTRL_OFFSET1_SHFT 0x08
|
||||
#define SWRM_DP_PORT_CTRL_SAMPLE_INTERVAL 0x00
|
||||
|
||||
/* Soundwire Slave Register definition */
|
||||
|
||||
#define SWRS_BASE_ADDRESS 0x00
|
||||
|
||||
#define SWRS_DP_REG_OFFSET(port, bank) ((0x100*port)+(0x10*bank))
|
||||
|
||||
#define SWRS_DP_CHANNEL_ENABLE_BANK(n, m) (SWRS_BASE_ADDRESS + 0x120 + \
|
||||
SWRS_DP_REG_OFFSET(n, m))
|
||||
#define SWRS_DP_SAMPLE_CONTROL_1_BANK(n, m) (SWRS_BASE_ADDRESS + 0x122 + \
|
||||
SWRS_DP_REG_OFFSET(n, m))
|
||||
#define SWRS_DP_OFFSET_CONTROL_1_BANK(n, m) (SWRS_BASE_ADDRESS + 0x124 + \
|
||||
SWRS_DP_REG_OFFSET(n, m))
|
||||
#define SWRS_DP_OFFSET_CONTROL_2_BANK(n, m) (SWRS_BASE_ADDRESS + 0x125 + \
|
||||
SWRS_DP_REG_OFFSET(n, m))
|
||||
#define SWRS_DP_HCONTROL_BANK(n, m) (SWRS_BASE_ADDRESS + 0x126 + \
|
||||
SWRS_DP_REG_OFFSET(n, m))
|
||||
#define SWRS_DP_BLOCK_CONTROL_3_BANK(n, m) (SWRS_BASE_ADDRESS + 0x127 + \
|
||||
SWRS_DP_REG_OFFSET(n, m))
|
||||
#define SWRS_SCP_FRAME_CTRL_BANK(m) (SWRS_BASE_ADDRESS + 0x60 + \
|
||||
0x10*m)
|
||||
#define SWRS_SCP_HOST_CLK_DIV2_CTL_BANK(m) (SWRS_BASE_ADDRESS + 0xE0 + \
|
||||
0x10*m)
|
||||
|
||||
#endif /* _SWRM_REGISTERS_H */
|
||||
2
include/Kbuild
Normal file
2
include/Kbuild
Normal file
@@ -0,0 +1,2 @@
|
||||
# Top-level Makefile calls into asm-$(ARCH)
|
||||
# List only non-arch directories below
|
||||
24
include/linux/avtimer_kernel.h
Normal file
24
include/linux/avtimer_kernel.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 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 _AVTIMER_H
|
||||
#define _AVTIMER_H
|
||||
|
||||
#include <uapi/linux/avtimer.h>
|
||||
|
||||
int avcs_core_open(void);
|
||||
int avcs_core_disable_power_collapse(int disable);/* true or flase */
|
||||
int avcs_core_query_timer(uint64_t *avtimer_tick);
|
||||
|
||||
#endif
|
||||
49
include/linux/mfd/msm-cdc-pinctrl.h
Normal file
49
include/linux/mfd/msm-cdc-pinctrl.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#ifndef __MFD_CDC_PINCTRL_H_
|
||||
#define __MFD_CDC_PINCTRL_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_MSM_CDC_PINCTRL)
|
||||
extern int msm_cdc_pinctrl_select_sleep_state(struct device_node *np);
|
||||
extern int msm_cdc_pinctrl_select_active_state(struct device_node *np);
|
||||
extern bool msm_cdc_pinctrl_get_state(struct device_node *np);
|
||||
extern int msm_cdc_get_gpio_state(struct device_node *np);
|
||||
int msm_cdc_pinctrl_drv_init(void);
|
||||
void msm_cdc_pinctrl_drv_exit(void);
|
||||
|
||||
#else
|
||||
int msm_cdc_pinctrl_select_sleep_state(struct device_node *np)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int msm_cdc_pinctrl_select_active_state(struct device_node *np)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int msm_cdc_get_gpio_state(struct device_node *np)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int msm_cdc_pinctrl_drv_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
void msm_cdc_pinctrl_drv_exit(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
48
include/linux/mfd/msm-cdc-supply.h
Normal file
48
include/linux/mfd/msm-cdc-supply.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/* Copyright (c) 2016, 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 __CODEC_POWER_SUPPLY_H__
|
||||
#define __CODEC_POWER_SUPPLY_H__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
struct cdc_regulator {
|
||||
const char *name;
|
||||
int min_uV;
|
||||
int max_uV;
|
||||
int optimum_uA;
|
||||
bool ondemand;
|
||||
struct regulator *regulator;
|
||||
};
|
||||
|
||||
extern int msm_cdc_get_power_supplies(struct device *dev,
|
||||
struct cdc_regulator **cdc_vreg,
|
||||
int *total_num_supplies);
|
||||
extern int msm_cdc_disable_static_supplies(struct device *dev,
|
||||
struct regulator_bulk_data *supplies,
|
||||
struct cdc_regulator *cdc_vreg,
|
||||
int num_supplies);
|
||||
extern int msm_cdc_release_supplies(struct device *dev,
|
||||
struct regulator_bulk_data *supplies,
|
||||
struct cdc_regulator *cdc_vreg,
|
||||
int num_supplies);
|
||||
extern int msm_cdc_enable_static_supplies(struct device *dev,
|
||||
struct regulator_bulk_data *supplies,
|
||||
struct cdc_regulator *cdc_vreg,
|
||||
int num_supplies);
|
||||
extern int msm_cdc_init_supplies(struct device *dev,
|
||||
struct regulator_bulk_data **supplies,
|
||||
struct cdc_regulator *cdc_vreg,
|
||||
int num_supplies);
|
||||
#endif
|
||||
55
include/linux/mfd/wcd9335/irq.h
Normal file
55
include/linux/mfd/wcd9335/irq.h
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.
|
||||
*/
|
||||
|
||||
#ifndef __WCD9335_IRQ_H_
|
||||
#define __WCD9335_IRQ_H_
|
||||
|
||||
enum {
|
||||
/* INTR_REG 0 */
|
||||
WCD9335_IRQ_FLL_LOCK_LOSS = 1,
|
||||
WCD9335_IRQ_HPH_PA_OCPL_FAULT,
|
||||
WCD9335_IRQ_HPH_PA_OCPR_FAULT,
|
||||
WCD9335_IRQ_EAR_PA_OCP_FAULT,
|
||||
WCD9335_IRQ_HPH_PA_CNPL_COMPLETE,
|
||||
WCD9335_IRQ_HPH_PA_CNPR_COMPLETE,
|
||||
WCD9335_IRQ_EAR_PA_CNP_COMPLETE,
|
||||
/* INTR_REG 1 */
|
||||
WCD9335_IRQ_MBHC_SW_DET,
|
||||
WCD9335_IRQ_MBHC_ELECT_INS_REM_DET,
|
||||
WCD9335_IRQ_MBHC_BUTTON_PRESS_DET,
|
||||
WCD9335_IRQ_MBHC_BUTTON_RELEASE_DET,
|
||||
WCD9335_IRQ_MBHC_ELECT_INS_REM_LEG_DET,
|
||||
WCD9335_IRQ_RESERVED_0,
|
||||
WCD9335_IRQ_RESERVED_1,
|
||||
WCD9335_IRQ_RESERVED_2,
|
||||
/* INTR_REG 2 */
|
||||
WCD9335_IRQ_LINE_PA1_CNP_COMPLETE,
|
||||
WCD9335_IRQ_LINE_PA2_CNP_COMPLETE,
|
||||
WCD9335_IRQ_LINE_PA3_CNP_COMPLETE,
|
||||
WCD9335_IRQ_LINE_PA4_CNP_COMPLETE,
|
||||
WCD9335_IRQ_SOUNDWIRE,
|
||||
WCD9335_IRQ_VDD_DIG_RAMP_COMPLETE,
|
||||
WCD9335_IRQ_RCO_ERROR,
|
||||
WCD9335_IRQ_SVA_ERROR,
|
||||
/* INTR_REG 3 */
|
||||
WCD9335_IRQ_MAD_AUDIO,
|
||||
WCD9335_IRQ_MAD_BEACON,
|
||||
WCD9335_IRQ_MAD_ULTRASOUND,
|
||||
WCD9335_IRQ_VBAT_ATTACK,
|
||||
WCD9335_IRQ_VBAT_RESTORE,
|
||||
WCD9335_IRQ_SVA_OUTBOX1,
|
||||
WCD9335_IRQ_SVA_OUTBOX2,
|
||||
WCD9335_NUM_IRQS,
|
||||
};
|
||||
|
||||
#endif
|
||||
1348
include/linux/mfd/wcd9335/registers.h
Normal file
1348
include/linux/mfd/wcd9335/registers.h
Normal file
File diff suppressed because it is too large
Load Diff
56
include/linux/mfd/wcd934x/irq.h
Normal file
56
include/linux/mfd/wcd934x/irq.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __WCD934X_IRQ_H_
|
||||
#define __WCD934X_IRQ_H_
|
||||
|
||||
enum {
|
||||
/* INTR_REG 0 */
|
||||
WCD934X_IRQ_MISC = 1,
|
||||
WCD934X_IRQ_HPH_PA_OCPL_FAULT,
|
||||
WCD934X_IRQ_HPH_PA_OCPR_FAULT,
|
||||
WCD934X_IRQ_EAR_PA_OCP_FAULT,
|
||||
WCD934X_IRQ_HPH_PA_CNPL_COMPLETE,
|
||||
WCD934X_IRQ_HPH_PA_CNPR_COMPLETE,
|
||||
WCD934X_IRQ_EAR_PA_CNP_COMPLETE,
|
||||
/* INTR_REG 1 */
|
||||
WCD934X_IRQ_MBHC_SW_DET,
|
||||
WCD934X_IRQ_MBHC_ELECT_INS_REM_DET,
|
||||
WCD934X_IRQ_MBHC_BUTTON_PRESS_DET,
|
||||
WCD934X_IRQ_MBHC_BUTTON_RELEASE_DET,
|
||||
WCD934X_IRQ_MBHC_ELECT_INS_REM_LEG_DET,
|
||||
WCD934X_IRQ_RESERVED_0,
|
||||
WCD934X_IRQ_RESERVED_1,
|
||||
WCD934X_IRQ_RESERVED_2,
|
||||
/* INTR_REG 2 */
|
||||
WCD934X_IRQ_LINE_PA1_CNP_COMPLETE,
|
||||
WCD934X_IRQ_LINE_PA2_CNP_COMPLETE,
|
||||
WCD934X_IRQ_SLNQ_ANALOG_ERROR,
|
||||
WCD934X_IRQ_RESERVED_3,
|
||||
WCD934X_IRQ_SOUNDWIRE,
|
||||
WCD934X_IRQ_VDD_DIG_RAMP_COMPLETE,
|
||||
WCD934X_IRQ_RCO_ERROR,
|
||||
WCD934X_IRQ_CPE_ERROR,
|
||||
/* INTR_REG 3 */
|
||||
WCD934X_IRQ_MAD_AUDIO,
|
||||
WCD934X_IRQ_MAD_BEACON,
|
||||
WCD934X_IRQ_MAD_ULTRASOUND,
|
||||
WCD934X_IRQ_VBAT_ATTACK,
|
||||
WCD934X_IRQ_VBAT_RESTORE,
|
||||
WCD934X_IRQ_CPE1_INTR,
|
||||
WCD934X_IRQ_RESERVED_4,
|
||||
WCD934X_IRQ_SLNQ_DIGITAL,
|
||||
WCD934X_NUM_IRQS,
|
||||
};
|
||||
|
||||
#endif
|
||||
1848
include/linux/mfd/wcd934x/registers.h
Normal file
1848
include/linux/mfd/wcd934x/registers.h
Normal file
File diff suppressed because it is too large
Load Diff
440
include/linux/mfd/wcd9xxx/core.h
Normal file
440
include/linux/mfd/wcd9xxx/core.h
Normal file
@@ -0,0 +1,440 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#ifndef __MFD_TABLA_CORE_H__
|
||||
#define __MFD_TABLA_CORE_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/pm_qos.h>
|
||||
|
||||
#define WCD9XXX_MAX_IRQ_REGS 4
|
||||
#define WCD9XXX_MAX_NUM_IRQS (WCD9XXX_MAX_IRQ_REGS * 8)
|
||||
#define WCD9XXX_SLIM_NUM_PORT_REG 3
|
||||
#define TABLA_VERSION_1_0 0
|
||||
#define TABLA_VERSION_1_1 1
|
||||
#define TABLA_VERSION_2_0 2
|
||||
#define TABLA_IS_1_X(ver) \
|
||||
(((ver == TABLA_VERSION_1_0) || (ver == TABLA_VERSION_1_1)) ? 1 : 0)
|
||||
#define TABLA_IS_2_0(ver) ((ver == TABLA_VERSION_2_0) ? 1 : 0)
|
||||
|
||||
#define WCD9XXX_SUPPLY_BUCK_NAME "cdc-vdd-buck"
|
||||
|
||||
#define SITAR_VERSION_1P0 0
|
||||
#define SITAR_VERSION_1P1 1
|
||||
#define SITAR_IS_1P0(ver) \
|
||||
((ver == SITAR_VERSION_1P0) ? 1 : 0)
|
||||
#define SITAR_IS_1P1(ver) \
|
||||
((ver == SITAR_VERSION_1P1) ? 1 : 0)
|
||||
|
||||
#define TAIKO_VERSION_1_0 1
|
||||
#define TAIKO_IS_1_0(ver) \
|
||||
((ver == TAIKO_VERSION_1_0) ? 1 : 0)
|
||||
|
||||
#define TAPAN_VERSION_1_0 0
|
||||
#define TAPAN_IS_1_0(ver) \
|
||||
((ver == TAPAN_VERSION_1_0) ? 1 : 0)
|
||||
|
||||
#define TOMTOM_VERSION_1_0 1
|
||||
#define TOMTOM_IS_1_0(ver) \
|
||||
((ver == TOMTOM_VERSION_1_0) ? 1 : 0)
|
||||
|
||||
#define TASHA_VERSION_1_0 0
|
||||
#define TASHA_VERSION_1_1 1
|
||||
#define TASHA_VERSION_2_0 2
|
||||
|
||||
#define TASHA_IS_1_0(wcd) \
|
||||
((wcd->type == WCD9335 || wcd->type == WCD9326) ? \
|
||||
((wcd->version == TASHA_VERSION_1_0) ? 1 : 0) : 0)
|
||||
|
||||
#define TASHA_IS_1_1(wcd) \
|
||||
((wcd->type == WCD9335 || wcd->type == WCD9326) ? \
|
||||
((wcd->version == TASHA_VERSION_1_1) ? 1 : 0) : 0)
|
||||
|
||||
#define TASHA_IS_2_0(wcd) \
|
||||
((wcd->type == WCD9335 || wcd->type == WCD9326) ? \
|
||||
((wcd->version == TASHA_VERSION_2_0) ? 1 : 0) : 0)
|
||||
|
||||
/*
|
||||
* As fine version info cannot be retrieved before tavil probe.
|
||||
* Define three coarse versions for possible future use before tavil probe.
|
||||
*/
|
||||
#define TAVIL_VERSION_1_0 0
|
||||
#define TAVIL_VERSION_1_1 1
|
||||
#define TAVIL_VERSION_WCD9340_1_0 2
|
||||
#define TAVIL_VERSION_WCD9341_1_0 3
|
||||
#define TAVIL_VERSION_WCD9340_1_1 4
|
||||
#define TAVIL_VERSION_WCD9341_1_1 5
|
||||
|
||||
#define TAVIL_IS_1_0(wcd) \
|
||||
((wcd->type == WCD934X) ? \
|
||||
((wcd->version == TAVIL_VERSION_1_0 || \
|
||||
wcd->version == TAVIL_VERSION_WCD9340_1_0 || \
|
||||
wcd->version == TAVIL_VERSION_WCD9341_1_0) ? 1 : 0) : 0)
|
||||
#define TAVIL_IS_1_1(wcd) \
|
||||
((wcd->type == WCD934X) ? \
|
||||
((wcd->version == TAVIL_VERSION_1_1 || \
|
||||
wcd->version == TAVIL_VERSION_WCD9340_1_1 || \
|
||||
wcd->version == TAVIL_VERSION_WCD9341_1_1) ? 1 : 0) : 0)
|
||||
#define TAVIL_IS_WCD9340_1_0(wcd) \
|
||||
((wcd->type == WCD934X) ? \
|
||||
((wcd->version == TAVIL_VERSION_WCD9340_1_0) ? 1 : 0) : 0)
|
||||
#define TAVIL_IS_WCD9341_1_0(wcd) \
|
||||
((wcd->type == WCD934X) ? \
|
||||
((wcd->version == TAVIL_VERSION_WCD9341_1_0) ? 1 : 0) : 0)
|
||||
#define TAVIL_IS_WCD9340_1_1(wcd) \
|
||||
((wcd->type == WCD934X) ? \
|
||||
((wcd->version == TAVIL_VERSION_WCD9340_1_1) ? 1 : 0) : 0)
|
||||
#define TAVIL_IS_WCD9341_1_1(wcd) \
|
||||
((wcd->type == WCD934X) ? \
|
||||
((wcd->version == TAVIL_VERSION_WCD9341_1_1) ? 1 : 0) : 0)
|
||||
|
||||
#define IS_CODEC_TYPE(wcd, wcdtype) \
|
||||
((wcd->type == wcdtype) ? true : false)
|
||||
#define IS_CODEC_VERSION(wcd, wcdversion) \
|
||||
((wcd->version == wcdversion) ? true : false)
|
||||
|
||||
enum {
|
||||
CDC_V_1_0,
|
||||
CDC_V_1_1,
|
||||
CDC_V_2_0,
|
||||
};
|
||||
|
||||
enum codec_variant {
|
||||
WCD9XXX,
|
||||
WCD9330,
|
||||
WCD9335,
|
||||
WCD9326,
|
||||
WCD934X,
|
||||
};
|
||||
|
||||
enum wcd9xxx_slim_slave_addr_type {
|
||||
WCD9XXX_SLIM_SLAVE_ADDR_TYPE_0,
|
||||
WCD9XXX_SLIM_SLAVE_ADDR_TYPE_1,
|
||||
};
|
||||
|
||||
enum wcd9xxx_pm_state {
|
||||
WCD9XXX_PM_SLEEPABLE,
|
||||
WCD9XXX_PM_AWAKE,
|
||||
WCD9XXX_PM_ASLEEP,
|
||||
};
|
||||
|
||||
enum {
|
||||
WCD9XXX_INTR_STATUS_BASE = 0,
|
||||
WCD9XXX_INTR_CLEAR_BASE,
|
||||
WCD9XXX_INTR_MASK_BASE,
|
||||
WCD9XXX_INTR_LEVEL_BASE,
|
||||
WCD9XXX_INTR_CLR_COMMIT,
|
||||
WCD9XXX_INTR_REG_MAX,
|
||||
};
|
||||
|
||||
enum wcd9xxx_intf_status {
|
||||
WCD9XXX_INTERFACE_TYPE_PROBING,
|
||||
WCD9XXX_INTERFACE_TYPE_SLIMBUS,
|
||||
WCD9XXX_INTERFACE_TYPE_I2C,
|
||||
};
|
||||
|
||||
enum {
|
||||
/* INTR_REG 0 */
|
||||
WCD9XXX_IRQ_SLIMBUS = 0,
|
||||
WCD9XXX_IRQ_MBHC_REMOVAL,
|
||||
WCD9XXX_IRQ_MBHC_SHORT_TERM,
|
||||
WCD9XXX_IRQ_MBHC_PRESS,
|
||||
WCD9XXX_IRQ_MBHC_RELEASE,
|
||||
WCD9XXX_IRQ_MBHC_POTENTIAL,
|
||||
WCD9XXX_IRQ_MBHC_INSERTION,
|
||||
WCD9XXX_IRQ_BG_PRECHARGE,
|
||||
/* INTR_REG 1 */
|
||||
WCD9XXX_IRQ_PA1_STARTUP,
|
||||
WCD9XXX_IRQ_PA2_STARTUP,
|
||||
WCD9XXX_IRQ_PA3_STARTUP,
|
||||
WCD9XXX_IRQ_PA4_STARTUP,
|
||||
WCD9306_IRQ_HPH_PA_OCPR_FAULT = WCD9XXX_IRQ_PA4_STARTUP,
|
||||
WCD9XXX_IRQ_PA5_STARTUP,
|
||||
WCD9XXX_IRQ_MICBIAS1_PRECHARGE,
|
||||
WCD9306_IRQ_HPH_PA_OCPL_FAULT = WCD9XXX_IRQ_MICBIAS1_PRECHARGE,
|
||||
WCD9XXX_IRQ_MICBIAS2_PRECHARGE,
|
||||
WCD9XXX_IRQ_MICBIAS3_PRECHARGE,
|
||||
/* INTR_REG 2 */
|
||||
WCD9XXX_IRQ_HPH_PA_OCPL_FAULT,
|
||||
WCD9XXX_IRQ_HPH_PA_OCPR_FAULT,
|
||||
WCD9XXX_IRQ_EAR_PA_OCPL_FAULT,
|
||||
WCD9XXX_IRQ_HPH_L_PA_STARTUP,
|
||||
WCD9XXX_IRQ_HPH_R_PA_STARTUP,
|
||||
WCD9320_IRQ_EAR_PA_STARTUP,
|
||||
WCD9306_IRQ_MBHC_JACK_SWITCH = WCD9320_IRQ_EAR_PA_STARTUP,
|
||||
WCD9310_NUM_IRQS,
|
||||
WCD9XXX_IRQ_RESERVED_0 = WCD9310_NUM_IRQS,
|
||||
WCD9XXX_IRQ_RESERVED_1,
|
||||
WCD9330_IRQ_SVASS_ERR_EXCEPTION = WCD9310_NUM_IRQS,
|
||||
WCD9330_IRQ_MBHC_JACK_SWITCH,
|
||||
/* INTR_REG 3 */
|
||||
WCD9XXX_IRQ_MAD_AUDIO,
|
||||
WCD9XXX_IRQ_MAD_ULTRASOUND,
|
||||
WCD9XXX_IRQ_MAD_BEACON,
|
||||
WCD9XXX_IRQ_SPEAKER_CLIPPING,
|
||||
WCD9320_IRQ_MBHC_JACK_SWITCH,
|
||||
WCD9306_NUM_IRQS,
|
||||
WCD9XXX_IRQ_VBAT_MONITOR_ATTACK = WCD9306_NUM_IRQS,
|
||||
WCD9XXX_IRQ_VBAT_MONITOR_RELEASE,
|
||||
WCD9XXX_NUM_IRQS,
|
||||
/* WCD9330 INTR1_REG 3*/
|
||||
WCD9330_IRQ_SVASS_ENGINE = WCD9XXX_IRQ_MAD_AUDIO,
|
||||
WCD9330_IRQ_MAD_AUDIO,
|
||||
WCD9330_IRQ_MAD_ULTRASOUND,
|
||||
WCD9330_IRQ_MAD_BEACON,
|
||||
WCD9330_IRQ_SPEAKER1_CLIPPING,
|
||||
WCD9330_IRQ_SPEAKER2_CLIPPING,
|
||||
WCD9330_IRQ_VBAT_MONITOR_ATTACK,
|
||||
WCD9330_IRQ_VBAT_MONITOR_RELEASE,
|
||||
WCD9330_NUM_IRQS,
|
||||
WCD9XXX_IRQ_RESERVED_2 = WCD9330_NUM_IRQS,
|
||||
};
|
||||
|
||||
enum {
|
||||
TABLA_NUM_IRQS = WCD9310_NUM_IRQS,
|
||||
SITAR_NUM_IRQS = WCD9310_NUM_IRQS,
|
||||
TAIKO_NUM_IRQS = WCD9XXX_NUM_IRQS,
|
||||
TAPAN_NUM_IRQS = WCD9306_NUM_IRQS,
|
||||
TOMTOM_NUM_IRQS = WCD9330_NUM_IRQS,
|
||||
};
|
||||
|
||||
struct intr_data {
|
||||
int intr_num;
|
||||
bool clear_first;
|
||||
};
|
||||
|
||||
struct wcd9xxx_core_resource {
|
||||
struct mutex irq_lock;
|
||||
struct mutex nested_irq_lock;
|
||||
|
||||
enum wcd9xxx_pm_state pm_state;
|
||||
struct mutex pm_lock;
|
||||
/* pm_wq notifies change of pm_state */
|
||||
wait_queue_head_t pm_wq;
|
||||
struct pm_qos_request pm_qos_req;
|
||||
int wlock_holders;
|
||||
|
||||
|
||||
/* holds the table of interrupts per codec */
|
||||
const struct intr_data *intr_table;
|
||||
int intr_table_size;
|
||||
unsigned int irq_base;
|
||||
unsigned int irq;
|
||||
u8 irq_masks_cur[WCD9XXX_MAX_IRQ_REGS];
|
||||
u8 irq_masks_cache[WCD9XXX_MAX_IRQ_REGS];
|
||||
bool irq_level_high[WCD9XXX_MAX_NUM_IRQS];
|
||||
int num_irqs;
|
||||
int num_irq_regs;
|
||||
u16 intr_reg[WCD9XXX_INTR_REG_MAX];
|
||||
struct regmap *wcd_core_regmap;
|
||||
|
||||
/* Pointer to parent container data structure */
|
||||
void *parent;
|
||||
|
||||
struct device *dev;
|
||||
struct irq_domain *domain;
|
||||
};
|
||||
|
||||
/*
|
||||
* data structure for Slimbus and I2S channel.
|
||||
* Some of fields are only used in smilbus mode
|
||||
*/
|
||||
struct wcd9xxx_ch {
|
||||
u32 sph; /* share channel handle - slimbus only */
|
||||
u32 ch_num; /*
|
||||
* vitrual channel number, such as 128 -144.
|
||||
* apply for slimbus only
|
||||
*/
|
||||
u16 ch_h; /* chanel handle - slimbus only */
|
||||
u16 port; /*
|
||||
* tabla port for RX and TX
|
||||
* such as 0-9 for TX and 10 -16 for RX
|
||||
* apply for both i2s and slimbus
|
||||
*/
|
||||
u16 shift; /*
|
||||
* shift bit for RX and TX
|
||||
* apply for both i2s and slimbus
|
||||
*/
|
||||
struct list_head list; /*
|
||||
* channel link list
|
||||
* apply for both i2s and slimbus
|
||||
*/
|
||||
};
|
||||
|
||||
struct wcd9xxx_codec_dai_data {
|
||||
u32 rate; /* sample rate */
|
||||
u32 bit_width; /* sit width 16,24,32 */
|
||||
struct list_head wcd9xxx_ch_list; /* channel list */
|
||||
u16 grph; /* slimbus group handle */
|
||||
unsigned long ch_mask;
|
||||
wait_queue_head_t dai_wait;
|
||||
bool bus_down_in_recovery;
|
||||
};
|
||||
|
||||
#define WCD9XXX_CH(xport, xshift) \
|
||||
{.port = xport, .shift = xshift}
|
||||
|
||||
enum wcd9xxx_chipid_major {
|
||||
TABLA_MAJOR = cpu_to_le16(0x100),
|
||||
SITAR_MAJOR = cpu_to_le16(0x101),
|
||||
TAIKO_MAJOR = cpu_to_le16(0x102),
|
||||
TAPAN_MAJOR = cpu_to_le16(0x103),
|
||||
TOMTOM_MAJOR = cpu_to_le16(0x105),
|
||||
TASHA_MAJOR = cpu_to_le16(0x0),
|
||||
TASHA2P0_MAJOR = cpu_to_le16(0x107),
|
||||
TAVIL_MAJOR = cpu_to_le16(0x108),
|
||||
};
|
||||
|
||||
enum codec_power_states {
|
||||
WCD_REGION_POWER_COLLAPSE_REMOVE,
|
||||
WCD_REGION_POWER_COLLAPSE_BEGIN,
|
||||
WCD_REGION_POWER_DOWN,
|
||||
};
|
||||
|
||||
enum wcd_power_regions {
|
||||
WCD9XXX_DIG_CORE_REGION_1,
|
||||
WCD9XXX_MAX_PWR_REGIONS,
|
||||
};
|
||||
|
||||
struct wcd9xxx_codec_type {
|
||||
u16 id_major;
|
||||
u16 id_minor;
|
||||
struct mfd_cell *dev;
|
||||
int size;
|
||||
int num_irqs;
|
||||
int version; /* -1 to retrieve version from chip version register */
|
||||
enum wcd9xxx_slim_slave_addr_type slim_slave_type;
|
||||
u16 i2c_chip_status;
|
||||
const struct intr_data *intr_tbl;
|
||||
int intr_tbl_size;
|
||||
u16 intr_reg[WCD9XXX_INTR_REG_MAX];
|
||||
};
|
||||
|
||||
struct wcd9xxx_power_region {
|
||||
enum codec_power_states power_state;
|
||||
u16 pwr_collapse_reg_min;
|
||||
u16 pwr_collapse_reg_max;
|
||||
};
|
||||
|
||||
struct wcd9xxx {
|
||||
struct device *dev;
|
||||
struct slim_device *slim;
|
||||
struct slim_device *slim_slave;
|
||||
struct mutex io_lock;
|
||||
struct mutex xfer_lock;
|
||||
struct mutex reset_lock;
|
||||
u8 version;
|
||||
|
||||
int reset_gpio;
|
||||
struct device_node *wcd_rst_np;
|
||||
|
||||
int (*read_dev)(struct wcd9xxx *wcd9xxx, unsigned short reg,
|
||||
int bytes, void *dest, bool interface_reg);
|
||||
int (*write_dev)(struct wcd9xxx *wcd9xxx, unsigned short reg,
|
||||
int bytes, void *src, bool interface_reg);
|
||||
int (*multi_reg_write)(struct wcd9xxx *wcd9xxx, const void *data,
|
||||
size_t count);
|
||||
int (*dev_down)(struct wcd9xxx *wcd9xxx);
|
||||
int (*post_reset)(struct wcd9xxx *wcd9xxx);
|
||||
|
||||
void *ssr_priv;
|
||||
bool dev_up;
|
||||
|
||||
u32 num_of_supplies;
|
||||
struct regulator_bulk_data *supplies;
|
||||
|
||||
struct wcd9xxx_core_resource core_res;
|
||||
|
||||
u16 id_minor;
|
||||
u16 id_major;
|
||||
|
||||
/* Slimbus or I2S port */
|
||||
u32 num_rx_port;
|
||||
u32 num_tx_port;
|
||||
struct wcd9xxx_ch *rx_chs;
|
||||
struct wcd9xxx_ch *tx_chs;
|
||||
u32 mclk_rate;
|
||||
enum codec_variant type;
|
||||
struct regmap *regmap;
|
||||
|
||||
struct wcd9xxx_codec_type *codec_type;
|
||||
bool prev_pg_valid;
|
||||
u8 prev_pg;
|
||||
u8 avoid_cdc_rstlow;
|
||||
struct wcd9xxx_power_region *wcd9xxx_pwr[WCD9XXX_MAX_PWR_REGIONS];
|
||||
};
|
||||
|
||||
struct wcd9xxx_reg_val {
|
||||
unsigned short reg; /* register address */
|
||||
u8 *buf; /* buffer to be written to reg. addr */
|
||||
int bytes; /* number of bytes to be written */
|
||||
};
|
||||
|
||||
int wcd9xxx_interface_reg_read(struct wcd9xxx *wcd9xxx, unsigned short reg);
|
||||
int wcd9xxx_interface_reg_write(struct wcd9xxx *wcd9xxx, unsigned short reg,
|
||||
u8 val);
|
||||
int wcd9xxx_get_logical_addresses(u8 *pgd_la, u8 *inf_la);
|
||||
int wcd9xxx_slim_write_repeat(struct wcd9xxx *wcd9xxx, unsigned short reg,
|
||||
int bytes, void *src);
|
||||
int wcd9xxx_slim_reserve_bw(struct wcd9xxx *wcd9xxx,
|
||||
u32 bw_ops, bool commit);
|
||||
int wcd9xxx_set_power_state(struct wcd9xxx *wcd9xxx, enum codec_power_states,
|
||||
enum wcd_power_regions);
|
||||
int wcd9xxx_get_current_power_state(struct wcd9xxx *wcd9xxx,
|
||||
enum wcd_power_regions);
|
||||
|
||||
int wcd9xxx_page_write(struct wcd9xxx *wcd9xxx, unsigned short *reg);
|
||||
|
||||
int wcd9xxx_slim_bulk_write(struct wcd9xxx *wcd9xxx,
|
||||
struct wcd9xxx_reg_val *bulk_reg,
|
||||
unsigned int size, bool interface);
|
||||
|
||||
extern int wcd9xxx_core_res_init(
|
||||
struct wcd9xxx_core_resource *wcd9xxx_core_res,
|
||||
int num_irqs, int num_irq_regs, struct regmap *wcd_regmap);
|
||||
|
||||
extern void wcd9xxx_core_res_deinit(
|
||||
struct wcd9xxx_core_resource *wcd9xxx_core_res);
|
||||
|
||||
extern int wcd9xxx_core_res_suspend(
|
||||
struct wcd9xxx_core_resource *wcd9xxx_core_res,
|
||||
pm_message_t pmesg);
|
||||
|
||||
extern int wcd9xxx_core_res_resume(
|
||||
struct wcd9xxx_core_resource *wcd9xxx_core_res);
|
||||
|
||||
extern int wcd9xxx_core_irq_init(
|
||||
struct wcd9xxx_core_resource *wcd9xxx_core_res);
|
||||
|
||||
extern int wcd9xxx_assign_irq(struct wcd9xxx_core_resource *wcd9xxx_core_res,
|
||||
unsigned int irq,
|
||||
unsigned int irq_base);
|
||||
|
||||
extern enum wcd9xxx_intf_status wcd9xxx_get_intf_type(void);
|
||||
extern void wcd9xxx_set_intf_type(enum wcd9xxx_intf_status);
|
||||
|
||||
extern enum wcd9xxx_pm_state wcd9xxx_pm_cmpxchg(
|
||||
struct wcd9xxx_core_resource *wcd9xxx_core_res,
|
||||
enum wcd9xxx_pm_state o,
|
||||
enum wcd9xxx_pm_state n);
|
||||
static inline int __init wcd9xxx_irq_of_init(struct device_node *node,
|
||||
struct device_node *parent)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wcd9xxx_init(void);
|
||||
void wcd9xxx_exit(void);
|
||||
#endif
|
||||
197
include/linux/mfd/wcd9xxx/pdata.h
Normal file
197
include/linux/mfd/wcd9xxx/pdata.h
Normal file
@@ -0,0 +1,197 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#ifndef __MFD_WCD9XXX_PDATA_H__
|
||||
|
||||
#define __MFD_WCD9XXX_PDATA_H__
|
||||
|
||||
#include <linux/slimbus/slimbus.h>
|
||||
#include <linux/mfd/msm-cdc-supply.h>
|
||||
|
||||
#define MICBIAS_EXT_BYP_CAP 0x00
|
||||
#define MICBIAS_NO_EXT_BYP_CAP 0x01
|
||||
|
||||
#define SITAR_LDOH_1P95_V 0x0
|
||||
#define SITAR_LDOH_2P35_V 0x1
|
||||
#define SITAR_LDOH_2P75_V 0x2
|
||||
#define SITAR_LDOH_2P85_V 0x3
|
||||
|
||||
#define SITAR_CFILT1_SEL 0x0
|
||||
#define SITAR_CFILT2_SEL 0x1
|
||||
#define SITAR_CFILT3_SEL 0x2
|
||||
|
||||
#define WCD9XXX_LDOH_1P95_V 0x0
|
||||
#define WCD9XXX_LDOH_2P35_V 0x1
|
||||
#define WCD9XXX_LDOH_2P75_V 0x2
|
||||
#define WCD9XXX_LDOH_2P85_V 0x3
|
||||
#define WCD9XXX_LDOH_3P0_V 0x3
|
||||
|
||||
#define TABLA_LDOH_1P95_V 0x0
|
||||
#define TABLA_LDOH_2P35_V 0x1
|
||||
#define TABLA_LDOH_2P75_V 0x2
|
||||
#define TABLA_LDOH_2P85_V 0x3
|
||||
|
||||
#define TABLA_CFILT1_SEL 0x0
|
||||
#define TABLA_CFILT2_SEL 0x1
|
||||
#define TABLA_CFILT3_SEL 0x2
|
||||
|
||||
#define MAX_AMIC_CHANNEL 7
|
||||
|
||||
#define TABLA_OCP_300_MA 0x0
|
||||
#define TABLA_OCP_350_MA 0x2
|
||||
#define TABLA_OCP_365_MA 0x3
|
||||
#define TABLA_OCP_150_MA 0x4
|
||||
#define TABLA_OCP_190_MA 0x6
|
||||
#define TABLA_OCP_220_MA 0x7
|
||||
|
||||
#define TABLA_DCYCLE_255 0x0
|
||||
#define TABLA_DCYCLE_511 0x1
|
||||
#define TABLA_DCYCLE_767 0x2
|
||||
#define TABLA_DCYCLE_1023 0x3
|
||||
#define TABLA_DCYCLE_1279 0x4
|
||||
#define TABLA_DCYCLE_1535 0x5
|
||||
#define TABLA_DCYCLE_1791 0x6
|
||||
#define TABLA_DCYCLE_2047 0x7
|
||||
#define TABLA_DCYCLE_2303 0x8
|
||||
#define TABLA_DCYCLE_2559 0x9
|
||||
#define TABLA_DCYCLE_2815 0xA
|
||||
#define TABLA_DCYCLE_3071 0xB
|
||||
#define TABLA_DCYCLE_3327 0xC
|
||||
#define TABLA_DCYCLE_3583 0xD
|
||||
#define TABLA_DCYCLE_3839 0xE
|
||||
#define TABLA_DCYCLE_4095 0xF
|
||||
|
||||
#define WCD9XXX_MCLK_CLK_12P288MHZ 12288000
|
||||
#define WCD9XXX_MCLK_CLK_9P6HZ 9600000
|
||||
|
||||
/* Only valid for 9.6 MHz mclk */
|
||||
#define WCD9XXX_DMIC_SAMPLE_RATE_600KHZ 600000
|
||||
#define WCD9XXX_DMIC_SAMPLE_RATE_2P4MHZ 2400000
|
||||
#define WCD9XXX_DMIC_SAMPLE_RATE_3P2MHZ 3200000
|
||||
#define WCD9XXX_DMIC_SAMPLE_RATE_4P8MHZ 4800000
|
||||
|
||||
/* Only valid for 12.288 MHz mclk */
|
||||
#define WCD9XXX_DMIC_SAMPLE_RATE_768KHZ 768000
|
||||
#define WCD9XXX_DMIC_SAMPLE_RATE_2P048MHZ 2048000
|
||||
#define WCD9XXX_DMIC_SAMPLE_RATE_3P072MHZ 3072000
|
||||
#define WCD9XXX_DMIC_SAMPLE_RATE_4P096MHZ 4096000
|
||||
#define WCD9XXX_DMIC_SAMPLE_RATE_6P144MHZ 6144000
|
||||
|
||||
#define WCD9XXX_DMIC_SAMPLE_RATE_UNDEFINED 0
|
||||
|
||||
#define WCD9XXX_DMIC_CLK_DRIVE_UNDEFINED 0
|
||||
|
||||
struct wcd9xxx_amic {
|
||||
/*legacy mode, txfe_enable and txfe_buff take 7 input
|
||||
* each bit represent the channel / TXFE number
|
||||
* and numbered as below
|
||||
* bit 0 = channel 1 / TXFE1_ENABLE / TXFE1_BUFF
|
||||
* bit 1 = channel 2 / TXFE2_ENABLE / TXFE2_BUFF
|
||||
* ...
|
||||
* bit 7 = channel 7 / TXFE7_ENABLE / TXFE7_BUFF
|
||||
*/
|
||||
u8 legacy_mode:MAX_AMIC_CHANNEL;
|
||||
u8 txfe_enable:MAX_AMIC_CHANNEL;
|
||||
u8 txfe_buff:MAX_AMIC_CHANNEL;
|
||||
u8 use_pdata:MAX_AMIC_CHANNEL;
|
||||
};
|
||||
|
||||
/* Each micbias can be assigned to one of three cfilters
|
||||
* Vbatt_min >= .15V + ldoh_v
|
||||
* ldoh_v >= .15v + cfiltx_mv
|
||||
* If ldoh_v = 1.95 160 mv < cfiltx_mv < 1800 mv
|
||||
* If ldoh_v = 2.35 200 mv < cfiltx_mv < 2200 mv
|
||||
* If ldoh_v = 2.75 240 mv < cfiltx_mv < 2600 mv
|
||||
* If ldoh_v = 2.85 250 mv < cfiltx_mv < 2700 mv
|
||||
*/
|
||||
|
||||
struct wcd9xxx_micbias_setting {
|
||||
u8 ldoh_v;
|
||||
u32 cfilt1_mv; /* in mv */
|
||||
u32 cfilt2_mv; /* in mv */
|
||||
u32 cfilt3_mv; /* in mv */
|
||||
u32 micb1_mv;
|
||||
u32 micb2_mv;
|
||||
u32 micb3_mv;
|
||||
u32 micb4_mv;
|
||||
/* Different WCD9xxx series codecs may not
|
||||
* have 4 mic biases. If a codec has fewer
|
||||
* mic biases, some of these properties will
|
||||
* not be used.
|
||||
*/
|
||||
u8 bias1_cfilt_sel;
|
||||
u8 bias2_cfilt_sel;
|
||||
u8 bias3_cfilt_sel;
|
||||
u8 bias4_cfilt_sel;
|
||||
u8 bias1_cap_mode;
|
||||
u8 bias2_cap_mode;
|
||||
u8 bias3_cap_mode;
|
||||
u8 bias4_cap_mode;
|
||||
bool bias2_is_headset_only;
|
||||
};
|
||||
|
||||
struct wcd9xxx_ocp_setting {
|
||||
unsigned int use_pdata:1; /* 0 - use sys default as recommended */
|
||||
unsigned int num_attempts:4; /* up to 15 attempts */
|
||||
unsigned int run_time:4; /* in duty cycle */
|
||||
unsigned int wait_time:4; /* in duty cycle */
|
||||
unsigned int hph_ocp_limit:3; /* Headphone OCP current limit */
|
||||
};
|
||||
|
||||
#define WCD9XXX_MAX_REGULATOR 9
|
||||
/*
|
||||
* format : TABLA_<POWER_SUPPLY_PIN_NAME>_CUR_MAX
|
||||
*
|
||||
* <POWER_SUPPLY_PIN_NAME> from Tabla objective spec
|
||||
*/
|
||||
|
||||
#define WCD9XXX_CDC_VDDA_CP_CUR_MAX 500000
|
||||
#define WCD9XXX_CDC_VDDA_RX_CUR_MAX 20000
|
||||
#define WCD9XXX_CDC_VDDA_TX_CUR_MAX 20000
|
||||
#define WCD9XXX_VDDIO_CDC_CUR_MAX 5000
|
||||
|
||||
#define WCD9XXX_VDDD_CDC_D_CUR_MAX 5000
|
||||
#define WCD9XXX_VDDD_CDC_A_CUR_MAX 5000
|
||||
|
||||
#define WCD9XXX_VDD_SPKDRV_NAME "cdc-vdd-spkdrv"
|
||||
#define WCD9XXX_VDD_SPKDRV2_NAME "cdc-vdd-spkdrv-2"
|
||||
|
||||
struct wcd9xxx_regulator {
|
||||
const char *name;
|
||||
int min_uV;
|
||||
int max_uV;
|
||||
int optimum_uA;
|
||||
bool ondemand;
|
||||
struct regulator *regulator;
|
||||
};
|
||||
|
||||
struct wcd9xxx_pdata {
|
||||
int irq;
|
||||
int irq_base;
|
||||
int num_irqs;
|
||||
int reset_gpio;
|
||||
struct device_node *wcd_rst_np;
|
||||
struct wcd9xxx_amic amic_settings;
|
||||
struct slim_device slimbus_slave_device;
|
||||
struct wcd9xxx_micbias_setting micbias;
|
||||
struct wcd9xxx_ocp_setting ocp;
|
||||
struct cdc_regulator *regulator;
|
||||
int num_supplies;
|
||||
u32 mclk_rate;
|
||||
u32 dmic_sample_rate;
|
||||
u32 mad_dmic_sample_rate;
|
||||
u32 ecpp_dmic_sample_rate;
|
||||
u32 dmic_clk_drv;
|
||||
u16 use_pinctrl;
|
||||
};
|
||||
|
||||
#endif
|
||||
37
include/linux/mfd/wcd9xxx/wcd9xxx-irq.h
Normal file
37
include/linux/mfd/wcd9xxx/wcd9xxx-irq.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/* 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/types.h>
|
||||
#include <linux/mfd/wcd9xxx/core.h>
|
||||
|
||||
#ifndef __MFD_WCD9XXX_IRQ_H
|
||||
#define __MFD_WCD9XXX_IRQ_H
|
||||
bool wcd9xxx_lock_sleep(struct wcd9xxx_core_resource *wcd9xxx_res);
|
||||
void wcd9xxx_unlock_sleep(struct wcd9xxx_core_resource *wcd9xxx_res);
|
||||
void wcd9xxx_nested_irq_lock(struct wcd9xxx_core_resource *wcd9xxx_res);
|
||||
void wcd9xxx_nested_irq_unlock(struct wcd9xxx_core_resource *wcd9xxx_res);
|
||||
int wcd9xxx_request_irq(struct wcd9xxx_core_resource *wcd9xxx_res, int irq,
|
||||
irq_handler_t handler, const char *name, void *data);
|
||||
|
||||
void wcd9xxx_free_irq(struct wcd9xxx_core_resource *wcd9xxx_res,
|
||||
int irq, void *data);
|
||||
void wcd9xxx_enable_irq(struct wcd9xxx_core_resource *wcd9xxx_res, int irq);
|
||||
void wcd9xxx_disable_irq(struct wcd9xxx_core_resource *wcd9xxx_res,
|
||||
int irq);
|
||||
void wcd9xxx_disable_irq_sync(struct wcd9xxx_core_resource *wcd9xxx_res,
|
||||
int irq);
|
||||
|
||||
int wcd9xxx_irq_init(struct wcd9xxx_core_resource *wcd9xxx_res);
|
||||
void wcd9xxx_irq_exit(struct wcd9xxx_core_resource *wcd9xxx_res);
|
||||
int wcd9xxx_irq_drv_init(void);
|
||||
void wcd9xxx_irq_drv_exit(void);
|
||||
#endif
|
||||
119
include/linux/mfd/wcd9xxx/wcd9xxx-slimslave.h
Normal file
119
include/linux/mfd/wcd9xxx/wcd9xxx-slimslave.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/* Copyright (c) 2012-2015, 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 __WCD9310_SLIMSLAVE_H_
|
||||
#define __WCD9310_SLIMSLAVE_H_
|
||||
|
||||
#include <linux/slimbus/slimbus.h>
|
||||
#include <linux/mfd/wcd9xxx/core.h>
|
||||
|
||||
|
||||
/*
|
||||
* client is expected to give port ids in the range of
|
||||
* 1-10 for pre Taiko Tx ports and 1-16 for Taiko
|
||||
* 1-7 for pre Taiko Rx ports and 1-16 for Tako,
|
||||
* we need to add offset for getting the absolute slave
|
||||
* port id before configuring the HW
|
||||
*/
|
||||
#define TABLA_SB_PGD_MAX_NUMBER_OF_TX_SLAVE_DEV_PORTS 10
|
||||
#define TAIKO_SB_PGD_MAX_NUMBER_OF_TX_SLAVE_DEV_PORTS 16
|
||||
|
||||
#define SLIM_MAX_TX_PORTS TAIKO_SB_PGD_MAX_NUMBER_OF_TX_SLAVE_DEV_PORTS
|
||||
|
||||
#define TABLA_SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS \
|
||||
TABLA_SB_PGD_MAX_NUMBER_OF_TX_SLAVE_DEV_PORTS
|
||||
#define TAIKO_SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS \
|
||||
TAIKO_SB_PGD_MAX_NUMBER_OF_TX_SLAVE_DEV_PORTS
|
||||
|
||||
#define TABLA_SB_PGD_MAX_NUMBER_OF_RX_SLAVE_DEV_PORTS 7
|
||||
#define TAIKO_SB_PGD_MAX_NUMBER_OF_RX_SLAVE_DEV_PORTS 13
|
||||
|
||||
#define SLIM_MAX_RX_PORTS TAIKO_SB_PGD_MAX_NUMBER_OF_RX_SLAVE_DEV_PORTS
|
||||
|
||||
#define SLIM_MAX_REG_ADDR (0x180 + 4 * (SLIM_MAX_RX_PORTS))
|
||||
|
||||
#define TABLA_SB_PGD_RX_PORT_MULTI_CHANNEL_0_START_PORT_ID \
|
||||
TABLA_SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS
|
||||
#define TAIKO_SB_PGD_RX_PORT_MULTI_CHANNEL_0_START_PORT_ID \
|
||||
TAIKO_SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS
|
||||
|
||||
#define TABLA_SB_PGD_RX_PORT_MULTI_CHANNEL_0_END_PORT_ID 16
|
||||
#define TAIKO_SB_PGD_RX_PORT_MULTI_CHANNEL_0_END_PORT_ID 31
|
||||
|
||||
#define TABLA_SB_PGD_TX_PORT_MULTI_CHANNEL_1_END_PORT_ID 9
|
||||
#define TAIKO_SB_PGD_TX_PORT_MULTI_CHANNEL_1_END_PORT_ID 15
|
||||
|
||||
/* below details are taken from SLIMBUS slave SWI */
|
||||
#define SB_PGD_PORT_BASE 0x000
|
||||
|
||||
#define SB_PGD_PORT_CFG_BYTE_ADDR(offset, port_num) \
|
||||
(SB_PGD_PORT_BASE + offset + (1 * port_num))
|
||||
|
||||
#define SB_PGD_TX_PORT_MULTI_CHANNEL_0(port_num) \
|
||||
(SB_PGD_PORT_BASE + 0x100 + 4*port_num)
|
||||
#define SB_PGD_TX_PORT_MULTI_CHANNEL_0_START_PORT_ID 0
|
||||
#define SB_PGD_TX_PORT_MULTI_CHANNEL_0_END_PORT_ID 7
|
||||
|
||||
#define SB_PGD_TX_PORT_MULTI_CHANNEL_1(port_num) \
|
||||
(SB_PGD_PORT_BASE + 0x101 + 4*port_num)
|
||||
#define SB_PGD_TX_PORT_MULTI_CHANNEL_1_START_PORT_ID 8
|
||||
|
||||
#define SB_PGD_RX_PORT_MULTI_CHANNEL_0(offset, port_num) \
|
||||
(SB_PGD_PORT_BASE + offset + (4 * port_num))
|
||||
|
||||
/* slave port water mark level
|
||||
* (0: 6bytes, 1: 9bytes, 2: 12 bytes, 3: 15 bytes)
|
||||
*/
|
||||
#define SLAVE_PORT_WATER_MARK_6BYTES 0
|
||||
#define SLAVE_PORT_WATER_MARK_9BYTES 1
|
||||
#define SLAVE_PORT_WATER_MARK_12BYTES 2
|
||||
#define SLAVE_PORT_WATER_MARK_15BYTES 3
|
||||
#define SLAVE_PORT_WATER_MARK_SHIFT 1
|
||||
#define SLAVE_PORT_ENABLE 1
|
||||
#define SLAVE_PORT_DISABLE 0
|
||||
#define WATER_MARK_VAL \
|
||||
((SLAVE_PORT_WATER_MARK_12BYTES << SLAVE_PORT_WATER_MARK_SHIFT) | \
|
||||
(SLAVE_PORT_ENABLE))
|
||||
#define BASE_CH_NUM 128
|
||||
|
||||
|
||||
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 wcd9xxx_deinit_slimslave(struct wcd9xxx *wcd9xxx);
|
||||
|
||||
int wcd9xxx_cfg_slim_sch_rx(struct wcd9xxx *wcd9xxx,
|
||||
struct list_head *wcd9xxx_ch_list,
|
||||
unsigned int rate, unsigned int bit_width,
|
||||
u16 *grph);
|
||||
int wcd9xxx_cfg_slim_sch_tx(struct wcd9xxx *wcd9xxx,
|
||||
struct list_head *wcd9xxx_ch_list,
|
||||
unsigned int rate, unsigned int bit_width,
|
||||
u16 *grph);
|
||||
int wcd9xxx_close_slim_sch_rx(struct wcd9xxx *wcd9xxx,
|
||||
struct list_head *wcd9xxx_ch_list, u16 grph);
|
||||
int wcd9xxx_close_slim_sch_tx(struct wcd9xxx *wcd9xxx,
|
||||
struct list_head *wcd9xxx_ch_list, u16 grph);
|
||||
int wcd9xxx_get_channel(struct wcd9xxx *wcd9xxx,
|
||||
unsigned int *rx_ch,
|
||||
unsigned int *tx_ch);
|
||||
int wcd9xxx_get_slave_port(unsigned int ch_num);
|
||||
int wcd9xxx_disconnect_port(struct wcd9xxx *wcd9xxx,
|
||||
struct list_head *wcd9xxx_ch_list, u16 grph);
|
||||
int wcd9xxx_rx_vport_validation(u32 port_id,
|
||||
struct list_head *codec_dai_list);
|
||||
int wcd9xxx_tx_vport_validation(u32 vtable, u32 port_id,
|
||||
struct wcd9xxx_codec_dai_data *codec_dai,
|
||||
u32 num_codec_dais);
|
||||
#endif /* __WCD9310_SLIMSLAVE_H_ */
|
||||
40
include/linux/mfd/wcd9xxx/wcd9xxx-utils.h
Normal file
40
include/linux/mfd/wcd9xxx/wcd9xxx-utils.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#ifndef __WCD9XXX_UTILS_H__
|
||||
#define __WCD9XXX_UTILS_H__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/mfd/wcd9xxx/pdata.h>
|
||||
#include <linux/mfd/wcd9xxx/core.h>
|
||||
|
||||
struct wcd9xxx_pdata *wcd9xxx_populate_dt_data(struct device *dev);
|
||||
int wcd9xxx_bringup(struct device *dev);
|
||||
int wcd9xxx_bringdown(struct device *dev);
|
||||
struct regmap *wcd9xxx_regmap_init(struct device *dev,
|
||||
const struct regmap_config *config);
|
||||
int wcd9xxx_reset(struct device *dev);
|
||||
int wcd9xxx_reset_low(struct device *dev);
|
||||
int wcd9xxx_get_codec_info(struct device *dev);
|
||||
|
||||
typedef int (*codec_bringup_fn)(struct wcd9xxx *);
|
||||
typedef int (*codec_bringdown_fn)(struct wcd9xxx *);
|
||||
typedef int (*codec_type_fn)(struct wcd9xxx *,
|
||||
struct wcd9xxx_codec_type *);
|
||||
|
||||
codec_bringdown_fn wcd9xxx_bringdown_fn(int type);
|
||||
codec_bringup_fn wcd9xxx_bringup_fn(int type);
|
||||
codec_type_fn wcd9xxx_get_codec_info_fn(int type);
|
||||
|
||||
#endif
|
||||
190
include/linux/qdsp6v2/apr.h
Normal file
190
include/linux/qdsp6v2/apr.h
Normal file
@@ -0,0 +1,190 @@
|
||||
/* Copyright (c) 2010-2016, 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 __APR_H_
|
||||
#define __APR_H_
|
||||
|
||||
#include <linux/mutex.h>
|
||||
#include <soc/qcom/subsystem_notif.h>
|
||||
|
||||
enum apr_subsys_state {
|
||||
APR_SUBSYS_DOWN,
|
||||
APR_SUBSYS_UP,
|
||||
APR_SUBSYS_LOADED,
|
||||
};
|
||||
|
||||
struct apr_q6 {
|
||||
void *pil;
|
||||
atomic_t q6_state;
|
||||
atomic_t modem_state;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
struct apr_hdr {
|
||||
uint16_t hdr_field;
|
||||
uint16_t pkt_size;
|
||||
uint8_t src_svc;
|
||||
uint8_t src_domain;
|
||||
uint16_t src_port;
|
||||
uint8_t dest_svc;
|
||||
uint8_t dest_domain;
|
||||
uint16_t dest_port;
|
||||
uint32_t token;
|
||||
uint32_t opcode;
|
||||
};
|
||||
|
||||
#define APR_HDR_LEN(hdr_len) ((hdr_len)/4)
|
||||
#define APR_PKT_SIZE(hdr_len, payload_len) ((hdr_len) + (payload_len))
|
||||
#define APR_HDR_FIELD(msg_type, hdr_len, ver)\
|
||||
(((msg_type & 0x3) << 8) | ((hdr_len & 0xF) << 4) | (ver & 0xF))
|
||||
|
||||
#define APR_HDR_SIZE sizeof(struct apr_hdr)
|
||||
|
||||
/* Version */
|
||||
#define APR_PKT_VER 0x0
|
||||
|
||||
/* Command and Response Types */
|
||||
#define APR_MSG_TYPE_EVENT 0x0
|
||||
#define APR_MSG_TYPE_CMD_RSP 0x1
|
||||
#define APR_MSG_TYPE_SEQ_CMD 0x2
|
||||
#define APR_MSG_TYPE_NSEQ_CMD 0x3
|
||||
#define APR_MSG_TYPE_MAX 0x04
|
||||
|
||||
/* APR Basic Response Message */
|
||||
#define APR_BASIC_RSP_RESULT 0x000110E8
|
||||
#define APR_RSP_ACCEPTED 0x000100BE
|
||||
|
||||
/* Domain IDs */
|
||||
#define APR_DOMAIN_SIM 0x1
|
||||
#define APR_DOMAIN_PC 0x2
|
||||
#define APR_DOMAIN_MODEM 0x3
|
||||
#define APR_DOMAIN_ADSP 0x4
|
||||
#define APR_DOMAIN_APPS 0x5
|
||||
#define APR_DOMAIN_MAX 0x6
|
||||
|
||||
/* ADSP service IDs */
|
||||
#define APR_SVC_TEST_CLIENT 0x2
|
||||
#define APR_SVC_ADSP_CORE 0x3
|
||||
#define APR_SVC_AFE 0x4
|
||||
#define APR_SVC_VSM 0x5
|
||||
#define APR_SVC_VPM 0x6
|
||||
#define APR_SVC_ASM 0x7
|
||||
#define APR_SVC_ADM 0x8
|
||||
#define APR_SVC_ADSP_MVM 0x09
|
||||
#define APR_SVC_ADSP_CVS 0x0A
|
||||
#define APR_SVC_ADSP_CVP 0x0B
|
||||
#define APR_SVC_USM 0x0C
|
||||
#define APR_SVC_LSM 0x0D
|
||||
#define APR_SVC_VIDC 0x16
|
||||
#define APR_SVC_MAX 0x17
|
||||
|
||||
/* Modem Service IDs */
|
||||
#define APR_SVC_MVS 0x3
|
||||
#define APR_SVC_MVM 0x4
|
||||
#define APR_SVC_CVS 0x5
|
||||
#define APR_SVC_CVP 0x6
|
||||
#define APR_SVC_SRD 0x7
|
||||
|
||||
/* APR Port IDs */
|
||||
#define APR_MAX_PORTS 0x80
|
||||
|
||||
#define APR_NAME_MAX 0x40
|
||||
|
||||
#define RESET_EVENTS 0x000130D7
|
||||
|
||||
#define LPASS_RESTART_EVENT 0x1000
|
||||
#define LPASS_RESTART_READY 0x1001
|
||||
|
||||
struct apr_client_data {
|
||||
uint16_t reset_event;
|
||||
uint16_t reset_proc;
|
||||
uint16_t payload_size;
|
||||
uint16_t hdr_len;
|
||||
uint16_t msg_type;
|
||||
uint16_t src;
|
||||
uint16_t dest_svc;
|
||||
uint16_t src_port;
|
||||
uint16_t dest_port;
|
||||
uint32_t token;
|
||||
uint32_t opcode;
|
||||
void *payload;
|
||||
};
|
||||
|
||||
typedef int32_t (*apr_fn)(struct apr_client_data *data, void *priv);
|
||||
|
||||
struct apr_svc {
|
||||
uint16_t id;
|
||||
uint16_t dest_id;
|
||||
uint16_t client_id;
|
||||
uint16_t dest_domain;
|
||||
uint8_t rvd;
|
||||
uint8_t port_cnt;
|
||||
uint8_t svc_cnt;
|
||||
uint8_t need_reset;
|
||||
apr_fn port_fn[APR_MAX_PORTS];
|
||||
void *port_priv[APR_MAX_PORTS];
|
||||
apr_fn fn;
|
||||
void *priv;
|
||||
struct mutex m_lock;
|
||||
spinlock_t w_lock;
|
||||
uint8_t pkt_owner;
|
||||
};
|
||||
|
||||
struct apr_client {
|
||||
uint8_t id;
|
||||
uint8_t svc_cnt;
|
||||
uint8_t rvd;
|
||||
struct mutex m_lock;
|
||||
struct apr_svc_ch_dev *handle;
|
||||
struct apr_svc svc[APR_SVC_MAX];
|
||||
};
|
||||
|
||||
struct apr_rx_intents {
|
||||
int num_of_intents;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
struct apr_pkt_cfg {
|
||||
uint8_t pkt_owner;
|
||||
struct apr_rx_intents intents;
|
||||
};
|
||||
|
||||
int apr_load_adsp_image(void);
|
||||
struct apr_client *apr_get_client(int dest_id, int client_id);
|
||||
int apr_wait_for_device_up(int dest_id);
|
||||
int apr_get_svc(const char *svc_name, int dest_id, int *client_id,
|
||||
int *svc_idx, int *svc_id);
|
||||
void apr_cb_func(void *buf, int len, void *priv);
|
||||
struct apr_svc *apr_register(char *dest, char *svc_name, apr_fn svc_fn,
|
||||
uint32_t src_port, void *priv);
|
||||
inline int apr_fill_hdr(void *handle, uint32_t *buf, uint16_t src_port,
|
||||
uint16_t msg_type, uint16_t dest_port,
|
||||
uint32_t token, uint32_t opcode, uint16_t len);
|
||||
|
||||
int apr_send_pkt(void *handle, uint32_t *buf);
|
||||
int apr_deregister(void *handle);
|
||||
void subsys_notif_register(char *client_name, int domain,
|
||||
struct notifier_block *nb);
|
||||
int apr_get_dest_id(char *dest);
|
||||
uint16_t apr_get_data_src(struct apr_hdr *hdr);
|
||||
void change_q6_state(int state);
|
||||
void q6audio_dsp_not_responding(void);
|
||||
void apr_reset(void *handle);
|
||||
enum apr_subsys_state apr_get_subsys_state(void);
|
||||
enum apr_subsys_state apr_get_modem_state(void);
|
||||
void apr_set_modem_state(enum apr_subsys_state state);
|
||||
enum apr_subsys_state apr_get_q6_state(void);
|
||||
int apr_set_q6_state(enum apr_subsys_state state);
|
||||
void apr_set_subsys_state(void);
|
||||
const char *apr_get_lpass_subsys_name(void);
|
||||
uint16_t apr_get_reset_domain(uint16_t proc);
|
||||
#endif
|
||||
90
include/linux/qdsp6v2/apr_tal.h
Normal file
90
include/linux/qdsp6v2/apr_tal.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/* Copyright (c) 2010-2011, 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.
|
||||
*
|
||||
*/
|
||||
#ifndef __APR_TAL_H_
|
||||
#define __APR_TAL_H_
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
/* APR Client IDs */
|
||||
#define APR_CLIENT_AUDIO 0x0
|
||||
#define APR_CLIENT_VOICE 0x1
|
||||
#define APR_CLIENT_MAX 0x2
|
||||
|
||||
#define APR_DL_SMD 0
|
||||
#define APR_DL_MAX 1
|
||||
|
||||
#define APR_DEST_MODEM 0
|
||||
#define APR_DEST_QDSP6 1
|
||||
#define APR_DEST_MAX 2
|
||||
|
||||
#if defined(CONFIG_MSM_QDSP6_APRV2_GLINK) || \
|
||||
defined(CONFIG_MSM_QDSP6_APRV3_GLINK)
|
||||
#define APR_MAX_BUF 512
|
||||
#else
|
||||
#define APR_MAX_BUF 8092
|
||||
#endif
|
||||
|
||||
#define APR_DEFAULT_NUM_OF_INTENTS 20
|
||||
|
||||
#define APR_OPEN_TIMEOUT_MS 5000
|
||||
|
||||
enum {
|
||||
/* If client sets the pkt_owner to APR_PKT_OWNER_DRIVER, APR
|
||||
* driver will allocate a buffer, where the user packet is
|
||||
* copied into, for each and every single Tx transmission.
|
||||
* The buffer is thereafter passed to underlying link layer
|
||||
* and freed upon the notification received from the link layer
|
||||
* that the packet has been consumed.
|
||||
*/
|
||||
APR_PKT_OWNER_DRIVER,
|
||||
/* If client sets the pkt_owner to APR_PKT_OWNER_CLIENT, APR
|
||||
* will pass the user packet memory address directly to underlying
|
||||
* link layer. In this case it is the client's responsibility to
|
||||
* make sure the packet is intact until being notified that the
|
||||
* packet has been consumed.
|
||||
*/
|
||||
APR_PKT_OWNER_CLIENT,
|
||||
};
|
||||
|
||||
struct apr_pkt_priv {
|
||||
/* This property is only applicable for APR over Glink.
|
||||
* It is ignored in APR over SMD cases.
|
||||
*/
|
||||
uint8_t pkt_owner;
|
||||
};
|
||||
|
||||
typedef void (*apr_svc_cb_fn)(void *buf, int len, void *priv);
|
||||
struct apr_svc_ch_dev *apr_tal_open(uint32_t svc, uint32_t dest,
|
||||
uint32_t dl, apr_svc_cb_fn func, void *priv);
|
||||
int apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data,
|
||||
struct apr_pkt_priv *pkt_priv, int len);
|
||||
int apr_tal_close(struct apr_svc_ch_dev *apr_ch);
|
||||
int apr_tal_rx_intents_config(struct apr_svc_ch_dev *apr_ch,
|
||||
int num_of_intents, uint32_t size);
|
||||
|
||||
|
||||
struct apr_svc_ch_dev {
|
||||
void *handle;
|
||||
spinlock_t w_lock;
|
||||
spinlock_t r_lock;
|
||||
struct mutex m_lock;
|
||||
apr_svc_cb_fn func;
|
||||
wait_queue_head_t wait;
|
||||
void *priv;
|
||||
unsigned int channel_state;
|
||||
bool if_remote_intent_ready;
|
||||
};
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user