1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright (c) 2017 Intel Corporation 4 * 5 * Intel Mobile Internet Devices (MID) based on Intel Atom SoCs have few 6 * microcontrollers inside to do some auxiliary tasks. One of such 7 * microcontroller is System Controller Unit (SCU) which, in particular, 8 * is servicing watchdog and controlling system reset function. 9 * 10 * This driver enables IPC channel to SCU. 11 */ 12#include <common.h> 13#include <dm.h> 14#include <regmap.h> 15#include <syscon.h> 16#include <asm/cpu.h> 17#include <asm/scu.h> 18#include <linux/bitops.h> 19#include <linux/delay.h> 20#include <linux/errno.h> 21#include <linux/io.h> 22#include <linux/kernel.h> 23 24/* SCU register map */ 25struct ipc_regs { 26 u32 cmd; 27 u32 status; 28 u32 sptr; 29 u32 dptr; 30 u32 reserved[28]; 31 u32 wbuf[4]; 32 u32 rbuf[4]; 33}; 34 35struct scu { 36 struct ipc_regs *regs; 37}; 38 39/** 40 * scu_ipc_send_command() - send command to SCU 41 * @regs: register map of SCU 42 * @cmd: command 43 * 44 * Command Register (Write Only): 45 * A write to this register results in an interrupt to the SCU core processor 46 * Format: 47 * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)| 48 */ 49static void scu_ipc_send_command(struct ipc_regs *regs, u32 cmd) 50{ 51 writel(cmd, ®s->cmd); 52} 53 54/** 55 * scu_ipc_check_status() - check status of last command 56 * @regs: register map of SCU 57 * 58 * Status Register (Read Only): 59 * Driver will read this register to get the ready/busy status of the IPC 60 * block and error status of the IPC command that was just processed by SCU 61 * Format: 62 * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)| 63 */ 64static int scu_ipc_check_status(struct ipc_regs *regs) 65{ 66 int loop_count = 100000; 67 int status; 68 69 do { 70 status = readl(®s->status); 71 if (!(status & BIT(0))) 72 break; 73 74 udelay(1); 75 } while (--loop_count); 76 if (!loop_count) 77 return -ETIMEDOUT; 78 79 if (status & BIT(1)) { 80 printf("%s() status=0x%08x\n", __func__, status); 81 return -EIO; 82 } 83 84 return 0; 85} 86 87static int scu_ipc_cmd(struct ipc_regs *regs, u32 cmd, u32 sub, 88 u32 *in, int inlen, u32 *out, int outlen) 89{ 90 int i, err; 91 92 for (i = 0; i < inlen; i++) 93 writel(*in++, ®s->wbuf[i]); 94 95 scu_ipc_send_command(regs, (inlen << 16) | (sub << 12) | cmd); 96 err = scu_ipc_check_status(regs); 97 98 if (!err) { 99 for (i = 0; i < outlen; i++) 100 *out++ = readl(®s->rbuf[i]); 101 } 102 103 return err; 104} 105 106/** 107 * scu_ipc_raw_command() - IPC command with data and pointers 108 * @cmd: IPC command code 109 * @sub: IPC command sub type 110 * @in: input data of this IPC command 111 * @inlen: input data length in dwords 112 * @out: output data of this IPC command 113 * @outlen: output data length in dwords 114 * @dptr: data writing to SPTR register 115 * @sptr: data writing to DPTR register 116 * 117 * Send an IPC command to SCU with input/output data and source/dest pointers. 118 * 119 * Return: an IPC error code or 0 on success. 120 */ 121int scu_ipc_raw_command(u32 cmd, u32 sub, u32 *in, int inlen, u32 *out, 122 int outlen, u32 dptr, u32 sptr) 123{ 124 int inbuflen = DIV_ROUND_UP(inlen, 4); 125 struct udevice *dev; 126 struct scu *scu; 127 int ret; 128 129 ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); 130 if (ret) 131 return ret; 132 133 scu = dev_get_priv(dev); 134 135 /* Up to 16 bytes */ 136 if (inbuflen > 4) 137 return -EINVAL; 138 139 writel(dptr, &scu->regs->dptr); 140 writel(sptr, &scu->regs->sptr); 141 142 /* 143 * SRAM controller doesn't support 8-bit writes, it only 144 * supports 32-bit writes, so we have to copy input data into 145 * the temporary buffer, and SCU FW will use the inlen to 146 * determine the actual input data length in the temporary 147 * buffer. 148 */ 149 150 u32 inbuf[4] = {0}; 151 152 memcpy(inbuf, in, inlen); 153 154 return scu_ipc_cmd(scu->regs, cmd, sub, inbuf, inlen, out, outlen); 155} 156 157/** 158 * scu_ipc_simple_command() - send a simple command 159 * @cmd: command 160 * @sub: sub type 161 * 162 * Issue a simple command to the SCU. Do not use this interface if 163 * you must then access data as any data values may be overwritten 164 * by another SCU access by the time this function returns. 165 * 166 * This function may sleep. Locking for SCU accesses is handled for 167 * the caller. 168 */ 169int scu_ipc_simple_command(u32 cmd, u32 sub) 170{ 171 struct scu *scu; 172 struct udevice *dev; 173 int ret; 174 175 ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); 176 if (ret) 177 return ret; 178 179 scu = dev_get_priv(dev); 180 181 scu_ipc_send_command(scu->regs, sub << 12 | cmd); 182 return scu_ipc_check_status(scu->regs); 183} 184 185/** 186 * scu_ipc_command - command with data 187 * @cmd: command 188 * @sub: sub type 189 * @in: input data 190 * @inlen: input length in dwords 191 * @out: output data 192 * @outlen: output length in dwords 193 * 194 * Issue a command to the SCU which involves data transfers. 195 */ 196int scu_ipc_command(u32 cmd, u32 sub, u32 *in, int inlen, u32 *out, int outlen) 197{ 198 struct scu *scu; 199 struct udevice *dev; 200 int ret; 201 202 ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); 203 if (ret) 204 return ret; 205 206 scu = dev_get_priv(dev); 207 208 return scu_ipc_cmd(scu->regs, cmd, sub, in, inlen, out, outlen); 209} 210 211static int scu_ipc_probe(struct udevice *dev) 212{ 213 struct scu *scu = dev_get_priv(dev); 214 215 scu->regs = syscon_get_first_range(X86_SYSCON_SCU); 216 217 return 0; 218} 219 220static const struct udevice_id scu_ipc_match[] = { 221 { .compatible = "intel,scu-ipc", .data = X86_SYSCON_SCU }, 222 { /* sentinel */ } 223}; 224 225U_BOOT_DRIVER(scu_ipc) = { 226 .name = "scu_ipc", 227 .id = UCLASS_SYSCON, 228 .of_match = scu_ipc_match, 229 .probe = scu_ipc_probe, 230 .priv_auto = sizeof(struct scu), 231}; 232