root/arch/arm/mach-orion5x/ts78xx-setup.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * arch/arm/mach-orion5x/ts78xx-setup.c
 *
 * Maintainer: Alexander Clouter <alex@digriz.org.uk>
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sysfs.h>
#include <linux/platform_device.h>
#include <linux/mv643xx_eth.h>
#include <linux/ata_platform.h>
#include <linux/mtd/platnand.h>
#include <linux/timeriomem-rng.h>
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include "common.h"
#include "mpp.h"
#include "orion5x.h"
#include "ts78xx-fpga.h"

/*****************************************************************************
 * TS-78xx Info
 ****************************************************************************/

/*
 * FPGA - lives where the PCI bus would be at ORION5X_PCI_MEM_PHYS_BASE
 */
#define TS78XX_FPGA_REGS_PHYS_BASE      0xe8000000
#define TS78XX_FPGA_REGS_VIRT_BASE      IOMEM(0xff900000)
#define TS78XX_FPGA_REGS_SIZE           SZ_1M

static struct ts78xx_fpga_data ts78xx_fpga = {
        .id             = 0,
        .state          = 1,
/*      .supports       = ... - populated by ts78xx_fpga_supports() */
};

/*****************************************************************************
 * I/O Address Mapping
 ****************************************************************************/
static struct map_desc ts78xx_io_desc[] __initdata = {
        {
                .virtual        = (unsigned long)TS78XX_FPGA_REGS_VIRT_BASE,
                .pfn            = __phys_to_pfn(TS78XX_FPGA_REGS_PHYS_BASE),
                .length         = TS78XX_FPGA_REGS_SIZE,
                .type           = MT_DEVICE,
        },
};

static void __init ts78xx_map_io(void)
{
        orion5x_map_io();
        iotable_init(ts78xx_io_desc, ARRAY_SIZE(ts78xx_io_desc));
}

/*****************************************************************************
 * Ethernet
 ****************************************************************************/
static struct mv643xx_eth_platform_data ts78xx_eth_data = {
        .phy_addr       = MV643XX_ETH_PHY_ADDR(0),
};

/*****************************************************************************
 * SATA
 ****************************************************************************/
static struct mv_sata_platform_data ts78xx_sata_data = {
        .n_ports        = 2,
};

/*****************************************************************************
 * RTC M48T86 - nicked^Wborrowed from arch/arm/mach-ep93xx/ts72xx.c
 ****************************************************************************/
#define TS_RTC_CTRL     (TS78XX_FPGA_REGS_PHYS_BASE + 0x808)
#define TS_RTC_DATA     (TS78XX_FPGA_REGS_PHYS_BASE + 0x80c)

static struct resource ts78xx_ts_rtc_resources[] = {
        DEFINE_RES_MEM(TS_RTC_CTRL, 0x01),
        DEFINE_RES_MEM(TS_RTC_DATA, 0x01),
};

static struct platform_device ts78xx_ts_rtc_device = {
        .name           = "rtc-m48t86",
        .id             = -1,
        .resource       = ts78xx_ts_rtc_resources,
        .num_resources  = ARRAY_SIZE(ts78xx_ts_rtc_resources),
};

static int ts78xx_ts_rtc_load(void)
{
        int rc;

        if (ts78xx_fpga.supports.ts_rtc.init == 0) {
                rc = platform_device_register(&ts78xx_ts_rtc_device);
                if (!rc)
                        ts78xx_fpga.supports.ts_rtc.init = 1;
        } else {
                rc = platform_device_add(&ts78xx_ts_rtc_device);
        }

        if (rc)
                pr_info("RTC could not be registered: %d\n", rc);

        return rc;
}

static void ts78xx_ts_rtc_unload(void)
{
        platform_device_del(&ts78xx_ts_rtc_device);
}

/*****************************************************************************
 * NAND Flash
 ****************************************************************************/
#define TS_NAND_CTRL    (TS78XX_FPGA_REGS_VIRT_BASE + 0x800)    /* VIRT */
#define TS_NAND_DATA    (TS78XX_FPGA_REGS_PHYS_BASE + 0x804)    /* PHYS */

/*
 * hardware specific access to control-lines
 *
 * ctrl:
 * NAND_NCE: bit 0 -> bit 2
 * NAND_CLE: bit 1 -> bit 1
 * NAND_ALE: bit 2 -> bit 0
 */
static void ts78xx_ts_nand_cmd_ctrl(struct nand_chip *this, int cmd,
                                    unsigned int ctrl)
{
        if (ctrl & NAND_CTRL_CHANGE) {
                unsigned char bits;

                bits = (ctrl & NAND_NCE) << 2;
                bits |= ctrl & NAND_CLE;
                bits |= (ctrl & NAND_ALE) >> 2;

                writeb((readb(TS_NAND_CTRL) & ~0x7) | bits, TS_NAND_CTRL);
        }

        if (cmd != NAND_CMD_NONE)
                writeb(cmd, this->legacy.IO_ADDR_W);
}

