* [PATCH v2 01/25] fifo32: add peek function
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 11:27 ` Mark Cave-Ayland
2024-10-08 1:18 ` [PATCH v2 02/25] tests/unit: add fifo32 tests Octavian Purdila
` (23 subsequent siblings)
24 siblings, 1 reply; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add fifo32_peek() that returns the first element from the queue
without popping it.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/qemu/fifo32.h | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/include/qemu/fifo32.h b/include/qemu/fifo32.h
index 4e9fd1b5ef..9de1807375 100644
--- a/include/qemu/fifo32.h
+++ b/include/qemu/fifo32.h
@@ -140,6 +140,34 @@ static inline uint32_t fifo32_pop(Fifo32 *fifo)
return ret;
}
+/**
+ * fifo32_peek:
+ * @fifo: fifo to peek at
+ *
+ * Returns the value from the FIFO's head without poping it. Behaviour
+ * is undefined if the FIFO is empty. Clients are responsible for
+ * checking for emptiness using fifo32_is_empty().
+ *
+ * Returns: the value from the FIFO's head
+ */
+
+static inline uint32_t fifo32_peek(Fifo32 *fifo)
+{
+ uint32_t ret = 0, num;
+ const uint8_t *buf;
+
+ buf = fifo8_peek_bufptr(&fifo->fifo, 4, &num);
+ if (num != 4) {
+ return ret;
+ }
+
+ for (int i = 0; i < sizeof(uint32_t); i++) {
+ ret |= buf[i] << (i * 8);
+ }
+
+ return ret;
+}
+
/**
* There is no fifo32_pop_buf() because the data is not stored in the buffer
* as a set of native-order words.
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* Re: [PATCH v2 01/25] fifo32: add peek function
2024-10-08 1:18 ` [PATCH v2 01/25] fifo32: add peek function Octavian Purdila
@ 2024-10-08 11:27 ` Mark Cave-Ayland
2024-10-08 17:25 ` Octavian Purdila
0 siblings, 1 reply; 29+ messages in thread
From: Mark Cave-Ayland @ 2024-10-08 11:27 UTC (permalink / raw)
To: Octavian Purdila, qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
On 08/10/2024 02:18, Octavian Purdila wrote:
> Add fifo32_peek() that returns the first element from the queue
> without popping it.
>
> Signed-off-by: Octavian Purdila <tavip@google.com>
> ---
> include/qemu/fifo32.h | 28 ++++++++++++++++++++++++++++
> 1 file changed, 28 insertions(+)
>
> diff --git a/include/qemu/fifo32.h b/include/qemu/fifo32.h
> index 4e9fd1b5ef..9de1807375 100644
> --- a/include/qemu/fifo32.h
> +++ b/include/qemu/fifo32.h
> @@ -140,6 +140,34 @@ static inline uint32_t fifo32_pop(Fifo32 *fifo)
> return ret;
> }
>
> +/**
> + * fifo32_peek:
> + * @fifo: fifo to peek at
> + *
> + * Returns the value from the FIFO's head without poping it. Behaviour
> + * is undefined if the FIFO is empty. Clients are responsible for
> + * checking for emptiness using fifo32_is_empty().
> + *
> + * Returns: the value from the FIFO's head
> + */
> +
> +static inline uint32_t fifo32_peek(Fifo32 *fifo)
> +{
> + uint32_t ret = 0, num;
> + const uint8_t *buf;
> +
> + buf = fifo8_peek_bufptr(&fifo->fifo, 4, &num);
Are you sure that you want to use fifo8_peek_bufptr() as opposed to fifo8_peek_buf()
here? The reason for using the latter function (and why fifo8_*_bufptr() functions
are not generally recommended) is that they will correctly handle the FIFO wraparound
caused by the drifting head pointer which can occur if you don't empty the entire
FIFO contents in a single *_pop() or *_pop_buf() operation.
> + if (num != 4) {
> + return ret;
> + }
> +
> + for (int i = 0; i < sizeof(uint32_t); i++) {
> + ret |= buf[i] << (i * 8);
> + }
> +
> + return ret;
> +}
> +
> /**
> * There is no fifo32_pop_buf() because the data is not stored in the buffer
> * as a set of native-order words.
ATB,
Mark.
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 01/25] fifo32: add peek function
2024-10-08 11:27 ` Mark Cave-Ayland
@ 2024-10-08 17:25 ` Octavian Purdila
2024-10-09 8:41 ` Mark Cave-Ayland
0 siblings, 1 reply; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 17:25 UTC (permalink / raw)
To: Mark Cave-Ayland
Cc: qemu-devel, qemu-arm, stefanst, pbonzini, peter.maydell,
marcandre.lureau, berrange, eduardo, luc, damien.hedde, alistair,
thuth, philmd, jsnow, crosa, lvivier
On Tue, Oct 8, 2024 at 4:27 AM Mark Cave-Ayland
<mark.cave-ayland@ilande.co.uk> wrote:
>
> On 08/10/2024 02:18, Octavian Purdila wrote:
>
> > Add fifo32_peek() that returns the first element from the queue
> > without popping it.
> >
> > Signed-off-by: Octavian Purdila <tavip@google.com>
> > ---
> > include/qemu/fifo32.h | 28 ++++++++++++++++++++++++++++
> > 1 file changed, 28 insertions(+)
> >
> > diff --git a/include/qemu/fifo32.h b/include/qemu/fifo32.h
> > index 4e9fd1b5ef..9de1807375 100644
> > --- a/include/qemu/fifo32.h
> > +++ b/include/qemu/fifo32.h
> > @@ -140,6 +140,34 @@ static inline uint32_t fifo32_pop(Fifo32 *fifo)
> > return ret;
> > }
> >
> > +/**
> > + * fifo32_peek:
> > + * @fifo: fifo to peek at
> > + *
> > + * Returns the value from the FIFO's head without poping it. Behaviour
> > + * is undefined if the FIFO is empty. Clients are responsible for
> > + * checking for emptiness using fifo32_is_empty().
> > + *
> > + * Returns: the value from the FIFO's head
> > + */
> > +
> > +static inline uint32_t fifo32_peek(Fifo32 *fifo)
> > +{
> > + uint32_t ret = 0, num;
> > + const uint8_t *buf;
> > +
> > + buf = fifo8_peek_bufptr(&fifo->fifo, 4, &num);
>
> Are you sure that you want to use fifo8_peek_bufptr() as opposed to fifo8_peek_buf()
> here? The reason for using the latter function (and why fifo8_*_bufptr() functions
> are not generally recommended) is that they will correctly handle the FIFO wraparound
> caused by the drifting head pointer which can occur if you don't empty the entire
> FIFO contents in a single *_pop() or *_pop_buf() operation.
>
I don't think that it matters in this case because the size of the
FIFO is always going to be a multiple of 4 and all push and pop
operations happen with 4 bytes as well. Am I missing something?
In any case, if it makes things more clear / consistent I can switch
to fifo8_peek_buf.
> > + if (num != 4) {
> > + return ret;
> > + }
> > +
> > + for (int i = 0; i < sizeof(uint32_t); i++) {
> > + ret |= buf[i] << (i * 8);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > /**
> > * There is no fifo32_pop_buf() because the data is not stored in the buffer
> > * as a set of native-order words.
>
>
> ATB,
>
> Mark.
>
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 01/25] fifo32: add peek function
2024-10-08 17:25 ` Octavian Purdila
@ 2024-10-09 8:41 ` Mark Cave-Ayland
0 siblings, 0 replies; 29+ messages in thread
From: Mark Cave-Ayland @ 2024-10-09 8:41 UTC (permalink / raw)
To: Octavian Purdila
Cc: qemu-devel, qemu-arm, stefanst, pbonzini, peter.maydell,
marcandre.lureau, berrange, eduardo, luc, damien.hedde, alistair,
thuth, philmd, jsnow, crosa, lvivier
On 08/10/2024 18:25, Octavian Purdila wrote:
> On Tue, Oct 8, 2024 at 4:27 AM Mark Cave-Ayland
> <mark.cave-ayland@ilande.co.uk> wrote:
>>
>> On 08/10/2024 02:18, Octavian Purdila wrote:
>>
>>> Add fifo32_peek() that returns the first element from the queue
>>> without popping it.
>>>
>>> Signed-off-by: Octavian Purdila <tavip@google.com>
>>> ---
>>> include/qemu/fifo32.h | 28 ++++++++++++++++++++++++++++
>>> 1 file changed, 28 insertions(+)
>>>
>>> diff --git a/include/qemu/fifo32.h b/include/qemu/fifo32.h
>>> index 4e9fd1b5ef..9de1807375 100644
>>> --- a/include/qemu/fifo32.h
>>> +++ b/include/qemu/fifo32.h
>>> @@ -140,6 +140,34 @@ static inline uint32_t fifo32_pop(Fifo32 *fifo)
>>> return ret;
>>> }
>>>
>>> +/**
>>> + * fifo32_peek:
>>> + * @fifo: fifo to peek at
>>> + *
>>> + * Returns the value from the FIFO's head without poping it. Behaviour
>>> + * is undefined if the FIFO is empty. Clients are responsible for
>>> + * checking for emptiness using fifo32_is_empty().
>>> + *
>>> + * Returns: the value from the FIFO's head
>>> + */
>>> +
>>> +static inline uint32_t fifo32_peek(Fifo32 *fifo)
>>> +{
>>> + uint32_t ret = 0, num;
>>> + const uint8_t *buf;
>>> +
>>> + buf = fifo8_peek_bufptr(&fifo->fifo, 4, &num);
>>
>> Are you sure that you want to use fifo8_peek_bufptr() as opposed to fifo8_peek_buf()
>> here? The reason for using the latter function (and why fifo8_*_bufptr() functions
>> are not generally recommended) is that they will correctly handle the FIFO wraparound
>> caused by the drifting head pointer which can occur if you don't empty the entire
>> FIFO contents in a single *_pop() or *_pop_buf() operation.
>>
>
> I don't think that it matters in this case because the size of the
> FIFO is always going to be a multiple of 4 and all push and pop
> operations happen with 4 bytes as well. Am I missing something?
>
> In any case, if it makes things more clear / consistent I can switch
> to fifo8_peek_buf.
I'm guess I'm just a little bit wary of the Fifo32 API since it appears that
fifo32_num_used(), fifo32_num_free() and fifo32_is_full() are written in a way that
suggests unaligned accesses can occur.
Given that fifo8_push() and fifo8_pop() should assert() upon failure I don't think
that's possible for Fifo32, but then all my test cases use Fifo8.
If you're confident from your tests that this can't happen then we can leave it as-is.
ATB,
Mark.
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v2 02/25] tests/unit: add fifo32 tests
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 01/25] fifo32: add peek function Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 03/25] scripts: add script to generate C header files from SVD XML files Octavian Purdila
` (22 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add push/pop and peek tests for fifo32.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
tests/unit/test-fifo.c | 50 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/tests/unit/test-fifo.c b/tests/unit/test-fifo.c
index 14153c41fa..d0e05ba95c 100644
--- a/tests/unit/test-fifo.c
+++ b/tests/unit/test-fifo.c
@@ -13,6 +13,7 @@
#include "qemu/osdep.h"
#include "migration/vmstate.h"
#include "qemu/fifo8.h"
+#include "qemu/fifo32.h"
const VMStateInfo vmstate_info_uint32;
const VMStateInfo vmstate_info_buffer;
@@ -432,6 +433,53 @@ static void test_fifo8_pushpop(void)
fifo8_destroy(&fifo);
}
+static void test_fifo32_pushpop(void)
+{
+ Fifo32 fifo;
+ uint32_t e;
+
+ fifo32_create(&fifo, 2);
+ fifo32_push(&fifo, 0x11121314);
+ fifo32_push(&fifo, 0x21222324);
+ g_assert(fifo32_num_used(&fifo) == 2);
+
+ e = fifo32_pop(&fifo);
+ g_assert(e == 0x11121314);
+ g_assert(fifo32_num_used(&fifo) == 1);
+
+ e = fifo32_peek(&fifo);
+ g_assert(e == 0x21222324);
+
+ g_assert(fifo32_num_used(&fifo) == 1);
+ fifo32_destroy(&fifo);
+}
+
+static void test_fifo32_peek(void)
+{
+ Fifo32 fifo;
+ uint32_t e;
+
+ fifo32_create(&fifo, 2);
+ fifo32_push(&fifo, 0x11121314);
+ fifo32_push(&fifo, 0x21222324);
+ g_assert(fifo32_num_used(&fifo) == 2);
+
+ e = fifo32_peek(&fifo);
+ g_assert(e == 0x11121314);
+ g_assert(fifo32_num_used(&fifo) == 2);
+
+ e = fifo32_pop(&fifo);
+ g_assert(e == 0x11121314);
+ g_assert(fifo32_num_used(&fifo) == 1);
+
+ e = fifo32_peek(&fifo);
+ g_assert(e == 0x21222324);
+ g_assert(fifo32_num_used(&fifo) == 1);
+
+ fifo32_destroy(&fifo);
+}
+
+
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
@@ -445,5 +493,7 @@ int main(int argc, char *argv[])
g_test_add_func("/fifo8/peek_bufptr_wrap", test_fifo8_peek_bufptr_wrap);
g_test_add_func("/fifo8/pop_bufptr", test_fifo8_pop_bufptr);
g_test_add_func("/fifo8/pop_bufptr_wrap", test_fifo8_pop_bufptr_wrap);
+ g_test_add_func("/fifo32/pushpop", test_fifo32_pushpop);
+ g_test_add_func("/fifo32/peek", test_fifo32_peek);
return g_test_run();
}
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 03/25] scripts: add script to generate C header files from SVD XML files
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 01/25] fifo32: add peek function Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 02/25] tests/unit: add fifo32 tests Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 04/25] Add mcux-soc-svd subproject Octavian Purdila
` (21 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
From: Stefan Stanacar <stefanst@google.com>
The CMSIS System View Description format(CMSIS-SVD) is an XML based
description of Arm Cortex-M microcontrollers provided and maintained
by sillicon vendors. It includes details such as peripherals registers
(down to bitfields), peripheral register block addresses, reset
values, etc.
This script uses this information to create header files that makes it
easier to emulate peripherals.
The script can be used to create either peripheral specific headers or
board / system specific information.
Peripheral specific headers contains information such as register
layout (using the qemu register fields infrastructure), register
names, register write masks and register reset values, the latter
using RegisterAccessInfo.
Here is an excerpt from a generated header:
#pragma once
#include "hw/registerfields.h"
/* Flexcomm */
#define FLEXCOMM_REGS_NO (1024)
/* Peripheral Select and Flexcomm module ID */
REG32(FLEXCOMM_PSELID, 4088);
/* Peripheral Select */
FIELD(FLEXCOMM_PSELID, PERSEL, 0, 3);
/* No peripheral selected. */
#define FLEXCOMM_PSELID_PERSEL_NO_PERIPH_SELECTED 0
/* USART function selected */
#define FLEXCOMM_PSELID_PERSEL_USART 1
/* SPI function selected */
#define FLEXCOMM_PSELID_PERSEL_SPI 2
/* I2C */
#define FLEXCOMM_PSELID_PERSEL_I2C 3
/* I2S Transmit */
#define FLEXCOMM_PSELID_PERSEL_I2S_TRANSMIT 4
/* I2S Receive */
#define FLEXCOMM_PSELID_PERSEL_I2S_RECEIVE 5
...
#define FLEXCOMM_REGISTER_ACCESS_INFO_ARRAY(_name) \
struct RegisterAccessInfo _name[FLEXCOMM_REGS_NO] = { \
[0 ... FLEXCOMM_REGS_NO -1] = { \
.name = "", \
.addr = -1, \
}, \
[0x3FE] = { \
.name = "PSELID", \
.addr = 0xFF8, \
.ro = 0xFFFFFFF0, \
.reset = 0x101000, \
}, \
[0x3FF] = { \
.name = "PID", \
.addr = 0xFFC, \
.ro = 0xFFFFFFFF, \
.reset = 0x0, \
}, \
}
The script has options to control which registers and fields should be
generated.
Board specific headers contains information about peripheral base
register addresses.
Signed-off-by: Stefan Stanacar <stefanst@google.com>
[tavip: pylint fixes, generate layout with qemu register fields
instead of bitfields, generate register names, romask and reset values,
add options to control which register and fields are generated]
Signed-off-by: Octavian Purdila <tavip@google.com>
---
meson.build | 4 +
| 415 ++++++++++++++++++++++++++++++++++++++
2 files changed, 419 insertions(+)
create mode 100755 scripts/svd-gen-header.py
diff --git a/meson.build b/meson.build
index 33954b3eba..5127e67d93 100644
--- a/meson.build
+++ b/meson.build
@@ -3330,6 +3330,10 @@ tracetool_depends = files(
'scripts/tracetool/__init__.py',
)
+svd_gen_header = [
+ python, files('scripts/svd-gen-header.py')
+]
+
qemu_version_cmd = [find_program('scripts/qemu-version.sh'),
meson.current_source_dir(),
get_option('pkgversion'), meson.project_version()]
--git a/scripts/svd-gen-header.py b/scripts/svd-gen-header.py
new file mode 100755
index 0000000000..7befa75421
--- /dev/null
+++ b/scripts/svd-gen-header.py
@@ -0,0 +1,415 @@
+#!/usr/bin/env python3
+
+# Copyright 2024 Google LLC
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or later.
+# See the COPYING file in the top-level directory.
+#
+# Use this script to generate a C header file from an SVD xml
+#
+# Two mode of operations are supported: peripheral and system.
+#
+# When running in peripheral mode a header for a specific peripheral
+# is going to be generated. It will define a type and structure with
+# all of the available registers at the bitfield level. An array that
+# contains the reigster names indexed by address is also going to be
+# generated as well as a function to initialize registers to their
+# reset values.
+#
+# Invocation example:
+#
+# svd_gen_header -i MIMXRT595S_cm33.xml -o flexcomm.h -p FLEXCOMM0 -t FLEXCOMM
+#
+# When running in system mode a header for a specific system /
+# platform will be generated. It will define register base addresses
+# and interrupt numbers for selected peripherals.
+#
+# Invocation example:
+#
+# svd_gen_header -i MIMXRT595S_cm33.xml -o rt500.h -s RT500 -p FLEXCOMM0 \
+# -p CLKCTL0 -p CLKCTL1
+#
+
+import argparse
+import fnmatch
+import re
+import os
+import sys
+import xml.etree.ElementTree
+import pysvd
+
+data_type_by_bits = {
+ 8: "uint8_t",
+ 16: "uint16_t",
+ 32: "uint32_t",
+}
+
+
+def get_register_array_name_and_size(reg):
+ """Return register name and register array size.
+
+ The SVD can define register arrays and pysvd encodes the whole set
+ as as regular register with their name prepended by [<array size>].
+
+ Returns a tuple with the register name and the size of the array or
+ zero if this is not a register set.
+
+ """
+
+ split = re.split(r"[\[\]]", reg.name)
+ return (split[0], int(split[1]) if len(split) > 1 else 0)
+
+
+def generate_comment(indent, text):
+ """Generate a comment block with for the given text with the given
+ indentation level.
+
+ If possible, use a single line /* */ comment block, otherwise use
+ a multiline comment block.
+
+ Newlines are preseved but tabs are not.
+
+ """
+
+ # preserve new lines
+ text = text.replace("\n", " \n ")
+ text = text.replace(" ", " ")
+
+ if len(text) + len("/* */") + len(" " * indent) <= 80 and "\n" not in text:
+ return f"{' '* indent}/* {text} */\n"
+
+ out = " " * indent + "/*\n"
+ line = " " * indent + " *"
+ for word in re.split(r"[ ]", text):
+ if len(line) + len(word) >= 79 or word == "\n":
+ out += line + "\n"
+ line = " " * indent + " *"
+ if word != "\n":
+ line += " " + word
+ else:
+ line += " " + word
+
+ out += line + "\n"
+
+ out += " " * indent + " */\n"
+ return out
+
+
+def get_fields(reg, dictionary):
+ """Return a list of fields from a register indexed dictionary.
+
+ The dictionary keys may contain wildcards.
+
+ """
+
+ for key in dictionary.keys():
+ if fnmatch.fnmatch(reg, key):
+ return dictionary[key]
+ return None
+
+
+def generate_reg(reg, dictionary):
+ """Check if the register should be generated"""
+
+ if get_fields(reg, dictionary):
+ return True
+ return False
+
+
+def skip_reg(reg, dictionary):
+ """Check if the register should be skipped"""
+
+ for key in dictionary.keys():
+ if fnmatch.fnmatch(reg, key) and dictionary[key] is None:
+ return True
+ return False
+
+
+def match_field(reg, field, dictionary):
+ """Match a register and field in a dictionary indexed by registers
+ that contains a list of fields.
+
+ Both the dictionary keys and the list of fields may contain wildcards.
+
+ """
+ fields = get_fields(reg, dictionary)
+ if not fields:
+ return False
+ for f in fields:
+ if fnmatch.fnmatch(field, f):
+ return True
+ return False
+
+
+def generate_field(name, reg_name, field, shared):
+ """Generate register field."""
+
+ out = generate_comment(0, field.description)
+ if shared:
+ out += "SHARED_"
+ out += f"FIELD({name}_{reg_name}, {field.name}, "
+ out += f"{field.bitOffset}, {field.bitWidth});\n"
+ if hasattr(field, "enumeratedValues") and field.bitWidth > 1:
+ for enum in field.enumeratedValues.enumeratedValues:
+ enum_name = f"{name}_{reg_name}_{field.name}_{enum.name}"
+ out += generate_comment(0, enum.description)
+ out += f"#define {enum_name} {enum.value}\n"
+ return out
+
+
+def generate_registers(name, periph, generate, skip):
+ """Generate register offsets and fields
+
+ Use registerfield macros to define register offsets and fields for
+ a given peripheral.
+
+ """
+
+ regs = sorted(periph.registers, key=lambda reg: reg.addressOffset)
+ out = generate_comment(0, periph.description)
+ out += f"#define {name}_REGS_NO ({regs[-1].addressOffset // 4 + 1})\n\n"
+ for reg in regs:
+ reg_name, reg_array_size = get_register_array_name_and_size(reg)
+ if not generate_reg(reg_name, generate):
+ continue
+ if skip_reg(reg_name, skip):
+ continue
+ out += generate_comment(0, reg.description)
+ if reg_array_size > 1:
+ for idx in range(0, reg_array_size):
+ addr = reg.addressOffset + idx * reg.size // 8
+ out += f"REG32({name}_{reg_name}{idx}, 0x{addr:X});\n"
+ else:
+ addr = reg.addressOffset
+ out += f"REG32({name}_{reg_name}, 0x{addr:X});\n"
+ for field in reg.fields:
+ if not match_field(reg_name, field.name, generate):
+ continue
+ if match_field(reg_name, field.name, skip):
+ continue
+ out += generate_field(
+ name, reg_name, field, True if reg_array_size > 1 else False
+ )
+ out += "\n"
+
+ return out
+
+
+def create_wmask(reg):
+ """Generate write mask for a register.
+
+ Generate a mask with all bits that are writable set to 1
+ """
+
+ wmask = 0
+ fields = sorted(reg.fields, key=lambda field: field.bitOffset)
+ if len(fields) > 0:
+ for field in fields:
+ if field.access != pysvd.type.access.read_only:
+ wmask |= ((1 << field.bitWidth) - 1) << field.bitOffset
+ else:
+ if reg.access != pysvd.type.access.read_only:
+ wmask = 0xFFFFFFFF
+ return wmask
+
+
+def create_romask(reg):
+ """Generate write mask for a register.
+
+ Generate a mask with all bits that are readonly set to 1
+ """
+
+ return ~create_wmask(reg) & 0xFFFFFFFF
+
+
+def generate_register_access_info(name, periph):
+ """Generate RegisterAccessInfo array macro"""
+
+ out = f"\n#define {name}_REGISTER_ACCESS_INFO_ARRAY(_name) \\\n"
+ out += f" struct RegisterAccessInfo _name[{name}_REGS_NO] = {{ \\\n"
+ out += f" [0 ... {name}_REGS_NO - 1] = {{ \\\n"
+ out += ' .name = "", \\\n'
+ out += " .addr = -1, \\\n"
+ out += " }, \\\n"
+ for reg in periph.registers:
+ reg_name, reg_array_size = get_register_array_name_and_size(reg)
+ if reg_array_size > 1:
+ for idx in range(0, reg_array_size):
+ addr = reg.addressOffset + idx * reg.size // 8
+ out += f" [0x{addr // 4:X}] = {{ \\\n"
+ out += f' .name = "{reg_name}{idx}", \\\n'
+ out += f" .addr = 0x{addr:X}, \\\n"
+ out += f" .ro = 0x{create_romask(reg):X}, \\\n"
+ out += f" .reset = 0x{reg.resetValue:X}, \\\n"
+ out += " }, \\\n"
+ else:
+ out += f" [0x{reg.addressOffset // 4:X}] = {{ \\\n"
+ out += f' .name = "{reg_name}", \\\n'
+ out += f" .addr = 0x{reg.addressOffset:X}, \\\n"
+ out += f" .ro = 0x{create_romask(reg):X}, \\\n"
+ out += f" .reset = 0x{reg.resetValue:X}, \\\n"
+ out += " }, \\\n"
+ out += " }\n"
+
+ return out
+
+
+def generate_peripheral_header(periph, name, args):
+ """Generate peripheral header
+
+ The following information is generated:
+
+ * typedef with all of the available registers and register fields,
+ position and mask defines for register fields.
+
+ * enum values that encode register fields options.
+
+ * a macro that defines the register names indexed by the relative
+ address of the register.
+
+ * a function that sets the registers to their reset values
+
+ """
+
+ generate = {}
+ for reg in args.fields.split():
+ if reg.find(":") > 0:
+ reg, fields = reg.split(":")
+ generate[reg] = fields.split(",")
+ else:
+ generate[reg] = ["*"]
+
+ skip = {}
+ for reg in args.no_fields.split():
+ if reg.find(":") > 0:
+ reg, fields = reg.split(":")
+ skip[reg] = fields.split(",")
+ else:
+ skip[reg] = None
+
+ out = generate_registers(name, periph, generate, skip)
+
+ out += generate_register_access_info(name, periph)
+
+ return out
+
+
+def get_same_class_peripherals(svd, periph):
+ """Get a list of peripherals that are instances of the same class."""
+
+ return [periph] + [
+ p
+ for p in svd.peripherals
+ if p.derivedFrom and p.derivedFrom.name == periph.name
+ ]
+
+
+def generate_system_header(system, svd, periph):
+ """Generate base and irq defines for given list of peripherals"""
+
+ out = ""
+
+ for p in get_same_class_peripherals(svd, periph):
+ out += f"#define {system}_{p.name}_BASE 0x{p.baseAddress:X}UL\n"
+ out += "\n"
+
+ for p in get_same_class_peripherals(svd, periph):
+ for irq in p.interrupts:
+ out += f"#define {system}_{irq.name}_IRQn 0x{irq.value}UL\n"
+ out += "\n"
+
+ return out
+
+
+def main():
+ """Script to generate C header file from an SVD file"""
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "-i", "--input", type=str, help="Input SVD file", required=True
+ )
+ parser.add_argument(
+ "-o", "--output", type=str, help="Output .h file", required=True
+ )
+ parser.add_argument(
+ "-p",
+ "--peripheral",
+ action="append",
+ help="peripheral name from the SVD file",
+ required=True,
+ )
+ parser.add_argument(
+ "-t",
+ "--type-name",
+ type=str,
+ help="name to be used for peripheral definitions",
+ required=False,
+ )
+ parser.add_argument(
+ "-s",
+ "--system",
+ type=str,
+ help="name to be used for the system definitions",
+ required=False,
+ )
+ parser.add_argument(
+ "--fields",
+ help="list of registers and fields that should be generated "
+ "in the following format 'REG[:FIELDS] ...' "
+ "where FIELDS is a list of comma separated fields that can be "
+ "empty; both regsiters and fields can be matched with wildcards; "
+ "'REG' is an alias for 'REG:*';",
+ required=False,
+ default="*:*",
+ )
+ parser.add_argument(
+ "--no-fields",
+ type=str,
+ help="list of register and fields that should not be generated "
+ "in the following format 'REG[:FIELDS] ...' "
+ "where FIELDS is a list of comma separated fields that can be "
+ "empty; both regsiters and fields can be matched with wildcards; "
+ "note that 'REG' will not generate neither the register nor its "
+ "fields while REG: will generate the register but none of its fields",
+ required=False,
+ default=":",
+ )
+
+ args = parser.parse_args()
+
+ node = xml.etree.ElementTree.parse(args.input).getroot()
+ svd = pysvd.element.Device(node)
+
+ # Write license header
+ header = svd.licenseText.strip()
+ header += f"\n\nAutomatically generated by {os.path.basename(__file__)} "
+ header += f"from {os.path.basename(args.input)}"
+ out = generate_comment(0, header)
+
+ # Write some generic defines
+ out += "#pragma once\n\n"
+
+ for name in args.peripheral:
+ periph = svd.find(name)
+ if periph:
+ if args.system:
+ out += generate_system_header(args.system, svd, periph)
+ else:
+ out += '#include "hw/register.h"\n\n'
+ out += generate_peripheral_header(
+ periph,
+ args.type_name if args.type_name else periph.name,
+ args,
+ )
+ else:
+ print(f"No such peripheral: {name}")
+ return 1
+
+ with open(args.output, "w", encoding="ascii") as output:
+ output.write(out)
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 04/25] Add mcux-soc-svd subproject
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (2 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 03/25] scripts: add script to generate C header files from SVD XML files Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 05/25] hw/misc: add support for flexcomm Octavian Purdila
` (20 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add mcux-soc-svd subproject that contains SVD files that are going to
be used to generate C header with register layout definitions and
other helpers to create device models.
Guard the subproject by a configuration option since it is rarely
going to be used - whenever new headers will be generated. It is
unlikely that already generated headers will be updated, with the
exception of minor hardware revisions.
Also export the rt595 SVD file which is going to be used by subsequent
patches.
TBD: switch to a qemu gitlab fork before merge
Signed-off-by: Octavian Purdila <tavip@google.com>
---
hw/arm/svd/meson.build | 4 ++++
meson_options.txt | 3 +++
scripts/meson-buildoptions.sh | 4 ++++
subprojects/.gitignore | 1 +
subprojects/mcux-soc-svd.wrap | 5 +++++
subprojects/packagefiles/mcux-soc-svd/meson.build | 5 +++++
6 files changed, 22 insertions(+)
create mode 100644 hw/arm/svd/meson.build
create mode 100644 subprojects/mcux-soc-svd.wrap
create mode 100644 subprojects/packagefiles/mcux-soc-svd/meson.build
diff --git a/hw/arm/svd/meson.build b/hw/arm/svd/meson.build
new file mode 100644
index 0000000000..7d83d2ccbc
--- /dev/null
+++ b/hw/arm/svd/meson.build
@@ -0,0 +1,4 @@
+if get_option('mcux-soc-svd')
+ mcux_soc_svd = subproject('mcux-soc-svd')
+ rt595 = mcux_soc_svd.get_variable('rt595')
+endif
diff --git a/meson_options.txt b/meson_options.txt
index 5ee1d95c9c..87a412194c 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -373,3 +373,6 @@ option('hexagon_idef_parser', type : 'boolean', value : true,
option('x86_version', type : 'combo', choices : ['0', '1', '2', '3', '4'], value: '1',
description: 'tweak required x86_64 architecture version beyond compiler default')
+
+option('mcux-soc-svd', type : 'boolean', value : false,
+ description: 'enable targets to generate C headers from mcux-soc-svd')
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index 3bee1c56df..0918eb1ab2 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -41,6 +41,8 @@ meson_options_help() {
printf "%s\n" ' --enable-lto Use link time optimization'
printf "%s\n" ' --enable-malloc=CHOICE choose memory allocator to use [system] (choices:'
printf "%s\n" ' jemalloc/system/tcmalloc)'
+ printf "%s\n" ' --enable-mcux-soc-svd enable targets to generate C headers from mcux-'
+ printf "%s\n" ' soc-svd'
printf "%s\n" ' --enable-module-upgrades try to load modules from alternate paths for'
printf "%s\n" ' upgrades'
printf "%s\n" ' --enable-rng-none dummy RNG, avoid using /dev/(u)random and'
@@ -393,6 +395,8 @@ _meson_option_parse() {
--enable-malloc-trim) printf "%s" -Dmalloc_trim=enabled ;;
--disable-malloc-trim) printf "%s" -Dmalloc_trim=disabled ;;
--mandir=*) quote_sh "-Dmandir=$2" ;;
+ --enable-mcux-soc-svd) printf "%s" -Dmcux-soc-svd=true ;;
+ --disable-mcux-soc-svd) printf "%s" -Dmcux-soc-svd=false ;;
--enable-membarrier) printf "%s" -Dmembarrier=enabled ;;
--disable-membarrier) printf "%s" -Dmembarrier=disabled ;;
--enable-module-upgrades) printf "%s" -Dmodule_upgrades=true ;;
diff --git a/subprojects/.gitignore b/subprojects/.gitignore
index adca0266be..bca8693ef4 100644
--- a/subprojects/.gitignore
+++ b/subprojects/.gitignore
@@ -6,3 +6,4 @@
/keycodemapdb
/libvfio-user
/slirp
+/mcux-soc-svd
diff --git a/subprojects/mcux-soc-svd.wrap b/subprojects/mcux-soc-svd.wrap
new file mode 100644
index 0000000000..80d18e8561
--- /dev/null
+++ b/subprojects/mcux-soc-svd.wrap
@@ -0,0 +1,5 @@
+[wrap-git]
+url = https://github.com/nxp-mcuxpresso/mcux-soc-svd/
+revision = 7f6f9ef7420144fe14cd9bc4d8e0e3523232da04
+patch_directory = mcux-soc-svd
+depth = 1
diff --git a/subprojects/packagefiles/mcux-soc-svd/meson.build b/subprojects/packagefiles/mcux-soc-svd/meson.build
new file mode 100644
index 0000000000..37c537d040
--- /dev/null
+++ b/subprojects/packagefiles/mcux-soc-svd/meson.build
@@ -0,0 +1,5 @@
+project('mcux-soc-svd')
+
+fs = import('fs')
+
+rt595 = fs.copyfile('MIMXRT595S/MIMXRT595S_cm33.xml')
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 05/25] hw/misc: add support for flexcomm
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (3 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 04/25] Add mcux-soc-svd subproject Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 06/25] hw/misc/flexcomm.c: add common fifo functionality Octavian Purdila
` (19 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add support for NXP's FLEXCOMM device model.
FLEXCOMM is a generic serial communication module which support
multiple functions: UART, SPI and I2C. These are configurable at
runtime.
An abstract FlexcomFunction class is created that will be extended for
specific functions. Each function has a dedicated overlapped
MemoryRegion that it is going to be activated when that particular
function is selected.
A generic MemoryRegion is used to map generic registers (PSELID, PID)
that is always active but has a lower priority. The function memory
region is trimed to not include the generic registers.
The FlexcommFunction object also gets access to the (non-concurrently)
shared register backstore.
The patch includes an automatically generated header which contains
the register layout and helpers.
The header can be regenerated with the svd-flexcomm target when the
build is configured with --enable-mcux-soc-svd.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/arm/svd/flexcomm.h | 70 ++++++
include/hw/misc/flexcomm.h | 49 +++++
include/hw/misc/flexcomm_function.h | 47 ++++
hw/misc/flexcomm.c | 319 ++++++++++++++++++++++++++++
hw/arm/meson.build | 2 +
hw/arm/svd/meson.build | 3 +
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 2 +
hw/misc/trace-events | 5 +
9 files changed, 500 insertions(+)
create mode 100644 include/hw/arm/svd/flexcomm.h
create mode 100644 include/hw/misc/flexcomm.h
create mode 100644 include/hw/misc/flexcomm_function.h
create mode 100644 hw/misc/flexcomm.c
diff --git a/include/hw/arm/svd/flexcomm.h b/include/hw/arm/svd/flexcomm.h
new file mode 100644
index 0000000000..564180bd02
--- /dev/null
+++ b/include/hw/arm/svd/flexcomm.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016-2023 NXP SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Automatically generated by svd-gen-header.py from MIMXRT595S_cm33.xml
+ */
+#pragma once
+
+#include "hw/register.h"
+
+/* Flexcomm */
+#define FLEXCOMM_REGS_NO (1024)
+
+/* Peripheral Select and Flexcomm module ID */
+REG32(FLEXCOMM_PSELID, 0xFF8);
+/* Peripheral Select */
+FIELD(FLEXCOMM_PSELID, PERSEL, 0, 3);
+/* No peripheral selected. */
+#define FLEXCOMM_PSELID_PERSEL_NO_PERIPH_SELECTED 0
+/* USART function selected */
+#define FLEXCOMM_PSELID_PERSEL_USART 1
+/* SPI function selected */
+#define FLEXCOMM_PSELID_PERSEL_SPI 2
+/* I2C */
+#define FLEXCOMM_PSELID_PERSEL_I2C 3
+/* I2S Transmit */
+#define FLEXCOMM_PSELID_PERSEL_I2S_TRANSMIT 4
+/* I2S Receive */
+#define FLEXCOMM_PSELID_PERSEL_I2S_RECEIVE 5
+/* Lock the peripheral select */
+FIELD(FLEXCOMM_PSELID, LOCK, 3, 1);
+/* USART present indicator */
+FIELD(FLEXCOMM_PSELID, USARTPRESENT, 4, 1);
+/* SPI present indicator */
+FIELD(FLEXCOMM_PSELID, SPIPRESENT, 5, 1);
+/* I2C present indicator */
+FIELD(FLEXCOMM_PSELID, I2CPRESENT, 6, 1);
+/* I2S Present */
+FIELD(FLEXCOMM_PSELID, I2SPRESENT, 7, 1);
+/* Flexcomm ID */
+FIELD(FLEXCOMM_PSELID, ID, 12, 20);
+
+/* Peripheral Identification */
+REG32(FLEXCOMM_PID, 0xFFC);
+/* Minor revision of module implementation */
+FIELD(FLEXCOMM_PID, Minor_Rev, 8, 4);
+/* Major revision of module implementation */
+FIELD(FLEXCOMM_PID, Major_Rev, 12, 4);
+/* Module identifier for the selected function */
+FIELD(FLEXCOMM_PID, ID, 16, 16);
+
+
+#define FLEXCOMM_REGISTER_ACCESS_INFO_ARRAY(_name) \
+ struct RegisterAccessInfo _name[FLEXCOMM_REGS_NO] = { \
+ [0 ... FLEXCOMM_REGS_NO - 1] = { \
+ .name = "", \
+ .addr = -1, \
+ }, \
+ [0x3FE] = { \
+ .name = "PSELID", \
+ .addr = 0xFF8, \
+ .ro = 0xFFFFFFF0, \
+ .reset = 0x101000, \
+ }, \
+ [0x3FF] = { \
+ .name = "PID", \
+ .addr = 0xFFC, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ }
diff --git a/include/hw/misc/flexcomm.h b/include/hw/misc/flexcomm.h
new file mode 100644
index 0000000000..ea0fe77f26
--- /dev/null
+++ b/include/hw/misc/flexcomm.h
@@ -0,0 +1,49 @@
+/*
+ * QEMU model for NXP's FLEXCOMM
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_FLEXCOMM_H
+#define HW_FLEXCOMM_H
+
+#include "hw/sysbus.h"
+#include "hw/arm/svd/flexcomm.h"
+#include "qemu/fifo32.h"
+
+#define FLEXCOMM_FUNC_USART 0
+#define FLEXCOMM_FUNC_SPI 1
+#define FLEXCOMM_FUNC_I2C 2
+#define FLEXCOMM_FUNC_I2S 3
+#define FLEXCOMM_FUNCTIONS 4
+
+#define FLEXCOMM_FULL 0xF
+#define FLEXCOMM_HSSPI (1 << FLEXCOMM_FUNC_SPI)
+#define FLEXCOMM_PMICI2C (1 << FLEXCOMM_FUNC_I2C)
+
+#define FLEXCOMM_PERSEL_USART 1
+#define FLEXCOMM_PERSEL_SPI 2
+#define FLEXCOMM_PERSEL_I2C 3
+#define FLEXCOMM_PERSEL_I2S_TX 4
+#define FLEXCOMM_PERSEL_I2S_RX 5
+
+#define TYPE_FLEXCOMM "flexcomm"
+OBJECT_DECLARE_SIMPLE_TYPE(FlexcommState, FLEXCOMM);
+
+struct FlexcommState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion container;
+ MemoryRegion mmio;
+ uint32_t regs[FLEXCOMM_REGS_NO];
+ uint32_t functions;
+ qemu_irq irq;
+ bool irq_state;
+};
+
+#endif /* HW_FLEXCOMM_H */
diff --git a/include/hw/misc/flexcomm_function.h b/include/hw/misc/flexcomm_function.h
new file mode 100644
index 0000000000..e498976927
--- /dev/null
+++ b/include/hw/misc/flexcomm_function.h
@@ -0,0 +1,47 @@
+/*
+ * QEMU model for NXP's FLEXCOMM
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_FLEXCOMM_FUNCTION_H
+#define HW_FLEXCOMM_FUNCTION_H
+
+#include "qemu/fifo32.h"
+#include "hw/sysbus.h"
+
+#define TYPE_FLEXCOMM_FUNCTION "flexcomm-function"
+OBJECT_DECLARE_TYPE(FlexcommFunction, FlexcommFunctionClass, FLEXCOMM_FUNCTION);
+
+struct FlexcommFunction {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ uint32_t *regs;
+};
+
+typedef void (*FlexcommFunctionSelect)(FlexcommFunction *f, bool selected);
+
+struct FlexcommFunctionClass {
+ SysBusDeviceClass parent_class;
+
+ const MemoryRegionOps *mmio_ops;
+ const char *name;
+ FlexcommFunctionSelect select;
+};
+
+static inline void flexcomm_select(FlexcommFunction *obj, bool selected)
+{
+ FlexcommFunctionClass *klass = FLEXCOMM_FUNCTION_GET_CLASS(obj);
+
+ klass->select(obj, selected);
+}
+
+void flexcomm_set_irq(FlexcommFunction *f, bool irq);
+
+#endif /* HW_FLEXCOMM_FUNCTION_H */
diff --git a/hw/misc/flexcomm.c b/hw/misc/flexcomm.c
new file mode 100644
index 0000000000..2337fb814c
--- /dev/null
+++ b/hw/misc/flexcomm.c
@@ -0,0 +1,319 @@
+/*
+ * QEMU model for NXP's FLEXCOMM
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/mmap-alloc.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "migration/vmstate.h"
+#include "exec/address-spaces.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "migration/vmstate.h"
+#include "hw/misc/flexcomm.h"
+
+#define REG(s, reg) (s->regs[R_FLEXCOMM_##reg])
+#define RF_WR(s, reg, field, val) \
+ ARRAY_FIELD_DP32(s->regs, FLEXCOMM_##reg, field, val)
+#define RF_RD(s, reg, field) \
+ ARRAY_FIELD_EX32(s->regs, FLEXCOMM_##reg, field)
+
+#define modname "FLEXCOMM"
+
+#define FLEXCOMM_FUNC_MMIO_SIZE \
+ ((FLEXCOMM_REGS_NO - 2) * sizeof(uint32_t))
+
+static const FLEXCOMM_REGISTER_ACCESS_INFO_ARRAY(reg_info);
+
+static inline bool has_function(FlexcommState *s, int function)
+{
+ return s->functions & (1 << function);
+}
+
+static inline int persel_to_function(FlexcommState *s)
+{
+ switch (RF_RD(s, PSELID, PERSEL)) {
+ case FLEXCOMM_PERSEL_USART:
+ return FLEXCOMM_FUNC_USART;
+ case FLEXCOMM_PERSEL_SPI:
+ return FLEXCOMM_FUNC_SPI;
+ case FLEXCOMM_PERSEL_I2C:
+ return FLEXCOMM_FUNC_I2C;
+ case FLEXCOMM_PERSEL_I2S_TX:
+ case FLEXCOMM_PERSEL_I2S_RX:
+ return FLEXCOMM_FUNC_I2S;
+ default:
+ return -1;
+ }
+}
+
+static void flexcomm_func_select(FlexcommState *s, bool selected)
+{
+ FlexcommFunction *obj[] = {
+ FLEXCOMM_FUNCTION(&s->usart),
+ FLEXCOMM_FUNCTION(&s->spi),
+ FLEXCOMM_FUNCTION(&s->i2c),
+ };
+ int f = persel_to_function(s);
+
+ if (f >= 0 && f < ARRAY_SIZE(obj)) {
+ flexcomm_select(obj[f], selected);
+ }
+}
+
+static void flexcomm_reset_enter(Object *o, ResetType type)
+{
+ FlexcommState *s = FLEXCOMM(o);
+
+ trace_flexcomm_reset();
+
+ flexcomm_func_select(s, false);
+
+ for (int i = 0; i < FLEXCOMM_REGS_NO; i++) {
+ hwaddr addr = reg_info[i].addr;
+
+ if (addr != -1) {
+ struct RegisterInfo ri = {
+ .data = &s->regs[addr / 4],
+ .data_size = 4,
+ .access = ®_info[i],
+ };
+
+ register_reset(&ri);
+ }
+ }
+
+ RF_WR(s, PSELID, USARTPRESENT, has_function(s, FLEXCOMM_FUNC_USART));
+ RF_WR(s, PSELID, SPIPRESENT, has_function(s, FLEXCOMM_FUNC_SPI));
+ RF_WR(s, PSELID, I2CPRESENT, has_function(s, FLEXCOMM_FUNC_I2C));
+ RF_WR(s, PSELID, I2SPRESENT, has_function(s, FLEXCOMM_FUNC_I2S));
+
+ s->irq_state = false;
+}
+
+static void flexcomm_reset_exit(Object *o, ResetType type)
+{
+ FlexcommState *s = FLEXCOMM(o);
+
+ qemu_set_irq(s->irq, s->irq_state);
+}
+
+
+static MemTxResult flexcomm_reg_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ FlexcommState *s = opaque;
+ MemTxResult ret = MEMTX_OK;
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+
+ switch (addr) {
+ case A_FLEXCOMM_PSELID:
+ case A_FLEXCOMM_PID:
+ {
+ *data = s->regs[addr / 4];
+ break;
+ }
+ default:
+ return MEMTX_ERROR;
+ }
+
+ trace_flexcomm_reg_read(DEVICE(s)->id, rai->name, addr, *data);
+ return ret;
+}
+
+static int flexcomm_check_function(FlexcommState *s)
+{
+ int f = persel_to_function(s);
+
+ if (f < 0 || !has_function(s, f)) {
+ return -1;
+ }
+
+ return f;
+}
+
+static MemTxResult flexcomm_reg_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ FlexcommState *s = opaque;
+ MemTxResult ret = MEMTX_OK;
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+ struct RegisterInfo ri = {
+ .data = &s->regs[addr / 4],
+ .data_size = 4,
+ .access = rai,
+ };
+
+ trace_flexcomm_reg_write(DEVICE(s)->id, rai->name, addr, value);
+
+ switch (addr) {
+ case A_FLEXCOMM_PID:
+ /* RO register, nothing do to */
+ break;
+ case A_FLEXCOMM_PSELID:
+ {
+ if (RF_RD(s, PSELID, LOCK)) {
+ break;
+ }
+
+ flexcomm_func_select(s, false);
+
+ register_write(&ri, value, ~0, modname, false);
+
+ if (flexcomm_check_function(s) < 0) {
+ RF_WR(s, PSELID, PERSEL, 0);
+ break;
+ }
+
+ flexcomm_func_select(s, true);
+ break;
+ }
+ default:
+ return MEMTX_ERROR;
+ }
+
+ return ret;
+}
+
+
+static const MemoryRegionOps flexcomm_ops = {
+ .read_with_attrs = flexcomm_reg_read,
+ .write_with_attrs = flexcomm_reg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static Property flexcomm_properties[] = {
+ DEFINE_PROP_UINT32("functions", FlexcommState, functions,
+ FLEXCOMM_FULL),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void flexcomm_init(Object *obj)
+{
+ FlexcommState *s = FLEXCOMM(obj);
+ DeviceState *dev = DEVICE(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init(&s->container, obj, "container", sizeof(s->regs));
+ memory_region_init_io(&s->mmio, obj, &flexcomm_ops, s,
+ TYPE_FLEXCOMM, sizeof(s->regs));
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->container);
+ sysbus_init_irq(sbd, &s->irq);
+}
+
+static void flexcomm_func_realize_and_unref(FlexcommFunction *f, Error **errp)
+{
+ FlexcommState *s = FLEXCOMM(OBJECT(f)->parent);
+ FlexcommFunctionClass *fc = FLEXCOMM_FUNCTION_GET_CLASS(f);
+
+ f->regs = s->regs;
+ memory_region_add_subregion_overlap(&s->container, 0, &f->mmio, 0);
+ DEVICE(f)->id = g_strdup_printf("%s-%s", DEVICE(s)->id, fc->name);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(f), errp);
+ memory_region_set_enabled(&f->mmio, false);
+}
+
+static void flexcomm_realize(DeviceState *dev, Error **errp)
+{
+ FlexcommState *s = FLEXCOMM(dev);
+
+ memory_region_add_subregion_overlap(&s->container, 0, &s->mmio, -1);
+}
+
+static const VMStateDescription vmstate_flexcomm = {
+ .name = "flexcomm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, FlexcommState, FLEXCOMM_REGS_NO),
+ VMSTATE_BOOL(irq_state, FlexcommState),
+ VMSTATE_FIFO32(rx_fifo, FlexcommState),
+ VMSTATE_FIFO32(tx_fifo, FlexcommState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void flexcomm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.enter = flexcomm_reset_enter;
+ rc->phases.exit = flexcomm_reset_exit;
+ device_class_set_props(dc, flexcomm_properties);
+ dc->realize = flexcomm_realize;
+ dc->vmsd = &vmstate_flexcomm;
+}
+
+void flexcomm_set_irq(FlexcommFunction *f, bool irq)
+{
+ FlexcommState *s = FLEXCOMM(OBJECT(f)->parent);
+
+ if (s->irq_state != irq) {
+ trace_flexcomm_irq(DEVICE(s)->id, irq);
+ qemu_set_irq(s->irq, irq);
+ s->irq_state = irq;
+ }
+}
+
+static void flexcomm_function_select(FlexcommFunction *f, bool selected)
+{
+ FlexcommFunctionClass *fc = FLEXCOMM_FUNCTION_GET_CLASS(f);
+
+ memory_region_set_enabled(&f->mmio, selected);
+}
+
+static void flexcomm_function_init(Object *obj)
+{
+ FlexcommFunctionClass *fc = FLEXCOMM_FUNCTION_GET_CLASS(obj);
+ FlexcommFunction *f = FLEXCOMM_FUNCTION(obj);
+
+ memory_region_init_io(&f->mmio, obj, fc->mmio_ops, obj, fc->name,
+ FLEXCOMM_FUNC_MMIO_SIZE);
+}
+
+static void flexcomm_function_class_init(ObjectClass *klass, void *data)
+{
+ FlexcommFunctionClass *fc = FLEXCOMM_FUNCTION_CLASS(klass);
+
+ fc->select = flexcomm_function_select;
+}
+
+static const TypeInfo flexcomm_types[] = {
+ {
+ .name = TYPE_FLEXCOMM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(FlexcommState),
+ .instance_init = flexcomm_init,
+ .class_init = flexcomm_class_init,
+ },
+ {
+ .name = TYPE_FLEXCOMM_FUNCTION,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(FlexcommFunction),
+ .abstract = true,
+ .class_size = sizeof(FlexcommFunctionClass),
+ .instance_init = flexcomm_function_init,
+ .class_init = flexcomm_function_class_init,
+ },
+};
+
+DEFINE_TYPES(flexcomm_types);
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 490234b3b8..83e4aea10e 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -73,3 +73,5 @@ system_ss.add(when: 'CONFIG_VERSATILE', if_true: files('versatilepb.c'))
system_ss.add(when: 'CONFIG_VEXPRESS', if_true: files('vexpress.c'))
hw_arch += {'arm': arm_ss}
+
+subdir('svd')
diff --git a/hw/arm/svd/meson.build b/hw/arm/svd/meson.build
index 7d83d2ccbc..4b0bbbbbdc 100644
--- a/hw/arm/svd/meson.build
+++ b/hw/arm/svd/meson.build
@@ -1,4 +1,7 @@
if get_option('mcux-soc-svd')
mcux_soc_svd = subproject('mcux-soc-svd')
rt595 = mcux_soc_svd.get_variable('rt595')
+ run_target('svd-flexcomm', command: svd_gen_header +
+ [ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/flexcomm.h',
+ '-p', 'FLEXCOMM0', '-t', 'FLEXCOMM'])
endif
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1e08785b83..14167ae9e8 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -213,4 +213,7 @@ config IOSB
config XLNX_VERSAL_TRNG
bool
+config FLEXCOMM
+ bool
+
source macio/Kconfig
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index a2951951b5..0d901b9c65 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -151,3 +151,5 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
# HPPA devices
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
+
+system_ss.add(when: 'CONFIG_FLEXCOMM', if_true: files('flexcomm.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 1be0717c0c..419879fd65 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -362,3 +362,8 @@ aspeed_sli_read(uint64_t offset, unsigned int size, uint32_t data) "To 0x%" PRIx
aspeed_sliio_write(uint64_t offset, unsigned int size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32
aspeed_sliio_read(uint64_t offset, unsigned int size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32
+# flexcomm
+flexcomm_reset(void) ""
+flexcomm_irq(const char *id, uint8_t irq) "%s %d"
+flexcomm_reg_read(const char *devname, const char *regname, uint32_t addr, uint32_t val) "%s: %s[0x%04x] -> 0x%08x"
+flexcomm_reg_write(const char *dename, const char *regname, uint32_t addr, uint32_t val) "%s: %s[0x%04x] <- 0x%08x"
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 06/25] hw/misc/flexcomm.c: add common fifo functionality
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (4 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 05/25] hw/misc: add support for flexcomm Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 07/25] hw/char: add support for flexcomm usart Octavian Purdila
` (18 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
FLEXCOMM SPI and USART share FIFO functionality. Add common helper
functions to avoid code duplication.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/misc/flexcomm.h | 2 +
include/hw/misc/flexcomm_function.h | 8 +++
hw/misc/flexcomm.c | 83 +++++++++++++++++++++++++++++
hw/misc/trace-events | 1 +
4 files changed, 94 insertions(+)
diff --git a/include/hw/misc/flexcomm.h b/include/hw/misc/flexcomm.h
index ea0fe77f26..832d4cd29d 100644
--- a/include/hw/misc/flexcomm.h
+++ b/include/hw/misc/flexcomm.h
@@ -44,6 +44,8 @@ struct FlexcommState {
uint32_t functions;
qemu_irq irq;
bool irq_state;
+ Fifo32 rx_fifo;
+ Fifo32 tx_fifo;
};
#endif /* HW_FLEXCOMM_H */
diff --git a/include/hw/misc/flexcomm_function.h b/include/hw/misc/flexcomm_function.h
index e498976927..3ff1677ff6 100644
--- a/include/hw/misc/flexcomm_function.h
+++ b/include/hw/misc/flexcomm_function.h
@@ -23,6 +23,8 @@ struct FlexcommFunction {
MemoryRegion mmio;
uint32_t *regs;
+ Fifo32 *tx_fifo;
+ Fifo32 *rx_fifo;
};
typedef void (*FlexcommFunctionSelect)(FlexcommFunction *f, bool selected);
@@ -32,6 +34,7 @@ struct FlexcommFunctionClass {
const MemoryRegionOps *mmio_ops;
const char *name;
+ bool has_fifos;
FlexcommFunctionSelect select;
};
@@ -43,5 +46,10 @@ static inline void flexcomm_select(FlexcommFunction *obj, bool selected)
}
void flexcomm_set_irq(FlexcommFunction *f, bool irq);
+void flexcomm_update_fifostat(FlexcommFunction *f);
+void flexcomm_clear_fifostat(FlexcommFunction *f, uint64_t value);
+void flexcomm_init_fifos(FlexcommFunction *f, unsigned num);
+void flexcomm_cleanup_fifos(FlexcommFunction *f);
+void flexcomm_reset_fifos(FlexcommFunction *f);
#endif /* HW_FLEXCOMM_FUNCTION_H */
diff --git a/hw/misc/flexcomm.c b/hw/misc/flexcomm.c
index 2337fb814c..a98d8845aa 100644
--- a/hw/misc/flexcomm.c
+++ b/hw/misc/flexcomm.c
@@ -22,6 +22,7 @@
#include "trace.h"
#include "migration/vmstate.h"
#include "hw/misc/flexcomm.h"
+#include "hw/arm/svd/flexcomm_usart.h"
#define REG(s, reg) (s->regs[R_FLEXCOMM_##reg])
#define RF_WR(s, reg, field, val) \
@@ -219,12 +220,22 @@ static void flexcomm_init(Object *obj)
sysbus_init_irq(sbd, &s->irq);
}
+static void flexcomm_finalize(Object *obj)
+{
+ FlexcommState *s = FLEXCOMM(obj);
+
+ /* release resources allocated by the function select (e.g. fifos) */
+ flexcomm_func_select(s, false);
+}
+
static void flexcomm_func_realize_and_unref(FlexcommFunction *f, Error **errp)
{
FlexcommState *s = FLEXCOMM(OBJECT(f)->parent);
FlexcommFunctionClass *fc = FLEXCOMM_FUNCTION_GET_CLASS(f);
f->regs = s->regs;
+ f->tx_fifo = &s->tx_fifo;
+ f->rx_fifo = &s->rx_fifo;
memory_region_add_subregion_overlap(&s->container, 0, &f->mmio, 0);
DEVICE(f)->id = g_strdup_printf("%s-%s", DEVICE(s)->id, fc->name);
sysbus_realize_and_unref(SYS_BUS_DEVICE(f), errp);
@@ -274,11 +285,82 @@ void flexcomm_set_irq(FlexcommFunction *f, bool irq)
}
}
+/* FIFO is shared between USART and SPI, provide common functions here */
+#define FIFO_REG(s, reg) (s->regs[R_FLEXCOMM_USART_FIFO##reg])
+#define FIFO_WR(s, reg, field, val) \
+ ARRAY_FIELD_DP32(s->regs, FLEXCOMM_USART_FIFO##reg, field, val)
+#define FIFO_RD(s, reg, field) \
+ ARRAY_FIELD_EX32(s->regs, FLEXCOMM_USART_FIFO##reg, field)
+
+void flexcomm_update_fifostat(FlexcommFunction *f)
+{
+ int rxlvl = fifo32_num_used(f->rx_fifo);
+ int txlvl = fifo32_num_used(f->tx_fifo);
+
+ FIFO_WR(f, STAT, RXLVL, rxlvl);
+ FIFO_WR(f, STAT, TXLVL, txlvl);
+ FIFO_WR(f, STAT, RXFULL, fifo32_is_full(f->rx_fifo) ? 1 : 0);
+ FIFO_WR(f, STAT, RXNOTEMPTY, !fifo32_is_empty(f->rx_fifo) ? 1 : 0);
+ FIFO_WR(f, STAT, TXNOTFULL, !fifo32_is_full(f->tx_fifo) ? 1 : 0);
+ FIFO_WR(f, STAT, TXEMPTY, fifo32_is_empty(f->tx_fifo) ? 1 : 0);
+
+ if (FIFO_RD(f, TRIG, RXLVLENA) && (rxlvl > FIFO_RD(f, TRIG, RXLVL))) {
+ FIFO_WR(f, INTSTAT, RXLVL, 1);
+ } else {
+ FIFO_WR(f, INTSTAT, RXLVL, 0);
+ }
+
+ if (FIFO_RD(f, TRIG, TXLVLENA) && (txlvl <= FIFO_RD(f, TRIG, TXLVL))) {
+ FIFO_WR(f, INTSTAT, TXLVL, 1);
+ } else {
+ FIFO_WR(f, INTSTAT, TXLVL, 0);
+ }
+
+ trace_flexcomm_fifostat(DEVICE(f)->id, FIFO_REG(f, STAT),
+ FIFO_REG(f, INTSTAT));
+}
+
+void flexcomm_reset_fifos(FlexcommFunction *f)
+{
+ if (FIFO_RD(f, CFG, EMPTYRX)) {
+ FIFO_WR(f, CFG, EMPTYRX, 0);
+ fifo32_reset(f->rx_fifo);
+ }
+ if (FIFO_RD(f, CFG, EMPTYTX)) {
+ FIFO_WR(f, CFG, EMPTYTX, 0);
+ fifo32_reset(f->tx_fifo);
+ }
+}
+
+void flexcomm_clear_fifostat(FlexcommFunction *f, uint64_t value)
+{
+ bool rxerr = FIELD_EX32(value, FLEXCOMM_USART_FIFOSTAT, RXERR);
+ bool txerr = FIELD_EX32(value, FLEXCOMM_USART_FIFOSTAT, TXERR);
+
+ if (rxerr) {
+ FIFO_WR(f, STAT, RXERR, 0);
+ }
+ if (txerr) {
+ FIFO_WR(f, STAT, TXERR, 0);
+ }
+}
+
static void flexcomm_function_select(FlexcommFunction *f, bool selected)
{
FlexcommFunctionClass *fc = FLEXCOMM_FUNCTION_GET_CLASS(f);
memory_region_set_enabled(&f->mmio, selected);
+ if (fc->has_fifos) {
+ if (selected) {
+ unsigned num = FIFO_RD(f, SIZE, FIFOSIZE);
+
+ fifo32_create(f->tx_fifo, num);
+ fifo32_create(f->rx_fifo, num);
+ } else {
+ fifo32_destroy(f->tx_fifo);
+ fifo32_destroy(f->rx_fifo);
+ }
+ }
}
static void flexcomm_function_init(Object *obj)
@@ -303,6 +385,7 @@ static const TypeInfo flexcomm_types[] = {
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(FlexcommState),
.instance_init = flexcomm_init,
+ .instance_finalize = flexcomm_finalize,
.class_init = flexcomm_class_init,
},
{
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 419879fd65..dc245905dc 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -367,3 +367,4 @@ flexcomm_reset(void) ""
flexcomm_irq(const char *id, uint8_t irq) "%s %d"
flexcomm_reg_read(const char *devname, const char *regname, uint32_t addr, uint32_t val) "%s: %s[0x%04x] -> 0x%08x"
flexcomm_reg_write(const char *dename, const char *regname, uint32_t addr, uint32_t val) "%s: %s[0x%04x] <- 0x%08x"
+flexcomm_fifostat(const char *id, uint32_t fifostat, uint32_t fifoinstat) "%s: %08x %08x"
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 07/25] hw/char: add support for flexcomm usart
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (5 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 06/25] hw/misc/flexcomm.c: add common fifo functionality Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 08/25] hw/i2c: add support for flexcomm i2c Octavian Purdila
` (17 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add support for NXP's flexcomm usart. It supports interupts and FIFO
access but no DMA.
The patch includes an automatically generated header which contains
the register layout and helpers.
The header can be regenerated with the svd-flexcomm-usart target when
the build is configured with --enable-mcux-soc-svd.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/arm/svd/flexcomm_usart.h | 294 ++++++++++++++++++++++++++++
include/hw/char/flexcomm_usart.h | 33 ++++
include/hw/misc/flexcomm.h | 2 +
hw/char/flexcomm_usart.c | 288 +++++++++++++++++++++++++++
hw/misc/flexcomm.c | 3 +
hw/arm/svd/meson.build | 4 +
hw/char/meson.build | 1 +
hw/char/trace-events | 8 +
8 files changed, 633 insertions(+)
create mode 100644 include/hw/arm/svd/flexcomm_usart.h
create mode 100644 include/hw/char/flexcomm_usart.h
create mode 100644 hw/char/flexcomm_usart.c
diff --git a/include/hw/arm/svd/flexcomm_usart.h b/include/hw/arm/svd/flexcomm_usart.h
new file mode 100644
index 0000000000..a226917182
--- /dev/null
+++ b/include/hw/arm/svd/flexcomm_usart.h
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2016-2023 NXP SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Automatically generated by svd-gen-header.py from MIMXRT595S_cm33.xml
+ */
+#pragma once
+
+#include "hw/register.h"
+
+/* Flexcomm USART */
+#define FLEXCOMM_USART_REGS_NO (1024)
+
+/* USART Configuration */
+REG32(FLEXCOMM_USART_CFG, 0x0);
+/* USART Enable */
+FIELD(FLEXCOMM_USART_CFG, ENABLE, 0, 1);
+
+/* USART Control */
+REG32(FLEXCOMM_USART_CTL, 0x4);
+
+/* USART Status */
+REG32(FLEXCOMM_USART_STAT, 0x8);
+
+/* Interrupt Enable Read and Set for USART (not FIFO) Status */
+REG32(FLEXCOMM_USART_INTENSET, 0xC);
+
+/* Interrupt Enable Clear */
+REG32(FLEXCOMM_USART_INTENCLR, 0x10);
+
+/* Baud Rate Generator */
+REG32(FLEXCOMM_USART_BRG, 0x20);
+
+/* Interrupt Status */
+REG32(FLEXCOMM_USART_INTSTAT, 0x24);
+
+/* Oversample Selection Register for Asynchronous Communication */
+REG32(FLEXCOMM_USART_OSR, 0x28);
+
+/* Address Register for Automatic Address Matching */
+REG32(FLEXCOMM_USART_ADDR, 0x2C);
+
+/* FIFO Configuration */
+REG32(FLEXCOMM_USART_FIFOCFG, 0xE00);
+/* Enable the Transmit FIFO. */
+FIELD(FLEXCOMM_USART_FIFOCFG, ENABLETX, 0, 1);
+/* Enable the Receive FIFO */
+FIELD(FLEXCOMM_USART_FIFOCFG, ENABLERX, 1, 1);
+/* Empty Command for the Transmit FIFO */
+FIELD(FLEXCOMM_USART_FIFOCFG, EMPTYTX, 16, 1);
+/* Empty Command for the Receive FIFO */
+FIELD(FLEXCOMM_USART_FIFOCFG, EMPTYRX, 17, 1);
+
+/* FIFO Status */
+REG32(FLEXCOMM_USART_FIFOSTAT, 0xE04);
+/* TX FIFO Error */
+FIELD(FLEXCOMM_USART_FIFOSTAT, TXERR, 0, 1);
+/* RX FIFO Error */
+FIELD(FLEXCOMM_USART_FIFOSTAT, RXERR, 1, 1);
+/* Peripheral Interrupt */
+FIELD(FLEXCOMM_USART_FIFOSTAT, PERINT, 3, 1);
+/* Transmit FIFO Empty */
+FIELD(FLEXCOMM_USART_FIFOSTAT, TXEMPTY, 4, 1);
+/* Transmit FIFO is Not Full */
+FIELD(FLEXCOMM_USART_FIFOSTAT, TXNOTFULL, 5, 1);
+/* Receive FIFO is Not Empty */
+FIELD(FLEXCOMM_USART_FIFOSTAT, RXNOTEMPTY, 6, 1);
+/* Receive FIFO is Full */
+FIELD(FLEXCOMM_USART_FIFOSTAT, RXFULL, 7, 1);
+/* Transmit FIFO Current Level */
+FIELD(FLEXCOMM_USART_FIFOSTAT, TXLVL, 8, 5);
+/* Receive FIFO Current Level */
+FIELD(FLEXCOMM_USART_FIFOSTAT, RXLVL, 16, 5);
+
+/* FIFO Trigger Settings for Interrupt and DMA Request */
+REG32(FLEXCOMM_USART_FIFOTRIG, 0xE08);
+/* Transmit FIFO Level Trigger Enable. */
+FIELD(FLEXCOMM_USART_FIFOTRIG, TXLVLENA, 0, 1);
+/* Receive FIFO Level Trigger Enable */
+FIELD(FLEXCOMM_USART_FIFOTRIG, RXLVLENA, 1, 1);
+/* Transmit FIFO Level Trigger Point */
+FIELD(FLEXCOMM_USART_FIFOTRIG, TXLVL, 8, 4);
+/* Trigger when the TX FIFO becomes empty */
+#define FLEXCOMM_USART_FIFOTRIG_TXLVL_TXLVL0 0
+/* Trigger when the TX FIFO level decreases to 1 entry */
+#define FLEXCOMM_USART_FIFOTRIG_TXLVL_TXLVL1 1
+/* Trigger when the TX FIFO level decreases to 15 entries (is no longer full) */
+#define FLEXCOMM_USART_FIFOTRIG_TXLVL_TXLVL15 15
+/* Receive FIFO Level Trigger Point */
+FIELD(FLEXCOMM_USART_FIFOTRIG, RXLVL, 16, 4);
+/* Trigger when the RX FIFO has received 1 entry (is no longer empty) */
+#define FLEXCOMM_USART_FIFOTRIG_RXLVL_RXLVL1 0
+/* Trigger when the RX FIFO has received 2 entries */
+#define FLEXCOMM_USART_FIFOTRIG_RXLVL_RXLVL2 1
+/* Trigger when the RX FIFO has received 16 entries (has become full) */
+#define FLEXCOMM_USART_FIFOTRIG_RXLVL_RXLVL15 15
+
+/* FIFO Interrupt Enable */
+REG32(FLEXCOMM_USART_FIFOINTENSET, 0xE10);
+/* Transmit Error Interrupt Enable */
+FIELD(FLEXCOMM_USART_FIFOINTENSET, TXERR, 0, 1);
+/* Receive Error Interrupt Enable */
+FIELD(FLEXCOMM_USART_FIFOINTENSET, RXERR, 1, 1);
+/* Transmit FIFO Level Interrupt Enable */
+FIELD(FLEXCOMM_USART_FIFOINTENSET, TXLVL, 2, 1);
+/* Receive FIFO Level Interrupt Enable */
+FIELD(FLEXCOMM_USART_FIFOINTENSET, RXLVL, 3, 1);
+
+/* FIFO Interrupt Enable Clear */
+REG32(FLEXCOMM_USART_FIFOINTENCLR, 0xE14);
+/* Transmit Error Interrupt Enable */
+FIELD(FLEXCOMM_USART_FIFOINTENCLR, TXERR, 0, 1);
+/* Receive Error Interrupt Enable */
+FIELD(FLEXCOMM_USART_FIFOINTENCLR, RXERR, 1, 1);
+/* Transmit FIFO Level Interrupt Enable */
+FIELD(FLEXCOMM_USART_FIFOINTENCLR, TXLVL, 2, 1);
+/* Receive FIFO Level Interrupt Enable */
+FIELD(FLEXCOMM_USART_FIFOINTENCLR, RXLVL, 3, 1);
+
+/* FIFO Interrupt Status */
+REG32(FLEXCOMM_USART_FIFOINTSTAT, 0xE18);
+/* TX FIFO Error Interrupt Status */
+FIELD(FLEXCOMM_USART_FIFOINTSTAT, TXERR, 0, 1);
+/* RX FIFO Error Interrupt Status */
+FIELD(FLEXCOMM_USART_FIFOINTSTAT, RXERR, 1, 1);
+/* Transmit FIFO Level Interrupt Status */
+FIELD(FLEXCOMM_USART_FIFOINTSTAT, TXLVL, 2, 1);
+/* Receive FIFO Level Interrupt Status */
+FIELD(FLEXCOMM_USART_FIFOINTSTAT, RXLVL, 3, 1);
+/* Peripheral Interrupt Status */
+FIELD(FLEXCOMM_USART_FIFOINTSTAT, PERINT, 4, 1);
+
+/* FIFO Write Data */
+REG32(FLEXCOMM_USART_FIFOWR, 0xE20);
+/* Transmit data to the FIFO */
+FIELD(FLEXCOMM_USART_FIFOWR, TXDATA, 0, 9);
+
+/* FIFO Read Data */
+REG32(FLEXCOMM_USART_FIFORD, 0xE30);
+/* Received Data from the FIFO */
+FIELD(FLEXCOMM_USART_FIFORD, RXDATA, 0, 9);
+/* Framing Error Status Flag */
+FIELD(FLEXCOMM_USART_FIFORD, FRAMERR, 13, 1);
+/* Parity Error Status Flag */
+FIELD(FLEXCOMM_USART_FIFORD, PARITYERR, 14, 1);
+/* Received Noise Flag */
+FIELD(FLEXCOMM_USART_FIFORD, RXNOISE, 15, 1);
+
+/* FIFO Data Read with No FIFO Pop */
+REG32(FLEXCOMM_USART_FIFORDNOPOP, 0xE40);
+/* Received Data from the FIFO */
+FIELD(FLEXCOMM_USART_FIFORDNOPOP, RXDATA, 0, 9);
+/* Framing Error Status Flag */
+FIELD(FLEXCOMM_USART_FIFORDNOPOP, FRAMERR, 13, 1);
+/* Parity Error Status Flag */
+FIELD(FLEXCOMM_USART_FIFORDNOPOP, PARITYERR, 14, 1);
+/* Received Noise Flag */
+FIELD(FLEXCOMM_USART_FIFORDNOPOP, RXNOISE, 15, 1);
+
+/* FIFO Size */
+REG32(FLEXCOMM_USART_FIFOSIZE, 0xE48);
+/* FIFO Size */
+FIELD(FLEXCOMM_USART_FIFOSIZE, FIFOSIZE, 0, 5);
+
+/* Peripheral Identification */
+REG32(FLEXCOMM_USART_ID, 0xFFC);
+
+
+#define FLEXCOMM_USART_REGISTER_ACCESS_INFO_ARRAY(_name) \
+ struct RegisterAccessInfo _name[FLEXCOMM_USART_REGS_NO] = { \
+ [0 ... FLEXCOMM_USART_REGS_NO - 1] = { \
+ .name = "", \
+ .addr = -1, \
+ }, \
+ [0x0] = { \
+ .name = "CFG", \
+ .addr = 0x0, \
+ .ro = 0xFF032402, \
+ .reset = 0x0, \
+ }, \
+ [0x1] = { \
+ .name = "CTL", \
+ .addr = 0x4, \
+ .ro = 0xFFFEFCB9, \
+ .reset = 0x0, \
+ }, \
+ [0x2] = { \
+ .name = "STAT", \
+ .addr = 0x8, \
+ .ro = 0xFFFE07DF, \
+ .reset = 0xA, \
+ }, \
+ [0x3] = { \
+ .name = "INTENSET", \
+ .addr = 0xC, \
+ .ro = 0xFFFE0797, \
+ .reset = 0x0, \
+ }, \
+ [0x4] = { \
+ .name = "INTENCLR", \
+ .addr = 0x10, \
+ .ro = 0xFFFE0797, \
+ .reset = 0x0, \
+ }, \
+ [0x8] = { \
+ .name = "BRG", \
+ .addr = 0x20, \
+ .ro = 0xFFFF0000, \
+ .reset = 0x0, \
+ }, \
+ [0x9] = { \
+ .name = "INTSTAT", \
+ .addr = 0x24, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0xA] = { \
+ .name = "OSR", \
+ .addr = 0x28, \
+ .ro = 0xFFFFFFF0, \
+ .reset = 0xF, \
+ }, \
+ [0xB] = { \
+ .name = "ADDR", \
+ .addr = 0x2C, \
+ .ro = 0xFFFFFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x380] = { \
+ .name = "FIFOCFG", \
+ .addr = 0xE00, \
+ .ro = 0xFFF80FFC, \
+ .reset = 0x0, \
+ }, \
+ [0x381] = { \
+ .name = "FIFOSTAT", \
+ .addr = 0xE04, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x30, \
+ }, \
+ [0x382] = { \
+ .name = "FIFOTRIG", \
+ .addr = 0xE08, \
+ .ro = 0xFFF0F0FC, \
+ .reset = 0x0, \
+ }, \
+ [0x384] = { \
+ .name = "FIFOINTENSET", \
+ .addr = 0xE10, \
+ .ro = 0xFFFFFFF0, \
+ .reset = 0x0, \
+ }, \
+ [0x385] = { \
+ .name = "FIFOINTENCLR", \
+ .addr = 0xE14, \
+ .ro = 0xFFFFFFF0, \
+ .reset = 0x0, \
+ }, \
+ [0x386] = { \
+ .name = "FIFOINTSTAT", \
+ .addr = 0xE18, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x388] = { \
+ .name = "FIFOWR", \
+ .addr = 0xE20, \
+ .ro = 0xFFFFFE00, \
+ .reset = 0x0, \
+ }, \
+ [0x38C] = { \
+ .name = "FIFORD", \
+ .addr = 0xE30, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x390] = { \
+ .name = "FIFORDNOPOP", \
+ .addr = 0xE40, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x392] = { \
+ .name = "FIFOSIZE", \
+ .addr = 0xE48, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x10, \
+ }, \
+ [0x3FF] = { \
+ .name = "ID", \
+ .addr = 0xFFC, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0xE0102100, \
+ }, \
+ }
diff --git a/include/hw/char/flexcomm_usart.h b/include/hw/char/flexcomm_usart.h
new file mode 100644
index 0000000000..e67b15208f
--- /dev/null
+++ b/include/hw/char/flexcomm_usart.h
@@ -0,0 +1,33 @@
+/*
+ * QEMU model for NXP's FLEXCOMM USART
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_FLEXCOMM_USART_H
+#define HW_FLEXCOMM_USART_H
+
+#include "hw/misc/flexcomm_function.h"
+#include "chardev/char-fe.h"
+
+#define TYPE_FLEXCOMM_USART "flexcomm-usart"
+OBJECT_DECLARE_TYPE(FlexcommUsartState, FlexcommUsartClass, FLEXCOMM_USART);
+
+struct FlexcommUsartState {
+ FlexcommFunction parent_obj;
+
+ CharBackend chr;
+};
+
+struct FlexcommUsartClass {
+ FlexcommFunctionClass parent_obj;
+
+ FlexcommFunctionSelect select;
+};
+
+#endif /* HW_FLEXCOMM_USART_H */
diff --git a/include/hw/misc/flexcomm.h b/include/hw/misc/flexcomm.h
index 832d4cd29d..679b7ea64d 100644
--- a/include/hw/misc/flexcomm.h
+++ b/include/hw/misc/flexcomm.h
@@ -15,6 +15,7 @@
#include "hw/sysbus.h"
#include "hw/arm/svd/flexcomm.h"
#include "qemu/fifo32.h"
+#include "hw/char/flexcomm_usart.h"
#define FLEXCOMM_FUNC_USART 0
#define FLEXCOMM_FUNC_SPI 1
@@ -46,6 +47,7 @@ struct FlexcommState {
bool irq_state;
Fifo32 rx_fifo;
Fifo32 tx_fifo;
+ FlexcommUsartState usart;
};
#endif /* HW_FLEXCOMM_H */
diff --git a/hw/char/flexcomm_usart.c b/hw/char/flexcomm_usart.c
new file mode 100644
index 0000000000..53ab5d8379
--- /dev/null
+++ b/hw/char/flexcomm_usart.c
@@ -0,0 +1,288 @@
+/*
+ * QEMU model for NXP's FLEXCOMM USART
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/cutils.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/address-spaces.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/misc/flexcomm.h"
+#include "hw/char/flexcomm_usart.h"
+#include "hw/arm/svd/flexcomm_usart.h"
+
+#define REG(s, reg) (s->regs[R_FLEXCOMM_USART_##reg])
+/* register field write helper macro */
+#define RF_RD(s, reg, field, val) \
+ ARRAY_FIELD_DP32(s->regs, FLEXCOMM_USART_##reg, field, val)
+/* register field read helper macro */
+#define RF_WR(s, reg, field) \
+ ARRAY_FIELD_EX32(s->regs, FLEXCOMM_USART_##reg, field)
+
+static FLEXCOMM_USART_REGISTER_ACCESS_INFO_ARRAY(reg_info);
+
+static void flexcomm_usart_reset(FlexcommFunction *f)
+{
+ for (int i = 0; i < FLEXCOMM_USART_REGS_NO; i++) {
+ hwaddr addr = reg_info[i].addr;
+
+ if (addr != -1) {
+ struct RegisterInfo ri = {
+ .data = &f->regs[addr / 4],
+ .data_size = 4,
+ .access = ®_info[i],
+ };
+
+ register_reset(&ri);
+ }
+ }
+}
+
+static void flexcomm_usart_irq_update(FlexcommFunction *f)
+{
+ bool irq, per_irqs, fifo_irqs, enabled = RF_WR(f, CFG, ENABLE);
+
+ flexcomm_update_fifostat(f);
+ fifo_irqs = REG(f, FIFOINTSTAT) & REG(f, FIFOINTENSET);
+
+ REG(f, INTSTAT) = REG(f, STAT) & REG(f, INTENSET);
+ per_irqs = REG(f, INTSTAT) != 0;
+
+ irq = enabled && (fifo_irqs || per_irqs);
+
+ trace_flexcomm_usart_irq(DEVICE(f)->id, irq, fifo_irqs, per_irqs, enabled);
+ flexcomm_set_irq(f, irq);
+}
+
+static int flexcomm_usart_rx_space(void *opaque)
+{
+ FlexcommUsartState *s = FLEXCOMM_USART(opaque);
+ FlexcommFunction *f = FLEXCOMM_FUNCTION(opaque);
+
+ uint32_t ret = fifo32_num_free(f->rx_fifo);
+
+ if (!RF_WR(f, CFG, ENABLE) || !RF_WR(f, FIFOCFG, ENABLERX)) {
+ ret = 0;
+ }
+
+ trace_flexcomm_usart_rx_space(DEVICE(s)->id, ret);
+
+ return ret;
+}
+
+static void flexcomm_usart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ FlexcommFunction *f = FLEXCOMM_FUNCTION(opaque);
+
+ if (!RF_WR(f, CFG, ENABLE) || !RF_WR(f, FIFOCFG, ENABLERX)) {
+ return;
+ }
+
+ trace_flexcomm_usart_rx(DEVICE(f)->id);
+
+ while (!fifo32_is_full(f->rx_fifo) && size) {
+ fifo32_push(f->rx_fifo, *buf++);
+ size--;
+ }
+
+ flexcomm_usart_irq_update(f);
+}
+
+static MemTxResult flexcomm_usart_reg_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ FlexcommFunction *f = FLEXCOMM_FUNCTION(opaque);
+ FlexcommUsartState *s = FLEXCOMM_USART(opaque);
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+ MemTxResult ret = MEMTX_OK;
+
+ if (size != 4) {
+ ret = MEMTX_ERROR;
+ goto out;
+ }
+
+ switch (addr) {
+ case A_FLEXCOMM_USART_FIFORD:
+ {
+ if (!fifo32_is_empty(f->rx_fifo)) {
+ *data = fifo32_pop(f->rx_fifo);
+ qemu_chr_fe_accept_input(&s->chr);
+ }
+ break;
+ }
+ case A_FLEXCOMM_USART_FIFORDNOPOP:
+ {
+ if (!fifo32_is_empty(f->rx_fifo)) {
+ *data = fifo32_peek(f->rx_fifo);
+ }
+ break;
+ }
+ default:
+ *data = f->regs[addr / 4];
+ break;
+ }
+
+ flexcomm_usart_irq_update(f);
+
+out:
+ trace_flexcomm_usart_reg_read(DEVICE(f)->id, rai->name, addr, *data);
+ return ret;
+}
+
+static MemTxResult flexcomm_usart_reg_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ FlexcommFunction *f = FLEXCOMM_FUNCTION(opaque);
+ FlexcommUsartState *s = FLEXCOMM_USART(opaque);
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+ struct RegisterInfo ri = {
+ .data = &f->regs[addr / 4],
+ .data_size = 4,
+ .access = rai,
+ };
+
+ trace_flexcomm_usart_reg_write(DEVICE(f)->id, rai->name, addr, value);
+
+ switch (addr) {
+ case A_FLEXCOMM_USART_INTENCLR:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ REG(f, INTENSET) &= ~REG(f, INTENCLR);
+ break;
+ }
+ case A_FLEXCOMM_USART_FIFOCFG:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ flexcomm_reset_fifos(f);
+ break;
+ }
+ case A_FLEXCOMM_USART_FIFOSTAT:
+ {
+ flexcomm_clear_fifostat(f, value);
+ break;
+ }
+ case A_FLEXCOMM_USART_FIFOINTENSET:
+ {
+ REG(f, FIFOINTENSET) |= value;
+ break;
+ }
+ case A_FLEXCOMM_USART_FIFOINTENCLR:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ REG(f, FIFOINTENSET) &= ~value;
+ break;
+ }
+ case A_FLEXCOMM_USART_FIFOWR:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+
+ if (!fifo32_is_full(f->tx_fifo)) {
+ fifo32_push(f->tx_fifo, REG(f, FIFOWR));
+ }
+
+ if (!RF_WR(f, CFG, ENABLE) || !RF_WR(f, FIFOCFG, ENABLETX)) {
+ break;
+ }
+
+ while (!fifo32_is_empty(f->tx_fifo)) {
+ uint32_t val32 = fifo32_pop(f->tx_fifo);
+ uint8_t val8 = val32 & 0xff;
+
+ trace_flexcomm_usart_tx(DEVICE(f)->id);
+ qemu_chr_fe_write_all(&s->chr, &val8, sizeof(val8));
+ }
+ break;
+ }
+ case A_FLEXCOMM_USART_CFG:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ break;
+ }
+ default:
+ register_write(&ri, value, ~0, NULL, false);
+ break;
+ }
+
+ flexcomm_usart_irq_update(f);
+
+ return MEMTX_OK;
+}
+
+static void flexcomm_usart_select(FlexcommFunction *f, bool selected)
+{
+ FlexcommUsartState *s = FLEXCOMM_USART(f);
+ FlexcommUsartClass *uc = FLEXCOMM_USART_GET_CLASS(f);
+
+ if (selected) {
+ qemu_chr_fe_set_handlers(&s->chr, flexcomm_usart_rx_space,
+ flexcomm_usart_rx, NULL, NULL,
+ s, NULL, true);
+ flexcomm_usart_reset(f);
+ } else {
+ qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, NULL, NULL, NULL, NULL,
+ false);
+ }
+ uc->select(f, selected);
+}
+
+static const MemoryRegionOps flexcomm_usart_ops = {
+ .read_with_attrs = flexcomm_usart_reg_read,
+ .write_with_attrs = flexcomm_usart_reg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static Property flexcomm_usart_properties[] = {
+ DEFINE_PROP_CHR("chardev", FlexcommUsartState, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void flexcomm_usart_realize(DeviceState *dev, Error **errp)
+{
+ qdev_prop_set_chr(dev, "chardev", qemu_chr_find(dev->id));
+}
+
+static void flexcomm_usart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ FlexcommFunctionClass *fc = FLEXCOMM_FUNCTION_CLASS(klass);
+ FlexcommUsartClass *uc = FLEXCOMM_USART_CLASS(klass);
+
+ device_class_set_props(dc, flexcomm_usart_properties);
+ dc->realize = flexcomm_usart_realize;
+ uc->select = fc->select;
+ fc->select = flexcomm_usart_select;
+ fc->name = "usart";
+ fc->has_fifos = true;
+ fc->mmio_ops = &flexcomm_usart_ops;
+}
+
+static const TypeInfo flexcomm_usart_types[] = {
+ {
+ .name = TYPE_FLEXCOMM_USART,
+ .parent = TYPE_FLEXCOMM_FUNCTION,
+ .instance_size = sizeof(FlexcommUsartState),
+ .class_init = flexcomm_usart_class_init,
+ .class_size = sizeof(FlexcommUsartClass),
+ },
+};
+
+DEFINE_TYPES(flexcomm_usart_types);
diff --git a/hw/misc/flexcomm.c b/hw/misc/flexcomm.c
index a98d8845aa..a291148f27 100644
--- a/hw/misc/flexcomm.c
+++ b/hw/misc/flexcomm.c
@@ -23,6 +23,7 @@
#include "migration/vmstate.h"
#include "hw/misc/flexcomm.h"
#include "hw/arm/svd/flexcomm_usart.h"
+#include "hw/char/flexcomm_usart.h"
#define REG(s, reg) (s->regs[R_FLEXCOMM_##reg])
#define RF_WR(s, reg, field, val) \
@@ -218,6 +219,7 @@ static void flexcomm_init(Object *obj)
TYPE_FLEXCOMM, sizeof(s->regs));
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->container);
sysbus_init_irq(sbd, &s->irq);
+ object_initialize_child(obj, "usart", &s->usart, TYPE_FLEXCOMM_USART);
}
static void flexcomm_finalize(Object *obj)
@@ -247,6 +249,7 @@ static void flexcomm_realize(DeviceState *dev, Error **errp)
FlexcommState *s = FLEXCOMM(dev);
memory_region_add_subregion_overlap(&s->container, 0, &s->mmio, -1);
+ flexcomm_func_realize_and_unref(FLEXCOMM_FUNCTION(&s->usart), errp);
}
static const VMStateDescription vmstate_flexcomm = {
diff --git a/hw/arm/svd/meson.build b/hw/arm/svd/meson.build
index 4b0bbbbbdc..3bff90bcbd 100644
--- a/hw/arm/svd/meson.build
+++ b/hw/arm/svd/meson.build
@@ -4,4 +4,8 @@ if get_option('mcux-soc-svd')
run_target('svd-flexcomm', command: svd_gen_header +
[ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/flexcomm.h',
'-p', 'FLEXCOMM0', '-t', 'FLEXCOMM'])
+ run_target('svd-flexcomm-usart', command: svd_gen_header +
+ [ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/flexcomm_usart.h',
+ '-p', 'USART0', '-t', 'FLEXCOMM_USART',
+ '--fields', 'CFG:ENABLE FIFOCFG:ENABLE*,EMPTY* FIFO*:* *:'])
endif
diff --git a/hw/char/meson.build b/hw/char/meson.build
index 1750834385..5c6aaf8309 100644
--- a/hw/char/meson.build
+++ b/hw/char/meson.build
@@ -39,3 +39,4 @@ system_ss.add(when: 'CONFIG_GOLDFISH_TTY', if_true: files('goldfish_tty.c'))
specific_ss.add(when: 'CONFIG_TERMINAL3270', if_true: files('terminal3270.c'))
specific_ss.add(when: 'CONFIG_VIRTIO', if_true: files('virtio-serial-bus.c'))
specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_vty.c'))
+system_ss.add(when: 'CONFIG_FLEXCOMM', if_true: files('flexcomm_usart.c'))
diff --git a/hw/char/trace-events b/hw/char/trace-events
index 59e1f734a7..578551b388 100644
--- a/hw/char/trace-events
+++ b/hw/char/trace-events
@@ -125,3 +125,11 @@ xen_console_unrealize(unsigned int idx) "idx %u"
xen_console_realize(unsigned int idx, const char *chrdev) "idx %u chrdev %s"
xen_console_device_create(unsigned int idx) "idx %u"
xen_console_device_destroy(unsigned int idx) "idx %u"
+
+# flexcomm_usart.c
+flexcomm_usart_reg_read(const char *id, const char *reg_name, uint32_t addr, uint32_t val) " %s: %s[0x%04x] -> 0x%08x"
+flexcomm_usart_reg_write(const char *id, const char *reg_name, uint32_t addr, uint32_t val) "%s: %s[0x%04x] <- 0x%08x"
+flexcomm_usart_rx_space(const char *id, uint32_t rx) "%s: %d"
+flexcomm_usart_rx(const char *id) "%s"
+flexcomm_usart_tx(const char *id) "%s"
+flexcomm_usart_irq(const char *id, bool irq, bool fifoirqs, bool perirqs, bool enabled) "%s: %d %d %d %d"
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 08/25] hw/i2c: add support for flexcomm i2c
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (6 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 07/25] hw/char: add support for flexcomm usart Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 09/25] hw/ssi: add support for flexcomm spi Octavian Purdila
` (16 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add support for NXP's flexcomm i2c. It does not support slave mode or
DMA.
The patch includes an automatically generated header which contains
the register layout and helpers.
The header can be regenerated with the svd-flexcomm-i2c target when
the build is configured with --enable-mcux-soc-svd.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/arm/svd/flexcomm_i2c.h | 229 +++++++++++++++++++++++++++
include/hw/i2c/flexcomm_i2c.h | 40 +++++
include/hw/misc/flexcomm.h | 2 +
hw/i2c/flexcomm_i2c.c | 250 ++++++++++++++++++++++++++++++
hw/misc/flexcomm.c | 3 +
hw/arm/svd/meson.build | 4 +
hw/i2c/meson.build | 1 +
hw/i2c/trace-events | 10 ++
hw/misc/Kconfig | 1 +
9 files changed, 540 insertions(+)
create mode 100644 include/hw/arm/svd/flexcomm_i2c.h
create mode 100644 include/hw/i2c/flexcomm_i2c.h
create mode 100644 hw/i2c/flexcomm_i2c.c
diff --git a/include/hw/arm/svd/flexcomm_i2c.h b/include/hw/arm/svd/flexcomm_i2c.h
new file mode 100644
index 0000000000..bd1dec16f3
--- /dev/null
+++ b/include/hw/arm/svd/flexcomm_i2c.h
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2016-2023 NXP SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Automatically generated by svd-gen-header.py from MIMXRT595S_cm33.xml
+ */
+#pragma once
+
+#include "hw/register.h"
+
+/* I2C Bus Interface */
+#define FLEXCOMM_I2C_REGS_NO (1024)
+
+/* Configuration Register */
+REG32(FLEXCOMM_I2C_CFG, 0x800);
+/* Master Enable */
+FIELD(FLEXCOMM_I2C_CFG, MSTEN, 0, 1);
+/* Slave Enable */
+FIELD(FLEXCOMM_I2C_CFG, SLVEN, 1, 1);
+/* Monitor Enable */
+FIELD(FLEXCOMM_I2C_CFG, MONEN, 2, 1);
+/* I2C bus Time-out Enable */
+FIELD(FLEXCOMM_I2C_CFG, TIMEOUTEN, 3, 1);
+/* Monitor function Clock Stretching */
+FIELD(FLEXCOMM_I2C_CFG, MONCLKSTR, 4, 1);
+/* High Speed mode Capable enable */
+FIELD(FLEXCOMM_I2C_CFG, HSCAPABLE, 5, 1);
+
+/* Status Register */
+REG32(FLEXCOMM_I2C_STAT, 0x804);
+/* Master Pending */
+FIELD(FLEXCOMM_I2C_STAT, MSTPENDING, 0, 1);
+/* Master State code */
+FIELD(FLEXCOMM_I2C_STAT, MSTSTATE, 1, 3);
+/* Idle. The Master function is available to be used for a new transaction. */
+#define FLEXCOMM_I2C_STAT_MSTSTATE_IDLE 0
+/*
+ * Receive ready. Received data is available (in Master Receiver mode). Address
+ * plus Read was previously sent and Acknowledged by a slave.
+ */
+#define FLEXCOMM_I2C_STAT_MSTSTATE_RECEIVE_READY 1
+/*
+ * Transmit ready. Data can be transmitted (in Master Transmitter mode).
+ * Address plus Write was previously sent and Acknowledged by a slave.
+ */
+#define FLEXCOMM_I2C_STAT_MSTSTATE_TRANSMIT_READY 2
+/* NACK Address. Slave NACKed address. */
+#define FLEXCOMM_I2C_STAT_MSTSTATE_NACK_ADDRESS 3
+/* NACK Data. Slave NACKed transmitted data. */
+#define FLEXCOMM_I2C_STAT_MSTSTATE_NACK_DATA 4
+
+/* Interrupt Enable Set Register */
+REG32(FLEXCOMM_I2C_INTENSET, 0x808);
+/* Master Pending interrupt Enable */
+FIELD(FLEXCOMM_I2C_INTENSET, MSTPENDINGEN, 0, 1);
+
+/* Interrupt Enable Clear Register */
+REG32(FLEXCOMM_I2C_INTENCLR, 0x80C);
+/* Master Pending interrupt clear */
+FIELD(FLEXCOMM_I2C_INTENCLR, MSTPENDINGCLR, 0, 1);
+
+/* Time-out Register */
+REG32(FLEXCOMM_I2C_TIMEOUT, 0x810);
+/* Time-out time value, the bottom 4 bits */
+FIELD(FLEXCOMM_I2C_TIMEOUT, TOMIN, 0, 4);
+
+/* Interrupt Status Register */
+REG32(FLEXCOMM_I2C_INTSTAT, 0x818);
+/* Master Pending */
+FIELD(FLEXCOMM_I2C_INTSTAT, MSTPENDING, 0, 1);
+
+/* Master Control Register */
+REG32(FLEXCOMM_I2C_MSTCTL, 0x820);
+/* Master Continue(write-only) */
+FIELD(FLEXCOMM_I2C_MSTCTL, MSTCONTINUE, 0, 1);
+/* Master Start control(write-only) */
+FIELD(FLEXCOMM_I2C_MSTCTL, MSTSTART, 1, 1);
+/* Master Stop control(write-only) */
+FIELD(FLEXCOMM_I2C_MSTCTL, MSTSTOP, 2, 1);
+/* Master DMA enable */
+FIELD(FLEXCOMM_I2C_MSTCTL, MSTDMA, 3, 1);
+
+/* Master Data Register */
+REG32(FLEXCOMM_I2C_MSTDAT, 0x828);
+/* Master function data register */
+FIELD(FLEXCOMM_I2C_MSTDAT, DATA, 0, 8);
+
+/* Slave Control Register */
+REG32(FLEXCOMM_I2C_SLVCTL, 0x840);
+
+/* Slave Data Register */
+REG32(FLEXCOMM_I2C_SLVDAT, 0x844);
+
+/* Slave Address Register */
+REG32(FLEXCOMM_I2C_SLVADR0, 0x848);
+
+/* Slave Address Register */
+REG32(FLEXCOMM_I2C_SLVADR1, 0x84C);
+
+/* Slave Address Register */
+REG32(FLEXCOMM_I2C_SLVADR2, 0x850);
+
+/* Slave Address Register */
+REG32(FLEXCOMM_I2C_SLVADR3, 0x854);
+
+/* Slave Qualification for Address 0 Register */
+REG32(FLEXCOMM_I2C_SLVQUAL0, 0x858);
+
+
+#define FLEXCOMM_I2C_REGISTER_ACCESS_INFO_ARRAY(_name) \
+ struct RegisterAccessInfo _name[FLEXCOMM_I2C_REGS_NO] = { \
+ [0 ... FLEXCOMM_I2C_REGS_NO - 1] = { \
+ .name = "", \
+ .addr = -1, \
+ }, \
+ [0x200] = { \
+ .name = "CFG", \
+ .addr = 0x800, \
+ .ro = 0xFFFFFFC0, \
+ .reset = 0x0, \
+ }, \
+ [0x201] = { \
+ .name = "STAT", \
+ .addr = 0x804, \
+ .ro = 0xFCF57FAF, \
+ .reset = 0x801, \
+ }, \
+ [0x202] = { \
+ .name = "INTENSET", \
+ .addr = 0x808, \
+ .ro = 0xFCF476AE, \
+ .reset = 0x0, \
+ }, \
+ [0x203] = { \
+ .name = "INTENCLR", \
+ .addr = 0x80C, \
+ .ro = 0xFCF476AE, \
+ .reset = 0x0, \
+ }, \
+ [0x204] = { \
+ .name = "TIMEOUT", \
+ .addr = 0x810, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFFFF, \
+ }, \
+ [0x205] = { \
+ .name = "CLKDIV", \
+ .addr = 0x814, \
+ .ro = 0xFFFF0000, \
+ .reset = 0x0, \
+ }, \
+ [0x206] = { \
+ .name = "INTSTAT", \
+ .addr = 0x818, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x801, \
+ }, \
+ [0x208] = { \
+ .name = "MSTCTL", \
+ .addr = 0x820, \
+ .ro = 0xFFFFFFF0, \
+ .reset = 0x0, \
+ }, \
+ [0x209] = { \
+ .name = "MSTTIME", \
+ .addr = 0x824, \
+ .ro = 0xFFFFFF88, \
+ .reset = 0x77, \
+ }, \
+ [0x20A] = { \
+ .name = "MSTDAT", \
+ .addr = 0x828, \
+ .ro = 0xFFFFFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x210] = { \
+ .name = "SLVCTL", \
+ .addr = 0x840, \
+ .ro = 0xFFFFFCF4, \
+ .reset = 0x0, \
+ }, \
+ [0x211] = { \
+ .name = "SLVDAT", \
+ .addr = 0x844, \
+ .ro = 0xFFFFFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x212] = { \
+ .name = "SLVADR0", \
+ .addr = 0x848, \
+ .ro = 0xFFFF7F00, \
+ .reset = 0x1, \
+ }, \
+ [0x213] = { \
+ .name = "SLVADR1", \
+ .addr = 0x84C, \
+ .ro = 0xFFFF7F00, \
+ .reset = 0x1, \
+ }, \
+ [0x214] = { \
+ .name = "SLVADR2", \
+ .addr = 0x850, \
+ .ro = 0xFFFF7F00, \
+ .reset = 0x1, \
+ }, \
+ [0x215] = { \
+ .name = "SLVADR3", \
+ .addr = 0x854, \
+ .ro = 0xFFFF7F00, \
+ .reset = 0x1, \
+ }, \
+ [0x216] = { \
+ .name = "SLVQUAL0", \
+ .addr = 0x858, \
+ .ro = 0xFFFFFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x220] = { \
+ .name = "MONRXDAT", \
+ .addr = 0x880, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x3FF] = { \
+ .name = "ID", \
+ .addr = 0xFFC, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0xE0301300, \
+ }, \
+ }
diff --git a/include/hw/i2c/flexcomm_i2c.h b/include/hw/i2c/flexcomm_i2c.h
new file mode 100644
index 0000000000..7b27e333d7
--- /dev/null
+++ b/include/hw/i2c/flexcomm_i2c.h
@@ -0,0 +1,40 @@
+/*
+ * QEMU model for NXP's FLEXCOMM I2C
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_FLEXCOMM_I2C_H
+#define HW_FLEXCOMM_I2C_H
+
+#include "hw/i2c/i2c.h"
+#include "hw/misc/flexcomm_function.h"
+
+#define TYPE_FLEXCOMM_I2C "flexcomm-i2c"
+OBJECT_DECLARE_TYPE(FlexcommI2cState, FlexcommI2cClass, FLEXCOMM_I2C);
+
+struct FlexcommI2cState {
+ FlexcommFunction parent_obj;
+
+ I2CBus *bus;
+};
+
+struct FlexcommI2cClass {
+ FlexcommFunctionClass parent_obj;
+
+ FlexcommFunctionSelect select;
+};
+
+#define MSTSTATE_IDLE 0
+#define MSTSTATE_RXRDY 1
+#define MSTSTATE_TXRDY 2
+#define MSTSTATE_NAKADR 3
+#define MSTSTATE_NAKDAT 4
+
+
+#endif /* HW_FLEXCOMM_I2C_H */
diff --git a/include/hw/misc/flexcomm.h b/include/hw/misc/flexcomm.h
index 679b7ea64d..c9f1cd3890 100644
--- a/include/hw/misc/flexcomm.h
+++ b/include/hw/misc/flexcomm.h
@@ -16,6 +16,7 @@
#include "hw/arm/svd/flexcomm.h"
#include "qemu/fifo32.h"
#include "hw/char/flexcomm_usart.h"
+#include "hw/i2c/flexcomm_i2c.h"
#define FLEXCOMM_FUNC_USART 0
#define FLEXCOMM_FUNC_SPI 1
@@ -48,6 +49,7 @@ struct FlexcommState {
Fifo32 rx_fifo;
Fifo32 tx_fifo;
FlexcommUsartState usart;
+ FlexcommI2cState i2c;
};
#endif /* HW_FLEXCOMM_H */
diff --git a/hw/i2c/flexcomm_i2c.c b/hw/i2c/flexcomm_i2c.c
new file mode 100644
index 0000000000..e5341ec241
--- /dev/null
+++ b/hw/i2c/flexcomm_i2c.c
@@ -0,0 +1,250 @@
+/*
+ * QEMU model for NXP's FLEXCOMM I2C
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/cutils.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/address-spaces.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/i2c/flexcomm_i2c.h"
+#include "hw/arm/svd/flexcomm_i2c.h"
+
+#define REG(s, reg) (s->regs[R_FLEXCOMM_I2C_##reg])
+#define RF_WR(s, reg, field, val) \
+ ARRAY_FIELD_DP32(s->regs, FLEXCOMM_I2C_##reg, field, val)
+#define RF_RD(s, reg, field) \
+ ARRAY_FIELD_EX32(s->regs, FLEXCOMM_I2C_##reg, field)
+
+static FLEXCOMM_I2C_REGISTER_ACCESS_INFO_ARRAY(reg_info);
+
+static void flexcomm_i2c_reset(FlexcommFunction *f)
+{
+ for (int i = 0; i < FLEXCOMM_I2C_REGS_NO; i++) {
+ hwaddr addr = reg_info[i].addr;
+
+ if (addr != -1) {
+ struct RegisterInfo ri = {
+ .data = &f->regs[addr / 4],
+ .data_size = 4,
+ .access = ®_info[i],
+ };
+
+ register_reset(&ri);
+ }
+ }
+}
+
+static void flexcomm_i2c_irq_update(FlexcommFunction *f)
+{
+ bool enabled = RF_RD(f, CFG, MSTEN);
+ bool irq, per_irqs;
+
+ REG(f, INTSTAT) = REG(f, STAT) & REG(f, INTENSET);
+ per_irqs = REG(f, INTSTAT) != 0;
+
+ irq = enabled && per_irqs;
+
+ trace_flexcomm_i2c_irq(DEVICE(f)->id, irq, per_irqs, enabled);
+ flexcomm_set_irq(f, irq);
+}
+
+static MemTxResult flexcomm_i2c_reg_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ FlexcommFunction *f = FLEXCOMM_FUNCTION(opaque);
+ MemTxResult ret = MEMTX_OK;
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+
+ if (size != 4) {
+ ret = MEMTX_ERROR;
+ goto out;
+ }
+
+ *data = f->regs[addr / 4];
+
+ flexcomm_i2c_irq_update(f);
+
+out:
+ trace_flexcomm_i2c_reg_read(DEVICE(f)->id, rai->name, addr, *data);
+ return ret;
+}
+
+static MemTxResult flexcomm_i2c_reg_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ FlexcommFunction *f = FLEXCOMM_FUNCTION(opaque);
+ FlexcommI2cState *s = FLEXCOMM_I2C(opaque);
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+ struct RegisterInfo ri = {
+ .data = &f->regs[addr / 4],
+ .data_size = 4,
+ .access = rai,
+ };
+
+ trace_flexcomm_i2c_reg_write(DEVICE(f)->id, rai->name, addr, value);
+
+ if (size != 4) {
+ return MEMTX_ERROR;
+ }
+
+ switch (addr) {
+ case A_FLEXCOMM_I2C_CFG:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ if (RF_RD(f, CFG, SLVEN)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "I2C slave not supported");
+ }
+ if (RF_RD(f, CFG, MONEN)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "I2C monitoring not supported");
+ }
+ break;
+ }
+ case A_FLEXCOMM_I2C_INTENCLR:
+ {
+ REG(f, INTENSET) &= ~value;
+ break;
+ }
+ case A_FLEXCOMM_I2C_TIMEOUT:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ /* The bottom 4 bits are hard-wired to 0xF */
+ RF_WR(f, TIMEOUT, TOMIN, 0xf);
+ break;
+ }
+ case A_FLEXCOMM_I2C_MSTCTL:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ if (RF_RD(f, MSTCTL, MSTSTART)) {
+ uint8_t i2c_addr = RF_RD(f, MSTDAT, DATA);
+ bool recv = i2c_addr & 1;
+
+ trace_flexcomm_i2c_start(DEVICE(s)->id, i2c_addr, recv);
+ if (i2c_start_transfer(s->bus, i2c_addr, recv)) {
+ RF_WR(f, STAT, MSTSTATE, MSTSTATE_NAKADR);
+ trace_flexcomm_i2c_nak(DEVICE(s)->id);
+ } else {
+ if (recv) {
+ uint8_t data = i2c_recv(s->bus);
+
+ RF_WR(f, MSTDAT, DATA, data);
+ trace_flexcomm_i2c_rx(DEVICE(s)->id, data);
+ RF_WR(f, STAT, MSTSTATE, MSTSTATE_RXRDY);
+ } else {
+ RF_WR(f, STAT, MSTSTATE, MSTSTATE_TXRDY);
+ }
+ }
+ }
+ if (RF_RD(f, MSTCTL, MSTSTOP)) {
+ RF_WR(f, STAT, MSTSTATE, MSTSTATE_IDLE);
+ i2c_end_transfer(s->bus);
+ }
+ if (RF_RD(f, MSTCTL, MSTCONTINUE)) {
+ if (RF_RD(f, STAT, MSTSTATE) == MSTSTATE_TXRDY) {
+ uint8_t data = RF_RD(f, MSTDAT, DATA);
+
+ trace_flexcomm_i2c_tx(DEVICE(s)->id, data);
+ if (i2c_send(s->bus, data)) {
+ RF_WR(f, STAT, MSTSTATE, MSTSTATE_NAKDAT);
+ }
+ } else if (RF_RD(f, STAT, MSTSTATE) == MSTSTATE_RXRDY) {
+ uint8_t data = i2c_recv(s->bus);
+
+ RF_WR(f, MSTDAT, DATA, data);
+ trace_flexcomm_i2c_rx(DEVICE(s)->id, data);
+ }
+ }
+ break;
+ }
+ case A_FLEXCOMM_I2C_STAT:
+ {
+ /* write 1 to clear bits */
+ REG(f, STAT) &= ~value;
+ break;
+ }
+ case A_FLEXCOMM_I2C_SLVCTL:
+ case A_FLEXCOMM_I2C_SLVDAT:
+ case A_FLEXCOMM_I2C_SLVADR0:
+ case A_FLEXCOMM_I2C_SLVADR1:
+ case A_FLEXCOMM_I2C_SLVADR2:
+ case A_FLEXCOMM_I2C_SLVADR3:
+ case A_FLEXCOMM_I2C_SLVQUAL0:
+ {
+ qemu_log_mask(LOG_GUEST_ERROR, "I2C slave not supported\n");
+ break;
+ }
+ default:
+ register_write(&ri, value, ~0, NULL, false);
+ break;
+ }
+
+ flexcomm_i2c_irq_update(f);
+
+ return MEMTX_OK;
+}
+
+static void flexcomm_i2c_select(FlexcommFunction *f, bool selected)
+{
+ FlexcommI2cClass *ic = FLEXCOMM_I2C_GET_CLASS(f);
+
+ if (selected) {
+ flexcomm_i2c_reset(f);
+ }
+ ic->select(f, selected);
+}
+
+static const MemoryRegionOps flexcomm_i2c_ops = {
+ .read_with_attrs = flexcomm_i2c_reg_read,
+ .write_with_attrs = flexcomm_i2c_reg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void flexcomm_i2c_realize(DeviceState *dev, Error **errp)
+{
+ FlexcommI2cState *s = FLEXCOMM_I2C(dev);
+
+ s->bus = i2c_init_bus(DEVICE(s), "bus");
+}
+
+static void flexcomm_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ FlexcommFunctionClass *fc = FLEXCOMM_FUNCTION_CLASS(klass);
+ FlexcommI2cClass *ic = FLEXCOMM_I2C_CLASS(klass);
+
+ dc->realize = flexcomm_i2c_realize;
+ ic->select = fc->select;
+ fc->select = flexcomm_i2c_select;
+ fc->name = "i2c";
+ fc->mmio_ops = &flexcomm_i2c_ops;
+}
+
+static const TypeInfo flexcomm_i2c_types[] = {
+ {
+ .name = TYPE_FLEXCOMM_I2C,
+ .parent = TYPE_FLEXCOMM_FUNCTION,
+ .instance_size = sizeof(FlexcommI2cState),
+ .class_init = flexcomm_i2c_class_init,
+ .class_size = sizeof(FlexcommI2cClass),
+ },
+};
+
+DEFINE_TYPES(flexcomm_i2c_types);
diff --git a/hw/misc/flexcomm.c b/hw/misc/flexcomm.c
index a291148f27..b1a2f01acf 100644
--- a/hw/misc/flexcomm.c
+++ b/hw/misc/flexcomm.c
@@ -24,6 +24,7 @@
#include "hw/misc/flexcomm.h"
#include "hw/arm/svd/flexcomm_usart.h"
#include "hw/char/flexcomm_usart.h"
+#include "hw/i2c/flexcomm_i2c.h"
#define REG(s, reg) (s->regs[R_FLEXCOMM_##reg])
#define RF_WR(s, reg, field, val) \
@@ -220,6 +221,7 @@ static void flexcomm_init(Object *obj)
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->container);
sysbus_init_irq(sbd, &s->irq);
object_initialize_child(obj, "usart", &s->usart, TYPE_FLEXCOMM_USART);
+ object_initialize_child(obj, "i2c", &s->i2c, TYPE_FLEXCOMM_I2C);
}
static void flexcomm_finalize(Object *obj)
@@ -250,6 +252,7 @@ static void flexcomm_realize(DeviceState *dev, Error **errp)
memory_region_add_subregion_overlap(&s->container, 0, &s->mmio, -1);
flexcomm_func_realize_and_unref(FLEXCOMM_FUNCTION(&s->usart), errp);
+ flexcomm_func_realize_and_unref(FLEXCOMM_FUNCTION(&s->i2c), errp);
}
static const VMStateDescription vmstate_flexcomm = {
diff --git a/hw/arm/svd/meson.build b/hw/arm/svd/meson.build
index 3bff90bcbd..2542b56294 100644
--- a/hw/arm/svd/meson.build
+++ b/hw/arm/svd/meson.build
@@ -8,4 +8,8 @@ if get_option('mcux-soc-svd')
[ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/flexcomm_usart.h',
'-p', 'USART0', '-t', 'FLEXCOMM_USART',
'--fields', 'CFG:ENABLE FIFOCFG:ENABLE*,EMPTY* FIFO*:* *:'])
+ run_target('svd-flexcomm-i2c', command: svd_gen_header +
+ [ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/flexcomm_i2c.h',
+ '-p', 'I2C0', '-t', 'FLEXCOMM_I2C',
+ '--fields', 'CFG TIMEOUT:TOMIN MSTCTL MSTDAT STAT:MSTPENDING,MSTSTATE INT*:MSTPENDING* SLV*:'])
endif
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index c459adcb59..e7d79e6938 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -18,4 +18,5 @@ i2c_ss.add(when: 'CONFIG_PPC4XX', if_true: files('ppc4xx_i2c.c'))
i2c_ss.add(when: 'CONFIG_PCA954X', if_true: files('i2c_mux_pca954x.c'))
i2c_ss.add(when: 'CONFIG_PMBUS', if_true: files('pmbus_device.c'))
i2c_ss.add(when: 'CONFIG_BCM2835_I2C', if_true: files('bcm2835_i2c.c'))
+i2c_ss.add(when: 'CONFIG_FLEXCOMM', if_true: files('flexcomm_i2c.c'))
system_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss)
diff --git a/hw/i2c/trace-events b/hw/i2c/trace-events
index 6900e06eda..9f0175fab7 100644
--- a/hw/i2c/trace-events
+++ b/hw/i2c/trace-events
@@ -51,3 +51,13 @@ npcm7xx_smbus_recv_fifo(const char *id, uint8_t received, uint8_t expected) "%s
pca954x_write_bytes(uint8_t value) "PCA954X write data: 0x%02x"
pca954x_read_data(uint8_t value) "PCA954X read data: 0x%02x"
+
+# flexcomm_i2c.c
+
+flexcomm_i2c_reg_read(const char *id, const char *reg_name, uint32_t addr, uint32_t val) " %s: %s[0x%04x] -> 0x%08x"
+flexcomm_i2c_reg_write(const char *id, const char *reg_name, uint32_t addr, uint32_t val) "%s: %s[0x%04x] <- 0x%08x"
+flexcomm_i2c_start(const char *id, uint8_t addr, uint8_t recv) "%s: 0x%02x %d"
+flexcomm_i2c_rx(const char *id, uint8_t data) "%s: <- 0x%02x"
+flexcomm_i2c_tx(const char *id, uint8_t data) "%s: -> 0x%02x"
+flexcomm_i2c_nak(const char *id) "%s: <- nak"
+flexcomm_i2c_irq(const char *id, bool irq, bool perirqs, bool enabled) "%s: %d %d %d"
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 14167ae9e8..9a244fa01d 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -215,5 +215,6 @@ config XLNX_VERSAL_TRNG
config FLEXCOMM
bool
+ select I2C
source macio/Kconfig
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 09/25] hw/ssi: add support for flexcomm spi
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (7 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 08/25] hw/i2c: add support for flexcomm i2c Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 10/25] hw/misc: add support for RT500's clock controller Octavian Purdila
` (15 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
From: Sebastian Ene <sebastianene@google.com>
Add support for NXP's flexcomm spi. It supports FIFO access,
interrupts and master mode only. It does not support DMA.
The patch includes an automatically generated header which contains
the register layout and helpers.
The header can be regenerated with the svd-flexcomm-spi target when
the build is configured with --enable-mcux-soc-svd.
Signed-off-by: Sebastian Ene <sebastianene@google.com>
[tavip: add suport for CFG.SPOL, CFG.LSBF, TX control flags per FIFO
entry and 8/16 bit access to FIFORD and FIFOWR, convert to Resettable
interface, add support for migration]
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/arm/svd/flexcomm_spi.h | 327 +++++++++++++++++++++++
include/hw/misc/flexcomm.h | 2 +
include/hw/ssi/flexcomm_spi.h | 36 +++
hw/misc/flexcomm.c | 3 +
hw/ssi/flexcomm_spi.c | 422 ++++++++++++++++++++++++++++++
hw/arm/svd/meson.build | 4 +
hw/misc/Kconfig | 1 +
hw/ssi/meson.build | 1 +
hw/ssi/trace-events | 5 +
9 files changed, 801 insertions(+)
create mode 100644 include/hw/arm/svd/flexcomm_spi.h
create mode 100644 include/hw/ssi/flexcomm_spi.h
create mode 100644 hw/ssi/flexcomm_spi.c
diff --git a/include/hw/arm/svd/flexcomm_spi.h b/include/hw/arm/svd/flexcomm_spi.h
new file mode 100644
index 0000000000..c9ab2c661a
--- /dev/null
+++ b/include/hw/arm/svd/flexcomm_spi.h
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2016-2023 NXP SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Automatically generated by svd-gen-header.py from MIMXRT595S_cm33.xml
+ */
+#pragma once
+
+#include "hw/register.h"
+
+/* Serial Peripheral Interfaces (SPI) */
+#define FLEXCOMM_SPI_REGS_NO (1024)
+
+/* Configuration Register */
+REG32(FLEXCOMM_SPI_CFG, 0x400);
+/* SPI Enable */
+FIELD(FLEXCOMM_SPI_CFG, ENABLE, 0, 1);
+/* Master Mode Select */
+FIELD(FLEXCOMM_SPI_CFG, MASTER, 2, 1);
+/* LSB First Mode Enable */
+FIELD(FLEXCOMM_SPI_CFG, LSBF, 3, 1);
+/* Clock Phase Select */
+FIELD(FLEXCOMM_SPI_CFG, CPHA, 4, 1);
+/* Clock Polarity Select */
+FIELD(FLEXCOMM_SPI_CFG, CPOL, 5, 1);
+/* Loopback Mode Enable */
+FIELD(FLEXCOMM_SPI_CFG, LOOP, 7, 1);
+/* SSEL0 Polarity Select */
+FIELD(FLEXCOMM_SPI_CFG, SPOL0, 8, 1);
+/* SSEL1 Polarity Select */
+FIELD(FLEXCOMM_SPI_CFG, SPOL1, 9, 1);
+/* SSEL2 Polarity Select */
+FIELD(FLEXCOMM_SPI_CFG, SPOL2, 10, 1);
+/* SSEL3 Polarity Select */
+FIELD(FLEXCOMM_SPI_CFG, SPOL3, 11, 1);
+
+/* Status Register */
+REG32(FLEXCOMM_SPI_STAT, 0x408);
+/* Slave Select Assert */
+FIELD(FLEXCOMM_SPI_STAT, SSA, 4, 1);
+/* Slave Select Deassert */
+FIELD(FLEXCOMM_SPI_STAT, SSD, 5, 1);
+/* Stalled Status Flag */
+FIELD(FLEXCOMM_SPI_STAT, STALLED, 6, 1);
+/* End Transfer Control */
+FIELD(FLEXCOMM_SPI_STAT, ENDTRANSFER, 7, 1);
+/* Master Idle Status Flag */
+FIELD(FLEXCOMM_SPI_STAT, MSTIDLE, 8, 1);
+
+/* Interrupt Enable Register */
+REG32(FLEXCOMM_SPI_INTENSET, 0x40C);
+
+/* Interrupt Enable Clear Register */
+REG32(FLEXCOMM_SPI_INTENCLR, 0x410);
+
+/* Interrupt Status Register */
+REG32(FLEXCOMM_SPI_INTSTAT, 0x428);
+
+/* FIFO Configuration Register */
+REG32(FLEXCOMM_SPI_FIFOCFG, 0xE00);
+/* Enable the Transmit FIFO */
+FIELD(FLEXCOMM_SPI_FIFOCFG, ENABLETX, 0, 1);
+/* Enable the Receive FIFO */
+FIELD(FLEXCOMM_SPI_FIFOCFG, ENABLERX, 1, 1);
+/* Empty Command for the Transmit FIFO */
+FIELD(FLEXCOMM_SPI_FIFOCFG, EMPTYTX, 16, 1);
+/* Empty Command for the Receive FIFO */
+FIELD(FLEXCOMM_SPI_FIFOCFG, EMPTYRX, 17, 1);
+
+/* FIFO Status Register */
+REG32(FLEXCOMM_SPI_FIFOSTAT, 0xE04);
+/* TX FIFO Error */
+FIELD(FLEXCOMM_SPI_FIFOSTAT, TXERR, 0, 1);
+/* RX FIFO Error */
+FIELD(FLEXCOMM_SPI_FIFOSTAT, RXERR, 1, 1);
+/* Peripheral Interrupt */
+FIELD(FLEXCOMM_SPI_FIFOSTAT, PERINT, 3, 1);
+/* Transmit FIFO Empty */
+FIELD(FLEXCOMM_SPI_FIFOSTAT, TXEMPTY, 4, 1);
+/* Transmit FIFO is Not Full */
+FIELD(FLEXCOMM_SPI_FIFOSTAT, TXNOTFULL, 5, 1);
+/* Receive FIFO is Not Empty */
+FIELD(FLEXCOMM_SPI_FIFOSTAT, RXNOTEMPTY, 6, 1);
+/* Receive FIFO is Full */
+FIELD(FLEXCOMM_SPI_FIFOSTAT, RXFULL, 7, 1);
+/* Transmit FIFO Current Level */
+FIELD(FLEXCOMM_SPI_FIFOSTAT, TXLVL, 8, 5);
+/* Receive FIFO Current Level */
+FIELD(FLEXCOMM_SPI_FIFOSTAT, RXLVL, 16, 5);
+
+/* FIFO Trigger Register */
+REG32(FLEXCOMM_SPI_FIFOTRIG, 0xE08);
+/* Transmit FIFO Level Trigger Enable */
+FIELD(FLEXCOMM_SPI_FIFOTRIG, TXLVLENA, 0, 1);
+/* Receive FIFO Level Trigger Enable */
+FIELD(FLEXCOMM_SPI_FIFOTRIG, RXLVLENA, 1, 1);
+/* Transmit FIFO Level Trigger Point */
+FIELD(FLEXCOMM_SPI_FIFOTRIG, TXLVL, 8, 4);
+/* Trigger when the TX FIFO becomes empty */
+#define FLEXCOMM_SPI_FIFOTRIG_TXLVL_TXLVL0 0
+/* Trigger when the TX FIFO level decreases to 1 entry */
+#define FLEXCOMM_SPI_FIFOTRIG_TXLVL_TXLVL1 1
+/* Trigger when the TX FIFO level decreases to 15 entries (is no longer full) */
+#define FLEXCOMM_SPI_FIFOTRIG_TXLVL_TXLVL15 15
+/* Receive FIFO Level Trigger Point */
+FIELD(FLEXCOMM_SPI_FIFOTRIG, RXLVL, 16, 4);
+/* Trigger when the RX FIFO has received 1 entry (is no longer empty) */
+#define FLEXCOMM_SPI_FIFOTRIG_RXLVL_RXLVL1 0
+/* Trigger when the RX FIFO has received 2 entries */
+#define FLEXCOMM_SPI_FIFOTRIG_RXLVL_RXLVL2 1
+/* Trigger when the RX FIFO has received 16 entries (has become full) */
+#define FLEXCOMM_SPI_FIFOTRIG_RXLVL_RXLVL15 15
+
+/* FIFO Interrupt Enable Register */
+REG32(FLEXCOMM_SPI_FIFOINTENSET, 0xE10);
+/* TX Error Interrupt Enable */
+FIELD(FLEXCOMM_SPI_FIFOINTENSET, TXERR, 0, 1);
+/* Receive Error Interrupt Enable */
+FIELD(FLEXCOMM_SPI_FIFOINTENSET, RXERR, 1, 1);
+/* Transmit FIFO Level Interrupt Enable */
+FIELD(FLEXCOMM_SPI_FIFOINTENSET, TXLVL, 2, 1);
+/* Receive FIFO Level Interrupt Enable */
+FIELD(FLEXCOMM_SPI_FIFOINTENSET, RXLVL, 3, 1);
+
+/* FIFO Interrupt Enable Clear Register */
+REG32(FLEXCOMM_SPI_FIFOINTENCLR, 0xE14);
+/* TX Error Interrupt Enable */
+FIELD(FLEXCOMM_SPI_FIFOINTENCLR, TXERR, 0, 1);
+/* Receive Error Interrupt Enable */
+FIELD(FLEXCOMM_SPI_FIFOINTENCLR, RXERR, 1, 1);
+/* Transmit FIFO Level Interrupt Enable */
+FIELD(FLEXCOMM_SPI_FIFOINTENCLR, TXLVL, 2, 1);
+/* Receive FIFO Level Interrupt Enable */
+FIELD(FLEXCOMM_SPI_FIFOINTENCLR, RXLVL, 3, 1);
+
+/* FIFO Interrupt Status Register */
+REG32(FLEXCOMM_SPI_FIFOINTSTAT, 0xE18);
+/* TX FIFO Error Interrupt Status */
+FIELD(FLEXCOMM_SPI_FIFOINTSTAT, TXERR, 0, 1);
+/* RX FIFO Error Interrupt Status */
+FIELD(FLEXCOMM_SPI_FIFOINTSTAT, RXERR, 1, 1);
+/* Transmit FIFO Level Interrupt Status */
+FIELD(FLEXCOMM_SPI_FIFOINTSTAT, TXLVL, 2, 1);
+/* Receive FIFO Level Interrupt Status */
+FIELD(FLEXCOMM_SPI_FIFOINTSTAT, RXLVL, 3, 1);
+/* Peripheral Interrupt Status */
+FIELD(FLEXCOMM_SPI_FIFOINTSTAT, PERINT, 4, 1);
+
+/* FIFO Write Data Register */
+REG32(FLEXCOMM_SPI_FIFOWR, 0xE20);
+/* Transmit Data to the FIFO */
+FIELD(FLEXCOMM_SPI_FIFOWR, TXDATA, 0, 16);
+/* Transmit Slave Select 0 */
+FIELD(FLEXCOMM_SPI_FIFOWR, TXSSEL0_N, 16, 1);
+/* Transmit Slave Select 1 */
+FIELD(FLEXCOMM_SPI_FIFOWR, TXSSEL1_N, 17, 1);
+/* Transmit Slave Select 2 */
+FIELD(FLEXCOMM_SPI_FIFOWR, TXSSEL2_N, 18, 1);
+/* Transmit Slave Select 3 */
+FIELD(FLEXCOMM_SPI_FIFOWR, TXSSEL3_N, 19, 1);
+/* End of Transfer */
+FIELD(FLEXCOMM_SPI_FIFOWR, EOT, 20, 1);
+/* End of Frame */
+FIELD(FLEXCOMM_SPI_FIFOWR, EOF, 21, 1);
+/* Receive Ignore */
+FIELD(FLEXCOMM_SPI_FIFOWR, RXIGNORE, 22, 1);
+/* Transmit Ignore */
+FIELD(FLEXCOMM_SPI_FIFOWR, TXIGNORE, 23, 1);
+/* Data Length */
+FIELD(FLEXCOMM_SPI_FIFOWR, LEN, 24, 4);
+/* Data transfer is 4 bits in length */
+#define FLEXCOMM_SPI_FIFOWR_LEN_LEN_4BITS 3
+/* Data transfer is 5 bits in length */
+#define FLEXCOMM_SPI_FIFOWR_LEN_LEN_5BITS 4
+/* Data transfer is 16 bits in length */
+#define FLEXCOMM_SPI_FIFOWR_LEN_LEN_16BITS 15
+
+/* FIFO Read Data Register */
+REG32(FLEXCOMM_SPI_FIFORD, 0xE30);
+/* Received Data from the FIFO */
+FIELD(FLEXCOMM_SPI_FIFORD, RXDATA, 0, 16);
+/* Slave Select 0 for Receive */
+FIELD(FLEXCOMM_SPI_FIFORD, RXSSEL0_N, 16, 1);
+/* Slave Select 1 for Receive */
+FIELD(FLEXCOMM_SPI_FIFORD, RXSSEL1_N, 17, 1);
+/* Slave Select 2 for Receive */
+FIELD(FLEXCOMM_SPI_FIFORD, RXSSEL2_N, 18, 1);
+/* Slave Select 3 for Receive */
+FIELD(FLEXCOMM_SPI_FIFORD, RXSSEL3_N, 19, 1);
+/* Start of Transfer Flag */
+FIELD(FLEXCOMM_SPI_FIFORD, SOT, 20, 1);
+
+/* FIFO Data Read with no FIFO Pop Register */
+REG32(FLEXCOMM_SPI_FIFORDNOPOP, 0xE40);
+/* Received Data from the FIFO */
+FIELD(FLEXCOMM_SPI_FIFORDNOPOP, RXDATA, 0, 16);
+/* Slave Select 0 for Receive */
+FIELD(FLEXCOMM_SPI_FIFORDNOPOP, RXSSEL0_N, 16, 1);
+/* Slave Select 1 for Receive */
+FIELD(FLEXCOMM_SPI_FIFORDNOPOP, RXSSEL1_N, 17, 1);
+/* Slave Select 2 for Receive */
+FIELD(FLEXCOMM_SPI_FIFORDNOPOP, RXSSEL2_N, 18, 1);
+/* Slave Select 3 for Receive */
+FIELD(FLEXCOMM_SPI_FIFORDNOPOP, RXSSEL3_N, 19, 1);
+/* Start of Transfer Flag */
+FIELD(FLEXCOMM_SPI_FIFORDNOPOP, SOT, 20, 1);
+
+/* FIFO Size Register */
+REG32(FLEXCOMM_SPI_FIFOSIZE, 0xE48);
+/* FIFO Size */
+FIELD(FLEXCOMM_SPI_FIFOSIZE, FIFOSIZE, 0, 5);
+
+
+#define FLEXCOMM_SPI_REGISTER_ACCESS_INFO_ARRAY(_name) \
+ struct RegisterAccessInfo _name[FLEXCOMM_SPI_REGS_NO] = { \
+ [0 ... FLEXCOMM_SPI_REGS_NO - 1] = { \
+ .name = "", \
+ .addr = -1, \
+ }, \
+ [0x100] = { \
+ .name = "CFG", \
+ .addr = 0x400, \
+ .ro = 0xFFFFF042, \
+ .reset = 0x0, \
+ }, \
+ [0x101] = { \
+ .name = "DLY", \
+ .addr = 0x404, \
+ .ro = 0xFFFF0000, \
+ .reset = 0x0, \
+ }, \
+ [0x102] = { \
+ .name = "STAT", \
+ .addr = 0x408, \
+ .ro = 0xFFFFFF4F, \
+ .reset = 0x100, \
+ }, \
+ [0x103] = { \
+ .name = "INTENSET", \
+ .addr = 0x40C, \
+ .ro = 0xFFFFFECF, \
+ .reset = 0x0, \
+ }, \
+ [0x104] = { \
+ .name = "INTENCLR", \
+ .addr = 0x410, \
+ .ro = 0xFFFFFECF, \
+ .reset = 0x0, \
+ }, \
+ [0x109] = { \
+ .name = "DIV", \
+ .addr = 0x424, \
+ .ro = 0xFFFF0000, \
+ .reset = 0x0, \
+ }, \
+ [0x10A] = { \
+ .name = "INTSTAT", \
+ .addr = 0x428, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x380] = { \
+ .name = "FIFOCFG", \
+ .addr = 0xE00, \
+ .ro = 0xFFF80FFC, \
+ .reset = 0x0, \
+ }, \
+ [0x381] = { \
+ .name = "FIFOSTAT", \
+ .addr = 0xE04, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x30, \
+ }, \
+ [0x382] = { \
+ .name = "FIFOTRIG", \
+ .addr = 0xE08, \
+ .ro = 0xFFF0F0FC, \
+ .reset = 0x0, \
+ }, \
+ [0x384] = { \
+ .name = "FIFOINTENSET", \
+ .addr = 0xE10, \
+ .ro = 0xFFFFFFF0, \
+ .reset = 0x0, \
+ }, \
+ [0x385] = { \
+ .name = "FIFOINTENCLR", \
+ .addr = 0xE14, \
+ .ro = 0xFFFFFFF0, \
+ .reset = 0x0, \
+ }, \
+ [0x386] = { \
+ .name = "FIFOINTSTAT", \
+ .addr = 0xE18, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x388] = { \
+ .name = "FIFOWR", \
+ .addr = 0xE20, \
+ .ro = 0xF0000000, \
+ .reset = 0x0, \
+ }, \
+ [0x38C] = { \
+ .name = "FIFORD", \
+ .addr = 0xE30, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x390] = { \
+ .name = "FIFORDNOPOP", \
+ .addr = 0xE40, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x392] = { \
+ .name = "FIFOSIZE", \
+ .addr = 0xE48, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x3FF] = { \
+ .name = "ID", \
+ .addr = 0xFFC, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0xE0201200, \
+ }, \
+ }
diff --git a/include/hw/misc/flexcomm.h b/include/hw/misc/flexcomm.h
index c9f1cd3890..757453df97 100644
--- a/include/hw/misc/flexcomm.h
+++ b/include/hw/misc/flexcomm.h
@@ -17,6 +17,7 @@
#include "qemu/fifo32.h"
#include "hw/char/flexcomm_usart.h"
#include "hw/i2c/flexcomm_i2c.h"
+#include "hw/ssi/flexcomm_spi.h"
#define FLEXCOMM_FUNC_USART 0
#define FLEXCOMM_FUNC_SPI 1
@@ -50,6 +51,7 @@ struct FlexcommState {
Fifo32 tx_fifo;
FlexcommUsartState usart;
FlexcommI2cState i2c;
+ FlexcommSpiState spi;
};
#endif /* HW_FLEXCOMM_H */
diff --git a/include/hw/ssi/flexcomm_spi.h b/include/hw/ssi/flexcomm_spi.h
new file mode 100644
index 0000000000..c39cc33de4
--- /dev/null
+++ b/include/hw/ssi/flexcomm_spi.h
@@ -0,0 +1,36 @@
+/*
+ * QEMU model for NXP's FLEXCOMM SPI
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_FLEXCOMM_SPI_H
+#define HW_FLEXCOMM_SPI_H
+
+#include "hw/misc/flexcomm_function.h"
+#include "hw/ssi/ssi.h"
+
+#define TYPE_FLEXCOMM_SPI "flexcomm-spi"
+OBJECT_DECLARE_TYPE(FlexcommSpiState, FlexcommSpiClass, FLEXCOMM_SPI);
+
+struct FlexcommSpiState {
+ FlexcommFunction parent_obj;
+
+ SSIBus *bus;
+ qemu_irq cs[4];
+ bool cs_asserted[4];
+ uint32_t tx_ctrl;
+};
+
+struct FlexcommSpiClass {
+ FlexcommFunctionClass parent_obj;
+
+ FlexcommFunctionSelect select;
+};
+
+#endif /* HW_FLEXCOMM_SPI_H */
diff --git a/hw/misc/flexcomm.c b/hw/misc/flexcomm.c
index b1a2f01acf..fef220eb5b 100644
--- a/hw/misc/flexcomm.c
+++ b/hw/misc/flexcomm.c
@@ -25,6 +25,7 @@
#include "hw/arm/svd/flexcomm_usart.h"
#include "hw/char/flexcomm_usart.h"
#include "hw/i2c/flexcomm_i2c.h"
+#include "hw/ssi/flexcomm_spi.h"
#define REG(s, reg) (s->regs[R_FLEXCOMM_##reg])
#define RF_WR(s, reg, field, val) \
@@ -222,6 +223,7 @@ static void flexcomm_init(Object *obj)
sysbus_init_irq(sbd, &s->irq);
object_initialize_child(obj, "usart", &s->usart, TYPE_FLEXCOMM_USART);
object_initialize_child(obj, "i2c", &s->i2c, TYPE_FLEXCOMM_I2C);
+ object_initialize_child(obj, "spi", &s->spi, TYPE_FLEXCOMM_SPI);
}
static void flexcomm_finalize(Object *obj)
@@ -253,6 +255,7 @@ static void flexcomm_realize(DeviceState *dev, Error **errp)
memory_region_add_subregion_overlap(&s->container, 0, &s->mmio, -1);
flexcomm_func_realize_and_unref(FLEXCOMM_FUNCTION(&s->usart), errp);
flexcomm_func_realize_and_unref(FLEXCOMM_FUNCTION(&s->i2c), errp);
+ flexcomm_func_realize_and_unref(FLEXCOMM_FUNCTION(&s->spi), errp);
}
static const VMStateDescription vmstate_flexcomm = {
diff --git a/hw/ssi/flexcomm_spi.c b/hw/ssi/flexcomm_spi.c
new file mode 100644
index 0000000000..d8ecd37024
--- /dev/null
+++ b/hw/ssi/flexcomm_spi.c
@@ -0,0 +1,422 @@
+/*
+ * QEMU model for NXP's FLEXCOMM SPI
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "qemu/cutils.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/address-spaces.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "migration/vmstate.h"
+#include "hw/misc/flexcomm.h"
+#include "hw/arm/svd/flexcomm_spi.h"
+
+#define REG(s, reg) (s->regs[R_FLEXCOMM_SPI_##reg])
+#define RF_WR(s, reg, field, val) \
+ ARRAY_FIELD_DP32(s->regs, FLEXCOMM_SPI_##reg, field, val)
+#define RF_RD(s, reg, field) \
+ ARRAY_FIELD_EX32(s->regs, FLEXCOMM_SPI_##reg, field)
+
+#define FLEXCOMM_SSEL_ASSERTED (0)
+#define FLEXCOMM_SSEL_DEASSERTED (1)
+
+#define FLEXCOMM_SPI_FIFOWR_LEN_MIN (3)
+#define FLEXCOMM_SPI_FIFOWR_LEN_MAX (15)
+
+static const FLEXCOMM_SPI_REGISTER_ACCESS_INFO_ARRAY(reg_info);
+
+static void flexcomm_spi_reset(FlexcommFunction *f)
+{
+ for (int i = 0; i < FLEXCOMM_SPI_REGS_NO; i++) {
+ hwaddr addr = reg_info[i].addr;
+
+ if (addr != -1) {
+ struct RegisterInfo ri = {
+ .data = &f->regs[addr / 4],
+ .data_size = 4,
+ .access = ®_info[i],
+ };
+
+ register_reset(&ri);
+ }
+ }
+
+ RF_WR(f, FIFOSIZE, FIFOSIZE, 0x8);
+}
+
+static void flexcomm_spi_irq_update(FlexcommFunction *f)
+{
+ bool irq, per_irqs, fifo_irqs, enabled = RF_RD(f, CFG, ENABLE);
+
+ flexcomm_update_fifostat(f);
+ fifo_irqs = REG(f, FIFOINTSTAT) & REG(f, FIFOINTENSET);
+
+ REG(f, INTSTAT) = REG(f, STAT) & REG(f, INTENSET);
+ per_irqs = REG(f, INTSTAT) != 0;
+
+ irq = enabled && (fifo_irqs || per_irqs);
+
+ trace_flexcomm_spi_irq(DEVICE(f)->id, irq, fifo_irqs, per_irqs, enabled);
+ flexcomm_set_irq(f, irq);
+}
+
+static void flexcomm_spi_select(FlexcommFunction *f, bool selected)
+{
+ FlexcommSpiState *s = FLEXCOMM_SPI(f);
+ FlexcommSpiClass *sc = FLEXCOMM_SPI_GET_CLASS(f);
+
+ if (selected) {
+ bool spol[] = {
+ RF_RD(f, CFG, SPOL0), RF_RD(f, CFG, SPOL1), RF_RD(f, CFG, SPOL2),
+ RF_RD(f, CFG, SPOL3),
+ };
+
+ flexcomm_spi_reset(f);
+ for (int i = 0; i < ARRAY_SIZE(s->cs); i++) {
+ s->cs_asserted[i] = false;
+ qemu_set_irq(s->cs[i], !spol[i]);
+ }
+ }
+ sc->select(f, selected);
+}
+
+static MemTxResult flexcomm_spi_reg_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ FlexcommFunction *f = FLEXCOMM_FUNCTION(opaque);
+ MemTxResult ret = MEMTX_OK;
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+
+ /*
+ * Allow 8/16 bits access to the FIFORD LSB half-word. This is supported by
+ * hardware and required for 1/2 byte(s) width DMA transfers.
+ */
+ if (size != 4 && addr != A_FLEXCOMM_SPI_FIFORD) {
+ ret = MEMTX_ERROR;
+ goto out;
+ }
+
+ switch (addr) {
+ case A_FLEXCOMM_SPI_FIFORD:
+ {
+ /* If we are running in loopback mode get the data from TX FIFO */
+ if (RF_RD(f, CFG, LOOP) &&
+ RF_RD(f, CFG, MASTER))
+ {
+ if (!fifo32_is_empty(f->tx_fifo)) {
+ *data = fifo32_pop(f->tx_fifo);
+ }
+ break;
+ }
+
+ if (!fifo32_is_empty(f->rx_fifo)) {
+ *data = fifo32_pop(f->rx_fifo);
+ }
+ break;
+ }
+ case A_FLEXCOMM_SPI_FIFORDNOPOP:
+ {
+ if (!fifo32_is_empty(f->rx_fifo)) {
+ *data = fifo32_peek(f->rx_fifo);
+ }
+ break;
+ }
+ default:
+ *data = f->regs[addr / 4];
+ break;
+ }
+
+ flexcomm_spi_irq_update(f);
+
+out:
+ trace_flexcomm_spi_reg_read(DEVICE(f)->id, rai->name, addr, *data);
+ return ret;
+}
+
+static uint32_t fifowr_len_bits(uint32_t val)
+{
+ int len = FIELD_EX32(val, FLEXCOMM_SPI_FIFOWR, LEN);
+
+ if (len < FLEXCOMM_SPI_FIFOWR_LEN_MIN ||
+ len > FLEXCOMM_SPI_FIFOWR_LEN_MAX) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid spi xfer len %d\n",
+ __func__, val);
+ return 0;
+ }
+
+ return len + 1;
+}
+
+static inline uint32_t fifowr_len_bytes(uint32_t val)
+{
+ return fifowr_len_bits(val) > 8 ? 2 : 1;
+}
+
+static uint32_t flexcomm_spi_xfer_word(FlexcommSpiState *s, uint32_t out_data,
+ int bytes, bool be)
+{
+ uint32_t word = 0;
+ int out = 0;
+
+ for (int i = 0; i < bytes; i++) {
+ if (be) {
+ int byte_offset = bytes - i - 1;
+ out = (out_data & (0xFF << byte_offset * 8)) >> byte_offset * 8;
+ word |= ssi_transfer(s->bus, out) << byte_offset * 8;
+ } else {
+ out = (out_data & (0xFF << i * 8)) >> i * 8;
+ word |= ssi_transfer(s->bus, out) << i * 8;
+ }
+ }
+
+ return word;
+}
+
+static uint32_t flexcomm_spi_get_ss_mask(FlexcommSpiState *s,
+ uint32_t txfifo_val)
+{
+ FlexcommFunction *f = FLEXCOMM_FUNCTION(s);
+
+ uint32_t slave_select_mask = 0;
+ bool ss[] = {
+ FIELD_EX32(txfifo_val, FLEXCOMM_SPI_FIFOWR, TXSSEL0_N),
+ FIELD_EX32(txfifo_val, FLEXCOMM_SPI_FIFOWR, TXSSEL1_N),
+ FIELD_EX32(txfifo_val, FLEXCOMM_SPI_FIFOWR, TXSSEL2_N),
+ FIELD_EX32(txfifo_val, FLEXCOMM_SPI_FIFOWR, TXSSEL3_N),
+ };
+ bool spol[] = {
+ RF_RD(f, CFG, SPOL0), RF_RD(f, CFG, SPOL1), RF_RD(f, CFG, SPOL2),
+ RF_RD(f, CFG, SPOL3),
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(s->cs); i++) {
+ int irq_level = ss[i] ? spol[i] : !spol[i];
+
+ slave_select_mask |= (ss[i] << i);
+ s->cs_asserted[i] = ss[i];
+ qemu_set_irq(s->cs[i], irq_level);
+ }
+
+ return slave_select_mask;
+}
+
+static MemTxResult flexcomm_spi_reg_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ FlexcommFunction *f = FLEXCOMM_FUNCTION(opaque);
+ FlexcommSpiState *s = FLEXCOMM_SPI(opaque);
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+ struct RegisterInfo ri = {
+ .data = &f->regs[addr / 4],
+ .data_size = 4,
+ .access = rai,
+ };
+ MemTxResult ret = MEMTX_OK;
+
+ trace_flexcomm_spi_reg_write(DEVICE(f)->id, rai->name, addr, value);
+
+ /*
+ * Allow 8/16 bits access to both the FIFOWR MSB and LSB half-words. The
+ * former is required for updating the control bits while the latter for DMA
+ * transfers of 1/2 byte(s) width.
+ */
+ if (size != 4 && (addr / 4 != R_FLEXCOMM_SPI_FIFOWR)) {
+ return MEMTX_ERROR;
+ }
+
+ switch (addr) {
+ case A_FLEXCOMM_SPI_CFG:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ break;
+ }
+ case A_FLEXCOMM_SPI_INTENCLR:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ REG(f, INTENSET) &= ~REG(f, INTENCLR);
+ break;
+ }
+ case A_FLEXCOMM_SPI_FIFOCFG:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ flexcomm_reset_fifos(f);
+ break;
+ }
+ case A_FLEXCOMM_SPI_FIFOSTAT:
+ {
+ flexcomm_clear_fifostat(f, value);
+ break;
+ }
+ case A_FLEXCOMM_SPI_FIFOINTENSET:
+ {
+ REG(f, FIFOINTENSET) |= value;
+ break;
+ }
+ case A_FLEXCOMM_SPI_FIFOINTENCLR:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ REG(f, FIFOINTENSET) &= ~REG(f, FIFOINTENCLR);
+ break;
+ }
+ /* update control bits but don't push into the FIFO */
+ case A_FLEXCOMM_SPI_FIFOWR + 2:
+ {
+ if (value != 0) {
+ s->tx_ctrl = value << 16;
+ }
+ break;
+ }
+ /* update control bits but don't push into the FIFO */
+ case A_FLEXCOMM_SPI_FIFOWR + 3:
+ {
+ if (value != 0) {
+ s->tx_ctrl = value << 24;
+ }
+ break;
+ }
+ case A_FLEXCOMM_SPI_FIFOWR:
+ {
+ /* fifo value contains both data and control bits */
+ uint32_t txfifo_val;
+ uint16_t tx_data = FIELD_EX32(value, FLEXCOMM_SPI_FIFOWR, TXDATA);
+ uint32_t tx_ctrl = value & 0xffff0000;
+
+ if (size > 2 && tx_ctrl != 0) {
+ /* non-zero writes to control bits updates them */
+ s->tx_ctrl = tx_ctrl;
+ }
+
+ /* push the data and control bits into the FIFO */
+ txfifo_val = tx_data | s->tx_ctrl;
+
+ if (!fifo32_is_full(f->tx_fifo)) {
+ fifo32_push(f->tx_fifo, txfifo_val);
+ }
+
+ if (!RF_RD(f, CFG, ENABLE) || !RF_RD(f, FIFOCFG, ENABLETX)) {
+ break;
+ }
+
+ /*
+ * On loopback mode we just insert the values in the TX FIFO. On slave
+ * mode master needs to initiate the SPI transfer.
+ */
+ if (RF_RD(f, CFG, LOOP) || !RF_RD(f, CFG, MASTER)) {
+ break;
+ }
+
+ while (!fifo32_is_empty(f->tx_fifo)) {
+ txfifo_val = fifo32_pop(f->tx_fifo);
+
+ uint32_t ss_mask = flexcomm_spi_get_ss_mask(s, txfifo_val);
+ uint32_t data = FIELD_EX32(txfifo_val, FLEXCOMM_SPI_FIFOWR, TXDATA);
+ uint8_t bytes = fifowr_len_bytes(txfifo_val);
+ bool msb = !RF_RD(f, CFG, LSBF);
+ uint32_t val32;
+
+ val32 = flexcomm_spi_xfer_word(s, data, bytes, msb);
+
+ if (!fifo32_is_full(f->rx_fifo)) {
+ /* Append the mask that informs which client is active */
+ val32 |= (ss_mask << R_FLEXCOMM_SPI_FIFORD_RXSSEL0_N_SHIFT);
+ fifo32_push(f->rx_fifo, val32);
+ }
+
+ /* If this is the end of the transfer raise the CS line */
+ if (FIELD_EX32(txfifo_val, FLEXCOMM_SPI_FIFOWR, EOT)) {
+ bool spol[ARRAY_SIZE(s->cs)] = {
+ RF_RD(f, CFG, SPOL0),
+ RF_RD(f, CFG, SPOL1),
+ RF_RD(f, CFG, SPOL2),
+ RF_RD(f, CFG, SPOL3),
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(s->cs); i++) {
+ if (s->cs_asserted[i]) {
+ s->cs_asserted[i] = false;
+ qemu_set_irq(s->cs[i], !spol[i]);
+ }
+ }
+ }
+ }
+ break;
+ }
+ default:
+ register_write(&ri, value, ~0, NULL, false);
+ break;
+ }
+
+ flexcomm_spi_irq_update(f);
+
+ return ret;
+}
+
+static const MemoryRegionOps flexcomm_spi_ops = {
+ .read_with_attrs = flexcomm_spi_reg_read,
+ .write_with_attrs = flexcomm_spi_reg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void flexcomm_spi_realize(DeviceState *dev, Error **error)
+{
+ FlexcommSpiState *s = FLEXCOMM_SPI(dev);
+
+ s->bus = ssi_create_bus(DEVICE(s), "bus");
+ qdev_init_gpio_out_named(DEVICE(s), s->cs, "cs", ARRAY_SIZE(s->cs));
+}
+
+static const VMStateDescription vmstate_flexcomm_spi = {
+ .name = "flexcomm-spi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_BOOL_ARRAY(cs_asserted, FlexcommSpiState, 4),
+ VMSTATE_UINT32(tx_ctrl, FlexcommSpiState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void flexcomm_spi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ FlexcommFunctionClass *fc = FLEXCOMM_FUNCTION_CLASS(klass);
+ FlexcommSpiClass *sc = FLEXCOMM_SPI_CLASS(klass);
+
+ dc->realize = flexcomm_spi_realize;
+ dc->vmsd = &vmstate_flexcomm_spi;
+ sc->select = fc->select;
+ fc->select = flexcomm_spi_select;
+ fc->name = "spi";
+ fc->has_fifos = true;
+ fc->mmio_ops = &flexcomm_spi_ops;
+}
+
+static const TypeInfo flexcomm_spi_types[] = {
+ {
+ .name = TYPE_FLEXCOMM_SPI,
+ .parent = TYPE_FLEXCOMM_FUNCTION,
+ .instance_size = sizeof(FlexcommSpiState),
+ .class_init = flexcomm_spi_class_init,
+ .class_size = sizeof(FlexcommSpiClass),
+ },
+};
+
+DEFINE_TYPES(flexcomm_spi_types);
diff --git a/hw/arm/svd/meson.build b/hw/arm/svd/meson.build
index 2542b56294..e7d62a7f35 100644
--- a/hw/arm/svd/meson.build
+++ b/hw/arm/svd/meson.build
@@ -12,4 +12,8 @@ if get_option('mcux-soc-svd')
[ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/flexcomm_i2c.h',
'-p', 'I2C0', '-t', 'FLEXCOMM_I2C',
'--fields', 'CFG TIMEOUT:TOMIN MSTCTL MSTDAT STAT:MSTPENDING,MSTSTATE INT*:MSTPENDING* SLV*:'])
+ run_target('svd-flexcomm-spi', command: svd_gen_header +
+ [ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/flexcomm_spi.h',
+ '-p', 'SPI0', '-t', 'FLEXCOMM_SPI',
+ '--fields', 'CFG FIFOCFG:ENABLE*,EMPTY* FIFO*:* INT*: STAT'])
endif
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 9a244fa01d..b373e651e1 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -216,5 +216,6 @@ config XLNX_VERSAL_TRNG
config FLEXCOMM
bool
select I2C
+ select SSI
source macio/Kconfig
diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build
index 23cd425ab0..58e0d14b37 100644
--- a/hw/ssi/meson.build
+++ b/hw/ssi/meson.build
@@ -12,3 +12,4 @@ system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_spi.c'))
system_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_spi_host.c'))
system_ss.add(when: 'CONFIG_BCM2835_SPI', if_true: files('bcm2835_spi.c'))
system_ss.add(when: 'CONFIG_PNV_SPI', if_true: files('pnv_spi.c'))
+system_ss.add(when: 'CONFIG_FLEXCOMM', if_true: files('flexcomm_spi.c'))
diff --git a/hw/ssi/trace-events b/hw/ssi/trace-events
index 089d269994..f849f1f8be 100644
--- a/hw/ssi/trace-events
+++ b/hw/ssi/trace-events
@@ -53,3 +53,8 @@ pnv_spi_rx_read_N2frame(void) ""
pnv_spi_shift_rx(uint8_t byte, uint32_t index) "byte = 0x%2.2x into RDR from payload index %d"
pnv_spi_sequencer_stop_requested(const char* reason) "due to %s"
pnv_spi_RDR_match(const char* result) "%s"
+
+# flexcomm_spi.c
+flexcomm_spi_reg_read(const char *id, const char *reg_name, uint32_t addr, uint32_t val) " %s: %s[0x%04x] -> 0x%08x"
+flexcomm_spi_reg_write(const char *id, const char *reg_name, uint32_t addr, uint32_t val) "%s: %s[0x%04x] <- 0x%08x"
+flexcomm_spi_irq(const char *id, bool irq, bool fifoirqs, bool perirqs, bool enabled) "%s: %d %d %d %d"
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 10/25] hw/misc: add support for RT500's clock controller
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (8 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 09/25] hw/ssi: add support for flexcomm spi Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 11/25] hw/ssi: add support for flexspi Octavian Purdila
` (14 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
It supports system and audio PLL initialization and SYSTICK and
OSTIMER clock source selection.
The patch includes automatically generated headers which contains the
register layout and helpers.
The headers can be regenerated with the svd-rt500-clkctl0 and
svd-rt500-clkctl1 targets when the build is configured with
--enable-mcux-soc-svd.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/arm/svd/rt500_clkctl0.h | 509 ++++++++++++++++++++++
include/hw/arm/svd/rt500_clkctl1.h | 675 +++++++++++++++++++++++++++++
include/hw/misc/rt500_clk_freqs.h | 18 +
include/hw/misc/rt500_clkctl0.h | 35 ++
include/hw/misc/rt500_clkctl1.h | 36 ++
hw/misc/rt500_clkctl0.c | 253 +++++++++++
hw/misc/rt500_clkctl1.c | 238 ++++++++++
hw/arm/Kconfig | 5 +
hw/arm/svd/meson.build | 8 +
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/trace-events | 8 +
12 files changed, 1789 insertions(+)
create mode 100644 include/hw/arm/svd/rt500_clkctl0.h
create mode 100644 include/hw/arm/svd/rt500_clkctl1.h
create mode 100644 include/hw/misc/rt500_clk_freqs.h
create mode 100644 include/hw/misc/rt500_clkctl0.h
create mode 100644 include/hw/misc/rt500_clkctl1.h
create mode 100644 hw/misc/rt500_clkctl0.c
create mode 100644 hw/misc/rt500_clkctl1.c
diff --git a/include/hw/arm/svd/rt500_clkctl0.h b/include/hw/arm/svd/rt500_clkctl0.h
new file mode 100644
index 0000000000..89be8ff5fb
--- /dev/null
+++ b/include/hw/arm/svd/rt500_clkctl0.h
@@ -0,0 +1,509 @@
+/*
+ * Copyright 2016-2023 NXP SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Automatically generated by svd-gen-header.py from MIMXRT595S_cm33.xml
+ */
+#pragma once
+
+#include "hw/register.h"
+
+/* Clock Controller 0 */
+#define RT500_CLKCTL0_REGS_NO (490)
+
+/* Clock Control 0 */
+REG32(RT500_CLKCTL0_PSCCTL0, 0x10);
+/* DSP clock control */
+FIELD(RT500_CLKCTL0_PSCCTL0, DSP_CLK, 1, 1);
+/* 128KB ROM Controller clock control */
+FIELD(RT500_CLKCTL0_PSCCTL0, ROM_CTRLR_CLK, 2, 1);
+
+/* Clock Control 1 */
+REG32(RT500_CLKCTL0_PSCCTL1, 0x14);
+
+/* Clock Control 2 */
+REG32(RT500_CLKCTL0_PSCCTL2, 0x18);
+
+/* Clock Control 0 Set */
+REG32(RT500_CLKCTL0_PSCCTL0_SET, 0x40);
+/* DSP clock set */
+FIELD(RT500_CLKCTL0_PSCCTL0_SET, DSP_CLK, 1, 1);
+/* 128KB ROM Controller clock set */
+FIELD(RT500_CLKCTL0_PSCCTL0_SET, ROM_CTRLR_CLK, 2, 1);
+
+/* Clock Control 1 Set */
+REG32(RT500_CLKCTL0_PSCCTL1_SET, 0x44);
+
+/* Clock Control 2 Set */
+REG32(RT500_CLKCTL0_PSCCTL2_SET, 0x48);
+
+/* Clock Control 0 Clear */
+REG32(RT500_CLKCTL0_PSCCTL0_CLR, 0x70);
+/* DSP clock clear */
+FIELD(RT500_CLKCTL0_PSCCTL0_CLR, DSP_CLK, 1, 1);
+/* 128KB ROM Controller clock clear */
+FIELD(RT500_CLKCTL0_PSCCTL0_CLR, ROM_CTRLR_CLK, 2, 1);
+
+/* Clock Control 1 Clear */
+REG32(RT500_CLKCTL0_PSCCTL1_CLR, 0x74);
+
+/* Clock Control 2 Clear */
+REG32(RT500_CLKCTL0_PSCCTL2_CLR, 0x78);
+
+/* FRO Clock Status */
+REG32(RT500_CLKCTL0_FROCLKSTATUS, 0x10C);
+
+/* System PLL0 PFD */
+REG32(RT500_CLKCTL0_SYSPLL0PFD, 0x218);
+/* PLL Fractional Divider 0 */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD0, 0, 6);
+/* PFD0 Clock Ready Status Flag */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD0_CLKRDY, 6, 1);
+/* PFD0 Clock Gate */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD0_CLKGATE, 7, 1);
+/* PLL Fractional Divider 1 */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD1, 8, 6);
+/* PFD1 Clock Ready Status Flag */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD1_CLKRDY, 14, 1);
+/* PFD1 Clock Gate */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD1_CLKGATE, 15, 1);
+/* PLL Fractional Divider 2 */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD2, 16, 6);
+/* PFD2 Clock Ready Status Flag */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD2_CLKRDY, 22, 1);
+/* PFD2 Clock Gate */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD2_CLKGATE, 23, 1);
+/* PLL Fractional Divider 3 */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD3, 24, 6);
+/* PFD3 Clock Ready Status Flag */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD3_CLKRDY, 30, 1);
+/* PFD3 Clock Gate */
+FIELD(RT500_CLKCTL0_SYSPLL0PFD, PFD3_CLKGATE, 31, 1);
+
+/* SYSTICK Functional Clock Select */
+REG32(RT500_CLKCTL0_SYSTICKFCLKSEL, 0x760);
+/* Select Clock Source */
+FIELD(RT500_CLKCTL0_SYSTICKFCLKSEL, SEL, 0, 3);
+/* Systick Divider Output Clock */
+#define RT500_CLKCTL0_SYSTICKFCLKSEL_SEL_SYSTICK_DIV_OUTPUT 0
+/* Low Power Oscillator Clock (LPOSC) */
+#define RT500_CLKCTL0_SYSTICKFCLKSEL_SEL_LPOSC 1
+/* 32 KHz RTC Clock */
+#define RT500_CLKCTL0_SYSTICKFCLKSEL_SEL_A32KHZ_RTC 2
+/* None; this may be selected to reduce power when no output is needed. */
+#define RT500_CLKCTL0_SYSTICKFCLKSEL_SEL_NONE 7
+
+/* SYSTICK Functional Clock Divider */
+REG32(RT500_CLKCTL0_SYSTICKFCLKDIV, 0x764);
+/* Clock Divider Value Selection */
+FIELD(RT500_CLKCTL0_SYSTICKFCLKDIV, DIV, 0, 8);
+/* Reset the Divider Counter */
+FIELD(RT500_CLKCTL0_SYSTICKFCLKDIV, RESET, 29, 1);
+/* Halt the Divider Counter */
+FIELD(RT500_CLKCTL0_SYSTICKFCLKDIV, HALT, 30, 1);
+/* Divider status flag */
+FIELD(RT500_CLKCTL0_SYSTICKFCLKDIV, REQFLAG, 31, 1);
+
+
+#define RT500_CLKCTL0_REGISTER_ACCESS_INFO_ARRAY(_name) \
+ struct RegisterAccessInfo _name[RT500_CLKCTL0_REGS_NO] = { \
+ [0 ... RT500_CLKCTL0_REGS_NO - 1] = { \
+ .name = "", \
+ .addr = -1, \
+ }, \
+ [0x4] = { \
+ .name = "PSCCTL0", \
+ .addr = 0x10, \
+ .ro = 0xA208E0E1, \
+ .reset = 0x5, \
+ }, \
+ [0x5] = { \
+ .name = "PSCCTL1", \
+ .addr = 0x14, \
+ .ro = 0xFEFE7FF3, \
+ .reset = 0x0, \
+ }, \
+ [0x6] = { \
+ .name = "PSCCTL2", \
+ .addr = 0x18, \
+ .ro = 0xDFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ [0x10] = { \
+ .name = "PSCCTL0_SET", \
+ .addr = 0x40, \
+ .ro = 0xA208E0E1, \
+ .reset = 0x0, \
+ }, \
+ [0x11] = { \
+ .name = "PSCCTL1_SET", \
+ .addr = 0x44, \
+ .ro = 0xFEFE7FF3, \
+ .reset = 0x0, \
+ }, \
+ [0x12] = { \
+ .name = "PSCCTL2_SET", \
+ .addr = 0x48, \
+ .ro = 0xDFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ [0x1C] = { \
+ .name = "PSCCTL0_CLR", \
+ .addr = 0x70, \
+ .ro = 0xA208E0E1, \
+ .reset = 0x0, \
+ }, \
+ [0x1D] = { \
+ .name = "PSCCTL1_CLR", \
+ .addr = 0x74, \
+ .ro = 0xFEFE7FF3, \
+ .reset = 0x0, \
+ }, \
+ [0x1E] = { \
+ .name = "PSCCTL2_CLR", \
+ .addr = 0x78, \
+ .ro = 0xDFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ [0x20] = { \
+ .name = "FRO_CONTROL", \
+ .addr = 0x80, \
+ .ro = 0x7C000000, \
+ .reset = 0x0, \
+ }, \
+ [0x21] = { \
+ .name = "FRO_CAPVAL", \
+ .addr = 0x84, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x23] = { \
+ .name = "FRO_RDTRIM", \
+ .addr = 0x8C, \
+ .ro = 0xFFFFF800, \
+ .reset = 0x3BF, \
+ }, \
+ [0x24] = { \
+ .name = "FRO_SCTRIM", \
+ .addr = 0x90, \
+ .ro = 0xFFFFFFC0, \
+ .reset = 0x20, \
+ }, \
+ [0x42] = { \
+ .name = "FRODIVSEL", \
+ .addr = 0x108, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ [0x43] = { \
+ .name = "FROCLKSTATUS", \
+ .addr = 0x10C, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x44] = { \
+ .name = "FRODIVOEN", \
+ .addr = 0x110, \
+ .ro = 0xFFFFFFE0, \
+ .reset = 0x0, \
+ }, \
+ [0x4C] = { \
+ .name = "LOWFREQCLKDIV", \
+ .addr = 0x130, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x58] = { \
+ .name = "SYSOSCCTL0", \
+ .addr = 0x160, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ [0x5A] = { \
+ .name = "SYSOSCBYPASS", \
+ .addr = 0x168, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x0, \
+ }, \
+ [0x64] = { \
+ .name = "LPOSCCTL0", \
+ .addr = 0x190, \
+ .ro = 0x7FFFFFFF, \
+ .reset = 0x807BC4D4, \
+ }, \
+ [0x70] = { \
+ .name = "OSC32KHZCTL0", \
+ .addr = 0x1C0, \
+ .ro = 0xFFFFFFFE, \
+ .reset = 0x0, \
+ }, \
+ [0x80] = { \
+ .name = "SYSPLL0CLKSEL", \
+ .addr = 0x200, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x81] = { \
+ .name = "SYSPLL0CTL0", \
+ .addr = 0x204, \
+ .ro = 0xFF00DFFC, \
+ .reset = 0x160002, \
+ }, \
+ [0x83] = { \
+ .name = "SYSPLL0LOCKTIMEDIV2", \
+ .addr = 0x20C, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xCAFE, \
+ }, \
+ [0x84] = { \
+ .name = "SYSPLL0NUM", \
+ .addr = 0x210, \
+ .ro = 0xC0000000, \
+ .reset = 0x4DD2F15, \
+ }, \
+ [0x85] = { \
+ .name = "SYSPLL0DENOM", \
+ .addr = 0x214, \
+ .ro = 0xC0000000, \
+ .reset = 0x1FFFFFDB, \
+ }, \
+ [0x86] = { \
+ .name = "SYSPLL0PFD", \
+ .addr = 0x218, \
+ .ro = 0x0, \
+ .reset = 0x80808080, \
+ }, \
+ [0x90] = { \
+ .name = "MAINPLLCLKDIV", \
+ .addr = 0x240, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x91] = { \
+ .name = "DSPPLLCLKDIV", \
+ .addr = 0x244, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x92] = { \
+ .name = "AUX0PLLCLKDIV", \
+ .addr = 0x248, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x93] = { \
+ .name = "AUX1PLLCLKDIV", \
+ .addr = 0x24C, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x100] = { \
+ .name = "SYSCPUAHBCLKDIV", \
+ .addr = 0x400, \
+ .ro = 0x7FFFFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x10C] = { \
+ .name = "MAINCLKSELA", \
+ .addr = 0x430, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ [0x10D] = { \
+ .name = "MAINCLKSELB", \
+ .addr = 0x434, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ [0x140] = { \
+ .name = "PFC0DIV", \
+ .addr = 0x500, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x141] = { \
+ .name = "PFC1DIV", \
+ .addr = 0x504, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x188] = { \
+ .name = "FLEXSPI0FCLKSEL", \
+ .addr = 0x620, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x189] = { \
+ .name = "FLEXSPI0FCLKDIV", \
+ .addr = 0x624, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x18C] = { \
+ .name = "FLEXSPI1FCLKSEL", \
+ .addr = 0x630, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x18D] = { \
+ .name = "FLEXSPI1FCLKDIV", \
+ .addr = 0x634, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x190] = { \
+ .name = "SCTFCLKSEL", \
+ .addr = 0x640, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x191] = { \
+ .name = "SCTIN7CLKDIV", \
+ .addr = 0x644, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x198] = { \
+ .name = "USBHSFCLKSEL", \
+ .addr = 0x660, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x199] = { \
+ .name = "USBHSFCLKDIV", \
+ .addr = 0x664, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1A0] = { \
+ .name = "SDIO0FCLKSEL", \
+ .addr = 0x680, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1A1] = { \
+ .name = "SDIO0FCLKDIV", \
+ .addr = 0x684, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1A4] = { \
+ .name = "SDIO1FCLKSEL", \
+ .addr = 0x690, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1A5] = { \
+ .name = "SDIO1FCLKDIV", \
+ .addr = 0x694, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1B4] = { \
+ .name = "ADC0FCLKSEL0", \
+ .addr = 0x6D0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1B5] = { \
+ .name = "ADC0FCLKSEL1", \
+ .addr = 0x6D4, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1B6] = { \
+ .name = "ADC0FCLKDIV", \
+ .addr = 0x6D8, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1C0] = { \
+ .name = "UTICKFCLKSEL", \
+ .addr = 0x700, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1C8] = { \
+ .name = "WDT0FCLKSEL", \
+ .addr = 0x720, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x0, \
+ }, \
+ [0x1CC] = { \
+ .name = "A32KHZWAKECLKSEL", \
+ .addr = 0x730, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x1, \
+ }, \
+ [0x1CD] = { \
+ .name = "A32KHZWAKECLKDIV", \
+ .addr = 0x734, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x1F, \
+ }, \
+ [0x1D8] = { \
+ .name = "SYSTICKFCLKSEL", \
+ .addr = 0x760, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1D9] = { \
+ .name = "SYSTICKFCLKDIV", \
+ .addr = 0x764, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1DC] = { \
+ .name = "DPHYCLKSEL", \
+ .addr = 0x770, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1DD] = { \
+ .name = "DPHYCLKDIV", \
+ .addr = 0x774, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1DE] = { \
+ .name = "DPHYESCCLKSEL", \
+ .addr = 0x778, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1DF] = { \
+ .name = "DPHYESCRXCLKDIV", \
+ .addr = 0x77C, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000010, \
+ }, \
+ [0x1E0] = { \
+ .name = "DPHYESCTXCLKDIV", \
+ .addr = 0x780, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000011, \
+ }, \
+ [0x1E4] = { \
+ .name = "GPUCLKSEL", \
+ .addr = 0x790, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1E5] = { \
+ .name = "GPUCLKDIV", \
+ .addr = 0x794, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1E8] = { \
+ .name = "DCPIXELCLKSEL", \
+ .addr = 0x7A0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1E9] = { \
+ .name = "DCPIXELCLKDIV", \
+ .addr = 0x7A4, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ }
diff --git a/include/hw/arm/svd/rt500_clkctl1.h b/include/hw/arm/svd/rt500_clkctl1.h
new file mode 100644
index 0000000000..baa24eb0fd
--- /dev/null
+++ b/include/hw/arm/svd/rt500_clkctl1.h
@@ -0,0 +1,675 @@
+/*
+ * Copyright 2016-2023 NXP SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Automatically generated by svd-gen-header.py from MIMXRT595S_cm33.xml
+ */
+#pragma once
+
+#include "hw/register.h"
+
+/* Clock Controller 1 */
+#define RT500_CLKCTL1_REGS_NO (526)
+
+/* Clock Control 0 */
+REG32(RT500_CLKCTL1_PSCCTL0, 0x10);
+/* FlexIO clock control */
+FIELD(RT500_CLKCTL1_PSCCTL0, FlexIO, 29, 1);
+
+/* Clock Control 1 */
+REG32(RT500_CLKCTL1_PSCCTL1, 0x14);
+
+/* Clock Control 2 */
+REG32(RT500_CLKCTL1_PSCCTL2, 0x18);
+
+/* Clock Set 0 */
+REG32(RT500_CLKCTL1_PSCCTL0_SET, 0x40);
+/* FlexIO clock control */
+FIELD(RT500_CLKCTL1_PSCCTL0_SET, FlexIO, 29, 1);
+
+/* Clock Set 1 */
+REG32(RT500_CLKCTL1_PSCCTL1_SET, 0x44);
+
+/* Clock Set 2 */
+REG32(RT500_CLKCTL1_PSCCTL2_SET, 0x48);
+
+/* Clock Clear 0 */
+REG32(RT500_CLKCTL1_PSCCTL0_CLR, 0x70);
+/* FlexIO clock control clear */
+FIELD(RT500_CLKCTL1_PSCCTL0_CLR, FlexIO, 29, 1);
+
+/* Clock Clear 1 */
+REG32(RT500_CLKCTL1_PSCCTL1_CLR, 0x74);
+
+/* Clock Clear 2 */
+REG32(RT500_CLKCTL1_PSCCTL2_CLR, 0x78);
+
+/* Audio PLL0 PFD */
+REG32(RT500_CLKCTL1_AUDIOPLL0PFD, 0x218);
+/* PLL Fractional Divider 0 */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD0, 0, 6);
+/* PFD0 Clock Ready Status Flag */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD0_CLKRDY, 6, 1);
+/* PFD0 Clock Gate */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD0_CLKGATE, 7, 1);
+/* PLL Fractional Divider 1 */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD1, 8, 6);
+/* PFD1 Clock Ready Status Flag */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD1_CLKRDY, 14, 1);
+/* PFD1 Clock Gate */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD1_CLKGATE, 15, 1);
+/* PLL Fractional Divider 2 */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD2, 16, 6);
+/* PFD2 Clock Ready Status Flag */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD2_CLKRDY, 22, 1);
+/* PFD2 Clock Gate */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD2_CLKGATE, 23, 1);
+/* PLL Fractional Divider 3 */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD3, 24, 6);
+/* PFD3 Clock Ready Status Flag */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD3_CLKRDY, 30, 1);
+/* PFD3 Clock Gate */
+FIELD(RT500_CLKCTL1_AUDIOPLL0PFD, PFD3_CLKGATE, 31, 1);
+
+/* OS Event Timer Functional Clock Select */
+REG32(RT500_CLKCTL1_OSEVENTTFCLKSEL, 0x480);
+/* OS Event Timer Functional Clock Source */
+FIELD(RT500_CLKCTL1_OSEVENTTFCLKSEL, SEL, 0, 3);
+/* Low Power Oscillator Clock (LPOSC) */
+#define RT500_CLKCTL1_OSEVENTTFCLKSEL_SEL_LPOSC 0
+/* RTC 32 KHz Clock */
+#define RT500_CLKCTL1_OSEVENTTFCLKSEL_SEL_RTC_32KHZ 1
+/* HCLK Free-Running Clock (Global Time Stamping) */
+#define RT500_CLKCTL1_OSEVENTTFCLKSEL_SEL_TEAL 2
+/* None, output gated to reduce power */
+#define RT500_CLKCTL1_OSEVENTTFCLKSEL_SEL_NONE 7
+
+
+#define RT500_CLKCTL1_REGISTER_ACCESS_INFO_ARRAY(_name) \
+ struct RegisterAccessInfo _name[RT500_CLKCTL1_REGS_NO] = { \
+ [0 ... RT500_CLKCTL1_REGS_NO - 1] = { \
+ .name = "", \
+ .addr = -1, \
+ }, \
+ [0x4] = { \
+ .name = "PSCCTL0", \
+ .addr = 0x10, \
+ .ro = 0xD40000FF, \
+ .reset = 0x0, \
+ }, \
+ [0x5] = { \
+ .name = "PSCCTL1", \
+ .addr = 0x14, \
+ .ro = 0x4E7EFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x6] = { \
+ .name = "PSCCTL2", \
+ .addr = 0x18, \
+ .ro = 0x3FFCFA60, \
+ .reset = 0x0, \
+ }, \
+ [0x10] = { \
+ .name = "PSCCTL0_SET", \
+ .addr = 0x40, \
+ .ro = 0xD40000FF, \
+ .reset = 0x0, \
+ }, \
+ [0x11] = { \
+ .name = "PSCCTL1_SET", \
+ .addr = 0x44, \
+ .ro = 0x4E7EFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x12] = { \
+ .name = "PSCCTL2_SET", \
+ .addr = 0x48, \
+ .ro = 0x3FFCFA60, \
+ .reset = 0x0, \
+ }, \
+ [0x1C] = { \
+ .name = "PSCCTL0_CLR", \
+ .addr = 0x70, \
+ .ro = 0xD40000FF, \
+ .reset = 0x0, \
+ }, \
+ [0x1D] = { \
+ .name = "PSCCTL1_CLR", \
+ .addr = 0x74, \
+ .ro = 0x4E7EFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x1E] = { \
+ .name = "PSCCTL2_CLR", \
+ .addr = 0x78, \
+ .ro = 0x3FFCFA60, \
+ .reset = 0x0, \
+ }, \
+ [0x80] = { \
+ .name = "AUDIOPLL0CLKSEL", \
+ .addr = 0x200, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x81] = { \
+ .name = "AUDIOPLL0CTL0", \
+ .addr = 0x204, \
+ .ro = 0xFF00DFFC, \
+ .reset = 0x160002, \
+ }, \
+ [0x83] = { \
+ .name = "AUDIOPLL0LOCKTIMEDIV2", \
+ .addr = 0x20C, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xCAFE, \
+ }, \
+ [0x84] = { \
+ .name = "AUDIOPLL0NUM", \
+ .addr = 0x210, \
+ .ro = 0xC0000000, \
+ .reset = 0x4DD2F15, \
+ }, \
+ [0x85] = { \
+ .name = "AUDIOPLL0DENOM", \
+ .addr = 0x214, \
+ .ro = 0xC0000000, \
+ .reset = 0x1FFFFFDB, \
+ }, \
+ [0x86] = { \
+ .name = "AUDIOPLL0PFD", \
+ .addr = 0x218, \
+ .ro = 0x0, \
+ .reset = 0x80808080, \
+ }, \
+ [0x90] = { \
+ .name = "AUDIOPLLCLKDIV", \
+ .addr = 0x240, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x100] = { \
+ .name = "DSPCPUCLKDIV", \
+ .addr = 0x400, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x10C] = { \
+ .name = "DSPCPUCLKSELA", \
+ .addr = 0x430, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ [0x10D] = { \
+ .name = "DSPCPUCLKSELB", \
+ .addr = 0x434, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ [0x120] = { \
+ .name = "OSEVENTTFCLKSEL", \
+ .addr = 0x480, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x0, \
+ }, \
+ [0x140] = { \
+ .name = "FRG0CLKSEL", \
+ .addr = 0x500, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x141] = { \
+ .name = "FRG0CTL", \
+ .addr = 0x504, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x142] = { \
+ .name = "FC0FCLKSEL", \
+ .addr = 0x508, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x148] = { \
+ .name = "FRG1CLKSEL", \
+ .addr = 0x520, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x149] = { \
+ .name = "FRG1CTL", \
+ .addr = 0x524, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x14A] = { \
+ .name = "FC1FCLKSEL", \
+ .addr = 0x528, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x150] = { \
+ .name = "FRG2CLKSEL", \
+ .addr = 0x540, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x151] = { \
+ .name = "FRG2CTL", \
+ .addr = 0x544, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x152] = { \
+ .name = "FC2FCLKSEL", \
+ .addr = 0x548, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x158] = { \
+ .name = "FRG3CLKSEL", \
+ .addr = 0x560, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x159] = { \
+ .name = "FRG3CTL", \
+ .addr = 0x564, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x15A] = { \
+ .name = "FC3FCLKSEL", \
+ .addr = 0x568, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x160] = { \
+ .name = "FRG4CLKSEL", \
+ .addr = 0x580, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x161] = { \
+ .name = "FRG4CTL", \
+ .addr = 0x584, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x162] = { \
+ .name = "FC4FCLKSEL", \
+ .addr = 0x588, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x168] = { \
+ .name = "FRG5CLKSEL", \
+ .addr = 0x5A0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x169] = { \
+ .name = "FRG5CTL", \
+ .addr = 0x5A4, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x16A] = { \
+ .name = "FC5FCLKSEL", \
+ .addr = 0x5A8, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x170] = { \
+ .name = "FRG6CLKSEL", \
+ .addr = 0x5C0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x171] = { \
+ .name = "FRG6CTL", \
+ .addr = 0x5C4, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x172] = { \
+ .name = "FC6FCLKSEL", \
+ .addr = 0x5C8, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x178] = { \
+ .name = "FRG7CLKSEL", \
+ .addr = 0x5E0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x179] = { \
+ .name = "FRG7CTL", \
+ .addr = 0x5E4, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x17A] = { \
+ .name = "FC7FCLKSEL", \
+ .addr = 0x5E8, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x180] = { \
+ .name = "FRG8CLKSEL", \
+ .addr = 0x600, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x181] = { \
+ .name = "FRG8CTL", \
+ .addr = 0x604, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x182] = { \
+ .name = "FC8FCLKSEL", \
+ .addr = 0x608, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x188] = { \
+ .name = "FRG9CLKSEL", \
+ .addr = 0x620, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x189] = { \
+ .name = "FRG9CTL", \
+ .addr = 0x624, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x18A] = { \
+ .name = "FC9FCLKSEL", \
+ .addr = 0x628, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x190] = { \
+ .name = "FRG10CLKSEL", \
+ .addr = 0x640, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x191] = { \
+ .name = "FRG10CTL", \
+ .addr = 0x644, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x192] = { \
+ .name = "FC10FCLKSEL", \
+ .addr = 0x648, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x198] = { \
+ .name = "FRG11CLKSEL", \
+ .addr = 0x660, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x199] = { \
+ .name = "FRG11CTL", \
+ .addr = 0x664, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x19A] = { \
+ .name = "FC11FCLKSEL", \
+ .addr = 0x668, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1A0] = { \
+ .name = "FRG12CLKSEL", \
+ .addr = 0x680, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1A1] = { \
+ .name = "FRG12CTL", \
+ .addr = 0x684, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x1A2] = { \
+ .name = "FC12FCLKSEL", \
+ .addr = 0x688, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1A8] = { \
+ .name = "FRG13CLKSEL", \
+ .addr = 0x6A0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1A9] = { \
+ .name = "FRG13CTL", \
+ .addr = 0x6A4, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x1AA] = { \
+ .name = "FC13FCLKSEL", \
+ .addr = 0x6A8, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1B0] = { \
+ .name = "FRG14CLKSEL", \
+ .addr = 0x6C0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1B1] = { \
+ .name = "FRG14CTL", \
+ .addr = 0x6C4, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x1B2] = { \
+ .name = "FC14FCLKSEL", \
+ .addr = 0x6C8, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1B8] = { \
+ .name = "FRG15CLKSEL", \
+ .addr = 0x6E0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1B9] = { \
+ .name = "FRG15CTL", \
+ .addr = 0x6E4, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x1BA] = { \
+ .name = "FC15FCLKSEL", \
+ .addr = 0x6E8, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1C0] = { \
+ .name = "FRG16CLKSEL", \
+ .addr = 0x700, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1C1] = { \
+ .name = "FRG16CTL", \
+ .addr = 0x704, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x1C2] = { \
+ .name = "FC16FCLKSEL", \
+ .addr = 0x708, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1C8] = { \
+ .name = "FRG17CLKSEL", \
+ .addr = 0x720, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1C9] = { \
+ .name = "FRG17CTL", \
+ .addr = 0x724, \
+ .ro = 0xFFFF0000, \
+ .reset = 0xFF, \
+ }, \
+ [0x1CA] = { \
+ .name = "FLEXIOCLKSEL", \
+ .addr = 0x728, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1D0] = { \
+ .name = "FLEXIOCLKDIV", \
+ .addr = 0x740, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1D8] = { \
+ .name = "FRGPLLCLKDIV", \
+ .addr = 0x760, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1E0] = { \
+ .name = "DMIC0FCLKSEL", \
+ .addr = 0x780, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1E1] = { \
+ .name = "DMIC0FCLKDIV", \
+ .addr = 0x784, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1E8] = { \
+ .name = "CT32BITFCLKSEL0", \
+ .addr = 0x7A0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1E9] = { \
+ .name = "CT32BITFCLKSEL1", \
+ .addr = 0x7A4, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1EA] = { \
+ .name = "CT32BITFCLKSEL2", \
+ .addr = 0x7A8, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1EB] = { \
+ .name = "CT32BITFCLKSEL3", \
+ .addr = 0x7AC, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1EC] = { \
+ .name = "CT32BITFCLKSEL4", \
+ .addr = 0x7B0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1F0] = { \
+ .name = "AUDIOMCLKSEL", \
+ .addr = 0x7C0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1F1] = { \
+ .name = "AUDIOMCLKDIV", \
+ .addr = 0x7C4, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x1F8] = { \
+ .name = "CLKOUTSEL0", \
+ .addr = 0x7E0, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1F9] = { \
+ .name = "CLKOUTSEL1", \
+ .addr = 0x7E4, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x1FA] = { \
+ .name = "CLKOUTFCLKDIV", \
+ .addr = 0x7E8, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x200] = { \
+ .name = "I3C01FCLKSEL", \
+ .addr = 0x800, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x201] = { \
+ .name = "I3C01FCLKSTCSEL", \
+ .addr = 0x804, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x202] = { \
+ .name = "I3C01FCLKSTCDIV", \
+ .addr = 0x808, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x203] = { \
+ .name = "I3C01FCLKSDIV", \
+ .addr = 0x80C, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x204] = { \
+ .name = "I3C01FCLKDIV", \
+ .addr = 0x810, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ [0x205] = { \
+ .name = "I3C01FCLKSTSTCLKSEL", \
+ .addr = 0x814, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x0, \
+ }, \
+ [0x208] = { \
+ .name = "WDT1FCLKSEL", \
+ .addr = 0x820, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x0, \
+ }, \
+ [0x20C] = { \
+ .name = "ACMP0FCLKSEL", \
+ .addr = 0x830, \
+ .ro = 0xFFFFFFF8, \
+ .reset = 0x7, \
+ }, \
+ [0x20D] = { \
+ .name = "ACMP0FCLKDIV", \
+ .addr = 0x834, \
+ .ro = 0x1FFFFF00, \
+ .reset = 0x40000000, \
+ }, \
+ }
diff --git a/include/hw/misc/rt500_clk_freqs.h b/include/hw/misc/rt500_clk_freqs.h
new file mode 100644
index 0000000000..1e366d4967
--- /dev/null
+++ b/include/hw/misc/rt500_clk_freqs.h
@@ -0,0 +1,18 @@
+/*
+ * QEMU model for RT500 Clock Controller
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_MISC_RT500_CLK_FREQS_H
+#define HW_MISC_RT500_CLK_FREQS_H
+
+#define RTC32KHZ_CLK_HZ 32000
+#define LPOSC_CLK_HZ 1000000
+
+#endif /* HW_MISC_RT500_CLK_FREQS_H */
diff --git a/include/hw/misc/rt500_clkctl0.h b/include/hw/misc/rt500_clkctl0.h
new file mode 100644
index 0000000000..890743a2ce
--- /dev/null
+++ b/include/hw/misc/rt500_clkctl0.h
@@ -0,0 +1,35 @@
+/*
+ * QEMU model for RT500 Clock Controller
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_MISC_RT500_CLKCTL0_H
+#define HW_MISC_RT500_CLKCTL0_H
+
+#include "hw/arm/svd/rt500_clkctl0.h"
+#include "hw/sysbus.h"
+
+#define TYPE_RT500_CLKCTL0 "rt500-clkctl0"
+#define RT500_CLKCTL0(o) OBJECT_CHECK(RT500ClkCtl0State, o, TYPE_RT500_CLKCTL0)
+
+#define SYSTICKFCLKSEL_DIVOUT 0
+#define SYSTICKFCLKSEL_LPOSC 1
+#define SYSTICKFCLKSEL_32KHZRTC 2
+#define SYSTICKFCLKSEL_NONE 7
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ uint32_t regs[RT500_CLKCTL0_REGS_NO];
+ Clock *systick_clk;
+ Clock *sysclk;
+} RT500ClkCtl0State;
+
+#endif /* HW_MISC_RT500_CLKCTL0_H */
diff --git a/include/hw/misc/rt500_clkctl1.h b/include/hw/misc/rt500_clkctl1.h
new file mode 100644
index 0000000000..8b012b1357
--- /dev/null
+++ b/include/hw/misc/rt500_clkctl1.h
@@ -0,0 +1,36 @@
+/*
+ * QEMU model for RT500 Clock Controller
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+
+#ifndef HW_MISC_RT500_CLKCTL1_H
+#define HW_MISC_RT500_CLKCTL1_H
+
+#include "hw/arm/svd/rt500_clkctl1.h"
+#include "hw/sysbus.h"
+
+#define TYPE_RT500_CLKCTL1 "rt500-clkctl1"
+#define RT500_CLKCTL1(o) OBJECT_CHECK(RT500ClkCtl1State, o, TYPE_RT500_CLKCTL1)
+
+#define OSEVENTTFCLKSEL_LPOSC 0
+#define OSEVENTTFCLKSEL_32KHZRTC 1
+#define OSEVENTTFCLKSEL_HCLK 2
+#define OSEVENTTFCLKSEL_NONE 7
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ uint32_t regs[RT500_CLKCTL1_REGS_NO];
+ Clock *sysclk;
+ Clock *ostimer_clk;
+} RT500ClkCtl1State;
+
+#endif /* HW_MISC_RT500_CLKCTL1_H */
diff --git a/hw/misc/rt500_clkctl0.c b/hw/misc/rt500_clkctl0.c
new file mode 100644
index 0000000000..7e7b176719
--- /dev/null
+++ b/hw/misc/rt500_clkctl0.c
@@ -0,0 +1,253 @@
+/*
+ * QEMU model for RT500 Clock Controller
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/clock.h"
+#include "hw/irq.h"
+#include "hw/qdev-clock.h"
+#include "hw/qdev-properties.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/address-spaces.h"
+#include "migration/vmstate.h"
+#include "hw/misc/rt500_clkctl0.h"
+#include "hw/misc/rt500_clk_freqs.h"
+
+#include "trace.h"
+
+#define REG(s, reg) (s->regs[R_RT500_CLKCTL0_##reg])
+#define RF_RD(s, reg, field) \
+ ARRAY_FIELD_EX32(s->regs, RT500_CLKCTL0_##reg, field)
+#define RF_WR(s, reg, field, val) \
+ ARRAY_FIELD_DP32(s->regs, RT500_CLKCTL0_##reg, field, val)
+
+static const RT500_CLKCTL0_REGISTER_ACCESS_INFO_ARRAY(reg_info);
+
+static MemTxResult rt500_clkctl0_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ RT500ClkCtl0State *s = opaque;
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+
+ switch (addr) {
+ case A_RT500_CLKCTL0_PSCCTL0_SET:
+ case A_RT500_CLKCTL0_PSCCTL1_SET:
+ case A_RT500_CLKCTL0_PSCCTL2_SET:
+ case A_RT500_CLKCTL0_PSCCTL0_CLR:
+ case A_RT500_CLKCTL0_PSCCTL1_CLR:
+ case A_RT500_CLKCTL0_PSCCTL2_CLR:
+ /* write only registers */
+ return MEMTX_ERROR;
+ default:
+ *data = s->regs[addr / 4];
+ break;
+ }
+
+ trace_rt500_clkctl0_reg_read(rai->name, addr, *data);
+ return MEMTX_OK;
+}
+
+static inline void set_systick_clk_from_div(RT500ClkCtl0State *s)
+{
+ uint32_t div = RF_RD(s, SYSTICKFCLKDIV, DIV) + 1;
+ uint32_t rate = clock_get_hz(s->sysclk);
+
+ clock_set_hz(s->systick_clk, rate / div);
+}
+
+static MemTxResult rt500_clkctl0_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ RT500ClkCtl0State *s = opaque;
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+ struct RegisterInfo ri = {
+ .data = &s->regs[addr / 4],
+ .data_size = 4,
+ .access = rai,
+ };
+
+ trace_rt500_clkctl0_reg_write(rai->name, addr, value);
+
+ switch (addr) {
+ case A_RT500_CLKCTL0_PSCCTL0:
+ case A_RT500_CLKCTL0_PSCCTL1:
+ case A_RT500_CLKCTL0_PSCCTL2:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ break;
+ }
+ case A_RT500_CLKCTL0_PSCCTL0_SET:
+ case A_RT500_CLKCTL0_PSCCTL1_SET:
+ case A_RT500_CLKCTL0_PSCCTL2_SET:
+ {
+ uint32_t tmp;
+
+ tmp = A_RT500_CLKCTL0_PSCCTL0 + (addr - A_RT500_CLKCTL0_PSCCTL0_SET);
+ s->regs[tmp / 4] |= value;
+ break;
+ }
+ case A_RT500_CLKCTL0_PSCCTL0_CLR:
+ case A_RT500_CLKCTL0_PSCCTL1_CLR:
+ case A_RT500_CLKCTL0_PSCCTL2_CLR:
+ {
+ uint32_t tmp;
+
+ tmp = A_RT500_CLKCTL0_PSCCTL0 + (addr - A_RT500_CLKCTL0_PSCCTL0_CLR);
+ s->regs[tmp / 4] &= ~value;
+ break;
+ }
+ default:
+ register_write(&ri, value, ~0, NULL, false);
+ }
+
+ switch (addr) {
+ case A_RT500_CLKCTL0_SYSPLL0PFD:
+ {
+ if (!RF_RD(s, SYSPLL0PFD, PFD0_CLKGATE)) {
+ RF_WR(s, SYSPLL0PFD, PFD0_CLKRDY, 1);
+ } else {
+ RF_WR(s, SYSPLL0PFD, PFD0_CLKRDY, 0);
+ }
+ if (!RF_RD(s, SYSPLL0PFD, PFD1_CLKGATE)) {
+ RF_WR(s, SYSPLL0PFD, PFD1_CLKRDY, 1);
+ } else {
+ RF_WR(s, SYSPLL0PFD, PFD1_CLKRDY, 0);
+ }
+ if (!RF_RD(s, SYSPLL0PFD, PFD2_CLKGATE)) {
+ RF_WR(s, SYSPLL0PFD, PFD2_CLKRDY, 1);
+ } else {
+ RF_WR(s, SYSPLL0PFD, PFD2_CLKRDY, 0);
+ }
+ if (!RF_RD(s, SYSPLL0PFD, PFD3_CLKGATE)) {
+ RF_WR(s, SYSPLL0PFD, PFD3_CLKRDY, 1);
+ } else {
+ RF_WR(s, SYSPLL0PFD, PFD3_CLKRDY, 0);
+ }
+ break;
+ }
+ case A_RT500_CLKCTL0_SYSTICKFCLKSEL:
+ {
+ switch (RF_RD(s, SYSTICKFCLKSEL, SEL)) {
+ case SYSTICKFCLKSEL_DIVOUT:
+ {
+ set_systick_clk_from_div(s);
+ break;
+ }
+ case SYSTICKFCLKSEL_LPOSC:
+ {
+ clock_set_hz(s->systick_clk, LPOSC_CLK_HZ);
+ break;
+ }
+ case SYSTICKFCLKSEL_32KHZRTC:
+ {
+ clock_set_hz(s->systick_clk, RTC32KHZ_CLK_HZ);
+ break;
+ }
+ case SYSTICKFCLKSEL_NONE:
+ {
+ clock_set_hz(s->systick_clk, 0);
+ break;
+ }
+ }
+ clock_propagate(s->systick_clk);
+ break;
+ }
+ case A_RT500_CLKCTL0_SYSTICKFCLKDIV:
+ {
+ if (RF_RD(s, SYSTICKFCLKSEL, SEL) == SYSTICKFCLKSEL_DIVOUT) {
+ set_systick_clk_from_div(s);
+ clock_propagate(s->systick_clk);
+ }
+ break;
+ }
+ }
+
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps rt500_clkctl0_ops = {
+ .read_with_attrs = rt500_clkctl0_read,
+ .write_with_attrs = rt500_clkctl0_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void rt500_clkctl0_reset_enter(Object *obj, ResetType type)
+{
+ RT500ClkCtl0State *s = RT500_CLKCTL0(obj);
+
+ for (int i = 0; i < RT500_CLKCTL0_REGS_NO; i++) {
+ hwaddr addr = reg_info[i].addr;
+
+ if (addr != -1) {
+ struct RegisterInfo ri = {
+ .data = &s->regs[addr / 4],
+ .data_size = 4,
+ .access = ®_info[i],
+ };
+
+ register_reset(&ri);
+ }
+ }
+
+ /* clock OK immediately after reset */
+ REG(s, FROCLKSTATUS) = 0x00000001;
+}
+
+static void rt500_clkctl0_init(Object *obj)
+{
+ RT500ClkCtl0State *s = RT500_CLKCTL0(obj);
+
+ memory_region_init_io(&s->mmio, obj, &rt500_clkctl0_ops, s,
+ TYPE_RT500_CLKCTL0, sizeof(s->regs));
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+ s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
+ s->systick_clk = qdev_init_clock_out(DEVICE(s), "systick_clk");
+}
+
+static const VMStateDescription vmstate_rt500_clkctl0 = {
+ .name = "rt500-clkctl0",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, RT500ClkCtl0State, RT500_CLKCTL0_REGS_NO),
+ VMSTATE_CLOCK(systick_clk, RT500ClkCtl0State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rt500_clkctl0_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.enter = rt500_clkctl0_reset_enter;
+ dc->vmsd = &vmstate_rt500_clkctl0;
+}
+
+static const TypeInfo rt500_clkctl0_types[] = {
+ {
+ .name = TYPE_RT500_CLKCTL0,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RT500ClkCtl0State),
+ .instance_init = rt500_clkctl0_init,
+ .class_init = rt500_clkctl0_class_init,
+ },
+};
+
+DEFINE_TYPES(rt500_clkctl0_types);
+
diff --git a/hw/misc/rt500_clkctl1.c b/hw/misc/rt500_clkctl1.c
new file mode 100644
index 0000000000..ed234ce0f6
--- /dev/null
+++ b/hw/misc/rt500_clkctl1.c
@@ -0,0 +1,238 @@
+/*
+ * QEMU model for RT500 Clock Controller
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/clock.h"
+#include "hw/irq.h"
+#include "hw/qdev-clock.h"
+#include "hw/qdev-properties.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/address-spaces.h"
+#include "migration/vmstate.h"
+#include "hw/misc/rt500_clkctl1.h"
+#include "hw/misc/rt500_clk_freqs.h"
+
+#include "trace.h"
+
+#define REG(s, reg) (s->regs[R_RT500_CLKCTL1_##reg])
+#define RF_RD(s, reg, field) \
+ ARRAY_FIELD_EX32(s->regs, RT500_CLKCTL1_##reg, field)
+#define RF_WR(s, reg, field, val) \
+ ARRAY_FIELD_DP32(s->regs, RT500_CLKCTL1_##reg, field, val)
+
+static RT500_CLKCTL1_REGISTER_ACCESS_INFO_ARRAY(reg_info);
+
+static MemTxResult rt500_clkctl1_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ RT500ClkCtl1State *s = opaque;
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+ MemTxResult ret = MEMTX_OK;
+
+ switch (addr) {
+ case A_RT500_CLKCTL1_PSCCTL0_SET:
+ case A_RT500_CLKCTL1_PSCCTL1_SET:
+ case A_RT500_CLKCTL1_PSCCTL2_SET:
+ case A_RT500_CLKCTL1_PSCCTL0_CLR:
+ case A_RT500_CLKCTL1_PSCCTL1_CLR:
+ case A_RT500_CLKCTL1_PSCCTL2_CLR:
+ /* write only registers */
+ ret = MEMTX_ERROR;
+ break;
+ default:
+ *data = s->regs[addr / 4];
+ break;
+ }
+
+ trace_rt500_clkctl1_reg_read(rai->name, addr, *data);
+ return ret;
+}
+
+static MemTxResult rt500_clkctl1_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ RT500ClkCtl1State *s = opaque;
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+ struct RegisterInfo ri = {
+ .data = &s->regs[addr / 4],
+ .data_size = 4,
+ .access = rai,
+ };
+
+ trace_rt500_clkctl1_reg_write(rai->name, addr, value);
+
+ switch (addr) {
+ case A_RT500_CLKCTL1_PSCCTL0:
+ case A_RT500_CLKCTL1_PSCCTL1:
+ case A_RT500_CLKCTL1_PSCCTL2:
+ {
+ s->regs[addr / 4] = value | s->regs[addr / 4];
+ break;
+ }
+ case A_RT500_CLKCTL1_PSCCTL0_SET:
+ case A_RT500_CLKCTL1_PSCCTL1_SET:
+ case A_RT500_CLKCTL1_PSCCTL2_SET:
+ {
+ uint32_t tmp;
+
+ tmp = A_RT500_CLKCTL1_PSCCTL0 + (addr - A_RT500_CLKCTL1_PSCCTL0_SET);
+ s->regs[tmp / 4] |= value;
+ break;
+ }
+ case A_RT500_CLKCTL1_PSCCTL0_CLR:
+ case A_RT500_CLKCTL1_PSCCTL1_CLR:
+ case A_RT500_CLKCTL1_PSCCTL2_CLR:
+ {
+ uint32_t tmp;
+
+ tmp = A_RT500_CLKCTL1_PSCCTL0 + (addr - A_RT500_CLKCTL1_PSCCTL0_CLR);
+ s->regs[tmp / 4] &= ~value;
+ break;
+ }
+ default:
+ register_write(&ri, value, ~0, NULL, false);
+ }
+
+ switch (addr) {
+ case A_RT500_CLKCTL1_AUDIOPLL0PFD:
+ {
+ if (!RF_RD(s, AUDIOPLL0PFD, PFD0_CLKGATE)) {
+ RF_WR(s, AUDIOPLL0PFD, PFD0_CLKRDY, 1);
+ } else {
+ RF_WR(s, AUDIOPLL0PFD, PFD0_CLKRDY, 0);
+ }
+ if (!RF_RD(s, AUDIOPLL0PFD, PFD1_CLKGATE)) {
+ RF_WR(s, AUDIOPLL0PFD, PFD1_CLKRDY, 1);
+ } else {
+ RF_WR(s, AUDIOPLL0PFD, PFD1_CLKRDY, 0);
+ }
+ if (!RF_RD(s, AUDIOPLL0PFD, PFD2_CLKGATE)) {
+ RF_WR(s, AUDIOPLL0PFD, PFD2_CLKRDY, 1);
+ } else {
+ RF_WR(s, AUDIOPLL0PFD, PFD2_CLKRDY, 0);
+ }
+ if (!RF_RD(s, AUDIOPLL0PFD, PFD3_CLKGATE)) {
+ RF_WR(s, AUDIOPLL0PFD, PFD3_CLKRDY, 1);
+ } else {
+ RF_WR(s, AUDIOPLL0PFD, PFD3_CLKRDY, 0);
+ }
+ break;
+ }
+ case A_RT500_CLKCTL1_OSEVENTTFCLKSEL:
+ {
+ switch (RF_RD(s, OSEVENTTFCLKSEL, SEL)) {
+ case OSEVENTTFCLKSEL_LPOSC:
+ {
+ clock_set_hz(s->ostimer_clk, LPOSC_CLK_HZ);
+ break;
+ }
+ case OSEVENTTFCLKSEL_32KHZRTC:
+ {
+ clock_set_hz(s->ostimer_clk, RTC32KHZ_CLK_HZ);
+ break;
+ }
+ case OSEVENTTFCLKSEL_HCLK:
+ {
+ clock_set_hz(s->ostimer_clk, clock_get_hz(s->sysclk));
+ break;
+ }
+ case OSEVENTTFCLKSEL_NONE:
+ {
+ clock_set_hz(s->ostimer_clk, 0);
+ break;
+ }
+ }
+
+ clock_propagate(s->ostimer_clk);
+ break;
+ }
+ }
+
+ return MEMTX_OK;
+}
+
+
+static const MemoryRegionOps rt500_clkctl1_ops = {
+ .read_with_attrs = rt500_clkctl1_read,
+ .write_with_attrs = rt500_clkctl1_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void rt500_clkctl1_reset(Object *obj, ResetType type)
+{
+ RT500ClkCtl1State *s = RT500_CLKCTL1(obj);
+
+ for (int i = 0; i < RT500_CLKCTL1_REGS_NO; i++) {
+ hwaddr addr = reg_info[i].addr;
+
+ if (addr != -1) {
+ struct RegisterInfo ri = {
+ .data = &s->regs[addr / 4],
+ .data_size = 4,
+ .access = ®_info[i],
+ };
+
+ register_reset(&ri);
+ }
+ }
+}
+
+static void rt500_clkctl1_init(Object *obj)
+{
+ RT500ClkCtl1State *s = RT500_CLKCTL1(obj);
+
+ memory_region_init_io(&s->mmio, obj, &rt500_clkctl1_ops, s,
+ TYPE_RT500_CLKCTL1, sizeof(s->regs));
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+ s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
+ s->ostimer_clk = qdev_init_clock_out(DEVICE(s), "ostimer_clk");
+}
+
+static const VMStateDescription vmstate_rt500_clkctl1 = {
+ .name = "rt500-clkctl1",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, RT500ClkCtl1State, RT500_CLKCTL1_REGS_NO),
+ VMSTATE_CLOCK(ostimer_clk, RT500ClkCtl1State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rt500_clkctl1_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ rc->phases.enter = rt500_clkctl1_reset;
+ dc->vmsd = &vmstate_rt500_clkctl1;
+
+}
+
+static const TypeInfo rt500_clkctl1_types[] = {
+ {
+ .name = TYPE_RT500_CLKCTL1,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RT500ClkCtl1State),
+ .instance_init = rt500_clkctl1_init,
+ .class_init = rt500_clkctl1_class_init,
+ }
+};
+
+DEFINE_TYPES(rt500_clkctl1_types);
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index eac5070514..658af0dace 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -629,3 +629,8 @@ config ARMSSE
select UNIMP
select SSE_COUNTER
select SSE_TIMER
+
+config RT500
+ bool
+ select FLEXCOMM
+ select RT500_CLKCTL
diff --git a/hw/arm/svd/meson.build b/hw/arm/svd/meson.build
index e7d62a7f35..9c458b314c 100644
--- a/hw/arm/svd/meson.build
+++ b/hw/arm/svd/meson.build
@@ -16,4 +16,12 @@ if get_option('mcux-soc-svd')
[ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/flexcomm_spi.h',
'-p', 'SPI0', '-t', 'FLEXCOMM_SPI',
'--fields', 'CFG FIFOCFG:ENABLE*,EMPTY* FIFO*:* INT*: STAT'])
+ run_target('svd-rt500-clkctl0', command: svd_gen_header +
+ [ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/rt500_clkctl0.h',
+ '-p', 'CLKCTL0', '-t', 'RT500_CLKCTL0',
+ '--fields', 'PSCCTL*:ROM_CTRLR_CLK,DSP_CLK SYSPLL0PFD SYSTICKFCLKSEL SYSTICKFCLKDIV FROCLKSTATUS:'])
+ run_target('svd-rt500-clkctl1', command: svd_gen_header +
+ [ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/rt500_clkctl1.h',
+ '-p', 'CLKCTL1', '-t', 'RT500_CLKCTL1',
+ '--fields', 'PSCCTL*:FlexIO AUDIOPLL0PFD OSEVENTTFCLKSEL'])
endif
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index b373e651e1..02feb93840 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -218,4 +218,7 @@ config FLEXCOMM
select I2C
select SSI
+config RT500_CLKCTL
+ bool
+
source macio/Kconfig
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 0d901b9c65..15f9153be4 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -153,3 +153,4 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_FLEXCOMM', if_true: files('flexcomm.c'))
+system_ss.add(when: 'CONFIG_RT500_CLKCTL', if_true: files('rt500_clkctl0.c', 'rt500_clkctl1.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index dc245905dc..b19393dd36 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -368,3 +368,11 @@ flexcomm_irq(const char *id, uint8_t irq) "%s %d"
flexcomm_reg_read(const char *devname, const char *regname, uint32_t addr, uint32_t val) "%s: %s[0x%04x] -> 0x%08x"
flexcomm_reg_write(const char *dename, const char *regname, uint32_t addr, uint32_t val) "%s: %s[0x%04x] <- 0x%08x"
flexcomm_fifostat(const char *id, uint32_t fifostat, uint32_t fifoinstat) "%s: %08x %08x"
+
+# rt500_clkctl0.c
+rt500_clkctl0_reg_read(const char *regname, uint32_t addr, uint32_t val) "%s[0x%04x] -> 0x%08x"
+rt500_clkctl0_reg_write(const char *regname, uint32_t addr, uint32_t val) "%s[0x%04x] <- 0x%08x"
+
+# rt500_clkctl1.c
+rt500_clkctl1_reg_read(const char *regname, uint32_t addr, uint32_t val) "%s[0x%04x] -> 0x%08x"
+rt500_clkctl1_reg_write(const char *regname, uint32_t addr, uint32_t val) "%s[0x%04x] <- 0x%08x"
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 11/25] hw/ssi: add support for flexspi
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (9 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 10/25] hw/misc: add support for RT500's clock controller Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 12/25] hw/misc: add support for RT500's reset controller Octavian Purdila
` (13 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
This is mostly a stub which completes SPI transactions as noops
by masking out the error interrupts and never clearing the IPCMDDONE
interrupt.
Although incomplete, this allows software that uses NXP's mcuxpresso
SDK to run the SDK board initialization functions.
It also supports AHB memory access, aka XIP, for now as simple RAM
memory regions.
The patch includes an automatically generated header which contains
the register layout and helpers.
The header can be regenerated with the svd-flexspi target when the
build is configured with --enable-mcux-soc-svd.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/arm/svd/flexspi.h | 1085 ++++++++++++++++++++++++++++++++++
include/hw/ssi/flexspi.h | 31 +
hw/ssi/flexspi.c | 181 ++++++
hw/arm/svd/meson.build | 4 +
hw/ssi/Kconfig | 4 +
hw/ssi/meson.build | 1 +
hw/ssi/trace-events | 4 +
7 files changed, 1310 insertions(+)
create mode 100644 include/hw/arm/svd/flexspi.h
create mode 100644 include/hw/ssi/flexspi.h
create mode 100644 hw/ssi/flexspi.c
diff --git a/include/hw/arm/svd/flexspi.h b/include/hw/arm/svd/flexspi.h
new file mode 100644
index 0000000000..6e7e715d51
--- /dev/null
+++ b/include/hw/arm/svd/flexspi.h
@@ -0,0 +1,1085 @@
+/*
+ * Copyright 2016-2023 NXP SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Automatically generated by svd-gen-header.py from MIMXRT595S_cm33.xml
+ */
+#pragma once
+
+#include "hw/register.h"
+
+/* FlexSPI */
+#define FLEXSPI_REGS_NO (267)
+
+/* Module Control Register 0 */
+REG32(FLEXSPI_MCR0, 0x0);
+/* Software Reset */
+FIELD(FLEXSPI_MCR0, SWRESET, 0, 1);
+
+/* Interrupt Register */
+REG32(FLEXSPI_INTR, 0x14);
+/*
+ * IP triggered Command Sequences Execution finished interrupt. This interrupt
+ * is also generated when there is IPCMDGE or IPCMDERR interrupt generated.
+ */
+FIELD(FLEXSPI_INTR, IPCMDDONE, 0, 1);
+
+/* Status Register 0 */
+REG32(FLEXSPI_STS0, 0xE0);
+/*
+ * This status bit indicates the state machine in SEQ_CTL is idle and there is
+ * command sequence executing on FlexSPI interface.
+ */
+FIELD(FLEXSPI_STS0, SEQIDLE, 0, 1);
+
+
+#define FLEXSPI_REGISTER_ACCESS_INFO_ARRAY(_name) \
+ struct RegisterAccessInfo _name[FLEXSPI_REGS_NO] = { \
+ [0 ... FLEXSPI_REGS_NO - 1] = { \
+ .name = "", \
+ .addr = -1, \
+ }, \
+ [0x0] = { \
+ .name = "MCR0", \
+ .addr = 0x0, \
+ .ro = 0x20CC, \
+ .reset = 0xFFFF80C2, \
+ }, \
+ [0x1] = { \
+ .name = "MCR1", \
+ .addr = 0x4, \
+ .ro = 0x0, \
+ .reset = 0xFFFFFFFF, \
+ }, \
+ [0x2] = { \
+ .name = "MCR2", \
+ .addr = 0x8, \
+ .ro = 0xFF37FF, \
+ .reset = 0x200081F7, \
+ }, \
+ [0x3] = { \
+ .name = "AHBCR", \
+ .addr = 0xC, \
+ .ro = 0xFFFFFB02, \
+ .reset = 0x18, \
+ }, \
+ [0x4] = { \
+ .name = "INTEN", \
+ .addr = 0x10, \
+ .ro = 0xFFFFC000, \
+ .reset = 0x0, \
+ }, \
+ [0x5] = { \
+ .name = "INTR", \
+ .addr = 0x14, \
+ .ro = 0xFFFFE000, \
+ .reset = 0x0, \
+ }, \
+ [0x6] = { \
+ .name = "LUTKEY", \
+ .addr = 0x18, \
+ .ro = 0x0, \
+ .reset = 0x5AF05AF0, \
+ }, \
+ [0x7] = { \
+ .name = "LUTCR", \
+ .addr = 0x1C, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x2, \
+ }, \
+ [0x8] = { \
+ .name = "AHBRXBUF0CR0", \
+ .addr = 0x20, \
+ .ro = 0x78F0FF00, \
+ .reset = 0x80000010, \
+ }, \
+ [0x9] = { \
+ .name = "AHBRXBUF1CR0", \
+ .addr = 0x24, \
+ .ro = 0x78F0FF00, \
+ .reset = 0x80010010, \
+ }, \
+ [0xA] = { \
+ .name = "AHBRXBUF2CR0", \
+ .addr = 0x28, \
+ .ro = 0x78F0FF00, \
+ .reset = 0x80020010, \
+ }, \
+ [0xB] = { \
+ .name = "AHBRXBUF3CR0", \
+ .addr = 0x2C, \
+ .ro = 0x78F0FF00, \
+ .reset = 0x80030010, \
+ }, \
+ [0xC] = { \
+ .name = "AHBRXBUF4CR0", \
+ .addr = 0x30, \
+ .ro = 0x78F0FF00, \
+ .reset = 0x80040010, \
+ }, \
+ [0xD] = { \
+ .name = "AHBRXBUF5CR0", \
+ .addr = 0x34, \
+ .ro = 0x78F0FF00, \
+ .reset = 0x80050010, \
+ }, \
+ [0xE] = { \
+ .name = "AHBRXBUF6CR0", \
+ .addr = 0x38, \
+ .ro = 0x78F0FF00, \
+ .reset = 0x80060010, \
+ }, \
+ [0xF] = { \
+ .name = "AHBRXBUF7CR0", \
+ .addr = 0x3C, \
+ .ro = 0x78F0FF00, \
+ .reset = 0x80070010, \
+ }, \
+ [0x18] = { \
+ .name = "FLSHA1CR0", \
+ .addr = 0x60, \
+ .ro = 0xFF800000, \
+ .reset = 0x10000, \
+ }, \
+ [0x19] = { \
+ .name = "FLSHA2CR0", \
+ .addr = 0x64, \
+ .ro = 0xFF800000, \
+ .reset = 0x10000, \
+ }, \
+ [0x1A] = { \
+ .name = "FLSHB1CR0", \
+ .addr = 0x68, \
+ .ro = 0xFF800000, \
+ .reset = 0x10000, \
+ }, \
+ [0x1B] = { \
+ .name = "FLSHB2CR0", \
+ .addr = 0x6C, \
+ .ro = 0xFF800000, \
+ .reset = 0x10000, \
+ }, \
+ [0x1C] = { \
+ .name = "FLSHCR1A1", \
+ .addr = 0x70, \
+ .ro = 0x0, \
+ .reset = 0x63, \
+ }, \
+ [0x1D] = { \
+ .name = "FLSHCR1A2", \
+ .addr = 0x74, \
+ .ro = 0x0, \
+ .reset = 0x63, \
+ }, \
+ [0x1E] = { \
+ .name = "FLSHCR1B1", \
+ .addr = 0x78, \
+ .ro = 0x0, \
+ .reset = 0x63, \
+ }, \
+ [0x1F] = { \
+ .name = "FLSHCR1B2", \
+ .addr = 0x7C, \
+ .ro = 0x0, \
+ .reset = 0x63, \
+ }, \
+ [0x20] = { \
+ .name = "FLSHCR2A1", \
+ .addr = 0x80, \
+ .ro = 0x1010, \
+ .reset = 0x0, \
+ }, \
+ [0x21] = { \
+ .name = "FLSHCR2A2", \
+ .addr = 0x84, \
+ .ro = 0x1010, \
+ .reset = 0x0, \
+ }, \
+ [0x22] = { \
+ .name = "FLSHCR2B1", \
+ .addr = 0x88, \
+ .ro = 0x1010, \
+ .reset = 0x0, \
+ }, \
+ [0x23] = { \
+ .name = "FLSHCR2B2", \
+ .addr = 0x8C, \
+ .ro = 0x1010, \
+ .reset = 0x0, \
+ }, \
+ [0x25] = { \
+ .name = "FLSHCR4", \
+ .addr = 0x94, \
+ .ro = 0xFFFFFFFA, \
+ .reset = 0x0, \
+ }, \
+ [0x28] = { \
+ .name = "IPCR0", \
+ .addr = 0xA0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x29] = { \
+ .name = "IPCR1", \
+ .addr = 0xA4, \
+ .ro = 0x78F00000, \
+ .reset = 0x0, \
+ }, \
+ [0x2C] = { \
+ .name = "IPCMD", \
+ .addr = 0xB0, \
+ .ro = 0xFFFFFFFE, \
+ .reset = 0x0, \
+ }, \
+ [0x2D] = { \
+ .name = "DLPR", \
+ .addr = 0xB4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x2E] = { \
+ .name = "IPRXFCR", \
+ .addr = 0xB8, \
+ .ro = 0xFFFFFE00, \
+ .reset = 0x0, \
+ }, \
+ [0x2F] = { \
+ .name = "IPTXFCR", \
+ .addr = 0xBC, \
+ .ro = 0xFFFFFE00, \
+ .reset = 0x0, \
+ }, \
+ [0x30] = { \
+ .name = "DLLCRA", \
+ .addr = 0xC0, \
+ .ro = 0xFFFF8084, \
+ .reset = 0x100, \
+ }, \
+ [0x31] = { \
+ .name = "DLLCRB", \
+ .addr = 0xC4, \
+ .ro = 0xFFFF8084, \
+ .reset = 0x100, \
+ }, \
+ [0x38] = { \
+ .name = "STS0", \
+ .addr = 0xE0, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x2, \
+ }, \
+ [0x39] = { \
+ .name = "STS1", \
+ .addr = 0xE4, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x3A] = { \
+ .name = "STS2", \
+ .addr = 0xE8, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x1000100, \
+ }, \
+ [0x3B] = { \
+ .name = "AHBSPNDSTS", \
+ .addr = 0xEC, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x3C] = { \
+ .name = "IPRXFSTS", \
+ .addr = 0xF0, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x3D] = { \
+ .name = "IPTXFSTS", \
+ .addr = 0xF4, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x40] = { \
+ .name = "RFDR0", \
+ .addr = 0x100, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x41] = { \
+ .name = "RFDR1", \
+ .addr = 0x104, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x42] = { \
+ .name = "RFDR2", \
+ .addr = 0x108, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x43] = { \
+ .name = "RFDR3", \
+ .addr = 0x10C, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x44] = { \
+ .name = "RFDR4", \
+ .addr = 0x110, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x45] = { \
+ .name = "RFDR5", \
+ .addr = 0x114, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x46] = { \
+ .name = "RFDR6", \
+ .addr = 0x118, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x47] = { \
+ .name = "RFDR7", \
+ .addr = 0x11C, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x48] = { \
+ .name = "RFDR8", \
+ .addr = 0x120, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x49] = { \
+ .name = "RFDR9", \
+ .addr = 0x124, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x4A] = { \
+ .name = "RFDR10", \
+ .addr = 0x128, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x4B] = { \
+ .name = "RFDR11", \
+ .addr = 0x12C, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x4C] = { \
+ .name = "RFDR12", \
+ .addr = 0x130, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x4D] = { \
+ .name = "RFDR13", \
+ .addr = 0x134, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x4E] = { \
+ .name = "RFDR14", \
+ .addr = 0x138, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x4F] = { \
+ .name = "RFDR15", \
+ .addr = 0x13C, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x50] = { \
+ .name = "RFDR16", \
+ .addr = 0x140, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x51] = { \
+ .name = "RFDR17", \
+ .addr = 0x144, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x52] = { \
+ .name = "RFDR18", \
+ .addr = 0x148, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x53] = { \
+ .name = "RFDR19", \
+ .addr = 0x14C, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x54] = { \
+ .name = "RFDR20", \
+ .addr = 0x150, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x55] = { \
+ .name = "RFDR21", \
+ .addr = 0x154, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x56] = { \
+ .name = "RFDR22", \
+ .addr = 0x158, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x57] = { \
+ .name = "RFDR23", \
+ .addr = 0x15C, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x58] = { \
+ .name = "RFDR24", \
+ .addr = 0x160, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x59] = { \
+ .name = "RFDR25", \
+ .addr = 0x164, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x5A] = { \
+ .name = "RFDR26", \
+ .addr = 0x168, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x5B] = { \
+ .name = "RFDR27", \
+ .addr = 0x16C, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x5C] = { \
+ .name = "RFDR28", \
+ .addr = 0x170, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x5D] = { \
+ .name = "RFDR29", \
+ .addr = 0x174, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x5E] = { \
+ .name = "RFDR30", \
+ .addr = 0x178, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x5F] = { \
+ .name = "RFDR31", \
+ .addr = 0x17C, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x60] = { \
+ .name = "TFDR0", \
+ .addr = 0x180, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x61] = { \
+ .name = "TFDR1", \
+ .addr = 0x184, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x62] = { \
+ .name = "TFDR2", \
+ .addr = 0x188, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x63] = { \
+ .name = "TFDR3", \
+ .addr = 0x18C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x64] = { \
+ .name = "TFDR4", \
+ .addr = 0x190, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x65] = { \
+ .name = "TFDR5", \
+ .addr = 0x194, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x66] = { \
+ .name = "TFDR6", \
+ .addr = 0x198, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x67] = { \
+ .name = "TFDR7", \
+ .addr = 0x19C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x68] = { \
+ .name = "TFDR8", \
+ .addr = 0x1A0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x69] = { \
+ .name = "TFDR9", \
+ .addr = 0x1A4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x6A] = { \
+ .name = "TFDR10", \
+ .addr = 0x1A8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x6B] = { \
+ .name = "TFDR11", \
+ .addr = 0x1AC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x6C] = { \
+ .name = "TFDR12", \
+ .addr = 0x1B0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x6D] = { \
+ .name = "TFDR13", \
+ .addr = 0x1B4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x6E] = { \
+ .name = "TFDR14", \
+ .addr = 0x1B8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x6F] = { \
+ .name = "TFDR15", \
+ .addr = 0x1BC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x70] = { \
+ .name = "TFDR16", \
+ .addr = 0x1C0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x71] = { \
+ .name = "TFDR17", \
+ .addr = 0x1C4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x72] = { \
+ .name = "TFDR18", \
+ .addr = 0x1C8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x73] = { \
+ .name = "TFDR19", \
+ .addr = 0x1CC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x74] = { \
+ .name = "TFDR20", \
+ .addr = 0x1D0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x75] = { \
+ .name = "TFDR21", \
+ .addr = 0x1D4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x76] = { \
+ .name = "TFDR22", \
+ .addr = 0x1D8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x77] = { \
+ .name = "TFDR23", \
+ .addr = 0x1DC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x78] = { \
+ .name = "TFDR24", \
+ .addr = 0x1E0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x79] = { \
+ .name = "TFDR25", \
+ .addr = 0x1E4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x7A] = { \
+ .name = "TFDR26", \
+ .addr = 0x1E8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x7B] = { \
+ .name = "TFDR27", \
+ .addr = 0x1EC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x7C] = { \
+ .name = "TFDR28", \
+ .addr = 0x1F0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x7D] = { \
+ .name = "TFDR29", \
+ .addr = 0x1F4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x7E] = { \
+ .name = "TFDR30", \
+ .addr = 0x1F8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x7F] = { \
+ .name = "TFDR31", \
+ .addr = 0x1FC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x80] = { \
+ .name = "LUT0", \
+ .addr = 0x200, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x81] = { \
+ .name = "LUT1", \
+ .addr = 0x204, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x82] = { \
+ .name = "LUT2", \
+ .addr = 0x208, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x83] = { \
+ .name = "LUT3", \
+ .addr = 0x20C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x84] = { \
+ .name = "LUT4", \
+ .addr = 0x210, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x85] = { \
+ .name = "LUT5", \
+ .addr = 0x214, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x86] = { \
+ .name = "LUT6", \
+ .addr = 0x218, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x87] = { \
+ .name = "LUT7", \
+ .addr = 0x21C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x88] = { \
+ .name = "LUT8", \
+ .addr = 0x220, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x89] = { \
+ .name = "LUT9", \
+ .addr = 0x224, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x8A] = { \
+ .name = "LUT10", \
+ .addr = 0x228, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x8B] = { \
+ .name = "LUT11", \
+ .addr = 0x22C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x8C] = { \
+ .name = "LUT12", \
+ .addr = 0x230, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x8D] = { \
+ .name = "LUT13", \
+ .addr = 0x234, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x8E] = { \
+ .name = "LUT14", \
+ .addr = 0x238, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x8F] = { \
+ .name = "LUT15", \
+ .addr = 0x23C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x90] = { \
+ .name = "LUT16", \
+ .addr = 0x240, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x91] = { \
+ .name = "LUT17", \
+ .addr = 0x244, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x92] = { \
+ .name = "LUT18", \
+ .addr = 0x248, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x93] = { \
+ .name = "LUT19", \
+ .addr = 0x24C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x94] = { \
+ .name = "LUT20", \
+ .addr = 0x250, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x95] = { \
+ .name = "LUT21", \
+ .addr = 0x254, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x96] = { \
+ .name = "LUT22", \
+ .addr = 0x258, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x97] = { \
+ .name = "LUT23", \
+ .addr = 0x25C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x98] = { \
+ .name = "LUT24", \
+ .addr = 0x260, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x99] = { \
+ .name = "LUT25", \
+ .addr = 0x264, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x9A] = { \
+ .name = "LUT26", \
+ .addr = 0x268, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x9B] = { \
+ .name = "LUT27", \
+ .addr = 0x26C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x9C] = { \
+ .name = "LUT28", \
+ .addr = 0x270, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x9D] = { \
+ .name = "LUT29", \
+ .addr = 0x274, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x9E] = { \
+ .name = "LUT30", \
+ .addr = 0x278, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x9F] = { \
+ .name = "LUT31", \
+ .addr = 0x27C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xA0] = { \
+ .name = "LUT32", \
+ .addr = 0x280, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xA1] = { \
+ .name = "LUT33", \
+ .addr = 0x284, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xA2] = { \
+ .name = "LUT34", \
+ .addr = 0x288, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xA3] = { \
+ .name = "LUT35", \
+ .addr = 0x28C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xA4] = { \
+ .name = "LUT36", \
+ .addr = 0x290, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xA5] = { \
+ .name = "LUT37", \
+ .addr = 0x294, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xA6] = { \
+ .name = "LUT38", \
+ .addr = 0x298, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xA7] = { \
+ .name = "LUT39", \
+ .addr = 0x29C, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xA8] = { \
+ .name = "LUT40", \
+ .addr = 0x2A0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xA9] = { \
+ .name = "LUT41", \
+ .addr = 0x2A4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xAA] = { \
+ .name = "LUT42", \
+ .addr = 0x2A8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xAB] = { \
+ .name = "LUT43", \
+ .addr = 0x2AC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xAC] = { \
+ .name = "LUT44", \
+ .addr = 0x2B0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xAD] = { \
+ .name = "LUT45", \
+ .addr = 0x2B4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xAE] = { \
+ .name = "LUT46", \
+ .addr = 0x2B8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xAF] = { \
+ .name = "LUT47", \
+ .addr = 0x2BC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xB0] = { \
+ .name = "LUT48", \
+ .addr = 0x2C0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xB1] = { \
+ .name = "LUT49", \
+ .addr = 0x2C4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xB2] = { \
+ .name = "LUT50", \
+ .addr = 0x2C8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xB3] = { \
+ .name = "LUT51", \
+ .addr = 0x2CC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xB4] = { \
+ .name = "LUT52", \
+ .addr = 0x2D0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xB5] = { \
+ .name = "LUT53", \
+ .addr = 0x2D4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xB6] = { \
+ .name = "LUT54", \
+ .addr = 0x2D8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xB7] = { \
+ .name = "LUT55", \
+ .addr = 0x2DC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xB8] = { \
+ .name = "LUT56", \
+ .addr = 0x2E0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xB9] = { \
+ .name = "LUT57", \
+ .addr = 0x2E4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xBA] = { \
+ .name = "LUT58", \
+ .addr = 0x2E8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xBB] = { \
+ .name = "LUT59", \
+ .addr = 0x2EC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xBC] = { \
+ .name = "LUT60", \
+ .addr = 0x2F0, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xBD] = { \
+ .name = "LUT61", \
+ .addr = 0x2F4, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xBE] = { \
+ .name = "LUT62", \
+ .addr = 0x2F8, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0xBF] = { \
+ .name = "LUT63", \
+ .addr = 0x2FC, \
+ .ro = 0x0, \
+ .reset = 0x0, \
+ }, \
+ [0x108] = { \
+ .name = "HADDRSTART", \
+ .addr = 0x420, \
+ .ro = 0xFFE, \
+ .reset = 0x0, \
+ }, \
+ [0x109] = { \
+ .name = "HADDREND", \
+ .addr = 0x424, \
+ .ro = 0xFFF, \
+ .reset = 0x0, \
+ }, \
+ [0x10A] = { \
+ .name = "HADDROFFSET", \
+ .addr = 0x428, \
+ .ro = 0xFFF, \
+ .reset = 0x0, \
+ }, \
+ }
diff --git a/include/hw/ssi/flexspi.h b/include/hw/ssi/flexspi.h
new file mode 100644
index 0000000000..51699e1ceb
--- /dev/null
+++ b/include/hw/ssi/flexspi.h
@@ -0,0 +1,31 @@
+/*
+ * QEMU model for FLEXSPI
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_RT500_FLEXSPI_H
+#define HW_RT500_FLEXSPI_H
+
+#include "hw/sysbus.h"
+#include "hw/ssi/ssi.h"
+#include "hw/arm/svd/flexspi.h"
+
+#define TYPE_FLEXSPI "flexspi"
+#define FLEXSPI(obj) OBJECT_CHECK(FlexSpiState, (obj), TYPE_FLEXSPI)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ uint32_t regs[FLEXSPI_REGS_NO];
+ MemoryRegion mem;
+ uint64_t mmap_size;
+} FlexSpiState;
+
+#endif /* HW_RT500_FLEXSPI_H */
diff --git a/hw/ssi/flexspi.c b/hw/ssi/flexspi.c
new file mode 100644
index 0000000000..d5d9e4f098
--- /dev/null
+++ b/hw/ssi/flexspi.c
@@ -0,0 +1,181 @@
+/*
+ * QEMU model for FLEXSPI
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/mmap-alloc.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "migration/vmstate.h"
+#include "exec/address-spaces.h"
+#include "hw/ssi/flexspi.h"
+#include "hw/arm/svd/flexspi.h"
+
+#include "trace.h"
+
+#define REG(s, reg) (s->regs[R_FLEXSPI_##reg])
+#define RF_WR(s, reg, field, val) \
+ ARRAY_FIELD_DP32(s->regs, FLEXSPI_##reg, field, val)
+#define RF_RD(s, reg, field) \
+ ARRAY_FIELD_EX32(s->regs, FLEXSPI_##reg, field)
+
+static FLEXSPI_REGISTER_ACCESS_INFO_ARRAY(reg_info);
+
+static void flexspi_reset_enter(Object *obj, ResetType type)
+{
+ FlexSpiState *s = FLEXSPI(obj);
+
+ for (int i = 0; i < FLEXSPI_REGS_NO; i++) {
+ hwaddr addr = reg_info[i].addr;
+
+ if (addr != -1) {
+ struct RegisterInfo ri = {
+ .data = &s->regs[addr / 4],
+ .data_size = 4,
+ .access = ®_info[i],
+ };
+
+ register_reset(&ri);
+ }
+ }
+
+ /* idle immediately after reset */
+ RF_WR(s, STS0, SEQIDLE, 1);
+}
+
+static MemTxResult flexspi_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ FlexSpiState *s = opaque;
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+ MemTxResult ret = MEMTX_OK;
+
+ switch (addr) {
+ default:
+ *data = s->regs[addr / 4];
+ break;
+ }
+
+ trace_flexspi_reg_read(DEVICE(s)->id, rai->name, addr, *data);
+ return ret;
+}
+
+
+static MemTxResult flexspi_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ FlexSpiState *s = opaque;
+ const struct RegisterAccessInfo *rai = ®_info[addr / 4];
+ struct RegisterInfo ri = {
+ .data = &s->regs[addr / 4],
+ .data_size = 4,
+ .access = rai,
+ };
+
+ trace_flexspi_reg_write(DEVICE(s)->id, rai->name, addr, value);
+
+ switch (addr) {
+ case A_FLEXSPI_MCR0:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+
+ if (RF_RD(s, MCR0, SWRESET)) {
+ RF_WR(s, MCR0, SWRESET, 0);
+ }
+ break;
+ }
+ case A_FLEXSPI_INTR:
+ {
+ /* fake SPI transfer completion */
+ RF_WR(s, INTR, IPCMDDONE, 1);
+ break;
+ }
+ default:
+ register_write(&ri, value, ~0, NULL, false);
+ break;
+ }
+
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps flexspi_ops = {
+ .read_with_attrs = flexspi_read,
+ .write_with_attrs = flexspi_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static Property flexspi_properties[] = {
+ DEFINE_PROP_UINT64("mmap_size", FlexSpiState, mmap_size, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void flexspi_init(Object *obj)
+{
+ FlexSpiState *s = FLEXSPI(obj);
+
+ memory_region_init_io(&s->mmio, obj, &flexspi_ops, s, TYPE_FLEXSPI,
+ sizeof(s->regs));
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static void flexspi_realize(DeviceState *dev, Error **errp)
+{
+ FlexSpiState *s = FLEXSPI(dev);
+
+ if (s->mmap_size) {
+ memory_region_init_ram(&s->mem, OBJECT(s), DEVICE(s)->id, s->mmap_size,
+ NULL);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mem);
+ }
+}
+
+static const VMStateDescription vmstate_flexspi = {
+ .name = "flexspi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, FlexSpiState, FLEXSPI_REGS_NO),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void flexspi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.enter = flexspi_reset_enter;
+ dc->realize = flexspi_realize;
+ dc->vmsd = &vmstate_flexspi;
+ device_class_set_props(dc, flexspi_properties);
+}
+
+static const TypeInfo flexspi_types[] = {
+ {
+ .name = TYPE_FLEXSPI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(FlexSpiState),
+ .instance_init = flexspi_init,
+ .class_init = flexspi_class_init,
+ },
+};
+
+DEFINE_TYPES(flexspi_types);
diff --git a/hw/arm/svd/meson.build b/hw/arm/svd/meson.build
index 9c458b314c..adf0f8327a 100644
--- a/hw/arm/svd/meson.build
+++ b/hw/arm/svd/meson.build
@@ -24,4 +24,8 @@ if get_option('mcux-soc-svd')
[ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/rt500_clkctl1.h',
'-p', 'CLKCTL1', '-t', 'RT500_CLKCTL1',
'--fields', 'PSCCTL*:FlexIO AUDIOPLL0PFD OSEVENTTFCLKSEL'])
+ run_target('svd-flexspi', command: svd_gen_header +
+ [ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/flexspi.h',
+ '-p', 'FLEXSPI0', '-t', 'FLEXSPI',
+ '--fields', 'STS0:SEQIDLE MCR0:SWRESET INTR:IPCMDDONE'])
endif
diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig
index 8d180de7cf..e3de40e6b6 100644
--- a/hw/ssi/Kconfig
+++ b/hw/ssi/Kconfig
@@ -28,3 +28,7 @@ config BCM2835_SPI
config PNV_SPI
bool
select SSI
+
+config FLEXSPI
+ bool
+ select SSI
diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build
index 58e0d14b37..31a2618d52 100644
--- a/hw/ssi/meson.build
+++ b/hw/ssi/meson.build
@@ -13,3 +13,4 @@ system_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_spi_host.c'))
system_ss.add(when: 'CONFIG_BCM2835_SPI', if_true: files('bcm2835_spi.c'))
system_ss.add(when: 'CONFIG_PNV_SPI', if_true: files('pnv_spi.c'))
system_ss.add(when: 'CONFIG_FLEXCOMM', if_true: files('flexcomm_spi.c'))
+system_ss.add(when: 'CONFIG_FLEXSPI', if_true: files('flexspi.c'))
diff --git a/hw/ssi/trace-events b/hw/ssi/trace-events
index f849f1f8be..dd2f04cb22 100644
--- a/hw/ssi/trace-events
+++ b/hw/ssi/trace-events
@@ -58,3 +58,7 @@ pnv_spi_RDR_match(const char* result) "%s"
flexcomm_spi_reg_read(const char *id, const char *reg_name, uint32_t addr, uint32_t val) " %s: %s[0x%04x] -> 0x%08x"
flexcomm_spi_reg_write(const char *id, const char *reg_name, uint32_t addr, uint32_t val) "%s: %s[0x%04x] <- 0x%08x"
flexcomm_spi_irq(const char *id, bool irq, bool fifoirqs, bool perirqs, bool enabled) "%s: %d %d %d %d"
+
+# flexspi.c
+flexspi_reg_read(const char *id, const char *reg_name, uint32_t addr, uint32_t val) " %s: %s[0x%04x] -> 0x%08x"
+flexspi_reg_write(const char *id, const char *reg_name, uint32_t addr, uint32_t val) "%s: %s[0x%04x] <- 0x%08x"
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 12/25] hw/misc: add support for RT500's reset controller
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (10 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 11/25] hw/ssi: add support for flexspi Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 13/25] hw/arm: add basic support for the RT500 SoC Octavian Purdila
` (12 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
The RT500 reset controller has two instances that have the same
register layout but with different fields for some registers.
The model only provides set and clear functionality for the various
reset lines which is common for both instances. Because of that only
one type is implemented for both controllers.
The patch includes automatically generated headers which contains the
register layout and helpers.
The header can be regenerated with the svd-rstctl0 and svd-rstctl1
targets when the build is configured with --enable-mcux-soc-svd.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/arm/svd/rt500_rstctl0.h | 110 ++++++++++++
include/hw/arm/svd/rt500_rstctl1.h | 110 ++++++++++++
include/hw/misc/rt500_rstctl.h | 32 ++++
hw/misc/rt500_rstctl.c | 258 +++++++++++++++++++++++++++++
hw/arm/svd/meson.build | 8 +
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/trace-events | 4 +
8 files changed, 526 insertions(+)
create mode 100644 include/hw/arm/svd/rt500_rstctl0.h
create mode 100644 include/hw/arm/svd/rt500_rstctl1.h
create mode 100644 include/hw/misc/rt500_rstctl.h
create mode 100644 hw/misc/rt500_rstctl.c
diff --git a/include/hw/arm/svd/rt500_rstctl0.h b/include/hw/arm/svd/rt500_rstctl0.h
new file mode 100644
index 0000000000..da25f22208
--- /dev/null
+++ b/include/hw/arm/svd/rt500_rstctl0.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016-2023 NXP SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Automatically generated by svd-gen-header.py from MIMXRT595S_cm33.xml
+ */
+#pragma once
+
+#include "hw/register.h"
+
+/* Reset Controller 0 */
+#define RT500_RSTCTL0_REGS_NO (31)
+
+/* System Reset Status Register */
+REG32(RT500_RSTCTL0_SYSRSTSTAT, 0x0);
+
+/* Peripheral Reset Control Register 0 */
+REG32(RT500_RSTCTL0_PRSTCTL0, 0x10);
+
+/* Peripheral Reset Control Register 1 */
+REG32(RT500_RSTCTL0_PRSTCTL1, 0x14);
+
+/* Peripheral Reset Control Register 2 */
+REG32(RT500_RSTCTL0_PRSTCTL2, 0x18);
+
+/* Peripheral Reset Control Register 0 SET */
+REG32(RT500_RSTCTL0_PRSTCTL0_SET, 0x40);
+
+/* Peripheral Reset Control Register 1 SET */
+REG32(RT500_RSTCTL0_PRSTCTL1_SET, 0x44);
+
+/* Peripheral Reset Control Register 2 SET */
+REG32(RT500_RSTCTL0_PRSTCTL2_SET, 0x48);
+
+/* Peripheral Reset Control Register 0 CLR */
+REG32(RT500_RSTCTL0_PRSTCTL0_CLR, 0x70);
+
+/* Peripheral Reset Control Register 1 CLR */
+REG32(RT500_RSTCTL0_PRSTCTL1_CLR, 0x74);
+
+/* Peripheral Reset Control Register 2 CLR */
+REG32(RT500_RSTCTL0_PRSTCTL2_CLR, 0x78);
+
+
+#define RT500_RSTCTL0_REGISTER_ACCESS_INFO_ARRAY(_name) \
+ struct RegisterAccessInfo _name[RT500_RSTCTL0_REGS_NO] = { \
+ [0 ... RT500_RSTCTL0_REGS_NO - 1] = { \
+ .name = "", \
+ .addr = -1, \
+ }, \
+ [0x0] = { \
+ .name = "SYSRSTSTAT", \
+ .addr = 0x0, \
+ .ro = 0xFFFFFF0E, \
+ .reset = 0x1, \
+ }, \
+ [0x4] = { \
+ .name = "PRSTCTL0", \
+ .addr = 0x10, \
+ .ro = 0x820AE0F5, \
+ .reset = 0x7DF51F0A, \
+ }, \
+ [0x5] = { \
+ .name = "PRSTCTL1", \
+ .addr = 0x14, \
+ .ro = 0xFEFE7FF3, \
+ .reset = 0x101800C, \
+ }, \
+ [0x6] = { \
+ .name = "PRSTCTL2", \
+ .addr = 0x18, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x1C000001, \
+ }, \
+ [0x10] = { \
+ .name = "PRSTCTL0_SET", \
+ .addr = 0x40, \
+ .ro = 0x820AE0F5, \
+ .reset = 0x0, \
+ }, \
+ [0x11] = { \
+ .name = "PRSTCTL1_SET", \
+ .addr = 0x44, \
+ .ro = 0xFEFE7FF3, \
+ .reset = 0x0, \
+ }, \
+ [0x12] = { \
+ .name = "PRSTCTL2_SET", \
+ .addr = 0x48, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ [0x1C] = { \
+ .name = "PRSTCTL0_CLR", \
+ .addr = 0x70, \
+ .ro = 0x820AE0F5, \
+ .reset = 0x0, \
+ }, \
+ [0x1D] = { \
+ .name = "PRSTCTL1_CLR", \
+ .addr = 0x74, \
+ .ro = 0xFEFE7FF3, \
+ .reset = 0x0, \
+ }, \
+ [0x1E] = { \
+ .name = "PRSTCTL2_CLR", \
+ .addr = 0x78, \
+ .ro = 0xFFFFFFFC, \
+ .reset = 0x0, \
+ }, \
+ }
diff --git a/include/hw/arm/svd/rt500_rstctl1.h b/include/hw/arm/svd/rt500_rstctl1.h
new file mode 100644
index 0000000000..7b0380798f
--- /dev/null
+++ b/include/hw/arm/svd/rt500_rstctl1.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016-2023 NXP SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Automatically generated by svd-gen-header.py from MIMXRT595S_cm33.xml
+ */
+#pragma once
+
+#include "hw/register.h"
+
+/* Reset Controller 1 */
+#define RT500_RSTCTL1_REGS_NO (31)
+
+/* System Reset Status Register */
+REG32(RT500_RSTCTL1_SYSRSTSTAT, 0x0);
+
+/* Peripheral Reset Control Register 0 */
+REG32(RT500_RSTCTL1_PRSTCTL0, 0x10);
+
+/* Peripheral Reset Control Register 1 */
+REG32(RT500_RSTCTL1_PRSTCTL1, 0x14);
+
+/* Peripheral Reset Control Register 2 */
+REG32(RT500_RSTCTL1_PRSTCTL2, 0x18);
+
+/* Peripheral Reset Control Register 0 SET */
+REG32(RT500_RSTCTL1_PRSTCTL0_SET, 0x40);
+
+/* Peripheral Reset Control Register 1 SET */
+REG32(RT500_RSTCTL1_PRSTCTL1_SET, 0x44);
+
+/* Peripheral Reset Control Register 2 SET */
+REG32(RT500_RSTCTL1_PRSTCTL2_SET, 0x48);
+
+/* Peripheral Reset Control Register 0 CLR */
+REG32(RT500_RSTCTL1_PRSTCTL0_CLR, 0x70);
+
+/* Peripheral Reset Control Register 1 CLR */
+REG32(RT500_RSTCTL1_PRSTCTL1_CLR, 0x74);
+
+/* Peripheral Reset Control Register 2 CLR */
+REG32(RT500_RSTCTL1_PRSTCTL2_CLR, 0x78);
+
+
+#define RT500_RSTCTL1_REGISTER_ACCESS_INFO_ARRAY(_name) \
+ struct RegisterAccessInfo _name[RT500_RSTCTL1_REGS_NO] = { \
+ [0 ... RT500_RSTCTL1_REGS_NO - 1] = { \
+ .name = "", \
+ .addr = -1, \
+ }, \
+ [0x0] = { \
+ .name = "SYSRSTSTAT", \
+ .addr = 0x0, \
+ .ro = 0xFFFFFFFF, \
+ .reset = 0x1, \
+ }, \
+ [0x4] = { \
+ .name = "PRSTCTL0", \
+ .addr = 0x10, \
+ .ro = 0xD40000FF, \
+ .reset = 0x1C0FF00, \
+ }, \
+ [0x5] = { \
+ .name = "PRSTCTL1", \
+ .addr = 0x14, \
+ .ro = 0x4E7EFF00, \
+ .reset = 0xB18100FF, \
+ }, \
+ [0x6] = { \
+ .name = "PRSTCTL2", \
+ .addr = 0x18, \
+ .ro = 0x3FFCFAE0, \
+ .reset = 0xC001011F, \
+ }, \
+ [0x10] = { \
+ .name = "PRSTCTL0_SET", \
+ .addr = 0x40, \
+ .ro = 0xD40000FF, \
+ .reset = 0x0, \
+ }, \
+ [0x11] = { \
+ .name = "PRSTCTL1_SET", \
+ .addr = 0x44, \
+ .ro = 0x4E7EFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x12] = { \
+ .name = "PRSTCTL2_SET", \
+ .addr = 0x48, \
+ .ro = 0x3FFCFAE0, \
+ .reset = 0x0, \
+ }, \
+ [0x1C] = { \
+ .name = "PRSTCTL0_CLR", \
+ .addr = 0x70, \
+ .ro = 0xD40000FF, \
+ .reset = 0x0, \
+ }, \
+ [0x1D] = { \
+ .name = "PRSTCTL1_CLR", \
+ .addr = 0x74, \
+ .ro = 0x4E7EFF00, \
+ .reset = 0x0, \
+ }, \
+ [0x1E] = { \
+ .name = "PRSTCTL2_CLR", \
+ .addr = 0x78, \
+ .ro = 0x3FFCFAE0, \
+ .reset = 0x0, \
+ }, \
+ }
diff --git a/include/hw/misc/rt500_rstctl.h b/include/hw/misc/rt500_rstctl.h
new file mode 100644
index 0000000000..ae7e304b2e
--- /dev/null
+++ b/include/hw/misc/rt500_rstctl.h
@@ -0,0 +1,32 @@
+/*
+ * QEMU model for RT500 Reset Controller
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_MISC_RT500_RSTCTL_H
+#define HW_MISC_RT500_RSTCTL_H
+
+#include "hw/arm/svd/rt500_rstctl0.h"
+#include "hw/arm/svd/rt500_rstctl1.h"
+#include "hw/sysbus.h"
+
+#define TYPE_RT500_RSTCTL "rt500-rstctl"
+#define RT500_RSTCTL(o) OBJECT_CHECK(RT500RstCtlState, o, TYPE_RT500_RSTCTL)
+
+#define TYPE_RT500_RSTCTL0 "rt500-rstctl0"
+#define TYPE_RT500_RSTCTL1 "rt500-rstctl1"
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ uint32_t regs[RT500_RSTCTL1_REGS_NO];
+} RT500RstCtlState;
+
+#endif /* HW_MISC_RT500_RSTCTL_H */
diff --git a/hw/misc/rt500_rstctl.c b/hw/misc/rt500_rstctl.c
new file mode 100644
index 0000000000..a6e818c0a0
--- /dev/null
+++ b/hw/misc/rt500_rstctl.c
@@ -0,0 +1,258 @@
+/*
+ * QEMU model for RT500 Reset Controller
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/address-spaces.h"
+#include "migration/vmstate.h"
+#include "hw/misc/rt500_rstctl.h"
+
+#include "trace.h"
+
+/*
+ * There are two intances for RSTCTL with the same register names and layout but
+ * with different fields.
+ */
+#define BUILD_BUG_REG_ADDR(reg) \
+ QEMU_BUILD_BUG_ON((int)A_RT500_RSTCTL0_##reg != (int)A_RT500_RSTCTL1_##reg)
+
+#define REG(s, reg) (s->regs[R_RT500_RSTCTL0_##reg])
+#define RF_WR(s, reg, field, val) \
+ ARRAY_FIELD_DP32(s->regs, RT500_RSTCTL0_##reg, field, val)
+#define RF_RD(s, reg, field) \
+ ARRAY_FIELD_EX32(s->regs, RT500_RSTCTL0_##reg, field)
+
+#define RSTCTL_SYSRSTSTAT_WMASK (BITS(7, 4) | BIT(0))
+#define RSTCL0_PRSCTL0_WMASK (BITS(30, 26) | BITS(24, 20) | BIT(18) | \
+ BIT(16) | BITS(12, 8) | BIT(3) | BIT(1))
+#define RSTCL0_PRSCTL1_WMASK (BIT(24) | BITS(16, 15) | BITS(3, 2))
+#define RSTCL0_PRSCTL2_WMASK (BITS(1, 0))
+#define RSTCL1_PRSCTL0_WMASK (BIT(29) | BIT(27) | BITS(25, 8))
+#define RSTCL1_PRSCTL1_WMASK (BIT(31) | BITS(29, 28) | BITS(24, 23) | \
+ BIT(16) | BITS(7, 0))
+#define RSTCL1_PRSCTL2_WMASK (BITS(31, 30) | BITS(17, 16) | BIT(10) | \
+ BIT(8) | BITS(4, 0))
+
+
+/*
+ * The two RSTCLK modules have different write register masks.
+ */
+typedef struct {
+ SysBusDeviceClass parent;
+ const struct RegisterAccessInfo *reg_info;
+ int reg_info_num;
+} RT500RstCtlClass;
+
+#define RT500_RSTCTL_CLASS(klass) \
+ OBJECT_CLASS_CHECK(RT500RstCtlClass, (klass), TYPE_RT500_RSTCTL)
+#define RT500_RSTCTL_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(RT500RstCtlClass, (obj), TYPE_RT500_RSTCTL)
+
+BUILD_BUG_REG_ADDR(SYSRSTSTAT);
+BUILD_BUG_REG_ADDR(PRSTCTL0);
+BUILD_BUG_REG_ADDR(PRSTCTL1);
+BUILD_BUG_REG_ADDR(PRSTCTL2);
+BUILD_BUG_REG_ADDR(PRSTCTL0_SET);
+BUILD_BUG_REG_ADDR(PRSTCTL1_SET);
+BUILD_BUG_REG_ADDR(PRSTCTL2_SET);
+BUILD_BUG_REG_ADDR(PRSTCTL0_CLR);
+BUILD_BUG_REG_ADDR(PRSTCTL1_CLR);
+BUILD_BUG_REG_ADDR(PRSTCTL2_CLR);
+
+static MemTxResult rt500_rstctl_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ RT500RstCtlState *s = opaque;
+ RT500RstCtlClass *c = RT500_RSTCTL_GET_CLASS(s);
+ const struct RegisterAccessInfo *rai = &c->reg_info[addr / 4];
+ MemTxResult ret = MEMTX_OK;
+
+ switch (addr) {
+ case A_RT500_RSTCTL0_SYSRSTSTAT:
+ case A_RT500_RSTCTL0_PRSTCTL0:
+ case A_RT500_RSTCTL0_PRSTCTL1:
+ case A_RT500_RSTCTL0_PRSTCTL2:
+ *data = s->regs[addr / 4];
+ break;
+ default:
+ ret = MEMTX_ERROR;
+ }
+
+ trace_rt500_rstctl_reg_read(DEVICE(s)->id, rai->name, addr, *data);
+ return ret;
+}
+
+static MemTxResult rt500_rstctl_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ RT500RstCtlState *s = opaque;
+ RT500RstCtlClass *c = RT500_RSTCTL_GET_CLASS(s);
+ const struct RegisterAccessInfo *rai = &c->reg_info[addr / 4];
+ struct RegisterInfo ri = {
+ .data = &s->regs[addr / 4],
+ .data_size = 4,
+ .access = rai,
+ };
+
+ trace_rt500_rstctl_reg_write(DEVICE(s)->id, rai->name, addr, value);
+
+ switch (addr) {
+ case A_RT500_RSTCTL0_SYSRSTSTAT:
+ {
+ /* write 1 to clear bits */
+ REG(s, SYSRSTSTAT) &= ~value;
+ break;
+ }
+ case A_RT500_RSTCTL0_PRSTCTL0:
+ case A_RT500_RSTCTL0_PRSTCTL1:
+ case A_RT500_RSTCTL0_PRSTCTL2:
+ {
+ register_write(&ri, value, ~0, NULL, false);
+ break;
+ }
+ case A_RT500_RSTCTL0_PRSTCTL0_SET:
+ case A_RT500_RSTCTL0_PRSTCTL1_SET:
+ case A_RT500_RSTCTL0_PRSTCTL2_SET:
+ {
+ uint32_t tmp;
+
+ tmp = A_RT500_RSTCTL0_PRSTCTL0 + (addr - A_RT500_RSTCTL0_PRSTCTL0_SET);
+ s->regs[tmp / 4] |= value;
+ break;
+ }
+ case A_RT500_RSTCTL0_PRSTCTL0_CLR:
+ case A_RT500_RSTCTL0_PRSTCTL1_CLR:
+ case A_RT500_RSTCTL0_PRSTCTL2_CLR:
+ {
+ uint32_t tmp;
+
+ tmp = A_RT500_RSTCTL0_PRSTCTL0 + (addr - A_RT500_RSTCTL0_PRSTCTL0_CLR);
+ s->regs[tmp / 4] &= ~value;
+ break;
+ }
+ }
+
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps rt500_rstctl_ops = {
+ .read_with_attrs = rt500_rstctl_read,
+ .write_with_attrs = rt500_rstctl_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void rt500_rstctl_reset_enter(Object *obj, ResetType type)
+{
+ RT500RstCtlState *s = RT500_RSTCTL(obj);
+ RT500RstCtlClass *c = RT500_RSTCTL_GET_CLASS(s);
+
+ for (int i = 0; i < c->reg_info_num; i++) {
+ hwaddr addr = c->reg_info[i].addr;
+
+ if (addr != -1) {
+ struct RegisterInfo ri = {
+ .data = &s->regs[addr / 4],
+ .data_size = 4,
+ .access = &c->reg_info[i],
+ };
+
+ register_reset(&ri);
+ }
+ }
+}
+
+static void rt500_rstctl_init(Object *obj)
+{
+ RT500RstCtlState *s = RT500_RSTCTL(obj);
+
+ memory_region_init_io(&s->mmio, obj, &rt500_rstctl_ops, s,
+ TYPE_RT500_RSTCTL, sizeof(s->regs));
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static const VMStateDescription vmstate_rt500_rstcl0 = {
+ .name = "rt500-rstctl0",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, RT500RstCtlState, RT500_RSTCTL0_REGS_NO),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_rt500_rstcl1 = {
+ .name = "rt500-rstctl1",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, RT500RstCtlState, RT500_RSTCTL1_REGS_NO),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rt500_rstctl0_class_init(ObjectClass *klass, void *data)
+{
+ RT500RstCtlClass *rc = RT500_RSTCTL_CLASS(klass);
+ static const RT500_RSTCTL0_REGISTER_ACCESS_INFO_ARRAY(reg_info);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ RESETTABLE_CLASS(klass)->phases.enter = rt500_rstctl_reset_enter;
+ dc->vmsd = &vmstate_rt500_rstcl0;
+ rc->reg_info = reg_info;
+ rc->reg_info_num = ARRAY_SIZE(reg_info);
+}
+
+static void rt500_rstctl1_class_init(ObjectClass *klass, void *data)
+{
+ RT500RstCtlClass *rc = RT500_RSTCTL_CLASS(klass);
+ static const RT500_RSTCTL1_REGISTER_ACCESS_INFO_ARRAY(reg_info);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ RESETTABLE_CLASS(klass)->phases.enter = rt500_rstctl_reset_enter;
+ dc->vmsd = &vmstate_rt500_rstcl1;
+ rc->reg_info = reg_info;
+ rc->reg_info_num = ARRAY_SIZE(reg_info);
+}
+
+static const TypeInfo rt500_rstctl_types[] = {
+ {
+ .name = TYPE_RT500_RSTCTL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RT500RstCtlState),
+ .instance_init = rt500_rstctl_init,
+ .abstract = true,
+ },
+ {
+ .name = TYPE_RT500_RSTCTL0,
+ .parent = TYPE_RT500_RSTCTL,
+ .class_init = rt500_rstctl0_class_init,
+ .class_size = sizeof(RT500RstCtlClass),
+ },
+ {
+ .name = TYPE_RT500_RSTCTL1,
+ .parent = TYPE_RT500_RSTCTL,
+ .class_init = rt500_rstctl1_class_init,
+ .class_size = sizeof(RT500RstCtlClass),
+ },
+};
+
+DEFINE_TYPES(rt500_rstctl_types);
diff --git a/hw/arm/svd/meson.build b/hw/arm/svd/meson.build
index adf0f8327a..eb2fab54f5 100644
--- a/hw/arm/svd/meson.build
+++ b/hw/arm/svd/meson.build
@@ -28,4 +28,12 @@ if get_option('mcux-soc-svd')
[ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/flexspi.h',
'-p', 'FLEXSPI0', '-t', 'FLEXSPI',
'--fields', 'STS0:SEQIDLE MCR0:SWRESET INTR:IPCMDDONE'])
+ run_target('svd-rt500-rstctl0', command: svd_gen_header +
+ [ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/rt500_rstctl0.h',
+ '-p', 'RSTCTL0', '-t', 'RT500_RSTCTL0',
+ '--fields', 'SYSRSTSTAT: PRSTCTL*:'])
+ run_target('svd-rt500-rstctl1', command: svd_gen_header +
+ [ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/rt500_rstctl1.h',
+ '-p', 'RSTCTL1', '-t', 'RT500_RSTCTL1',
+ '--fields', 'SYSRSTSTAT: PRSTCTL*:'])
endif
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 02feb93840..4b688aead2 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -221,4 +221,7 @@ config FLEXCOMM
config RT500_CLKCTL
bool
+config RT500_RSTCTL
+ bool
+
source macio/Kconfig
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 15f9153be4..bf0988fd43 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -154,3 +154,4 @@ system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_FLEXCOMM', if_true: files('flexcomm.c'))
system_ss.add(when: 'CONFIG_RT500_CLKCTL', if_true: files('rt500_clkctl0.c', 'rt500_clkctl1.c'))
+system_ss.add(when: 'CONFIG_RT500_RSTCTL', if_true: files('rt500_rstctl.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index b19393dd36..721ebe4bb7 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -376,3 +376,7 @@ rt500_clkctl0_reg_write(const char *regname, uint32_t addr, uint32_t val) "%s[0x
# rt500_clkctl1.c
rt500_clkctl1_reg_read(const char *regname, uint32_t addr, uint32_t val) "%s[0x%04x] -> 0x%08x"
rt500_clkctl1_reg_write(const char *regname, uint32_t addr, uint32_t val) "%s[0x%04x] <- 0x%08x"
+
+# rt500_rstctl.c
+rt500_rstctl_reg_read(const char *id, const char *regname, uint32_t addr, uint32_t val) "%s: %s[0x%04x] -> 0x%08x"
+rt500_rstctl_reg_write(const char *id, const char *regname, uint32_t addr, uint32_t val) "%s: %s[0x%04x] <- 0x%08x"
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 13/25] hw/arm: add basic support for the RT500 SoC
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (11 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 12/25] hw/misc: add support for RT500's reset controller Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 14/25] hw/arm: add RT595-EVK board Octavian Purdila
` (11 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add basic support for the RT500 SoC. It supports enough peripherals to
run the NXP's microXpresso SDK hello world example.
The patch includes an automatically generated header which contains
peripheral base addreses and interrupt numbers.
The header can be regenerated with the svd-rt500 target when the
build is configured with --enable-mcux-soc-svd.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/arm/rt500.h | 44 +++++
include/hw/arm/svd/rt500.h | 63 +++++++
hw/arm/rt500.c | 329 +++++++++++++++++++++++++++++++++++++
hw/arm/Kconfig | 2 +
hw/arm/meson.build | 1 +
hw/arm/svd/meson.build | 4 +
6 files changed, 443 insertions(+)
create mode 100644 include/hw/arm/rt500.h
create mode 100644 include/hw/arm/svd/rt500.h
create mode 100644 hw/arm/rt500.c
diff --git a/include/hw/arm/rt500.h b/include/hw/arm/rt500.h
new file mode 100644
index 0000000000..26e08c39a6
--- /dev/null
+++ b/include/hw/arm/rt500.h
@@ -0,0 +1,44 @@
+/*
+ * i.MX RT500 platforms.
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_ARM_RT500_H
+#define HW_ARM_RT500_H
+
+#include "hw/arm/armv7m.h"
+#include "hw/misc/flexcomm.h"
+#include "hw/misc/rt500_clkctl0.h"
+#include "hw/misc/rt500_clkctl1.h"
+#include "hw/ssi/flexspi.h"
+#include "hw/misc/rt500_rstctl.h"
+
+#define TYPE_RT500 "rt500"
+#define RT500(obj) OBJECT_CHECK(RT500State, (obj), TYPE_RT500)
+
+#define RT500_FLEXCOMM_NUM (17)
+#define RT500_FLEXSPI_NUM (2)
+#define RT500_RSTCTL_NUM (2)
+
+typedef struct RT500State {
+ SysBusDevice parent_obj;
+
+ ARMv7MState armv7m;
+ MemoryRegion *mem;
+ FlexcommState flexcomm[RT500_FLEXCOMM_NUM];
+ RT500ClkCtl0State clkctl0;
+ RT500ClkCtl1State clkctl1;
+ FlexSpiState flexspi[RT500_FLEXSPI_NUM];
+ RT500RstCtlState rstctl[RT500_RSTCTL_NUM];
+
+ Clock *sysclk;
+ Clock *refclk;
+} RT500State;
+
+#endif /* HW_ARM_RT500_H */
diff --git a/include/hw/arm/svd/rt500.h b/include/hw/arm/svd/rt500.h
new file mode 100644
index 0000000000..3594258f2e
--- /dev/null
+++ b/include/hw/arm/svd/rt500.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-2023 NXP SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Automatically generated by svd-gen-header.py from MIMXRT595S_cm33.xml
+ */
+#pragma once
+
+#define RT500_FLEXCOMM0_BASE 0x40106000UL
+#define RT500_FLEXCOMM1_BASE 0x40107000UL
+#define RT500_FLEXCOMM2_BASE 0x40108000UL
+#define RT500_FLEXCOMM3_BASE 0x40109000UL
+#define RT500_FLEXCOMM4_BASE 0x40122000UL
+#define RT500_FLEXCOMM5_BASE 0x40123000UL
+#define RT500_FLEXCOMM6_BASE 0x40124000UL
+#define RT500_FLEXCOMM7_BASE 0x40125000UL
+#define RT500_FLEXCOMM14_BASE 0x40126000UL
+#define RT500_FLEXCOMM15_BASE 0x40127000UL
+#define RT500_FLEXCOMM16_BASE 0x40128000UL
+#define RT500_FLEXCOMM8_BASE 0x40209000UL
+#define RT500_FLEXCOMM9_BASE 0x4020A000UL
+#define RT500_FLEXCOMM10_BASE 0x4020B000UL
+#define RT500_FLEXCOMM11_BASE 0x4020C000UL
+#define RT500_FLEXCOMM12_BASE 0x4020D000UL
+#define RT500_FLEXCOMM13_BASE 0x4020E000UL
+
+#define RT500_FLEXCOMM0_IRQn 0x14UL
+#define RT500_FLEXCOMM1_IRQn 0x15UL
+#define RT500_FLEXCOMM2_IRQn 0x16UL
+#define RT500_FLEXCOMM3_IRQn 0x17UL
+#define RT500_FLEXCOMM4_IRQn 0x18UL
+#define RT500_FLEXCOMM5_IRQn 0x19UL
+#define RT500_FLEXCOMM6_IRQn 0x43UL
+#define RT500_FLEXCOMM7_IRQn 0x44UL
+#define RT500_FLEXCOMM14_IRQn 0x20UL
+#define RT500_FLEXCOMM15_IRQn 0x21UL
+#define RT500_FLEXCOMM16_IRQn 0x66UL
+#define RT500_FLEXCOMM8_IRQn 0x60UL
+#define RT500_FLEXCOMM9_IRQn 0x61UL
+#define RT500_FLEXCOMM10_IRQn 0x62UL
+#define RT500_FLEXCOMM11_IRQn 0x63UL
+#define RT500_FLEXCOMM12_IRQn 0x64UL
+#define RT500_FLEXCOMM13_IRQn 0x65UL
+
+#define RT500_CLKCTL0_BASE 0x40001000UL
+
+
+#define RT500_CLKCTL1_BASE 0x40021000UL
+
+
+#define RT500_FLEXSPI0_BASE 0x40134000UL
+
+#define RT500_FLEXSPI0_FLEXSPI1_IRQn 0x42UL
+
+#define RT500_FLEXSPI1_BASE 0x4013C000UL
+
+#define RT500_FLEXSPI0_FLEXSPI1_IRQn 0x42UL
+
+#define RT500_RSTCTL0_BASE 0x40000000UL
+
+
+#define RT500_RSTCTL1_BASE 0x40020000UL
+
+
diff --git a/hw/arm/rt500.c b/hw/arm/rt500.c
new file mode 100644
index 0000000000..9e4cfb539e
--- /dev/null
+++ b/hw/arm/rt500.c
@@ -0,0 +1,329 @@
+/*
+ * i.MX RT500 platforms.
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/arm/boot.h"
+#include "hw/boards.h"
+#include "hw/irq.h"
+#include "qemu/log.h"
+#include "qemu/datadir.h"
+#include "qemu/units.h"
+#include "exec/address-spaces.h"
+#include "sysemu/reset.h"
+#include "sysemu/runstate.h"
+#include "sysemu/sysemu.h"
+#include "hw/arm/armv7m.h"
+#include "hw/loader.h"
+#include "hw/qdev-clock.h"
+#include "hw/misc/unimp.h"
+#include "hw/arm/rt500.h"
+#include "hw/arm/svd/rt500.h"
+
+#define MMAP_SRAM_CODE_BASE (0x0)
+#define MMAP_SRAM_DATA_BASE (0x20000000)
+#define MMAP_SRAM_SIZE (5 * MiB)
+#define MMAP_BOOT_ROM_BASE (0x03000000)
+#define MMAP_BOOT_ROM_SIZE (192 * KiB)
+#define MMAP_SDMA_RAM_BASE (0x24100000)
+#define MMAP_SDMA_RAM_SIZE (32 * KiB)
+#define MMAP_FLEXSPI0_BASE (0x08000000)
+#define MMAP_FLEXSPI0_SIZE (128 * MiB)
+#define MMAP_FLEXSPI1_BASE (0x28000000)
+#define MMAP_FLEXSPI1_SIZE (128 * MiB)
+
+#define SECURE_OFFSET (0x10000000)
+
+#define RT500_NUM_IRQ (RT500_FLEXCOMM16_IRQn + 1)
+
+typedef enum MemInfoType {
+ MEM_RAM,
+ MEM_ROM,
+ MEM_ALIAS
+} MemInfoType;
+
+static void do_sys_reset(void *opaque, int n, int level)
+{
+ if (level) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ }
+}
+
+static void rt500_init(Object *obj)
+{
+ RT500State *s = RT500(obj);
+
+ /* Add ARMv7-M device */
+ object_initialize_child(obj, "armv7m", &s->armv7m, TYPE_ARMV7M);
+
+ for (int i = 0; i < RT500_FLEXCOMM_NUM; i++) {
+ char *id = g_strdup_printf("flexcomm%d", i);
+
+ object_initialize_child(obj, id, &s->flexcomm[i], TYPE_FLEXCOMM);
+ DEVICE(&s->flexcomm[i])->id = id;
+ }
+
+ object_initialize_child(obj, "clkctl0", &s->clkctl0, TYPE_RT500_CLKCTL0);
+ object_initialize_child(obj, "clkctl1", &s->clkctl1, TYPE_RT500_CLKCTL1);
+
+ /* Initialize clocks */
+ s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
+ s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
+
+ for (int i = 0; i < RT500_FLEXSPI_NUM; i++) {
+ char *id = g_strdup_printf("flexspi%d", i);
+
+ object_initialize_child(obj, id, &s->flexspi[i], TYPE_FLEXSPI);
+ DEVICE(&s->flexspi[i])->id = id;
+ }
+
+ for (int i = 0; i < RT500_RSTCTL_NUM; i++) {
+ static const char *types[] = {
+ TYPE_RT500_RSTCTL0, TYPE_RT500_RSTCTL1
+ };
+ char *id = g_strdup_printf("rstctl%d", i);
+
+ object_initialize_child(obj, id, &s->rstctl[i], types[i]);
+ DEVICE(&s->rstctl[i])->id = id;
+ }
+}
+
+static void rt500_realize_memory(RT500State *s, Error **errp)
+{
+ static const struct {
+ const char *name;
+ hwaddr base;
+ size_t size;
+ MemInfoType type;
+ int alias_for;
+ } mem_info[] = {
+ {
+ .name = "SRAM (code bus)",
+ .base = MMAP_SRAM_CODE_BASE,
+ .size = MMAP_SRAM_SIZE,
+ .type = MEM_RAM,
+ },
+ {
+ .name = "BOOT-ROM",
+ .base = MMAP_BOOT_ROM_BASE,
+ .size = MMAP_BOOT_ROM_SIZE,
+ .type = MEM_ROM,
+ },
+ {
+ .name = "Smart DMA RAM",
+ .base = MMAP_SDMA_RAM_BASE,
+ .size = MMAP_SDMA_RAM_SIZE,
+ .type = MEM_RAM,
+ },
+ {
+ .name = "SRAM (data bus)",
+ .base = MMAP_SRAM_DATA_BASE,
+ .size = MMAP_SRAM_SIZE,
+ .type = MEM_ALIAS,
+ .alias_for = 0
+ },
+ };
+
+ s->mem = g_malloc_n(2 * ARRAY_SIZE(mem_info), sizeof(MemoryRegion));
+ for (int i = 0; i < ARRAY_SIZE(mem_info); i++) {
+ const char *name = mem_info[i].name;
+ int size = mem_info[i].size;
+ int type = mem_info[i].type;
+ int alias_for = mem_info[i].alias_for;
+ MemoryRegion *mem = &s->mem[i];
+ uint32_t base = mem_info[i].base;
+ MemoryRegion *sec_mem;
+ char sec_name[256];
+
+ switch (type) {
+ case MEM_RAM:
+ memory_region_init_ram(mem, OBJECT(s), name, size, errp);
+ break;
+ case MEM_ROM:
+ memory_region_init_rom(mem, OBJECT(s), name, size, errp);
+ break;
+ case MEM_ALIAS:
+ {
+ MemoryRegion *orig = &s->mem[alias_for];
+
+ memory_region_init_alias(mem, OBJECT(s), name, orig, 0, size);
+ break;
+ }
+ default:
+ g_assert_not_reached();
+ }
+
+ memory_region_add_subregion(get_system_memory(), base, mem);
+
+ /* create secure alias */
+ snprintf(sec_name, sizeof(sec_name), "SECURE %s", name);
+ sec_mem = &s->mem[ARRAY_SIZE(mem_info) + i];
+ if (type == MEM_ALIAS) {
+ mem = &s->mem[alias_for];
+ }
+ memory_region_init_alias(sec_mem, OBJECT(s), sec_name, mem, 0, size);
+ memory_region_add_subregion(get_system_memory(), base + SECURE_OFFSET,
+ sec_mem);
+
+ if (mem_info[i].type == MEM_ROM) {
+ char *fname = qemu_find_file(QEMU_FILE_TYPE_BIOS, "rt500.rom");
+
+ if (fname) {
+ int fsize = get_image_size(fname);
+ int ret;
+
+ if (fsize > size) {
+ error_setg(errp, "rom file too big: %d > %d", fsize, size);
+ } else {
+ ret = load_image_targphys(fname, base, size);
+ if (ret < 0) {
+ error_setg(errp, "could not load rom: %s", fname);
+ }
+ }
+ }
+ g_free(fname);
+ }
+ }
+}
+
+static void rt500_realize(DeviceState *dev, Error **errp)
+{
+ MachineState *ms = MACHINE(qdev_get_machine());
+ RT500State *s = RT500(dev);
+
+ rt500_realize_memory(s, errp);
+
+ /* Setup ARMv7M CPU */
+ qdev_prop_set_uint32(DEVICE(&s->armv7m), "num-irq", RT500_NUM_IRQ);
+ qdev_prop_set_uint8(DEVICE(&s->armv7m), "num-prio-bits", 3);
+ qdev_prop_set_string(DEVICE(&s->armv7m), "cpu-type", "cortex-m33-arm-cpu");
+ object_property_set_link(OBJECT(&s->armv7m), "memory",
+ OBJECT(get_system_memory()), &error_abort);
+ if (!ms->kernel_filename) {
+ qdev_prop_set_uint32(DEVICE(&s->armv7m), "init-nsvtor",
+ MMAP_BOOT_ROM_BASE);
+ qdev_prop_set_uint32(DEVICE(&s->armv7m), "init-svtor",
+ MMAP_BOOT_ROM_BASE + SECURE_OFFSET);
+ }
+
+ qdev_connect_clock_in(DEVICE(&s->armv7m), "cpuclk", s->sysclk);
+ qdev_connect_clock_in(DEVICE(&s->armv7m), "refclk",
+ qdev_get_clock_out(DEVICE(&s->clkctl0), "systick_clk"));
+
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(&s->armv7m), errp);
+ qdev_connect_gpio_out_named(DEVICE(&s->armv7m), "SYSRESETREQ", 0,
+ qemu_allocate_irq(&do_sys_reset, NULL, 0));
+
+ /* Setup FLEXCOMM */
+ for (int i = 0; i < RT500_FLEXCOMM_NUM; i++) {
+ static const uint32_t addr[] = {
+ RT500_FLEXCOMM0_BASE, RT500_FLEXCOMM1_BASE, RT500_FLEXCOMM2_BASE,
+ RT500_FLEXCOMM3_BASE, RT500_FLEXCOMM4_BASE, RT500_FLEXCOMM5_BASE,
+ RT500_FLEXCOMM6_BASE, RT500_FLEXCOMM7_BASE, RT500_FLEXCOMM8_BASE,
+ RT500_FLEXCOMM8_BASE, RT500_FLEXCOMM10_BASE, RT500_FLEXCOMM11_BASE,
+ RT500_FLEXCOMM12_BASE, RT500_FLEXCOMM13_BASE, RT500_FLEXCOMM14_BASE,
+ RT500_FLEXCOMM15_BASE, RT500_FLEXCOMM16_BASE
+ };
+ static const int irq[] = {
+ RT500_FLEXCOMM0_IRQn, RT500_FLEXCOMM1_IRQn, RT500_FLEXCOMM2_IRQn,
+ RT500_FLEXCOMM3_IRQn, RT500_FLEXCOMM4_IRQn, RT500_FLEXCOMM5_IRQn,
+ RT500_FLEXCOMM6_IRQn, RT500_FLEXCOMM7_IRQn, RT500_FLEXCOMM8_IRQn,
+ RT500_FLEXCOMM9_IRQn, RT500_FLEXCOMM10_IRQn, RT500_FLEXCOMM11_IRQn,
+ RT500_FLEXCOMM12_IRQn, RT500_FLEXCOMM13_IRQn, RT500_FLEXCOMM14_IRQn,
+ RT500_FLEXCOMM15_IRQn, RT500_FLEXCOMM16_IRQn
+ };
+ static const int functions[] = {
+ FLEXCOMM_FULL, FLEXCOMM_FULL, FLEXCOMM_FULL,
+ FLEXCOMM_FULL, FLEXCOMM_FULL, FLEXCOMM_FULL,
+ FLEXCOMM_FULL, FLEXCOMM_FULL, FLEXCOMM_FULL,
+ FLEXCOMM_FULL, FLEXCOMM_FULL, FLEXCOMM_FULL,
+ FLEXCOMM_FULL, FLEXCOMM_FULL, FLEXCOMM_HSSPI,
+ FLEXCOMM_PMICI2C, FLEXCOMM_HSSPI
+ };
+ DeviceState *ds = DEVICE(&s->flexcomm[i]);
+
+ qdev_prop_set_uint32(ds, "functions", functions[i]);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(ds), errp);
+ sysbus_mmio_map(SYS_BUS_DEVICE(ds), 0, addr[i]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(ds), 0,
+ qdev_get_gpio_in(DEVICE(&s->armv7m), irq[i]));
+ }
+
+ /* Setup CTLCTL0 */
+ qdev_connect_clock_in(DEVICE(&s->clkctl0), "sysclk", s->sysclk);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(DEVICE(&s->clkctl0)), errp);
+ sysbus_mmio_map(SYS_BUS_DEVICE(DEVICE(&s->clkctl0)), 0, RT500_CLKCTL0_BASE);
+
+ /* Setup CTLCTL1 */
+ qdev_connect_clock_in(DEVICE(&s->clkctl1), "sysclk", s->sysclk);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(DEVICE(&s->clkctl1)), errp);
+ sysbus_mmio_map(SYS_BUS_DEVICE(DEVICE(&s->clkctl1)), 0, RT500_CLKCTL1_BASE);
+
+ /* Setup FlexSPI */
+ for (int i = 0; i < RT500_FLEXSPI_NUM; i++) {
+ static const uint32_t addr[] = {
+ RT500_FLEXSPI0_BASE, RT500_FLEXSPI1_BASE
+ };
+ static const uint32_t mmap_base[] = {
+ MMAP_FLEXSPI0_BASE, MMAP_FLEXSPI1_BASE
+ };
+ static const uint32_t mmap_size[] = {
+ MMAP_FLEXSPI0_SIZE, MMAP_FLEXSPI1_SIZE,
+ };
+ DeviceState *ds = DEVICE(&s->flexspi[i]);
+
+ qdev_prop_set_uint32(ds, "mmap_size", mmap_size[i]);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(ds), errp);
+ sysbus_mmio_map(SYS_BUS_DEVICE(ds), 0, addr[i]);
+ sysbus_mmio_map(SYS_BUS_DEVICE(ds), 1, mmap_base[i]);
+ }
+
+ /* Setup reset controllers */
+ for (int i = 0; i < RT500_RSTCTL_NUM; i++) {
+ DeviceState *ds = DEVICE(&s->rstctl[i]);
+ static const uint32_t addr[] = {
+ RT500_RSTCTL0_BASE, RT500_RSTCTL1_BASE
+ };
+
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(ds), errp);
+ sysbus_mmio_map(SYS_BUS_DEVICE(ds), 0, addr[i]);
+ }
+}
+
+static void rt500_unrealize(DeviceState *ds)
+{
+ RT500State *s = RT500(ds);
+
+ g_free(s->mem);
+}
+
+static void rt500_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = rt500_realize;
+ dc->unrealize = rt500_unrealize;
+ dc->desc = "RT500 (ARM Cortex-M33)";
+}
+
+static const TypeInfo rt500_types[] = {
+ {
+ .name = TYPE_RT500,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RT500State),
+ .instance_init = rt500_init,
+ .class_init = rt500_class_init,
+ },
+};
+
+DEFINE_TYPES(rt500_types);
+
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 658af0dace..5bc9438945 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -634,3 +634,5 @@ config RT500
bool
select FLEXCOMM
select RT500_CLKCTL
+ select FLEXSPI
+ select RT500_RSTCTL
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 83e4aea10e..a2b20617c9 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -60,6 +60,7 @@ arm_ss.add(when: 'CONFIG_XEN', if_true: files(
'xen-stubs.c',
'xen-pvh.c',
))
+arm_ss.add(when: 'CONFIG_RT500', if_true: files('rt500.c'))
system_ss.add(when: 'CONFIG_ARM_SMMUV3', if_true: files('smmu-common.c'))
system_ss.add(when: 'CONFIG_COLLIE', if_true: files('collie.c'))
diff --git a/hw/arm/svd/meson.build b/hw/arm/svd/meson.build
index eb2fab54f5..07bcc523fd 100644
--- a/hw/arm/svd/meson.build
+++ b/hw/arm/svd/meson.build
@@ -36,4 +36,8 @@ if get_option('mcux-soc-svd')
[ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/rt500_rstctl1.h',
'-p', 'RSTCTL1', '-t', 'RT500_RSTCTL1',
'--fields', 'SYSRSTSTAT: PRSTCTL*:'])
+ run_target('svd-rt500', command: svd_gen_header +
+ [ '-i', rt595, '-o', '@SOURCE_ROOT@/include/hw/arm/svd/rt500.h',
+ '-s', 'RT500', '-p', 'FLEXCOMM0', '-p', 'CLKCTL0', '-p', 'CLKCTL1',
+ '-p', 'FLEXSPI0', '-p', 'FLEXSPI1', '-p', 'RSTCTL0', '-p', 'RSTCTL1'])
endif
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 14/25] hw/arm: add RT595-EVK board
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (12 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 13/25] hw/arm: add basic support for the RT500 SoC Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 15/25] tests/qtest: add register access macros and functions Octavian Purdila
` (10 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add basic support for the RT595-EVK board, enough to be able to run
the NXP's microXpresso SDK hello world example.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
hw/arm/rt595-evk.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++
hw/arm/Kconfig | 5 ++++
hw/arm/meson.build | 1 +
3 files changed, 70 insertions(+)
create mode 100644 hw/arm/rt595-evk.c
diff --git a/hw/arm/rt595-evk.c b/hw/arm/rt595-evk.c
new file mode 100644
index 0000000000..4f875a0b12
--- /dev/null
+++ b/hw/arm/rt595-evk.c
@@ -0,0 +1,64 @@
+/*
+ * i.MX RT595 EVK
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "elf.h"
+#include "exec/address-spaces.h"
+#include "hw/loader.h"
+#include "hw/sysbus.h"
+#include "hw/boards.h"
+#include "qemu/log.h"
+#include "hw/arm/armv7m.h"
+#include "hw/arm/boot.h"
+#include "qapi/error.h"
+#include "hw/arm/rt500.h"
+#include "hw/qdev-clock.h"
+#include "sysemu/reset.h"
+
+static void rt595_evk_reset(MachineState *ms, ResetType reason)
+{
+ /*
+ * CPU reset is not done by default, we need to do it manually when the
+ * machine is reset.
+ */
+ cpu_reset(first_cpu);
+
+ qemu_devices_reset(reason);
+}
+
+static void rt595_evk_init(MachineState *ms)
+{
+ RT500State *s;
+ Clock *sysclk;
+
+ sysclk = clock_new(OBJECT(ms), "SYSCLK");
+ clock_set_hz(sysclk, 200000000);
+
+ s = RT500(object_new(TYPE_RT500));
+ qdev_connect_clock_in(DEVICE(s), "sysclk", sysclk);
+ object_property_add_child(OBJECT(ms), "soc", OBJECT(s));
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal);
+
+ if (ms->kernel_filename) {
+ armv7m_load_kernel(ARM_CPU(first_cpu), ms->kernel_filename, 0, 0);
+ }
+}
+
+static void rt595_evk_machine_init(MachineClass *mc)
+{
+ mc->desc = "RT595 EVK Machine (ARM Cortex-M33)";
+ mc->init = rt595_evk_init;
+ mc->reset = rt595_evk_reset;
+
+ mc->ignore_memory_transaction_failures = true;
+}
+
+DEFINE_MACHINE("rt595-evk", rt595_evk_machine_init);
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 5bc9438945..86d704cf1b 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -636,3 +636,8 @@ config RT500
select RT500_CLKCTL
select FLEXSPI
select RT500_RSTCTL
+
+config RT595_EVK
+ bool
+ default y
+ select RT500
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index a2b20617c9..48ae30acef 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -61,6 +61,7 @@ arm_ss.add(when: 'CONFIG_XEN', if_true: files(
'xen-pvh.c',
))
arm_ss.add(when: 'CONFIG_RT500', if_true: files('rt500.c'))
+arm_ss.add(when: 'CONFIG_RT595_EVK', if_true: files('rt595-evk.c'))
system_ss.add(when: 'CONFIG_ARM_SMMUV3', if_true: files('smmu-common.c'))
system_ss.add(when: 'CONFIG_COLLIE', if_true: files('collie.c'))
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 15/25] tests/qtest: add register access macros and functions
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (13 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 14/25] hw/arm: add RT595-EVK board Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 16/25] system/qtest: add APIS to check for memory access failures Octavian Purdila
` (9 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add utility macros for accessing register or register bit fields in
tests, e.g.:
REG32_WRITE(FLEXCOMM, PSELID, persel);
g_assert(REG32_READ_FIELD(FLEXCOMM, PSELID, PERSEL) == persel);
Signed-off-by: Octavian Purdila <tavip@google.com>
---
tests/qtest/reg-utils.h | 70 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
create mode 100644 tests/qtest/reg-utils.h
diff --git a/tests/qtest/reg-utils.h b/tests/qtest/reg-utils.h
new file mode 100644
index 0000000000..e09aaf3333
--- /dev/null
+++ b/tests/qtest/reg-utils.h
@@ -0,0 +1,70 @@
+/*
+ * Register access utilities for device tests.
+ *
+ * Copyright (C) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#ifndef _REG_UTILS_H
+#define _REG_UTILS_H
+
+#include "libqtest-single.h"
+#include "hw/registerfields.h"
+
+#ifdef DEBUG_REG
+#define debug(fmt, args...) fprintf(stderr, fmt, ## args)
+#else
+#define debug(fmt, args...)
+#endif
+
+#define _REG_OFF(mod, reg) (A_##mod##_##reg)
+
+#define REG32_READ(mod, reg) \
+ ({ \
+ uint32_t value; \
+ value = readl(mod##_BASE + _REG_OFF(mod, reg)); \
+ debug("[%s] -> %08x\n", #reg, value); \
+ value; \
+ })
+
+#define REG32_WRITE(mod, reg, value) \
+ do { \
+ debug("[%s] <- %08x\n", #reg, value); \
+ writel(mod##_BASE + _REG_OFF(mod, reg), value); \
+ } while (0)
+
+#define REG_FIELD_VAL(v, mod, reg, field) \
+ FIELD_EX32(v, mod##_##reg, field) \
+
+#define REG32_READ_FIELD(mod, reg, field) \
+ REG_FIELD_VAL(REG32_READ(mod, reg), mod, reg, field)
+
+#define REG32_WRITE_FIELD(mod, reg, field, val) \
+ do { \
+ uint32_t _tmp = REG32_READ(mod, reg); \
+ _tmp = FIELD_DP32(_tmp, mod##_##reg, field, val); \
+ REG32_WRITE(mod, reg, _tmp); \
+ } while (0)
+
+#define REG32_WRITE_FIELD_NOUPDATE(mod, reg, field, val) \
+ do { \
+ uint32_t _tmp = FIELD_DP32(0, mod##_##reg, field, val); \
+ REG32_WRITE(mod, reg, _tmp); \
+ } while (0)
+
+#define WAIT_REG32_FIELD(ns, mod, reg, field, val) \
+ do { \
+ clock_step(ns); \
+ g_assert_cmpuint(REG32_READ_FIELD(mod, reg, field), ==, val); \
+ } while (0)
+
+#define REG32_READ_FAIL(mod, reg) \
+ readl_fail(mod##_BASE + _REG_OFF(mod, reg))
+
+#define REG32_WRITE_FAIL(mod, reg, value) \
+ writel_fail(mod##_BASE + _REG_OFF(mod, reg), value)
+
+#endif /* _REG_UTILS_H */
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 16/25] system/qtest: add APIS to check for memory access failures
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (14 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 15/25] tests/qtest: add register access macros and functions Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 17/25] tests/qtest: add flexcomm tests Octavian Purdila
` (8 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add read*/write*_fail qtest APIs to check for memory access failures.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
tests/qtest/libqtest-single.h | 92 +++++++++++++++++++++++++++++++++++
tests/qtest/libqtest.h | 76 +++++++++++++++++++++++++++++
system/qtest.c | 44 ++++++++++-------
tests/qtest/libqtest.c | 73 ++++++++++++++++++++++++++-
4 files changed, 265 insertions(+), 20 deletions(-)
diff --git a/tests/qtest/libqtest-single.h b/tests/qtest/libqtest-single.h
index 851724cbcb..c22037c8b2 100644
--- a/tests/qtest/libqtest-single.h
+++ b/tests/qtest/libqtest-single.h
@@ -265,6 +265,98 @@ static inline uint64_t readq(uint64_t addr)
return qtest_readq(global_qtest, addr);
}
+/**
+ * writeb_fail:
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes an 8-bit value to memory expecting a failure.
+ */
+static inline void writeb_fail(uint64_t addr, uint8_t value)
+{
+ qtest_writeb_fail(global_qtest, addr, value);
+}
+
+/**
+ * writew_fail:
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 16-bit value to memory expecting a failure.
+ */
+static inline void writew_fail(uint64_t addr, uint16_t value)
+{
+ qtest_writew_fail(global_qtest, addr, value);
+}
+
+/**
+ * writel_fail:
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 32-bit value to memory expecting a failure.
+ */
+static inline void writel_fail(uint64_t addr, uint32_t value)
+{
+ qtest_writel_fail(global_qtest, addr, value);
+}
+
+/**
+ * writeq_fail:
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 64-bit value to memory expecting a failure.
+ */
+static inline void writeq_fail(uint64_t addr, uint64_t value)
+{
+ qtest_writeq_fail(global_qtest, addr, value);
+}
+
+/**
+ * readb_fail:
+ * @addr: Guest address to read from.
+ *
+ * Reads an 8-bit value from memory expecting a failure.
+ */
+static inline void readb_fail(uint64_t addr)
+{
+ qtest_readb_fail(global_qtest, addr);
+}
+
+/**
+ * readw_fail:
+ * @addr: Guest address to read from.
+ *
+ * Reads a 16-bit value from memory expecting a failure.
+ */
+static inline void readw_fail(uint64_t addr)
+{
+ qtest_readw_fail(global_qtest, addr);
+}
+
+/**
+ * readl_fail:
+ * @addr: Guest address to read from.
+ *
+ * Reads a 32-bit value from memory expecting a failure.
+ */
+static inline void readl_fail(uint64_t addr)
+{
+ qtest_readl_fail(global_qtest, addr);
+}
+
+/**
+ * readq_fail:
+ * @addr: Guest address to read from.
+ *
+ * Reads a 64-bit value from memory expecting a failure.
+ */
+static inline void readq_fail(uint64_t addr)
+{
+ qtest_readq_fail(global_qtest, addr);
+}
+
/**
* memread:
* @addr: Guest address to read from.
diff --git a/tests/qtest/libqtest.h b/tests/qtest/libqtest.h
index beb96b18eb..f9bbeb2e60 100644
--- a/tests/qtest/libqtest.h
+++ b/tests/qtest/libqtest.h
@@ -549,6 +549,82 @@ uint32_t qtest_readl(QTestState *s, uint64_t addr);
*/
uint64_t qtest_readq(QTestState *s, uint64_t addr);
+/**
+ * qtest_writeb_fail:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes an 8-bit value to memory expecting a failure.
+ */
+void qtest_writeb_fail(QTestState *s, uint64_t addr, uint8_t value);
+
+/**
+ * qtest_writew_fail:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 16-bit value to memory expecting a failure.
+ */
+void qtest_writew_fail(QTestState *s, uint64_t addr, uint16_t value);
+
+/**
+ * qtest_writel_fail:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 32-bit value to memory expecting a failure.
+ */
+void qtest_writel_fail(QTestState *s, uint64_t addr, uint32_t value);
+
+/**
+ * qtest_writeq_fail:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 64-bit value to memory expecting a failure.
+ */
+void qtest_writeq_fail(QTestState *s, uint64_t addr, uint64_t value);
+
+/**
+ * qtest_readb_fail:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to read from.
+ *
+ * Reads an 8-bit value from memory expecting a failure.
+ */
+void qtest_readb_fail(QTestState *s, uint64_t addr);
+
+/**
+ * qtest_readw_fail:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to read from.
+ *
+ * Reads a 16-bit value from memory expecting a failure.
+ */
+void qtest_readw_fail(QTestState *s, uint64_t addr);
+
+/**
+ * qtest_readl_fail:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to read from.
+ *
+ * Reads a 32-bit value from memory expecting a failure.
+ */
+void qtest_readl_fail(QTestState *s, uint64_t addr);
+
+/**
+ * qtest_readq_fail:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to read from.
+ *
+ * Reads a 64-bit value from memory expecting a failure.
+ */
+void qtest_readq_fail(QTestState *s, uint64_t addr);
+
/**
* qtest_memread:
* @s: #QTestState instance to operate on.
diff --git a/system/qtest.c b/system/qtest.c
index 12703a2045..95bb80a2bc 100644
--- a/system/qtest.c
+++ b/system/qtest.c
@@ -514,26 +514,30 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
if (words[0][5] == 'b') {
uint8_t data = value;
- address_space_write(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED,
- &data, 1);
+ ret = address_space_write(first_cpu->as, addr,
+ MEMTXATTRS_UNSPECIFIED, &data, 1);
} else if (words[0][5] == 'w') {
uint16_t data = value;
tswap16s(&data);
- address_space_write(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED,
- &data, 2);
+ ret = address_space_write(first_cpu->as, addr,
+ MEMTXATTRS_UNSPECIFIED, &data, 2);
} else if (words[0][5] == 'l') {
uint32_t data = value;
tswap32s(&data);
- address_space_write(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED,
- &data, 4);
+ ret = address_space_write(first_cpu->as, addr,
+ MEMTXATTRS_UNSPECIFIED, &data, 4);
} else if (words[0][5] == 'q') {
uint64_t data = value;
tswap64s(&data);
- address_space_write(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED,
- &data, 8);
+ ret = address_space_write(first_cpu->as, addr,
+ MEMTXATTRS_UNSPECIFIED, &data, 8);
}
qtest_send_prefix(chr);
- qtest_send(chr, "OK\n");
+ if (ret == MEMTX_OK) {
+ qtest_send(chr, "OK\n");
+ } else {
+ qtest_send(chr, "FAIL\n");
+ }
} else if (strcmp(words[0], "readb") == 0 ||
strcmp(words[0], "readw") == 0 ||
strcmp(words[0], "readl") == 0 ||
@@ -548,26 +552,30 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
if (words[0][4] == 'b') {
uint8_t data;
- address_space_read(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED,
- &data, 1);
+ ret = address_space_read(first_cpu->as, addr,
+ MEMTXATTRS_UNSPECIFIED, &data, 1);
value = data;
} else if (words[0][4] == 'w') {
uint16_t data;
- address_space_read(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED,
- &data, 2);
+ ret = address_space_read(first_cpu->as, addr,
+ MEMTXATTRS_UNSPECIFIED, &data, 2);
value = tswap16(data);
} else if (words[0][4] == 'l') {
uint32_t data;
- address_space_read(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED,
- &data, 4);
+ ret = address_space_read(first_cpu->as, addr,
+ MEMTXATTRS_UNSPECIFIED, &data, 4);
value = tswap32(data);
} else if (words[0][4] == 'q') {
- address_space_read(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED,
- &value, 8);
+ ret = address_space_read(first_cpu->as, addr,
+ MEMTXATTRS_UNSPECIFIED, &value, 8);
tswap64s(&value);
}
qtest_send_prefix(chr);
- qtest_sendf(chr, "OK 0x%016" PRIx64 "\n", value);
+ if (ret == MEMTX_OK) {
+ qtest_sendf(chr, "OK 0x%016" PRIx64 "\n", value);
+ } else {
+ qtest_sendf(chr, "FAIL\n");
+ }
} else if (strcmp(words[0], "read") == 0) {
g_autoptr(GString) enc = NULL;
uint64_t addr, len;
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
index 9d07de1fbd..4055d6b953 100644
--- a/tests/qtest/libqtest.c
+++ b/tests/qtest/libqtest.c
@@ -666,7 +666,7 @@ static GString *qtest_client_socket_recv_line(QTestState *s)
return line;
}
-static gchar **qtest_rsp_args(QTestState *s, int expected_args)
+static gchar **_qtest_rsp_args(QTestState *s, int expected_args, bool fail)
{
GString *line;
gchar **words;
@@ -700,7 +700,11 @@ redo:
}
g_assert(words[0] != NULL);
- g_assert_cmpstr(words[0], ==, "OK");
+ if (fail) {
+ g_assert_cmpstr(words[0], ==, "FAIL");
+ } else {
+ g_assert_cmpstr(words[0], ==, "OK");
+ }
for (i = 0; i < expected_args; i++) {
g_assert(words[i] != NULL);
@@ -709,6 +713,11 @@ redo:
return words;
}
+static gchar **qtest_rsp_args(QTestState *s, int expected_args)
+{
+ return _qtest_rsp_args(s, expected_args, false);
+}
+
static void qtest_rsp(QTestState *s)
{
gchar **words = qtest_rsp_args(s, 0);
@@ -716,6 +725,13 @@ static void qtest_rsp(QTestState *s)
g_strfreev(words);
}
+static void qtest_rsp_fail(QTestState *s)
+{
+ gchar **words = _qtest_rsp_args(s, 0, true);
+
+ g_strfreev(words);
+}
+
static int qtest_query_target_endianness(QTestState *s)
{
gchar **args;
@@ -1103,6 +1119,13 @@ static void qtest_write(QTestState *s, const char *cmd, uint64_t addr,
qtest_rsp(s);
}
+static void qtest_write_fail(QTestState *s, const char *cmd, uint64_t addr,
+ uint64_t value)
+{
+ qtest_sendf(s, "%s 0x%" PRIx64 " 0x%" PRIx64 "\n", cmd, addr, value);
+ qtest_rsp_fail(s);
+}
+
void qtest_writeb(QTestState *s, uint64_t addr, uint8_t value)
{
qtest_write(s, "writeb", addr, value);
@@ -1123,6 +1146,26 @@ void qtest_writeq(QTestState *s, uint64_t addr, uint64_t value)
qtest_write(s, "writeq", addr, value);
}
+void qtest_writeb_fail(QTestState *s, uint64_t addr, uint8_t value)
+{
+ qtest_write_fail(s, "writeb", addr, value);
+}
+
+void qtest_writew_fail(QTestState *s, uint64_t addr, uint16_t value)
+{
+ qtest_write_fail(s, "writew", addr, value);
+}
+
+void qtest_writel_fail(QTestState *s, uint64_t addr, uint32_t value)
+{
+ qtest_write_fail(s, "writel", addr, value);
+}
+
+void qtest_writeq_fail(QTestState *s, uint64_t addr, uint64_t value)
+{
+ qtest_write_fail(s, "writeq", addr, value);
+}
+
static uint64_t qtest_read(QTestState *s, const char *cmd, uint64_t addr)
{
gchar **args;
@@ -1138,6 +1181,12 @@ static uint64_t qtest_read(QTestState *s, const char *cmd, uint64_t addr)
return value;
}
+static void qtest_read_fail(QTestState *s, const char *cmd, uint64_t addr)
+{
+ qtest_sendf(s, "%s 0x%" PRIx64 "\n", cmd, addr);
+ qtest_rsp_fail(s);
+}
+
uint8_t qtest_readb(QTestState *s, uint64_t addr)
{
return qtest_read(s, "readb", addr);
@@ -1158,6 +1207,26 @@ uint64_t qtest_readq(QTestState *s, uint64_t addr)
return qtest_read(s, "readq", addr);
}
+void qtest_readb_fail(QTestState *s, uint64_t addr)
+{
+ qtest_read_fail(s, "readb", addr);
+}
+
+void qtest_readw_fail(QTestState *s, uint64_t addr)
+{
+ qtest_read_fail(s, "readw", addr);
+}
+
+void qtest_readl_fail(QTestState *s, uint64_t addr)
+{
+ qtest_read_fail(s, "readl", addr);
+}
+
+void qtest_readq_fail(QTestState *s, uint64_t addr)
+{
+ qtest_read(s, "readq", addr);
+}
+
static int hex2nib(char ch)
{
if (ch >= '0' && ch <= '9') {
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 17/25] tests/qtest: add flexcomm tests
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (15 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 16/25] system/qtest: add APIS to check for memory access failures Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 18/25] tests/qtest: add flexcomm usart tests Octavian Purdila
` (7 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add flexcomm function selection unit tests.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
tests/qtest/flexcomm-test.c | 82 +++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 83 insertions(+)
create mode 100644 tests/qtest/flexcomm-test.c
diff --git a/tests/qtest/flexcomm-test.c b/tests/qtest/flexcomm-test.c
new file mode 100644
index 0000000000..ffbee8bfb8
--- /dev/null
+++ b/tests/qtest/flexcomm-test.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/main-loop.h"
+#include "exec/memory.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+
+#include "hw/misc/flexcomm.h"
+#include "hw/arm/svd/flexcomm.h"
+#include "hw/arm/svd/rt500.h"
+#include "reg-utils.h"
+
+#define FLEXCOMM_BASE RT500_FLEXCOMM0_BASE
+
+static void select_test(gconstpointer data)
+{
+ static const unsigned persel[] = {
+ FLEXCOMM_PERSEL_USART,
+ FLEXCOMM_PERSEL_SPI,
+ FLEXCOMM_PERSEL_I2C,
+ };
+
+ g_assert(REG32_READ_FIELD(FLEXCOMM, PSELID, PERSEL) == 0);
+
+ /* no register access until a function is selected */
+ readl_fail(FLEXCOMM_BASE);
+ writel_fail(FLEXCOMM_BASE, 0);
+
+ for (int i = 0; i < ARRAY_SIZE(persel); i++) {
+
+ REG32_WRITE(FLEXCOMM, PSELID, persel[i]);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM, PSELID, PERSEL), ==,
+ persel[i]);
+
+ /* test that we can access function registers */
+ writel(FLEXCOMM_BASE, 0xabcd);
+ readl(FLEXCOMM_BASE);
+ }
+
+ /* try to select something invalid */
+ REG32_WRITE(FLEXCOMM, PSELID, 7);
+ /* check for no function selected */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM, PSELID, PERSEL), ==, 0);
+
+ /* now select and lock USART */
+ REG32_WRITE(FLEXCOMM, PSELID,
+ FIELD_DP32(FLEXCOMM_PERSEL_USART, FLEXCOMM_PSELID, LOCK, 1));
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM, PSELID, PERSEL), ==,
+ FLEXCOMM_PERSEL_USART);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM, PSELID, LOCK), ==, 1);
+
+ /* try to change the selection to spi */
+ REG32_WRITE(FLEXCOMM, PSELID, FLEXCOMM_PERSEL_SPI);
+ /* it should still be locked USART */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM, PSELID, PERSEL), ==,
+ FLEXCOMM_PERSEL_USART);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM, PSELID, LOCK), ==, 1);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_data_func("/flexcomm/select", NULL, select_test);
+ qtest_start("-M rt595-evk");
+ ret = g_test_run();
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 2b90abf000..f35bb52aa2 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -233,6 +233,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \
(config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and
config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_FLEXCOMM')? ['flexcomm-test'] : []) + \
['arm-cpu-features',
'boot-serial-test']
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 18/25] tests/qtest: add flexcomm usart tests
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (16 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 17/25] tests/qtest: add flexcomm tests Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 19/25] hw/misc: add i2c-tester Octavian Purdila
` (6 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add flexcomm usart polling and irq unit tests.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
tests/qtest/flexcomm-usart-test.c | 316 ++++++++++++++++++++++++++++++
tests/qtest/meson.build | 3 +-
2 files changed, 318 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/flexcomm-usart-test.c
diff --git a/tests/qtest/flexcomm-usart-test.c b/tests/qtest/flexcomm-usart-test.c
new file mode 100644
index 0000000000..0ffa49dd6f
--- /dev/null
+++ b/tests/qtest/flexcomm-usart-test.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+
+#include "io/channel-socket.h"
+#include "qemu/config-file.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "qemu/sockets.h"
+#include "sysemu/sysemu.h"
+#include "qemu/main-loop.h"
+#include "qemu/option.h"
+#include "exec/memory.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+
+#include "hw/misc/flexcomm.h"
+#include "hw/arm/svd/flexcomm_usart.h"
+#include "hw/arm/svd/rt500.h"
+#include "reg-utils.h"
+
+#define FLEXCOMM_BASE RT500_FLEXCOMM0_BASE
+#define FLEXCOMM_USART_BASE RT500_FLEXCOMM0_BASE
+#define DEVICE_NAME "/machine/soc/flexcomm0"
+
+struct TestState {
+ QTestState *qtest;
+ QIOChannel *ioc;
+};
+
+static void polling_test(gconstpointer user_data)
+{
+ struct TestState *t = (struct TestState *)user_data;
+ uint32_t tmp;
+ char byte;
+ int fifo_size;
+ QDict *resp;
+
+ resp = qmp("{\"execute\": \"system_reset\"}");
+ qdict_unref(resp);
+
+ /* select and lock USART */
+ tmp = FIELD_DP32(FLEXCOMM_PERSEL_USART, FLEXCOMM_PSELID, LOCK, 1);
+ REG32_WRITE(FLEXCOMM, PSELID, tmp);
+
+ fifo_size = REG32_READ_FIELD(FLEXCOMM_USART, FIFOSIZE, FIFOSIZE);
+
+ /* enable USART */
+ REG32_WRITE_FIELD(FLEXCOMM_USART, CFG, ENABLE, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, CFG, ENABLE), ==, 1);
+
+ /* enable TX and RX FIFO */
+ REG32_WRITE_FIELD(FLEXCOMM_USART, FIFOCFG, ENABLETX, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOCFG, ENABLETX),
+ ==, 1);
+ REG32_WRITE_FIELD(FLEXCOMM_USART, FIFOCFG, ENABLERX, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOCFG, ENABLERX),
+ ==, 1);
+
+ /* test writes and fifo counters wrap */
+ for (int i = 0; i < fifo_size / 2; i++) {
+ /* check fifostat */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, RXFULL),
+ ==, 0);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, RXNOTEMPTY),
+ ==, 0);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, TXNOTFULL),
+ ==, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, TXEMPTY),
+ ==, 1);
+
+ REG32_WRITE(FLEXCOMM_USART, FIFOWR, 'a' + i);
+ qio_channel_read(t->ioc, &byte, 1, &error_abort);
+ g_assert_cmpuint(byte, ==, 'a' + i);
+ }
+
+ /* test reads and fifo level */
+
+ for (int i = 0; i < fifo_size / 2; i++) {
+ byte = 'A' + i;
+ g_assert_cmpuint(qio_channel_write(t->ioc, &byte, 1, &error_abort),
+ ==, 1);
+ }
+
+ /* wait for the RXLVL to update */
+ WAIT_REG32_FIELD(1000, FLEXCOMM_USART, FIFOSTAT, RXLVL,
+ fifo_size / 2);
+
+ /* check fifo stat */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, RXFULL),
+ ==, 0);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, RXNOTEMPTY),
+ ==, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, TXNOTFULL),
+ ==, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, TXEMPTY),
+ ==, 1);
+
+ /* send until FIFO is full */
+ for (int i = fifo_size / 2; i < fifo_size; i++) {
+ byte = 'A' + i;
+ g_assert_cmpuint(qio_channel_write(t->ioc, &byte, 1, &error_abort),
+ ==, 1);
+ }
+
+ /* wait for the RXLVL to update */
+ WAIT_REG32_FIELD(1000, FLEXCOMM_USART, FIFOSTAT, RXLVL, fifo_size);
+
+ /* check fifo stat */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, RXFULL),
+ ==, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, RXNOTEMPTY),
+ ==, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, TXNOTFULL),
+ ==, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, TXEMPTY),
+ ==, 1);
+
+ /* check read no pop */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFORDNOPOP, RXDATA),
+ ==, 'A');
+
+ /* now read from the fifo */
+ for (int i = 0; i < fifo_size; i++) {
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFORD, RXDATA),
+ ==, 'A' + i);
+ }
+
+ /* check fifostat */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, RXFULL), ==, 0);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, RXNOTEMPTY),
+ ==, 0);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, TXNOTFULL),
+ ==, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, TXEMPTY),
+ ==, 1);
+}
+
+static void irq_test(gconstpointer user_data)
+{
+ struct TestState *t = (struct TestState *)user_data;
+ char buf[256] = { 0, };
+ uint32_t tmp;
+ QDict *resp;
+
+ resp = qmp("{\"execute\": \"system_reset\"}");
+ qdict_unref(resp);
+
+ qtest_irq_intercept_out_named(t->qtest, DEVICE_NAME,
+ SYSBUS_DEVICE_GPIO_IRQ);
+
+ /* select and lock FLEXCOMM_USART */
+ tmp = FIELD_DP32(FLEXCOMM_PERSEL_USART, FLEXCOMM_PSELID, LOCK, 1);
+ REG32_WRITE(FLEXCOMM, PSELID, tmp);
+
+ /*
+ * set RX IRQ/DMA trigger level to 4 bytes - value 3 in FIFOTRIG
+ *
+ * 0000 - Trigger when the RX FIFO has received 1 entry (is no longer empty)
+ * 0001 - Trigger when the RX FIFO has received 2 entries
+ * 1111 - Trigger when the RX FIFO has received 16 entries (has become full)
+ */
+ REG32_WRITE_FIELD(FLEXCOMM_USART, FIFOTRIG, RXLVL, 3);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOTRIG, RXLVL),
+ ==, 3);
+
+ /* enable RX trigger for IRQ/DMA */
+ REG32_WRITE_FIELD(FLEXCOMM_USART, FIFOTRIG, RXLVLENA, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOTRIG, RXLVLENA),
+ ==, 1);
+
+ /* enable RXLVL interrupt */
+ REG32_WRITE_FIELD(FLEXCOMM_USART, FIFOINTENSET, RXLVL, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOINTENSET, RXLVL),
+ ==, 1);
+
+ /* enable FLEXCOMM_USART */
+ REG32_WRITE_FIELD(FLEXCOMM_USART, CFG, ENABLE, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, CFG, ENABLE),
+ ==, 1);
+
+ /* enable TX and RX FIFO */
+ REG32_WRITE_FIELD(FLEXCOMM_USART, FIFOCFG, ENABLETX, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOCFG, ENABLETX),
+ ==, 1);
+ REG32_WRITE_FIELD(FLEXCOMM_USART, FIFOCFG, ENABLERX, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOCFG, ENABLERX),
+ ==, 1);
+
+ /* check interrupt status */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOINTSTAT, RXLVL),
+ ==, 0);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOINTSTAT, TXLVL),
+ ==, 0);
+ g_assert_false(get_irq(0));
+
+ /* enable TX trigger for IRQ/DMA */
+ REG32_WRITE_FIELD(FLEXCOMM_USART, FIFOTRIG, TXLVLENA, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOTRIG, TXLVLENA),
+ ==, 1);
+
+ /* enable irq for TX */
+ REG32_WRITE_FIELD(FLEXCOMM_USART, FIFOINTENSET, TXLVL, 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOINTENSET, TXLVL),
+ ==, 1);
+
+ /* check TX irq */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOINTSTAT, TXLVL),
+ ==, 1);
+ g_assert_true(get_irq(0));
+
+ /* disable irq for TX */
+ REG32_WRITE_FIELD(FLEXCOMM_USART, FIFOTRIG, TXLVLENA, 0);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOTRIG, TXLVLENA),
+ ==, 0);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOINTSTAT, TXLVL),
+ ==, 0);
+ g_assert_false(get_irq(0));
+
+ /* send 3 bytes */
+ g_assert_cmpuint(qio_channel_write(t->ioc, buf, 3, &error_abort),
+ ==, 3);
+
+ /* check that we have 3 bytes in the fifo */
+ WAIT_REG32_FIELD(1000, FLEXCOMM_USART, FIFOSTAT, RXLVL, 3);
+
+ /* and no interrupt has been triggered yet */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOINTSTAT, RXLVL),
+ ==, 0);
+ g_assert_false(get_irq(0));
+
+ /* push it over the edge */
+ g_assert_cmpuint(qio_channel_write(t->ioc, buf, 1, &error_abort), ==, 1);
+
+ /* check that we have 4 bytes in the fifo */
+ WAIT_REG32_FIELD(1000, FLEXCOMM_USART, FIFOSTAT, RXLVL, 4);
+
+ /* and the interrupt has been triggered */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOINTSTAT, RXLVL),
+ ==, 1);
+ g_assert_true(get_irq(0));
+
+ /* read one byte from the fifo */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFORD, RXDATA),
+ ==, 0);
+
+ /* we should have 3 bytes in the FIFO */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOSTAT, RXLVL),
+ ==, 3);
+
+ /* and no interrupts active */
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_USART, FIFOINTSTAT, RXLVL),
+ ==, 0);
+ g_assert_false(get_irq(0));
+}
+
+static void close_ioc(void *ioc)
+{
+ qio_channel_close(ioc, NULL);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ struct TestState test;
+ char *tmp_path = g_dir_make_tmp("qemu-flexcomm-usart-test.XXXXXX", NULL);
+ SocketAddress addr = {
+ .type = SOCKET_ADDRESS_TYPE_UNIX,
+ .u.q_unix.path = g_build_filename(tmp_path, "sock", NULL),
+ };
+ char *args;
+ QIOChannelSocket *lioc;
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+
+ lioc = qio_channel_socket_new();
+ qio_channel_socket_listen_sync(lioc, &addr, 1, &error_abort);
+
+ qtest_add_data_func("/flexcomm-usart/polling", &test, polling_test);
+ qtest_add_data_func("/flexcomm-usart/irq", &test, irq_test);
+
+ args = g_strdup_printf("-M rt595-evk "
+ "-chardev socket,id=flexcomm0-usart,path=%s",
+ addr.u.q_unix.path);
+ test.qtest = qtest_start(args);
+
+ qio_channel_wait(QIO_CHANNEL(lioc), G_IO_IN);
+ test.ioc = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
+ g_assert(test.ioc);
+ qtest_add_abrt_handler(close_ioc, test.ioc);
+
+ ret = g_test_run();
+
+ qtest_end();
+
+ qtest_remove_abrt_handler(test.ioc);
+ g_unlink(addr.u.q_unix.path);
+ g_free(addr.u.q_unix.path);
+ g_rmdir(tmp_path);
+ g_free(tmp_path);
+ g_free(args);
+ object_unref(OBJECT(test.ioc));
+ object_unref(OBJECT(lioc));
+
+ return ret;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index f35bb52aa2..c7a5bb61e9 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -233,7 +233,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \
(config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and
config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \
- (config_all_devices.has_key('CONFIG_FLEXCOMM')? ['flexcomm-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_FLEXCOMM') ? ['flexcomm-test', 'flexcomm-usart-test'] : []) + \
['arm-cpu-features',
'boot-serial-test']
@@ -350,6 +350,7 @@ qtests = {
'virtio-net-failover': files('migration-helpers.c'),
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
'netdev-socket': files('netdev-socket.c', '../unit/socket-helpers.c'),
+ 'flexcomm-usart-test': [io],
}
if vnc.found()
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 19/25] hw/misc: add i2c-tester
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (17 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 18/25] tests/qtest: add flexcomm usart tests Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 20/25] tests/qtest: add tests for flexcomm i2c Octavian Purdila
` (5 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add a simple i2c peripheral to be used for testing I2C device
models. The peripheral has a fixed number of registers that can be
read and written.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/misc/i2c_tester.h | 43 ++++++++++++++
hw/misc/i2c_tester.c | 109 +++++++++++++++++++++++++++++++++++
hw/misc/Kconfig | 5 ++
hw/misc/meson.build | 2 +
4 files changed, 159 insertions(+)
create mode 100644 include/hw/misc/i2c_tester.h
create mode 100644 hw/misc/i2c_tester.c
diff --git a/include/hw/misc/i2c_tester.h b/include/hw/misc/i2c_tester.h
new file mode 100644
index 0000000000..f8292fc219
--- /dev/null
+++ b/include/hw/misc/i2c_tester.h
@@ -0,0 +1,43 @@
+/*
+ * Simple I2C peripheral for testing I2C device models.
+ *
+ * At the moment of introducing this not all of the functionality can be tested
+ * with an existing QEMU peripheral device, notably error paths such as when a
+ * peripheral device responds with an I2C_NACK during a transaction.
+ *
+ * It also provides a place where new future functionality can be added to help
+ * with more kinds of tests rather than trying to hack it in a real device where
+ * it might not even be possible.
+ *
+ * The peripheral allows reading and writing to a fixed number of registers.
+ * The first transmitted byte in a transaction sets the index register. Note
+ * that the index register is not auto-incremented on neither reads nor writes.
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_I2C_TESTER_H
+#define HW_I2C_TESTER_H
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "hw/irq.h"
+
+#define I2C_TESTER_NUM_REGS 0x31
+
+#define TYPE_I2C_TESTER "i2c-tester"
+#define I2C_TESTER(obj) OBJECT_CHECK(I2cTesterState, (obj), TYPE_I2C_TESTER)
+
+typedef struct {
+ I2CSlave i2c;
+ bool set_reg_idx;
+ uint8_t reg_idx;
+ uint8_t regs[I2C_TESTER_NUM_REGS];
+} I2cTesterState;
+
+#endif /* HW_I2C_TESTER_H */
diff --git a/hw/misc/i2c_tester.c b/hw/misc/i2c_tester.c
new file mode 100644
index 0000000000..77ce8bf91a
--- /dev/null
+++ b/hw/misc/i2c_tester.c
@@ -0,0 +1,109 @@
+/*
+ * Simple I2C peripheral for testing I2C device models.
+ *
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "hw/misc/i2c_tester.h"
+
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "migration/vmstate.h"
+
+static void i2c_tester_reset_enter(Object *o, ResetType type)
+{
+ I2cTesterState *s = I2C_TESTER(o);
+
+ s->set_reg_idx = false;
+ s->reg_idx = 0;
+ memset(s->regs, 0, I2C_TESTER_NUM_REGS);
+}
+
+static int i2c_tester_event(I2CSlave *i2c, enum i2c_event event)
+{
+ I2cTesterState *s = I2C_TESTER(i2c);
+
+ if (event == I2C_START_SEND) {
+ s->set_reg_idx = true;
+ }
+
+ return 0;
+}
+
+static uint8_t i2c_tester_rx(I2CSlave *i2c)
+{
+ I2cTesterState *s = I2C_TESTER(i2c);
+
+ if (s->reg_idx >= I2C_TESTER_NUM_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid reg 0x%02x\n", __func__,
+ s->reg_idx);
+ return I2C_NACK;
+ }
+
+ return s->regs[s->reg_idx];
+}
+
+static int i2c_tester_tx(I2CSlave *i2c, uint8_t data)
+{
+ I2cTesterState *s = I2C_TESTER(i2c);
+
+ if (s->set_reg_idx) {
+ /* Setting the register in which the operation will be done. */
+ s->reg_idx = data;
+ s->set_reg_idx = false;
+ return 0;
+ }
+
+ if (s->reg_idx >= I2C_TESTER_NUM_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid reg 0x%02x\n", __func__,
+ s->reg_idx);
+ return I2C_NACK;
+ }
+
+ /* Write reg data. */
+ s->regs[s->reg_idx] = data;
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_i2c_tester = {
+ .name = "i2c-tester",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_I2C_SLAVE(i2c, I2cTesterState),
+ VMSTATE_BOOL(set_reg_idx, I2cTesterState),
+ VMSTATE_UINT8(reg_idx, I2cTesterState),
+ VMSTATE_UINT8_ARRAY(regs, I2cTesterState, I2C_TESTER_NUM_REGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void i2c_tester_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ ResettableClass *rc = RESETTABLE_CLASS(oc);
+ I2CSlaveClass *isc = I2C_SLAVE_CLASS(oc);
+
+ rc->phases.enter = i2c_tester_reset_enter;
+ dc->vmsd = &vmstate_i2c_tester;
+ isc->event = i2c_tester_event;
+ isc->recv = i2c_tester_rx;
+ isc->send = i2c_tester_tx;
+}
+
+static const TypeInfo i2c_tester_types[] = {
+ {
+ .name = TYPE_I2C_TESTER,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(I2cTesterState),
+ .class_init = i2c_tester_class_init
+ },
+};
+
+DEFINE_TYPES(i2c_tester_types);
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 4b688aead2..3e93c12c8e 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -213,6 +213,11 @@ config IOSB
config XLNX_VERSAL_TRNG
bool
+config I2C_TESTER
+ bool
+ default y if TEST_DEVICES
+ depends on I2C
+
config FLEXCOMM
bool
select I2C
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index bf0988fd43..cd29db37d7 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -152,6 +152,8 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
# HPPA devices
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
+system_ss.add(when: 'CONFIG_I2C_TESTER', if_true: files('i2c_tester.c'))
+
system_ss.add(when: 'CONFIG_FLEXCOMM', if_true: files('flexcomm.c'))
system_ss.add(when: 'CONFIG_RT500_CLKCTL', if_true: files('rt500_clkctl0.c', 'rt500_clkctl1.c'))
system_ss.add(when: 'CONFIG_RT500_RSTCTL', if_true: files('rt500_rstctl.c'))
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 20/25] tests/qtest: add tests for flexcomm i2c
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (18 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 19/25] hw/misc: add i2c-tester Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 21/25] hw/ssi: allow NULL realize callbacks for peripherals Octavian Purdila
` (4 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add master mode tests for flexcomm i2c.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
tests/qtest/flexcomm-i2c-test.c | 170 ++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 2 +-
2 files changed, 171 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/flexcomm-i2c-test.c
diff --git a/tests/qtest/flexcomm-i2c-test.c b/tests/qtest/flexcomm-i2c-test.c
new file mode 100644
index 0000000000..45f31da7de
--- /dev/null
+++ b/tests/qtest/flexcomm-i2c-test.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2024 Google LLC.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "qemu/config-file.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "qemu/sockets.h"
+#include "sysemu/sysemu.h"
+#include "qemu/main-loop.h"
+#include "qemu/option.h"
+#include "exec/memory.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-core.h"
+
+#include "hw/misc/flexcomm.h"
+#include "hw/arm/svd/flexcomm_i2c.h"
+#include "hw/arm/svd/rt500.h"
+#include "hw/misc/i2c_tester.h"
+#include "reg-utils.h"
+
+#define PERIPH_ADDR (0x50)
+#define INVALID_ADDR (0x10)
+
+#define REG_ADDR 11
+#define REG_VALUE 0xAA
+
+#define FLEXCOMM_BASE RT500_FLEXCOMM0_BASE
+#define FLEXCOMM_I2C_BASE RT500_FLEXCOMM0_BASE
+#define DEVICE_NAME "/machine/soc/flexcomm0"
+
+struct TestState {
+ QTestState *qtest;
+};
+
+static void master_test(gconstpointer user_data)
+{
+ struct TestState *t = (struct TestState *)user_data;
+ uint32_t tmp;
+
+ qtest_irq_intercept_out_named(t->qtest, DEVICE_NAME,
+ SYSBUS_DEVICE_GPIO_IRQ);
+
+ /* Select and lock I2C */
+ tmp = FLEXCOMM_PERSEL_I2C;
+ FIELD_DP32(tmp, FLEXCOMM_PSELID, LOCK, 1);
+ REG32_WRITE(FLEXCOMM, PSELID, tmp);
+
+ /* Enable master mode */
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, CFG, MSTEN, 1);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, CFG, MSTEN) == 1);
+
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTPENDING) == 1);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_IDLE);
+
+ /* Enable interrupts */
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, INTENSET, MSTPENDINGEN, 1);
+ g_assert_true(get_irq(0));
+
+ /* start for invalid address */
+ REG32_WRITE(FLEXCOMM_I2C, MSTDAT, INVALID_ADDR);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTART, 1);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_NAKADR);
+ g_assert_true(get_irq(0));
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTOP, 1);
+
+ /* write past the last register */
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, MSTDAT, DATA, PERIPH_ADDR);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTART, 1);
+ g_assert_true(get_irq(0));
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_TXRDY);
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, MSTDAT, DATA, I2C_TESTER_NUM_REGS + 10);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTCONTINUE, 1);
+ g_assert_true(get_irq(0));
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_TXRDY);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTCONTINUE, 1);
+ g_assert_true(get_irq(0));
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_NAKDAT);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTOP, 1);
+
+ /* write value to register */
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, MSTDAT, DATA, PERIPH_ADDR);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTART, 1);
+ g_assert_true(get_irq(0));
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_TXRDY);
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, MSTDAT, DATA, REG_ADDR);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTCONTINUE, 1);
+ g_assert_true(get_irq(0));
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_TXRDY);
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, MSTDAT, DATA, REG_VALUE);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTCONTINUE, 1);
+ g_assert_true(get_irq(0));
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_TXRDY);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTOP, 1);
+ g_assert_true(get_irq(0));
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_IDLE);
+
+ /* read value back from register */
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, MSTDAT, DATA, PERIPH_ADDR);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTART, 1);
+ g_assert_true(get_irq(0));
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_TXRDY);
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, MSTDAT, DATA, REG_ADDR);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTCONTINUE, 1);
+ g_assert_true(get_irq(0));
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_TXRDY);
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, MSTDAT, DATA, (PERIPH_ADDR + 1));
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTART, 1);
+ g_assert_true(get_irq(0));
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_RXRDY);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_I2C, MSTDAT, DATA), ==,
+ REG_VALUE);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTOP, 1);
+
+ /*
+ * Check that the master ended the transaction (i.e. i2c_end_transfer was
+ * called). If the master does not properly end the transaction this would
+ * be seen as a restart and it would not be NACKed.
+ */
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, MSTDAT, DATA, INVALID_ADDR);
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTART, 1);
+
+ g_assert(REG32_READ_FIELD(FLEXCOMM_I2C, STAT, MSTSTATE) ==
+ MSTSTATE_NAKADR);
+ g_assert_true(get_irq(0));
+ REG32_WRITE_FIELD_NOUPDATE(FLEXCOMM_I2C, MSTCTL, MSTSTOP, 1);
+
+ /* Disable interrupts */
+ REG32_WRITE_FIELD(FLEXCOMM_I2C, INTENCLR, MSTPENDINGCLR, 1);
+ g_assert_false(get_irq(0));
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ struct TestState test;
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_data_func("/flexcomm-i2c/master", &test, master_test);
+
+ test.qtest = qtest_start("-M rt595-evk "
+ "-device i2c-tester,address=0x50,bus=/flexcomm0-i2c");
+ ret = g_test_run();
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index c7a5bb61e9..c21b18c304 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -233,7 +233,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \
(config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and
config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \
- (config_all_devices.has_key('CONFIG_FLEXCOMM') ? ['flexcomm-test', 'flexcomm-usart-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_FLEXCOMM') ? ['flexcomm-test', 'flexcomm-usart-test', 'flexcomm-i2c-test'] : []) + \
['arm-cpu-features',
'boot-serial-test']
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 21/25] hw/ssi: allow NULL realize callbacks for peripherals
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (19 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 20/25] tests/qtest: add tests for flexcomm i2c Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 22/25] hw/misc: add spi-tester Octavian Purdila
` (3 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Signed-off-by: Octavian Purdila <tavip@google.com>
---
hw/ssi/ssi.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/hw/ssi/ssi.c b/hw/ssi/ssi.c
index 3f357e8f16..d1f3ce7c22 100644
--- a/hw/ssi/ssi.c
+++ b/hw/ssi/ssi.c
@@ -105,7 +105,9 @@ static void ssi_peripheral_realize(DeviceState *dev, Error **errp)
}
s->spc = ssc;
- ssc->realize(s, errp);
+ if (ssc->realize) {
+ ssc->realize(s, errp);
+ }
}
static Property ssi_peripheral_properties[] = {
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 22/25] hw/misc: add spi-tester
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (20 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 21/25] hw/ssi: allow NULL realize callbacks for peripherals Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 23/25] tests/qtest: add tests for flexcomm spi Octavian Purdila
` (2 subsequent siblings)
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add a simple SPI peripheral that echoes back received data. Useful for
testing SPI controllers.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/misc/spi_tester.h | 32 +++++++++++++++++
hw/misc/spi_tester.c | 67 ++++++++++++++++++++++++++++++++++++
hw/misc/Kconfig | 5 +++
hw/misc/meson.build | 1 +
4 files changed, 105 insertions(+)
create mode 100644 include/hw/misc/spi_tester.h
create mode 100644 hw/misc/spi_tester.c
diff --git a/include/hw/misc/spi_tester.h b/include/hw/misc/spi_tester.h
new file mode 100644
index 0000000000..8935f3f1af
--- /dev/null
+++ b/include/hw/misc/spi_tester.h
@@ -0,0 +1,32 @@
+/*
+ * Simple SPI peripheral device used for SPI controller testing.
+ *
+ * Copyright (c) 2024 Google LLC.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_SPI_TESTER_H
+#define HW_SPI_TESTER_H
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/bswap.h"
+#include "hw/irq.h"
+#include "hw/ssi/ssi.h"
+#include "qemu/timer.h"
+#include "hw/qdev-properties.h"
+
+#define TYPE_SPI_TESTER "spi-tester"
+#define SPI_TESTER(obj) OBJECT_CHECK(SpiTesterState, (obj), TYPE_SPI_TESTER)
+
+typedef struct {
+ SSIPeripheral ssidev;
+ bool cs;
+} SpiTesterState;
+
+#endif /* HW_SPI_TESTER_H */
diff --git a/hw/misc/spi_tester.c b/hw/misc/spi_tester.c
new file mode 100644
index 0000000000..2793ce52dc
--- /dev/null
+++ b/hw/misc/spi_tester.c
@@ -0,0 +1,67 @@
+/*
+ * Simple SPI peripheral echo device used for SPI controller testing.
+ *
+ * Copyright (c) 2024 Google LLC.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "migration/vmstate.h"
+#include "hw/misc/spi_tester.h"
+
+static uint32_t spi_tester_transfer(SSIPeripheral *dev, uint32_t value)
+{
+ SpiTesterState *s = SPI_TESTER(dev);
+
+ if (s->cs) {
+ return 0;
+ }
+
+ return value;
+}
+
+static int spi_tester_set_cs(SSIPeripheral *dev, bool select)
+{
+ SpiTesterState *s = SPI_TESTER(dev);
+
+ s->cs = select;
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_spi_tester = {
+ .name = "spi-tester",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_SSI_PERIPHERAL(ssidev, SpiTesterState),
+ VMSTATE_BOOL(cs, SpiTesterState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void spi_tester_class_init(ObjectClass *klass, void *data)
+{
+ SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_spi_tester;
+ k->transfer = spi_tester_transfer;
+ k->set_cs = spi_tester_set_cs;
+ k->cs_polarity = SSI_CS_LOW;
+}
+
+static const TypeInfo spi_tester_types[] = {
+ {
+ .name = TYPE_SPI_TESTER,
+ .parent = TYPE_SSI_PERIPHERAL,
+ .instance_size = sizeof(SpiTesterState),
+ .class_init = spi_tester_class_init,
+ },
+};
+
+DEFINE_TYPES(spi_tester_types);
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 3e93c12c8e..484ee3149f 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -218,6 +218,11 @@ config I2C_TESTER
default y if TEST_DEVICES
depends on I2C
+config SPI_TESTER
+ bool
+ default y if TEST_DEVICES
+ depends on SSI
+
config FLEXCOMM
bool
select I2C
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index cd29db37d7..0cece9a964 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -153,6 +153,7 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_I2C_TESTER', if_true: files('i2c_tester.c'))
+system_ss.add(when: 'CONFIG_SPI_TESTER', if_true: files('spi_tester.c'))
system_ss.add(when: 'CONFIG_FLEXCOMM', if_true: files('flexcomm.c'))
system_ss.add(when: 'CONFIG_RT500_CLKCTL', if_true: files('rt500_clkctl0.c', 'rt500_clkctl1.c'))
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 23/25] tests/qtest: add tests for flexcomm spi
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (21 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 22/25] hw/misc: add spi-tester Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 24/25] systems/qtest: add device clock APIs Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 25/25] tests/qtest: add tests for RT500's clock controller Octavian Purdila
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
From: Sebastian Ene <sebastianene@google.com>
Add master and loopback tests for flexcomm spi.
Signed-off-by: Sebastian Ene <sebastianene@google.com>
[tavip: add master mode test, convert to qtest]
Signed-off-by: Octavian Purdila <tavip@google.com>
---
tests/qtest/flexcomm-spi-test.c | 145 ++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 2 +-
2 files changed, 146 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/flexcomm-spi-test.c
diff --git a/tests/qtest/flexcomm-spi-test.c b/tests/qtest/flexcomm-spi-test.c
new file mode 100644
index 0000000000..4658835b8f
--- /dev/null
+++ b/tests/qtest/flexcomm-spi-test.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2024 Google LLC.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "qemu/config-file.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "qemu/sockets.h"
+#include "sysemu/sysemu.h"
+#include "qemu/main-loop.h"
+#include "qemu/option.h"
+#include "exec/memory.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-core.h"
+
+#include "hw/misc/flexcomm.h"
+#include "hw/arm/svd/flexcomm_spi.h"
+#include "hw/arm/svd/rt500.h"
+#include "reg-utils.h"
+
+/* The number of words sent on the SPI in loopback mode. */
+#define SEQ_LOOPBACK_MODE (8)
+
+/* This value is used to set the cycle counter for the spi tester */
+#define SPI_TESTER_CONFIG (0x10)
+
+#define FLEXCOMM_BASE RT500_FLEXCOMM0_BASE
+#define FLEXCOMM_SPI_BASE RT500_FLEXCOMM0_BASE
+#define DEVICE_NAME "/machine/soc/flexcomm0"
+
+static void configure_spi(bool master, bool is_loopback_mode)
+{
+ uint32_t tmp;
+
+ /* Select and lock SPI */
+ tmp = FLEXCOMM_PERSEL_SPI;
+ FIELD_DP32(tmp, FLEXCOMM_PSELID, LOCK, 1);
+ REG32_WRITE(FLEXCOMM, PSELID, tmp);
+
+ /* Disable the FIFO */
+ REG32_WRITE_FIELD(FLEXCOMM_SPI, CFG, ENABLE, 0);
+ REG32_WRITE_FIELD(FLEXCOMM_SPI, FIFOCFG, ENABLETX, 0);
+ REG32_WRITE_FIELD(FLEXCOMM_SPI, FIFOCFG, ENABLERX, 0);
+
+ if (is_loopback_mode) {
+ /* Set up SPI interface - loop mode, master mode */
+ REG32_WRITE_FIELD(FLEXCOMM_SPI, CFG, LOOP, 1);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, CFG, LOOP) == 1);
+ }
+
+ if (master) {
+ REG32_WRITE_FIELD(FLEXCOMM_SPI, CFG, MASTER, 1);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, CFG, MASTER) == 1);
+ } else {
+ REG32_WRITE_FIELD(FLEXCOMM_SPI, CFG, MASTER, 0);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, CFG, MASTER) == 0);
+ }
+
+ /* Enable the FIFO */
+ REG32_WRITE_FIELD(FLEXCOMM_SPI, FIFOCFG, ENABLETX, 1);
+ REG32_WRITE_FIELD(FLEXCOMM_SPI, FIFOCFG, ENABLERX, 1);
+
+ /* Enable the SPI */
+ REG32_WRITE_FIELD(FLEXCOMM_SPI, CFG, ENABLE, 1);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, CFG, ENABLE) == 1);
+}
+
+/* The SPI controller running in master mode can run in loopback mode for */
+/* internal testing. Transmit and receive lines are connected together. */
+static void loopback_test(gconstpointer user_data)
+{
+ configure_spi(true, true);
+
+ /* Write a sequence */
+ for (int i = 0; i < SEQ_LOOPBACK_MODE; i++) {
+ REG32_WRITE(FLEXCOMM_SPI, FIFOWR, i);
+ }
+
+ /* Read the sequence back */
+ for (int i = 0; i < SEQ_LOOPBACK_MODE; i++) {
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, FIFORD, RXDATA) == i);
+ }
+}
+
+static void master_test(gconstpointer user_data)
+{
+ uint32_t tmp;
+
+ configure_spi(true, false);
+
+ REG32_WRITE_FIELD(FLEXCOMM_SPI, CFG, LSBF, 1);
+
+ /* single 16bit word transfer */
+
+ tmp = FIELD_DP32(0x1122, FLEXCOMM_SPI_FIFOWR, EOT, 1);
+ tmp = FIELD_DP32(tmp, FLEXCOMM_SPI_FIFOWR, TXSSEL0_N, 1);
+ tmp = FIELD_DP32(tmp, FLEXCOMM_SPI_FIFOWR, LEN, 0xF);
+ REG32_WRITE(FLEXCOMM_SPI, FIFOWR, tmp);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, FIFOSTAT, RXNOTEMPTY) == 1);
+ g_assert_cmpuint(REG32_READ_FIELD(FLEXCOMM_SPI, FIFORD, RXDATA),
+ ==, 0x1122);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, FIFOSTAT, RXNOTEMPTY) == 0);
+
+ /* multi word 8 bits transfer */
+
+ tmp = FIELD_DP32(0x11, FLEXCOMM_SPI_FIFOWR, TXSSEL0_N, 1);
+ tmp = FIELD_DP32(tmp, FLEXCOMM_SPI_FIFOWR, LEN, 0x7);
+ REG32_WRITE(FLEXCOMM_SPI, FIFOWR, tmp);
+ tmp = 0x22;
+ FIELD_DP32(tmp, FLEXCOMM_SPI_FIFOWR, EOT, 1);
+ FIELD_DP32(tmp, FLEXCOMM_SPI_FIFOWR, TXSSEL0_N, 1);
+ FIELD_DP32(tmp, FLEXCOMM_SPI_FIFOWR, LEN, 0x7);
+ REG32_WRITE(FLEXCOMM_SPI, FIFOWR, tmp);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, FIFOSTAT, RXNOTEMPTY) == 1);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, FIFORD, RXDATA) == 0x11);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, FIFOSTAT, RXNOTEMPTY) == 1);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, FIFORD, RXDATA) == 0x22);
+ g_assert(REG32_READ_FIELD(FLEXCOMM_SPI, FIFOSTAT, RXNOTEMPTY) == 0);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_data_func("/flexcomm-spi/loopack", NULL, loopback_test);
+ qtest_add_data_func("/flexcomm-spi/master", NULL, master_test);
+
+ qtest_start("-M rt595-evk -device spi-tester,bus=/flexcomm0-spi");
+ ret = g_test_run();
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index c21b18c304..d3bf33b855 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -233,7 +233,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \
(config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and
config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \
- (config_all_devices.has_key('CONFIG_FLEXCOMM') ? ['flexcomm-test', 'flexcomm-usart-test', 'flexcomm-i2c-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_FLEXCOMM') ? ['flexcomm-test', 'flexcomm-usart-test', 'flexcomm-i2c-test', 'flexcomm-spi-test'] : []) + \
['arm-cpu-features',
'boot-serial-test']
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 24/25] systems/qtest: add device clock APIs
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (22 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 23/25] tests/qtest: add tests for flexcomm spi Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
2024-10-08 1:18 ` [PATCH v2 25/25] tests/qtest: add tests for RT500's clock controller Octavian Purdila
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add qtest APIs to check the device clock frequency.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
include/hw/qdev-clock.h | 10 +++++++
tests/qtest/libqtest-single.h | 24 +++++++++++++++++
tests/qtest/libqtest.h | 22 +++++++++++++++
hw/core/qdev-clock.c | 2 +-
system/qtest.c | 51 +++++++++++++++++++++++++++++++++++
tests/qtest/libqtest.c | 29 ++++++++++++++++++++
6 files changed, 137 insertions(+), 1 deletion(-)
diff --git a/include/hw/qdev-clock.h b/include/hw/qdev-clock.h
index ffa0f7ba09..19ed34ae88 100644
--- a/include/hw/qdev-clock.h
+++ b/include/hw/qdev-clock.h
@@ -15,6 +15,7 @@
#define QDEV_CLOCK_H
#include "hw/clock.h"
+#include "hw/qdev-core.h"
/**
* qdev_init_clock_in:
@@ -161,4 +162,13 @@ typedef struct ClockPortInitElem ClockPortInitArray[];
*/
void qdev_init_clocks(DeviceState *dev, const ClockPortInitArray clocks);
+/**
+ * qdev_get_clocklist:
+ * @dev: the device to find clock for
+ * @name: clock name
+ *
+ * Returns: a named clock list entry or NULL if the clock was not found
+ */
+NamedClockList *qdev_get_clocklist(DeviceState *dev, const char *name);
+
#endif /* QDEV_CLOCK_H */
diff --git a/tests/qtest/libqtest-single.h b/tests/qtest/libqtest-single.h
index c22037c8b2..51eb69ff74 100644
--- a/tests/qtest/libqtest-single.h
+++ b/tests/qtest/libqtest-single.h
@@ -408,4 +408,28 @@ static inline int64_t clock_step(int64_t step)
return qtest_clock_step(global_qtest, step);
}
+/**
+ * qtest_qdev_clock_in_get_hz:
+ * @path: QOM path of a device.
+ * @name: Clock name.
+ *
+ * Returns: device clock frequency in HZ
+ */
+static inline uint64_t dev_clock_in_get_hz(const char *path, const char *name)
+{
+ return qtest_dev_clock_in_get_hz(global_qtest, path, name);
+}
+
+/**
+ * qtest_qdev_clock_out_get_hz:
+ * @path: QOM path of a device.
+ * @name: Clock name.
+ *
+ * Returns: device clock frequency in HZ
+ */
+static inline uint64_t dev_clock_out_get_hz(const char *path, const char *name)
+{
+ return qtest_dev_clock_out_get_hz(global_qtest, path, name);
+}
+
#endif
diff --git a/tests/qtest/libqtest.h b/tests/qtest/libqtest.h
index f9bbeb2e60..cfb7098985 100644
--- a/tests/qtest/libqtest.h
+++ b/tests/qtest/libqtest.h
@@ -1169,4 +1169,26 @@ bool have_qemu_img(void);
*/
bool mkimg(const char *file, const char *fmt, unsigned size_mb);
+/**
+ * qtest_qdev_clock_in_get_hz:
+ * @s: #QTestState instance to operate on.
+ * @path: QOM path of a device.
+ * @name: Clock name.
+ *
+ * Returns: device clock frequency in HZ
+ */
+uint64_t qtest_dev_clock_in_get_hz(QTestState *s, const char *path,
+ const char *name);
+
+/**
+ * qtest_qdev_clock_out_get_hz:
+ * @s: #QTestState instance to operate on.
+ * @path: QOM path of a device.
+ * @name: Clock name.
+ *
+ * Returns: device clock frequency in HZ
+ */
+uint64_t qtest_dev_clock_out_get_hz(QTestState *s, const char *path,
+ const char *name);
+
#endif
diff --git a/hw/core/qdev-clock.c b/hw/core/qdev-clock.c
index 82799577f3..3c9e2d5d73 100644
--- a/hw/core/qdev-clock.c
+++ b/hw/core/qdev-clock.c
@@ -144,7 +144,7 @@ void qdev_init_clocks(DeviceState *dev, const ClockPortInitArray clocks)
}
}
-static NamedClockList *qdev_get_clocklist(DeviceState *dev, const char *name)
+NamedClockList *qdev_get_clocklist(DeviceState *dev, const char *name)
{
NamedClockList *ncl;
diff --git a/system/qtest.c b/system/qtest.c
index 95bb80a2bc..465666a416 100644
--- a/system/qtest.c
+++ b/system/qtest.c
@@ -19,6 +19,7 @@
#include "exec/ioport.h"
#include "exec/memory.h"
#include "exec/tswap.h"
+#include "hw/qdev-clock.h"
#include "hw/qdev-core.h"
#include "hw/irq.h"
#include "hw/core/cpu.h"
@@ -245,6 +246,20 @@ static void *qtest_server_send_opaque;
*
* Forcibly set the given interrupt pin to the given level.
*
+ * Device clock frequency
+ * """"""""""""""""""""""
+ *
+ * .. code-block:: none
+ *
+ * > qdev_clock_out_get_hz QOM-PATH CLOCK-NAME
+ * < OK HZ
+ *
+ * .. code-block:: none
+ *
+ * > qdev_clock_in_get_hz QOM-PATH CLOCK-NAME
+ * < OK HZ
+ *
+ * where HZ is the clock frequency in hertz.
*/
static int hex2nib(char ch)
@@ -758,6 +773,42 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
qtest_send_prefix(chr);
qtest_sendf(chr, "OK %"PRIi64"\n",
(int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ } else if (strcmp(words[0], "qdev_clock_in_get_hz") == 0 ||
+ strcmp(words[0], "qdev_clock_out_get_hz") == 0) {
+ bool is_outbound = words[0][11] == 'o';
+ DeviceState *dev;
+ NamedClockList *ncl;
+
+ g_assert(words[1]);
+ g_assert(words[2]);
+
+ dev = DEVICE(object_resolve_path(words[1], NULL));
+ if (!dev) {
+ qtest_send_prefix(chr);
+ qtest_send(chr, "FAIL Unknown device\n");
+ return;
+ }
+
+ ncl = qdev_get_clocklist(dev, words[2]);
+ if (!ncl) {
+ qtest_send_prefix(chr);
+ qtest_send(chr, "FAIL Unknown clock\n");
+ return;
+ }
+
+ if (is_outbound && !ncl->output) {
+ qtest_send_prefix(chr);
+ qtest_send(chr, "FAIL Not an output clock\n");
+ return;
+ }
+
+ if (!is_outbound && ncl->output) {
+ qtest_send_prefix(chr);
+ qtest_send(chr, "FAIL Not an input clock\n");
+ return;
+ }
+
+ qtest_sendf(chr, "OK %u\n", clock_get_hz(ncl->clock));
} else if (process_command_cb && process_command_cb(chr, words)) {
/* Command got consumed by the callback handler */
} else {
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
index 4055d6b953..cc11b5f42e 100644
--- a/tests/qtest/libqtest.c
+++ b/tests/qtest/libqtest.c
@@ -2065,3 +2065,32 @@ bool mkimg(const char *file, const char *fmt, unsigned size_mb)
return ret && !err;
}
+
+static uint64_t qtest_dev_clock_get_hz(QTestState *s, const char *path,
+ const char *name, bool out)
+{
+ gchar **args;
+ int ret;
+ uint64_t value;
+
+ qtest_sendf(s, "qdev_clock_%s_get_hz %s %s\n", out ? "out" : "in",
+ path, name);
+ args = qtest_rsp_args(s, 2);
+ ret = qemu_strtou64(args[1], NULL, 0, &value);
+ g_assert(!ret);
+ g_strfreev(args);
+
+ return value;
+}
+
+uint64_t qtest_dev_clock_out_get_hz(QTestState *s, const char *path,
+ const char *name)
+{
+ return qtest_dev_clock_get_hz(s, path, name, true);
+}
+
+uint64_t qtest_dev_clock_in_get_hz(QTestState *s, const char *path,
+ const char *name)
+{
+ return qtest_dev_clock_get_hz(s, path, name, false);
+}
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 25/25] tests/qtest: add tests for RT500's clock controller
2024-10-08 1:18 [PATCH v2 00/25] NXP i.MX RT595 Octavian Purdila
` (23 preceding siblings ...)
2024-10-08 1:18 ` [PATCH v2 24/25] systems/qtest: add device clock APIs Octavian Purdila
@ 2024-10-08 1:18 ` Octavian Purdila
24 siblings, 0 replies; 29+ messages in thread
From: Octavian Purdila @ 2024-10-08 1:18 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, stefanst, pbonzini, peter.maydell, marcandre.lureau,
berrange, eduardo, luc, damien.hedde, alistair, thuth, philmd,
jsnow, crosa, lvivier
Add test to exercise clocks set and clear, system PLL initialization,
audio PLL initialization, systick and ostimer clock source selection.
Signed-off-by: Octavian Purdila <tavip@google.com>
---
tests/qtest/rt500-clkctl-test.c | 195 ++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 196 insertions(+)
create mode 100644 tests/qtest/rt500-clkctl-test.c
diff --git a/tests/qtest/rt500-clkctl-test.c b/tests/qtest/rt500-clkctl-test.c
new file mode 100644
index 0000000000..d5b83d81da
--- /dev/null
+++ b/tests/qtest/rt500-clkctl-test.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2024 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/main-loop.h"
+#include "exec/memory.h"
+#include "hw/clock.h"
+#include "hw/irq.h"
+#include "hw/qdev-clock.h"
+#include "hw/qdev-properties.h"
+
+#include "hw/misc/rt500_clkctl0.h"
+#include "hw/misc/rt500_clkctl1.h"
+#include "hw/misc/rt500_clk_freqs.h"
+#include "hw/arm/svd/rt500.h"
+#include "reg-utils.h"
+
+#define SYSCLK_HZ 200000000
+#define CLKCTL0_NAME "/machine/soc/clkctl0"
+#define CLKCTL1_NAME "/machine/soc/clkctl1"
+
+static void pscctl_test(gconstpointer user_data)
+{
+ /* rom controller clock should be enabled at reset */
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, PSCCTL0, ROM_CTRLR_CLK) == 1);
+
+ /* DSP clk is disabled at reset */
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, PSCCTL0, DSP_CLK) == 0);
+
+ /* check PSCTL_SET functionality */
+ REG32_WRITE_FIELD_NOUPDATE(RT500_CLKCTL0, PSCCTL0_SET, DSP_CLK, 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, PSCCTL0, DSP_CLK) == 1);
+
+ /* check PSCTL_CLR functionality */
+ REG32_WRITE_FIELD_NOUPDATE(RT500_CLKCTL0, PSCCTL0_CLR, DSP_CLK, 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, PSCCTL0, DSP_CLK) == 0);
+
+ /* FLEXIO clk is disabled at reset */
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, PSCCTL0, FlexIO) == 0);
+
+ /* check PSCTL_SET functionality */
+ REG32_WRITE_FIELD_NOUPDATE(RT500_CLKCTL1, PSCCTL0_SET, FlexIO, 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, PSCCTL0, FlexIO) == 1);
+
+ /* check PSCTL_CLR functionality */
+ REG32_WRITE_FIELD_NOUPDATE(RT500_CLKCTL1, PSCCTL0_CLR, FlexIO, 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, PSCCTL0, FlexIO) == 0);
+}
+
+static void audiopll0pfd_test(gconstpointer user_data)
+{
+ /* audio plls are gated at boot */
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD3_CLKGATE) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD2_CLKGATE) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD1_CLKGATE) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD0_CLKGATE) == 1);
+
+ /* ,,, and clocks are not ready */
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD3_CLKRDY) == 0);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD2_CLKRDY) == 0);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD1_CLKRDY) == 0);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD0_CLKRDY) == 0);
+
+ /* ungate all plls and check that clocks are ready */
+ REG32_WRITE_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD3_CLKGATE, 0);
+ REG32_WRITE_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD2_CLKGATE, 0);
+ REG32_WRITE_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD1_CLKGATE, 0);
+ REG32_WRITE_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD0_CLKGATE, 0);
+
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD3_CLKRDY) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD2_CLKRDY) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD1_CLKRDY) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL1, AUDIOPLL0PFD, PFD0_CLKRDY) == 1);
+}
+
+static void syspll0pfd_test(gconstpointer user_data)
+{
+ /* system plls are gated at boot */
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD3_CLKGATE) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD2_CLKGATE) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD1_CLKGATE) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD0_CLKGATE) == 1);
+
+ /* ,,, and clocks are not ready */
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD3_CLKRDY) == 0);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD2_CLKRDY) == 0);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD1_CLKRDY) == 0);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD0_CLKRDY) == 0);
+
+ /* ungate all plls and check that clocks are ready */
+ REG32_WRITE_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD3_CLKGATE, 0);
+ REG32_WRITE_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD2_CLKGATE, 0);
+ REG32_WRITE_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD1_CLKGATE, 0);
+ REG32_WRITE_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD0_CLKGATE, 0);
+
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD3_CLKRDY) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD2_CLKRDY) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD1_CLKRDY) == 1);
+ g_assert(REG32_READ_FIELD(RT500_CLKCTL0, SYSPLL0PFD, PFD0_CLKRDY) == 1);
+}
+
+static void systick_clk_test(gconstpointer user_data)
+{
+ /* systick is not running at reset */
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL0_NAME, "systick_clk"), ==, 0);
+
+ /* select divout no divisor */
+ REG32_WRITE_FIELD(RT500_CLKCTL0, SYSTICKFCLKSEL, SEL,
+ SYSTICKFCLKSEL_DIVOUT);
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL0_NAME, "systick_clk"),
+ ==, SYSCLK_HZ);
+
+ /* change divisor to 2 */
+ REG32_WRITE_FIELD(RT500_CLKCTL0, SYSTICKFCLKDIV, DIV, 1);
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL0_NAME, "systick_clk"),
+ ==, SYSCLK_HZ / 2);
+
+ /* select lpsoc */
+ REG32_WRITE_FIELD(RT500_CLKCTL0, SYSTICKFCLKSEL, SEL,
+ SYSTICKFCLKSEL_LPOSC);
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL0_NAME, "systick_clk"),
+ ==, LPOSC_CLK_HZ);
+
+ /* select lpsoc */
+ REG32_WRITE_FIELD(RT500_CLKCTL0, SYSTICKFCLKSEL, SEL,
+ SYSTICKFCLKSEL_32KHZRTC);
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL0_NAME, "systick_clk"),
+ ==, RTC32KHZ_CLK_HZ);
+
+ /* disable clock */
+ REG32_WRITE_FIELD(RT500_CLKCTL0, SYSTICKFCLKSEL, SEL,
+ SYSTICKFCLKSEL_NONE);
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL0_NAME, "systick_clk"),
+ ==, 0);
+}
+
+static void ostimer_clk_test(gconstpointer user_data)
+{
+ /* systick is not running at reset */
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL1_NAME, "ostimer_clk"), ==, 0);
+
+ /* select lpsoc */
+ REG32_WRITE_FIELD(RT500_CLKCTL1, OSEVENTTFCLKSEL, SEL,
+ OSEVENTTFCLKSEL_LPOSC);
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL1_NAME, "ostimer_clk"), ==,
+ LPOSC_CLK_HZ);
+
+ /* select 32khz RTC */
+ REG32_WRITE_FIELD(RT500_CLKCTL1, OSEVENTTFCLKSEL, SEL,
+ OSEVENTTFCLKSEL_32KHZRTC);
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL1_NAME, "ostimer_clk"), ==,
+ RTC32KHZ_CLK_HZ);
+
+ /* select hclk */
+ REG32_WRITE_FIELD(RT500_CLKCTL1, OSEVENTTFCLKSEL, SEL,
+ OSEVENTTFCLKSEL_HCLK);
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL1_NAME, "ostimer_clk"), ==,
+ SYSCLK_HZ);
+
+ /* disable clock */
+ REG32_WRITE_FIELD(RT500_CLKCTL1, OSEVENTTFCLKSEL, SEL,
+ OSEVENTTFCLKSEL_NONE);
+ g_assert_cmpuint(dev_clock_out_get_hz(CLKCTL1_NAME, "ostimer_clk"), ==, 0);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_data_func("/rt500-clkctl/pscctl-test", NULL, pscctl_test);
+ qtest_add_data_func("/rt500-clkctl/syspll0pfd-test", NULL,
+ syspll0pfd_test);
+ qtest_add_data_func("/rt500-clkctl/audiopll0pfd-test", NULL,
+ audiopll0pfd_test);
+ g_test_add_data_func("/rt500-clkctl/systick-test", NULL,
+ systick_clk_test);
+ g_test_add_data_func("/rt500-clkctl/ostimer-clk-test", NULL,
+ ostimer_clk_test);
+
+ qtest_start("-M rt595-evk");
+ ret = g_test_run();
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index d3bf33b855..1a2d2ca3e1 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -234,6 +234,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and
config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \
(config_all_devices.has_key('CONFIG_FLEXCOMM') ? ['flexcomm-test', 'flexcomm-usart-test', 'flexcomm-i2c-test', 'flexcomm-spi-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_RT500_CLKCTL') ? ['rt500-clkctl-test'] : []) + \
['arm-cpu-features',
'boot-serial-test']
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread