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