static int ts78xx_ts_nand_dev_ready(struct nand_chip *chip)
{
        return readb(TS_NAND_CTRL) & 0x20;
}

static void ts78xx_ts_nand_write_buf(struct nand_chip *chip,
                                     const uint8_t *buf, int len)
{
        void __iomem *io_base = chip->legacy.IO_ADDR_W;
        unsigned long off = ((unsigned long)buf & 3);
        int sz;

        if (off) {
                sz = min_t(int, 4 - off, len);
                writesb(io_base, buf, sz);
                buf += sz;
                len -= sz;
        }

        sz = len >> 2;
        if (sz) {
                u32 *buf32 = (u32 *)buf;
                writesl(io_base, buf32, sz);
                buf += sz << 2;
                len -= sz << 2;
        }

        if (len)
                writesb(io_base, buf, len);
}

static void ts78xx_ts_nand_read_buf(struct nand_chip *chip,
                                    uint8_t *buf, int len)
{
        void __iomem *io_base = chip->legacy.IO_ADDR_R;
        unsigned long off = ((unsigned long)buf & 3);
        int sz;

        if (off) {
                sz = min_t(int, 4 - off, len);
                readsb(io_base, buf, sz);
                buf += sz;
                len -= sz;
        }

        sz = len >> 2;
        if (sz) {
                u32 *buf32 = (u32 *)buf;
                readsl(io_base, buf32, sz);
                buf += sz << 2;
                len -= sz << 2;
        }

        if (len)
                readsb(io_base, buf, len);
}

static struct mtd_partition ts78xx_ts_nand_parts[] = {
        {
                .name           = "mbr",
                .offset         = 0,
                .size           = SZ_128K,
                .mask_flags     = MTD_WRITEABLE,
        }, {
                .name           = "kernel",
                .offset         = MTDPART_OFS_APPEND,
                .size           = SZ_4M,
        }, {
                .name           = "initrd",
                .offset         = MTDPART_OFS_APPEND,
                .size           = SZ_4M,
        }, {
                .name           = "rootfs",
                .offset         = MTDPART_OFS_APPEND,
                .size           = MTDPART_SIZ_FULL,
        }
};

static struct platform_nand_data ts78xx_ts_nand_data = {
        .chip   = {
                .nr_chips               = 1,
                .partitions             = ts78xx_ts_nand_parts,
                .nr_partitions          = ARRAY_SIZE(ts78xx_ts_nand_parts),
                .chip_delay             = 15,
                .bbt_options            = NAND_BBT_USE_FLASH,
        },
        .ctrl   = {
                /*
                 * The HW ECC offloading functions, used to give about a 9%
                 * performance increase for 'dd if=/dev/mtdblockX' and 5% for
                 * nanddump.  This all however was changed by git commit
                 * e6cf5df1838c28bb060ac45b5585e48e71bbc740 so now there is
                 * no performance advantage to be had so we no longer bother
                 */
                .cmd_ctrl               = ts78xx_ts_nand_cmd_ctrl,
                .dev_ready              = ts78xx_ts_nand_dev_ready,
                .write_buf              = ts78xx_ts_nand_write_buf,
                .read_buf               = ts78xx_ts_nand_read_buf,
        },
};

static struct resource ts78xx_ts_nand_resources
                        = DEFINE_RES_MEM(TS_NAND_DATA, 4);

static struct platform_device ts78xx_ts_nand_device = {
        .name           = "gen_nand",
        .id             = -1,
        .dev            = {
                .platform_data  = &ts78xx_ts_nand_data,
        },
        .resource       = &ts78xx_ts_nand_resources,
        .num_resources  = 1,
};

static int ts78xx_ts_nand_load(void)
{
        int rc;

        if (ts78xx_fpga.supports.ts_nand.init == 0) {
                rc = platform_device_register(&ts78xx_ts_nand_device);
                if (!rc)
                        ts78xx_fpga.supports.ts_nand.init = 1;
        } else
                rc = platform_device_add(&ts78xx_ts_nand_device);

        if (rc)
                pr_info("NAND could not be registered: %d\n", rc);
        return rc;
};

static void ts78xx_ts_nand_unload(void)
{
        platform_device_del(&ts78xx_ts_nand_device);
}

/*****************************************************************************
 * HW RNG
 ****************************************************************************/
#define TS_RNG_DATA     (TS78XX_FPGA_REGS_PHYS_BASE | 0x044)

static struct resource ts78xx_ts_rng_resource
                        = DEFINE_RES_MEM(TS_RNG_DATA, 4);

static struct timeriomem_rng_data ts78xx_ts_rng_data = {
        .period         = 1000000, /* one second */
};

