diff --git a/drivers/clk/samsung/Kconfig b/drivers/clk/samsung/Kconfig index 70a8b82a0136..198d8b621289 100644 --- a/drivers/clk/samsung/Kconfig +++ b/drivers/clk/samsung/Kconfig @@ -97,7 +97,7 @@ config EXYNOS_CLKOUT config EXYNOS_ACPM_CLK tristate "Clock driver controlled via ACPM interface" - depends on EXYNOS_ACPM_PROTOCOL || (COMPILE_TEST && !EXYNOS_ACPM_PROTOCOL) + depends on EXYNOS_ACPM_PROTOCOL help This driver provides support for clocks that are controlled by firmware that implements the ACPM interface. diff --git a/drivers/clk/samsung/clk-acpm.c b/drivers/clk/samsung/clk-acpm.c index d8944160793a..953ca8d5720a 100644 --- a/drivers/clk/samsung/clk-acpm.c +++ b/drivers/clk/samsung/clk-acpm.c @@ -68,8 +68,8 @@ static unsigned long acpm_clk_recalc_rate(struct clk_hw *hw, { struct acpm_clk *clk = to_acpm_clk(hw); - return clk->handle->ops.dvfs_ops.get_rate(clk->handle, - clk->mbox_chan_id, clk->id); + return clk->handle->ops->dvfs.get_rate(clk->handle, clk->mbox_chan_id, + clk->id); } static int acpm_clk_determine_rate(struct clk_hw *hw, @@ -89,8 +89,8 @@ static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate, { struct acpm_clk *clk = to_acpm_clk(hw); - return clk->handle->ops.dvfs_ops.set_rate(clk->handle, - clk->mbox_chan_id, clk->id, rate); + return clk->handle->ops->dvfs.set_rate(clk->handle, clk->mbox_chan_id, + clk->id, rate); } static const struct clk_ops acpm_clk_ops = { diff --git a/drivers/firmware/samsung/Makefile b/drivers/firmware/samsung/Makefile index 80d4f89b33a9..5a6f72bececf 100644 --- a/drivers/firmware/samsung/Makefile +++ b/drivers/firmware/samsung/Makefile @@ -3,4 +3,5 @@ acpm-protocol-objs := exynos-acpm.o acpm-protocol-objs += exynos-acpm-pmic.o acpm-protocol-objs += exynos-acpm-dvfs.o +acpm-protocol-objs += exynos-acpm-tmu.o obj-$(CONFIG_EXYNOS_ACPM_PROTOCOL) += acpm-protocol.o diff --git a/drivers/firmware/samsung/exynos-acpm-dvfs.c b/drivers/firmware/samsung/exynos-acpm-dvfs.c index 06bdf62dea1f..7266312ef5a6 100644 --- a/drivers/firmware/samsung/exynos-acpm-dvfs.c +++ b/drivers/firmware/samsung/exynos-acpm-dvfs.c @@ -21,19 +21,6 @@ #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) { @@ -51,7 +38,7 @@ int acpm_dvfs_set_rate(struct acpm_handle *handle, 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); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, false); return acpm_do_xfer(handle, &xfer); } @@ -71,7 +58,7 @@ unsigned long acpm_dvfs_get_rate(struct acpm_handle *handle, int ret; acpm_dvfs_init_get_rate_cmd(cmd, clk_id); - acpm_dvfs_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) diff --git a/drivers/firmware/samsung/exynos-acpm-pmic.c b/drivers/firmware/samsung/exynos-acpm-pmic.c index 0c50993cc9a8..f032f2c69685 100644 --- a/drivers/firmware/samsung/exynos-acpm-pmic.c +++ b/drivers/firmware/samsung/exynos-acpm-pmic.c @@ -58,16 +58,6 @@ 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) | @@ -86,7 +76,7 @@ int acpm_pmic_read_reg(struct acpm_handle *handle, int ret; acpm_pmic_init_read_cmd(cmd, type, reg, chan); - acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -119,7 +109,7 @@ int acpm_pmic_bulk_read(struct acpm_handle *handle, 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); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -159,7 +149,7 @@ int acpm_pmic_write_reg(struct acpm_handle *handle, int ret; acpm_pmic_init_write_cmd(cmd, type, reg, chan, value); - acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -199,7 +189,7 @@ int acpm_pmic_bulk_write(struct acpm_handle *handle, 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); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -229,7 +219,7 @@ int acpm_pmic_update_reg(struct acpm_handle *handle, 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); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) diff --git a/drivers/firmware/samsung/exynos-acpm-tmu.c b/drivers/firmware/samsung/exynos-acpm-tmu.c new file mode 100644 index 000000000000..c68d60b4c0b3 --- /dev/null +++ b/drivers/firmware/samsung/exynos-acpm-tmu.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2020 Samsung Electronics Co., Ltd. + * Copyright 2020 Google LLC. + * Copyright 2026 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "exynos-acpm.h" +#include "exynos-acpm-tmu.h" + +/* IPC Request Types */ +#define ACPM_TMU_INIT 0x01 +#define ACPM_TMU_READ_TEMP 0x02 +#define ACPM_TMU_SUSPEND 0x04 +#define ACPM_TMU_RESUME 0x10 +#define ACPM_TMU_THRESHOLD 0x11 +#define ACPM_TMU_INTEN 0x12 +#define ACPM_TMU_CONTROL 0x13 +#define ACPM_TMU_IRQ_CLEAR 0x14 + +#define ACPM_TMU_TX_DATA_LEN 8 +#define ACPM_TMU_RX_DATA_LEN 7 + +struct acpm_tmu_tx { + u16 ctx; + u16 fw_use; + u8 type; + u8 rsvd0; + u8 tzid; + u8 rsvd1; + u8 data[ACPM_TMU_TX_DATA_LEN]; +} __packed; + +struct acpm_tmu_rx { + u16 ctx; + u16 fw_use; + u8 type; + s8 ret; + u8 tzid; + s8 temp; + u8 rsvd; + u8 data[ACPM_TMU_RX_DATA_LEN]; +} __packed; + +union acpm_tmu_msg { + u32 data[4]; + struct acpm_tmu_tx tx; + struct acpm_tmu_rx rx; +}; + +static int acpm_tmu_to_linux_err(s8 fw_err) +{ + /* + * ACPM_TMU_INIT uses BIT(0) and BIT(1) of msg.rx.ret to flag APM + * capabilities. Treat zero and all positive values as success. + */ + if (fw_err >= 0) + return 0; + + if (fw_err == -1) + return -EACCES; + + return -EIO; +} + +int acpm_tmu_init(struct acpm_handle *handle, unsigned int acpm_chan_id) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_INIT; + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_read_temp(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, int *temp) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_READ_TEMP; + msg.tx.tzid = tz; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + ret = acpm_tmu_to_linux_err(msg.rx.ret); + if (ret) + return ret; + + *temp = msg.rx.temp; + + return 0; +} + +int acpm_tmu_set_threshold(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, + const u8 temperature[8], size_t tlen) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + if (tlen > ACPM_TMU_TX_DATA_LEN) + return -EINVAL; + + msg.tx.type = ACPM_TMU_THRESHOLD; + msg.tx.tzid = tz; + memcpy(msg.tx.data, temperature, tlen); + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_set_interrupt_enable(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, u8 inten) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_INTEN; + msg.tx.tzid = tz; + msg.tx.data[0] = inten; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_tz_control(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, bool enable) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_CONTROL; + msg.tx.tzid = tz; + msg.tx.data[0] = enable ? 1 : 0; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_clear_tz_irq(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_IRQ_CLEAR; + msg.tx.tzid = tz; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_suspend(struct acpm_handle *handle, unsigned int acpm_chan_id) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_SUSPEND; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_resume(struct acpm_handle *handle, unsigned int acpm_chan_id) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_RESUME; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} diff --git a/drivers/firmware/samsung/exynos-acpm-tmu.h b/drivers/firmware/samsung/exynos-acpm-tmu.h new file mode 100644 index 000000000000..8b89f29fda67 --- /dev/null +++ b/drivers/firmware/samsung/exynos-acpm-tmu.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2020 Samsung Electronics Co., Ltd. + * Copyright 2020 Google LLC. + * Copyright 2026 Linaro Ltd. + */ +#ifndef __EXYNOS_ACPM_TMU_H__ +#define __EXYNOS_ACPM_TMU_H__ + +#include + +struct acpm_handle; + +int acpm_tmu_init(struct acpm_handle *handle, unsigned int acpm_chan_id); +int acpm_tmu_read_temp(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, int *temp); +int acpm_tmu_set_threshold(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, + const u8 temperature[8], size_t tlen); +int acpm_tmu_set_interrupt_enable(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, u8 inten); +int acpm_tmu_tz_control(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, bool enable); +int acpm_tmu_clear_tz_irq(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz); +int acpm_tmu_suspend(struct acpm_handle *handle, unsigned int acpm_chan_id); +int acpm_tmu_resume(struct acpm_handle *handle, unsigned int acpm_chan_id); +#endif /* __EXYNOS_ACPM_TMU_H__ */ diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index 16c46ed60837..942a2e9f02f5 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -7,11 +7,12 @@ #include #include -#include +#include #include #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include "exynos-acpm.h" #include "exynos-acpm-dvfs.h" #include "exynos-acpm-pmic.h" +#include "exynos-acpm-tmu.h" #define ACPM_PROTOCOL_SEQNUM GENMASK(21, 16) @@ -103,13 +105,16 @@ struct acpm_queue { * struct acpm_rx_data - RX queue data. * * @cmd: pointer to where the data shall be saved. - * @n_cmd: number of 32-bit commands. - * @response: true if the client expects the RX data. + * @cmdcnt: allocated capacity of the @cmd buffer in 32-bit words. + * @rxcnt: expected length of the response in 32-bit words. + * @completed: flag indicating if the firmware response has been fully + * processed. */ struct acpm_rx_data { - u32 *cmd; - size_t n_cmd; - bool response; + u32 *cmd __counted_by_ptr(cmdcnt); + size_t cmdcnt; + size_t rxcnt; + bool completed; }; #define ACPM_SEQNUM_MAX 64 @@ -199,31 +204,33 @@ static void acpm_get_saved_rx(struct acpm_chan *achan, const struct acpm_rx_data *rx_data = &achan->rx_data[tx_seqnum - 1]; u32 rx_seqnum; - if (!rx_data->response) + if (!rx_data->rxcnt) return; rx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, rx_data->cmd[0]); - if (rx_seqnum == tx_seqnum) { + if (rx_seqnum == tx_seqnum) memcpy(xfer->rxd, rx_data->cmd, xfer->rxcnt * sizeof(*xfer->rxd)); - clear_bit(rx_seqnum - 1, achan->bitmap_seqnum); - } } /** * acpm_get_rx() - get response from RX queue. * @achan: ACPM channel info. * @xfer: reference to the transfer to get response for. + * @native_match: pointer to a boolean set to true if the thread natively + * processed its own sequence number during this call. * * Return: 0 on success, -errno otherwise. */ -static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) +static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer, + bool *native_match) { u32 rx_front, rx_seqnum, tx_seqnum, seqnum; const void __iomem *base, *addr; struct acpm_rx_data *rx_data; u32 i, val, mlen; - bool rx_set = false; + + *native_match = false; guard(mutex)(&achan->rx_lock); @@ -232,10 +239,8 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) tx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, xfer->txd[0]); - if (i == rx_front) { - acpm_get_saved_rx(achan, xfer, tx_seqnum); + if (i == rx_front) return 0; - } base = achan->rx.base; mlen = achan->mlen; @@ -256,11 +261,16 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) seqnum = rx_seqnum - 1; rx_data = &achan->rx_data[seqnum]; - if (rx_data->response) { + if (rx_data->rxcnt) { if (rx_seqnum == tx_seqnum) { __ioread32_copy(xfer->rxd, addr, xfer->rxcnt); - rx_set = true; - clear_bit(seqnum, achan->bitmap_seqnum); + /* + * Signal completion to the polling thread. + * Pairs with smp_load_acquire() in polling + * loop. + */ + smp_store_release(&rx_data->completed, true); + *native_match = true; } else { /* * The RX data corresponds to another request. @@ -268,10 +278,23 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) * clear yet the bitmap. It will be cleared * after the response is copied to the request. */ - __ioread32_copy(rx_data->cmd, addr, xfer->rxcnt); + __ioread32_copy(rx_data->cmd, addr, + rx_data->rxcnt); + /* + * Signal completion to the polling thread. + * Pairs with smp_load_acquire() in polling + * loop. + */ + smp_store_release(&rx_data->completed, true); } } else { - clear_bit(seqnum, achan->bitmap_seqnum); + /* + * Signal completion to the polling thread. + * Pairs with smp_load_acquire() in polling loop. + */ + smp_store_release(&rx_data->completed, true); + if (rx_seqnum == tx_seqnum) + *native_match = true; } i = (i + 1) % achan->qlen; @@ -280,13 +303,6 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) /* We saved all responses, mark RX empty. */ writel(rx_front, achan->rx.rear); - /* - * If the response was not in this iteration of the queue, check if the - * RX data was previously saved. - */ - if (!rx_set) - acpm_get_saved_rx(achan, xfer, tx_seqnum); - return 0; } @@ -301,6 +317,7 @@ static int acpm_dequeue_by_polling(struct acpm_chan *achan, const struct acpm_xfer *xfer) { struct device *dev = achan->acpm->dev; + bool native_match; ktime_t timeout; u32 seqnum; int ret; @@ -309,12 +326,25 @@ static int acpm_dequeue_by_polling(struct acpm_chan *achan, timeout = ktime_add_us(ktime_get(), ACPM_POLL_TIMEOUT_US); do { - ret = acpm_get_rx(achan, xfer); + ret = acpm_get_rx(achan, xfer, &native_match); if (ret) return ret; - if (!test_bit(seqnum - 1, achan->bitmap_seqnum)) + /* + * Safely check if our specific transaction has been processed. + * smp_load_acquire prevents the CPU from speculatively + * executing subsequent instructions before the transaction is + * synchronized. + */ + if (smp_load_acquire(&achan->rx_data[seqnum - 1].completed)) { + /* Retrieve payload if another thread cached it for us */ + if (!native_match) + acpm_get_saved_rx(achan, xfer, seqnum); + + /* Relinquish ownership of the sequence slot */ + clear_bit_unlock(seqnum - 1, achan->bitmap_seqnum); return 0; + } /* Determined experimentally. */ udelay(20); @@ -362,29 +392,48 @@ static int acpm_wait_for_queue_slots(struct acpm_chan *achan, u32 next_tx_front) * TX queue. * @achan: ACPM channel info. * @xfer: reference to the transfer being prepared. + * + * Return: 0 on success, -errno otherwise. */ -static void acpm_prepare_xfer(struct acpm_chan *achan, - const struct acpm_xfer *xfer) +static int acpm_prepare_xfer(struct acpm_chan *achan, + const struct acpm_xfer *xfer) { struct acpm_rx_data *rx_data; u32 *txd = (u32 *)xfer->txd; + unsigned long size = ACPM_SEQNUM_MAX - 1; + unsigned long bit = achan->seqnum; - /* Prevent chan->seqnum from being re-used */ - do { - if (++achan->seqnum == ACPM_SEQNUM_MAX) - achan->seqnum = 1; - } while (test_bit(achan->seqnum - 1, achan->bitmap_seqnum)); + bit = find_next_zero_bit(achan->bitmap_seqnum, size, bit); + if (bit >= size) { + bit = find_first_zero_bit(achan->bitmap_seqnum, size); + if (bit >= size) { + dev_err_ratelimited(achan->acpm->dev, + "ACPM sequence number pool exhausted\n"); + return -EBUSY; + } + } + /* + * Execute the atomic set to formally claim the bit and establish + * LKMM Acquire semantics against the RX thread's clear_bit_unlock(). + * A loop is unnecessary because allocations are strictly serialized + * by tx_lock. + */ + if (WARN_ON_ONCE(test_and_set_bit_lock(bit, achan->bitmap_seqnum))) + return -EIO; + + /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */ + achan->seqnum = bit + 1; txd[0] |= FIELD_PREP(ACPM_PROTOCOL_SEQNUM, achan->seqnum); /* Clear data for upcoming responses */ - rx_data = &achan->rx_data[achan->seqnum - 1]; - memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->n_cmd); - if (xfer->rxd) - rx_data->response = true; + rx_data = &achan->rx_data[bit]; + rx_data->completed = false; + memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->cmdcnt); + /* zero means no response expected */ + rx_data->rxcnt = xfer->rxcnt; - /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */ - set_bit(achan->seqnum - 1, achan->bitmap_seqnum); + return 0; } /** @@ -444,7 +493,9 @@ int acpm_do_xfer(struct acpm_handle *handle, const struct acpm_xfer *xfer) if (ret) return ret; - acpm_prepare_xfer(achan, xfer); + ret = acpm_prepare_xfer(achan, xfer); + if (ret) + return ret; /* Write TX command. */ __iowrite32_copy(achan->tx.base + achan->mlen * tx_front, @@ -463,6 +514,32 @@ int acpm_do_xfer(struct acpm_handle *handle, const struct acpm_xfer *xfer) return acpm_wait_for_message_response(achan, xfer); } +/** + * acpm_set_xfer() - initialize an ACPM IPC transfer structure. + * @xfer: pointer to the ACPM transfer structure that is being initialized. + * @cmd: pointer to the buffer containing the command to be transmitted + * to the ACPM firmware. + * @cmdcnt: length of the command in 32-bit words. + * @acpm_chan_id: mailbox channel identifier. + * @response: boolean flag indicating whether the kernel expects the ACPM + * firmware to send a reply to this specific command. + */ +void acpm_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdcnt, + unsigned int acpm_chan_id, bool response) +{ + xfer->acpm_chan_id = acpm_chan_id; + xfer->txcnt = cmdcnt; + xfer->txd = cmd; + + if (response) { + xfer->rxcnt = cmdcnt; + xfer->rxd = cmd; + } else { + xfer->rxcnt = 0; + xfer->rxd = NULL; + } +} + /** * acpm_chan_shmem_get_params() - get channel parameters and addresses of the * TX/RX queues. @@ -504,19 +581,19 @@ static int acpm_achan_alloc_cmds(struct acpm_chan *achan) { struct device *dev = achan->acpm->dev; struct acpm_rx_data *rx_data; - size_t cmd_size, n_cmd; + size_t cmd_size, cmdcnt; int i; if (achan->mlen == 0) return 0; cmd_size = sizeof(*(achan->rx_data[0].cmd)); - n_cmd = DIV_ROUND_UP_ULL(achan->mlen, cmd_size); + cmdcnt = DIV_ROUND_UP_ULL(achan->mlen, cmd_size); for (i = 0; i < ACPM_SEQNUM_MAX; i++) { rx_data = &achan->rx_data[i]; - rx_data->n_cmd = n_cmd; - rx_data->cmd = devm_kcalloc(dev, n_cmd, cmd_size, GFP_KERNEL); + rx_data->cmdcnt = cmdcnt; + rx_data->cmd = devm_kcalloc(dev, cmdcnt, cmd_size, GFP_KERNEL); if (!rx_data->cmd) return -ENOMEM; } @@ -526,10 +603,11 @@ static int acpm_achan_alloc_cmds(struct acpm_chan *achan) /** * acpm_free_mbox_chans() - free mailbox channels. - * @acpm: pointer to driver data. + * @data: pointer to driver data. */ -static void acpm_free_mbox_chans(struct acpm_info *acpm) +static void acpm_free_mbox_chans(void *data) { + struct acpm_info *acpm = data; int i; for (i = 0; i < acpm->num_chans; i++) @@ -557,6 +635,10 @@ static int acpm_channels_init(struct acpm_info *acpm) if (!acpm->chans) return -ENOMEM; + ret = devm_add_action_or_reset(dev, acpm_free_mbox_chans, acpm); + if (ret) + return dev_err_probe(dev, ret, "Failed to add mbox free action.\n"); + chans_shmem = acpm->sram_base + readl(&shmem->chans); for (i = 0; i < acpm->num_chans; i++) { @@ -578,39 +660,44 @@ static int acpm_channels_init(struct acpm_info *acpm) cl->dev = dev; achan->chan = mbox_request_channel(cl, 0); - if (IS_ERR(achan->chan)) { - acpm_free_mbox_chans(acpm); + if (IS_ERR(achan->chan)) return PTR_ERR(achan->chan); - } } return 0; } -/** - * acpm_setup_ops() - setup the operations structures. - * @acpm: pointer to the driver data. - */ -static void acpm_setup_ops(struct acpm_info *acpm) -{ - struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs_ops; - struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic_ops; - - dvfs_ops->set_rate = acpm_dvfs_set_rate; - dvfs_ops->get_rate = acpm_dvfs_get_rate; - - pmic_ops->read_reg = acpm_pmic_read_reg; - pmic_ops->bulk_read = acpm_pmic_bulk_read; - pmic_ops->write_reg = acpm_pmic_write_reg; - pmic_ops->bulk_write = acpm_pmic_bulk_write; - pmic_ops->update_reg = acpm_pmic_update_reg; -} - static void acpm_clk_pdev_unregister(void *data) { platform_device_unregister(data); } +static const struct acpm_ops exynos_acpm_driver_ops = { + .dvfs = { + .set_rate = acpm_dvfs_set_rate, + .get_rate = acpm_dvfs_get_rate, + }, + + .pmic = { + .read_reg = acpm_pmic_read_reg, + .bulk_read = acpm_pmic_bulk_read, + .write_reg = acpm_pmic_write_reg, + .bulk_write = acpm_pmic_bulk_write, + .update_reg = acpm_pmic_update_reg, + }, + + .tmu = { + .init = acpm_tmu_init, + .read_temp = acpm_tmu_read_temp, + .set_threshold = acpm_tmu_set_threshold, + .set_interrupt_enable = acpm_tmu_set_interrupt_enable, + .tz_control = acpm_tmu_tz_control, + .clear_tz_irq = acpm_tmu_clear_tz_irq, + .suspend = acpm_tmu_suspend, + .resume = acpm_tmu_resume, + }, +}; + static int acpm_probe(struct platform_device *pdev) { const struct acpm_match_data *match_data; @@ -651,7 +738,7 @@ static int acpm_probe(struct platform_device *pdev) if (ret) return ret; - acpm_setup_ops(acpm); + acpm->handle.ops = &exynos_acpm_driver_ops; platform_set_drvdata(pdev, acpm); @@ -766,6 +853,29 @@ struct acpm_handle *devm_acpm_get_by_node(struct device *dev, } EXPORT_SYMBOL_GPL(devm_acpm_get_by_node); +/** + * devm_acpm_get_by_phandle - Resource managed lookup of the standardized + * "samsung,acpm-ipc" handle. + * @dev: consumer device + * + * Return: pointer to handle on success, ERR_PTR(-errno) otherwise. + */ +struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev) +{ + struct acpm_handle *handle; + struct device_node *np; + + np = of_parse_phandle(dev->of_node, "samsung,acpm-ipc", 0); + if (!np) + return ERR_PTR(-ENODEV); + + handle = devm_acpm_get_by_node(dev, np); + of_node_put(np); + + return handle; +} +EXPORT_SYMBOL_GPL(devm_acpm_get_by_phandle); + static const struct acpm_match_data acpm_gs101 = { .initdata_base = ACPM_GS101_INITDATA_BASE, .acpm_clk_dev_name = "gs101-acpm-clk", diff --git a/drivers/firmware/samsung/exynos-acpm.h b/drivers/firmware/samsung/exynos-acpm.h index 5df8354dc96c..708f6b0102ac 100644 --- a/drivers/firmware/samsung/exynos-acpm.h +++ b/drivers/firmware/samsung/exynos-acpm.h @@ -17,6 +17,8 @@ struct acpm_xfer { struct acpm_handle; +void acpm_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdcnt, + unsigned int acpm_chan_id, bool response); int acpm_do_xfer(struct acpm_handle *handle, const struct acpm_xfer *xfer); diff --git a/drivers/mfd/sec-acpm.c b/drivers/mfd/sec-acpm.c index 0e23b9d9f7ee..3397d13d3b7f 100644 --- a/drivers/mfd/sec-acpm.c +++ b/drivers/mfd/sec-acpm.c @@ -391,7 +391,7 @@ static int sec_pmic_acpm_bus_write(void *context, const void *data, { struct sec_pmic_acpm_bus_context *ctx = context; struct acpm_handle *acpm = ctx->shared->acpm; - const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops; + const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic; size_t val_count = count - BITS_TO_BYTES(ACPM_ADDR_BITS); const u8 *d = data; const u8 *vals = &d[BITS_TO_BYTES(ACPM_ADDR_BITS)]; @@ -411,7 +411,7 @@ static int sec_pmic_acpm_bus_read(void *context, const void *reg_buf, size_t reg { struct sec_pmic_acpm_bus_context *ctx = context; struct acpm_handle *acpm = ctx->shared->acpm; - const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops; + const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic; const u8 *r = reg_buf; u8 reg; @@ -430,7 +430,7 @@ static int sec_pmic_acpm_bus_reg_update_bits(void *context, unsigned int reg, un { struct sec_pmic_acpm_bus_context *ctx = context; struct acpm_handle *acpm = ctx->shared->acpm; - const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops; + const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic; return pmic_ops->update_reg(acpm, ctx->shared->acpm_chan_id, ctx->type, reg & 0xff, ctx->shared->speedy_channel, val, mask); diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h index 13f17dc4443b..c6b35c0ff300 100644 --- a/include/linux/firmware/samsung/exynos-acpm-protocol.h +++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h @@ -34,31 +34,41 @@ struct acpm_pmic_ops { u8 type, u8 reg, u8 chan, u8 value, u8 mask); }; +struct acpm_tmu_ops { + int (*init)(struct acpm_handle *handle, unsigned int acpm_chan_id); + int (*read_temp)(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, int *temp); + int (*set_threshold)(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, + const u8 temperature[8], size_t tlen); + int (*set_interrupt_enable)(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, u8 inten); + int (*tz_control)(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, bool enable); + int (*clear_tz_irq)(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz); + int (*suspend)(struct acpm_handle *handle, unsigned int acpm_chan_id); + int (*resume)(struct acpm_handle *handle, unsigned int acpm_chan_id); +}; + struct acpm_ops { - struct acpm_dvfs_ops dvfs_ops; - struct acpm_pmic_ops pmic_ops; + struct acpm_dvfs_ops dvfs; + struct acpm_pmic_ops pmic; + struct acpm_tmu_ops tmu; }; /** * struct acpm_handle - Reference to an initialized protocol instance - * @ops: + * @ops: pointer to the constant ACPM protocol operations. */ struct acpm_handle { - struct acpm_ops ops; + const struct acpm_ops *ops; }; struct device; -#if IS_ENABLED(CONFIG_EXYNOS_ACPM_PROTOCOL) struct acpm_handle *devm_acpm_get_by_node(struct device *dev, struct device_node *np); -#else - -static inline struct acpm_handle *devm_acpm_get_by_node(struct device *dev, - struct device_node *np) -{ - return NULL; -} -#endif +struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev); #endif /* __EXYNOS_ACPM_PROTOCOL_H */