board: rockchip: add Anbernic RGXX3 Series Devices

The Anbernic RGxx3 is a "pseudo-device" that encompasses the following
devices:

 - Anbernic RG353M
 - Anbernic RG353P
 - Anbernic RG353V
 - Anbernic RG353VS
 - Anbernic RG503

The rk3566-anbernic-rgxx3.dtsi is synced with upstream Linux, but
rk3566-anbernic-rgxx3.dts is a U-Boot specific devicetree that
is used for all RGxx3 devices.

Via the board.c file, the bootloader automatically sets the correct
fdtfile, board, and board_name environment variables so that the
correct devicetree can be passed to Linux. It is also possible to
simply hard-code a single devicetree in the boot.scr file and use
that to load Linux as well.

The common specifications for each device are:

 - Rockchip RK3566 SoC
 - 2 external SDMMC slots
 - 1 USB-C host port, 1 USB-C peripheral port
 - 1 mini-HDMI output
 - MIPI-DSI based display panel
 - ADC controlled joysticks with a GPIO mux
 - GPIO buttons
 - A PWM controlled vibrator
 - An ADC controlled button

All of the common features are defined in the devicetree synced from
upstream Linux.

TODO: DSI panel auto-detection for the RG353 devices (requires porting
of DSI controller driver and DSI-DPHY driver to send DSI commands to
the panel).

Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
Reviewed-by: Kever Yang <kever.yang@rock-chips.com>
diff --git a/board/anbernic/rgxx3_rk3566/Kconfig b/board/anbernic/rgxx3_rk3566/Kconfig
new file mode 100644
index 0000000..6743a28
--- /dev/null
+++ b/board/anbernic/rgxx3_rk3566/Kconfig
@@ -0,0 +1,15 @@
+if TARGET_ANBERNIC_RGXX3_RK3566
+
+config SYS_BOARD
+	default "rgxx3_rk3566"
+
+config SYS_VENDOR
+	default "anbernic"
+
+config SYS_CONFIG_NAME
+	default "anbernic-rgxx3-rk3566"
+
+config BOARD_SPECIFIC_OPTIONS
+	def_bool y
+
+endif
diff --git a/board/anbernic/rgxx3_rk3566/MAINTAINERS b/board/anbernic/rgxx3_rk3566/MAINTAINERS
new file mode 100644
index 0000000..647e49d
--- /dev/null
+++ b/board/anbernic/rgxx3_rk3566/MAINTAINERS
@@ -0,0 +1,6 @@
+RGXX3-RK3566
+M:	Chris Morgan <macromorgan@hotmail.com>
+S:	Maintained
+F:	board/anbernic/rgxx3-rk3566
+F:	include/configs/anbernic-rgxx3-rk3566
+F:	configs/anbernic-rgxx3_defconfig
diff --git a/board/anbernic/rgxx3_rk3566/Makefile b/board/anbernic/rgxx3_rk3566/Makefile
new file mode 100644
index 0000000..afd3e0a
--- /dev/null
+++ b/board/anbernic/rgxx3_rk3566/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier:     GPL-2.0+
+#
+# Copyright (c) 2023 Chris Morgan <macromorgan@hotmail.com>
+#
+
+obj-y += rgxx3-rk3566.o
diff --git a/board/anbernic/rgxx3_rk3566/rgxx3-rk3566.c b/board/anbernic/rgxx3_rk3566/rgxx3-rk3566.c
new file mode 100644
index 0000000..decc46d
--- /dev/null
+++ b/board/anbernic/rgxx3_rk3566/rgxx3-rk3566.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 Chris Morgan <macromorgan@hotmail.com>
+ */
+
+#include <abuf.h>
+#include <adc.h>
+#include <asm/io.h>
+#include <dm.h>
+#include <linux/delay.h>
+#include <pwm.h>
+#include <rng.h>
+#include <stdlib.h>
+#include <mmc.h>
+#include <env.h>
+
+#define GPIO0_BASE		0xfdd60000
+#define GPIO_SWPORT_DR_H	0x0004
+#define GPIO_SWPORT_DDR_H	0x000c
+#define GPIO_A5			BIT(5)
+#define GPIO_A6			BIT(6)
+
+#define GPIO_WRITEMASK(bits)	((bits) << 16)
+
+#define DTB_DIR			"rockchip/"
+
+struct rg3xx_model {
+	const char *board;
+	const char *board_name;
+	const char *fdtfile;
+};
+
+enum rgxx3_device_id {
+	RG353M,
+	RG353P,
+	RG353V,
+	RG353VS,
+	RG503,
+};
+
+static const struct rg3xx_model rg3xx_model_details[] = {
+	[RG353M] = {
+		"rk3566-anbernic-rg353m",
+		"RG353M",
+		DTB_DIR "rk3566-anbernic-rg353m.dtb",
+	},
+	[RG353P] = {
+		"rk3566-anbernic-rg353p",
+		"RG353P",
+		DTB_DIR "rk3566-anbernic-rg353p.dtb",
+	},
+	[RG353V] = {
+		"rk3566-anbernic-rg353v",
+		"RG353V",
+		DTB_DIR "rk3566-anbernic-rg353v.dtb",
+	},
+	[RG353VS] = {
+		"rk3566-anbernic-rg353vs",
+		"RG353VS",
+		DTB_DIR "rk3566-anbernic-rg353vs.dtb",
+	},
+	[RG503] = {
+		"rk3566-anbernic-rg503",
+		"RG503",
+		DTB_DIR "rk3566-anbernic-rg503.dtb",
+	},
+};
+
+/*
+ * Start LED very early so user knows device is on. Set color
+ * to amber.
+ */
+void spl_board_init(void)
+{
+	/* Set GPIO0_A5 and GPIO0_A6 to output. */
+	writel(GPIO_WRITEMASK(GPIO_A6 | GPIO_A5) | (GPIO_A6 | GPIO_A5),
+	       (GPIO0_BASE + GPIO_SWPORT_DDR_H));
+	/* Set GPIO0_A5 to 0 and GPIO0_A6 to 1. */
+	writel(GPIO_WRITEMASK(GPIO_A6 | GPIO_A5) | GPIO_A6,
+	       (GPIO0_BASE + GPIO_SWPORT_DR_H));
+}
+
+/* Use hardware rng to seed Linux random. */
+int board_rng_seed(struct abuf *buf)
+{
+	struct udevice *dev;
+	size_t len = 0x8;
+	u64 *data;
+
+	data = malloc(len);
+	if (!data) {
+		printf("Out of memory\n");
+		return -ENOMEM;
+	}
+
+	if (uclass_get_device(UCLASS_RNG, 0, &dev) || !dev) {
+		printf("No RNG device\n");
+		return -ENODEV;
+	}
+
+	if (dm_rng_read(dev, data, len)) {
+		printf("Reading RNG failed\n");
+		return -EIO;
+	}
+
+	abuf_init_set(buf, data, len);
+
+	return 0;
+}
+
+/*
+ * Buzz the buzzer so the user knows something is going on. Make it
+ * optional in case PWM is disabled.
+ */
+void __maybe_unused startup_buzz(void)
+{
+	struct udevice *dev;
+	int err;
+
+	err = uclass_get_device(UCLASS_PWM, 0, &dev);
+	if (err)
+		printf("pwm not found\n");
+
+	pwm_set_enable(dev, 0, 1);
+	mdelay(200);
+	pwm_set_enable(dev, 0, 0);
+}
+
+/* Detect which Anbernic RGXX3 device we are using so as to load the
+ * correct devicetree for Linux. Set an environment variable once
+ * found. The detection depends on the value of ADC channel 1, the
+ * presence of an eMMC on mmc0, and querying the DSI panel (TODO).
+ */
+int rgxx3_detect_device(void)
+{
+	u32 adc_info;
+	int ret;
+	int board_id = -ENXIO;
+	struct mmc *mmc;
+
+	ret = adc_channel_single_shot("saradc@fe720000", 1, &adc_info);
+	if (ret) {
+		printf("Read SARADC failed with error %d\n", ret);
+		return ret;
+	}
+
+	/* Observed value 517. */
+	if (adc_info > 505 && adc_info < 530)
+		board_id = RG353M;
+	/* Observed value 695. */
+	if (adc_info > 680 && adc_info < 710)
+		board_id = RG353V;
+	/* Documented value 860. */
+	if (adc_info > 850 && adc_info < 870)
+		board_id = RG353P;
+	/* Observed value 1023. */
+	if (adc_info > 1010)
+		board_id = RG503;
+
+	/*
+	 * Try to access the eMMC on an RG353V. If it's missing, it's
+	 * an RG353VS. Note we could also check for a touchscreen at
+	 * 0x1a on i2c2.
+	 */
+	if (board_id == RG353V) {
+		mmc = find_mmc_device(0);
+		if (mmc) {
+			ret = mmc_init(mmc);
+			if (ret)
+				board_id = RG353VS;
+		}
+	}
+
+	if (board_id < 0)
+		return board_id;
+
+	env_set("board", rg3xx_model_details[board_id].board);
+	env_set("board_name",
+		rg3xx_model_details[board_id].board_name);
+	env_set("fdtfile", rg3xx_model_details[board_id].fdtfile);
+
+	return 0;
+}
+
+int rk_board_late_init(void)
+{
+	int ret;
+
+	/* Turn off orange LED and turn on green LED. */
+	writel(GPIO_WRITEMASK(GPIO_A6 | GPIO_A5) | GPIO_A5,
+	       (GPIO0_BASE + GPIO_SWPORT_DR_H));
+
+	ret = rgxx3_detect_device();
+	if (ret) {
+		printf("Unable to detect device type: %d\n", ret);
+		return ret;
+	}
+
+	if (IS_ENABLED(CONFIG_DM_PWM))
+		startup_buzz();
+
+	return 0;
+}