1d9b2a2bbSLauri Kasanen // SPDX-License-Identifier: GPL-2.0 2d9b2a2bbSLauri Kasanen /* 3d9b2a2bbSLauri Kasanen * Support for the N64 cart. 4d9b2a2bbSLauri Kasanen * 5d9b2a2bbSLauri Kasanen * Copyright (c) 2021 Lauri Kasanen 6d9b2a2bbSLauri Kasanen */ 7d9b2a2bbSLauri Kasanen 8f1e19224SChaitanya Kulkarni #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 9d9b2a2bbSLauri Kasanen #include <linux/bitops.h> 10d9b2a2bbSLauri Kasanen #include <linux/blkdev.h> 11d9b2a2bbSLauri Kasanen #include <linux/dma-mapping.h> 12d9b2a2bbSLauri Kasanen #include <linux/init.h> 13d9b2a2bbSLauri Kasanen #include <linux/module.h> 14d9b2a2bbSLauri Kasanen #include <linux/platform_device.h> 15d9b2a2bbSLauri Kasanen 162ce503b3SChaitanya Kulkarni enum { 172ce503b3SChaitanya Kulkarni PI_DRAM_REG = 0, 182ce503b3SChaitanya Kulkarni PI_CART_REG, 192ce503b3SChaitanya Kulkarni PI_READ_REG, 202ce503b3SChaitanya Kulkarni PI_WRITE_REG, 212ce503b3SChaitanya Kulkarni PI_STATUS_REG, 222ce503b3SChaitanya Kulkarni }; 23d9b2a2bbSLauri Kasanen 24d9b2a2bbSLauri Kasanen #define PI_STATUS_DMA_BUSY (1 << 0) 25d9b2a2bbSLauri Kasanen #define PI_STATUS_IO_BUSY (1 << 1) 26d9b2a2bbSLauri Kasanen 27d9b2a2bbSLauri Kasanen #define CART_DOMAIN 0x10000000 28d9b2a2bbSLauri Kasanen #define CART_MAX 0x1FFFFFFF 29d9b2a2bbSLauri Kasanen 30d9b2a2bbSLauri Kasanen #define MIN_ALIGNMENT 8 31d9b2a2bbSLauri Kasanen 32e39e3132SChaitanya Kulkarni static u32 __iomem *reg_base; 33e39e3132SChaitanya Kulkarni static struct device *dev; 34e39e3132SChaitanya Kulkarni 35e39e3132SChaitanya Kulkarni static unsigned int start; 36e39e3132SChaitanya Kulkarni module_param(start, uint, 0); 37e39e3132SChaitanya Kulkarni MODULE_PARM_DESC(start, "Start address of the cart block data"); 38e39e3132SChaitanya Kulkarni 39e39e3132SChaitanya Kulkarni static unsigned int size; 40e39e3132SChaitanya Kulkarni module_param(size, uint, 0); 41e39e3132SChaitanya Kulkarni MODULE_PARM_DESC(size, "Size of the cart block data, in bytes"); 42e39e3132SChaitanya Kulkarni 43d9b2a2bbSLauri Kasanen static void n64cart_write_reg(const u8 reg, const u32 value) 44d9b2a2bbSLauri Kasanen { 45d9b2a2bbSLauri Kasanen writel(value, reg_base + reg); 46d9b2a2bbSLauri Kasanen } 47d9b2a2bbSLauri Kasanen 48d9b2a2bbSLauri Kasanen static u32 n64cart_read_reg(const u8 reg) 49d9b2a2bbSLauri Kasanen { 50d9b2a2bbSLauri Kasanen return readl(reg_base + reg); 51d9b2a2bbSLauri Kasanen } 52d9b2a2bbSLauri Kasanen 53d9b2a2bbSLauri Kasanen static void n64cart_wait_dma(void) 54d9b2a2bbSLauri Kasanen { 55d9b2a2bbSLauri Kasanen while (n64cart_read_reg(PI_STATUS_REG) & 56d9b2a2bbSLauri Kasanen (PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY)) 57d9b2a2bbSLauri Kasanen cpu_relax(); 58d9b2a2bbSLauri Kasanen } 59d9b2a2bbSLauri Kasanen 60d9b2a2bbSLauri Kasanen /* 61d9b2a2bbSLauri Kasanen * Process a single bvec of a bio. 62d9b2a2bbSLauri Kasanen */ 63d9b2a2bbSLauri Kasanen static bool n64cart_do_bvec(struct device *dev, struct bio_vec *bv, u32 pos) 64d9b2a2bbSLauri Kasanen { 65d9b2a2bbSLauri Kasanen dma_addr_t dma_addr; 66d9b2a2bbSLauri Kasanen const u32 bstart = pos + start; 67d9b2a2bbSLauri Kasanen 68d9b2a2bbSLauri Kasanen /* Alignment check */ 69d9b2a2bbSLauri Kasanen WARN_ON_ONCE((bv->bv_offset & (MIN_ALIGNMENT - 1)) || 70d9b2a2bbSLauri Kasanen (bv->bv_len & (MIN_ALIGNMENT - 1))); 71d9b2a2bbSLauri Kasanen 72d9b2a2bbSLauri Kasanen dma_addr = dma_map_bvec(dev, bv, DMA_FROM_DEVICE, 0); 73d9b2a2bbSLauri Kasanen if (dma_mapping_error(dev, dma_addr)) 74d9b2a2bbSLauri Kasanen return false; 75d9b2a2bbSLauri Kasanen 76d9b2a2bbSLauri Kasanen n64cart_wait_dma(); 77d9b2a2bbSLauri Kasanen 78d9b2a2bbSLauri Kasanen n64cart_write_reg(PI_DRAM_REG, dma_addr + bv->bv_offset); 79d9b2a2bbSLauri Kasanen n64cart_write_reg(PI_CART_REG, (bstart | CART_DOMAIN) & CART_MAX); 80d9b2a2bbSLauri Kasanen n64cart_write_reg(PI_WRITE_REG, bv->bv_len - 1); 81d9b2a2bbSLauri Kasanen 82d9b2a2bbSLauri Kasanen n64cart_wait_dma(); 83d9b2a2bbSLauri Kasanen 84d9b2a2bbSLauri Kasanen dma_unmap_page(dev, dma_addr, bv->bv_len, DMA_FROM_DEVICE); 85d9b2a2bbSLauri Kasanen return true; 86d9b2a2bbSLauri Kasanen } 87d9b2a2bbSLauri Kasanen 88d9b2a2bbSLauri Kasanen static blk_qc_t n64cart_submit_bio(struct bio *bio) 89d9b2a2bbSLauri Kasanen { 90d9b2a2bbSLauri Kasanen struct bio_vec bvec; 91d9b2a2bbSLauri Kasanen u32 pos; 92d9b2a2bbSLauri Kasanen struct bvec_iter iter; 93d9b2a2bbSLauri Kasanen 94d9b2a2bbSLauri Kasanen pos = bio->bi_iter.bi_sector << SECTOR_SHIFT; 95d9b2a2bbSLauri Kasanen 96d9b2a2bbSLauri Kasanen bio_for_each_segment(bvec, bio, iter) { 97d9b2a2bbSLauri Kasanen if (!n64cart_do_bvec(dev, &bvec, pos)) 98d9b2a2bbSLauri Kasanen goto io_error; 99d9b2a2bbSLauri Kasanen pos += bvec.bv_len; 100d9b2a2bbSLauri Kasanen } 101d9b2a2bbSLauri Kasanen 102d9b2a2bbSLauri Kasanen bio_endio(bio); 103d9b2a2bbSLauri Kasanen return BLK_QC_T_NONE; 104d9b2a2bbSLauri Kasanen io_error: 105d9b2a2bbSLauri Kasanen bio_io_error(bio); 106d9b2a2bbSLauri Kasanen return BLK_QC_T_NONE; 107d9b2a2bbSLauri Kasanen } 108d9b2a2bbSLauri Kasanen 109d9b2a2bbSLauri Kasanen static const struct block_device_operations n64cart_fops = { 110d9b2a2bbSLauri Kasanen .owner = THIS_MODULE, 111d9b2a2bbSLauri Kasanen .submit_bio = n64cart_submit_bio, 112d9b2a2bbSLauri Kasanen }; 113d9b2a2bbSLauri Kasanen 114d9b2a2bbSLauri Kasanen /* 115d9b2a2bbSLauri Kasanen * The target device is embedded and RAM-constrained. We save RAM 116d9b2a2bbSLauri Kasanen * by initializing in __init code that gets dropped late in boot. 117d9b2a2bbSLauri Kasanen * For the same reason there is no module or unloading support. 118d9b2a2bbSLauri Kasanen */ 119d9b2a2bbSLauri Kasanen static int __init n64cart_probe(struct platform_device *pdev) 120d9b2a2bbSLauri Kasanen { 121d9b2a2bbSLauri Kasanen int err; 122d9b2a2bbSLauri Kasanen struct request_queue *queue; 123d9b2a2bbSLauri Kasanen struct gendisk *disk; 124d9b2a2bbSLauri Kasanen 125d9b2a2bbSLauri Kasanen if (!start || !size) { 126f1e19224SChaitanya Kulkarni pr_err("start or size not specified\n"); 127d9b2a2bbSLauri Kasanen return -ENODEV; 128d9b2a2bbSLauri Kasanen } 129d9b2a2bbSLauri Kasanen 130d9b2a2bbSLauri Kasanen if (size & 4095) { 131f1e19224SChaitanya Kulkarni pr_err("size must be a multiple of 4K\n"); 132d9b2a2bbSLauri Kasanen return -ENODEV; 133d9b2a2bbSLauri Kasanen } 134d9b2a2bbSLauri Kasanen 135d9b2a2bbSLauri Kasanen queue = blk_alloc_queue(NUMA_NO_NODE); 136d9b2a2bbSLauri Kasanen if (!queue) { 137d9b2a2bbSLauri Kasanen return -ENOMEM; 138d9b2a2bbSLauri Kasanen } 139d9b2a2bbSLauri Kasanen 140d9b2a2bbSLauri Kasanen reg_base = devm_platform_ioremap_resource(pdev, 0); 141d9b2a2bbSLauri Kasanen if (!reg_base) { 142d9b2a2bbSLauri Kasanen err = -EINVAL; 143d9b2a2bbSLauri Kasanen goto fail_queue; 144d9b2a2bbSLauri Kasanen } 145d9b2a2bbSLauri Kasanen 146d9b2a2bbSLauri Kasanen disk = alloc_disk(0); 147d9b2a2bbSLauri Kasanen if (!disk) { 148d9b2a2bbSLauri Kasanen err = -ENOMEM; 149d9b2a2bbSLauri Kasanen goto fail_queue; 150d9b2a2bbSLauri Kasanen } 151d9b2a2bbSLauri Kasanen 152d9b2a2bbSLauri Kasanen dev = &pdev->dev; 153d9b2a2bbSLauri Kasanen 154d9b2a2bbSLauri Kasanen disk->first_minor = 0; 155d9b2a2bbSLauri Kasanen disk->queue = queue; 156d9b2a2bbSLauri Kasanen disk->flags = GENHD_FL_NO_PART_SCAN | GENHD_FL_EXT_DEVT; 157d9b2a2bbSLauri Kasanen disk->fops = &n64cart_fops; 158d9b2a2bbSLauri Kasanen strcpy(disk->disk_name, "n64cart"); 159d9b2a2bbSLauri Kasanen 160*857f6fdeSChaitanya Kulkarni set_capacity(disk, size >> SECTOR_SHIFT); 161d9b2a2bbSLauri Kasanen set_disk_ro(disk, 1); 162d9b2a2bbSLauri Kasanen 163d9b2a2bbSLauri Kasanen blk_queue_flag_set(QUEUE_FLAG_NONROT, queue); 164d9b2a2bbSLauri Kasanen blk_queue_physical_block_size(queue, 4096); 165d9b2a2bbSLauri Kasanen blk_queue_logical_block_size(queue, 4096); 166d9b2a2bbSLauri Kasanen 167d9b2a2bbSLauri Kasanen add_disk(disk); 168d9b2a2bbSLauri Kasanen 169d9b2a2bbSLauri Kasanen pr_info("n64cart: %u kb disk\n", size / 1024); 170d9b2a2bbSLauri Kasanen 171d9b2a2bbSLauri Kasanen return 0; 172d9b2a2bbSLauri Kasanen fail_queue: 173d9b2a2bbSLauri Kasanen blk_cleanup_queue(queue); 174d9b2a2bbSLauri Kasanen 175d9b2a2bbSLauri Kasanen return err; 176d9b2a2bbSLauri Kasanen } 177d9b2a2bbSLauri Kasanen 178d9b2a2bbSLauri Kasanen static struct platform_driver n64cart_driver = { 179d9b2a2bbSLauri Kasanen .driver = { 180d9b2a2bbSLauri Kasanen .name = "n64cart", 181d9b2a2bbSLauri Kasanen }, 182d9b2a2bbSLauri Kasanen }; 183d9b2a2bbSLauri Kasanen 184d9b2a2bbSLauri Kasanen static int __init n64cart_init(void) 185d9b2a2bbSLauri Kasanen { 186d9b2a2bbSLauri Kasanen return platform_driver_probe(&n64cart_driver, n64cart_probe); 187d9b2a2bbSLauri Kasanen } 188d9b2a2bbSLauri Kasanen 189d9b2a2bbSLauri Kasanen module_init(n64cart_init); 1909ee8c9a1SChaitanya Kulkarni 1919ee8c9a1SChaitanya Kulkarni MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>"); 1929ee8c9a1SChaitanya Kulkarni MODULE_DESCRIPTION("Driver for the N64 cart"); 1939ee8c9a1SChaitanya Kulkarni MODULE_LICENSE("GPL"); 194