static struct platform_device ts78xx_ts_rng_device = {
        .name           = "timeriomem_rng",
        .id             = -1,
        .dev            = {
                .platform_data  = &ts78xx_ts_rng_data,
        },
        .resource       = &ts78xx_ts_rng_resource,
        .num_resources  = 1,
};

static int ts78xx_ts_rng_load(void)
{
        int rc;

        if (ts78xx_fpga.supports.ts_rng.init == 0) {
                rc = platform_device_register(&ts78xx_ts_rng_device);
                if (!rc)
                        ts78xx_fpga.supports.ts_rng.init = 1;
        } else
                rc = platform_device_add(&ts78xx_ts_rng_device);

        if (rc)
                pr_info("RNG could not be registered: %d\n", rc);
        return rc;
};

static void ts78xx_ts_rng_unload(void)
{
        platform_device_del(&ts78xx_ts_rng_device);
}

/*****************************************************************************
 * FPGA 'hotplug' support code
 ****************************************************************************/
static void ts78xx_fpga_devices_zero_init(void)
{
        ts78xx_fpga.supports.ts_rtc.init = 0;
        ts78xx_fpga.supports.ts_nand.init = 0;
        ts78xx_fpga.supports.ts_rng.init = 0;
}

static void ts78xx_fpga_supports(void)
{
        /* TODO: put this 'table' into ts78xx-fpga.h */
        switch (ts78xx_fpga.id) {
        case TS7800_REV_1:
        case TS7800_REV_2:
        case TS7800_REV_3:
        case TS7800_REV_4:
        case TS7800_REV_5:
        case TS7800_REV_6:
        case TS7800_REV_7:
        case TS7800_REV_8:
        case TS7800_REV_9:
                ts78xx_fpga.supports.ts_rtc.present = 1;
                ts78xx_fpga.supports.ts_nand.present = 1;
                ts78xx_fpga.supports.ts_rng.present = 1;
                break;
        default:
                /* enable devices if magic matches */
                switch ((ts78xx_fpga.id >> 8) & 0xffffff) {
                case TS7800_FPGA_MAGIC:
                        pr_warn("unrecognised FPGA revision 0x%.2x\n",
                                ts78xx_fpga.id & 0xff);
                        ts78xx_fpga.supports.ts_rtc.present = 1;
                        ts78xx_fpga.supports.ts_nand.present = 1;
                        ts78xx_fpga.supports.ts_rng.present = 1;
                        break;
                default:
                        ts78xx_fpga.supports.ts_rtc.present = 0;
                        ts78xx_fpga.supports.ts_nand.present = 0;
                        ts78xx_fpga.supports.ts_rng.present = 0;
                }
        }
}

static int ts78xx_fpga_load_devices(void)
{
        int tmp, ret = 0;

        if (ts78xx_fpga.supports.ts_rtc.present == 1) {
                tmp = ts78xx_ts_rtc_load();
                if (tmp)
                        ts78xx_fpga.supports.ts_rtc.present = 0;
                ret |= tmp;
        }
        if (ts78xx_fpga.supports.ts_nand.present == 1) {
                tmp = ts78xx_ts_nand_load();
                if (tmp)
                        ts78xx_fpga.supports.ts_nand.present = 0;
                ret |= tmp;
        }
        if (ts78xx_fpga.supports.ts_rng.present == 1) {
                tmp = ts78xx_ts_rng_load();
                if (tmp)
                        ts78xx_fpga.supports.ts_rng.present = 0;
                ret |= tmp;
        }

        return ret;
}

static int ts78xx_fpga_unload_devices(void)
{

        if (ts78xx_fpga.supports.ts_rtc.present == 1)
                ts78xx_ts_rtc_unload();
        if (ts78xx_fpga.supports.ts_nand.present == 1)
                ts78xx_ts_nand_unload();
        if (ts78xx_fpga.supports.ts_rng.present == 1)
                ts78xx_ts_rng_unload();

        return 0;
}

static int ts78xx_fpga_load(void)
{
        ts78xx_fpga.id = readl(TS78XX_FPGA_REGS_VIRT_BASE);

        pr_info("FPGA magic=0x%.6x, rev=0x%.2x\n",
                        (ts78xx_fpga.id >> 8) & 0xffffff,
                        ts78xx_fpga.id & 0xff);

        ts78xx_fpga_supports();

        if (ts78xx_fpga_load_devices()) {
                ts78xx_fpga.state = -1;
                return -EBUSY;
        }

        return 0;
};

