-
Notifications
You must be signed in to change notification settings - Fork 210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ISP (work in progress) #319
base: main
Are you sure you want to change the base?
Changes from all commits
9ef073a
336dfb0
0098f4a
d8cecca
caa4021
f81b2c3
a3cb742
e709f9a
ca3ca11
3b549bd
bb2d532
067f61b
bf07bd0
4511899
b5c2fcb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,6 +103,7 @@ OBJECTS := \ | |
i2c.o \ | ||
iodev.o \ | ||
iova.o \ | ||
isp.o \ | ||
kboot.o \ | ||
main.o \ | ||
mcc.o \ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
/* SPDX-License-Identifier: MIT */ | ||
|
||
#include "adt.h" | ||
#include "dart.h" | ||
#include "firmware.h" | ||
#include "pmgr.h" | ||
#include "soc.h" | ||
#include "utils.h" | ||
|
||
#define ISP_ASC_VERSION 0x1800000 | ||
|
||
#define ISP_VER_T8103 0xb0090 | ||
#define ISP_VER_T6000 0xb3091 | ||
#define ISP_VER_T8112 0xc1090 | ||
#define ISP_VER_T6020 0xc3091 | ||
|
||
// PMGR offset to enable to get the version info to work | ||
#define ISP_PMGR_T8103 0x4018 | ||
#define ISP_PMGR_T6000 0x8 | ||
#define ISP_PMGR_T6020 0x4008 | ||
|
||
/* ISP DART has some quirks we must work around */ | ||
|
||
#define DART_T8020_ENABLED_STREAMS 0xfc | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to do all this DART stuff? Linux should be in charge of DARTs, and this is unlikely to work on M2+ as they use a different DART type. dart,t8110 uses very different registers, I don't think it's going to work with the stream selects for dart,t8020. We've generally gotten away without these DART tunables, just by describing things properly in the DT. Do we know what they do? In particular the TCRs should be managed by Linux, so it shouldn't be necessary to poke them. If we need to apply the tunables in m1n1 due to "fun special stuff" that's fine, but we should try to figure out the minimum viable init sequence. This should also probably be in common DART code, not specific to ISP, since as far as I know it's all part of the DART nodes anyway. There's lots of regs/etc defined in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For normal DARTs, its register range count is 1 or 2, with the optional second range specifying DAPF. Walking ADT, the DART nodes with a reg count > 2:
Conveniently, both the ANE & AVE are derivatives of ISP. I also briefly looked at AVE for this exact reason. All three have the same DMA setup, involving an n-number of "fake" dart domains referenced by its periperal DMA channels. For the ISP, the (actual) dart is index 0, the next 2 are the fake/reference darts, and the last range is DAPF. ANE is n=2, AVE is n=1. These ranges look like real darts, and when poked, the registers somewhat behave like a real one, but they cannot substitute the main dart nor provide a new address space. Instead, certain values must be manually "synced" to the main domain for DMA to work, notably TTBR and TLB invalidation. E.g. without syncing TTBR, the AVE/ISP firmware boots fine (ASC PTE is ok) but will fail when asked to do work (for the ISP, frames are dropped); the ANE specifically cannot DMA to/from L2 without TTBR synced; its internal command DMA channel works fine. Also, both ANE/ISP cannot reuse pages without also calling TLB invalidation on the fake domains beforehand (DART IRQ gets stuck). TTBR we can call once after init (though not quite a tunable because the main TTBR isn't initialized yet), but TLB invalidation must follow the main dart call every time, so that's really not a tunable. Currently isp-iommu.c hacks around this, mainly because I can and without bothering sven (though I'm the only one outside of iommu calling |
||
#define DART_T8020_STREAM_COMMAND 0x20 | ||
#define DART_T8020_STREAM_SELECT 0x34 | ||
#define DART_T8020_TCR_OFF 0x100 | ||
#define DART_T8020_TTBR 0x200 | ||
|
||
#define DART_T8020_TCR_TRANSLATE_ENABLE BIT(7) | ||
#define DART_T8020_STREAM_COMMAND_INVALIDATE BIT(20) | ||
|
||
struct dart_tunables { | ||
u64 offset; | ||
u64 clear; | ||
u64 set; | ||
}; | ||
|
||
static void isp_ctrr_init_t8020(u64 base, const struct dart_tunables *config, u32 length) | ||
{ | ||
/* DART error handler gets stuck w/o these */ | ||
write32(base + DART_T8020_ENABLED_STREAMS, 0x1); | ||
write32(base + 0x2f0, 0x0); | ||
write32(base + DART_T8020_STREAM_SELECT, 0xffffffff); | ||
write32(base + DART_T8020_STREAM_COMMAND, DART_T8020_STREAM_COMMAND_INVALIDATE); | ||
|
||
/* I think these lock CTRR? Configurable __TEXT read-only region? */ | ||
int count = length / sizeof(*config); | ||
for (int i = 0; i < count; i++) { | ||
u64 offset = config->offset & 0xffff; | ||
u32 set = config->set & 0xffffffff; | ||
mask32(base + offset, read32(base + offset), set); | ||
config++; | ||
} | ||
|
||
write32(base + DART_T8020_TCR_OFF, DART_T8020_TCR_TRANSLATE_ENABLE); | ||
write32(base + 0x13c, 0x20000); | ||
} | ||
|
||
static void isp_ctrr_init_t6000(u64 base, const struct dart_tunables *config, u32 length, int index) | ||
{ | ||
write32(base + DART_T8020_ENABLED_STREAMS, 0x1); | ||
write32(base + 0x2f0, 0x0); | ||
mask32(base + DART_T8020_STREAM_SELECT, read32(base + DART_T8020_STREAM_SELECT), 0xffff); | ||
// write32(base + DART_T8020_STREAM_SELECT, 0xffff); // diff from t8020 | ||
write32(base + DART_T8020_STREAM_COMMAND, 0x0); | ||
|
||
int count = length / sizeof(*config); | ||
for (int i = 0; i < count; i++) { | ||
u64 offset = config->offset & 0xffff; | ||
u32 set = config->set & 0xffffffff; | ||
mask32(base + offset, read32(base + offset), set); | ||
config++; | ||
} | ||
|
||
write32(base + DART_T8020_TCR_OFF, DART_T8020_TCR_TRANSLATE_ENABLE); | ||
u32 val = 0x20000; | ||
if (!index) | ||
val |= 0x100; | ||
write32(base + 0x13c, val); | ||
} | ||
|
||
static bool isp_initialized = false; | ||
static u64 heap_phys, heap_iova, heap_size, heap_top; | ||
|
||
int isp_get_heap(u64 *phys, u64 *iova, u64 *size, u64 *top) | ||
{ | ||
if (!isp_initialized) | ||
return -1; | ||
|
||
*phys = heap_phys; | ||
*iova = heap_iova; | ||
*size = heap_size; | ||
*top = heap_top; | ||
return 0; | ||
} | ||
|
||
int isp_init(void) | ||
{ | ||
int err = 0; | ||
|
||
const char *isp_path = "/arm-io/isp"; | ||
const char *dart_path = "/arm-io/dart-isp"; | ||
|
||
int adt_path[8], adt_isp_path[8]; | ||
int isp_node = adt_path_offset_trace(adt, isp_path, adt_isp_path); | ||
int node = adt_path_offset_trace(adt, dart_path, adt_path); | ||
if (node < 0 || isp_node < 0) { | ||
isp_path = "/arm-io/isp0"; | ||
dart_path = "/arm-io/dart-isp0"; | ||
isp_node = adt_path_offset_trace(adt, isp_path, adt_isp_path); | ||
node = adt_path_offset_trace(adt, dart_path, adt_path); | ||
} | ||
if (node < 0) | ||
return 0; | ||
|
||
if (pmgr_adt_power_enable(isp_path) < 0) | ||
return -1; | ||
|
||
u64 isp_base; | ||
u64 pmgr_base; | ||
err = adt_get_reg(adt, adt_isp_path, "reg", 0, &isp_base, NULL); | ||
if (err) | ||
return err; | ||
|
||
err = adt_get_reg(adt, adt_isp_path, "reg", 1, &pmgr_base, NULL); | ||
if (err) | ||
return err; | ||
|
||
u32 pmgr_off; | ||
switch (chip_id) { | ||
case T8103: | ||
case T8112: | ||
pmgr_off = ISP_PMGR_T8103; | ||
break; | ||
case T6000 ... T6002: | ||
pmgr_off = ISP_PMGR_T6000; | ||
break; | ||
case T6020 ... T6022: | ||
pmgr_off = ISP_PMGR_T6020; | ||
break; | ||
default: | ||
printf("isp: Unsupported SoC\n"); | ||
return -1; | ||
} | ||
|
||
err = pmgr_set_mode(pmgr_base + pmgr_off, PMGR_PS_ACTIVE); | ||
if (err) { | ||
printf("isp: Failed to power on\n"); | ||
return err; | ||
} | ||
|
||
u32 ver_rev = read32(isp_base + ISP_ASC_VERSION); | ||
printf("isp: Version 0x%x\n", ver_rev); | ||
|
||
pmgr_set_mode(pmgr_base + pmgr_off, PMGR_PS_PWRGATE); | ||
|
||
/* TODO: confirm versions */ | ||
switch (ver_rev) { | ||
case ISP_VER_T8103: | ||
case ISP_VER_T8112: | ||
switch (os_firmware.version) { | ||
case V12_3 ... V12_4: | ||
heap_top = 0x1800000; | ||
break; | ||
case V13_5: | ||
heap_top = 0x1000000; | ||
break; | ||
default: | ||
printf("isp: unsupported firmware\n"); | ||
return -1; | ||
} | ||
break; | ||
case ISP_VER_T6000: | ||
switch (os_firmware.version) { | ||
case V12_3: | ||
heap_top = 0xe00000; | ||
break; | ||
case V13_5: | ||
heap_top = 0xf00000; | ||
break; | ||
default: | ||
printf("isp: unsupported firmware\n"); | ||
return -1; | ||
} | ||
break; | ||
case ISP_VER_T6020: | ||
switch (os_firmware.version) { | ||
case V13_5: | ||
heap_top = 0xf00000; | ||
break; | ||
default: | ||
printf("isp: unsupported firmware\n"); | ||
return -1; | ||
} | ||
break; | ||
default: | ||
printf("isp: unknown revision 0x%x\n", ver_rev); | ||
return -1; | ||
} | ||
|
||
const struct adt_segment_ranges *seg; | ||
u32 segments_len; | ||
|
||
seg = adt_getprop(adt, isp_node, "segment-ranges", &segments_len); | ||
unsigned int count = segments_len / sizeof(*seg); | ||
|
||
heap_iova = seg[count - 1].iova + seg[count - 1].size; | ||
heap_size = heap_top - heap_iova; | ||
heap_phys = top_of_memory_alloc(heap_size); | ||
|
||
printf("isp: Heap: 0x%lx..0x%lx (0x%lx @ 0x%lx)\n", heap_iova, heap_top, heap_size, heap_phys); | ||
|
||
enum dart_type_t type; | ||
const char *type_s; | ||
if (adt_is_compatible(adt, node, "dart,t8020")) { | ||
type = DART_T8020; | ||
type_s = "t8020"; | ||
} else if (adt_is_compatible(adt, node, "dart,t6000")) { | ||
type = DART_T6000; | ||
type_s = "t6000"; | ||
} else if (adt_is_compatible(adt, node, "dart,t8110")) { | ||
type = DART_T8110; | ||
type_s = "t8110"; | ||
} else { | ||
printf("isp: dart %s is of an unknown type\n", dart_path); | ||
return -1; | ||
} | ||
|
||
int dart_domain_count = 3; // TODO get from dt | ||
for (int index = 0; index < dart_domain_count; index++) { | ||
u64 base; | ||
err = adt_get_reg(adt, adt_path, "reg", index, &base, NULL); | ||
if (err < 0) | ||
goto out; | ||
|
||
u32 length; | ||
char prop[32] = "dart-tunables-instance"; | ||
snprintf(prop, sizeof(prop), "dart-tunables-instance-%u", index); | ||
const struct dart_tunables *config = adt_getprop(adt, node, prop, &length); | ||
if (!config || !length) { | ||
printf("isp: Error getting ADT node %s property %s.\n", isp_path, prop); | ||
err = -1; | ||
goto out; | ||
} | ||
|
||
err = adt_get_reg(adt, adt_path, "reg", index, &base, NULL); | ||
if (err < 0) | ||
goto out; | ||
|
||
switch (type) { | ||
case DART_T8020: | ||
isp_ctrr_init_t8020(base, config, length); | ||
break; | ||
case DART_T6000: | ||
isp_ctrr_init_t6000(base, config, length, index); | ||
break; | ||
case DART_T8110: | ||
printf("isp: warning: dart type %s not tested yet!\n", type_s); | ||
isp_ctrr_init_t8020(base, config, length); | ||
break; | ||
} | ||
} | ||
|
||
isp_initialized = true; | ||
|
||
out: | ||
pmgr_adt_power_disable(isp_path); | ||
return err; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/* SPDX-License-Identifier: MIT */ | ||
|
||
#ifndef ISP_H | ||
#define ISP_H | ||
|
||
#include "types.h" | ||
|
||
int isp_init(void); | ||
int isp_get_heap(u64 *phys, u64 *iova, u64 *size, u64 *top); | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should consider deriving the register range index from ADT. I think it was the
instance
property that held that info? Would that work here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instance
are names of each dapf entry (DAPF, SMMU, TLL, DART). I couldn't find another prop either. But for all of my test cases (t8103 ane/aop/isp; mtp/pmp gets dropped anyway) it's the last range index, which we could get fromreg_len / ((a_cells + s_cells) * 4)
. If this holds up I can revert this & addadt_get_reg_count()
instead.