mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
firmware: add Exynos ACPM protocol driver
Alive Clock and Power Manager (ACPM) Message Protocol is defined for the purpose of communication between the ACPM firmware and masters (AP, AOC, ...). ACPM firmware operates on the Active Power Management (APM) module that handles overall power activities. ACPM and masters regard each other as independent hardware component and communicate with each other using mailbox messages and shared memory. This protocol driver provides the interface for all the client drivers making use of the features offered by the APM. Add ACPM protocol support. Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org> Link: https://lore.kernel.org/r/20250213-gs101-acpm-v9-2-8b0281b93c8b@linaro.org Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
This commit is contained in:
committed by
Krzysztof Kozlowski
parent
97b9ee2972
commit
a88927b534
@@ -267,6 +267,7 @@ source "drivers/firmware/meson/Kconfig"
|
||||
source "drivers/firmware/microchip/Kconfig"
|
||||
source "drivers/firmware/psci/Kconfig"
|
||||
source "drivers/firmware/qcom/Kconfig"
|
||||
source "drivers/firmware/samsung/Kconfig"
|
||||
source "drivers/firmware/smccc/Kconfig"
|
||||
source "drivers/firmware/tegra/Kconfig"
|
||||
source "drivers/firmware/xilinx/Kconfig"
|
||||
|
||||
@@ -33,6 +33,7 @@ obj-y += efi/
|
||||
obj-y += imx/
|
||||
obj-y += psci/
|
||||
obj-y += qcom/
|
||||
obj-y += samsung/
|
||||
obj-y += smccc/
|
||||
obj-y += tegra/
|
||||
obj-y += xilinx/
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
config EXYNOS_ACPM_PROTOCOL
|
||||
tristate "Exynos Alive Clock and Power Manager (ACPM) Message Protocol"
|
||||
depends on ARCH_EXYNOS || COMPILE_TEST
|
||||
depends on MAILBOX
|
||||
help
|
||||
Alive Clock and Power Manager (ACPM) Message Protocol is defined for
|
||||
the purpose of communication between the ACPM firmware and masters
|
||||
(AP, AOC, ...). ACPM firmware operates on the Active Power Management
|
||||
(APM) module that handles overall power activities.
|
||||
|
||||
This protocol driver provides interface for all the client drivers
|
||||
making use of the features offered by the APM.
|
||||
@@ -0,0 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
acpm-protocol-objs := exynos-acpm.o exynos-acpm-pmic.o
|
||||
obj-$(CONFIG_EXYNOS_ACPM_PROTOCOL) += acpm-protocol.o
|
||||
@@ -0,0 +1,224 @@
|
||||
// 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,
|
||||
unsigned int acpm_chan_id)
|
||||
{
|
||||
xfer->txd = cmd;
|
||||
xfer->rxd = cmd;
|
||||
xfer->txlen = sizeof(cmd);
|
||||
xfer->rxlen = sizeof(cmd);
|
||||
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, 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, 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, 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, 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, acpm_chan_id);
|
||||
|
||||
ret = acpm_do_xfer(handle, &xfer);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright 2020 Samsung Electronics Co., Ltd.
|
||||
* Copyright 2020 Google LLC.
|
||||
* Copyright 2024 Linaro Ltd.
|
||||
*/
|
||||
#ifndef __EXYNOS_ACPM_PMIC_H__
|
||||
#define __EXYNOS_ACPM_PMIC_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct acpm_handle;
|
||||
|
||||
int acpm_pmic_read_reg(const struct acpm_handle *handle,
|
||||
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
||||
u8 *buf);
|
||||
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);
|
||||
int acpm_pmic_write_reg(const struct acpm_handle *handle,
|
||||
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
||||
u8 value);
|
||||
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);
|
||||
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);
|
||||
#endif /* __EXYNOS_ACPM_PMIC_H__ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright 2020 Samsung Electronics Co., Ltd.
|
||||
* Copyright 2020 Google LLC.
|
||||
* Copyright 2024 Linaro Ltd.
|
||||
*/
|
||||
#ifndef __EXYNOS_ACPM_H__
|
||||
#define __EXYNOS_ACPM_H__
|
||||
|
||||
struct acpm_xfer {
|
||||
const u32 *txd;
|
||||
u32 *rxd;
|
||||
size_t txlen;
|
||||
size_t rxlen;
|
||||
unsigned int acpm_chan_id;
|
||||
};
|
||||
|
||||
struct acpm_handle;
|
||||
|
||||
int acpm_do_xfer(const struct acpm_handle *handle,
|
||||
const struct acpm_xfer *xfer);
|
||||
|
||||
#endif /* __EXYNOS_ACPM_H__ */
|
||||
@@ -0,0 +1,49 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright 2020 Samsung Electronics Co., Ltd.
|
||||
* Copyright 2020 Google LLC.
|
||||
* Copyright 2024 Linaro Ltd.
|
||||
*/
|
||||
|
||||
#ifndef __EXYNOS_ACPM_PROTOCOL_H
|
||||
#define __EXYNOS_ACPM_PROTOCOL_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct acpm_handle;
|
||||
|
||||
struct acpm_pmic_ops {
|
||||
int (*read_reg)(const struct acpm_handle *handle,
|
||||
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
||||
u8 *buf);
|
||||
int (*bulk_read)(const struct acpm_handle *handle,
|
||||
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
||||
u8 count, u8 *buf);
|
||||
int (*write_reg)(const struct acpm_handle *handle,
|
||||
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
||||
u8 value);
|
||||
int (*bulk_write)(const struct acpm_handle *handle,
|
||||
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
||||
u8 count, const u8 *buf);
|
||||
int (*update_reg)(const struct acpm_handle *handle,
|
||||
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
|
||||
u8 value, u8 mask);
|
||||
};
|
||||
|
||||
struct acpm_ops {
|
||||
struct acpm_pmic_ops pmic_ops;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct acpm_handle - Reference to an initialized protocol instance
|
||||
* @ops:
|
||||
*/
|
||||
struct acpm_handle {
|
||||
struct acpm_ops ops;
|
||||
};
|
||||
|
||||
struct device;
|
||||
|
||||
const struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev,
|
||||
const char *property);
|
||||
#endif /* __EXYNOS_ACPM_PROTOCOL_H */
|
||||
Reference in New Issue
Block a user