2021-01-23 10:53:27 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Support for the N64 cart .
*
* Copyright ( c ) 2021 Lauri Kasanen
*/
2021-01-26 02:32:35 +03:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2021-01-23 10:53:27 +03:00
# include <linux/bitops.h>
# include <linux/blkdev.h>
# include <linux/dma-mapping.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/platform_device.h>
2021-01-26 02:32:38 +03:00
enum {
PI_DRAM_REG = 0 ,
PI_CART_REG ,
PI_READ_REG ,
PI_WRITE_REG ,
PI_STATUS_REG ,
} ;
2021-01-23 10:53:27 +03:00
# define PI_STATUS_DMA_BUSY (1 << 0)
# define PI_STATUS_IO_BUSY (1 << 1)
# define CART_DOMAIN 0x10000000
# define CART_MAX 0x1FFFFFFF
# define MIN_ALIGNMENT 8
2021-01-26 02:32:37 +03:00
static u32 __iomem * reg_base ;
static unsigned int start ;
module_param ( start , uint , 0 ) ;
MODULE_PARM_DESC ( start , " Start address of the cart block data " ) ;
static unsigned int size ;
module_param ( size , uint , 0 ) ;
MODULE_PARM_DESC ( size , " Size of the cart block data, in bytes " ) ;
2021-01-23 10:53:27 +03:00
static void n64cart_write_reg ( const u8 reg , const u32 value )
{
writel ( value , reg_base + reg ) ;
}
static u32 n64cart_read_reg ( const u8 reg )
{
return readl ( reg_base + reg ) ;
}
static void n64cart_wait_dma ( void )
{
while ( n64cart_read_reg ( PI_STATUS_REG ) &
( PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY ) )
cpu_relax ( ) ;
}
/*
* Process a single bvec of a bio .
*/
static bool n64cart_do_bvec ( struct device * dev , struct bio_vec * bv , u32 pos )
{
dma_addr_t dma_addr ;
const u32 bstart = pos + start ;
/* Alignment check */
WARN_ON_ONCE ( ( bv - > bv_offset & ( MIN_ALIGNMENT - 1 ) ) | |
( bv - > bv_len & ( MIN_ALIGNMENT - 1 ) ) ) ;
dma_addr = dma_map_bvec ( dev , bv , DMA_FROM_DEVICE , 0 ) ;
if ( dma_mapping_error ( dev , dma_addr ) )
return false ;
n64cart_wait_dma ( ) ;
2021-08-04 12:49:58 +03:00
n64cart_write_reg ( PI_DRAM_REG , dma_addr ) ;
2021-01-23 10:53:27 +03:00
n64cart_write_reg ( PI_CART_REG , ( bstart | CART_DOMAIN ) & CART_MAX ) ;
n64cart_write_reg ( PI_WRITE_REG , bv - > bv_len - 1 ) ;
n64cart_wait_dma ( ) ;
dma_unmap_page ( dev , dma_addr , bv - > bv_len , DMA_FROM_DEVICE ) ;
return true ;
}
2021-10-12 14:12:24 +03:00
static void n64cart_submit_bio ( struct bio * bio )
2021-01-23 10:53:27 +03:00
{
struct bio_vec bvec ;
struct bvec_iter iter ;
2022-03-21 10:12:16 +03:00
struct device * dev = bio - > bi_bdev - > bd_disk - > private_data ;
2021-01-26 02:32:41 +03:00
u32 pos = bio - > bi_iter . bi_sector < < SECTOR_SHIFT ;
2021-01-23 10:53:27 +03:00
bio_for_each_segment ( bvec , bio , iter ) {
2021-10-12 14:12:24 +03:00
if ( ! n64cart_do_bvec ( dev , & bvec , pos ) ) {
bio_io_error ( bio ) ;
return ;
}
2021-01-23 10:53:27 +03:00
pos + = bvec . bv_len ;
}
bio_endio ( bio ) ;
}
static const struct block_device_operations n64cart_fops = {
. owner = THIS_MODULE ,
. submit_bio = n64cart_submit_bio ,
} ;
/*
* The target device is embedded and RAM - constrained . We save RAM
* by initializing in __init code that gets dropped late in boot .
* For the same reason there is no module or unloading support .
*/
static int __init n64cart_probe ( struct platform_device * pdev )
{
struct gendisk * disk ;
2021-09-28 01:01:02 +03:00
int err = - ENOMEM ;
2021-01-23 10:53:27 +03:00
if ( ! start | | ! size ) {
2021-01-26 02:32:35 +03:00
pr_err ( " start or size not specified \n " ) ;
2021-01-23 10:53:27 +03:00
return - ENODEV ;
}
if ( size & 4095 ) {
2021-01-26 02:32:35 +03:00
pr_err ( " size must be a multiple of 4K \n " ) ;
2021-01-23 10:53:27 +03:00
return - ENODEV ;
}
reg_base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2021-09-09 12:06:08 +03:00
if ( IS_ERR ( reg_base ) )
return PTR_ERR ( reg_base ) ;
2021-01-23 10:53:27 +03:00
2021-05-21 08:51:11 +03:00
disk = blk_alloc_disk ( NUMA_NO_NODE ) ;
2021-01-26 02:32:42 +03:00
if ( ! disk )
2021-09-28 01:01:02 +03:00
goto out ;
2021-01-26 02:32:42 +03:00
2021-01-23 10:53:27 +03:00
disk - > first_minor = 0 ;
2021-11-22 16:06:17 +03:00
disk - > flags = GENHD_FL_NO_PART ;
2021-01-23 10:53:27 +03:00
disk - > fops = & n64cart_fops ;
2021-01-26 02:32:43 +03:00
disk - > private_data = & pdev - > dev ;
2021-01-23 10:53:27 +03:00
strcpy ( disk - > disk_name , " n64cart " ) ;
2021-01-26 02:32:39 +03:00
set_capacity ( disk , size > > SECTOR_SHIFT ) ;
2021-01-23 10:53:27 +03:00
set_disk_ro ( disk , 1 ) ;
2021-01-26 02:32:42 +03:00
blk_queue_flag_set ( QUEUE_FLAG_NONROT , disk - > queue ) ;
blk_queue_physical_block_size ( disk - > queue , 4096 ) ;
blk_queue_logical_block_size ( disk - > queue , 4096 ) ;
2021-01-23 10:53:27 +03:00
2021-09-28 01:01:02 +03:00
err = add_disk ( disk ) ;
if ( err )
goto out_cleanup_disk ;
2021-01-23 10:53:27 +03:00
pr_info ( " n64cart: %u kb disk \n " , size / 1024 ) ;
return 0 ;
2021-09-28 01:01:02 +03:00
out_cleanup_disk :
blk_cleanup_disk ( disk ) ;
out :
return err ;
2021-01-23 10:53:27 +03:00
}
static struct platform_driver n64cart_driver = {
. driver = {
. name = " n64cart " ,
} ,
} ;
static int __init n64cart_init ( void )
{
return platform_driver_probe ( & n64cart_driver , n64cart_probe ) ;
}
module_init ( n64cart_init ) ;
2021-01-26 02:32:36 +03:00
MODULE_AUTHOR ( " Lauri Kasanen <cand@gmx.com> " ) ;
MODULE_DESCRIPTION ( " Driver for the N64 cart " ) ;
MODULE_LICENSE ( " GPL " ) ;