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>
82 lines
2.0 KiB
C
82 lines
2.0 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright 2020 Samsung Electronics Co., Ltd.
|
|
* Copyright 2020 Google LLC.
|
|
* Copyright 2025 Linaro Ltd.
|
|
*/
|
|
|
|
#include <linux/array_size.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/firmware/samsung/exynos-acpm-protocol.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/types.h>
|
|
#include <linux/units.h>
|
|
|
|
#include "exynos-acpm.h"
|
|
#include "exynos-acpm-dvfs.h"
|
|
|
|
#define ACPM_DVFS_ID GENMASK(11, 0)
|
|
#define ACPM_DVFS_REQ_TYPE GENMASK(15, 0)
|
|
|
|
#define ACPM_DVFS_FREQ_REQ 0
|
|
#define ACPM_DVFS_FREQ_GET 1
|
|
|
|
static void acpm_dvfs_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
|
|
unsigned int acpm_chan_id, bool response)
|
|
{
|
|
xfer->acpm_chan_id = acpm_chan_id;
|
|
xfer->txcnt = cmdlen;
|
|
xfer->txd = cmd;
|
|
|
|
if (response) {
|
|
xfer->rxcnt = cmdlen;
|
|
xfer->rxd = cmd;
|
|
}
|
|
}
|
|
|
|
static void acpm_dvfs_init_set_rate_cmd(u32 cmd[4], unsigned int clk_id,
|
|
unsigned long rate)
|
|
{
|
|
cmd[0] = FIELD_PREP(ACPM_DVFS_ID, clk_id);
|
|
cmd[1] = rate / HZ_PER_KHZ;
|
|
cmd[2] = FIELD_PREP(ACPM_DVFS_REQ_TYPE, ACPM_DVFS_FREQ_REQ);
|
|
cmd[3] = ktime_to_ms(ktime_get());
|
|
}
|
|
|
|
int acpm_dvfs_set_rate(struct acpm_handle *handle,
|
|
unsigned int acpm_chan_id, unsigned int clk_id,
|
|
unsigned long rate)
|
|
{
|
|
struct acpm_xfer xfer = {0};
|
|
u32 cmd[4];
|
|
|
|
acpm_dvfs_init_set_rate_cmd(cmd, clk_id, rate);
|
|
acpm_dvfs_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, false);
|
|
|
|
return acpm_do_xfer(handle, &xfer);
|
|
}
|
|
|
|
static void acpm_dvfs_init_get_rate_cmd(u32 cmd[4], unsigned int clk_id)
|
|
{
|
|
cmd[0] = FIELD_PREP(ACPM_DVFS_ID, clk_id);
|
|
cmd[2] = FIELD_PREP(ACPM_DVFS_REQ_TYPE, ACPM_DVFS_FREQ_GET);
|
|
cmd[3] = ktime_to_ms(ktime_get());
|
|
}
|
|
|
|
unsigned long acpm_dvfs_get_rate(struct acpm_handle *handle,
|
|
unsigned int acpm_chan_id, unsigned int clk_id)
|
|
{
|
|
struct acpm_xfer xfer;
|
|
unsigned int cmd[4] = {0};
|
|
int ret;
|
|
|
|
acpm_dvfs_init_get_rate_cmd(cmd, clk_id);
|
|
acpm_dvfs_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
|
|
|
|
ret = acpm_do_xfer(handle, &xfer);
|
|
if (ret)
|
|
return 0;
|
|
|
|
return xfer.rxd[1] * HZ_PER_KHZ;
|
|
}
|