Hi Tudor > -----Original Message----- > From: Tudor Ambarus <tudor.ambarus@xxxxxxxxxx> > Sent: Thursday, October 17, 2024 10:07 PM > To: jassisinghbrar@xxxxxxxxx; krzk@xxxxxxxxxx > Cc: alim.akhtar@xxxxxxxxxxx; mst@xxxxxxxxxx; javierm@xxxxxxxxxx; > tzimmermann@xxxxxxx; bartosz.golaszewski@xxxxxxxxxx; > luzmaximilian@xxxxxxxxx; sudeep.holla@xxxxxxx; > conor.dooley@xxxxxxxxxxxxx; bjorn@xxxxxxxxxxxx; ulf.hansson@xxxxxxxxxx; > linux-samsung-soc@xxxxxxxxxxxxxxx; linux-kernel@xxxxxxxxxxxxxxx; linux- > arm-kernel@xxxxxxxxxxxxxxxxxxx; marcan@xxxxxxxxx; neal@xxxxxxxxx; > alyssa@xxxxxxxxxxxxx; broonie@xxxxxxxxxx; andre.draszik@xxxxxxxxxx; > willmcvicker@xxxxxxxxxx; peter.griffin@xxxxxxxxxx; kernel- > team@xxxxxxxxxxx; vincent.guittot@xxxxxxxxxx; daniel.lezcano@xxxxxxxxxx; > Tudor Ambarus <tudor.ambarus@xxxxxxxxxx> > Subject: [PATCH v2 2/2] firmware: add exynos acpm driver > > ACPM (Alive Clock and Power Manager) is a firmware that operates on the > APM (Active Power Management) module that handles overall power > management activities. ACPM and masters regard each other as > independent hardware component and communicate with each other using > mailbox messages and shared memory. > > The mailbox channels are initialized based on the configuration data found at > a specific offset into the shared memory (mmio-sram). The configuration > data consists of channel id, message and queue lengths, pointers to the RX > and TX queues which are also part of the SRAM, and whether RX works by > polling or interrupts. All known clients of this driver are using polling channels, > thus the driver implements for now just polling mode. > > Add support for the exynos acpm core driver. Helper drivers will follow. > These will construct the mailbox messages in the format expected by the > firmware. > > Signed-off-by: Tudor Ambarus <tudor.ambarus@xxxxxxxxxx> > --- > drivers/firmware/Kconfig | 1 + > drivers/firmware/Makefile | 1 + > drivers/firmware/samsung/Kconfig | 11 + > drivers/firmware/samsung/Makefile | 3 + > drivers/firmware/samsung/exynos-acpm.c | 703 > ++++++++++++++++++++ > include/linux/mailbox/exynos-acpm-message.h | 21 + > 6 files changed, 740 insertions(+) > create mode 100644 drivers/firmware/samsung/Kconfig create mode > 100644 drivers/firmware/samsung/Makefile create mode 100644 > drivers/firmware/samsung/exynos-acpm.c > create mode 100644 include/linux/mailbox/exynos-acpm-message.h > > diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index > 71d8b26c4103..24edb956831b 100644 > --- a/drivers/firmware/Kconfig > +++ b/drivers/firmware/Kconfig > @@ -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" > diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index > 7a8d486e718f..91efcc868a05 100644 > --- a/drivers/firmware/Makefile > +++ b/drivers/firmware/Makefile > @@ -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/ > diff --git a/drivers/firmware/samsung/Kconfig > b/drivers/firmware/samsung/Kconfig > new file mode 100644 > index 000000000000..f908773c1441 > --- /dev/null > +++ b/drivers/firmware/samsung/Kconfig > @@ -0,0 +1,11 @@ > +# SPDX-License-Identifier: GPL-2.0-only > + > +config EXYNOS_ACPM This looks misleading to me, as you mentioned below, ACPM is a FW which runs on APM module, and The proposed driver is a communication method between Application processor and APM module, Which is via MAILBOX. So preferably EXYNOS_MAILBOX_APM is more meaningful here. > + tristate "Exynos ACPM (Alive Clock and Power Manager) driver > support" > + select MAILBOX > + help > + ACPM is a firmware that operates on the APM (Active Power > Management) > + module that handles overall power management activities. ACPM > and > + masters regard each other as independent hardware component > and > + communicate with each other using mailbox messages and shared > memory. > + This module provides the means to communicate with the ACPM > firmware. > diff --git a/drivers/firmware/samsung/Makefile > b/drivers/firmware/samsung/Makefile > new file mode 100644 > index 000000000000..35ff3076bbea > --- /dev/null > +++ b/drivers/firmware/samsung/Makefile > @@ -0,0 +1,3 @@ > +# SPDX-License-Identifier: GPL-2.0-only > + > +obj-$(CONFIG_EXYNOS_ACPM) += exynos-acpm.o > diff --git a/drivers/firmware/samsung/exynos-acpm.c > b/drivers/firmware/samsung/exynos-acpm.c > new file mode 100644 > index 000000000000..c3ad4dc7a9e0 > --- /dev/null > +++ b/drivers/firmware/samsung/exynos-acpm.c > @@ -0,0 +1,703 @@ > +// 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/bitmap.h> > +#include <linux/bits.h> > +#include <linux/container_of.h> > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/io.h> > +#include <linux/iopoll.h> > +#include <linux/mailbox_controller.h> > +#include <linux/mailbox/exynos-acpm-message.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/of.h> > +#include <linux/of_address.h> > +#include <linux/platform_device.h> > +#include <linux/slab.h> > + > +#define EXYNOS_ACPM_MCUCTRL 0x0 /* Mailbox Control > Register */ > +#define EXYNOS_ACPM_INTCR0 0x24 /* Interrupt Clear > Register 0 */ > +#define EXYNOS_ACPM_INTMR0 0x28 /* Interrupt Mask > Register 0 */ > +#define EXYNOS_ACPM_INTSR0 0x2c /* Interrupt Status > Register 0 */ > +#define EXYNOS_ACPM_INTMSR0 0x30 /* Interrupt Mask > Status Register 0 */ > +#define EXYNOS_ACPM_INTGR1 0x40 /* Interrupt > Generation Register 1 */ > +#define EXYNOS_ACPM_INTMR1 0x48 /* Interrupt Mask > Register 1 */ > +#define EXYNOS_ACPM_INTSR1 0x4c /* Interrupt Status > Register 1 */ > +#define EXYNOS_ACPM_INTMSR1 0x50 /* Interrupt Mask > Status Register 1 */ > + > +#define EXYNOS_ACPM_INTMR0_MASK GENMASK(15, 0) > +#define EXYNOS_ACPM_PROTOCOL_SEQNUM GENMASK(21, 16) > + > +/* The unit of counter is 20 us. 5000 * 20 = 100 ms */ > +#define EXYNOS_ACPM_POLL_TIMEOUT 5000 > +#define EXYNOS_ACPM_TX_TIMEOUT_US 500000 > + > +/** > + * struct exynos_acpm_shmem - mailbox shared memory configuration > information. > + * @reserved: reserved for future use. > + * @chans: offset to array of struct exynos_acpm_shmem_chan. > + * @reserved1: reserved for future use. > + * @num_chans: number of channels. > + */ > +struct exynos_acpm_shmem { > + u32 reserved[2]; > + u32 chans; > + u32 reserved1[3]; > + u32 num_chans; > +}; > + > +/** > + * struct exynos_acpm_shmem_chan - descriptor of a shared memory > channel. > + * > + * @id: channel ID. > + * @reserved: reserved for future use. > + * @rx_rear: rear pointer of RX queue. > + * @rx_front: front pointer of RX queue. > + * @rx_base: base address of RX queue. > + * @reserved1: reserved for future use. > + * @tx_rear: rear pointer of TX queue. > + * @tx_front: front pointer of TX queue. > + * @tx_base: base address of TX queue. > + * @qlen: queue length. Applies to both TX/RX queues. > + * @mlen: message length. Applies to both TX/RX queues. > + * @reserved2: reserved for future use. > + * @polling: true when the channel works on polling. > + */ > +struct exynos_acpm_shmem_chan { > + u32 id; > + u32 reserved[3]; > + u32 rx_rear; > + u32 rx_front; > + u32 rx_base; > + u32 reserved1[3]; > + u32 tx_rear; > + u32 tx_front; > + u32 tx_base; > + u32 qlen; > + u32 mlen; > + u32 reserved2[2]; > + u32 polling; > +}; > + > +/** > + * struct exynos_acpm_queue - exynos acpm queue. > + * > + * @rear: rear address of the queue. > + * @front: front address of the queue. > + * @base: base address of the queue. > + */ > +struct exynos_acpm_queue { > + void __iomem *rear; > + void __iomem *front; > + void __iomem *base; > +}; > + > +/** > + * struct exynos_acpm_rx_data - RX queue data. > + * > + * @cmd: pointer to where the data shall be saved. > + * @response: true if the client expects the RX data. > + */ > +struct exynos_acpm_rx_data { > + u32 *cmd; > + bool response; > +}; > + > +#define EXYNOS_ACPM_SEQNUM_MAX 64 > + > +/** > + * struct exynos_acpm_chan - driver internal representation of a channel. > + * @tx: TX queue. The enqueue is done by the host. > + * - front index is written by the host. > + * - rear index is written by the firmware. > + * > + * @rx: RX queue. The enqueue is done by the firmware. > + * - front index is written by the firmware. > + * - rear index is written by the host. > + * @rx_lock: protects RX queue. The RX queue is accessed just in > + * process context. > + * @tx_lock: protects TX queue. > + * @qlen: queue length. Applies to both TX/RX queues. > + * @mlen: message length. Applies to both TX/RX queues. > + * @seqnum: sequence number of the last message enqueued on TX > queue. > + * @id: channel ID. > + * @polling: true when the channel works on polling. > + * @bitmap_seqnum: bitmap that tracks the messages on the TX/RX > queues. > + * @rx_data: internal buffer used to drain the RX queue. > + */ > +struct exynos_acpm_chan { > + struct exynos_acpm_queue tx; > + struct exynos_acpm_queue rx; > + struct mutex rx_lock; > + spinlock_t tx_lock; > + > + unsigned int qlen; > + unsigned int mlen; > + u8 seqnum; > + u8 id; > + bool polling; > + > + DECLARE_BITMAP(bitmap_seqnum, EXYNOS_ACPM_SEQNUM_MAX > - 1); > + struct exynos_acpm_rx_data > rx_data[EXYNOS_ACPM_SEQNUM_MAX]; }; > + > +/** > + * struct exynos_acpm - driver's private data. > + * @shmem: pointer to the SRAM configuration data. > + * @chans: pointer to the ACPM channel parameters retrieved from > SRAM. > + * @sram_base: base address of SRAM. > + * @regs: mailbox registers base address. > + * @mbox: pointer to the mailbox controller. > + * @wq: pointer to workqueue. > + * @dev: pointer to the exynos-acpm device. > + * @pclk: pointer to the mailbox peripheral clock. > + * @num_chans: number of channels available for this controller. > + */ > +struct exynos_acpm { > + struct exynos_acpm_shmem *shmem; > + struct exynos_acpm_chan *chans; > + void __iomem *sram_base; > + void __iomem *regs; > + struct mbox_controller *mbox; > + struct workqueue_struct *wq; > + struct device *dev; > + struct clk *pclk; > + u32 num_chans; > +}; > + > +/** > + * struct exynos_acpm_work_data - data structure representing the work. > + * @mbox_chan: pointer to the mailbox channel. > + * @req: pointer to the mailbox request. > + * @callback: pointer to a callback function to be invoked upon > + * completion of this request. > + * @work: describes the task to be executed. > + */ > +struct exynos_acpm_work_data { > + struct mbox_chan *mbox_chan; > + struct mbox_request *req; > + void (*callback)(struct exynos_acpm_work_data *work_data, int > status); > + struct work_struct work; > +}; > + > +static int exynos_acpm_get_rx(struct mbox_chan *mbox_chan, > + struct mbox_request *req) > +{ > + struct exynos_acpm_chan *chan = mbox_chan->con_priv; > + struct exynos_acpm_message *tx = req->tx; > + struct exynos_acpm_message *rx = req->rx; > + struct exynos_acpm_rx_data *rx_data; > + const void __iomem *base, *addr; > + u32 rx_front, rx_seqnum, tx_seqnum, seqnum; > + u32 i, val, mlen; > + bool rx_set = false; > + > + rx_front = readl_relaxed(chan->rx.front); > + i = readl_relaxed(chan->rx.rear); > + > + /* Bail out if RX is empty. */ > + if (i == rx_front) > + return 0; > + > + base = chan->rx.base; > + mlen = chan->mlen; > + > + tx_seqnum = FIELD_GET(EXYNOS_ACPM_PROTOCOL_SEQNUM, tx- > >cmd[0]); > + > + /* Drain RX queue. */ > + do { > + /* Read RX seqnum. */ > + addr = base + mlen * i; > + val = readl_relaxed(addr); > + > + rx_seqnum = > FIELD_GET(EXYNOS_ACPM_PROTOCOL_SEQNUM, val); > + if (!rx_seqnum) > + return -EIO; > + /* > + * mssg seqnum starts with value 1, whereas the driver > considers > + * the first mssg at index 0. > + */ > + seqnum = rx_seqnum - 1; > + rx_data = &chan->rx_data[seqnum]; > + > + if (rx_data->response) { > + if (rx_seqnum == tx_seqnum) { > + __ioread32_copy(rx->cmd, addr, req->rxlen / > 4); > + rx_set = true; > + clear_bit(seqnum, chan->bitmap_seqnum); > + } else { > + /* > + * The RX data corresponds to another > request. > + * Save the data to drain the queue, but don't > + * clear yet the bitmap. It will be cleared > + * after the response is copied to the request. > + */ > + __ioread32_copy(rx_data->cmd, addr, > + req->rxlen / 4); > + } > + } else { > + clear_bit(seqnum, chan->bitmap_seqnum); > + } > + > + i = (i + 1) % chan->qlen; > + } while (i != rx_front); > + > + /* We saved all responses, mark RX empty. */ > + writel_relaxed(rx_front, chan->rx.rear); > + > + /* Flush SRAM posted writes. */ > + readl_relaxed(chan->rx.front); > + > + /* > + * If the response was not in this iteration of the queue, check if the > + * RX data was previously saved. > + */ > + rx_data = &chan->rx_data[tx_seqnum - 1]; > + if (!rx_set && rx_data->response) { > + rx_seqnum = > FIELD_GET(EXYNOS_ACPM_PROTOCOL_SEQNUM, > + rx_data->cmd[0]); > + > + if (rx_seqnum == tx_seqnum) { > + memcpy(rx->cmd, rx_data->cmd, req->rxlen); > + clear_bit(rx_seqnum - 1, chan->bitmap_seqnum); > + } > + } > + > + return 0; > +} > + > +static int exynos_acpm_dequeue_by_polling(struct mbox_chan > *mbox_chan, > + struct mbox_request *req) > +{ > + struct exynos_acpm_chan *chan = mbox_chan->con_priv; > + struct exynos_acpm_message *tx = req->tx; > + struct device *dev = mbox_chan->mbox->dev; > + unsigned int cnt_20us = 0; > + u32 seqnum; > + int ret; > + > + seqnum = FIELD_GET(EXYNOS_ACPM_PROTOCOL_SEQNUM, tx- > >cmd[0]); > + > + do { > + ret = mutex_lock_interruptible(&chan->rx_lock); > + if (ret) > + return ret; > + ret = exynos_acpm_get_rx(mbox_chan, req); > + mutex_unlock(&chan->rx_lock); > + if (ret) > + return ret; > + > + if (!test_bit(seqnum - 1, chan->bitmap_seqnum)) { > + dev_vdbg(dev, "cnt_20us = %d.\n", cnt_20us); > + return 0; > + } > + > + /* Determined experimentally. */ > + usleep_range(20, 30); > + cnt_20us++; > + } while (cnt_20us < EXYNOS_ACPM_POLL_TIMEOUT); > + > + dev_err(dev, "Timeout! ch:%u s:%u bitmap:%lx, cnt_20us = %d.\n", > + chan->id, seqnum, chan->bitmap_seqnum[0], cnt_20us); > + > + return -ETIME; > +} > + > +static void exynos_acpm_done(struct exynos_acpm_work_data > *work_data, > +int status) { > + struct mbox_request *req = work_data->req; > + > + kfree(work_data); > + mbox_request_complete(req, status); > +} > + > +static void exynos_acpm_work_handler(struct work_struct *work) { > + struct exynos_acpm_work_data *work_data = > + container_of(work, struct exynos_acpm_work_data, work); > + struct mbox_chan *mbox_chan = work_data->mbox_chan; > + int ret; > + > + ret = exynos_acpm_dequeue_by_polling(mbox_chan, work_data- > >req); > + work_data->callback(work_data, ret); > +} > + > +static struct exynos_acpm_work_data * > + exynos_acpm_init_work(struct mbox_chan *mbox_chan, > + struct mbox_request *req) > +{ > + struct exynos_acpm_work_data *work_data; > + gfp_t gfp = (req->flags & MBOX_REQ_MAY_SLEEP) ? GFP_KERNEL : > +GFP_ATOMIC; > + > + work_data = kmalloc(sizeof(*work_data), gfp); > + if (!work_data) > + return ERR_PTR(-ENOMEM); > + > + work_data->mbox_chan = mbox_chan; > + work_data->req = req; > + work_data->callback = exynos_acpm_done; > + INIT_WORK(&work_data->work, exynos_acpm_work_handler); > + > + return work_data; > +} > + > +static void exynos_acpm_prepare_request(struct mbox_chan *mbox_chan, > + struct mbox_request *req) > +{ > + struct exynos_acpm_chan *chan = mbox_chan->con_priv; > + struct exynos_acpm_message *tx = req->tx; > + struct exynos_acpm_rx_data *rx_data; > + > + /* Prevent chan->seqnum from being re-used */ > + do { > + if (++chan->seqnum == EXYNOS_ACPM_SEQNUM_MAX) > + chan->seqnum = 1; > + } while (test_bit(chan->seqnum - 1, chan->bitmap_seqnum)); > + > + tx->cmd[0] |= FIELD_PREP(EXYNOS_ACPM_PROTOCOL_SEQNUM, > chan->seqnum); > + > + /* Clear data for upcoming responses */ > + rx_data = &chan->rx_data[chan->seqnum - 1]; > + memset(rx_data->cmd, 0, sizeof(*(rx_data->cmd)) * chan->mlen); > + if (req->rx) > + rx_data->response = true; > + > + /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */ > + set_bit(chan->seqnum - 1, chan->bitmap_seqnum); } > + > +static int exynos_acpm_wait_for_queue_slots(struct mbox_chan > *mbox_chan, > + u32 next_tx_front) > +{ > + struct exynos_acpm_chan *chan = mbox_chan->con_priv; > + struct device *dev = mbox_chan->mbox->dev; > + u32 val, ret; > + > + /* > + * Wait for RX front to keep up with TX front. Make sure there's at > + * least one element between them. > + */ > + ret = readl_relaxed_poll_timeout_atomic(chan->rx.front, val, > + next_tx_front != val, 1, > + > EXYNOS_ACPM_TX_TIMEOUT_US); > + if (ret) { > + dev_err(dev, "RX front can not keep up with TX front.\n"); > + return ret; > + } > + > + ret = readl_relaxed_poll_timeout_atomic(chan->tx.rear, val, > + next_tx_front != val, 1, > + > EXYNOS_ACPM_TX_TIMEOUT_US); > + if (ret) > + dev_err(dev, "TX queue is full.\n"); > + > + return ret; > +} > + > +static int exynos_acpm_send_request(struct mbox_chan *mbox_chan, > + struct mbox_request *req) > +{ > + struct exynos_acpm *exynos_acpm = dev_get_drvdata(mbox_chan- > >mbox->dev); > + struct exynos_acpm_chan *chan = mbox_chan->con_priv; > + struct exynos_acpm_message *tx = req->tx; > + struct exynos_acpm_work_data *work_data; > + u32 idx, tx_front; > + unsigned long flags; > + int ret; > + > + if (!tx || !tx->cmd || req->txlen > chan->mlen || > + req->rxlen > chan->mlen) > + return -EINVAL; > + > + work_data = exynos_acpm_init_work(mbox_chan, req); > + if (IS_ERR(work_data)) > + return PTR_ERR(work_data); > + > + spin_lock_irqsave(&chan->tx_lock, flags); > + > + tx_front = readl_relaxed(chan->tx.front); > + idx = (tx_front + 1) % chan->qlen; > + > + ret = exynos_acpm_wait_for_queue_slots(mbox_chan, idx); > + if (ret) > + goto exit; > + > + exynos_acpm_prepare_request(mbox_chan, req); > + > + /* Write TX command. */ > + __iowrite32_copy(chan->tx.base + chan->mlen * tx_front, tx->cmd, > + req->txlen / 4); > + > + /* Advance TX front. */ > + writel_relaxed(idx, chan->tx.front); > + > + /* Flush SRAM posted writes. */ > + readl_relaxed(chan->tx.front); > + > + /* Generate ACPM interrupt. */ > + writel_relaxed(BIT(chan->id), exynos_acpm->regs + > EXYNOS_ACPM_INTGR1); > + > + /* Flush mailbox controller posted writes. */ > + readl_relaxed(exynos_acpm->regs + EXYNOS_ACPM_MCUCTRL); > + > + spin_unlock_irqrestore(&chan->tx_lock, flags); > + > + queue_work(exynos_acpm->wq, &work_data->work); > + > + return -EINPROGRESS; > +exit: > + spin_unlock_irqrestore(&chan->tx_lock, flags); > + kfree(work_data); > + return ret; > +} > + > +static int exynos_acpm_chan_startup(struct mbox_chan *mbox_chan) { > + struct exynos_acpm_chan *chan = mbox_chan->con_priv; > + > + if (!chan->polling) { > + dev_err(mbox_chan->mbox->dev, "IRQs not > supported.\n"); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static const struct mbox_chan_ops exynos_acpm_chan_ops = { > + .send_request = exynos_acpm_send_request, > + .startup = exynos_acpm_chan_startup, > +}; > + > +static void __iomem *exynos_acpm_get_iomem_addr(void __iomem > *base, > + void __iomem *addr) > +{ > + u32 offset; > + > + offset = readl_relaxed(addr); > + return base + offset; > +} > + > +static void exynos_acpm_rx_queue_init(struct exynos_acpm > *exynos_acpm, > + struct exynos_acpm_shmem_chan > *shmem_chan, > + struct exynos_acpm_queue *rx) { > + void __iomem *base = exynos_acpm->sram_base; > + > + rx->base = exynos_acpm_get_iomem_addr(base, &shmem_chan- > >tx_base); > + rx->front = exynos_acpm_get_iomem_addr(base, &shmem_chan- > >tx_front); > + rx->rear = exynos_acpm_get_iomem_addr(base, &shmem_chan- > >tx_rear); } > + > +static void exynos_acpm_tx_queue_init(struct exynos_acpm > *exynos_acpm, > + struct exynos_acpm_shmem_chan > *shmem_chan, > + struct exynos_acpm_queue *tx) { > + void __iomem *base = exynos_acpm->sram_base; > + > + tx->base = exynos_acpm_get_iomem_addr(base, &shmem_chan- > >rx_base); > + tx->front = exynos_acpm_get_iomem_addr(base, &shmem_chan- > >rx_front); > + tx->rear = exynos_acpm_get_iomem_addr(base, &shmem_chan- > >rx_rear); } > + > +static int exynos_acpm_alloc_cmds(struct exynos_acpm *exynos_acpm, > + struct exynos_acpm_chan *chan) > +{ > + struct device *dev = exynos_acpm->dev; > + struct exynos_acpm_rx_data *rx_data; > + unsigned int mlen = chan->mlen; > + int i; > + > + for (i = 0; i < EXYNOS_ACPM_SEQNUM_MAX; i++) { > + rx_data = &chan->rx_data[i]; > + rx_data->cmd = devm_kcalloc(dev, mlen, sizeof(*(rx_data- > >cmd)), > + GFP_KERNEL); > + if (!rx_data->cmd) > + return -ENOMEM; > + } > + > + return 0; > +} > + > +static int exynos_acpm_chans_init(struct exynos_acpm *exynos_acpm) { > + struct exynos_acpm_shmem_chan *shmem_chans, *shmem_chan; > + struct exynos_acpm_shmem *shmem = exynos_acpm->shmem; > + struct mbox_chan *mbox_chan, *mbox_chans; > + struct exynos_acpm_chan *chan, *chans; > + struct device *dev = exynos_acpm->dev; > + int i, ret; > + > + exynos_acpm->num_chans = readl_relaxed(&shmem->num_chans); > + > + mbox_chans = devm_kcalloc(dev, exynos_acpm->num_chans, > + sizeof(*mbox_chans), GFP_KERNEL); > + if (!mbox_chans) > + return -ENOMEM; > + > + chans = devm_kcalloc(dev, exynos_acpm->num_chans, > sizeof(*chans), > + GFP_KERNEL); > + if (!chans) > + return -ENOMEM; > + > + shmem_chans = exynos_acpm_get_iomem_addr(exynos_acpm- > >sram_base, > + &shmem->chans); > + > + for (i = 0; i < exynos_acpm->num_chans; i++) { > + shmem_chan = &shmem_chans[i]; > + mbox_chan = &mbox_chans[i]; > + chan = &chans[i]; > + > + /* Set parameters for the mailbox channel. */ > + mbox_chan->con_priv = chan; > + mbox_chan->mbox = exynos_acpm->mbox; > + > + /* Set parameters for the ACPM channel. */ > + chan->mlen = readl_relaxed(&shmem_chan->mlen); > + > + ret = exynos_acpm_alloc_cmds(exynos_acpm, chan); > + if (ret) > + return ret; > + > + chan->polling = readl_relaxed(&shmem_chan->polling); > + chan->id = readl_relaxed(&shmem_chan->id); > + chan->qlen = readl_relaxed(&shmem_chan->qlen); > + > + exynos_acpm_rx_queue_init(exynos_acpm, shmem_chan, > &chan->rx); > + exynos_acpm_tx_queue_init(exynos_acpm, shmem_chan, > &chan->tx); > + > + mutex_init(&chan->rx_lock); > + spin_lock_init(&chan->tx_lock); > + > + dev_vdbg(dev, "ID = %d poll = %d, mlen = %d, qlen = %d\n", > + chan->id, chan->polling, chan->mlen, chan->qlen); > + } > + > + /* Save pointers to the ACPM and mailbox channels. */ > + exynos_acpm->mbox->chans = mbox_chans; > + exynos_acpm->chans = chans; > + > + return 0; > +} > + > +static const struct of_device_id exynos_acpm_match[] = { > + { .compatible = "google,gs101-acpm" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, exynos_acpm_match); > + > +static int exynos_acpm_probe(struct platform_device *pdev) { > + struct device_node *node = pdev->dev.of_node; > + struct device *dev = &pdev->dev; > + struct exynos_acpm *exynos_acpm; > + struct mbox_controller *mbox; > + struct device_node *shmem; > + resource_size_t size; > + struct resource res; > + const __be32 *prop; > + int ret; > + > + exynos_acpm = devm_kzalloc(dev, sizeof(*exynos_acpm), > GFP_KERNEL); > + if (!exynos_acpm) > + return -ENOMEM; > + > + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); > + if (!mbox) > + return -ENOMEM; > + > + exynos_acpm->regs = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(exynos_acpm->regs)) > + return PTR_ERR(exynos_acpm->regs); > + > + shmem = of_parse_phandle(node, "shmem", 0); > + ret = of_address_to_resource(shmem, 0, &res); > + of_node_put(shmem); > + if (ret) { > + dev_err(dev, "Failed to get shared memory.\n"); > + return ret; > + } > + > + size = resource_size(&res); > + exynos_acpm->sram_base = devm_ioremap(dev, res.start, size); > + if (!exynos_acpm->sram_base) { > + dev_err(dev, "Failed to ioremap shared memory.\n"); > + return -ENOMEM; > + } > + > + prop = of_get_property(node, "initdata-base", NULL); > + if (!prop) { > + dev_err(dev, "Parsing initdata_base failed.\n"); > + return -EINVAL; > + } > + > + exynos_acpm->pclk = devm_clk_get(dev, "pclk"); > + if (IS_ERR(exynos_acpm->pclk)) { > + dev_err(dev, "Missing peripheral clock.\n"); > + return PTR_ERR(exynos_acpm->pclk); > + } > + > + ret = clk_prepare_enable(exynos_acpm->pclk); > + if (ret) { > + dev_err(dev, "Failed to enable the peripheral clock.\n"); > + return ret; > + } > + > + exynos_acpm->wq = alloc_workqueue("exynos-acpm-wq", 0, 0); > + if (!exynos_acpm->wq) > + return -ENOMEM; > + > + exynos_acpm->dev = dev; > + exynos_acpm->mbox = mbox; > + exynos_acpm->shmem = exynos_acpm->sram_base + > be32_to_cpup(prop); > + > + ret = exynos_acpm_chans_init(exynos_acpm); > + if (ret) > + return ret; > + > + mbox->num_chans = exynos_acpm->num_chans; > + mbox->dev = dev; > + mbox->ops = &exynos_acpm_chan_ops; > + > + platform_set_drvdata(pdev, exynos_acpm); > + > + /* Mask out all interrupts. We support just polling channels for now. > */ > + writel_relaxed(EXYNOS_ACPM_INTMR0_MASK, > + exynos_acpm->regs + EXYNOS_ACPM_INTMR0); > + > + ret = devm_mbox_controller_register(dev, mbox); > + if (ret) > + dev_err(dev, "Failed to register mbox_controller(%d).\n", > ret); > + > + return ret; > +} > + > +static void exynos_acpm_remove(struct platform_device *pdev) { > + struct exynos_acpm *exynos_acpm = platform_get_drvdata(pdev); > + > + flush_workqueue(exynos_acpm->wq); > + destroy_workqueue(exynos_acpm->wq); > + clk_disable_unprepare(exynos_acpm->pclk); > +} > + > +static struct platform_driver exynos_acpm_driver = { > + .probe = exynos_acpm_probe, > + .remove = exynos_acpm_remove, > + .driver = { > + .name = "exynos-acpm", > + .owner = THIS_MODULE, > + .of_match_table = exynos_acpm_match, > + }, > +}; > +module_platform_driver(exynos_acpm_driver); > + > +MODULE_AUTHOR("Tudor Ambarus <tudor.ambarus@xxxxxxxxxx>"); > +MODULE_DESCRIPTION("EXYNOS ACPM mailbox driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/linux/mailbox/exynos-acpm-message.h > b/include/linux/mailbox/exynos-acpm-message.h > new file mode 100644 > index 000000000000..3799868c40b8 > --- /dev/null > +++ b/include/linux/mailbox/exynos-acpm-message.h > @@ -0,0 +1,21 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright 2024 Linaro Ltd. > + */ > + > +#ifndef _LINUX_EXYNOS_ACPM_MESSAGE_H_ > +#define _LINUX_EXYNOS_ACPM_MESSAGE_H_ > + > +#include <linux/types.h> > + > +/** > + * struct exynos_acpm_message - exynos ACPM mailbox message format. > + * @cmd: pointer to u32 command. > + * @len: length of the command. > + */ > +struct exynos_acpm_message { > + u32 *cmd; > + size_t len; > +}; > + > +#endif /* _LINUX_EXYNOS_ACPM_MESSAGE_H_ */ > -- > 2.47.0.rc1.288.g06298d1525-goog