333 lines
8.2 KiB
C
333 lines
8.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Low-level I/O functions.
|
|
*
|
|
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
|
|
* Copyright (c) 2010, ST-Ericsson
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/align.h>
|
|
|
|
#include "hwio.h"
|
|
#include "wfx.h"
|
|
#include "bus.h"
|
|
#include "traces.h"
|
|
|
|
#define WFX_HIF_BUFFER_SIZE 0x2000
|
|
|
|
static int wfx_read32(struct wfx_dev *wdev, int reg, u32 *val)
|
|
{
|
|
int ret;
|
|
__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
|
|
|
|
*val = ~0; /* Never return undefined value */
|
|
if (!tmp)
|
|
return -ENOMEM;
|
|
ret = wdev->hwbus_ops->copy_from_io(wdev->hwbus_priv, reg, tmp, sizeof(u32));
|
|
if (ret >= 0)
|
|
*val = le32_to_cpu(*tmp);
|
|
kfree(tmp);
|
|
if (ret)
|
|
dev_err(wdev->dev, "%s: bus communication error: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int wfx_write32(struct wfx_dev *wdev, int reg, u32 val)
|
|
{
|
|
int ret;
|
|
__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
|
|
|
|
if (!tmp)
|
|
return -ENOMEM;
|
|
*tmp = cpu_to_le32(val);
|
|
ret = wdev->hwbus_ops->copy_to_io(wdev->hwbus_priv, reg, tmp, sizeof(u32));
|
|
kfree(tmp);
|
|
if (ret)
|
|
dev_err(wdev->dev, "%s: bus communication error: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int wfx_read32_locked(struct wfx_dev *wdev, int reg, u32 *val)
|
|
{
|
|
int ret;
|
|
|
|
wdev->hwbus_ops->lock(wdev->hwbus_priv);
|
|
ret = wfx_read32(wdev, reg, val);
|
|
_trace_io_read32(reg, *val);
|
|
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
|
|
return ret;
|
|
}
|
|
|
|
static int wfx_write32_locked(struct wfx_dev *wdev, int reg, u32 val)
|
|
{
|
|
int ret;
|
|
|
|
wdev->hwbus_ops->lock(wdev->hwbus_priv);
|
|
ret = wfx_write32(wdev, reg, val);
|
|
_trace_io_write32(reg, val);
|
|
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
|
|
return ret;
|
|
}
|
|
|
|
static int wfx_write32_bits_locked(struct wfx_dev *wdev, int reg, u32 mask, u32 val)
|
|
{
|
|
int ret;
|
|
u32 val_r, val_w;
|
|
|
|
WARN_ON(~mask & val);
|
|
val &= mask;
|
|
wdev->hwbus_ops->lock(wdev->hwbus_priv);
|
|
ret = wfx_read32(wdev, reg, &val_r);
|
|
_trace_io_read32(reg, val_r);
|
|
if (ret < 0)
|
|
goto err;
|
|
val_w = (val_r & ~mask) | val;
|
|
if (val_w != val_r) {
|
|
ret = wfx_write32(wdev, reg, val_w);
|
|
_trace_io_write32(reg, val_w);
|
|
}
|
|
err:
|
|
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
|
|
return ret;
|
|
}
|
|
|
|
static int wfx_indirect_read(struct wfx_dev *wdev, int reg, u32 addr, void *buf, size_t len)
|
|
{
|
|
int ret;
|
|
int i;
|
|
u32 cfg;
|
|
u32 prefetch;
|
|
|
|
WARN_ON(len >= WFX_HIF_BUFFER_SIZE);
|
|
WARN_ON(reg != WFX_REG_AHB_DPORT && reg != WFX_REG_SRAM_DPORT);
|
|
|
|
if (reg == WFX_REG_AHB_DPORT)
|
|
prefetch = CFG_PREFETCH_AHB;
|
|
else if (reg == WFX_REG_SRAM_DPORT)
|
|
prefetch = CFG_PREFETCH_SRAM;
|
|
else
|
|
return -ENODEV;
|
|
|
|
ret = wfx_write32(wdev, WFX_REG_BASE_ADDR, addr);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
ret = wfx_read32(wdev, WFX_REG_CONFIG, &cfg);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
ret = wfx_write32(wdev, WFX_REG_CONFIG, cfg | prefetch);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
for (i = 0; i < 20; i++) {
|
|
ret = wfx_read32(wdev, WFX_REG_CONFIG, &cfg);
|
|
if (ret < 0)
|
|
goto err;
|
|
if (!(cfg & prefetch))
|
|
break;
|
|
usleep_range(200, 250);
|
|
}
|
|
if (i == 20) {
|
|
ret = -ETIMEDOUT;
|
|
goto err;
|
|
}
|
|
|
|
ret = wdev->hwbus_ops->copy_from_io(wdev->hwbus_priv, reg, buf, len);
|
|
|
|
err:
|
|
if (ret < 0)
|
|
memset(buf, 0xFF, len); /* Never return undefined value */
|
|
return ret;
|
|
}
|
|
|
|
static int wfx_indirect_write(struct wfx_dev *wdev, int reg, u32 addr,
|
|
const void *buf, size_t len)
|
|
{
|
|
int ret;
|
|
|
|
WARN_ON(len >= WFX_HIF_BUFFER_SIZE);
|
|
WARN_ON(reg != WFX_REG_AHB_DPORT && reg != WFX_REG_SRAM_DPORT);
|
|
ret = wfx_write32(wdev, WFX_REG_BASE_ADDR, addr);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return wdev->hwbus_ops->copy_to_io(wdev->hwbus_priv, reg, buf, len);
|
|
}
|
|
|
|
static int wfx_indirect_read_locked(struct wfx_dev *wdev, int reg, u32 addr,
|
|
void *buf, size_t len)
|
|
{
|
|
int ret;
|
|
|
|
wdev->hwbus_ops->lock(wdev->hwbus_priv);
|
|
ret = wfx_indirect_read(wdev, reg, addr, buf, len);
|
|
_trace_io_ind_read(reg, addr, buf, len);
|
|
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
|
|
return ret;
|
|
}
|
|
|
|
static int wfx_indirect_write_locked(struct wfx_dev *wdev, int reg, u32 addr,
|
|
const void *buf, size_t len)
|
|
{
|
|
int ret;
|
|
|
|
wdev->hwbus_ops->lock(wdev->hwbus_priv);
|
|
ret = wfx_indirect_write(wdev, reg, addr, buf, len);
|
|
_trace_io_ind_write(reg, addr, buf, len);
|
|
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
|
|
return ret;
|
|
}
|
|
|
|
static int wfx_indirect_read32_locked(struct wfx_dev *wdev, int reg, u32 addr, u32 *val)
|
|
{
|
|
int ret;
|
|
__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
|
|
|
|
if (!tmp)
|
|
return -ENOMEM;
|
|
wdev->hwbus_ops->lock(wdev->hwbus_priv);
|
|
ret = wfx_indirect_read(wdev, reg, addr, tmp, sizeof(u32));
|
|
*val = le32_to_cpu(*tmp);
|
|
_trace_io_ind_read32(reg, addr, *val);
|
|
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
|
|
kfree(tmp);
|
|
return ret;
|
|
}
|
|
|
|
static int wfx_indirect_write32_locked(struct wfx_dev *wdev, int reg, u32 addr, u32 val)
|
|
{
|
|
int ret;
|
|
__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
|
|
|
|
if (!tmp)
|
|
return -ENOMEM;
|
|
*tmp = cpu_to_le32(val);
|
|
wdev->hwbus_ops->lock(wdev->hwbus_priv);
|
|
ret = wfx_indirect_write(wdev, reg, addr, tmp, sizeof(u32));
|
|
_trace_io_ind_write32(reg, addr, val);
|
|
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
|
|
kfree(tmp);
|
|
return ret;
|
|
}
|
|
|
|
int wfx_data_read(struct wfx_dev *wdev, void *buf, size_t len)
|
|
{
|
|
int ret;
|
|
|
|
WARN(!IS_ALIGNED((uintptr_t)buf, 4), "unaligned buffer");
|
|
wdev->hwbus_ops->lock(wdev->hwbus_priv);
|
|
ret = wdev->hwbus_ops->copy_from_io(wdev->hwbus_priv, WFX_REG_IN_OUT_QUEUE, buf, len);
|
|
_trace_io_read(WFX_REG_IN_OUT_QUEUE, buf, len);
|
|
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
|
|
if (ret)
|
|
dev_err(wdev->dev, "%s: bus communication error: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
int wfx_data_write(struct wfx_dev *wdev, const void *buf, size_t len)
|
|
{
|
|
int ret;
|
|
|
|
WARN(!IS_ALIGNED((uintptr_t)buf, 4), "unaligned buffer");
|
|
wdev->hwbus_ops->lock(wdev->hwbus_priv);
|
|
ret = wdev->hwbus_ops->copy_to_io(wdev->hwbus_priv, WFX_REG_IN_OUT_QUEUE, buf, len);
|
|
_trace_io_write(WFX_REG_IN_OUT_QUEUE, buf, len);
|
|
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
|
|
if (ret)
|
|
dev_err(wdev->dev, "%s: bus communication error: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
int wfx_sram_buf_read(struct wfx_dev *wdev, u32 addr, void *buf, size_t len)
|
|
{
|
|
return wfx_indirect_read_locked(wdev, WFX_REG_SRAM_DPORT, addr, buf, len);
|
|
}
|
|
|
|
int wfx_ahb_buf_read(struct wfx_dev *wdev, u32 addr, void *buf, size_t len)
|
|
{
|
|
return wfx_indirect_read_locked(wdev, WFX_REG_AHB_DPORT, addr, buf, len);
|
|
}
|
|
|
|
int wfx_sram_buf_write(struct wfx_dev *wdev, u32 addr, const void *buf, size_t len)
|
|
{
|
|
return wfx_indirect_write_locked(wdev, WFX_REG_SRAM_DPORT, addr, buf, len);
|
|
}
|
|
|
|
int wfx_ahb_buf_write(struct wfx_dev *wdev, u32 addr, const void *buf, size_t len)
|
|
{
|
|
return wfx_indirect_write_locked(wdev, WFX_REG_AHB_DPORT, addr, buf, len);
|
|
}
|
|
|
|
int wfx_sram_reg_read(struct wfx_dev *wdev, u32 addr, u32 *val)
|
|
{
|
|
return wfx_indirect_read32_locked(wdev, WFX_REG_SRAM_DPORT, addr, val);
|
|
}
|
|
|
|
int wfx_ahb_reg_read(struct wfx_dev *wdev, u32 addr, u32 *val)
|
|
{
|
|
return wfx_indirect_read32_locked(wdev, WFX_REG_AHB_DPORT, addr, val);
|
|
}
|
|
|
|
int wfx_sram_reg_write(struct wfx_dev *wdev, u32 addr, u32 val)
|
|
{
|
|
return wfx_indirect_write32_locked(wdev, WFX_REG_SRAM_DPORT, addr, val);
|
|
}
|
|
|
|
int wfx_ahb_reg_write(struct wfx_dev *wdev, u32 addr, u32 val)
|
|
{
|
|
return wfx_indirect_write32_locked(wdev, WFX_REG_AHB_DPORT, addr, val);
|
|
}
|
|
|
|
int wfx_config_reg_read(struct wfx_dev *wdev, u32 *val)
|
|
{
|
|
return wfx_read32_locked(wdev, WFX_REG_CONFIG, val);
|
|
}
|
|
|
|
int wfx_config_reg_write(struct wfx_dev *wdev, u32 val)
|
|
{
|
|
return wfx_write32_locked(wdev, WFX_REG_CONFIG, val);
|
|
}
|
|
|
|
int wfx_config_reg_write_bits(struct wfx_dev *wdev, u32 mask, u32 val)
|
|
{
|
|
return wfx_write32_bits_locked(wdev, WFX_REG_CONFIG, mask, val);
|
|
}
|
|
|
|
int wfx_control_reg_read(struct wfx_dev *wdev, u32 *val)
|
|
{
|
|
return wfx_read32_locked(wdev, WFX_REG_CONTROL, val);
|
|
}
|
|
|
|
int wfx_control_reg_write(struct wfx_dev *wdev, u32 val)
|
|
{
|
|
return wfx_write32_locked(wdev, WFX_REG_CONTROL, val);
|
|
}
|
|
|
|
int wfx_control_reg_write_bits(struct wfx_dev *wdev, u32 mask, u32 val)
|
|
{
|
|
return wfx_write32_bits_locked(wdev, WFX_REG_CONTROL, mask, val);
|
|
}
|
|
|
|
int wfx_igpr_reg_read(struct wfx_dev *wdev, int index, u32 *val)
|
|
{
|
|
int ret;
|
|
|
|
*val = ~0; /* Never return undefined value */
|
|
ret = wfx_write32_locked(wdev, WFX_REG_SET_GEN_R_W, IGPR_RW | index << 24);
|
|
if (ret)
|
|
return ret;
|
|
ret = wfx_read32_locked(wdev, WFX_REG_SET_GEN_R_W, val);
|
|
if (ret)
|
|
return ret;
|
|
*val &= IGPR_VALUE;
|
|
return ret;
|
|
}
|
|
|
|
int wfx_igpr_reg_write(struct wfx_dev *wdev, int index, u32 val)
|
|
{
|
|
return wfx_write32_locked(wdev, WFX_REG_SET_GEN_R_W, index << 24 | val);
|
|
}
|