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 | |
16 | enum { |
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 | |
32 | static u32 __iomem *reg_base; |
33 | |
34 | static unsigned int start; |
35 | module_param(start, uint, 0); |
36 | MODULE_PARM_DESC(start, "Start address of the cart block data" ); |
37 | |
38 | static unsigned int size; |
39 | module_param(size, uint, 0); |
40 | MODULE_PARM_DESC(size, "Size of the cart block data, in bytes" ); |
41 | |
42 | static void n64cart_write_reg(const u8 reg, const u32 value) |
43 | { |
44 | writel(val: value, addr: reg_base + reg); |
45 | } |
46 | |
47 | static u32 n64cart_read_reg(const u8 reg) |
48 | { |
49 | return readl(addr: reg_base + reg); |
50 | } |
51 | |
52 | static void n64cart_wait_dma(void) |
53 | { |
54 | while (n64cart_read_reg(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 | */ |
62 | static 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(reg: PI_DRAM_REG, value: dma_addr); |
78 | n64cart_write_reg(reg: PI_CART_REG, value: (bstart | CART_DOMAIN) & CART_MAX); |
79 | n64cart_write_reg(reg: PI_WRITE_REG, value: 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 | |
87 | static 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, bv: &bvec, pos)) { |
96 | bio_io_error(bio); |
97 | return; |
98 | } |
99 | pos += bvec.bv_len; |
100 | } |
101 | |
102 | bio_endio(bio); |
103 | } |
104 | |
105 | static 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 | */ |
115 | static 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, index: 0); |
131 | if (IS_ERR(ptr: reg_base)) |
132 | return PTR_ERR(ptr: 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(p: disk->disk_name, q: "n64cart" ); |
143 | |
144 | set_capacity(disk, size: size >> SECTOR_SHIFT); |
145 | set_disk_ro(disk, read_only: 1); |
146 | |
147 | blk_queue_flag_set(QUEUE_FLAG_NONROT, q: 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 | |
159 | out_cleanup_disk: |
160 | put_disk(disk); |
161 | out: |
162 | return err; |
163 | } |
164 | |
165 | static struct platform_driver n64cart_driver = { |
166 | .driver = { |
167 | .name = "n64cart" , |
168 | }, |
169 | }; |
170 | |
171 | static int __init n64cart_init(void) |
172 | { |
173 | return platform_driver_probe(&n64cart_driver, n64cart_probe); |
174 | } |
175 | |
176 | module_init(n64cart_init); |
177 | |
178 | MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>" ); |
179 | MODULE_DESCRIPTION("Driver for the N64 cart" ); |
180 | MODULE_LICENSE("GPL" ); |
181 | |