285 lines
6.0 KiB
C
285 lines
6.0 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (c) 2022, ArtInChip Technology Co., Ltd
|
|
* Dehuang Wu <dehuang.wu@artinchip.com>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <asm/io.h>
|
|
#include <dm.h>
|
|
#include <dm/device.h>
|
|
#include <dm/device_compat.h>
|
|
#include <clk.h>
|
|
#include <reset.h>
|
|
#include <misc.h>
|
|
|
|
#ifndef CONFIG_SPL_BUILD
|
|
|
|
#define SID_MAX_WORDS 64
|
|
#define SID_REG_CTL 0x0
|
|
#define SID_REG_ADDR 0x4
|
|
#define SID_REG_WDATA 0x8
|
|
#define SID_REG_RDATA 0xC
|
|
#define SID_REG_TIMING 0x10
|
|
#define SID_REG_VERSION 0xFC
|
|
|
|
struct aic_sid_platdata {
|
|
void __iomem *base;
|
|
struct clk clk;
|
|
struct reset_ctl reset;
|
|
u32 max_words;
|
|
};
|
|
|
|
#define SID_VERSION_200 0x200
|
|
|
|
#define SID_OPCODE_OFS 16
|
|
#define SID_OPCODE_MSK (0xFFF << SID_OPCODE_OFS)
|
|
#define SID_OPCODE 0xA1C
|
|
|
|
#define SID_STS_OFS 8
|
|
#define SID_STS_MSK (0x1FU << SID_STS_OFS)
|
|
|
|
#define SID_READ_START_OFS 4
|
|
#define SID_READ_START_MSK (0x1 << SID_READ_START_OFS)
|
|
|
|
#define SID_WRITE_START_OFS 0
|
|
#define SID_WRITE_START_MSK (0x1 << SID_WRITE_START_OFS)
|
|
|
|
static int wait_sid_ready(struct aic_sid_platdata *plat)
|
|
{
|
|
u32 val, msk, idle = 0;
|
|
s32 i;
|
|
|
|
val = readl(plat->base + SID_REG_VERSION);
|
|
if (0x200 != val)
|
|
idle = 2;
|
|
|
|
msk = SID_STS_MSK | SID_READ_START_MSK | SID_WRITE_START_MSK;
|
|
for (i = 1000; i > 0; i--) {
|
|
val = readl(plat->base + SID_REG_CTL);
|
|
if ((val & msk) == (idle << SID_STS_OFS))
|
|
return 0;
|
|
}
|
|
|
|
return(-1);
|
|
}
|
|
|
|
static u32 aic_sid_read_word(struct aic_sid_platdata *plat, u32 wid)
|
|
{
|
|
u32 addr, rval = 0, val = 0;
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
addr = (wid + plat->max_words * i) << 2;
|
|
writel(addr, plat->base + SID_REG_ADDR);
|
|
|
|
val = readl(plat->base + SID_REG_CTL);
|
|
val &= ~(SID_OPCODE_MSK | SID_READ_START_MSK);
|
|
val |= ((SID_OPCODE << SID_OPCODE_OFS) | (SID_READ_START_MSK));
|
|
writel(val, plat->base + SID_REG_CTL);
|
|
|
|
/* Wait read finish */
|
|
while ((readl(plat->base + SID_REG_CTL) & SID_READ_START_MSK))
|
|
;
|
|
|
|
rval |= readl(plat->base + SID_REG_RDATA);
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
static int aic_sid_write_word(struct aic_sid_platdata *plat, u32 wid, u32 wval)
|
|
{
|
|
u32 addr, val;
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
addr = (wid + plat->max_words * i) << 2;
|
|
writel(addr, plat->base + SID_REG_ADDR);
|
|
writel(wval, plat->base + SID_REG_WDATA);
|
|
|
|
val = readl(plat->base + SID_REG_CTL);
|
|
val &= ~(SID_OPCODE_MSK);
|
|
val &= ~(SID_WRITE_START_MSK);
|
|
val |= ((SID_OPCODE << SID_OPCODE_OFS) | (SID_WRITE_START_MSK));
|
|
writel(val, plat->base + SID_REG_CTL);
|
|
//udelay(10);
|
|
|
|
/* Wait write done */
|
|
while ((readl(plat->base + SID_REG_CTL) & SID_WRITE_START_MSK))
|
|
;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_sid_read(struct udevice *dev, int offset, void *buf, int size)
|
|
{
|
|
struct aic_sid_platdata *plat = dev_get_plat(dev);
|
|
u32 val, wid, ofs, end, end_siz;
|
|
u8 *p;
|
|
|
|
if (offset < 0 || (offset + size) > (4 * plat->max_words)) {
|
|
pr_err("Invalid offset %d.\n", offset);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (wait_sid_ready(plat)) {
|
|
pr_err("Failed to wait SID status ready.\n");
|
|
return -1;
|
|
}
|
|
|
|
wid = offset >> 2;
|
|
ofs = offset % 4;
|
|
end = (offset + size + 3) >> 2;
|
|
end_siz = (offset + size) % 4;
|
|
p = buf;
|
|
while (wid < end) {
|
|
val = aic_sid_read_word(plat, wid);
|
|
if (wid == (end - 1) && end_siz) {
|
|
memcpy(p, ((u8 *)&val) + ofs, end_siz);
|
|
p += end_siz;
|
|
} else {
|
|
memcpy(p, ((u8 *)&val) + ofs, 4 - ofs);
|
|
p += (4 - ofs);
|
|
}
|
|
wid++;
|
|
ofs = 0;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static int aic_sid_write(struct udevice *dev, int offset, const void *buf,
|
|
int size)
|
|
{
|
|
struct aic_sid_platdata *plat = dev_get_plat(dev);
|
|
u32 val, wid, ofs, end, end_siz, cpsiz;
|
|
const u8 *p;
|
|
|
|
if (offset < 0 || (offset + size) > (4 * plat->max_words)) {
|
|
pr_err("Invalid offset %d.\n", offset);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (wait_sid_ready(plat)) {
|
|
pr_err("Failed to wait SID status ready.\n");
|
|
return -1;
|
|
}
|
|
|
|
val = 0;
|
|
wid = offset >> 2;
|
|
ofs = offset % 4;
|
|
end = (offset + size + 3) >> 2;
|
|
end_siz = (offset + size) % 4;
|
|
p = buf;
|
|
|
|
while (wid < end) {
|
|
if (wid == (end - 1) && end_siz) {
|
|
cpsiz = end_siz;
|
|
if (cpsiz > size)
|
|
cpsiz = size;
|
|
memcpy(((u8 *)&val) + ofs, p, cpsiz);
|
|
p += cpsiz;
|
|
} else {
|
|
cpsiz = 4 - ofs;
|
|
if (cpsiz > size)
|
|
cpsiz = size;
|
|
memcpy(((u8 *)&val) + ofs, p, cpsiz);
|
|
p += (4 - ofs);
|
|
}
|
|
aic_sid_write_word(plat, wid, val);
|
|
ofs = 0;
|
|
wid++;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static inline int aic_sid_set_clock(struct udevice *dev, bool enable)
|
|
{
|
|
int ret = 0;
|
|
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_CLK_ARTINCHIP)
|
|
struct aic_sid_platdata *plat = dev_get_plat(dev);
|
|
|
|
if (!enable) {
|
|
clk_disable(&plat->clk);
|
|
if (reset_valid(&plat->reset))
|
|
reset_assert(&plat->reset);
|
|
return 0;
|
|
}
|
|
|
|
ret = clk_enable(&plat->clk);
|
|
if (ret) {
|
|
dev_err(dev, "failed to enable clock (ret=%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (reset_valid(&plat->reset)) {
|
|
ret = reset_deassert(&plat->reset);
|
|
if (ret) {
|
|
dev_err(dev, "failed to deassert reset\n");
|
|
goto err;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
err:
|
|
clk_disable(&plat->clk);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
static int aic_sid_probe(struct udevice *dev)
|
|
{
|
|
struct aic_sid_platdata *plat = dev_get_plat(dev);
|
|
ofnode node = dev_ofnode(dev);
|
|
int ret = 0;
|
|
u32 timing = 0;
|
|
|
|
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_CLK_ARTINCHIP)
|
|
ret = clk_get_by_index(dev, 0, &plat->clk);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = reset_get_by_index(dev, 0, &plat->reset);
|
|
if (ret && ret != -ENOENT) {
|
|
dev_err(dev, "failed to get reset\n");
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
plat->base = (void *)devfdt_get_addr(dev);
|
|
|
|
ret = aic_sid_set_clock(dev, true);
|
|
|
|
if (!ofnode_read_u32(node, "aic,timing", &timing)) {
|
|
writel(timing, plat->base + SID_REG_TIMING);
|
|
}
|
|
|
|
if (ofnode_read_u32(node, "aic,max-words", &(plat->max_words))) {
|
|
dev_info(dev, "Can't parse max-words value\n");
|
|
plat->max_words = SID_MAX_WORDS;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct misc_ops aic_sid_ops = {
|
|
.read = aic_sid_read,
|
|
.write = aic_sid_write,
|
|
};
|
|
|
|
static const struct udevice_id aic_sid_ids[] = {
|
|
{ .compatible = "artinchip,aic-sid-v1.0" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(artinchip_sid) = {
|
|
.name = "artinchip_aic_sid",
|
|
.id = UCLASS_MISC,
|
|
.ops = &aic_sid_ops,
|
|
.of_match = aic_sid_ids,
|
|
.probe = aic_sid_probe,
|
|
.plat_auto = sizeof(struct aic_sid_platdata),
|
|
};
|
|
#endif
|