Home | History | Annotate | Download | only in lib
      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, &regs->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(&regs->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++, &regs->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(&regs->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