mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Struct acpm_xfer holds two buffers with u32 commands - rxd and txd - and counts their size by rxlen and txlen. "len" suffix is here ambiguous, so could mean length of the buffer or length of commands, and these are not the same since each command is u32. Rename these to rxcnt and txcnt, and change their usage to count the number of commands in each buffer. This will have a benefit of allowing to use __counted_by_ptr later. Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> Reviewed-by: Tudor Ambarus <tudor.ambarus@linaro.org> Link: https://patch.msgid.link/20260219-firmare-acpm-counted-v2-2-e1f7389237d3@oss.qualcomm.com Signed-off-by: Krzysztof Kozlowski <krzk@kernel.org>
240 lines
6.2 KiB
C
240 lines
6.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright 2020 Samsung Electronics Co., Ltd.
|
|
* Copyright 2020 Google LLC.
|
|
* Copyright 2024 Linaro Ltd.
|
|
*/
|
|
#include <linux/array_size.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/firmware/samsung/exynos-acpm-protocol.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "exynos-acpm.h"
|
|
#include "exynos-acpm-pmic.h"
|
|
|
|
#define ACPM_PMIC_CHANNEL GENMASK(15, 12)
|
|
#define ACPM_PMIC_TYPE GENMASK(11, 8)
|
|
#define ACPM_PMIC_REG GENMASK(7, 0)
|
|
|
|
#define ACPM_PMIC_RETURN GENMASK(31, 24)
|
|
#define ACPM_PMIC_MASK GENMASK(23, 16)
|
|
#define ACPM_PMIC_VALUE GENMASK(15, 8)
|
|
#define ACPM_PMIC_FUNC GENMASK(7, 0)
|
|
|
|
#define ACPM_PMIC_BULK_SHIFT 8
|
|
#define ACPM_PMIC_BULK_MASK GENMASK(7, 0)
|
|
#define ACPM_PMIC_BULK_MAX_COUNT 8
|
|
|
|
enum exynos_acpm_pmic_func {
|
|
ACPM_PMIC_READ,
|
|
ACPM_PMIC_WRITE,
|
|
ACPM_PMIC_UPDATE,
|
|
ACPM_PMIC_BULK_READ,
|
|
ACPM_PMIC_BULK_WRITE,
|
|
};
|
|
|
|
static const int acpm_pmic_linux_errmap[] = {
|
|
[0] = 0, /* ACPM_PMIC_SUCCESS */
|
|
[1] = -EACCES, /* Read register can't be accessed or issues to access it. */
|
|
[2] = -EACCES, /* Write register can't be accessed or issues to access it. */
|
|
};
|
|
|
|
static int acpm_pmic_to_linux_err(unsigned int err)
|
|
{
|
|
if (err >= 0 && err < ARRAY_SIZE(acpm_pmic_linux_errmap))
|
|
return acpm_pmic_linux_errmap[err];
|
|
return -EIO;
|
|
}
|
|
|
|
static inline u32 acpm_pmic_set_bulk(u32 data, unsigned int i)
|
|
{
|
|
return (data & ACPM_PMIC_BULK_MASK) << (ACPM_PMIC_BULK_SHIFT * i);
|
|
}
|
|
|
|
static inline u32 acpm_pmic_get_bulk(u32 data, unsigned int i)
|
|
{
|
|
return (data >> (ACPM_PMIC_BULK_SHIFT * i)) & ACPM_PMIC_BULK_MASK;
|
|
}
|
|
|
|
static void acpm_pmic_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
|
|
unsigned int acpm_chan_id)
|
|
{
|
|
xfer->txd = cmd;
|
|
xfer->rxd = cmd;
|
|
xfer->txcnt = cmdlen;
|
|
xfer->rxcnt = cmdlen;
|
|
xfer->acpm_chan_id = acpm_chan_id;
|
|
}
|
|
|
|
static void acpm_pmic_init_read_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan)
|
|
{
|
|
cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
|
|
FIELD_PREP(ACPM_PMIC_REG, reg) |
|
|
FIELD_PREP(ACPM_PMIC_CHANNEL, chan);
|
|
cmd[1] = FIELD_PREP(ACPM_PMIC_FUNC, ACPM_PMIC_READ);
|
|
cmd[3] = ktime_to_ms(ktime_get());
|
|
}
|
|
|
|
int acpm_pmic_read_reg(const struct acpm_handle *handle,
|
|
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
|
u8 *buf)
|
|
{
|
|
struct acpm_xfer xfer;
|
|
u32 cmd[4] = {0};
|
|
int ret;
|
|
|
|
acpm_pmic_init_read_cmd(cmd, type, reg, chan);
|
|
acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*buf = FIELD_GET(ACPM_PMIC_VALUE, xfer.rxd[1]);
|
|
|
|
return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]));
|
|
}
|
|
|
|
static void acpm_pmic_init_bulk_read_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan,
|
|
u8 count)
|
|
{
|
|
cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
|
|
FIELD_PREP(ACPM_PMIC_REG, reg) |
|
|
FIELD_PREP(ACPM_PMIC_CHANNEL, chan);
|
|
cmd[1] = FIELD_PREP(ACPM_PMIC_FUNC, ACPM_PMIC_BULK_READ) |
|
|
FIELD_PREP(ACPM_PMIC_VALUE, count);
|
|
}
|
|
|
|
int acpm_pmic_bulk_read(const struct acpm_handle *handle,
|
|
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
|
u8 count, u8 *buf)
|
|
{
|
|
struct acpm_xfer xfer;
|
|
u32 cmd[4] = {0};
|
|
int i, ret;
|
|
|
|
if (count > ACPM_PMIC_BULK_MAX_COUNT)
|
|
return -EINVAL;
|
|
|
|
acpm_pmic_init_bulk_read_cmd(cmd, type, reg, chan, count);
|
|
acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]));
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (i < 4)
|
|
buf[i] = acpm_pmic_get_bulk(xfer.rxd[2], i);
|
|
else
|
|
buf[i] = acpm_pmic_get_bulk(xfer.rxd[3], i - 4);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void acpm_pmic_init_write_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan,
|
|
u8 value)
|
|
{
|
|
cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
|
|
FIELD_PREP(ACPM_PMIC_REG, reg) |
|
|
FIELD_PREP(ACPM_PMIC_CHANNEL, chan);
|
|
cmd[1] = FIELD_PREP(ACPM_PMIC_FUNC, ACPM_PMIC_WRITE) |
|
|
FIELD_PREP(ACPM_PMIC_VALUE, value);
|
|
cmd[3] = ktime_to_ms(ktime_get());
|
|
}
|
|
|
|
int acpm_pmic_write_reg(const struct acpm_handle *handle,
|
|
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
|
u8 value)
|
|
{
|
|
struct acpm_xfer xfer;
|
|
u32 cmd[4] = {0};
|
|
int ret;
|
|
|
|
acpm_pmic_init_write_cmd(cmd, type, reg, chan, value);
|
|
acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]));
|
|
}
|
|
|
|
static void acpm_pmic_init_bulk_write_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan,
|
|
u8 count, const u8 *buf)
|
|
{
|
|
int i;
|
|
|
|
cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
|
|
FIELD_PREP(ACPM_PMIC_REG, reg) |
|
|
FIELD_PREP(ACPM_PMIC_CHANNEL, chan);
|
|
cmd[1] = FIELD_PREP(ACPM_PMIC_FUNC, ACPM_PMIC_BULK_WRITE) |
|
|
FIELD_PREP(ACPM_PMIC_VALUE, count);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (i < 4)
|
|
cmd[2] |= acpm_pmic_set_bulk(buf[i], i);
|
|
else
|
|
cmd[3] |= acpm_pmic_set_bulk(buf[i], i - 4);
|
|
}
|
|
}
|
|
|
|
int acpm_pmic_bulk_write(const struct acpm_handle *handle,
|
|
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
|
u8 count, const u8 *buf)
|
|
{
|
|
struct acpm_xfer xfer;
|
|
u32 cmd[4] = {0};
|
|
int ret;
|
|
|
|
if (count > ACPM_PMIC_BULK_MAX_COUNT)
|
|
return -EINVAL;
|
|
|
|
acpm_pmic_init_bulk_write_cmd(cmd, type, reg, chan, count, buf);
|
|
acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]));
|
|
}
|
|
|
|
static void acpm_pmic_init_update_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan,
|
|
u8 value, u8 mask)
|
|
{
|
|
cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
|
|
FIELD_PREP(ACPM_PMIC_REG, reg) |
|
|
FIELD_PREP(ACPM_PMIC_CHANNEL, chan);
|
|
cmd[1] = FIELD_PREP(ACPM_PMIC_FUNC, ACPM_PMIC_UPDATE) |
|
|
FIELD_PREP(ACPM_PMIC_VALUE, value) |
|
|
FIELD_PREP(ACPM_PMIC_MASK, mask);
|
|
cmd[3] = ktime_to_ms(ktime_get());
|
|
}
|
|
|
|
int acpm_pmic_update_reg(const struct acpm_handle *handle,
|
|
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
|
u8 value, u8 mask)
|
|
{
|
|
struct acpm_xfer xfer;
|
|
u32 cmd[4] = {0};
|
|
int ret;
|
|
|
|
acpm_pmic_init_update_cmd(cmd, type, reg, chan, value, mask);
|
|
acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]));
|
|
}
|