mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-21 03:33:59 +08:00
Merge remote-tracking branch 'asoc/topic/adsp' into asoc-next
This commit is contained in:
commit
c331a23b3c
@ -16,6 +16,7 @@
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
struct regmap;
|
||||
struct regcache_ops;
|
||||
@ -39,6 +40,13 @@ struct regmap_format {
|
||||
unsigned int (*parse_val)(void *buf);
|
||||
};
|
||||
|
||||
struct regmap_async {
|
||||
struct list_head list;
|
||||
struct work_struct cleanup;
|
||||
struct regmap *map;
|
||||
void *work_buf;
|
||||
};
|
||||
|
||||
struct regmap {
|
||||
struct mutex mutex;
|
||||
spinlock_t spinlock;
|
||||
@ -53,6 +61,11 @@ struct regmap {
|
||||
void *bus_context;
|
||||
const char *name;
|
||||
|
||||
spinlock_t async_lock;
|
||||
wait_queue_head_t async_waitq;
|
||||
struct list_head async_list;
|
||||
int async_ret;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
struct dentry *debugfs;
|
||||
const char *debugfs_name;
|
||||
@ -74,6 +87,9 @@ struct regmap {
|
||||
const struct regmap_access_table *volatile_table;
|
||||
const struct regmap_access_table *precious_table;
|
||||
|
||||
int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
|
||||
int (*reg_write)(void *context, unsigned int reg, unsigned int val);
|
||||
|
||||
u8 read_flag_mask;
|
||||
u8 write_flag_mask;
|
||||
|
||||
@ -175,6 +191,8 @@ bool regcache_set_val(void *base, unsigned int idx,
|
||||
unsigned int val, unsigned int word_size);
|
||||
int regcache_lookup_reg(struct regmap *map, unsigned int reg);
|
||||
|
||||
void regmap_async_complete_cb(struct regmap_async *async, int ret);
|
||||
|
||||
extern struct regcache_ops regcache_rbtree_ops;
|
||||
extern struct regcache_ops regcache_lzo_ops;
|
||||
|
||||
|
@ -15,6 +15,21 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
struct regmap_async_spi {
|
||||
struct regmap_async core;
|
||||
struct spi_message m;
|
||||
struct spi_transfer t[2];
|
||||
};
|
||||
|
||||
static void regmap_spi_complete(void *data)
|
||||
{
|
||||
struct regmap_async_spi *async = data;
|
||||
|
||||
regmap_async_complete_cb(&async->core, async->m.status);
|
||||
}
|
||||
|
||||
static int regmap_spi_write(void *context, const void *data, size_t count)
|
||||
{
|
||||
struct device *dev = context;
|
||||
@ -40,6 +55,41 @@ static int regmap_spi_gather_write(void *context,
|
||||
return spi_sync(spi, &m);
|
||||
}
|
||||
|
||||
static int regmap_spi_async_write(void *context,
|
||||
const void *reg, size_t reg_len,
|
||||
const void *val, size_t val_len,
|
||||
struct regmap_async *a)
|
||||
{
|
||||
struct regmap_async_spi *async = container_of(a,
|
||||
struct regmap_async_spi,
|
||||
core);
|
||||
struct device *dev = context;
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
|
||||
async->t[0].tx_buf = reg;
|
||||
async->t[0].len = reg_len;
|
||||
async->t[1].tx_buf = val;
|
||||
async->t[1].len = val_len;
|
||||
|
||||
spi_message_init(&async->m);
|
||||
spi_message_add_tail(&async->t[0], &async->m);
|
||||
spi_message_add_tail(&async->t[1], &async->m);
|
||||
|
||||
async->m.complete = regmap_spi_complete;
|
||||
async->m.context = async;
|
||||
|
||||
return spi_async(spi, &async->m);
|
||||
}
|
||||
|
||||
static struct regmap_async *regmap_spi_async_alloc(void)
|
||||
{
|
||||
struct regmap_async_spi *async_spi;
|
||||
|
||||
async_spi = kzalloc(sizeof(*async_spi), GFP_KERNEL);
|
||||
|
||||
return &async_spi->core;
|
||||
}
|
||||
|
||||
static int regmap_spi_read(void *context,
|
||||
const void *reg, size_t reg_size,
|
||||
void *val, size_t val_size)
|
||||
@ -53,6 +103,8 @@ static int regmap_spi_read(void *context,
|
||||
static struct regmap_bus regmap_spi = {
|
||||
.write = regmap_spi_write,
|
||||
.gather_write = regmap_spi_gather_write,
|
||||
.async_write = regmap_spi_async_write,
|
||||
.async_alloc = regmap_spi_async_alloc,
|
||||
.read = regmap_spi_read,
|
||||
.read_flag_mask = 0x80,
|
||||
};
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/regmap.h>
|
||||
@ -34,6 +35,22 @@ static int _regmap_update_bits(struct regmap *map, unsigned int reg,
|
||||
unsigned int mask, unsigned int val,
|
||||
bool *change);
|
||||
|
||||
static int _regmap_bus_read(void *context, unsigned int reg,
|
||||
unsigned int *val);
|
||||
static int _regmap_bus_formatted_write(void *context, unsigned int reg,
|
||||
unsigned int val);
|
||||
static int _regmap_bus_raw_write(void *context, unsigned int reg,
|
||||
unsigned int val);
|
||||
|
||||
static void async_cleanup(struct work_struct *work)
|
||||
{
|
||||
struct regmap_async *async = container_of(work, struct regmap_async,
|
||||
cleanup);
|
||||
|
||||
kfree(async->work_buf);
|
||||
kfree(async);
|
||||
}
|
||||
|
||||
bool regmap_reg_in_ranges(unsigned int reg,
|
||||
const struct regmap_range *ranges,
|
||||
unsigned int nranges)
|
||||
@ -423,6 +440,10 @@ struct regmap *regmap_init(struct device *dev,
|
||||
map->cache_type = config->cache_type;
|
||||
map->name = config->name;
|
||||
|
||||
spin_lock_init(&map->async_lock);
|
||||
INIT_LIST_HEAD(&map->async_list);
|
||||
init_waitqueue_head(&map->async_waitq);
|
||||
|
||||
if (config->read_flag_mask || config->write_flag_mask) {
|
||||
map->read_flag_mask = config->read_flag_mask;
|
||||
map->write_flag_mask = config->write_flag_mask;
|
||||
@ -430,6 +451,8 @@ struct regmap *regmap_init(struct device *dev,
|
||||
map->read_flag_mask = bus->read_flag_mask;
|
||||
}
|
||||
|
||||
map->reg_read = _regmap_bus_read;
|
||||
|
||||
reg_endian = config->reg_format_endian;
|
||||
if (reg_endian == REGMAP_ENDIAN_DEFAULT)
|
||||
reg_endian = bus->reg_format_endian_default;
|
||||
@ -575,6 +598,11 @@ struct regmap *regmap_init(struct device *dev,
|
||||
goto err_map;
|
||||
}
|
||||
|
||||
if (map->format.format_write)
|
||||
map->reg_write = _regmap_bus_formatted_write;
|
||||
else if (map->format.format_val)
|
||||
map->reg_write = _regmap_bus_raw_write;
|
||||
|
||||
map->range_tree = RB_ROOT;
|
||||
for (i = 0; i < config->num_ranges; i++) {
|
||||
const struct regmap_range_cfg *range_cfg = &config->ranges[i];
|
||||
@ -870,10 +898,13 @@ static int _regmap_select_page(struct regmap *map, unsigned int *reg,
|
||||
}
|
||||
|
||||
static int _regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
const void *val, size_t val_len)
|
||||
const void *val, size_t val_len, bool async)
|
||||
{
|
||||
struct regmap_range_node *range;
|
||||
unsigned long flags;
|
||||
u8 *u8 = map->work_buf;
|
||||
void *work_val = map->work_buf + map->format.reg_bytes +
|
||||
map->format.pad_bytes;
|
||||
void *buf;
|
||||
int ret = -ENOTSUPP;
|
||||
size_t len;
|
||||
@ -918,7 +949,7 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
dev_dbg(map->dev, "Writing window %d/%zu\n",
|
||||
win_residue, val_len / map->format.val_bytes);
|
||||
ret = _regmap_raw_write(map, reg, val, win_residue *
|
||||
map->format.val_bytes);
|
||||
map->format.val_bytes, async);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
@ -941,6 +972,50 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
|
||||
u8[0] |= map->write_flag_mask;
|
||||
|
||||
if (async && map->bus->async_write) {
|
||||
struct regmap_async *async = map->bus->async_alloc();
|
||||
if (!async)
|
||||
return -ENOMEM;
|
||||
|
||||
async->work_buf = kzalloc(map->format.buf_size,
|
||||
GFP_KERNEL | GFP_DMA);
|
||||
if (!async->work_buf) {
|
||||
kfree(async);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
INIT_WORK(&async->cleanup, async_cleanup);
|
||||
async->map = map;
|
||||
|
||||
/* If the caller supplied the value we can use it safely. */
|
||||
memcpy(async->work_buf, map->work_buf, map->format.pad_bytes +
|
||||
map->format.reg_bytes + map->format.val_bytes);
|
||||
if (val == work_val)
|
||||
val = async->work_buf + map->format.pad_bytes +
|
||||
map->format.reg_bytes;
|
||||
|
||||
spin_lock_irqsave(&map->async_lock, flags);
|
||||
list_add_tail(&async->list, &map->async_list);
|
||||
spin_unlock_irqrestore(&map->async_lock, flags);
|
||||
|
||||
ret = map->bus->async_write(map->bus_context, async->work_buf,
|
||||
map->format.reg_bytes +
|
||||
map->format.pad_bytes,
|
||||
val, val_len, async);
|
||||
|
||||
if (ret != 0) {
|
||||
dev_err(map->dev, "Failed to schedule write: %d\n",
|
||||
ret);
|
||||
|
||||
spin_lock_irqsave(&map->async_lock, flags);
|
||||
list_del(&async->list);
|
||||
spin_unlock_irqrestore(&map->async_lock, flags);
|
||||
|
||||
kfree(async->work_buf);
|
||||
kfree(async);
|
||||
}
|
||||
}
|
||||
|
||||
trace_regmap_hw_write_start(map->dev, reg,
|
||||
val_len / map->format.val_bytes);
|
||||
|
||||
@ -948,8 +1023,7 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
* send the work_buf directly, otherwise try to do a gather
|
||||
* write.
|
||||
*/
|
||||
if (val == (map->work_buf + map->format.pad_bytes +
|
||||
map->format.reg_bytes))
|
||||
if (val == work_val)
|
||||
ret = map->bus->write(map->bus_context, map->work_buf,
|
||||
map->format.reg_bytes +
|
||||
map->format.pad_bytes +
|
||||
@ -981,12 +1055,54 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _regmap_bus_formatted_write(void *context, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
int ret;
|
||||
struct regmap_range_node *range;
|
||||
struct regmap *map = context;
|
||||
|
||||
BUG_ON(!map->format.format_write);
|
||||
|
||||
range = _regmap_range_lookup(map, reg);
|
||||
if (range) {
|
||||
ret = _regmap_select_page(map, ®, range, 1);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
map->format.format_write(map, reg, val);
|
||||
|
||||
trace_regmap_hw_write_start(map->dev, reg, 1);
|
||||
|
||||
ret = map->bus->write(map->bus_context, map->work_buf,
|
||||
map->format.buf_size);
|
||||
|
||||
trace_regmap_hw_write_done(map->dev, reg, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _regmap_bus_raw_write(void *context, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
struct regmap *map = context;
|
||||
|
||||
BUG_ON(!map->format.format_val);
|
||||
|
||||
map->format.format_val(map->work_buf + map->format.reg_bytes
|
||||
+ map->format.pad_bytes, val, 0);
|
||||
return _regmap_raw_write(map, reg,
|
||||
map->work_buf +
|
||||
map->format.reg_bytes +
|
||||
map->format.pad_bytes,
|
||||
map->format.val_bytes, false);
|
||||
}
|
||||
|
||||
int _regmap_write(struct regmap *map, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
struct regmap_range_node *range;
|
||||
int ret;
|
||||
BUG_ON(!map->format.format_write && !map->format.format_val);
|
||||
|
||||
if (!map->cache_bypass && map->format.format_write) {
|
||||
ret = regcache_write(map, reg, val);
|
||||
@ -1005,33 +1121,7 @@ int _regmap_write(struct regmap *map, unsigned int reg,
|
||||
|
||||
trace_regmap_reg_write(map->dev, reg, val);
|
||||
|
||||
if (map->format.format_write) {
|
||||
range = _regmap_range_lookup(map, reg);
|
||||
if (range) {
|
||||
ret = _regmap_select_page(map, ®, range, 1);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
map->format.format_write(map, reg, val);
|
||||
|
||||
trace_regmap_hw_write_start(map->dev, reg, 1);
|
||||
|
||||
ret = map->bus->write(map->bus_context, map->work_buf,
|
||||
map->format.buf_size);
|
||||
|
||||
trace_regmap_hw_write_done(map->dev, reg, 1);
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
map->format.format_val(map->work_buf + map->format.reg_bytes
|
||||
+ map->format.pad_bytes, val, 0);
|
||||
return _regmap_raw_write(map, reg,
|
||||
map->work_buf +
|
||||
map->format.reg_bytes +
|
||||
map->format.pad_bytes,
|
||||
map->format.val_bytes);
|
||||
}
|
||||
return map->reg_write(map, reg, val);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1089,7 +1179,7 @@ int regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
|
||||
map->lock(map->lock_arg);
|
||||
|
||||
ret = _regmap_raw_write(map, reg, val, val_len);
|
||||
ret = _regmap_raw_write(map, reg, val, val_len, false);
|
||||
|
||||
map->unlock(map->lock_arg);
|
||||
|
||||
@ -1145,14 +1235,15 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,
|
||||
if (map->use_single_rw) {
|
||||
for (i = 0; i < val_count; i++) {
|
||||
ret = regmap_raw_write(map,
|
||||
reg + (i * map->reg_stride),
|
||||
val + (i * val_bytes),
|
||||
val_bytes);
|
||||
reg + (i * map->reg_stride),
|
||||
val + (i * val_bytes),
|
||||
val_bytes);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
ret = _regmap_raw_write(map, reg, wval, val_bytes * val_count);
|
||||
ret = _regmap_raw_write(map, reg, wval, val_bytes * val_count,
|
||||
false);
|
||||
}
|
||||
|
||||
if (val_bytes != 1)
|
||||
@ -1164,6 +1255,48 @@ out:
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_bulk_write);
|
||||
|
||||
/**
|
||||
* regmap_raw_write_async(): Write raw values to one or more registers
|
||||
* asynchronously
|
||||
*
|
||||
* @map: Register map to write to
|
||||
* @reg: Initial register to write to
|
||||
* @val: Block of data to be written, laid out for direct transmission to the
|
||||
* device. Must be valid until regmap_async_complete() is called.
|
||||
* @val_len: Length of data pointed to by val.
|
||||
*
|
||||
* This function is intended to be used for things like firmware
|
||||
* download where a large block of data needs to be transferred to the
|
||||
* device. No formatting will be done on the data provided.
|
||||
*
|
||||
* If supported by the underlying bus the write will be scheduled
|
||||
* asynchronously, helping maximise I/O speed on higher speed buses
|
||||
* like SPI. regmap_async_complete() can be called to ensure that all
|
||||
* asynchrnous writes have been completed.
|
||||
*
|
||||
* A value of zero will be returned on success, a negative errno will
|
||||
* be returned in error cases.
|
||||
*/
|
||||
int regmap_raw_write_async(struct regmap *map, unsigned int reg,
|
||||
const void *val, size_t val_len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (val_len % map->format.val_bytes)
|
||||
return -EINVAL;
|
||||
if (reg % map->reg_stride)
|
||||
return -EINVAL;
|
||||
|
||||
map->lock(map->lock_arg);
|
||||
|
||||
ret = _regmap_raw_write(map, reg, val, val_len, true);
|
||||
|
||||
map->unlock(map->lock_arg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_raw_write_async);
|
||||
|
||||
static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val,
|
||||
unsigned int val_len)
|
||||
{
|
||||
@ -1202,10 +1335,27 @@ static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _regmap_bus_read(void *context, unsigned int reg,
|
||||
unsigned int *val)
|
||||
{
|
||||
int ret;
|
||||
struct regmap *map = context;
|
||||
|
||||
if (!map->format.parse_val)
|
||||
return -EINVAL;
|
||||
|
||||
ret = _regmap_raw_read(map, reg, map->work_buf, map->format.val_bytes);
|
||||
if (ret == 0)
|
||||
*val = map->format.parse_val(map->work_buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _regmap_read(struct regmap *map, unsigned int reg,
|
||||
unsigned int *val)
|
||||
{
|
||||
int ret;
|
||||
BUG_ON(!map->reg_read);
|
||||
|
||||
if (!map->cache_bypass) {
|
||||
ret = regcache_read(map, reg, val);
|
||||
@ -1213,26 +1363,21 @@ static int _regmap_read(struct regmap *map, unsigned int reg,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!map->format.parse_val)
|
||||
return -EINVAL;
|
||||
|
||||
if (map->cache_only)
|
||||
return -EBUSY;
|
||||
|
||||
ret = _regmap_raw_read(map, reg, map->work_buf, map->format.val_bytes);
|
||||
ret = map->reg_read(map, reg, val);
|
||||
if (ret == 0) {
|
||||
*val = map->format.parse_val(map->work_buf);
|
||||
|
||||
#ifdef LOG_DEVICE
|
||||
if (strcmp(dev_name(map->dev), LOG_DEVICE) == 0)
|
||||
dev_info(map->dev, "%x => %x\n", reg, *val);
|
||||
#endif
|
||||
|
||||
trace_regmap_reg_read(map->dev, reg, *val);
|
||||
}
|
||||
|
||||
if (ret == 0 && !map->cache_bypass)
|
||||
regcache_write(map, reg, *val);
|
||||
if (!map->cache_bypass)
|
||||
regcache_write(map, reg, *val);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -1450,6 +1595,68 @@ int regmap_update_bits_check(struct regmap *map, unsigned int reg,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_update_bits_check);
|
||||
|
||||
void regmap_async_complete_cb(struct regmap_async *async, int ret)
|
||||
{
|
||||
struct regmap *map = async->map;
|
||||
bool wake;
|
||||
|
||||
spin_lock(&map->async_lock);
|
||||
|
||||
list_del(&async->list);
|
||||
wake = list_empty(&map->async_list);
|
||||
|
||||
if (ret != 0)
|
||||
map->async_ret = ret;
|
||||
|
||||
spin_unlock(&map->async_lock);
|
||||
|
||||
schedule_work(&async->cleanup);
|
||||
|
||||
if (wake)
|
||||
wake_up(&map->async_waitq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_async_complete_cb);
|
||||
|
||||
static int regmap_async_is_done(struct regmap *map)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&map->async_lock, flags);
|
||||
ret = list_empty(&map->async_list);
|
||||
spin_unlock_irqrestore(&map->async_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* regmap_async_complete: Ensure all asynchronous I/O has completed.
|
||||
*
|
||||
* @map: Map to operate on.
|
||||
*
|
||||
* Blocks until any pending asynchronous I/O has completed. Returns
|
||||
* an error code for any failed I/O operations.
|
||||
*/
|
||||
int regmap_async_complete(struct regmap *map)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
/* Nothing to do with no async support */
|
||||
if (!map->bus->async_write)
|
||||
return 0;
|
||||
|
||||
wait_event(map->async_waitq, regmap_async_is_done(map));
|
||||
|
||||
spin_lock_irqsave(&map->async_lock, flags);
|
||||
ret = map->async_ret;
|
||||
map->async_ret = 0;
|
||||
spin_unlock_irqrestore(&map->async_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_async_complete);
|
||||
|
||||
/**
|
||||
* regmap_register_patch: Register and apply register updates to be applied
|
||||
* on device initialistion
|
||||
|
@ -235,14 +235,21 @@ struct regmap_range_cfg {
|
||||
unsigned int window_len;
|
||||
};
|
||||
|
||||
struct regmap_async;
|
||||
|
||||
typedef int (*regmap_hw_write)(void *context, const void *data,
|
||||
size_t count);
|
||||
typedef int (*regmap_hw_gather_write)(void *context,
|
||||
const void *reg, size_t reg_len,
|
||||
const void *val, size_t val_len);
|
||||
typedef int (*regmap_hw_async_write)(void *context,
|
||||
const void *reg, size_t reg_len,
|
||||
const void *val, size_t val_len,
|
||||
struct regmap_async *async);
|
||||
typedef int (*regmap_hw_read)(void *context,
|
||||
const void *reg_buf, size_t reg_size,
|
||||
void *val_buf, size_t val_size);
|
||||
typedef struct regmap_async *(*regmap_hw_async_alloc)(void);
|
||||
typedef void (*regmap_hw_free_context)(void *context);
|
||||
|
||||
/**
|
||||
@ -255,8 +262,11 @@ typedef void (*regmap_hw_free_context)(void *context);
|
||||
* @write: Write operation.
|
||||
* @gather_write: Write operation with split register/value, return -ENOTSUPP
|
||||
* if not implemented on a given device.
|
||||
* @async_write: Write operation which completes asynchronously, optional and
|
||||
* must serialise with respect to non-async I/O.
|
||||
* @read: Read operation. Data is returned in the buffer used to transmit
|
||||
* data.
|
||||
* @async_alloc: Allocate a regmap_async() structure.
|
||||
* @read_flag_mask: Mask to be set in the top byte of the register when doing
|
||||
* a read.
|
||||
* @reg_format_endian_default: Default endianness for formatted register
|
||||
@ -265,13 +275,16 @@ typedef void (*regmap_hw_free_context)(void *context);
|
||||
* @val_format_endian_default: Default endianness for formatted register
|
||||
* values. Used when the regmap_config specifies DEFAULT. If this is
|
||||
* DEFAULT, BIG is assumed.
|
||||
* @async_size: Size of struct used for async work.
|
||||
*/
|
||||
struct regmap_bus {
|
||||
bool fast_io;
|
||||
regmap_hw_write write;
|
||||
regmap_hw_gather_write gather_write;
|
||||
regmap_hw_async_write async_write;
|
||||
regmap_hw_read read;
|
||||
regmap_hw_free_context free_context;
|
||||
regmap_hw_async_alloc async_alloc;
|
||||
u8 read_flag_mask;
|
||||
enum regmap_endian reg_format_endian_default;
|
||||
enum regmap_endian val_format_endian_default;
|
||||
@ -310,6 +323,8 @@ int regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
const void *val, size_t val_len);
|
||||
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,
|
||||
size_t val_count);
|
||||
int regmap_raw_write_async(struct regmap *map, unsigned int reg,
|
||||
const void *val, size_t val_len);
|
||||
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);
|
||||
int regmap_raw_read(struct regmap *map, unsigned int reg,
|
||||
void *val, size_t val_len);
|
||||
@ -321,6 +336,7 @@ int regmap_update_bits_check(struct regmap *map, unsigned int reg,
|
||||
unsigned int mask, unsigned int val,
|
||||
bool *change);
|
||||
int regmap_get_val_bytes(struct regmap *map);
|
||||
int regmap_async_complete(struct regmap *map);
|
||||
|
||||
int regcache_sync(struct regmap *map);
|
||||
int regcache_sync_region(struct regmap *map, unsigned int min,
|
||||
@ -422,6 +438,13 @@ static inline int regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline int regmap_raw_write_async(struct regmap *map, unsigned int reg,
|
||||
const void *val, size_t val_len)
|
||||
{
|
||||
WARN_ONCE(1, "regmap API is disabled");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline int regmap_bulk_write(struct regmap *map, unsigned int reg,
|
||||
const void *val, size_t val_count)
|
||||
{
|
||||
@ -500,6 +523,11 @@ static inline void regcache_mark_dirty(struct regmap *map)
|
||||
WARN_ONCE(1, "regmap API is disabled");
|
||||
}
|
||||
|
||||
static inline void regmap_async_complete(struct regmap *map)
|
||||
{
|
||||
WARN_ONCE(1, "regmap API is disabled");
|
||||
}
|
||||
|
||||
static inline int regmap_register_patch(struct regmap *map,
|
||||
const struct reg_default *regs,
|
||||
int num_regs)
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
@ -103,6 +104,13 @@
|
||||
#define ADSP1_START_SHIFT 0 /* DSP1_START */
|
||||
#define ADSP1_START_WIDTH 1 /* DSP1_START */
|
||||
|
||||
/*
|
||||
* ADSP1 Control 31
|
||||
*/
|
||||
#define ADSP1_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */
|
||||
#define ADSP1_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */
|
||||
#define ADSP1_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */
|
||||
|
||||
#define ADSP2_CONTROL 0x0
|
||||
#define ADSP2_CLOCKING 0x1
|
||||
#define ADSP2_STATUS1 0x4
|
||||
@ -146,6 +154,109 @@
|
||||
#define ADSP2_RAM_RDY_SHIFT 0
|
||||
#define ADSP2_RAM_RDY_WIDTH 1
|
||||
|
||||
struct wm_adsp_buf {
|
||||
struct list_head list;
|
||||
void *buf;
|
||||
};
|
||||
|
||||
static struct wm_adsp_buf *wm_adsp_buf_alloc(const void *src, size_t len,
|
||||
struct list_head *list)
|
||||
{
|
||||
struct wm_adsp_buf *buf = kzalloc(sizeof(*buf), GFP_KERNEL);
|
||||
|
||||
if (buf == NULL)
|
||||
return NULL;
|
||||
|
||||
buf->buf = kmemdup(src, len, GFP_KERNEL | GFP_DMA);
|
||||
if (!buf->buf) {
|
||||
kfree(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (list)
|
||||
list_add_tail(&buf->list, list);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void wm_adsp_buf_free(struct list_head *list)
|
||||
{
|
||||
while (!list_empty(list)) {
|
||||
struct wm_adsp_buf *buf = list_first_entry(list,
|
||||
struct wm_adsp_buf,
|
||||
list);
|
||||
list_del(&buf->list);
|
||||
kfree(buf->buf);
|
||||
kfree(buf);
|
||||
}
|
||||
}
|
||||
|
||||
#define WM_ADSP_NUM_FW 4
|
||||
|
||||
static const char *wm_adsp_fw_text[WM_ADSP_NUM_FW] = {
|
||||
"MBC/VSS", "Tx", "Tx Speaker", "Rx ANC"
|
||||
};
|
||||
|
||||
static struct {
|
||||
const char *file;
|
||||
} wm_adsp_fw[WM_ADSP_NUM_FW] = {
|
||||
{ .file = "mbc-vss" },
|
||||
{ .file = "tx" },
|
||||
{ .file = "tx-spk" },
|
||||
{ .file = "rx-anc" },
|
||||
};
|
||||
|
||||
static int wm_adsp_fw_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
||||
struct wm_adsp *adsp = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
ucontrol->value.integer.value[0] = adsp[e->shift_l].fw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm_adsp_fw_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
||||
struct wm_adsp *adsp = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
if (ucontrol->value.integer.value[0] == adsp[e->shift_l].fw)
|
||||
return 0;
|
||||
|
||||
if (ucontrol->value.integer.value[0] >= WM_ADSP_NUM_FW)
|
||||
return -EINVAL;
|
||||
|
||||
if (adsp[e->shift_l].running)
|
||||
return -EBUSY;
|
||||
|
||||
adsp[e->shift_l].fw = ucontrol->value.integer.value[0];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct soc_enum wm_adsp_fw_enum[] = {
|
||||
SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
||||
SOC_ENUM_SINGLE(0, 1, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
||||
SOC_ENUM_SINGLE(0, 2, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
||||
SOC_ENUM_SINGLE(0, 3, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
||||
};
|
||||
|
||||
const struct snd_kcontrol_new wm_adsp_fw_controls[] = {
|
||||
SOC_ENUM_EXT("DSP1 Firmware", wm_adsp_fw_enum[0],
|
||||
wm_adsp_fw_get, wm_adsp_fw_put),
|
||||
SOC_ENUM_EXT("DSP2 Firmware", wm_adsp_fw_enum[1],
|
||||
wm_adsp_fw_get, wm_adsp_fw_put),
|
||||
SOC_ENUM_EXT("DSP3 Firmware", wm_adsp_fw_enum[2],
|
||||
wm_adsp_fw_get, wm_adsp_fw_put),
|
||||
SOC_ENUM_EXT("DSP4 Firmware", wm_adsp_fw_enum[3],
|
||||
wm_adsp_fw_get, wm_adsp_fw_put),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(wm_adsp_fw_controls);
|
||||
|
||||
static struct wm_adsp_region const *wm_adsp_find_region(struct wm_adsp *dsp,
|
||||
int type)
|
||||
@ -159,8 +270,29 @@ static struct wm_adsp_region const *wm_adsp_find_region(struct wm_adsp *dsp,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static unsigned int wm_adsp_region_to_reg(struct wm_adsp_region const *region,
|
||||
unsigned int offset)
|
||||
{
|
||||
switch (region->type) {
|
||||
case WMFW_ADSP1_PM:
|
||||
return region->base + (offset * 3);
|
||||
case WMFW_ADSP1_DM:
|
||||
return region->base + (offset * 2);
|
||||
case WMFW_ADSP2_XM:
|
||||
return region->base + (offset * 2);
|
||||
case WMFW_ADSP2_YM:
|
||||
return region->base + (offset * 2);
|
||||
case WMFW_ADSP1_ZM:
|
||||
return region->base + (offset * 2);
|
||||
default:
|
||||
WARN_ON(NULL != "Unknown memory region type");
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
static int wm_adsp_load(struct wm_adsp *dsp)
|
||||
{
|
||||
LIST_HEAD(buf_list);
|
||||
const struct firmware *firmware;
|
||||
struct regmap *regmap = dsp->regmap;
|
||||
unsigned int pos = 0;
|
||||
@ -172,7 +304,7 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
||||
const struct wm_adsp_region *mem;
|
||||
const char *region_name;
|
||||
char *file, *text;
|
||||
void *buf;
|
||||
struct wm_adsp_buf *buf;
|
||||
unsigned int reg;
|
||||
int regions = 0;
|
||||
int ret, offset, type, sizes;
|
||||
@ -181,7 +313,8 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
||||
if (file == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(file, PAGE_SIZE, "%s-dsp%d.wmfw", dsp->part, dsp->num);
|
||||
snprintf(file, PAGE_SIZE, "%s-dsp%d-%s.wmfw", dsp->part, dsp->num,
|
||||
wm_adsp_fw[dsp->fw].file);
|
||||
file[PAGE_SIZE - 1] = '\0';
|
||||
|
||||
ret = request_firmware(&firmware, file, dsp->dev);
|
||||
@ -286,27 +419,27 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
||||
case WMFW_ADSP1_PM:
|
||||
BUG_ON(!mem);
|
||||
region_name = "PM";
|
||||
reg = mem->base + (offset * 3);
|
||||
reg = wm_adsp_region_to_reg(mem, offset);
|
||||
break;
|
||||
case WMFW_ADSP1_DM:
|
||||
BUG_ON(!mem);
|
||||
region_name = "DM";
|
||||
reg = mem->base + (offset * 2);
|
||||
reg = wm_adsp_region_to_reg(mem, offset);
|
||||
break;
|
||||
case WMFW_ADSP2_XM:
|
||||
BUG_ON(!mem);
|
||||
region_name = "XM";
|
||||
reg = mem->base + (offset * 2);
|
||||
reg = wm_adsp_region_to_reg(mem, offset);
|
||||
break;
|
||||
case WMFW_ADSP2_YM:
|
||||
BUG_ON(!mem);
|
||||
region_name = "YM";
|
||||
reg = mem->base + (offset * 2);
|
||||
reg = wm_adsp_region_to_reg(mem, offset);
|
||||
break;
|
||||
case WMFW_ADSP1_ZM:
|
||||
BUG_ON(!mem);
|
||||
region_name = "ZM";
|
||||
reg = mem->base + (offset * 2);
|
||||
reg = wm_adsp_region_to_reg(mem, offset);
|
||||
break;
|
||||
default:
|
||||
adsp_warn(dsp,
|
||||
@ -326,18 +459,16 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
||||
}
|
||||
|
||||
if (reg) {
|
||||
buf = kmemdup(region->data, le32_to_cpu(region->len),
|
||||
GFP_KERNEL | GFP_DMA);
|
||||
buf = wm_adsp_buf_alloc(region->data,
|
||||
le32_to_cpu(region->len),
|
||||
&buf_list);
|
||||
if (!buf) {
|
||||
adsp_err(dsp, "Out of memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = regmap_raw_write(regmap, reg, buf,
|
||||
le32_to_cpu(region->len));
|
||||
|
||||
kfree(buf);
|
||||
|
||||
ret = regmap_raw_write_async(regmap, reg, buf->buf,
|
||||
le32_to_cpu(region->len));
|
||||
if (ret != 0) {
|
||||
adsp_err(dsp,
|
||||
"%s.%d: Failed to write %d bytes at %d in %s: %d\n",
|
||||
@ -351,12 +482,20 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
||||
pos += le32_to_cpu(region->len) + sizeof(*region);
|
||||
regions++;
|
||||
}
|
||||
|
||||
|
||||
ret = regmap_async_complete(regmap);
|
||||
if (ret != 0) {
|
||||
adsp_err(dsp, "Failed to complete async write: %d\n", ret);
|
||||
goto out_fw;
|
||||
}
|
||||
|
||||
if (pos > firmware->size)
|
||||
adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n",
|
||||
file, regions, pos - firmware->size);
|
||||
|
||||
out_fw:
|
||||
regmap_async_complete(regmap);
|
||||
wm_adsp_buf_free(&buf_list);
|
||||
release_firmware(firmware);
|
||||
out:
|
||||
kfree(file);
|
||||
@ -364,22 +503,222 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wm_adsp_setup_algs(struct wm_adsp *dsp)
|
||||
{
|
||||
struct regmap *regmap = dsp->regmap;
|
||||
struct wmfw_adsp1_id_hdr adsp1_id;
|
||||
struct wmfw_adsp2_id_hdr adsp2_id;
|
||||
struct wmfw_adsp1_alg_hdr *adsp1_alg;
|
||||
struct wmfw_adsp2_alg_hdr *adsp2_alg;
|
||||
void *alg, *buf;
|
||||
struct wm_adsp_alg_region *region;
|
||||
const struct wm_adsp_region *mem;
|
||||
unsigned int pos, term;
|
||||
size_t algs, buf_size;
|
||||
__be32 val;
|
||||
int i, ret;
|
||||
|
||||
switch (dsp->type) {
|
||||
case WMFW_ADSP1:
|
||||
mem = wm_adsp_find_region(dsp, WMFW_ADSP1_DM);
|
||||
break;
|
||||
case WMFW_ADSP2:
|
||||
mem = wm_adsp_find_region(dsp, WMFW_ADSP2_XM);
|
||||
break;
|
||||
default:
|
||||
mem = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mem == NULL) {
|
||||
BUG_ON(mem != NULL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (dsp->type) {
|
||||
case WMFW_ADSP1:
|
||||
ret = regmap_raw_read(regmap, mem->base, &adsp1_id,
|
||||
sizeof(adsp1_id));
|
||||
if (ret != 0) {
|
||||
adsp_err(dsp, "Failed to read algorithm info: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
buf = &adsp1_id;
|
||||
buf_size = sizeof(adsp1_id);
|
||||
|
||||
algs = be32_to_cpu(adsp1_id.algs);
|
||||
adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n",
|
||||
be32_to_cpu(adsp1_id.fw.id),
|
||||
(be32_to_cpu(adsp1_id.fw.ver) & 0xff0000) >> 16,
|
||||
(be32_to_cpu(adsp1_id.fw.ver) & 0xff00) >> 8,
|
||||
be32_to_cpu(adsp1_id.fw.ver) & 0xff,
|
||||
algs);
|
||||
|
||||
pos = sizeof(adsp1_id) / 2;
|
||||
term = pos + ((sizeof(*adsp1_alg) * algs) / 2);
|
||||
break;
|
||||
|
||||
case WMFW_ADSP2:
|
||||
ret = regmap_raw_read(regmap, mem->base, &adsp2_id,
|
||||
sizeof(adsp2_id));
|
||||
if (ret != 0) {
|
||||
adsp_err(dsp, "Failed to read algorithm info: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
buf = &adsp2_id;
|
||||
buf_size = sizeof(adsp2_id);
|
||||
|
||||
algs = be32_to_cpu(adsp2_id.algs);
|
||||
adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n",
|
||||
be32_to_cpu(adsp2_id.fw.id),
|
||||
(be32_to_cpu(adsp2_id.fw.ver) & 0xff0000) >> 16,
|
||||
(be32_to_cpu(adsp2_id.fw.ver) & 0xff00) >> 8,
|
||||
be32_to_cpu(adsp2_id.fw.ver) & 0xff,
|
||||
algs);
|
||||
|
||||
pos = sizeof(adsp2_id) / 2;
|
||||
term = pos + ((sizeof(*adsp2_alg) * algs) / 2);
|
||||
break;
|
||||
|
||||
default:
|
||||
BUG_ON(NULL == "Unknown DSP type");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (algs == 0) {
|
||||
adsp_err(dsp, "No algorithms\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (algs > 1024) {
|
||||
adsp_err(dsp, "Algorithm count %zx excessive\n", algs);
|
||||
print_hex_dump_bytes(dev_name(dsp->dev), DUMP_PREFIX_OFFSET,
|
||||
buf, buf_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Read the terminator first to validate the length */
|
||||
ret = regmap_raw_read(regmap, mem->base + term, &val, sizeof(val));
|
||||
if (ret != 0) {
|
||||
adsp_err(dsp, "Failed to read algorithm list end: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (be32_to_cpu(val) != 0xbedead)
|
||||
adsp_warn(dsp, "Algorithm list end %x 0x%x != 0xbeadead\n",
|
||||
term, be32_to_cpu(val));
|
||||
|
||||
alg = kzalloc((term - pos) * 2, GFP_KERNEL | GFP_DMA);
|
||||
if (!alg)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = regmap_raw_read(regmap, mem->base + pos, alg, (term - pos) * 2);
|
||||
if (ret != 0) {
|
||||
adsp_err(dsp, "Failed to read algorithm list: %d\n",
|
||||
ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
adsp1_alg = alg;
|
||||
adsp2_alg = alg;
|
||||
|
||||
for (i = 0; i < algs; i++) {
|
||||
switch (dsp->type) {
|
||||
case WMFW_ADSP1:
|
||||
adsp_info(dsp, "%d: ID %x v%d.%d.%d DM@%x ZM@%x\n",
|
||||
i, be32_to_cpu(adsp1_alg[i].alg.id),
|
||||
(be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff0000) >> 16,
|
||||
(be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff00) >> 8,
|
||||
be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff,
|
||||
be32_to_cpu(adsp1_alg[i].dm),
|
||||
be32_to_cpu(adsp1_alg[i].zm));
|
||||
|
||||
region = kzalloc(sizeof(*region), GFP_KERNEL);
|
||||
if (!region)
|
||||
return -ENOMEM;
|
||||
region->type = WMFW_ADSP1_DM;
|
||||
region->alg = be32_to_cpu(adsp1_alg[i].alg.id);
|
||||
region->base = be32_to_cpu(adsp1_alg[i].dm);
|
||||
list_add_tail(®ion->list, &dsp->alg_regions);
|
||||
|
||||
region = kzalloc(sizeof(*region), GFP_KERNEL);
|
||||
if (!region)
|
||||
return -ENOMEM;
|
||||
region->type = WMFW_ADSP1_ZM;
|
||||
region->alg = be32_to_cpu(adsp1_alg[i].alg.id);
|
||||
region->base = be32_to_cpu(adsp1_alg[i].zm);
|
||||
list_add_tail(®ion->list, &dsp->alg_regions);
|
||||
break;
|
||||
|
||||
case WMFW_ADSP2:
|
||||
adsp_info(dsp,
|
||||
"%d: ID %x v%d.%d.%d XM@%x YM@%x ZM@%x\n",
|
||||
i, be32_to_cpu(adsp2_alg[i].alg.id),
|
||||
(be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff0000) >> 16,
|
||||
(be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff00) >> 8,
|
||||
be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff,
|
||||
be32_to_cpu(adsp2_alg[i].xm),
|
||||
be32_to_cpu(adsp2_alg[i].ym),
|
||||
be32_to_cpu(adsp2_alg[i].zm));
|
||||
|
||||
region = kzalloc(sizeof(*region), GFP_KERNEL);
|
||||
if (!region)
|
||||
return -ENOMEM;
|
||||
region->type = WMFW_ADSP2_XM;
|
||||
region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
|
||||
region->base = be32_to_cpu(adsp2_alg[i].xm);
|
||||
list_add_tail(®ion->list, &dsp->alg_regions);
|
||||
|
||||
region = kzalloc(sizeof(*region), GFP_KERNEL);
|
||||
if (!region)
|
||||
return -ENOMEM;
|
||||
region->type = WMFW_ADSP2_YM;
|
||||
region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
|
||||
region->base = be32_to_cpu(adsp2_alg[i].ym);
|
||||
list_add_tail(®ion->list, &dsp->alg_regions);
|
||||
|
||||
region = kzalloc(sizeof(*region), GFP_KERNEL);
|
||||
if (!region)
|
||||
return -ENOMEM;
|
||||
region->type = WMFW_ADSP2_ZM;
|
||||
region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
|
||||
region->base = be32_to_cpu(adsp2_alg[i].zm);
|
||||
list_add_tail(®ion->list, &dsp->alg_regions);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(alg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wm_adsp_load_coeff(struct wm_adsp *dsp)
|
||||
{
|
||||
LIST_HEAD(buf_list);
|
||||
struct regmap *regmap = dsp->regmap;
|
||||
struct wmfw_coeff_hdr *hdr;
|
||||
struct wmfw_coeff_item *blk;
|
||||
const struct firmware *firmware;
|
||||
const struct wm_adsp_region *mem;
|
||||
struct wm_adsp_alg_region *alg_region;
|
||||
const char *region_name;
|
||||
int ret, pos, blocks, type, offset, reg;
|
||||
char *file;
|
||||
void *buf;
|
||||
struct wm_adsp_buf *buf;
|
||||
int tmp;
|
||||
|
||||
file = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
||||
if (file == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(file, PAGE_SIZE, "%s-dsp%d.bin", dsp->part, dsp->num);
|
||||
snprintf(file, PAGE_SIZE, "%s-dsp%d-%s.bin", dsp->part, dsp->num,
|
||||
wm_adsp_fw[dsp->fw].file);
|
||||
file[PAGE_SIZE - 1] = '\0';
|
||||
|
||||
ret = request_firmware(&firmware, file, dsp->dev);
|
||||
@ -402,6 +741,16 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp)
|
||||
goto out_fw;
|
||||
}
|
||||
|
||||
switch (be32_to_cpu(hdr->rev) & 0xff) {
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
adsp_err(dsp, "%s: Unsupported coefficient file format %d\n",
|
||||
file, be32_to_cpu(hdr->rev) & 0xff);
|
||||
ret = -EINVAL;
|
||||
goto out_fw;
|
||||
}
|
||||
|
||||
adsp_dbg(dsp, "%s: v%d.%d.%d\n", file,
|
||||
(le32_to_cpu(hdr->ver) >> 16) & 0xff,
|
||||
(le32_to_cpu(hdr->ver) >> 8) & 0xff,
|
||||
@ -414,8 +763,8 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp)
|
||||
pos - firmware->size > sizeof(*blk)) {
|
||||
blk = (void*)(&firmware->data[pos]);
|
||||
|
||||
type = be32_to_cpu(blk->type) & 0xff;
|
||||
offset = le32_to_cpu(blk->offset) & 0xffffff;
|
||||
type = le16_to_cpu(blk->type);
|
||||
offset = le16_to_cpu(blk->offset);
|
||||
|
||||
adsp_dbg(dsp, "%s.%d: %x v%d.%d.%d\n",
|
||||
file, blocks, le32_to_cpu(blk->id),
|
||||
@ -428,52 +777,105 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp)
|
||||
reg = 0;
|
||||
region_name = "Unknown";
|
||||
switch (type) {
|
||||
case WMFW_NAME_TEXT:
|
||||
case WMFW_INFO_TEXT:
|
||||
case (WMFW_NAME_TEXT << 8):
|
||||
case (WMFW_INFO_TEXT << 8):
|
||||
break;
|
||||
case WMFW_ABSOLUTE:
|
||||
case (WMFW_ABSOLUTE << 8):
|
||||
region_name = "register";
|
||||
reg = offset;
|
||||
break;
|
||||
|
||||
case WMFW_ADSP1_DM:
|
||||
case WMFW_ADSP1_ZM:
|
||||
case WMFW_ADSP2_XM:
|
||||
case WMFW_ADSP2_YM:
|
||||
adsp_dbg(dsp, "%s.%d: %d bytes in %x for %x\n",
|
||||
file, blocks, le32_to_cpu(blk->len),
|
||||
type, le32_to_cpu(blk->id));
|
||||
|
||||
mem = wm_adsp_find_region(dsp, type);
|
||||
if (!mem) {
|
||||
adsp_err(dsp, "No base for region %x\n", type);
|
||||
break;
|
||||
}
|
||||
|
||||
reg = 0;
|
||||
list_for_each_entry(alg_region,
|
||||
&dsp->alg_regions, list) {
|
||||
if (le32_to_cpu(blk->id) == alg_region->alg &&
|
||||
type == alg_region->type) {
|
||||
reg = alg_region->base;
|
||||
reg = wm_adsp_region_to_reg(mem,
|
||||
reg);
|
||||
reg += offset;
|
||||
}
|
||||
}
|
||||
|
||||
if (reg == 0)
|
||||
adsp_err(dsp, "No %x for algorithm %x\n",
|
||||
type, le32_to_cpu(blk->id));
|
||||
break;
|
||||
|
||||
default:
|
||||
adsp_err(dsp, "Unknown region type %x\n", type);
|
||||
adsp_err(dsp, "%s.%d: Unknown region type %x at %d\n",
|
||||
file, blocks, type, pos);
|
||||
break;
|
||||
}
|
||||
|
||||
if (reg) {
|
||||
buf = kmemdup(blk->data, le32_to_cpu(blk->len),
|
||||
GFP_KERNEL | GFP_DMA);
|
||||
buf = wm_adsp_buf_alloc(blk->data,
|
||||
le32_to_cpu(blk->len),
|
||||
&buf_list);
|
||||
if (!buf) {
|
||||
adsp_err(dsp, "Out of memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = regmap_raw_write(regmap, reg, blk->data,
|
||||
le32_to_cpu(blk->len));
|
||||
adsp_dbg(dsp, "%s.%d: Writing %d bytes at %x\n",
|
||||
file, blocks, le32_to_cpu(blk->len),
|
||||
reg);
|
||||
ret = regmap_raw_write_async(regmap, reg, buf->buf,
|
||||
le32_to_cpu(blk->len));
|
||||
if (ret != 0) {
|
||||
adsp_err(dsp,
|
||||
"%s.%d: Failed to write to %x in %s\n",
|
||||
file, blocks, reg, region_name);
|
||||
}
|
||||
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
pos += le32_to_cpu(blk->len) + sizeof(*blk);
|
||||
tmp = le32_to_cpu(blk->len) % 4;
|
||||
if (tmp)
|
||||
pos += le32_to_cpu(blk->len) + (4 - tmp) + sizeof(*blk);
|
||||
else
|
||||
pos += le32_to_cpu(blk->len) + sizeof(*blk);
|
||||
|
||||
blocks++;
|
||||
}
|
||||
|
||||
ret = regmap_async_complete(regmap);
|
||||
if (ret != 0)
|
||||
adsp_err(dsp, "Failed to complete async write: %d\n", ret);
|
||||
|
||||
if (pos > firmware->size)
|
||||
adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n",
|
||||
file, blocks, pos - firmware->size);
|
||||
|
||||
out_fw:
|
||||
release_firmware(firmware);
|
||||
wm_adsp_buf_free(&buf_list);
|
||||
out:
|
||||
kfree(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wm_adsp1_init(struct wm_adsp *adsp)
|
||||
{
|
||||
INIT_LIST_HEAD(&adsp->alg_regions);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm_adsp1_init);
|
||||
|
||||
int wm_adsp1_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol,
|
||||
int event)
|
||||
@ -482,16 +884,46 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w,
|
||||
struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec);
|
||||
struct wm_adsp *dsp = &dsps[w->shift];
|
||||
int ret;
|
||||
int val;
|
||||
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_POST_PMU:
|
||||
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30,
|
||||
ADSP1_SYS_ENA, ADSP1_SYS_ENA);
|
||||
|
||||
/*
|
||||
* For simplicity set the DSP clock rate to be the
|
||||
* SYSCLK rate rather than making it configurable.
|
||||
*/
|
||||
if(dsp->sysclk_reg) {
|
||||
ret = regmap_read(dsp->regmap, dsp->sysclk_reg, &val);
|
||||
if (ret != 0) {
|
||||
adsp_err(dsp, "Failed to read SYSCLK state: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
val = (val & dsp->sysclk_mask)
|
||||
>> dsp->sysclk_shift;
|
||||
|
||||
ret = regmap_update_bits(dsp->regmap,
|
||||
dsp->base + ADSP1_CONTROL_31,
|
||||
ADSP1_CLK_SEL_MASK, val);
|
||||
if (ret != 0) {
|
||||
adsp_err(dsp, "Failed to set clock rate: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = wm_adsp_load(dsp);
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
|
||||
ret = wm_adsp_setup_algs(dsp);
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
|
||||
ret = wm_adsp_load_coeff(dsp);
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
@ -563,6 +995,7 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_soc_codec *codec = w->codec;
|
||||
struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec);
|
||||
struct wm_adsp *dsp = &dsps[w->shift];
|
||||
struct wm_adsp_alg_region *alg_region;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
@ -628,6 +1061,10 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
|
||||
ret = wm_adsp_setup_algs(dsp);
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
|
||||
ret = wm_adsp_load_coeff(dsp);
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
@ -638,9 +1075,13 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
|
||||
ADSP2_CORE_ENA | ADSP2_START);
|
||||
if (ret != 0)
|
||||
goto err;
|
||||
|
||||
dsp->running = true;
|
||||
break;
|
||||
|
||||
case SND_SOC_DAPM_PRE_PMD:
|
||||
dsp->running = false;
|
||||
|
||||
regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
|
||||
ADSP2_SYS_ENA | ADSP2_CORE_ENA |
|
||||
ADSP2_START, 0);
|
||||
@ -664,6 +1105,14 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
|
||||
"Failed to enable supply: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
while (!list_empty(&dsp->alg_regions)) {
|
||||
alg_region = list_first_entry(&dsp->alg_regions,
|
||||
struct wm_adsp_alg_region,
|
||||
list);
|
||||
list_del(&alg_region->list);
|
||||
kfree(alg_region);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -693,6 +1142,8 @@ int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs)
|
||||
return ret;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&adsp->alg_regions);
|
||||
|
||||
if (dvfs) {
|
||||
adsp->dvfs = devm_regulator_get(adsp->dev, "DCVDD");
|
||||
if (IS_ERR(adsp->dvfs)) {
|
||||
|
@ -25,6 +25,13 @@ struct wm_adsp_region {
|
||||
unsigned int base;
|
||||
};
|
||||
|
||||
struct wm_adsp_alg_region {
|
||||
struct list_head list;
|
||||
unsigned int alg;
|
||||
int type;
|
||||
unsigned int base;
|
||||
};
|
||||
|
||||
struct wm_adsp {
|
||||
const char *part;
|
||||
int num;
|
||||
@ -33,10 +40,18 @@ struct wm_adsp {
|
||||
struct regmap *regmap;
|
||||
|
||||
int base;
|
||||
int sysclk_reg;
|
||||
int sysclk_mask;
|
||||
int sysclk_shift;
|
||||
|
||||
struct list_head alg_regions;
|
||||
|
||||
const struct wm_adsp_region *mem;
|
||||
int num_mems;
|
||||
|
||||
int fw;
|
||||
bool running;
|
||||
|
||||
struct regulator *dvfs;
|
||||
};
|
||||
|
||||
@ -50,6 +65,9 @@ struct wm_adsp {
|
||||
.shift = num, .event = wm_adsp2_event, \
|
||||
.event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD }
|
||||
|
||||
extern const struct snd_kcontrol_new wm_adsp_fw_controls[];
|
||||
|
||||
int wm_adsp1_init(struct wm_adsp *adsp);
|
||||
int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs);
|
||||
int wm_adsp1_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event);
|
||||
|
@ -93,15 +93,20 @@ struct wmfw_adsp2_alg_hdr {
|
||||
struct wmfw_coeff_hdr {
|
||||
u8 magic[4];
|
||||
__le32 len;
|
||||
__le32 ver;
|
||||
union {
|
||||
__be32 rev;
|
||||
__le32 ver;
|
||||
};
|
||||
union {
|
||||
__be32 core;
|
||||
__le32 core_ver;
|
||||
};
|
||||
u8 data[];
|
||||
} __packed;
|
||||
|
||||
struct wmfw_coeff_item {
|
||||
union {
|
||||
__be32 type;
|
||||
__le32 offset;
|
||||
};
|
||||
__le16 offset;
|
||||
__le16 type;
|
||||
__le32 id;
|
||||
__le32 ver;
|
||||
__le32 sr;
|
||||
|
Loading…
Reference in New Issue
Block a user