Gentoo Wiki


This article is part of the Hardware series.
Laptops TV Tuner Cards Wireless Servers Storage Other Hardware Motherboards Related



This page might be useful if you have a Texas Instruments PCI1620 Cardbus Controller on your computer with a memory card reader adapter (in fact I think it is a PC Card) and you would like to make it work. It will take some work since firmware has to be loaded.

This is the relevant output of lspci:

0000:02:04.0 CardBus bridge: Texas Instruments PCI1620 PC Card Controller (rev 01)
0000:02:04.1 CardBus bridge: Texas Instruments PCI1620 PC Card Controller (rev 01)
0000:02:04.2 System peripheral: Texas Instruments PCI1620 Firmware Loading Function (rev 01)

Get the firmware

This controller's RAM needs to be loaded with its firmware before the memory card reader can be used. The firmware can be found in the windows driver distribution in the file named tiumfw.bin. I presume that you have the windows drivers for the device. I can't provide you the firmware file since I think it is the propriety of whoever wrote it and copyrighted stuff can't be submitted. Nevertheless I think that if you have the windows driver, you can use the firmware file. To get the firmware, search the support site for your device and look for updated drivers for your CardBus controller. For example, the HP TC1100 Tablet uses this chip and provides a driver here (note link may change without warning). The driver can be "installed" using wine and the firmware blob found in .wine/drive_c/windows/tiinst/tiumfw.bin. Your location may be different; use find .wine/ -name \*.bin to find it.

From what I know (In my case) the firmware is xor-ed byte-wise with a key and we need the reverse this operation since the correct binary must be loaded into the controller's RAM. I can't tell you the key used to xor the binary but an easy way to recover the original binary is the following. The key used a byte so it could be any from 0 to 255. A simple program can be used to do the xor with all the possible keys and check the output for any strings in it. A sample program to do this (by Krzysztof Kosiński):

Code: fwdecode.cpp
 /* PCI1620 firmware decoder
    (c) 2008-01-20 by Krzysztof Kosiński
    The copyright owner grants permission to distribute, modify,
    and use this program without restrictions */
 #include <iostream>
 #include <fstream>
 #include <iomanip>
 #include <cstring>
 #include <sys/stat.h>
 using namespace std;
 int main(int argc, char *argv[])
    if(argc != 4) return 1;
    char *mem, *work;
    size_t fsize;
    const size_t nsize = strlen(argv[3]);
    // get firmware size
    struct stat f;
    if(stat(argv[1], &f) != 0) return 1;
    fsize = f.st_size;
    // read the entire file into memory and create temporary storage
    mem = new char[fsize];
    work = new char[fsize];
    {  ifstream fw(argv[1], ios::in | ios::binary);, fsize); } // we don't need the ifstream object any more
    // test all keys and look for the given string
    for(int i=0; i<255; ++i)
       char key = (char) i;
       for(size_t i=0; i<fsize; ++i)
          work[i] = mem[i] ^ key;
       // look for the string given as third argument - ugly brute force match
       for (size_t i=0; i<(fsize - nsize); ++i)
          if(!memcmp(work + i, argv[3], nsize))
             // write key to cout and save file
             cout << "Found key: 0x" << hex << ((int) key & 0xff) << endl;
             ofstream fwdec(argv[2], ios::out | ios::binary);
             fwdec.write(work, fsize);
             return 0;
    // no success
    cout << "Key not found." << endl;
    return 1;

This program takes three arguments (no checking is performed): first - the filename of encoded firmware, second - the filename under which to save the decoded firmware, and third - the search string to use to test whether the right key was found. Examples of strings contained in the firmware are: "memorystick", "POWER", "multimediacard", "PENDING", "STATUS", "ATTRIB", "REGISTER", "RANGE", "iNITIALIZE", "uNKNOWN". When the program finds the right key, it will output the decoded file and print the found key to standard output.


$ fwdecode tiumfw.bin pci1620.bin memorystick

Another way to get the firmware loaded is boot first to MSWindows, use the device, and then reboot into Linux.