static int ts78xx_fpga_unload(void)
{
        unsigned int fpga_id;

        fpga_id = readl(TS78XX_FPGA_REGS_VIRT_BASE);

        /*
         * There does not seem to be a feasible way to block access to the GPIO
         * pins from userspace (/dev/mem).  This if clause should hopefully warn
         * those foolish enough not to follow 'policy' :)
         *
         * UrJTAG SVN since r1381 can be used to reprogram the FPGA
         */
        if (ts78xx_fpga.id != fpga_id) {
                pr_err("FPGA magic/rev mismatch\n"
                        "TS-78xx FPGA: was 0x%.6x/%.2x but now 0x%.6x/%.2x\n",
                        (ts78xx_fpga.id >> 8) & 0xffffff, ts78xx_fpga.id & 0xff,
                        (fpga_id >> 8) & 0xffffff, fpga_id & 0xff);
                ts78xx_fpga.state = -1;
                return -EBUSY;
        }

        if (ts78xx_fpga_unload_devices()) {
                ts78xx_fpga.state = -1;
                return -EBUSY;
        }

        return 0;
};

static ssize_t ts78xx_fpga_show(struct kobject *kobj,
                        struct kobj_attribute *attr, char *buf)
{
        if (ts78xx_fpga.state < 0)
                return sprintf(buf, "borked\n");

        return sprintf(buf, "%s\n", (ts78xx_fpga.state) ? "online" : "offline");
}

static ssize_t ts78xx_fpga_store(struct kobject *kobj,
                        struct kobj_attribute *attr, const char *buf, size_t n)
{
        int value, ret;

        if (ts78xx_fpga.state < 0) {
                pr_err("FPGA borked, you must powercycle ASAP\n");
                return -EBUSY;
        }

        if (strncmp(buf, "online", sizeof("online") - 1) == 0)
                value = 1;
        else if (strncmp(buf, "offline", sizeof("offline") - 1) == 0)
                value = 0;
        else
                return -EINVAL;

        if (ts78xx_fpga.state == value)
                return n;

        ret = (ts78xx_fpga.state == 0)
                ? ts78xx_fpga_load()
                : ts78xx_fpga_unload();

        if (!(ret < 0))
                ts78xx_fpga.state = value;

        return n;
}

static struct kobj_attribute ts78xx_fpga_attr =
        __ATTR(ts78xx_fpga, 0644, ts78xx_fpga_show, ts78xx_fpga_store);

/*****************************************************************************
 * General Setup
 ****************************************************************************/
static unsigned int ts78xx_mpp_modes[] __initdata = {
        MPP0_UNUSED,
        MPP1_GPIO,              /* JTAG Clock */
        MPP2_GPIO,              /* JTAG Data In */
        MPP3_GPIO,              /* Lat ECP2 256 FPGA - PB2B */
        MPP4_GPIO,              /* JTAG Data Out */
        MPP5_GPIO,              /* JTAG TMS */
        MPP6_GPIO,              /* Lat ECP2 256 FPGA - PB31A_CLK4+ */
        MPP7_GPIO,              /* Lat ECP2 256 FPGA - PB22B */
        MPP8_UNUSED,
        MPP9_UNUSED,
        MPP10_UNUSED,
        MPP11_UNUSED,
        MPP12_UNUSED,
        MPP13_UNUSED,
        MPP14_UNUSED,
        MPP15_UNUSED,
        MPP16_UART,
        MPP17_UART,
        MPP18_UART,
        MPP19_UART,
        /*
         * MPP[20] PCI Clock Out 1
         * MPP[21] PCI Clock Out 0
         * MPP[22] Unused
         * MPP[23] Unused
         * MPP[24] Unused
         * MPP[25] Unused
         */
        0,
};

static void __init ts78xx_init(void)
{
        int ret;

        /*
         * Setup basic Orion functions. Need to be called early.
         */
        orion5x_init();

        orion5x_mpp_conf(ts78xx_mpp_modes);

        /*
         * Configure peripherals.
         */
        orion5x_ehci0_init();
        orion5x_ehci1_init();
        orion5x_eth_init(&ts78xx_eth_data);
        orion5x_sata_init(&ts78xx_sata_data);
        orion5x_uart0_init();
        orion5x_uart1_init();
        orion5x_xor_init();

        /* FPGA init */
        ts78xx_fpga_devices_zero_init();
        ret = ts78xx_fpga_load();
        ret = sysfs_create_file(firmware_kobj, &ts78xx_fpga_attr.attr);
        if (ret)
                pr_err("sysfs_create_file failed: %d\n", ret);
}

MACHINE_START(TS78XX, "Technologic Systems TS-78xx SBC")
        /* Maintainer: Alexander Clouter <alex@digriz.org.uk> */
        .atag_offset    = 0x100,
        .nr_irqs        = ORION5X_NR_IRQS,
        .init_machine   = ts78xx_init,
        .map_io         = ts78xx_map_io,
        .init_early     = orion5x_init_early,
        .init_irq       = orion5x_init_irq,
        .init_time      = orion5x_timer_init,
        .restart        = orion5x_restart,
MACHINE_END