mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-31 22:23:05 +00:00

ACPM commands that return more than 8 bytes currently don't work
correctly, as this driver ignores any such returned bytes.
This is evident in at least acpm_pmic_bulk_read(), where up to 8
registers can be read back and those 8 register values are placed
starting at &xfer->rxd[8].
The reason is that xfter->rxlen is initialized with the size of a
pointer (8 bytes), rather than the size of the byte array that pointer
points to (16 bytes)
Update the code such that we set the number of bytes expected to be the
size of the rx buffer.
Note1: While different commands have different lengths rx buffers, we
have to specify the same length for all rx buffers since acpm_get_rx()
assumes they're all the same length.
Note2: The different commands also have different lengths tx buffers,
but before switching the code to use the minimum possible length, some
more testing would have to be done to ensure this works correctly in
all situations. It seems wiser to just apply this fix here without
additional logic changes for now.
Fixes: a88927b534
("firmware: add Exynos ACPM protocol driver")
Reviewed-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Signed-off-by: André Draszik <andre.draszik@linaro.org>
Link: https://lore.kernel.org/r/20250319-acpm-fixes-v2-1-ac2c1bcf322b@linaro.org
Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
225 lines
5.6 KiB
C
225 lines
5.6 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/bitfield.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 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->txlen = cmdlen;
|
|
xfer->rxlen = 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, sizeof(cmd), acpm_chan_id);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*buf = FIELD_GET(ACPM_PMIC_VALUE, xfer.rxd[1]);
|
|
|
|
return 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, sizeof(cmd), acpm_chan_id);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = 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, sizeof(cmd), acpm_chan_id);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 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, sizeof(cmd), acpm_chan_id);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 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, sizeof(cmd), acpm_chan_id);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]);
|
|
}
|