Configure the kernel

For basic PC Card support, see this guide. The socket is yenta-compatible.

Also enable firmware loading support:

Linux Kernel Configuration: firmware loading support
 Device Drivers --->
   Generic Driver Options --->
       <M> Userspace firmware loading support

Enable PATA support since the controller will emulate an inserted memory card as a pata disk.

Linux Kernel Configuration: PATA support
 Device Drivers --->
   Serial ATA (prod) and Parallel ATA (experimental drivers) --->
     <*> ATA device support

Build the needed modules

Since I have nowhere to put the files I will just paste the code here sorry for that. I used two modules: one to load the firmware called tiumfwl (you can get the original file but config.h must be replaced with autoconf.h in order to compile it) and the other to handle the emulated ata device that appears upon a memory card insertion called pata_pci1620 which is modified version of pata_pcmcia to work with this device. The makefile for the modules:

File: Makefile
obj-m += tiumfwl.o
obj-m += pata_pci1620.o

KERNELDIR=/usr/src/linux-`uname -r`

        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
        rm -rf Module.symvers
        $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

Firmware loader:

File: tiumfwl.c
 * tiumfwl.c: Texas Instruments UltraMedia firmware loader
 * Copyright (C) 2006 Jochen Eisinger <>
 * Firmware is:
 *      Copyright (C) 2002 Texas Instruments

#include <linux/autoconf.h>

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/compiler.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/firmware.h>

#define DRV_MODULE_NAME         "tiumfwl"
#define PFX DRV_MODULE_NAME     ": "
#define DRV_MODULE_VERSION      "1.10"
#define DRV_MODULE_RELDATE      "June 7, 2006"

static char version[] __devinitdata =
        "Texas Instruments UltraMedia firmware loader\n";

static int key = 0;

module_param(key, int, 0);

MODULE_AUTHOR("Jochen Eisinger <>");
MODULE_DESCRIPTION("Texas Instruments UltraMedia firmware loader");

#define REG_DATA_ADDR   0x00
#define REG_LOADER_CTRL 0x04

#define FLG_ADDR_RST    0x08
#define FLG_DONE        0x04
#define FLG_PROGRAM     0x02
#define FLG_ERR         0x01

