| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Copyright (c) 2017 Intel Corporation | |
| * | |
| * Intel Mobile Internet Devices (MID) based on Intel Atom SoCs have few | |
| * microcontrollers inside to do some auxiliary tasks. One of such | |
| * microcontroller is System Controller Unit (SCU) which, in particular, | |
| * is servicing watchdog and controlling system reset function. | |
| * | |
| * This driver enables IPC channel to SCU. | |
| */ | |
| #include <dm.h> | |
| #include <regmap.h> | |
| #include <syscon.h> | |
| #include <asm/cpu.h> | |
| #include <asm/scu.h> | |
| #include <linux/bitops.h> | |
| #include <linux/delay.h> | |
| #include <linux/errno.h> | |
| #include <linux/io.h> | |
| #include <linux/kernel.h> | |
| /* SCU register map */ | |
| struct ipc_regs { | |
| u32 cmd; | |
| u32 status; | |
| u32 sptr; | |
| u32 dptr; | |
| u32 reserved[28]; | |
| u32 wbuf[4]; | |
| u32 rbuf[4]; | |
| }; | |
| struct scu { | |
| struct ipc_regs *regs; | |
| }; | |
| /** | |
| * scu_ipc_send_command() - send command to SCU | |
| * @regs: register map of SCU | |
| * @cmd: command | |
| * | |
| * Command Register (Write Only): | |
| * A write to this register results in an interrupt to the SCU core processor | |
| * Format: | |
| * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)| | |
| */ | |
| static void scu_ipc_send_command(struct ipc_regs *regs, u32 cmd) | |
| { | |
| writel(cmd, ®s->cmd); | |
| } | |
| /** | |
| * scu_ipc_check_status() - check status of last command | |
| * @regs: register map of SCU | |
| * | |
| * Status Register (Read Only): | |
| * Driver will read this register to get the ready/busy status of the IPC | |
| * block and error status of the IPC command that was just processed by SCU | |
| * Format: | |
| * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)| | |
| */ | |
| static int scu_ipc_check_status(struct ipc_regs *regs) | |
| { | |
| int loop_count = 100000; | |
| int status; | |
| do { | |
| status = readl(®s->status); | |
| if (!(status & BIT(0))) | |
| break; | |
| udelay(1); | |
| } while (--loop_count); | |
| if (!loop_count) | |
| return -ETIMEDOUT; | |
| if (status & BIT(1)) { | |
| printf("%s() status=0x%08x\n", __func__, status); | |
| return -EIO; | |
| } | |
| return 0; | |
| } | |
| static int scu_ipc_cmd(struct ipc_regs *regs, u32 cmd, u32 sub, | |
| u32 *in, int inlen, u32 *out, int outlen) | |
| { | |
| int i, err; | |
| for (i = 0; i < inlen; i++) | |
| writel(*in++, ®s->wbuf[i]); | |
| scu_ipc_send_command(regs, (inlen << 16) | (sub << 12) | cmd); | |
| err = scu_ipc_check_status(regs); | |
| if (!err) { | |
| for (i = 0; i < outlen; i++) | |
| *out++ = readl(®s->rbuf[i]); | |
| } | |
| return err; | |
| } | |
| /** | |
| * scu_ipc_raw_command() - IPC command with data and pointers | |
| * @cmd: IPC command code | |
| * @sub: IPC command sub type | |
| * @in: input data of this IPC command | |
| * @inlen: input data length in dwords | |
| * @out: output data of this IPC command | |
| * @outlen: output data length in dwords | |
| * @dptr: data writing to SPTR register | |
| * @sptr: data writing to DPTR register | |
| * | |
| * Send an IPC command to SCU with input/output data and source/dest pointers. | |
| * | |
| * Return: an IPC error code or 0 on success. | |
| */ | |
| int scu_ipc_raw_command(u32 cmd, u32 sub, u32 *in, int inlen, u32 *out, | |
| int outlen, u32 dptr, u32 sptr) | |
| { | |
| int inbuflen = DIV_ROUND_UP(inlen, 4); | |
| struct udevice *dev; | |
| struct scu *scu; | |
| int ret; | |
| ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); | |
| if (ret) | |
| return ret; | |
| scu = dev_get_priv(dev); | |
| /* Up to 16 bytes */ | |
| if (inbuflen > 4) | |
| return -EINVAL; | |
| writel(dptr, &scu->regs->dptr); | |
| writel(sptr, &scu->regs->sptr); | |
| /* | |
| * SRAM controller doesn't support 8-bit writes, it only | |
| * supports 32-bit writes, so we have to copy input data into | |
| * the temporary buffer, and SCU FW will use the inlen to | |
| * determine the actual input data length in the temporary | |
| * buffer. | |
| */ | |
| u32 inbuf[4] = {0}; | |
| memcpy(inbuf, in, inlen); | |
| return scu_ipc_cmd(scu->regs, cmd, sub, inbuf, inlen, out, outlen); | |
| } | |
| /** | |
| * scu_ipc_simple_command() - send a simple command | |
| * @cmd: command | |
| * @sub: sub type | |
| * | |
| * Issue a simple command to the SCU. Do not use this interface if | |
| * you must then access data as any data values may be overwritten | |
| * by another SCU access by the time this function returns. | |
| * | |
| * This function may sleep. Locking for SCU accesses is handled for | |
| * the caller. | |
| */ | |
| int scu_ipc_simple_command(u32 cmd, u32 sub) | |
| { | |
| struct scu *scu; | |
| struct udevice *dev; | |
| int ret; | |
| ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); | |
| if (ret) | |
| return ret; | |
| scu = dev_get_priv(dev); | |
| scu_ipc_send_command(scu->regs, sub << 12 | cmd); | |
| return scu_ipc_check_status(scu->regs); | |
| } | |
| /** | |
| * scu_ipc_command - command with data | |
| * @cmd: command | |
| * @sub: sub type | |
| * @in: input data | |
| * @inlen: input length in dwords | |
| * @out: output data | |
| * @outlen: output length in dwords | |
| * | |
| * Issue a command to the SCU which involves data transfers. | |
| */ | |
| int scu_ipc_command(u32 cmd, u32 sub, u32 *in, int inlen, u32 *out, int outlen) | |
| { | |
| struct scu *scu; | |
| struct udevice *dev; | |
| int ret; | |
| ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); | |
| if (ret) | |
| return ret; | |
| scu = dev_get_priv(dev); | |
| return scu_ipc_cmd(scu->regs, cmd, sub, in, inlen, out, outlen); | |
| } | |
| static int scu_ipc_probe(struct udevice *dev) | |
| { | |
| struct scu *scu = dev_get_priv(dev); | |
| scu->regs = syscon_get_first_range(X86_SYSCON_SCU); | |
| return 0; | |
| } | |
| static const struct udevice_id scu_ipc_match[] = { | |
| { .compatible = "intel,scu-ipc", .data = X86_SYSCON_SCU }, | |
| { /* sentinel */ } | |
| }; | |
| U_BOOT_DRIVER(scu_ipc) = { | |
| .name = "scu_ipc", | |
| .id = UCLASS_SYSCON, | |
| .of_match = scu_ipc_match, | |
| .probe = scu_ipc_probe, | |
| .priv_auto = sizeof(struct scu), | |
| }; |