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