uboot/arch/x86/lib/scu.c
<<
>>
Prefs
   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, &regs->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(&regs->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++, &regs->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(&regs->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