mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
All the functions operating on the 'handle' pointer are claiming it is a
pointer to const thus they should not modify the handle. In fact that's
a false statement, because first thing these functions do is drop the
cast to const with container_of:
struct acpm_info *acpm = handle_to_acpm_info(handle);
And with such cast the handle is easily writable with simple:
acpm->handle.ops.pmic_ops.read_reg = NULL;
The code is not correct logically, either, because functions like
acpm_get_by_node() and acpm_handle_put() are meant to modify the handle
reference counting, thus they must modify the handle. Modification here
happens anyway, even if the reference counting is stored in the
container which the handle is part of.
The code does not have actual visible bug, but incorrect 'const'
annotations could lead to incorrect compiler decisions.
Fixes: a88927b534 ("firmware: add Exynos ACPM protocol driver")
Cc: stable@vger.kernel.org
Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Link: https://patch.msgid.link/20260224104203.42950-2-krzysztof.kozlowski@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(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(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(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(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(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]));
|
|
}
|