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/errno.h> 19 #include <linux/io.h> 20 #include <linux/kernel.h> 21 22 /* SCU register map */ 23 struct ipc_regs { 24 u32 cmd; 25 u32 status; 26 u32 sptr; 27 u32 dptr; 28 u32 reserved[28]; 29 u32 wbuf[4]; 30 u32 rbuf[4]; 31 }; 32 33 struct scu { 34 struct ipc_regs *regs; 35 }; 36 37 /** 38 * scu_ipc_send_command() - send command to SCU 39 * @regs: register map of SCU 40 * @cmd: command 41 * 42 * Command Register (Write Only): 43 * A write to this register results in an interrupt to the SCU core processor 44 * Format: 45 * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)| 46 */ 47 static void scu_ipc_send_command(struct ipc_regs *regs, u32 cmd) 48 { 49 writel(cmd, ®s->cmd); 50 } 51 52 /** 53 * scu_ipc_check_status() - check status of last command 54 * @regs: register map of SCU 55 * 56 * Status Register (Read Only): 57 * Driver will read this register to get the ready/busy status of the IPC 58 * block and error status of the IPC command that was just processed by SCU 59 * Format: 60 * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)| 61 */ 62 static int scu_ipc_check_status(struct ipc_regs *regs) 63 { 64 int loop_count = 100000; 65 int status; 66 67 do { 68 status = readl(®s->status); 69 if (!(status & BIT(0))) 70 break; 71 72 udelay(1); 73 } while (--loop_count); 74 if (!loop_count) 75 return -ETIMEDOUT; 76 77 if (status & BIT(1)) { 78 printf("%s() status=0x%08x\n", __func__, status); 79 return -EIO; 80 } 81 82 return 0; 83 } 84 85 static int scu_ipc_cmd(struct ipc_regs *regs, u32 cmd, u32 sub, 86 u32 *in, int inlen, u32 *out, int outlen) 87 { 88 int i, err; 89 90 for (i = 0; i < inlen; i++) 91 writel(*in++, ®s->wbuf[i]); 92 93 scu_ipc_send_command(regs, (inlen << 16) | (sub << 12) | cmd); 94 err = scu_ipc_check_status(regs); 95 96 if (!err) { 97 for (i = 0; i < outlen; i++) 98 *out++ = readl(®s->rbuf[i]); 99 } 100 101 return err; 102 } 103 104 /** 105 * scu_ipc_simple_command() - send a simple command 106 * @cmd: command 107 * @sub: sub type 108 * 109 * Issue a simple command to the SCU. Do not use this interface if 110 * you must then access data as any data values may be overwritten 111 * by another SCU access by the time this function returns. 112 * 113 * This function may sleep. Locking for SCU accesses is handled for 114 * the caller. 115 */ 116 int scu_ipc_simple_command(u32 cmd, u32 sub) 117 { 118 struct scu *scu; 119 struct udevice *dev; 120 int ret; 121 122 ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); 123 if (ret) 124 return ret; 125 126 scu = dev_get_priv(dev); 127 128 scu_ipc_send_command(scu->regs, sub << 12 | cmd); 129 return scu_ipc_check_status(scu->regs); 130 } 131 132 int scu_ipc_command(u32 cmd, u32 sub, u32 *in, int inlen, u32 *out, int outlen) 133 { 134 struct scu *scu; 135 struct udevice *dev; 136 int ret; 137 138 ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); 139 if (ret) 140 return ret; 141 142 scu = dev_get_priv(dev); 143 144 return scu_ipc_cmd(scu->regs, cmd, sub, in, inlen, out, outlen); 145 } 146 147 static int scu_ipc_probe(struct udevice *dev) 148 { 149 struct scu *scu = dev_get_priv(dev); 150 151 scu->regs = syscon_get_first_range(X86_SYSCON_SCU); 152 153 return 0; 154 } 155 156 static const struct udevice_id scu_ipc_match[] = { 157 { .compatible = "intel,scu-ipc", .data = X86_SYSCON_SCU }, 158 { /* sentinel */ } 159 }; 160 161 U_BOOT_DRIVER(scu_ipc) = { 162 .name = "scu_ipc", 163 .id = UCLASS_SYSCON, 164 .of_match = scu_ipc_match, 165 .probe = scu_ipc_probe, 166 .priv_auto_alloc_size = sizeof(struct scu), 167 }; 168