forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1273 lines
33 KiB
1273 lines
33 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Xilinx SystemACE device driver |
|
* |
|
* Copyright 2007 Secret Lab Technologies Ltd. |
|
*/ |
|
|
|
/* |
|
* The SystemACE chip is designed to configure FPGAs by loading an FPGA |
|
* bitstream from a file on a CF card and squirting it into FPGAs connected |
|
* to the SystemACE JTAG chain. It also has the advantage of providing an |
|
* MPU interface which can be used to control the FPGA configuration process |
|
* and to use the attached CF card for general purpose storage. |
|
* |
|
* This driver is a block device driver for the SystemACE. |
|
* |
|
* Initialization: |
|
* The driver registers itself as a platform_device driver at module |
|
* load time. The platform bus will take care of calling the |
|
* ace_probe() method for all SystemACE instances in the system. Any |
|
* number of SystemACE instances are supported. ace_probe() calls |
|
* ace_setup() which initialized all data structures, reads the CF |
|
* id structure and registers the device. |
|
* |
|
* Processing: |
|
* Just about all of the heavy lifting in this driver is performed by |
|
* a Finite State Machine (FSM). The driver needs to wait on a number |
|
* of events; some raised by interrupts, some which need to be polled |
|
* for. Describing all of the behaviour in a FSM seems to be the |
|
* easiest way to keep the complexity low and make it easy to |
|
* understand what the driver is doing. If the block ops or the |
|
* request function need to interact with the hardware, then they |
|
* simply need to flag the request and kick of FSM processing. |
|
* |
|
* The FSM itself is atomic-safe code which can be run from any |
|
* context. The general process flow is: |
|
* 1. obtain the ace->lock spinlock. |
|
* 2. loop on ace_fsm_dostate() until the ace->fsm_continue flag is |
|
* cleared. |
|
* 3. release the lock. |
|
* |
|
* Individual states do not sleep in any way. If a condition needs to |
|
* be waited for then the state much clear the fsm_continue flag and |
|
* either schedule the FSM to be run again at a later time, or expect |
|
* an interrupt to call the FSM when the desired condition is met. |
|
* |
|
* In normal operation, the FSM is processed at interrupt context |
|
* either when the driver's tasklet is scheduled, or when an irq is |
|
* raised by the hardware. The tasklet can be scheduled at any time. |
|
* The request method in particular schedules the tasklet when a new |
|
* request has been indicated by the block layer. Once started, the |
|
* FSM proceeds as far as it can processing the request until it |
|
* needs on a hardware event. At this point, it must yield execution. |
|
* |
|
* A state has two options when yielding execution: |
|
* 1. ace_fsm_yield() |
|
* - Call if need to poll for event. |
|
* - clears the fsm_continue flag to exit the processing loop |
|
* - reschedules the tasklet to run again as soon as possible |
|
* 2. ace_fsm_yieldirq() |
|
* - Call if an irq is expected from the HW |
|
* - clears the fsm_continue flag to exit the processing loop |
|
* - does not reschedule the tasklet so the FSM will not be processed |
|
* again until an irq is received. |
|
* After calling a yield function, the state must return control back |
|
* to the FSM main loop. |
|
* |
|
* Additionally, the driver maintains a kernel timer which can process |
|
* the FSM. If the FSM gets stalled, typically due to a missed |
|
* interrupt, then the kernel timer will expire and the driver can |
|
* continue where it left off. |
|
* |
|
* To Do: |
|
* - Add FPGA configuration control interface. |
|
* - Request major number from lanana |
|
*/ |
|
|
|
#undef DEBUG |
|
|
|
#include <linux/module.h> |
|
#include <linux/ctype.h> |
|
#include <linux/init.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/errno.h> |
|
#include <linux/kernel.h> |
|
#include <linux/delay.h> |
|
#include <linux/slab.h> |
|
#include <linux/blk-mq.h> |
|
#include <linux/mutex.h> |
|
#include <linux/ata.h> |
|
#include <linux/hdreg.h> |
|
#include <linux/platform_device.h> |
|
#if defined(CONFIG_OF) |
|
#include <linux/of_address.h> |
|
#include <linux/of_device.h> |
|
#include <linux/of_platform.h> |
|
#endif |
|
|
|
MODULE_AUTHOR("Grant Likely <[email protected]>"); |
|
MODULE_DESCRIPTION("Xilinx SystemACE device driver"); |
|
MODULE_LICENSE("GPL"); |
|
|
|
/* SystemACE register definitions */ |
|
#define ACE_BUSMODE (0x00) |
|
|
|
#define ACE_STATUS (0x04) |
|
#define ACE_STATUS_CFGLOCK (0x00000001) |
|
#define ACE_STATUS_MPULOCK (0x00000002) |
|
#define ACE_STATUS_CFGERROR (0x00000004) /* config controller error */ |
|
#define ACE_STATUS_CFCERROR (0x00000008) /* CF controller error */ |
|
#define ACE_STATUS_CFDETECT (0x00000010) |
|
#define ACE_STATUS_DATABUFRDY (0x00000020) |
|
#define ACE_STATUS_DATABUFMODE (0x00000040) |
|
#define ACE_STATUS_CFGDONE (0x00000080) |
|
#define ACE_STATUS_RDYFORCFCMD (0x00000100) |
|
#define ACE_STATUS_CFGMODEPIN (0x00000200) |
|
#define ACE_STATUS_CFGADDR_MASK (0x0000e000) |
|
#define ACE_STATUS_CFBSY (0x00020000) |
|
#define ACE_STATUS_CFRDY (0x00040000) |
|
#define ACE_STATUS_CFDWF (0x00080000) |
|
#define ACE_STATUS_CFDSC (0x00100000) |
|
#define ACE_STATUS_CFDRQ (0x00200000) |
|
#define ACE_STATUS_CFCORR (0x00400000) |
|
#define ACE_STATUS_CFERR (0x00800000) |
|
|
|
#define ACE_ERROR (0x08) |
|
#define ACE_CFGLBA (0x0c) |
|
#define ACE_MPULBA (0x10) |
|
|
|
#define ACE_SECCNTCMD (0x14) |
|
#define ACE_SECCNTCMD_RESET (0x0100) |
|
#define ACE_SECCNTCMD_IDENTIFY (0x0200) |
|
#define ACE_SECCNTCMD_READ_DATA (0x0300) |
|
#define ACE_SECCNTCMD_WRITE_DATA (0x0400) |
|
#define ACE_SECCNTCMD_ABORT (0x0600) |
|
|
|
#define ACE_VERSION (0x16) |
|
#define ACE_VERSION_REVISION_MASK (0x00FF) |
|
#define ACE_VERSION_MINOR_MASK (0x0F00) |
|
#define ACE_VERSION_MAJOR_MASK (0xF000) |
|
|
|
#define ACE_CTRL (0x18) |
|
#define ACE_CTRL_FORCELOCKREQ (0x0001) |
|
#define ACE_CTRL_LOCKREQ (0x0002) |
|
#define ACE_CTRL_FORCECFGADDR (0x0004) |
|
#define ACE_CTRL_FORCECFGMODE (0x0008) |
|
#define ACE_CTRL_CFGMODE (0x0010) |
|
#define ACE_CTRL_CFGSTART (0x0020) |
|
#define ACE_CTRL_CFGSEL (0x0040) |
|
#define ACE_CTRL_CFGRESET (0x0080) |
|
#define ACE_CTRL_DATABUFRDYIRQ (0x0100) |
|
#define ACE_CTRL_ERRORIRQ (0x0200) |
|
#define ACE_CTRL_CFGDONEIRQ (0x0400) |
|
#define ACE_CTRL_RESETIRQ (0x0800) |
|
#define ACE_CTRL_CFGPROG (0x1000) |
|
#define ACE_CTRL_CFGADDR_MASK (0xe000) |
|
|
|
#define ACE_FATSTAT (0x1c) |
|
|
|
#define ACE_NUM_MINORS 16 |
|
#define ACE_SECTOR_SIZE (512) |
|
#define ACE_FIFO_SIZE (32) |
|
#define ACE_BUF_PER_SECTOR (ACE_SECTOR_SIZE / ACE_FIFO_SIZE) |
|
|
|
#define ACE_BUS_WIDTH_8 0 |
|
#define ACE_BUS_WIDTH_16 1 |
|
|
|
struct ace_reg_ops; |
|
|
|
struct ace_device { |
|
/* driver state data */ |
|
int id; |
|
int media_change; |
|
int users; |
|
struct list_head list; |
|
|
|
/* finite state machine data */ |
|
struct tasklet_struct fsm_tasklet; |
|
uint fsm_task; /* Current activity (ACE_TASK_*) */ |
|
uint fsm_state; /* Current state (ACE_FSM_STATE_*) */ |
|
uint fsm_continue_flag; /* cleared to exit FSM mainloop */ |
|
uint fsm_iter_num; |
|
struct timer_list stall_timer; |
|
|
|
/* Transfer state/result, use for both id and block request */ |
|
struct request *req; /* request being processed */ |
|
void *data_ptr; /* pointer to I/O buffer */ |
|
int data_count; /* number of buffers remaining */ |
|
int data_result; /* Result of transfer; 0 := success */ |
|
|
|
int id_req_count; /* count of id requests */ |
|
int id_result; |
|
struct completion id_completion; /* used when id req finishes */ |
|
int in_irq; |
|
|
|
/* Details of hardware device */ |
|
resource_size_t physaddr; |
|
void __iomem *baseaddr; |
|
int irq; |
|
int bus_width; /* 0 := 8 bit; 1 := 16 bit */ |
|
struct ace_reg_ops *reg_ops; |
|
int lock_count; |
|
|
|
/* Block device data structures */ |
|
spinlock_t lock; |
|
struct device *dev; |
|
struct request_queue *queue; |
|
struct gendisk *gd; |
|
struct blk_mq_tag_set tag_set; |
|
struct list_head rq_list; |
|
|
|
/* Inserted CF card parameters */ |
|
u16 cf_id[ATA_ID_WORDS]; |
|
}; |
|
|
|
static DEFINE_MUTEX(xsysace_mutex); |
|
static int ace_major; |
|
|
|
/* --------------------------------------------------------------------- |
|
* Low level register access |
|
*/ |
|
|
|
struct ace_reg_ops { |
|
u16(*in) (struct ace_device * ace, int reg); |
|
void (*out) (struct ace_device * ace, int reg, u16 val); |
|
void (*datain) (struct ace_device * ace); |
|
void (*dataout) (struct ace_device * ace); |
|
}; |
|
|
|
/* 8 Bit bus width */ |
|
static u16 ace_in_8(struct ace_device *ace, int reg) |
|
{ |
|
void __iomem *r = ace->baseaddr + reg; |
|
return in_8(r) | (in_8(r + 1) << 8); |
|
} |
|
|
|
static void ace_out_8(struct ace_device *ace, int reg, u16 val) |
|
{ |
|
void __iomem *r = ace->baseaddr + reg; |
|
out_8(r, val); |
|
out_8(r + 1, val >> 8); |
|
} |
|
|
|
static void ace_datain_8(struct ace_device *ace) |
|
{ |
|
void __iomem *r = ace->baseaddr + 0x40; |
|
u8 *dst = ace->data_ptr; |
|
int i = ACE_FIFO_SIZE; |
|
while (i--) |
|
*dst++ = in_8(r++); |
|
ace->data_ptr = dst; |
|
} |
|
|
|
static void ace_dataout_8(struct ace_device *ace) |
|
{ |
|
void __iomem *r = ace->baseaddr + 0x40; |
|
u8 *src = ace->data_ptr; |
|
int i = ACE_FIFO_SIZE; |
|
while (i--) |
|
out_8(r++, *src++); |
|
ace->data_ptr = src; |
|
} |
|
|
|
static struct ace_reg_ops ace_reg_8_ops = { |
|
.in = ace_in_8, |
|
.out = ace_out_8, |
|
.datain = ace_datain_8, |
|
.dataout = ace_dataout_8, |
|
}; |
|
|
|
/* 16 bit big endian bus attachment */ |
|
static u16 ace_in_be16(struct ace_device *ace, int reg) |
|
{ |
|
return in_be16(ace->baseaddr + reg); |
|
} |
|
|
|
static void ace_out_be16(struct ace_device *ace, int reg, u16 val) |
|
{ |
|
out_be16(ace->baseaddr + reg, val); |
|
} |
|
|
|
static void ace_datain_be16(struct ace_device *ace) |
|
{ |
|
int i = ACE_FIFO_SIZE / 2; |
|
u16 *dst = ace->data_ptr; |
|
while (i--) |
|
*dst++ = in_le16(ace->baseaddr + 0x40); |
|
ace->data_ptr = dst; |
|
} |
|
|
|
static void ace_dataout_be16(struct ace_device *ace) |
|
{ |
|
int i = ACE_FIFO_SIZE / 2; |
|
u16 *src = ace->data_ptr; |
|
while (i--) |
|
out_le16(ace->baseaddr + 0x40, *src++); |
|
ace->data_ptr = src; |
|
} |
|
|
|
/* 16 bit little endian bus attachment */ |
|
static u16 ace_in_le16(struct ace_device *ace, int reg) |
|
{ |
|
return in_le16(ace->baseaddr + reg); |
|
} |
|
|
|
static void ace_out_le16(struct ace_device *ace, int reg, u16 val) |
|
{ |
|
out_le16(ace->baseaddr + reg, val); |
|
} |
|
|
|
static void ace_datain_le16(struct ace_device *ace) |
|
{ |
|
int i = ACE_FIFO_SIZE / 2; |
|
u16 *dst = ace->data_ptr; |
|
while (i--) |
|
*dst++ = in_be16(ace->baseaddr + 0x40); |
|
ace->data_ptr = dst; |
|
} |
|
|
|
static void ace_dataout_le16(struct ace_device *ace) |
|
{ |
|
int i = ACE_FIFO_SIZE / 2; |
|
u16 *src = ace->data_ptr; |
|
while (i--) |
|
out_be16(ace->baseaddr + 0x40, *src++); |
|
ace->data_ptr = src; |
|
} |
|
|
|
static struct ace_reg_ops ace_reg_be16_ops = { |
|
.in = ace_in_be16, |
|
.out = ace_out_be16, |
|
.datain = ace_datain_be16, |
|
.dataout = ace_dataout_be16, |
|
}; |
|
|
|
static struct ace_reg_ops ace_reg_le16_ops = { |
|
.in = ace_in_le16, |
|
.out = ace_out_le16, |
|
.datain = ace_datain_le16, |
|
.dataout = ace_dataout_le16, |
|
}; |
|
|
|
static inline u16 ace_in(struct ace_device *ace, int reg) |
|
{ |
|
return ace->reg_ops->in(ace, reg); |
|
} |
|
|
|
static inline u32 ace_in32(struct ace_device *ace, int reg) |
|
{ |
|
return ace_in(ace, reg) | (ace_in(ace, reg + 2) << 16); |
|
} |
|
|
|
static inline void ace_out(struct ace_device *ace, int reg, u16 val) |
|
{ |
|
ace->reg_ops->out(ace, reg, val); |
|
} |
|
|
|
static inline void ace_out32(struct ace_device *ace, int reg, u32 val) |
|
{ |
|
ace_out(ace, reg, val); |
|
ace_out(ace, reg + 2, val >> 16); |
|
} |
|
|
|
/* --------------------------------------------------------------------- |
|
* Debug support functions |
|
*/ |
|
|
|
#if defined(DEBUG) |
|
static void ace_dump_mem(void *base, int len) |
|
{ |
|
const char *ptr = base; |
|
int i, j; |
|
|
|
for (i = 0; i < len; i += 16) { |
|
printk(KERN_INFO "%.8x:", i); |
|
for (j = 0; j < 16; j++) { |
|
if (!(j % 4)) |
|
printk(" "); |
|
printk("%.2x", ptr[i + j]); |
|
} |
|
printk(" "); |
|
for (j = 0; j < 16; j++) |
|
printk("%c", isprint(ptr[i + j]) ? ptr[i + j] : '.'); |
|
printk("\n"); |
|
} |
|
} |
|
#else |
|
static inline void ace_dump_mem(void *base, int len) |
|
{ |
|
} |
|
#endif |
|
|
|
static void ace_dump_regs(struct ace_device *ace) |
|
{ |
|
dev_info(ace->dev, |
|
" ctrl: %.8x seccnt/cmd: %.4x ver:%.4x\n" |
|
" status:%.8x mpu_lba:%.8x busmode:%4x\n" |
|
" error: %.8x cfg_lba:%.8x fatstat:%.4x\n", |
|
ace_in32(ace, ACE_CTRL), |
|
ace_in(ace, ACE_SECCNTCMD), |
|
ace_in(ace, ACE_VERSION), |
|
ace_in32(ace, ACE_STATUS), |
|
ace_in32(ace, ACE_MPULBA), |
|
ace_in(ace, ACE_BUSMODE), |
|
ace_in32(ace, ACE_ERROR), |
|
ace_in32(ace, ACE_CFGLBA), ace_in(ace, ACE_FATSTAT)); |
|
} |
|
|
|
static void ace_fix_driveid(u16 *id) |
|
{ |
|
#if defined(__BIG_ENDIAN) |
|
int i; |
|
|
|
/* All half words have wrong byte order; swap the bytes */ |
|
for (i = 0; i < ATA_ID_WORDS; i++, id++) |
|
*id = le16_to_cpu(*id); |
|
#endif |
|
} |
|
|
|
/* --------------------------------------------------------------------- |
|
* Finite State Machine (FSM) implementation |
|
*/ |
|
|
|
/* FSM tasks; used to direct state transitions */ |
|
#define ACE_TASK_IDLE 0 |
|
#define ACE_TASK_IDENTIFY 1 |
|
#define ACE_TASK_READ 2 |
|
#define ACE_TASK_WRITE 3 |
|
#define ACE_FSM_NUM_TASKS 4 |
|
|
|
/* FSM state definitions */ |
|
#define ACE_FSM_STATE_IDLE 0 |
|
#define ACE_FSM_STATE_REQ_LOCK 1 |
|
#define ACE_FSM_STATE_WAIT_LOCK 2 |
|
#define ACE_FSM_STATE_WAIT_CFREADY 3 |
|
#define ACE_FSM_STATE_IDENTIFY_PREPARE 4 |
|
#define ACE_FSM_STATE_IDENTIFY_TRANSFER 5 |
|
#define ACE_FSM_STATE_IDENTIFY_COMPLETE 6 |
|
#define ACE_FSM_STATE_REQ_PREPARE 7 |
|
#define ACE_FSM_STATE_REQ_TRANSFER 8 |
|
#define ACE_FSM_STATE_REQ_COMPLETE 9 |
|
#define ACE_FSM_STATE_ERROR 10 |
|
#define ACE_FSM_NUM_STATES 11 |
|
|
|
/* Set flag to exit FSM loop and reschedule tasklet */ |
|
static inline void ace_fsm_yieldpoll(struct ace_device *ace) |
|
{ |
|
tasklet_schedule(&ace->fsm_tasklet); |
|
ace->fsm_continue_flag = 0; |
|
} |
|
|
|
static inline void ace_fsm_yield(struct ace_device *ace) |
|
{ |
|
dev_dbg(ace->dev, "%s()\n", __func__); |
|
ace_fsm_yieldpoll(ace); |
|
} |
|
|
|
/* Set flag to exit FSM loop and wait for IRQ to reschedule tasklet */ |
|
static inline void ace_fsm_yieldirq(struct ace_device *ace) |
|
{ |
|
dev_dbg(ace->dev, "ace_fsm_yieldirq()\n"); |
|
|
|
if (ace->irq > 0) |
|
ace->fsm_continue_flag = 0; |
|
else |
|
ace_fsm_yieldpoll(ace); |
|
} |
|
|
|
static bool ace_has_next_request(struct request_queue *q) |
|
{ |
|
struct ace_device *ace = q->queuedata; |
|
|
|
return !list_empty(&ace->rq_list); |
|
} |
|
|
|
/* Get the next read/write request; ending requests that we don't handle */ |
|
static struct request *ace_get_next_request(struct request_queue *q) |
|
{ |
|
struct ace_device *ace = q->queuedata; |
|
struct request *rq; |
|
|
|
rq = list_first_entry_or_null(&ace->rq_list, struct request, queuelist); |
|
if (rq) { |
|
list_del_init(&rq->queuelist); |
|
blk_mq_start_request(rq); |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static void ace_fsm_dostate(struct ace_device *ace) |
|
{ |
|
struct request *req; |
|
u32 status; |
|
u16 val; |
|
int count; |
|
|
|
#if defined(DEBUG) |
|
dev_dbg(ace->dev, "fsm_state=%i, id_req_count=%i\n", |
|
ace->fsm_state, ace->id_req_count); |
|
#endif |
|
|
|
/* Verify that there is actually a CF in the slot. If not, then |
|
* bail out back to the idle state and wake up all the waiters */ |
|
status = ace_in32(ace, ACE_STATUS); |
|
if ((status & ACE_STATUS_CFDETECT) == 0) { |
|
ace->fsm_state = ACE_FSM_STATE_IDLE; |
|
ace->media_change = 1; |
|
set_capacity(ace->gd, 0); |
|
dev_info(ace->dev, "No CF in slot\n"); |
|
|
|
/* Drop all in-flight and pending requests */ |
|
if (ace->req) { |
|
blk_mq_end_request(ace->req, BLK_STS_IOERR); |
|
ace->req = NULL; |
|
} |
|
while ((req = ace_get_next_request(ace->queue)) != NULL) |
|
blk_mq_end_request(req, BLK_STS_IOERR); |
|
|
|
/* Drop back to IDLE state and notify waiters */ |
|
ace->fsm_state = ACE_FSM_STATE_IDLE; |
|
ace->id_result = -EIO; |
|
while (ace->id_req_count) { |
|
complete(&ace->id_completion); |
|
ace->id_req_count--; |
|
} |
|
} |
|
|
|
switch (ace->fsm_state) { |
|
case ACE_FSM_STATE_IDLE: |
|
/* See if there is anything to do */ |
|
if (ace->id_req_count || ace_has_next_request(ace->queue)) { |
|
ace->fsm_iter_num++; |
|
ace->fsm_state = ACE_FSM_STATE_REQ_LOCK; |
|
mod_timer(&ace->stall_timer, jiffies + HZ); |
|
if (!timer_pending(&ace->stall_timer)) |
|
add_timer(&ace->stall_timer); |
|
break; |
|
} |
|
del_timer(&ace->stall_timer); |
|
ace->fsm_continue_flag = 0; |
|
break; |
|
|
|
case ACE_FSM_STATE_REQ_LOCK: |
|
if (ace_in(ace, ACE_STATUS) & ACE_STATUS_MPULOCK) { |
|
/* Already have the lock, jump to next state */ |
|
ace->fsm_state = ACE_FSM_STATE_WAIT_CFREADY; |
|
break; |
|
} |
|
|
|
/* Request the lock */ |
|
val = ace_in(ace, ACE_CTRL); |
|
ace_out(ace, ACE_CTRL, val | ACE_CTRL_LOCKREQ); |
|
ace->fsm_state = ACE_FSM_STATE_WAIT_LOCK; |
|
break; |
|
|
|
case ACE_FSM_STATE_WAIT_LOCK: |
|
if (ace_in(ace, ACE_STATUS) & ACE_STATUS_MPULOCK) { |
|
/* got the lock; move to next state */ |
|
ace->fsm_state = ACE_FSM_STATE_WAIT_CFREADY; |
|
break; |
|
} |
|
|
|
/* wait a bit for the lock */ |
|
ace_fsm_yield(ace); |
|
break; |
|
|
|
case ACE_FSM_STATE_WAIT_CFREADY: |
|
status = ace_in32(ace, ACE_STATUS); |
|
if (!(status & ACE_STATUS_RDYFORCFCMD) || |
|
(status & ACE_STATUS_CFBSY)) { |
|
/* CF card isn't ready; it needs to be polled */ |
|
ace_fsm_yield(ace); |
|
break; |
|
} |
|
|
|
/* Device is ready for command; determine what to do next */ |
|
if (ace->id_req_count) |
|
ace->fsm_state = ACE_FSM_STATE_IDENTIFY_PREPARE; |
|
else |
|
ace->fsm_state = ACE_FSM_STATE_REQ_PREPARE; |
|
break; |
|
|
|
case ACE_FSM_STATE_IDENTIFY_PREPARE: |
|
/* Send identify command */ |
|
ace->fsm_task = ACE_TASK_IDENTIFY; |
|
ace->data_ptr = ace->cf_id; |
|
ace->data_count = ACE_BUF_PER_SECTOR; |
|
ace_out(ace, ACE_SECCNTCMD, ACE_SECCNTCMD_IDENTIFY); |
|
|
|
/* As per datasheet, put config controller in reset */ |
|
val = ace_in(ace, ACE_CTRL); |
|
ace_out(ace, ACE_CTRL, val | ACE_CTRL_CFGRESET); |
|
|
|
/* irq handler takes over from this point; wait for the |
|
* transfer to complete */ |
|
ace->fsm_state = ACE_FSM_STATE_IDENTIFY_TRANSFER; |
|
ace_fsm_yieldirq(ace); |
|
break; |
|
|
|
case ACE_FSM_STATE_IDENTIFY_TRANSFER: |
|
/* Check that the sysace is ready to receive data */ |
|
status = ace_in32(ace, ACE_STATUS); |
|
if (status & ACE_STATUS_CFBSY) { |
|
dev_dbg(ace->dev, "CFBSY set; t=%i iter=%i dc=%i\n", |
|
ace->fsm_task, ace->fsm_iter_num, |
|
ace->data_count); |
|
ace_fsm_yield(ace); |
|
break; |
|
} |
|
if (!(status & ACE_STATUS_DATABUFRDY)) { |
|
ace_fsm_yield(ace); |
|
break; |
|
} |
|
|
|
/* Transfer the next buffer */ |
|
ace->reg_ops->datain(ace); |
|
ace->data_count--; |
|
|
|
/* If there are still buffers to be transfers; jump out here */ |
|
if (ace->data_count != 0) { |
|
ace_fsm_yieldirq(ace); |
|
break; |
|
} |
|
|
|
/* transfer finished; kick state machine */ |
|
dev_dbg(ace->dev, "identify finished\n"); |
|
ace->fsm_state = ACE_FSM_STATE_IDENTIFY_COMPLETE; |
|
break; |
|
|
|
case ACE_FSM_STATE_IDENTIFY_COMPLETE: |
|
ace_fix_driveid(ace->cf_id); |
|
ace_dump_mem(ace->cf_id, 512); /* Debug: Dump out disk ID */ |
|
|
|
if (ace->data_result) { |
|
/* Error occurred, disable the disk */ |
|
ace->media_change = 1; |
|
set_capacity(ace->gd, 0); |
|
dev_err(ace->dev, "error fetching CF id (%i)\n", |
|
ace->data_result); |
|
} else { |
|
ace->media_change = 0; |
|
|
|
/* Record disk parameters */ |
|
set_capacity(ace->gd, |
|
ata_id_u32(ace->cf_id, ATA_ID_LBA_CAPACITY)); |
|
dev_info(ace->dev, "capacity: %i sectors\n", |
|
ata_id_u32(ace->cf_id, ATA_ID_LBA_CAPACITY)); |
|
} |
|
|
|
/* We're done, drop to IDLE state and notify waiters */ |
|
ace->fsm_state = ACE_FSM_STATE_IDLE; |
|
ace->id_result = ace->data_result; |
|
while (ace->id_req_count) { |
|
complete(&ace->id_completion); |
|
ace->id_req_count--; |
|
} |
|
break; |
|
|
|
case ACE_FSM_STATE_REQ_PREPARE: |
|
req = ace_get_next_request(ace->queue); |
|
if (!req) { |
|
ace->fsm_state = ACE_FSM_STATE_IDLE; |
|
break; |
|
} |
|
|
|
/* Okay, it's a data request, set it up for transfer */ |
|
dev_dbg(ace->dev, |
|
"request: sec=%llx hcnt=%x, ccnt=%x, dir=%i\n", |
|
(unsigned long long)blk_rq_pos(req), |
|
blk_rq_sectors(req), blk_rq_cur_sectors(req), |
|
rq_data_dir(req)); |
|
|
|
ace->req = req; |
|
ace->data_ptr = bio_data(req->bio); |
|
ace->data_count = blk_rq_cur_sectors(req) * ACE_BUF_PER_SECTOR; |
|
ace_out32(ace, ACE_MPULBA, blk_rq_pos(req) & 0x0FFFFFFF); |
|
|
|
count = blk_rq_sectors(req); |
|
if (rq_data_dir(req)) { |
|
/* Kick off write request */ |
|
dev_dbg(ace->dev, "write data\n"); |
|
ace->fsm_task = ACE_TASK_WRITE; |
|
ace_out(ace, ACE_SECCNTCMD, |
|
count | ACE_SECCNTCMD_WRITE_DATA); |
|
} else { |
|
/* Kick off read request */ |
|
dev_dbg(ace->dev, "read data\n"); |
|
ace->fsm_task = ACE_TASK_READ; |
|
ace_out(ace, ACE_SECCNTCMD, |
|
count | ACE_SECCNTCMD_READ_DATA); |
|
} |
|
|
|
/* As per datasheet, put config controller in reset */ |
|
val = ace_in(ace, ACE_CTRL); |
|
ace_out(ace, ACE_CTRL, val | ACE_CTRL_CFGRESET); |
|
|
|
/* Move to the transfer state. The systemace will raise |
|
* an interrupt once there is something to do |
|
*/ |
|
ace->fsm_state = ACE_FSM_STATE_REQ_TRANSFER; |
|
if (ace->fsm_task == ACE_TASK_READ) |
|
ace_fsm_yieldirq(ace); /* wait for data ready */ |
|
break; |
|
|
|
case ACE_FSM_STATE_REQ_TRANSFER: |
|
/* Check that the sysace is ready to receive data */ |
|
status = ace_in32(ace, ACE_STATUS); |
|
if (status & ACE_STATUS_CFBSY) { |
|
dev_dbg(ace->dev, |
|
"CFBSY set; t=%i iter=%i c=%i dc=%i irq=%i\n", |
|
ace->fsm_task, ace->fsm_iter_num, |
|
blk_rq_cur_sectors(ace->req) * 16, |
|
ace->data_count, ace->in_irq); |
|
ace_fsm_yield(ace); /* need to poll CFBSY bit */ |
|
break; |
|
} |
|
if (!(status & ACE_STATUS_DATABUFRDY)) { |
|
dev_dbg(ace->dev, |
|
"DATABUF not set; t=%i iter=%i c=%i dc=%i irq=%i\n", |
|
ace->fsm_task, ace->fsm_iter_num, |
|
blk_rq_cur_sectors(ace->req) * 16, |
|
ace->data_count, ace->in_irq); |
|
ace_fsm_yieldirq(ace); |
|
break; |
|
} |
|
|
|
/* Transfer the next buffer */ |
|
if (ace->fsm_task == ACE_TASK_WRITE) |
|
ace->reg_ops->dataout(ace); |
|
else |
|
ace->reg_ops->datain(ace); |
|
ace->data_count--; |
|
|
|
/* If there are still buffers to be transfers; jump out here */ |
|
if (ace->data_count != 0) { |
|
ace_fsm_yieldirq(ace); |
|
break; |
|
} |
|
|
|
/* bio finished; is there another one? */ |
|
if (blk_update_request(ace->req, BLK_STS_OK, |
|
blk_rq_cur_bytes(ace->req))) { |
|
/* dev_dbg(ace->dev, "next block; h=%u c=%u\n", |
|
* blk_rq_sectors(ace->req), |
|
* blk_rq_cur_sectors(ace->req)); |
|
*/ |
|
ace->data_ptr = bio_data(ace->req->bio); |
|
ace->data_count = blk_rq_cur_sectors(ace->req) * 16; |
|
ace_fsm_yieldirq(ace); |
|
break; |
|
} |
|
|
|
ace->fsm_state = ACE_FSM_STATE_REQ_COMPLETE; |
|
break; |
|
|
|
case ACE_FSM_STATE_REQ_COMPLETE: |
|
ace->req = NULL; |
|
|
|
/* Finished request; go to idle state */ |
|
ace->fsm_state = ACE_FSM_STATE_IDLE; |
|
break; |
|
|
|
default: |
|
ace->fsm_state = ACE_FSM_STATE_IDLE; |
|
break; |
|
} |
|
} |
|
|
|
static void ace_fsm_tasklet(unsigned long data) |
|
{ |
|
struct ace_device *ace = (void *)data; |
|
unsigned long flags; |
|
|
|
spin_lock_irqsave(&ace->lock, flags); |
|
|
|
/* Loop over state machine until told to stop */ |
|
ace->fsm_continue_flag = 1; |
|
while (ace->fsm_continue_flag) |
|
ace_fsm_dostate(ace); |
|
|
|
spin_unlock_irqrestore(&ace->lock, flags); |
|
} |
|
|
|
static void ace_stall_timer(struct timer_list *t) |
|
{ |
|
struct ace_device *ace = from_timer(ace, t, stall_timer); |
|
unsigned long flags; |
|
|
|
dev_warn(ace->dev, |
|
"kicking stalled fsm; state=%i task=%i iter=%i dc=%i\n", |
|
ace->fsm_state, ace->fsm_task, ace->fsm_iter_num, |
|
ace->data_count); |
|
spin_lock_irqsave(&ace->lock, flags); |
|
|
|
/* Rearm the stall timer *before* entering FSM (which may then |
|
* delete the timer) */ |
|
mod_timer(&ace->stall_timer, jiffies + HZ); |
|
|
|
/* Loop over state machine until told to stop */ |
|
ace->fsm_continue_flag = 1; |
|
while (ace->fsm_continue_flag) |
|
ace_fsm_dostate(ace); |
|
|
|
spin_unlock_irqrestore(&ace->lock, flags); |
|
} |
|
|
|
/* --------------------------------------------------------------------- |
|
* Interrupt handling routines |
|
*/ |
|
static int ace_interrupt_checkstate(struct ace_device *ace) |
|
{ |
|
u32 sreg = ace_in32(ace, ACE_STATUS); |
|
u16 creg = ace_in(ace, ACE_CTRL); |
|
|
|
/* Check for error occurrence */ |
|
if ((sreg & (ACE_STATUS_CFGERROR | ACE_STATUS_CFCERROR)) && |
|
(creg & ACE_CTRL_ERRORIRQ)) { |
|
dev_err(ace->dev, "transfer failure\n"); |
|
ace_dump_regs(ace); |
|
return -EIO; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static irqreturn_t ace_interrupt(int irq, void *dev_id) |
|
{ |
|
u16 creg; |
|
struct ace_device *ace = dev_id; |
|
|
|
/* be safe and get the lock */ |
|
spin_lock(&ace->lock); |
|
ace->in_irq = 1; |
|
|
|
/* clear the interrupt */ |
|
creg = ace_in(ace, ACE_CTRL); |
|
ace_out(ace, ACE_CTRL, creg | ACE_CTRL_RESETIRQ); |
|
ace_out(ace, ACE_CTRL, creg); |
|
|
|
/* check for IO failures */ |
|
if (ace_interrupt_checkstate(ace)) |
|
ace->data_result = -EIO; |
|
|
|
if (ace->fsm_task == 0) { |
|
dev_err(ace->dev, |
|
"spurious irq; stat=%.8x ctrl=%.8x cmd=%.4x\n", |
|
ace_in32(ace, ACE_STATUS), ace_in32(ace, ACE_CTRL), |
|
ace_in(ace, ACE_SECCNTCMD)); |
|
dev_err(ace->dev, "fsm_task=%i fsm_state=%i data_count=%i\n", |
|
ace->fsm_task, ace->fsm_state, ace->data_count); |
|
} |
|
|
|
/* Loop over state machine until told to stop */ |
|
ace->fsm_continue_flag = 1; |
|
while (ace->fsm_continue_flag) |
|
ace_fsm_dostate(ace); |
|
|
|
/* done with interrupt; drop the lock */ |
|
ace->in_irq = 0; |
|
spin_unlock(&ace->lock); |
|
|
|
return IRQ_HANDLED; |
|
} |
|
|
|
/* --------------------------------------------------------------------- |
|
* Block ops |
|
*/ |
|
static blk_status_t ace_queue_rq(struct blk_mq_hw_ctx *hctx, |
|
const struct blk_mq_queue_data *bd) |
|
{ |
|
struct ace_device *ace = hctx->queue->queuedata; |
|
struct request *req = bd->rq; |
|
|
|
if (blk_rq_is_passthrough(req)) { |
|
blk_mq_start_request(req); |
|
return BLK_STS_IOERR; |
|
} |
|
|
|
spin_lock_irq(&ace->lock); |
|
list_add_tail(&req->queuelist, &ace->rq_list); |
|
spin_unlock_irq(&ace->lock); |
|
|
|
tasklet_schedule(&ace->fsm_tasklet); |
|
return BLK_STS_OK; |
|
} |
|
|
|
static unsigned int ace_check_events(struct gendisk *gd, unsigned int clearing) |
|
{ |
|
struct ace_device *ace = gd->private_data; |
|
dev_dbg(ace->dev, "ace_check_events(): %i\n", ace->media_change); |
|
|
|
return ace->media_change ? DISK_EVENT_MEDIA_CHANGE : 0; |
|
} |
|
|
|
static void ace_media_changed(struct ace_device *ace) |
|
{ |
|
unsigned long flags; |
|
|
|
dev_dbg(ace->dev, "requesting cf id and scheduling tasklet\n"); |
|
|
|
spin_lock_irqsave(&ace->lock, flags); |
|
ace->id_req_count++; |
|
spin_unlock_irqrestore(&ace->lock, flags); |
|
|
|
tasklet_schedule(&ace->fsm_tasklet); |
|
wait_for_completion(&ace->id_completion); |
|
|
|
dev_dbg(ace->dev, "revalidate complete\n"); |
|
} |
|
|
|
static int ace_open(struct block_device *bdev, fmode_t mode) |
|
{ |
|
struct ace_device *ace = bdev->bd_disk->private_data; |
|
unsigned long flags; |
|
|
|
dev_dbg(ace->dev, "ace_open() users=%i\n", ace->users + 1); |
|
|
|
mutex_lock(&xsysace_mutex); |
|
spin_lock_irqsave(&ace->lock, flags); |
|
ace->users++; |
|
spin_unlock_irqrestore(&ace->lock, flags); |
|
|
|
if (bdev_check_media_change(bdev) && ace->media_change) |
|
ace_media_changed(ace); |
|
mutex_unlock(&xsysace_mutex); |
|
|
|
return 0; |
|
} |
|
|
|
static void ace_release(struct gendisk *disk, fmode_t mode) |
|
{ |
|
struct ace_device *ace = disk->private_data; |
|
unsigned long flags; |
|
u16 val; |
|
|
|
dev_dbg(ace->dev, "ace_release() users=%i\n", ace->users - 1); |
|
|
|
mutex_lock(&xsysace_mutex); |
|
spin_lock_irqsave(&ace->lock, flags); |
|
ace->users--; |
|
if (ace->users == 0) { |
|
val = ace_in(ace, ACE_CTRL); |
|
ace_out(ace, ACE_CTRL, val & ~ACE_CTRL_LOCKREQ); |
|
} |
|
spin_unlock_irqrestore(&ace->lock, flags); |
|
mutex_unlock(&xsysace_mutex); |
|
} |
|
|
|
static int ace_getgeo(struct block_device *bdev, struct hd_geometry *geo) |
|
{ |
|
struct ace_device *ace = bdev->bd_disk->private_data; |
|
u16 *cf_id = ace->cf_id; |
|
|
|
dev_dbg(ace->dev, "ace_getgeo()\n"); |
|
|
|
geo->heads = cf_id[ATA_ID_HEADS]; |
|
geo->sectors = cf_id[ATA_ID_SECTORS]; |
|
geo->cylinders = cf_id[ATA_ID_CYLS]; |
|
|
|
return 0; |
|
} |
|
|
|
static const struct block_device_operations ace_fops = { |
|
.owner = THIS_MODULE, |
|
.open = ace_open, |
|
.release = ace_release, |
|
.check_events = ace_check_events, |
|
.getgeo = ace_getgeo, |
|
}; |
|
|
|
static const struct blk_mq_ops ace_mq_ops = { |
|
.queue_rq = ace_queue_rq, |
|
}; |
|
|
|
/* -------------------------------------------------------------------- |
|
* SystemACE device setup/teardown code |
|
*/ |
|
static int ace_setup(struct ace_device *ace) |
|
{ |
|
u16 version; |
|
u16 val; |
|
int rc; |
|
|
|
dev_dbg(ace->dev, "ace_setup(ace=0x%p)\n", ace); |
|
dev_dbg(ace->dev, "physaddr=0x%llx irq=%i\n", |
|
(unsigned long long)ace->physaddr, ace->irq); |
|
|
|
spin_lock_init(&ace->lock); |
|
init_completion(&ace->id_completion); |
|
INIT_LIST_HEAD(&ace->rq_list); |
|
|
|
/* |
|
* Map the device |
|
*/ |
|
ace->baseaddr = ioremap(ace->physaddr, 0x80); |
|
if (!ace->baseaddr) |
|
goto err_ioremap; |
|
|
|
/* |
|
* Initialize the state machine tasklet and stall timer |
|
*/ |
|
tasklet_init(&ace->fsm_tasklet, ace_fsm_tasklet, (unsigned long)ace); |
|
timer_setup(&ace->stall_timer, ace_stall_timer, 0); |
|
|
|
/* |
|
* Initialize the request queue |
|
*/ |
|
ace->queue = blk_mq_init_sq_queue(&ace->tag_set, &ace_mq_ops, 2, |
|
BLK_MQ_F_SHOULD_MERGE); |
|
if (IS_ERR(ace->queue)) { |
|
rc = PTR_ERR(ace->queue); |
|
ace->queue = NULL; |
|
goto err_blk_initq; |
|
} |
|
ace->queue->queuedata = ace; |
|
|
|
blk_queue_logical_block_size(ace->queue, 512); |
|
blk_queue_bounce_limit(ace->queue, BLK_BOUNCE_HIGH); |
|
|
|
/* |
|
* Allocate and initialize GD structure |
|
*/ |
|
ace->gd = alloc_disk(ACE_NUM_MINORS); |
|
if (!ace->gd) |
|
goto err_alloc_disk; |
|
|
|
ace->gd->major = ace_major; |
|
ace->gd->first_minor = ace->id * ACE_NUM_MINORS; |
|
ace->gd->fops = &ace_fops; |
|
ace->gd->events = DISK_EVENT_MEDIA_CHANGE; |
|
ace->gd->queue = ace->queue; |
|
ace->gd->private_data = ace; |
|
snprintf(ace->gd->disk_name, 32, "xs%c", ace->id + 'a'); |
|
|
|
/* set bus width */ |
|
if (ace->bus_width == ACE_BUS_WIDTH_16) { |
|
/* 0x0101 should work regardless of endianess */ |
|
ace_out_le16(ace, ACE_BUSMODE, 0x0101); |
|
|
|
/* read it back to determine endianess */ |
|
if (ace_in_le16(ace, ACE_BUSMODE) == 0x0001) |
|
ace->reg_ops = &ace_reg_le16_ops; |
|
else |
|
ace->reg_ops = &ace_reg_be16_ops; |
|
} else { |
|
ace_out_8(ace, ACE_BUSMODE, 0x00); |
|
ace->reg_ops = &ace_reg_8_ops; |
|
} |
|
|
|
/* Make sure version register is sane */ |
|
version = ace_in(ace, ACE_VERSION); |
|
if ((version == 0) || (version == 0xFFFF)) |
|
goto err_read; |
|
|
|
/* Put sysace in a sane state by clearing most control reg bits */ |
|
ace_out(ace, ACE_CTRL, ACE_CTRL_FORCECFGMODE | |
|
ACE_CTRL_DATABUFRDYIRQ | ACE_CTRL_ERRORIRQ); |
|
|
|
/* Now we can hook up the irq handler */ |
|
if (ace->irq > 0) { |
|
rc = request_irq(ace->irq, ace_interrupt, 0, "systemace", ace); |
|
if (rc) { |
|
/* Failure - fall back to polled mode */ |
|
dev_err(ace->dev, "request_irq failed\n"); |
|
ace->irq = rc; |
|
} |
|
} |
|
|
|
/* Enable interrupts */ |
|
val = ace_in(ace, ACE_CTRL); |
|
val |= ACE_CTRL_DATABUFRDYIRQ | ACE_CTRL_ERRORIRQ; |
|
ace_out(ace, ACE_CTRL, val); |
|
|
|
/* Print the identification */ |
|
dev_info(ace->dev, "Xilinx SystemACE revision %i.%i.%i\n", |
|
(version >> 12) & 0xf, (version >> 8) & 0x0f, version & 0xff); |
|
dev_dbg(ace->dev, "physaddr 0x%llx, mapped to 0x%p, irq=%i\n", |
|
(unsigned long long) ace->physaddr, ace->baseaddr, ace->irq); |
|
|
|
ace->media_change = 1; |
|
ace_media_changed(ace); |
|
|
|
/* Make the sysace device 'live' */ |
|
add_disk(ace->gd); |
|
|
|
return 0; |
|
|
|
err_read: |
|
/* prevent double queue cleanup */ |
|
ace->gd->queue = NULL; |
|
put_disk(ace->gd); |
|
err_alloc_disk: |
|
blk_cleanup_queue(ace->queue); |
|
blk_mq_free_tag_set(&ace->tag_set); |
|
err_blk_initq: |
|
iounmap(ace->baseaddr); |
|
err_ioremap: |
|
dev_info(ace->dev, "xsysace: error initializing device at 0x%llx\n", |
|
(unsigned long long) ace->physaddr); |
|
return -ENOMEM; |
|
} |
|
|
|
static void ace_teardown(struct ace_device *ace) |
|
{ |
|
if (ace->gd) { |
|
del_gendisk(ace->gd); |
|
put_disk(ace->gd); |
|
} |
|
|
|
if (ace->queue) { |
|
blk_cleanup_queue(ace->queue); |
|
blk_mq_free_tag_set(&ace->tag_set); |
|
} |
|
|
|
tasklet_kill(&ace->fsm_tasklet); |
|
|
|
if (ace->irq > 0) |
|
free_irq(ace->irq, ace); |
|
|
|
iounmap(ace->baseaddr); |
|
} |
|
|
|
static int ace_alloc(struct device *dev, int id, resource_size_t physaddr, |
|
int irq, int bus_width) |
|
{ |
|
struct ace_device *ace; |
|
int rc; |
|
dev_dbg(dev, "ace_alloc(%p)\n", dev); |
|
|
|
/* Allocate and initialize the ace device structure */ |
|
ace = kzalloc(sizeof(struct ace_device), GFP_KERNEL); |
|
if (!ace) { |
|
rc = -ENOMEM; |
|
goto err_alloc; |
|
} |
|
|
|
ace->dev = dev; |
|
ace->id = id; |
|
ace->physaddr = physaddr; |
|
ace->irq = irq; |
|
ace->bus_width = bus_width; |
|
|
|
/* Call the setup code */ |
|
rc = ace_setup(ace); |
|
if (rc) |
|
goto err_setup; |
|
|
|
dev_set_drvdata(dev, ace); |
|
return 0; |
|
|
|
err_setup: |
|
dev_set_drvdata(dev, NULL); |
|
kfree(ace); |
|
err_alloc: |
|
dev_err(dev, "could not initialize device, err=%i\n", rc); |
|
return rc; |
|
} |
|
|
|
static void ace_free(struct device *dev) |
|
{ |
|
struct ace_device *ace = dev_get_drvdata(dev); |
|
dev_dbg(dev, "ace_free(%p)\n", dev); |
|
|
|
if (ace) { |
|
ace_teardown(ace); |
|
dev_set_drvdata(dev, NULL); |
|
kfree(ace); |
|
} |
|
} |
|
|
|
/* --------------------------------------------------------------------- |
|
* Platform Bus Support |
|
*/ |
|
|
|
static int ace_probe(struct platform_device *dev) |
|
{ |
|
int bus_width = ACE_BUS_WIDTH_16; /* FIXME: should not be hard coded */ |
|
resource_size_t physaddr; |
|
struct resource *res; |
|
u32 id = dev->id; |
|
int irq; |
|
int i; |
|
|
|
dev_dbg(&dev->dev, "ace_probe(%p)\n", dev); |
|
|
|
/* device id and bus width */ |
|
if (of_property_read_u32(dev->dev.of_node, "port-number", &id)) |
|
id = 0; |
|
if (of_find_property(dev->dev.of_node, "8-bit", NULL)) |
|
bus_width = ACE_BUS_WIDTH_8; |
|
|
|
res = platform_get_resource(dev, IORESOURCE_MEM, 0); |
|
if (!res) |
|
return -EINVAL; |
|
|
|
physaddr = res->start; |
|
if (!physaddr) |
|
return -ENODEV; |
|
|
|
irq = platform_get_irq_optional(dev, 0); |
|
|
|
/* Call the bus-independent setup code */ |
|
return ace_alloc(&dev->dev, id, physaddr, irq, bus_width); |
|
} |
|
|
|
/* |
|
* Platform bus remove() method |
|
*/ |
|
static int ace_remove(struct platform_device *dev) |
|
{ |
|
ace_free(&dev->dev); |
|
return 0; |
|
} |
|
|
|
#if defined(CONFIG_OF) |
|
/* Match table for of_platform binding */ |
|
static const struct of_device_id ace_of_match[] = { |
|
{ .compatible = "xlnx,opb-sysace-1.00.b", }, |
|
{ .compatible = "xlnx,opb-sysace-1.00.c", }, |
|
{ .compatible = "xlnx,xps-sysace-1.00.a", }, |
|
{ .compatible = "xlnx,sysace", }, |
|
{}, |
|
}; |
|
MODULE_DEVICE_TABLE(of, ace_of_match); |
|
#else /* CONFIG_OF */ |
|
#define ace_of_match NULL |
|
#endif /* CONFIG_OF */ |
|
|
|
static struct platform_driver ace_platform_driver = { |
|
.probe = ace_probe, |
|
.remove = ace_remove, |
|
.driver = { |
|
.name = "xsysace", |
|
.of_match_table = ace_of_match, |
|
}, |
|
}; |
|
|
|
/* --------------------------------------------------------------------- |
|
* Module init/exit routines |
|
*/ |
|
static int __init ace_init(void) |
|
{ |
|
int rc; |
|
|
|
ace_major = register_blkdev(ace_major, "xsysace"); |
|
if (ace_major <= 0) { |
|
rc = -ENOMEM; |
|
goto err_blk; |
|
} |
|
|
|
rc = platform_driver_register(&ace_platform_driver); |
|
if (rc) |
|
goto err_plat; |
|
|
|
pr_info("Xilinx SystemACE device driver, major=%i\n", ace_major); |
|
return 0; |
|
|
|
err_plat: |
|
unregister_blkdev(ace_major, "xsysace"); |
|
err_blk: |
|
printk(KERN_ERR "xsysace: registration failed; err=%i\n", rc); |
|
return rc; |
|
} |
|
module_init(ace_init); |
|
|
|
static void __exit ace_exit(void) |
|
{ |
|
pr_debug("Unregistering Xilinx SystemACE driver\n"); |
|
platform_driver_unregister(&ace_platform_driver); |
|
unregister_blkdev(ace_major, "xsysace"); |
|
} |
|
module_exit(ace_exit);
|
|
|