| /* | 
 |  *  ricoh_mmc.c - Dummy driver to disable the Rioch MMC controller. | 
 |  * | 
 |  *  Copyright (C) 2007 Philip Langdale, All Rights Reserved. | 
 |  * | 
 |  * 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 of the License, or (at | 
 |  * your option) any later version. | 
 |  */ | 
 |  | 
 | /* | 
 |  * This is a conceptually ridiculous driver, but it is required by the way | 
 |  * the Ricoh multi-function chips (R5CXXX) work. These chips implement | 
 |  * the four main memory card controllers (SD, MMC, MS, xD) and one or both | 
 |  * of cardbus or firewire. It happens that they implement SD and MMC | 
 |  * support as separate controllers (and PCI functions). The linux SDHCI | 
 |  * driver supports MMC cards but the chip detects MMC cards in hardware | 
 |  * and directs them to the MMC controller - so the SDHCI driver never sees | 
 |  * them. To get around this, we must disable the useless MMC controller. | 
 |  * At that point, the SDHCI controller will start seeing them. As a bonus, | 
 |  * a detection event occurs immediately, even if the MMC card is already | 
 |  * in the reader. | 
 |  * | 
 |  * It seems to be the case that the relevant PCI registers to deactivate the | 
 |  * MMC controller live on PCI function 0, which might be the cardbus controller | 
 |  * or the firewire controller, depending on the particular chip in question. As | 
 |  * such, it makes what this driver has to do unavoidably ugly. Such is life. | 
 |  */ | 
 |  | 
 | #include <linux/pci.h> | 
 |  | 
 | #define DRIVER_NAME "ricoh-mmc" | 
 |  | 
 | static const struct pci_device_id pci_ids[] __devinitdata = { | 
 | 	{ | 
 | 		.vendor		= PCI_VENDOR_ID_RICOH, | 
 | 		.device		= PCI_DEVICE_ID_RICOH_R5C843, | 
 | 		.subvendor	= PCI_ANY_ID, | 
 | 		.subdevice	= PCI_ANY_ID, | 
 | 	}, | 
 | 	{ /* end: all zeroes */ }, | 
 | }; | 
 |  | 
 | MODULE_DEVICE_TABLE(pci, pci_ids); | 
 |  | 
 | static int ricoh_mmc_disable(struct pci_dev *fw_dev) | 
 | { | 
 | 	u8 write_enable; | 
 | 	u8 write_target; | 
 | 	u8 disable; | 
 |  | 
 | 	if (fw_dev->device == PCI_DEVICE_ID_RICOH_RL5C476) { | 
 | 		/* via RL5C476 */ | 
 |  | 
 | 		pci_read_config_byte(fw_dev, 0xB7, &disable); | 
 | 		if (disable & 0x02) { | 
 | 			printk(KERN_INFO DRIVER_NAME | 
 | 				": Controller already disabled. " \ | 
 | 				"Nothing to do.\n"); | 
 | 			return -ENODEV; | 
 | 		} | 
 |  | 
 | 		pci_read_config_byte(fw_dev, 0x8E, &write_enable); | 
 | 		pci_write_config_byte(fw_dev, 0x8E, 0xAA); | 
 | 		pci_read_config_byte(fw_dev, 0x8D, &write_target); | 
 | 		pci_write_config_byte(fw_dev, 0x8D, 0xB7); | 
 | 		pci_write_config_byte(fw_dev, 0xB7, disable | 0x02); | 
 | 		pci_write_config_byte(fw_dev, 0x8E, write_enable); | 
 | 		pci_write_config_byte(fw_dev, 0x8D, write_target); | 
 | 	} else { | 
 | 		/* via R5C832 */ | 
 |  | 
 | 		pci_read_config_byte(fw_dev, 0xCB, &disable); | 
 | 		if (disable & 0x02) { | 
 | 			printk(KERN_INFO DRIVER_NAME | 
 | 			       ": Controller already disabled. " \ | 
 | 				"Nothing to do.\n"); | 
 | 			return -ENODEV; | 
 | 		} | 
 |  | 
 | 		pci_read_config_byte(fw_dev, 0xCA, &write_enable); | 
 | 		pci_write_config_byte(fw_dev, 0xCA, 0x57); | 
 | 		pci_write_config_byte(fw_dev, 0xCB, disable | 0x02); | 
 | 		pci_write_config_byte(fw_dev, 0xCA, write_enable); | 
 | 	} | 
 |  | 
 | 	printk(KERN_INFO DRIVER_NAME | 
 | 	       ": Controller is now disabled.\n"); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int ricoh_mmc_enable(struct pci_dev *fw_dev) | 
 | { | 
 | 	u8 write_enable; | 
 | 	u8 write_target; | 
 | 	u8 disable; | 
 |  | 
 | 	if (fw_dev->device == PCI_DEVICE_ID_RICOH_RL5C476) { | 
 | 		/* via RL5C476 */ | 
 |  | 
 | 		pci_read_config_byte(fw_dev, 0x8E, &write_enable); | 
 | 		pci_write_config_byte(fw_dev, 0x8E, 0xAA); | 
 | 		pci_read_config_byte(fw_dev, 0x8D, &write_target); | 
 | 		pci_write_config_byte(fw_dev, 0x8D, 0xB7); | 
 | 		pci_read_config_byte(fw_dev, 0xB7, &disable); | 
 | 		pci_write_config_byte(fw_dev, 0xB7, disable & ~0x02); | 
 | 		pci_write_config_byte(fw_dev, 0x8E, write_enable); | 
 | 		pci_write_config_byte(fw_dev, 0x8D, write_target); | 
 | 	} else { | 
 | 		/* via R5C832 */ | 
 |  | 
 | 		pci_read_config_byte(fw_dev, 0xCA, &write_enable); | 
 | 		pci_read_config_byte(fw_dev, 0xCB, &disable); | 
 | 		pci_write_config_byte(fw_dev, 0xCA, 0x57); | 
 | 		pci_write_config_byte(fw_dev, 0xCB, disable & ~0x02); | 
 | 		pci_write_config_byte(fw_dev, 0xCA, write_enable); | 
 | 	} | 
 |  | 
 | 	printk(KERN_INFO DRIVER_NAME | 
 | 	       ": Controller is now re-enabled.\n"); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int __devinit ricoh_mmc_probe(struct pci_dev *pdev, | 
 | 				     const struct pci_device_id *ent) | 
 | { | 
 | 	u8 rev; | 
 | 	u8 ctrlfound = 0; | 
 |  | 
 | 	struct pci_dev *fw_dev = NULL; | 
 |  | 
 | 	BUG_ON(pdev == NULL); | 
 | 	BUG_ON(ent == NULL); | 
 |  | 
 | 	pci_read_config_byte(pdev, PCI_CLASS_REVISION, &rev); | 
 |  | 
 | 	printk(KERN_INFO DRIVER_NAME | 
 | 		": Ricoh MMC controller found at %s [%04x:%04x] (rev %x)\n", | 
 | 		pci_name(pdev), (int)pdev->vendor, (int)pdev->device, | 
 | 		(int)rev); | 
 |  | 
 | 	while ((fw_dev = | 
 | 		pci_get_device(PCI_VENDOR_ID_RICOH, | 
 | 			PCI_DEVICE_ID_RICOH_RL5C476, fw_dev))) { | 
 | 		if (PCI_SLOT(pdev->devfn) == PCI_SLOT(fw_dev->devfn) && | 
 | 		    PCI_FUNC(fw_dev->devfn) == 0 && | 
 | 		    pdev->bus == fw_dev->bus) { | 
 | 			if (ricoh_mmc_disable(fw_dev) != 0) | 
 | 				return -ENODEV; | 
 |  | 
 | 			pci_set_drvdata(pdev, fw_dev); | 
 |  | 
 | 			++ctrlfound; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	fw_dev = NULL; | 
 |  | 
 | 	while (!ctrlfound && | 
 | 	    (fw_dev = pci_get_device(PCI_VENDOR_ID_RICOH, | 
 | 					PCI_DEVICE_ID_RICOH_R5C832, fw_dev))) { | 
 | 		if (PCI_SLOT(pdev->devfn) == PCI_SLOT(fw_dev->devfn) && | 
 | 		    PCI_FUNC(fw_dev->devfn) == 0 && | 
 | 		    pdev->bus == fw_dev->bus) { | 
 | 			if (ricoh_mmc_disable(fw_dev) != 0) | 
 | 				return -ENODEV; | 
 |  | 
 | 			pci_set_drvdata(pdev, fw_dev); | 
 |  | 
 | 			++ctrlfound; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (!ctrlfound) { | 
 | 		printk(KERN_WARNING DRIVER_NAME | 
 | 		       ": Main Ricoh function not found. Cannot disable controller.\n"); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void __devexit ricoh_mmc_remove(struct pci_dev *pdev) | 
 | { | 
 | 	struct pci_dev *fw_dev = NULL; | 
 |  | 
 | 	fw_dev = pci_get_drvdata(pdev); | 
 | 	BUG_ON(fw_dev == NULL); | 
 |  | 
 | 	ricoh_mmc_enable(fw_dev); | 
 |  | 
 | 	pci_set_drvdata(pdev, NULL); | 
 | } | 
 |  | 
 | static int ricoh_mmc_suspend_late(struct pci_dev *pdev, pm_message_t state) | 
 | { | 
 | 	struct pci_dev *fw_dev = NULL; | 
 |  | 
 | 	fw_dev = pci_get_drvdata(pdev); | 
 | 	BUG_ON(fw_dev == NULL); | 
 |  | 
 | 	printk(KERN_INFO DRIVER_NAME ": Suspending.\n"); | 
 |  | 
 | 	ricoh_mmc_enable(fw_dev); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int ricoh_mmc_resume_early(struct pci_dev *pdev) | 
 | { | 
 | 	struct pci_dev *fw_dev = NULL; | 
 |  | 
 | 	fw_dev = pci_get_drvdata(pdev); | 
 | 	BUG_ON(fw_dev == NULL); | 
 |  | 
 | 	printk(KERN_INFO DRIVER_NAME ": Resuming.\n"); | 
 |  | 
 | 	ricoh_mmc_disable(fw_dev); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct pci_driver ricoh_mmc_driver = { | 
 | 	.name = 	DRIVER_NAME, | 
 | 	.id_table =	pci_ids, | 
 | 	.probe = 	ricoh_mmc_probe, | 
 | 	.remove =	__devexit_p(ricoh_mmc_remove), | 
 | 	.suspend_late =	ricoh_mmc_suspend_late, | 
 | 	.resume_early =	ricoh_mmc_resume_early, | 
 | }; | 
 |  | 
 | /*****************************************************************************\ | 
 |  *                                                                           * | 
 |  * Driver init/exit                                                          * | 
 |  *                                                                           * | 
 | \*****************************************************************************/ | 
 |  | 
 | static int __init ricoh_mmc_drv_init(void) | 
 | { | 
 | 	printk(KERN_INFO DRIVER_NAME | 
 | 		": Ricoh MMC Controller disabling driver\n"); | 
 | 	printk(KERN_INFO DRIVER_NAME ": Copyright(c) Philip Langdale\n"); | 
 |  | 
 | 	return pci_register_driver(&ricoh_mmc_driver); | 
 | } | 
 |  | 
 | static void __exit ricoh_mmc_drv_exit(void) | 
 | { | 
 | 	pci_unregister_driver(&ricoh_mmc_driver); | 
 | } | 
 |  | 
 | module_init(ricoh_mmc_drv_init); | 
 | module_exit(ricoh_mmc_drv_exit); | 
 |  | 
 | MODULE_AUTHOR("Philip Langdale <philipl@alumni.utexas.net>"); | 
 | MODULE_DESCRIPTION("Ricoh MMC Controller disabling driver"); | 
 | MODULE_LICENSE("GPL"); | 
 |  |