static struct pci_device_id tiumfwl_pci_tbl[] = {
        { PCI_VENDOR_ID_TI, 0x8204,             /* PCI7510 */
          PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0UL },
        { PCI_VENDOR_ID_TI, 0x8201,             /* PCI1620 */
          PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0UL },
        { 0, }

MODULE_DEVICE_TABLE(pci, tiumfwl_pci_tbl);

static struct {
        unsigned short vendor;
        unsigned short device;
        int encrypted;
        char * fw;
} tiumfwl_fw_map[] = {
        { PCI_VENDOR_ID_TI, 0x8204, 0, "pci7510.bin" },
        { PCI_VENDOR_ID_TI, 0x8201, 0, "pci1620.bin" },
        { 0, }

static int tiumfwl_load_firmware(struct pci_dev *pdev)
        int i;
        const struct firmware *fw_entry = NULL;
        u8 *fw_ptr;
        long fw_len;
        int io_base;
        unsigned int state;

        for (i=0; tiumfwl_fw_map[i].vendor; i++)
                if ((tiumfwl_fw_map[i].vendor == pdev->vendor) &&
                    (tiumfwl_fw_map[i].device == pdev->device))

        if (request_firmware(&fw_entry, tiumfwl_fw_map[i].fw, &pdev->dev)) {
                printk(KERN_ERR PFX "%s: request_firmware() failed\n",
                return -1;

        fw_ptr = (u8 *)fw_entry->data;
        fw_len = fw_entry->size;

        if (tiumfwl_fw_map[i].encrypted)
                for (i=0; i<fw_entry->size; i++)
                        fw_ptr[i] ^= (u8)key;

        io_base = pci_resource_start(pdev, 0);

        if (((fw_entry->size >= 0x5000) && (fw_entry->size < 0x8000)) ||
            (fw_entry->size > 0xF000)) {
                printk(KERN_ERR PFX
                       "%s: invalid firmware size\n", pci_name(pdev));
                goto err_out;

        /* start firmware upload sequence */
        outl_p(0, io_base + REG_LOADER_CTRL);
        outl_p(FLG_ADDR_RST | FLG_PROGRAM, io_base + REG_LOADER_CTRL);

        /* clear ERR flag */
        inl_p(io_base + REG_LOADER_CTRL);

        /* set start address */
        outl_p(0, io_base + REG_DATA_ADDR);
        state = inl_p(io_base + REG_LOADER_CTRL);

        if (state & FLG_ERR) {
                for (i=0; i<5; i++) {
                        outl_p(0, io_base + REG_LOADER_CTRL);
                        outl_p(FLG_ADDR_RST | FLG_PROGRAM,
                               io_base + REG_LOADER_CTRL);
                        outl_p(0, io_base + REG_DATA_ADDR);
                        state = inl_p(io_base + REG_LOADER_CTRL);
                        if (!(state & FLG_ERR))

                if (state & FLG_ERR) {
                        printk(KERN_ERR PFX
                               "%s: failed to initiate firmware upload\n",
                        goto err_out;

        while (fw_len > 0) {
                if ((fw_ptr - (u8 *)fw_entry->data) == 0x5000) {
                        fw_len -= 0x3000;
                        fw_ptr += 0x3000;

                        outl_p(0, io_base + REG_LOADER_CTRL);
                        outl_p(FLG_ADDR_RST | FLG_PROGRAM,
                               io_base + REG_LOADER_CTRL);

                        /* clear ERR flag */
                        inl_p(io_base + REG_LOADER_CTRL);

                        /* set start address */
                        outl_p(0x8000, io_base + REG_DATA_ADDR);
                        state = inl_p(io_base + REG_LOADER_CTRL);

                        if (state & FLG_ERR) {
                                for (i=0; i<5; i++) {
                                        outl_p(0, io_base + REG_LOADER_CTRL);
                                        outl_p(FLG_ADDR_RST | FLG_PROGRAM,
                                               io_base + REG_LOADER_CTRL);
                                        outl_p(0, io_base + REG_DATA_ADDR);
                                        state = inl_p(io_base +
                                        if (!(state & FLG_ERR))

                                if (state & FLG_ERR) {
                                        printk(KERN_ERR PFX
                                               "%s: failed to skip to next "
                                               "firmware location\n",
                                        goto err_out;

                /* upload a byte */
                outb_p(*(fw_ptr), io_base + REG_DATA_ADDR);
                state = inl_p(io_base + REG_LOADER_CTRL);
                if (state & FLG_ERR) {
                        printk(KERN_ERR PFX
                               "%s: failed to write data at "
                               "address 0x%04x\n",
                               (fw_ptr - (u8 *)fw_entry->data));
                        goto err_out;

        /* set done bit */
        outl_p(FLG_DONE, io_base + REG_LOADER_CTRL);


        printk(KERN_INFO PFX "%s: firmware successfully loaded\n",

        return 0;


        return -1;

static int __devinit tiumfwl_probe(struct pci_dev *pdev,
                                   const struct pci_device_id *ent)
        static int tiumfwl_version_printed = 0;
        int err;

        if (tiumfwl_version_printed++ == 0)
                printk(KERN_INFO "%s", version);

        err = pci_enable_device(pdev);
        if (err) {
                printk(KERN_ERR PFX "Cannot enable PCI device, "
                return err;

        err = pci_request_regions(pdev, DRV_MODULE_NAME);
        if (err) {
                printk(KERN_ERR PFX "Cannot obtain PCI resources, "
                return err;

        err = tiumfwl_load_firmware(pdev);
        if (err) {
                printk(KERN_ERR PFX "Failed to load firmware, aborting.\n");
                return err;

        return 0;

static void __devexit tiumfwl_release(struct pci_dev *pdev)

static struct pci_driver tiumfwl_driver = {
        .name           = DRV_MODULE_NAME,
        .id_table       = tiumfwl_pci_tbl,
        .probe          = tiumfwl_probe,
        .remove         = tiumfwl_release,

static int __init tiumfwl_init(void)
	 /* in kernel <= 2.6.21 use return pci_module_init(&tiumfwl_driver); instead */
        return pci_register_driver(&tiumfwl_driver);

static void __exit tiumfwl_cleanup(void)


Pata driver (works only with kernel >=2.6.22) is basically the pata_pcmcia.c module available in the vanilla kernel with the data transfer function customized to perform the transfer on 8 bits instead of the default 16 bits (the only problem that prevents pata_pcmcia to work with this device). 2008/02/12: hey, that's now included on 2.6.25 kernel! See . Make sure you're using the pata_pcmcia kernel module, not the ice_cs one.

File: pata_pci1620.c
 *   pata_pci1620.c - Driver for eulated PATA controller.
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  GNU General Public License for more details.
 *  You should have received a copy of the GNU General Public License
 *  along with this program; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *   Heavily based upon pata_pcmcia.c, ide-cs.c
 *   The initial developer of the original code is David A. Hinds
 *   <>.  Portions created by David A. Hinds
 *   are Copyright (C) 1999 David A. Hinds.  All Rights Reserved.

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/delay.h>
#include <scsi/scsi_host.h>
#include <linux/ata.h>
#include <linux/libata.h>

#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/ds.h>
#include <pcmcia/cisreg.h>
#include <pcmcia/ciscode.h>

#define DRV_NAME "pata_pci1620"
#define DRV_VERSION "0.1.1"

void pci1620_ata_pio_data_xfer(struct ata_device *adev, 
			       unsigned char *buf, 
			       unsigned int buflen, int write_data)
	unsigned int length = buflen;
	struct ata_port *ap = adev->ap;
	if (write_data)
		while (length--)
			iowrite8(*buf++, ap->ioaddr.data_addr);
		while (length--)
			*buf++ = ioread8(ap->ioaddr.data_addr);

 *	Private data structure to glue stuff together

struct ata_pcmcia_info {
	struct pcmcia_device *pdev;
	int		ndev;
	dev_node_t	node;

 *	pcmcia_set_mode	-	PCMCIA specific mode setup
 *	@ap: Port
 *	@r_failed_dev: Return pointer for failed device
 *	Perform the tuning and setup of the devices and timings, which
 *	for PCMCIA is the same as any other controller. We wrap it however
 *	as we need to spot hardware with incorrect or missing master/slave
 *	decode, which alas is embarrassingly common in the PC world

static int pcmcia_set_mode(struct ata_port *ap, struct ata_device **r_failed_dev)
	struct ata_device *master = &ap->device[0];
	struct ata_device *slave = &ap->device[1];

	if (!ata_dev_enabled(master) || !ata_dev_enabled(slave))
		return ata_do_set_mode(ap, r_failed_dev);

	if (memcmp(master->id + ATA_ID_FW_REV,  slave->id + ATA_ID_FW_REV,
		/* Suspicious match, but could be two cards from
		   the same vendor - check serial */
		if (memcmp(master->id + ATA_ID_SERNO, slave->id + ATA_ID_SERNO,
			   ATA_ID_SERNO_LEN) == 0 && master->id[ATA_ID_SERNO] >> 8) {
			ata_dev_printk(slave, KERN_WARNING, "is a ghost device, ignoring.\n");
	return ata_do_set_mode(ap, r_failed_dev);

static struct scsi_host_template pcmcia_sht = {
	.module			= THIS_MODULE,
	.name			= DRV_NAME,
	.ioctl			= ata_scsi_ioctl,
	.queuecommand		= ata_scsi_queuecmd,
	.can_queue		= ATA_DEF_QUEUE,
	.this_id		= ATA_SHT_THIS_ID,
	.sg_tablesize		= LIBATA_MAX_PRD,
	.cmd_per_lun		= ATA_SHT_CMD_PER_LUN,
	.emulated		= ATA_SHT_EMULATED,
	.use_clustering		= ATA_SHT_USE_CLUSTERING,
	.proc_name		= DRV_NAME,
	.dma_boundary		= ATA_DMA_BOUNDARY,
	.slave_configure	= ata_scsi_slave_config,
	.slave_destroy		= ata_scsi_slave_destroy,
	.bios_param		= ata_std_bios_param,

static struct ata_port_operations pcmcia_port_ops = {
	.set_mode	= pcmcia_set_mode,
	.port_disable	= ata_port_disable,
	.tf_load	= ata_tf_load,
	.tf_read	= ata_tf_read,
	.check_status 	= ata_check_status,
	.exec_command	= ata_exec_command,
	.dev_select 	= ata_std_dev_select,

	.freeze		= ata_bmdma_freeze,
	.thaw		= ata_bmdma_thaw,
	.error_handler	= ata_bmdma_error_handler,
	.post_internal_cmd = ata_bmdma_post_internal_cmd,
	.cable_detect	= ata_cable_40wire,

	.qc_prep 	= ata_qc_prep,
	.qc_issue	= ata_qc_issue_prot,

	.data_xfer	= pci1620_ata_pio_data_xfer,

	.irq_clear	= ata_bmdma_irq_clear,
	.irq_on		= ata_irq_on,
	.irq_ack	= ata_irq_ack,

	.port_start	= ata_sff_port_start,

#define CS_CHECK(fn, ret) \
do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0)

 *	pcmcia_init_one		-	attach a PCMCIA interface
 *	@pdev: pcmcia device
 *	Register a PCMCIA IDE interface. Such interfaces are PIO 0 and
 *	shared IRQ.

static int pcmcia_init_one(struct pcmcia_device *pdev)
	struct ata_host *host;
	struct ata_port *ap;
	struct ata_pcmcia_info *info;
	tuple_t tuple;
	struct {
		unsigned short buf[128];
		cisparse_t parse;
		config_info_t conf;
		cistpl_cftable_entry_t dflt;
	} *stk = NULL;
	cistpl_cftable_entry_t *cfg;
	int pass, last_ret = 0, last_fn = 0, is_kme = 0, ret = -ENOMEM;
	unsigned long io_base, ctl_base;
	void __iomem *io_addr, *ctl_addr;

	info = kzalloc(sizeof(*info), GFP_KERNEL);
	if (info == NULL)
		return -ENOMEM;

	/* Glue stuff together. FIXME: We may be able to get rid of info with care */
	info->pdev = pdev;
	pdev->priv = info;

	/* Set up attributes in order to probe card and get resources */
	pdev->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO;
	pdev->io.Attributes2 = IO_DATA_PATH_WIDTH_8;
	pdev->io.IOAddrLines = 3;
	pdev->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING;
	pdev->irq.IRQInfo1 = IRQ_LEVEL_ID;
	pdev->conf.Attributes = CONF_ENABLE_IRQ;
	pdev->conf.IntType = INT_MEMORY_AND_IO;

	/* Allocate resoure probing structures */

	stk = kzalloc(sizeof(*stk), GFP_KERNEL);
	if (!stk)
		goto out1;

	cfg = &stk->parse.cftable_entry;

	/* Tuples we are walking */
	tuple.TupleData = (cisdata_t *)&stk->buf;
	tuple.TupleOffset = 0;
	tuple.TupleDataMax = 255;
	tuple.Attributes = 0;

	/* See if we have a manufacturer identifier. Use it to set is_kme for
	   vendor quirks */
	is_kme = ((pdev->manf_id == MANFID_KME) &&
		  ((pdev->card_id == PRODID_KME_KXLC005_A) ||
		   (pdev->card_id == PRODID_KME_KXLC005_B)));

	/* Not sure if this is right... look up the current Vcc */
	CS_CHECK(GetConfigurationInfo, pcmcia_get_configuration_info(pdev, &stk->conf));
/*	link->conf.Vcc = stk->conf.Vcc; */

	pass = io_base = ctl_base = 0;
	tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;
	tuple.Attributes = 0;
	CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(pdev, &tuple));

	/* Now munch the resources looking for a suitable set */
	while (1) {
		if (pcmcia_get_tuple_data(pdev, &tuple) != 0)
			goto next_entry;
		if (pcmcia_parse_tuple(pdev, &tuple, &stk->parse) != 0)
			goto next_entry;
		/* Check for matching Vcc, unless we're desperate */
		if (!pass) {
			if (cfg->vcc.present & (1 << CISTPL_POWER_VNOM)) {
				if (stk->conf.Vcc != cfg->vcc.param[CISTPL_POWER_VNOM] / 10000)
					goto next_entry;
			} else if (stk->dflt.vcc.present & (1 << CISTPL_POWER_VNOM)) {
				if (stk->conf.Vcc != stk->dflt.vcc.param[CISTPL_POWER_VNOM] / 10000)
					goto next_entry;

		if (cfg->vpp1.present & (1 << CISTPL_POWER_VNOM))
			pdev->conf.Vpp = cfg->vpp1.param[CISTPL_POWER_VNOM] / 10000;
		else if (stk->dflt.vpp1.present & (1 << CISTPL_POWER_VNOM))
			pdev->conf.Vpp = stk->dflt.vpp1.param[CISTPL_POWER_VNOM] / 10000;

		if ((cfg->io.nwin > 0) || (stk-> > 0)) {
			cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &stk->;
			pdev->conf.ConfigIndex = cfg->index;
			pdev->io.BasePort1 = io->win[0].base;
			pdev->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK;
			if (!(io->flags & CISTPL_IO_16BIT))
				pdev->io.Attributes1 = IO_DATA_PATH_WIDTH_8;
			if (io->nwin == 2) {
				pdev->io.NumPorts1 = 8;
				pdev->io.BasePort2 = io->win[1].base;
				pdev->io.NumPorts2 = (is_kme) ? 2 : 1;
				if (pcmcia_request_io(pdev, &pdev->io) != 0)
					goto next_entry;
				io_base = pdev->io.BasePort1;
				ctl_base = pdev->io.BasePort2;
			} else if ((io->nwin == 1) && (io->win[0].len >= 16)) {
				pdev->io.NumPorts1 = io->win[0].len;
				pdev->io.NumPorts2 = 0;
				if (pcmcia_request_io(pdev, &pdev->io) != 0)
					goto next_entry;
				io_base = pdev->io.BasePort1;
				ctl_base = pdev->io.BasePort1 + 0x0e;
			} else goto next_entry;
			/* If we've got this far, we're done */
		if (cfg->flags & CISTPL_CFTABLE_DEFAULT)
			memcpy(&stk->dflt, cfg, sizeof(stk->dflt));
		if (pass) {
			CS_CHECK(GetNextTuple, pcmcia_get_next_tuple(pdev, &tuple));
		} else if (pcmcia_get_next_tuple(pdev, &tuple) != 0) {
			CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(pdev, &tuple));
			memset(&stk->dflt, 0, sizeof(stk->dflt));

	CS_CHECK(RequestIRQ, pcmcia_request_irq(pdev, &pdev->irq));
	CS_CHECK(RequestConfiguration, pcmcia_request_configuration(pdev, &pdev->conf));

	/* iomap */
	ret = -ENOMEM;
	io_addr = devm_ioport_map(&pdev->dev, io_base, 8);
	ctl_addr = devm_ioport_map(&pdev->dev, ctl_base, 1);
	if (!io_addr || !ctl_addr)
		goto failed;

	/* Success. Disable the IRQ nIEN line, do quirks */
	iowrite8(0x02, ctl_addr);
	if (is_kme)
		iowrite8(0x81, ctl_addr + 0x01);

	/* FIXME: Could be more ports at base + 0x10 but we only deal with
	   one right now */
	if (pdev->io.NumPorts1 >= 0x20)
		printk(KERN_WARNING DRV_NAME ": second channel not yet supported.\n");

 	 *	Having done the PCMCIA plumbing the ATA side is relatively
 	 *	sane.
	ret = -ENOMEM;
	host = ata_host_alloc(&pdev->dev, 1);
	if (!host)
		goto failed;
	ap = host->ports[0];

	ap->ops = &pcmcia_port_ops;
	ap->pio_mask = 1;		/* ISA so PIO 0 cycles */
	ap->flags |= ATA_FLAG_SLAVE_POSS;
	ap->ioaddr.cmd_addr = io_addr;
	ap->ioaddr.altstatus_addr = ctl_addr;
	ap->ioaddr.ctl_addr = ctl_addr;

	/* activate */
	ret = ata_host_activate(host, pdev->irq.AssignedIRQ, ata_interrupt,
				IRQF_SHARED, &pcmcia_sht);
	if (ret)
		goto failed;

	info->ndev = 1;
	return 0;

	cs_error(pdev, last_fn, last_ret);
	info->ndev = 0;
	return ret;

 *	pcmcia_remove_one	-	unplug an pcmcia interface
 *	@pdev: pcmcia device
 *	A PCMCIA ATA device has been unplugged. Perform the needed
 *	cleanup. Also called on module unload for any active devices.

static void pcmcia_remove_one(struct pcmcia_device *pdev)
	struct ata_pcmcia_info *info = pdev->priv;
	struct device *dev = &pdev->dev;

	if (info != NULL) {
		/* If we have attached the device to the ATA layer, detach it */
		if (info->ndev) {
			struct ata_host *host = dev_get_drvdata(dev);
		info->ndev = 0;
		pdev->priv = NULL;

static struct pcmcia_device_id pcmcia_devices[] = {
	PCMCIA_DEVICE_MANF_CARD(0x0097, 0x1620),

MODULE_DEVICE_TABLE(pcmcia, pcmcia_devices);

static struct pcmcia_driver pcmcia_driver = {
	.owner		= THIS_MODULE,
	.drv = {
		.name		= DRV_NAME,
	.id_table	= pcmcia_devices,
	.probe		= pcmcia_init_one,
	.remove		= pcmcia_remove_one,

static int __init pcmcia_init(void)
	return pcmcia_register_driver(&pcmcia_driver);

static void __exit pcmcia_exit(void)

MODULE_AUTHOR("Cristian Onet");
MODULE_DESCRIPTION("low-level driver for ATA device emulated by PCI1620 chip");


Check for the files and build them by running:


Load the modules

Load the firmware loader module. It has as a dependency firmware_class module so make sure that it is already loaded. Be sure that the firmware file is in /lib/firmware.

#modprobe firmware_class
#insmod tiumfwl.ko 
#dmesg | grep tiumfwl
tiumfwl: firmware successfully loaded

This means that the firmware was loaded correctly.

The firmware is loaded in RAM, so if the computer has been hibernated, you have to rmmod the tiumfwl module and modprobe it so the fw is loaded again. I hope somebody can make the tiumfwl hibernate-friendly to upload the fw automatically after resuming.

Load the pata driver.

 #insmod pata_pci1620.ko

Reading a memory card

After completing the above steps the machine is ready to read a memory card (I have only tested it with an SD card). Just insert the card and a device should appear in dev (/dev/sd*). In order to use it mount it first. Any modern distro should automount it and launch a file browser.

Performance issues

I have tested the module with a 1 GB SD card and the transfer rate was at about 500 kB/s. I don't know at what speeds should the card reader operate and since I don't have Win I can't test the provided driver. But I assume that the transfer speed could be improved since the driver is detected as PIO2 capable but it operates at PIO0 (see the ATA-1 specification) because of a missing device setup phase. But for now I think this is acceptable. Updated: yes, it is faster in mswindows.


Retrieved from ""

Last modified: Thu, 04 Sep 2008 05:33:00 +0000 Hits: 6,513