#include <KernelExport.h>
#include <stdlib.h>
#include <string.h>
#include <ata_adapter.h>
#include <tracing.h>
#define TRACE(x...) do { dprintf("si-3112: " x); ktrace_printf("si-3112: " x); } while (0)
#define FLOW(x...)
#define DRIVER_PRETTY_NAME "Silicon Image SATA"
#define CONTROLLER_NAME DRIVER_PRETTY_NAME
#define CONTROLLER_MODULE_NAME "busses/ata/silicon_image_3112/driver_v1"
#define CHANNEL_MODULE_NAME "busses/ata/silicon_image_3112/channel/v1"
enum asic_type {
ASIC_SI3112 = 0,
ASIC_SI3114 = 1,
};
static const struct {
uint16 channel_count;
uint32 mmio_bar_size;
const char *asic_name;
} kASICData[2] = {
{ 2, 0x200, "si3112" },
{ 4, 0x400, "si3114" },
};
static const struct {
uint16 cmd;
uint16 ctl;
uint16 bmdma;
uint16 sien;
uint16 stat;
const char *name;
} kControllerChannelData[] = {
{ 0x80, 0x8A, 0x00, 0x148, 0xA0, "Primary Channel" },
{ 0xC0, 0xCA, 0x08, 0x1C8, 0xE0, "Secondary Channel" },
{ 0x280, 0x28A, 0x200, 0x348, 0x2A0, "Tertiary Channel" },
{ 0x2C0, 0x2CA, 0x208, 0x3C8, 0x2E0, "Quaternary Channel" },
};
#define SI_SYSCFG 0x48
#define SI_BMDMA2 0x200
#define SI_INT_STEERING 0x02
#define SI_MASK_2PORT ((1 << 22) | (1 << 23))
#define SI_MASK_4PORT ((1 << 22) | (1 << 23) | (1 << 24) | (1 << 25))
struct channel_data;
typedef struct controller_data {
pci_device_module_info *pci;
pci_device *device;
device_node *node;
struct channel_data *channel[4];
int channel_count;
uint32 asic_index;
uint32 int_num;
area_id mmio_area;
addr_t mmio_addr;
uint32 lost;
} controller_data;
typedef struct channel_data {
pci_device_module_info *pci;
device_node * node;
pci_device * device;
ata_channel ataChannel;
volatile uint8 * task_file;
volatile uint8 * control_block;
volatile uint8 * command_block;
volatile uint8 * dev_ctrl;
volatile uint8 * bm_status_reg;
volatile uint8 * bm_command_reg;
volatile uint32 * bm_prdt_address;
volatile uint32 * stat;
area_id prd_area;
prd_entry * prdt;
phys_addr_t prdt_phys;
uint32 dma_active;
uint32 lost;
} channel_data;
static ata_for_controller_interface* sATA;
static ata_adapter_interface* sATAAdapter;
static device_manager_info* sDeviceManager;
static status_t device_control_write(void *channel_cookie, uint8 val);
static int32 handle_interrupt(void *arg);
static float
controller_supports(device_node *parent)
{
const char *bus;
uint16 vendorID;
uint16 deviceID;
if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) != B_OK
|| strcmp(bus, "pci") != 0)
return B_ERROR;
if (sDeviceManager->get_attr_uint16(parent, B_DEVICE_VENDOR_ID, &vendorID, false) != B_OK
|| sDeviceManager->get_attr_uint16(parent, B_DEVICE_ID, &deviceID, false) != B_OK) {
return B_ERROR;
}
#define ID(v, d) (((v) << 16) | (d))
switch (ID(vendorID, deviceID)) {
case ID(0x1095, 0x0240):
case ID(0x1095, 0x3112):
case ID(0x1095, 0x3114):
case ID(0x1095, 0x3512):
case ID(0x1002, 0x436e):
case ID(0x1002, 0x4379):
case ID(0x1002, 0x437a):
break;
default:
return 0.0f;
}
TRACE("controller found! vendor 0x%04x, device 0x%04x\n", vendorID, deviceID);
return 0.8f;
}
static status_t
controller_probe(device_node *parent)
{
pci_device *device;
pci_device_module_info *pci;
uint32 mmioBase;
uint16 deviceID;
uint16 vendorID;
uint8 interruptNumber;
int asicIndex;
TRACE("controller_probe\n");
sDeviceManager->get_driver(parent, (driver_module_info **)&pci, (void **)&device);
deviceID = pci->read_pci_config(device, PCI_device_id, 2);
vendorID = pci->read_pci_config(device, PCI_vendor_id, 2);
interruptNumber = pci->read_pci_config(device, PCI_interrupt_line, 1);
mmioBase = pci->read_pci_config(device, PCI_base_registers + 20, 4)
& PCI_address_io_mask;
switch (ID(vendorID, deviceID)) {
case ID(0x1095, 0x0240):
case ID(0x1095, 0x3112):
case ID(0x1095, 0x3512):
case ID(0x1002, 0x436e):
case ID(0x1002, 0x4379):
case ID(0x1002, 0x437a):
asicIndex = ASIC_SI3112;
break;
case ID(0x1095, 0x3114):
asicIndex = ASIC_SI3114;
break;
default:
TRACE("unsupported asic\n");
return B_ERROR;
}
if (!mmioBase) {
TRACE("mmio not configured\n");
return B_ERROR;
}
if (interruptNumber == 0 || interruptNumber == 0xff) {
TRACE("irq not configured\n");
return B_ERROR;
}
{
io_resource resources[2] = {
{ B_IO_MEMORY, mmioBase, kASICData[asicIndex].mmio_bar_size },
{}
};
device_attr attrs[] = {
{ ATA_CONTROLLER_MAX_DEVICES_ITEM, B_UINT8_TYPE,
{ .ui8 = kASICData[asicIndex].channel_count }},
{ ATA_CONTROLLER_CAN_DMA_ITEM, B_UINT8_TYPE, { .ui8 = true }},
{ ATA_CONTROLLER_CONTROLLER_NAME_ITEM, B_STRING_TYPE,
{ .string = CONTROLLER_NAME }},
{ B_DMA_ALIGNMENT, B_UINT32_TYPE, { .ui32 = 1}},
{ B_DMA_BOUNDARY, B_UINT32_TYPE, { .ui32 = 0xffff }},
{ B_DMA_MAX_SEGMENT_BLOCKS, B_UINT32_TYPE, { .ui32 = 0x10000 }},
{ B_DMA_MAX_SEGMENT_COUNT, B_UINT32_TYPE,
{ .ui32 = ATA_ADAPTER_MAX_SG_COUNT }},
{ B_DMA_HIGH_ADDRESS, B_UINT64_TYPE, { .ui64 = 0x100000000LL }},
{ "silicon_image_3112/asic_index", B_UINT32_TYPE,
{ .ui32 = asicIndex }},
{ "silicon_image_3112/mmio_base", B_UINT32_TYPE,
{ .ui32 = mmioBase }},
{ "silicon_image_3112/int_num", B_UINT32_TYPE,
{ .ui32 = interruptNumber }},
{ NULL }
};
TRACE("publishing controller\n");
return sDeviceManager->register_node(parent, CONTROLLER_MODULE_NAME, attrs,
resources, NULL);
}
}
static status_t
controller_init(device_node *node, void **_controllerCookie)
{
controller_data *controller;
device_node *parent;
pci_device_module_info *pci;
pci_device *device;
uint32 asicIndex;
uint32 mmioBase;
addr_t mmioAddr;
area_id mmioArea;
uint32 interruptNumber;
status_t res;
uint32 temp;
int i;
TRACE("controller_init\n");
if (sDeviceManager->get_attr_uint32(node, "silicon_image_3112/asic_index", &asicIndex, false) != B_OK)
return B_ERROR;
if (sDeviceManager->get_attr_uint32(node, "silicon_image_3112/mmio_base", &mmioBase, false) != B_OK)
return B_ERROR;
if (sDeviceManager->get_attr_uint32(node, "silicon_image_3112/int_num", &interruptNumber, false) != B_OK)
return B_ERROR;
controller = malloc(sizeof(controller_data));
if (!controller)
return B_NO_MEMORY;
FLOW("controller %p\n", controller);
mmioArea = map_physical_memory("Silicon Image SATA regs", mmioBase,
kASICData[asicIndex].mmio_bar_size, B_ANY_KERNEL_ADDRESS,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void **)&mmioAddr);
if (mmioArea < B_OK) {
TRACE("controller_init: mapping memory failed\n");
free(controller);
return B_ERROR;
}
parent = sDeviceManager->get_parent_node(node);
sDeviceManager->get_driver(parent, (driver_module_info **)&pci, (void **)&device);
sDeviceManager->put_node(parent);
TRACE("asic index %" B_PRId32 "\n", asicIndex);
TRACE("asic name %s\n", kASICData[asicIndex].asic_name);
TRACE("int num %" B_PRId32 "\n", interruptNumber);
TRACE("mmio addr %" B_PRIxADDR"\n", mmioAddr);
controller->pci = pci;
controller->device = device;
controller->node = node;
controller->asic_index = asicIndex;
controller->int_num = interruptNumber;
controller->mmio_area = mmioArea;
controller->mmio_addr = mmioAddr;
controller->lost = 0;
controller->channel_count = kASICData[asicIndex].channel_count;
for (i = 0; i < kASICData[asicIndex].channel_count; i++)
controller->channel[i] = 0;
if (asicIndex == ASIC_SI3114) {
temp = *(volatile uint32 *)(mmioAddr + SI_BMDMA2);
*(volatile uint32 *)(mmioAddr + SI_BMDMA2) = temp | SI_INT_STEERING;
*(volatile uint32 *)(mmioAddr + SI_BMDMA2);
}
for (i = 0; i < kASICData[asicIndex].channel_count; i++)
*(volatile uint32 *)(mmioAddr + kControllerChannelData[i].sien) = 0;
*(volatile uint32 *)(mmioAddr + kControllerChannelData[0].sien);
res = install_io_interrupt_handler(interruptNumber, handle_interrupt,
controller, 0);
if (res < B_OK) {
TRACE("controller_init: installing interrupt handler failed\n");
delete_area(mmioArea);
free(controller);
return res;
}
temp = *(volatile uint32 *)(mmioAddr + SI_SYSCFG);
temp &= (asicIndex == ASIC_SI3114) ? (~SI_MASK_4PORT) : (~SI_MASK_2PORT);
*(volatile uint32 *)(mmioAddr + SI_SYSCFG) = temp;
*(volatile uint32 *)(mmioAddr + SI_SYSCFG);
*_controllerCookie = controller;
TRACE("controller_init success\n");
return B_OK;
}
static void
controller_uninit(void *controllerCookie)
{
controller_data *controller = controllerCookie;
uint32 temp;
TRACE("controller_uninit enter\n");
temp = *(volatile uint32 *)(controller->mmio_addr + SI_SYSCFG);
temp |= (controller->asic_index == ASIC_SI3114) ? SI_MASK_4PORT : SI_MASK_2PORT;
*(volatile uint32 *)(controller->mmio_addr + SI_SYSCFG) = temp;
*(volatile uint32 *)(controller->mmio_addr + SI_SYSCFG);
remove_io_interrupt_handler(controller->int_num, handle_interrupt,
controller);
delete_area(controller->mmio_area);
free(controller);
TRACE("controller_uninit leave\n");
}
static status_t
controller_register_channels(void *cookie)
{
controller_data *controller = cookie;
int index;
for (index = 0; index < kASICData[controller->asic_index].channel_count;
index++) {
device_attr attrs[] = {
{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = DRIVER_PRETTY_NAME }},
{ B_DEVICE_FIXED_CHILD, B_STRING_TYPE, { .string = ATA_FOR_CONTROLLER_MODULE_NAME }},
{ ATA_CONTROLLER_CAN_DMA_ITEM, B_UINT8_TYPE, { .ui8 = true }},
{ "silicon_image_3112/chan_index", B_UINT32_TYPE, { .ui32 = index }},
{ NULL }
};
TRACE("publishing %s\n", kControllerChannelData[index].name);
sDeviceManager->register_node(controller->node, CHANNEL_MODULE_NAME, attrs,
NULL, NULL);
}
return B_OK;
}
static void
controller_removed(void *controllerCookie)
{
controller_data *controller = controllerCookie;
controller->lost = 1;
}
static status_t
channel_init(device_node *node, void **_channelCookie)
{
controller_data *controller;
device_node *parent;
channel_data *channel;
physical_entry entry;
size_t prdtSize;
uint32 channelIndex;
TRACE("channel_init enter\n");
channel = malloc(sizeof(channel_data));
if (!channel)
return B_NO_MEMORY;
if (sDeviceManager->get_attr_uint32(node, "silicon_image_3112/chan_index", &channelIndex, false) != B_OK)
goto err;
#if 0
if (1 ){
uint8 bus, device, function;
uint16 vendorID, deviceID;
sDeviceManager->get_attr_uint8(node, PCI_DEVICE_BUS_ITEM, &bus, true);
sDeviceManager->get_attr_uint8(node, PCI_DEVICE_DEVICE_ITEM, &device, true);
sDeviceManager->get_attr_uint8(node, PCI_DEVICE_FUNCTION_ITEM, &function, true);
sDeviceManager->get_attr_uint16(node, PCI_DEVICE_VENDOR_ID_ITEM, &vendorID, true);
sDeviceManager->get_attr_uint16(node, PCI_DEVICE_DEVICE_ID_ITEM, &deviceID, true);
TRACE("bus %3d, device %2d, function %2d: vendor %04x, device %04x\n",
bus, device, function, vendorID, deviceID);
}
#endif
TRACE("channel_index %" B_PRId32 "\n", channelIndex);
TRACE("channel name: %s\n", kControllerChannelData[channelIndex].name);
TRACE("channel %p\n", channel);
parent = sDeviceManager->get_parent_node(node);
sDeviceManager->get_driver(parent, NULL, (void **)&controller);
sDeviceManager->put_node(parent);
TRACE("controller %p\n", controller);
TRACE("mmio_addr %p\n", (void *)controller->mmio_addr);
prdtSize = (ATA_ADAPTER_MAX_SG_COUNT * sizeof(prd_entry) + (B_PAGE_SIZE - 1)) & ~(B_PAGE_SIZE - 1);
channel->prd_area = create_area("prd", (void **)&channel->prdt,
B_ANY_KERNEL_ADDRESS, prdtSize, B_32_BIT_CONTIGUOUS,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
if (channel->prd_area < B_OK) {
TRACE("creating prd_area failed\n");
goto err;
}
get_memory_map(channel->prdt, prdtSize, &entry, 1);
channel->prdt_phys = entry.address;
channel->pci = controller->pci;
channel->device = controller->device;
channel->node = node;
channel->ataChannel = NULL;
channel->task_file = (volatile uint8 *)(controller->mmio_addr + kControllerChannelData[channelIndex].cmd + 1);
channel->control_block = (volatile uint8 *)(controller->mmio_addr + kControllerChannelData[channelIndex].ctl);
channel->command_block = (volatile uint8 *)(controller->mmio_addr + kControllerChannelData[channelIndex].cmd);
channel->dev_ctrl = (volatile uint8 *)(controller->mmio_addr + kControllerChannelData[channelIndex].ctl);
channel->bm_prdt_address = (volatile uint32 *)(controller->mmio_addr + kControllerChannelData[channelIndex].bmdma + ATA_BM_PRDT_ADDRESS);
channel->bm_status_reg = (volatile uint8 *)(controller->mmio_addr + kControllerChannelData[channelIndex].bmdma + ATA_BM_STATUS_REG);
channel->bm_command_reg = (volatile uint8 *)(controller->mmio_addr + kControllerChannelData[channelIndex].bmdma + ATA_BM_COMMAND_REG);
channel->stat = (volatile uint32 *)(controller->mmio_addr + kControllerChannelData[channelIndex].stat);
channel->lost = 0;
channel->dma_active = 0;
controller->channel[channelIndex] = channel;
device_control_write(channel, ATA_DEVICE_CONTROL_BIT3
| ATA_DEVICE_CONTROL_DISABLE_INTS);
*_channelCookie = channel;
TRACE("channel_init leave\n");
return B_OK;
err:
free(channel);
return B_ERROR;
}
static void
channel_uninit(void *channelCookie)
{
channel_data *channel = channelCookie;
TRACE("channel_uninit enter\n");
device_control_write(channel, ATA_DEVICE_CONTROL_BIT3
| ATA_DEVICE_CONTROL_DISABLE_INTS);
delete_area(channel->prd_area);
free(channel);
TRACE("channel_uninit leave\n");
}
static void
channel_removed(void *channelCookie)
{
channel_data *channel = channelCookie;
channel->lost = 1;
}
static void
set_channel(void *channelCookie, ata_channel ataChannel)
{
channel_data *channel = channelCookie;
channel->ataChannel = ataChannel;
}
static status_t
task_file_write(void *channelCookie, ata_task_file *tf, ata_reg_mask mask)
{
channel_data *channel = channelCookie;
int i;
FLOW("task_file_write\n");
if (channel->lost)
return B_ERROR;
for (i = 0; i < 7; i++) {
if( ((1 << (i+7)) & mask) != 0 ) {
FLOW("%x->HI(%x)\n", tf->raw.r[i + 7], i );
channel->task_file[i] = tf->raw.r[i + 7];
}
if (((1 << i) & mask) != 0) {
FLOW("%x->LO(%x)\n", tf->raw.r[i], i );
channel->task_file[i] = tf->raw.r[i];
}
}
*channel->dev_ctrl;
return B_OK;
}
static status_t
task_file_read(void *channelCookie, ata_task_file *tf, ata_reg_mask mask)
{
channel_data *channel = channelCookie;
int i;
FLOW("task_file_read\n");
if (channel->lost)
return B_ERROR;
for (i = 0; i < 7; i++) {
if (((1 << i) & mask) != 0) {
tf->raw.r[i] = channel->task_file[i];
FLOW("%x: %x\n", i, (int)tf->raw.r[i] );
}
}
return B_OK;
}
static uint8
altstatus_read(void *channelCookie)
{
channel_data *channel = channelCookie;
FLOW("altstatus_read\n");
if (channel->lost)
return 0x01;
return *channel->dev_ctrl;
}
static status_t
device_control_write(void *channelCookie, uint8 val)
{
channel_data *channel = channelCookie;
FLOW("device_control_write 0x%x\n", val);
if (channel->lost)
return B_ERROR;
*channel->dev_ctrl = val;
*channel->dev_ctrl;
return B_OK;
}
static status_t
pio_write(void *channelCookie, uint16 *data, int count, bool force_16bit)
{
channel_data *channel = channelCookie;
if (channel->lost)
return B_ERROR;
FLOW("pio_write force_16bit = %d, (count & 1) = %d\n", force_16bit, (count & 1));
if ((count & 1) != 0 || force_16bit) {
volatile uint16 * base = (volatile uint16 *)channel->command_block;
for ( ; count > 0; --count)
*base = *(data++);
} else {
volatile uint32 * base = (volatile uint32 *)channel->command_block;
uint32 *cur_data = (uint32 *)data;
for ( ; count > 0; count -= 2 )
*base = *(cur_data++);
}
*channel->dev_ctrl;
return B_OK;
}
static status_t
pio_read(void *channelCookie, uint16 *data, int count, bool force_16bit)
{
channel_data *channel = channelCookie;
if (channel->lost)
return B_ERROR;
FLOW("pio_read force_16bit = %d, (count & 1) = %d\n", force_16bit, (count & 1));
if ((count & 1) != 0 || force_16bit) {
volatile uint16 * base = (volatile uint16 *)channel->command_block;
for ( ; count > 0; --count)
*(data++) = *base;
} else {
volatile uint32 * base = (volatile uint32 *)channel->command_block;
uint32 *cur_data = (uint32 *)data;
for ( ; count > 0; count -= 2 )
*(cur_data++) = *base;
}
return B_OK;
}
static status_t
dma_prepare(void *channelCookie, const physical_entry *sg_list,
size_t sg_list_count, bool write)
{
channel_data *channel = channelCookie;
pci_device_module_info *pci = channel->pci;
pci_device *device = channel->device;
prd_entry *prd = channel->prdt;
uint8 command;
uint8 status;
uint32 temp;
int i;
FLOW("dma_prepare enter\n");
for (i = sg_list_count - 1, prd = channel->prdt; i >= 0;
--i, ++prd, ++sg_list ) {
prd->address = B_HOST_TO_LENDIAN_INT32((uint32)pci->ram_address(device,
sg_list->address));
prd->count = B_HOST_TO_LENDIAN_INT16((uint16)sg_list->size);
prd->EOT = i == 0;
FLOW("%" B_PRIx32", %" B_PRId16", %" B_PRId8"\n", prd->address, prd->count, prd->EOT);
}
temp = (*channel->bm_prdt_address) & 3;
temp |= B_HOST_TO_LENDIAN_INT32((uint32)pci->ram_address(device,
channel->prdt_phys)) & ~3;
*channel->bm_prdt_address = temp;
*channel->dev_ctrl;
status = *channel->bm_status_reg | ATA_BM_STATUS_INTERRUPT
| ATA_BM_STATUS_ERROR;
*channel->bm_status_reg = status;
*channel->dev_ctrl;
command = *channel->bm_command_reg;
if (write)
command &= ~ATA_BM_COMMAND_READ_FROM_DEVICE;
else
command |= ATA_BM_COMMAND_READ_FROM_DEVICE;
*channel->bm_command_reg = command;
*channel->dev_ctrl;
FLOW("dma_prepare leave\n");
return B_OK;
}
static status_t
dma_start(void *channelCookie)
{
channel_data *channel = channelCookie;
uint8 command;
FLOW("dma_start enter\n");
command = *channel->bm_command_reg | ATA_BM_COMMAND_START_STOP;
channel->dma_active = true;
*channel->bm_command_reg = command;
*channel->dev_ctrl;
FLOW("dma_start leave\n");
return B_OK;
}
static status_t
dma_finish(void *channelCookie)
{
channel_data *channel = channelCookie;
uint8 command;
uint8 status;
FLOW("dma_finish enter\n");
status = *channel->bm_status_reg;
command = *channel->bm_command_reg;
*channel->bm_command_reg = command & ~ATA_BM_COMMAND_START_STOP;
channel->dma_active = false;
*channel->bm_status_reg = status | ATA_BM_STATUS_ERROR;
*channel->dev_ctrl;
if ((status & ATA_BM_STATUS_ACTIVE) != 0) {
TRACE("dma_finish: buffer too large\n");
return B_DEV_DATA_OVERRUN;
}
if ((status & ATA_BM_STATUS_ERROR) != 0) {
FLOW("dma_finish: failed\n");
return B_ERROR;
}
FLOW("dma_finish leave\n");
return B_OK;
}
static int32
handle_interrupt(void *arg)
{
controller_data *controller = arg;
uint8 statusATA, statusBM;
int32 result;
int i;
FLOW("handle_interrupt\n");
result = B_UNHANDLED_INTERRUPT;
for (i = 0; i < controller->channel_count; i++) {
channel_data *channel = controller->channel[i];
if (!channel || channel->lost)
continue;
if (!channel->dma_active) {
*(channel->command_block + 7);
continue;
}
statusBM = *channel->bm_status_reg;
if (statusBM & ATA_BM_STATUS_INTERRUPT) {
statusATA = *(channel->command_block + 7);
*channel->bm_status_reg
= (statusBM & 0xf8) | ATA_BM_STATUS_INTERRUPT;
sATA->interrupt_handler(channel->ataChannel, statusATA);
result = B_INVOKE_SCHEDULER;
}
}
return result;
}
module_dependency module_dependencies[] = {
{ ATA_FOR_CONTROLLER_MODULE_NAME, (module_info **)&sATA },
{ B_DEVICE_MANAGER_MODULE_NAME, (module_info **)&sDeviceManager },
{ ATA_ADAPTER_MODULE_NAME, (module_info **)&sATAAdapter },
{}
};
static ata_controller_interface sChannelInterface = {
{
{
CHANNEL_MODULE_NAME,
0,
NULL
},
.supports_device = NULL,
.register_device = NULL,
.init_driver = &channel_init,
.uninit_driver = &channel_uninit,
.register_child_devices = NULL,
.device_removed = &channel_removed,
},
.set_channel = &set_channel,
.write_command_block_regs = &task_file_write,
.read_command_block_regs = &task_file_read,
.get_altstatus = &altstatus_read,
.write_device_control = &device_control_write,
.write_pio = &pio_write,
.read_pio = &pio_read,
.prepare_dma = &dma_prepare,
.start_dma = &dma_start,
.finish_dma = &dma_finish,
};
static driver_module_info sControllerInterface = {
{
CONTROLLER_MODULE_NAME,
0,
NULL
},
.supports_device = &controller_supports,
.register_device = &controller_probe,
.init_driver = &controller_init,
.uninit_driver = &controller_uninit,
.register_child_devices = &controller_register_channels,
.device_removed = &controller_removed,
};
module_info *modules[] = {
(module_info *)&sControllerInterface,
(module_info *)&sChannelInterface,
NULL
};