![Tejun Heo](/assets/img/avatar_default.png)
percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
804 lines
22 KiB
C
804 lines
22 KiB
C
/*
|
|
* cb710/mmc.c
|
|
*
|
|
* Copyright by Michał Mirosław, 2008-2009
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/delay.h>
|
|
#include "cb710-mmc.h"
|
|
|
|
static const u8 cb710_clock_divider_log2[8] = {
|
|
/* 1, 2, 4, 8, 16, 32, 128, 512 */
|
|
0, 1, 2, 3, 4, 5, 7, 9
|
|
};
|
|
#define CB710_MAX_DIVIDER_IDX \
|
|
(ARRAY_SIZE(cb710_clock_divider_log2) - 1)
|
|
|
|
static const u8 cb710_src_freq_mhz[16] = {
|
|
33, 10, 20, 25, 30, 35, 40, 45,
|
|
50, 55, 60, 65, 70, 75, 80, 85
|
|
};
|
|
|
|
static void cb710_mmc_set_clock(struct mmc_host *mmc, int hz)
|
|
{
|
|
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
|
|
struct pci_dev *pdev = cb710_slot_to_chip(slot)->pdev;
|
|
u32 src_freq_idx;
|
|
u32 divider_idx;
|
|
int src_hz;
|
|
|
|
/* this is magic, unverifiable for me, unless I get
|
|
* MMC card with cables connected to bus signals */
|
|
pci_read_config_dword(pdev, 0x48, &src_freq_idx);
|
|
src_freq_idx = (src_freq_idx >> 16) & 0xF;
|
|
src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
|
|
|
|
for (divider_idx = 0; divider_idx < CB710_MAX_DIVIDER_IDX; ++divider_idx) {
|
|
if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
|
|
break;
|
|
}
|
|
|
|
if (src_freq_idx)
|
|
divider_idx |= 0x8;
|
|
|
|
cb710_pci_update_config_reg(pdev, 0x40, ~0xF0000000, divider_idx << 28);
|
|
|
|
dev_dbg(cb710_slot_dev(slot),
|
|
"clock set to %d Hz, wanted %d Hz; flag = %d\n",
|
|
src_hz >> cb710_clock_divider_log2[divider_idx & 7],
|
|
hz, (divider_idx & 8) != 0);
|
|
}
|
|
|
|
static void __cb710_mmc_enable_irq(struct cb710_slot *slot,
|
|
unsigned short enable, unsigned short mask)
|
|
{
|
|
/* clear global IE
|
|
* - it gets set later if any interrupt sources are enabled */
|
|
mask |= CB710_MMC_IE_IRQ_ENABLE;
|
|
|
|
/* look like interrupt is fired whenever
|
|
* WORD[0x0C] & WORD[0x10] != 0;
|
|
* -> bit 15 port 0x0C seems to be global interrupt enable
|
|
*/
|
|
|
|
enable = (cb710_read_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT)
|
|
& ~mask) | enable;
|
|
|
|
if (enable)
|
|
enable |= CB710_MMC_IE_IRQ_ENABLE;
|
|
|
|
cb710_write_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT, enable);
|
|
}
|
|
|
|
static void cb710_mmc_enable_irq(struct cb710_slot *slot,
|
|
unsigned short enable, unsigned short mask)
|
|
{
|
|
struct cb710_mmc_reader *reader = mmc_priv(cb710_slot_to_mmc(slot));
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&reader->irq_lock, flags);
|
|
/* this is the only thing irq_lock protects */
|
|
__cb710_mmc_enable_irq(slot, enable, mask);
|
|
spin_unlock_irqrestore(&reader->irq_lock, flags);
|
|
}
|
|
|
|
static void cb710_mmc_reset_events(struct cb710_slot *slot)
|
|
{
|
|
cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, 0xFF);
|
|
cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, 0xFF);
|
|
cb710_write_port_8(slot, CB710_MMC_STATUS2_PORT, 0xFF);
|
|
}
|
|
|
|
static int cb710_mmc_is_card_inserted(struct cb710_slot *slot)
|
|
{
|
|
return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
|
|
& CB710_MMC_S3_CARD_DETECTED;
|
|
}
|
|
|
|
static void cb710_mmc_enable_4bit_data(struct cb710_slot *slot, int enable)
|
|
{
|
|
dev_dbg(cb710_slot_dev(slot), "configuring %d-data-line%s mode\n",
|
|
enable ? 4 : 1, enable ? "s" : "");
|
|
if (enable)
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
|
|
CB710_MMC_C1_4BIT_DATA_BUS, 0);
|
|
else
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
|
|
0, CB710_MMC_C1_4BIT_DATA_BUS);
|
|
}
|
|
|
|
static int cb710_check_event(struct cb710_slot *slot, u8 what)
|
|
{
|
|
u16 status;
|
|
|
|
status = cb710_read_port_16(slot, CB710_MMC_STATUS_PORT);
|
|
|
|
if (status & CB710_MMC_S0_FIFO_UNDERFLOW) {
|
|
/* it is just a guess, so log it */
|
|
dev_dbg(cb710_slot_dev(slot),
|
|
"CHECK : ignoring bit 6 in status %04X\n", status);
|
|
cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
|
|
CB710_MMC_S0_FIFO_UNDERFLOW);
|
|
status &= ~CB710_MMC_S0_FIFO_UNDERFLOW;
|
|
}
|
|
|
|
if (status & CB710_MMC_STATUS_ERROR_EVENTS) {
|
|
dev_dbg(cb710_slot_dev(slot),
|
|
"CHECK : returning EIO on status %04X\n", status);
|
|
cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, status & 0xFF);
|
|
cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
|
|
CB710_MMC_S1_RESET);
|
|
return -EIO;
|
|
}
|
|
|
|
/* 'what' is a bit in MMC_STATUS1 */
|
|
if ((status >> 8) & what) {
|
|
cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, what);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cb710_wait_for_event(struct cb710_slot *slot, u8 what)
|
|
{
|
|
int err = 0;
|
|
unsigned limit = 2000000; /* FIXME: real timeout */
|
|
|
|
#ifdef CONFIG_CB710_DEBUG
|
|
u32 e, x;
|
|
e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
|
|
#endif
|
|
|
|
while (!(err = cb710_check_event(slot, what))) {
|
|
if (!--limit) {
|
|
cb710_dump_regs(cb710_slot_to_chip(slot),
|
|
CB710_DUMP_REGS_MMC);
|
|
err = -ETIMEDOUT;
|
|
break;
|
|
}
|
|
udelay(1);
|
|
}
|
|
|
|
#ifdef CONFIG_CB710_DEBUG
|
|
x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
|
|
|
|
limit = 2000000 - limit;
|
|
if (limit > 100)
|
|
dev_dbg(cb710_slot_dev(slot),
|
|
"WAIT10: waited %d loops, what %d, entry val %08X, exit val %08X\n",
|
|
limit, what, e, x);
|
|
#endif
|
|
return err < 0 ? err : 0;
|
|
}
|
|
|
|
|
|
static int cb710_wait_while_busy(struct cb710_slot *slot, uint8_t mask)
|
|
{
|
|
unsigned limit = 500000; /* FIXME: real timeout */
|
|
int err = 0;
|
|
|
|
#ifdef CONFIG_CB710_DEBUG
|
|
u32 e, x;
|
|
e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
|
|
#endif
|
|
|
|
while (cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & mask) {
|
|
if (!--limit) {
|
|
cb710_dump_regs(cb710_slot_to_chip(slot),
|
|
CB710_DUMP_REGS_MMC);
|
|
err = -ETIMEDOUT;
|
|
break;
|
|
}
|
|
udelay(1);
|
|
}
|
|
|
|
#ifdef CONFIG_CB710_DEBUG
|
|
x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
|
|
|
|
limit = 500000 - limit;
|
|
if (limit > 100)
|
|
dev_dbg(cb710_slot_dev(slot),
|
|
"WAIT12: waited %d loops, mask %02X, entry val %08X, exit val %08X\n",
|
|
limit, mask, e, x);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void cb710_mmc_set_transfer_size(struct cb710_slot *slot,
|
|
size_t count, size_t blocksize)
|
|
{
|
|
cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
|
|
cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT,
|
|
((count - 1) << 16)|(blocksize - 1));
|
|
|
|
dev_vdbg(cb710_slot_dev(slot), "set up for %zu block%s of %zu bytes\n",
|
|
count, count == 1 ? "" : "s", blocksize);
|
|
}
|
|
|
|
static void cb710_mmc_fifo_hack(struct cb710_slot *slot)
|
|
{
|
|
/* without this, received data is prepended with 8-bytes of zeroes */
|
|
u32 r1, r2;
|
|
int ok = 0;
|
|
|
|
r1 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
|
|
r2 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
|
|
if (cb710_read_port_8(slot, CB710_MMC_STATUS0_PORT)
|
|
& CB710_MMC_S0_FIFO_UNDERFLOW) {
|
|
cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
|
|
CB710_MMC_S0_FIFO_UNDERFLOW);
|
|
ok = 1;
|
|
}
|
|
|
|
dev_dbg(cb710_slot_dev(slot),
|
|
"FIFO-read-hack: expected STATUS0 bit was %s\n",
|
|
ok ? "set." : "NOT SET!");
|
|
dev_dbg(cb710_slot_dev(slot),
|
|
"FIFO-read-hack: dwords ignored: %08X %08X - %s\n",
|
|
r1, r2, (r1|r2) ? "BAD (NOT ZERO)!" : "ok");
|
|
}
|
|
|
|
static int cb710_mmc_receive_pio(struct cb710_slot *slot,
|
|
struct sg_mapping_iter *miter, size_t dw_count)
|
|
{
|
|
if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & CB710_MMC_S2_FIFO_READY)) {
|
|
int err = cb710_wait_for_event(slot,
|
|
CB710_MMC_S1_PIO_TRANSFER_DONE);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
cb710_sg_dwiter_write_from_io(miter,
|
|
slot->iobase + CB710_MMC_DATA_PORT, dw_count);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool cb710_is_transfer_size_supported(struct mmc_data *data)
|
|
{
|
|
return !(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8));
|
|
}
|
|
|
|
static int cb710_mmc_receive(struct cb710_slot *slot, struct mmc_data *data)
|
|
{
|
|
struct sg_mapping_iter miter;
|
|
size_t len, blocks = data->blocks;
|
|
int err = 0;
|
|
|
|
/* TODO: I don't know how/if the hardware handles non-16B-boundary blocks
|
|
* except single 8B block */
|
|
if (unlikely(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8)))
|
|
return -EINVAL;
|
|
|
|
sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_TO_SG);
|
|
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
|
|
15, CB710_MMC_C2_READ_PIO_SIZE_MASK);
|
|
|
|
cb710_mmc_fifo_hack(slot);
|
|
|
|
while (blocks-- > 0) {
|
|
len = data->blksz;
|
|
|
|
while (len >= 16) {
|
|
err = cb710_mmc_receive_pio(slot, &miter, 4);
|
|
if (err)
|
|
goto out;
|
|
len -= 16;
|
|
}
|
|
|
|
if (!len)
|
|
continue;
|
|
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
|
|
len - 1, CB710_MMC_C2_READ_PIO_SIZE_MASK);
|
|
|
|
len = (len >= 8) ? 4 : 2;
|
|
err = cb710_mmc_receive_pio(slot, &miter, len);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
out:
|
|
sg_miter_stop(&miter);
|
|
return err;
|
|
}
|
|
|
|
static int cb710_mmc_send(struct cb710_slot *slot, struct mmc_data *data)
|
|
{
|
|
struct sg_mapping_iter miter;
|
|
size_t len, blocks = data->blocks;
|
|
int err = 0;
|
|
|
|
/* TODO: I don't know how/if the hardware handles multiple
|
|
* non-16B-boundary blocks */
|
|
if (unlikely(data->blocks > 1 && data->blksz & 15))
|
|
return -EINVAL;
|
|
|
|
sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_FROM_SG);
|
|
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
|
|
0, CB710_MMC_C2_READ_PIO_SIZE_MASK);
|
|
|
|
while (blocks-- > 0) {
|
|
len = (data->blksz + 15) >> 4;
|
|
do {
|
|
if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
|
|
& CB710_MMC_S2_FIFO_EMPTY)) {
|
|
err = cb710_wait_for_event(slot,
|
|
CB710_MMC_S1_PIO_TRANSFER_DONE);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
cb710_sg_dwiter_read_to_io(&miter,
|
|
slot->iobase + CB710_MMC_DATA_PORT, 4);
|
|
} while (--len);
|
|
}
|
|
out:
|
|
sg_miter_stop(&miter);
|
|
return err;
|
|
}
|
|
|
|
static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
|
|
struct mmc_command *cmd)
|
|
{
|
|
unsigned int flags = cmd->flags;
|
|
u16 cb_flags = 0;
|
|
|
|
/* Windows driver returned 0 for commands for which no response
|
|
* is expected. It happened that there were only two such commands
|
|
* used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
|
|
* as well be a bug in that driver.
|
|
*
|
|
* Original driver set bit 14 for MMC/SD application
|
|
* commands. There's no difference 'on the wire' and
|
|
* it apparently works without it anyway.
|
|
*/
|
|
|
|
switch (flags & MMC_CMD_MASK) {
|
|
case MMC_CMD_AC: cb_flags = CB710_MMC_CMD_AC; break;
|
|
case MMC_CMD_ADTC: cb_flags = CB710_MMC_CMD_ADTC; break;
|
|
case MMC_CMD_BC: cb_flags = CB710_MMC_CMD_BC; break;
|
|
case MMC_CMD_BCR: cb_flags = CB710_MMC_CMD_BCR; break;
|
|
}
|
|
|
|
if (flags & MMC_RSP_BUSY)
|
|
cb_flags |= CB710_MMC_RSP_BUSY;
|
|
|
|
cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
|
|
|
|
if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
|
|
cb_flags |= CB710_MMC_DATA_READ;
|
|
|
|
if (flags & MMC_RSP_PRESENT) {
|
|
/* Windows driver set 01 at bits 4,3 except for
|
|
* MMC_SET_BLOCKLEN where it set 10. Maybe the
|
|
* hardware can do something special about this
|
|
* command? The original driver looks buggy/incomplete
|
|
* anyway so we ignore this for now.
|
|
*
|
|
* I assume that 00 here means no response is expected.
|
|
*/
|
|
cb_flags |= CB710_MMC_RSP_PRESENT;
|
|
|
|
if (flags & MMC_RSP_136)
|
|
cb_flags |= CB710_MMC_RSP_136;
|
|
if (!(flags & MMC_RSP_CRC))
|
|
cb_flags |= CB710_MMC_RSP_NO_CRC;
|
|
}
|
|
|
|
return cb_flags;
|
|
}
|
|
|
|
static void cb710_receive_response(struct cb710_slot *slot,
|
|
struct mmc_command *cmd)
|
|
{
|
|
unsigned rsp_opcode, wanted_opcode;
|
|
|
|
/* Looks like final byte with CRC is always stripped (same as SDHCI) */
|
|
if (cmd->flags & MMC_RSP_136) {
|
|
u32 resp[4];
|
|
|
|
resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE3_PORT);
|
|
resp[1] = cb710_read_port_32(slot, CB710_MMC_RESPONSE2_PORT);
|
|
resp[2] = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT);
|
|
resp[3] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
|
|
rsp_opcode = resp[0] >> 24;
|
|
|
|
cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
|
|
cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
|
|
cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
|
|
cmd->resp[3] = (resp[3] << 8);
|
|
} else {
|
|
rsp_opcode = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT) & 0x3F;
|
|
cmd->resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
|
|
}
|
|
|
|
wanted_opcode = (cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F;
|
|
if (rsp_opcode != wanted_opcode)
|
|
cmd->error = -EILSEQ;
|
|
}
|
|
|
|
static int cb710_mmc_transfer_data(struct cb710_slot *slot,
|
|
struct mmc_data *data)
|
|
{
|
|
int error, to;
|
|
|
|
if (data->flags & MMC_DATA_READ)
|
|
error = cb710_mmc_receive(slot, data);
|
|
else
|
|
error = cb710_mmc_send(slot, data);
|
|
|
|
to = cb710_wait_for_event(slot, CB710_MMC_S1_DATA_TRANSFER_DONE);
|
|
if (!error)
|
|
error = to;
|
|
|
|
if (!error)
|
|
data->bytes_xfered = data->blksz * data->blocks;
|
|
return error;
|
|
}
|
|
|
|
static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd)
|
|
{
|
|
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
|
|
struct cb710_mmc_reader *reader = mmc_priv(mmc);
|
|
struct mmc_data *data = cmd->data;
|
|
|
|
u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
|
|
dev_dbg(cb710_slot_dev(slot), "cmd request: 0x%04X\n", cb_cmd);
|
|
|
|
if (data) {
|
|
if (!cb710_is_transfer_size_supported(data)) {
|
|
data->error = -EINVAL;
|
|
return -1;
|
|
}
|
|
cb710_mmc_set_transfer_size(slot, data->blocks, data->blksz);
|
|
}
|
|
|
|
cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20|CB710_MMC_S2_BUSY_10);
|
|
cb710_write_port_16(slot, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
|
|
cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
|
|
cb710_write_port_32(slot, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
|
|
cb710_mmc_reset_events(slot);
|
|
cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x01, 0);
|
|
|
|
cmd->error = cb710_wait_for_event(slot, CB710_MMC_S1_COMMAND_SENT);
|
|
if (cmd->error)
|
|
return -1;
|
|
|
|
if (cmd->flags & MMC_RSP_PRESENT) {
|
|
cb710_receive_response(slot, cmd);
|
|
if (cmd->error)
|
|
return -1;
|
|
}
|
|
|
|
if (data)
|
|
data->error = cb710_mmc_transfer_data(slot, data);
|
|
return 0;
|
|
}
|
|
|
|
static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
{
|
|
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
|
|
struct cb710_mmc_reader *reader = mmc_priv(mmc);
|
|
|
|
WARN_ON(reader->mrq != NULL);
|
|
|
|
reader->mrq = mrq;
|
|
cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
|
|
|
|
if (cb710_mmc_is_card_inserted(slot)) {
|
|
if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
|
|
cb710_mmc_command(mmc, mrq->stop);
|
|
mdelay(1);
|
|
} else {
|
|
mrq->cmd->error = -ENOMEDIUM;
|
|
}
|
|
|
|
tasklet_schedule(&reader->finish_req_tasklet);
|
|
}
|
|
|
|
static int cb710_mmc_powerup(struct cb710_slot *slot)
|
|
{
|
|
#ifdef CONFIG_CB710_DEBUG
|
|
struct cb710_chip *chip = cb710_slot_to_chip(slot);
|
|
#endif
|
|
int err;
|
|
|
|
/* a lot of magic; see comment in cb710_mmc_set_clock() */
|
|
dev_dbg(cb710_slot_dev(slot), "bus powerup\n");
|
|
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
|
|
err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
|
|
if (unlikely(err))
|
|
return err;
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x80, 0);
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x80, 0);
|
|
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
|
|
mdelay(1);
|
|
dev_dbg(cb710_slot_dev(slot), "after delay 1\n");
|
|
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
|
|
err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
|
|
if (unlikely(err))
|
|
return err;
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x09, 0);
|
|
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
|
|
mdelay(1);
|
|
dev_dbg(cb710_slot_dev(slot), "after delay 2\n");
|
|
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
|
|
err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
|
|
if (unlikely(err))
|
|
return err;
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x08);
|
|
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
|
|
mdelay(2);
|
|
dev_dbg(cb710_slot_dev(slot), "after delay 3\n");
|
|
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x70, 0);
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, 0x80, 0);
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x03, 0);
|
|
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
|
|
err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
|
|
if (unlikely(err))
|
|
return err;
|
|
/* This port behaves weird: quick byte reads of 0x08,0x09 return
|
|
* 0xFF,0x00 after writing 0xFFFF to 0x08; it works correctly when
|
|
* read/written from userspace... What am I missing here?
|
|
* (it doesn't depend on write-to-read delay) */
|
|
cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0xFFFF);
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
|
|
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
|
|
dev_dbg(cb710_slot_dev(slot), "bus powerup finished\n");
|
|
|
|
return cb710_check_event(slot, 0);
|
|
}
|
|
|
|
static void cb710_mmc_powerdown(struct cb710_slot *slot)
|
|
{
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x81);
|
|
cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0, 0x80);
|
|
}
|
|
|
|
static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
{
|
|
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
|
|
struct cb710_mmc_reader *reader = mmc_priv(mmc);
|
|
int err;
|
|
|
|
cb710_mmc_set_clock(mmc, ios->clock);
|
|
|
|
if (!cb710_mmc_is_card_inserted(slot)) {
|
|
dev_dbg(cb710_slot_dev(slot),
|
|
"no card inserted - ignoring bus powerup request\n");
|
|
ios->power_mode = MMC_POWER_OFF;
|
|
}
|
|
|
|
if (ios->power_mode != reader->last_power_mode)
|
|
switch (ios->power_mode) {
|
|
case MMC_POWER_ON:
|
|
err = cb710_mmc_powerup(slot);
|
|
if (err) {
|
|
dev_warn(cb710_slot_dev(slot),
|
|
"powerup failed (%d)- retrying\n", err);
|
|
cb710_mmc_powerdown(slot);
|
|
udelay(1);
|
|
err = cb710_mmc_powerup(slot);
|
|
if (err)
|
|
dev_warn(cb710_slot_dev(slot),
|
|
"powerup retry failed (%d) - expect errors\n",
|
|
err);
|
|
}
|
|
reader->last_power_mode = MMC_POWER_ON;
|
|
break;
|
|
case MMC_POWER_OFF:
|
|
cb710_mmc_powerdown(slot);
|
|
reader->last_power_mode = MMC_POWER_OFF;
|
|
break;
|
|
case MMC_POWER_UP:
|
|
default:
|
|
/* ignore */;
|
|
}
|
|
|
|
cb710_mmc_enable_4bit_data(slot, ios->bus_width != MMC_BUS_WIDTH_1);
|
|
|
|
cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
|
|
}
|
|
|
|
static int cb710_mmc_get_ro(struct mmc_host *mmc)
|
|
{
|
|
struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
|
|
|
|
return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
|
|
& CB710_MMC_S3_WRITE_PROTECTED;
|
|
}
|
|
|
|
static int cb710_mmc_irq_handler(struct cb710_slot *slot)
|
|
{
|
|
struct mmc_host *mmc = cb710_slot_to_mmc(slot);
|
|
struct cb710_mmc_reader *reader = mmc_priv(mmc);
|
|
u32 status, config1, config2, irqen;
|
|
|
|
status = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
|
|
irqen = cb710_read_port_32(slot, CB710_MMC_IRQ_ENABLE_PORT);
|
|
config2 = cb710_read_port_32(slot, CB710_MMC_CONFIGB_PORT);
|
|
config1 = cb710_read_port_32(slot, CB710_MMC_CONFIG_PORT);
|
|
|
|
dev_dbg(cb710_slot_dev(slot), "interrupt; status: %08X, "
|
|
"ie: %08X, c2: %08X, c1: %08X\n",
|
|
status, irqen, config2, config1);
|
|
|
|
if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
|
|
/* ack the event */
|
|
cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
|
|
CB710_MMC_S1_CARD_CHANGED);
|
|
if ((irqen & CB710_MMC_IE_CISTATUS_MASK)
|
|
== CB710_MMC_IE_CISTATUS_MASK)
|
|
mmc_detect_change(mmc, HZ/5);
|
|
} else {
|
|
dev_dbg(cb710_slot_dev(slot), "unknown interrupt (test)\n");
|
|
spin_lock(&reader->irq_lock);
|
|
__cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_TEST_MASK);
|
|
spin_unlock(&reader->irq_lock);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void cb710_mmc_finish_request_tasklet(unsigned long data)
|
|
{
|
|
struct mmc_host *mmc = (void *)data;
|
|
struct cb710_mmc_reader *reader = mmc_priv(mmc);
|
|
struct mmc_request *mrq = reader->mrq;
|
|
|
|
reader->mrq = NULL;
|
|
mmc_request_done(mmc, mrq);
|
|
}
|
|
|
|
static const struct mmc_host_ops cb710_mmc_host = {
|
|
.request = cb710_mmc_request,
|
|
.set_ios = cb710_mmc_set_ios,
|
|
.get_ro = cb710_mmc_get_ro
|
|
};
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
static int cb710_mmc_suspend(struct platform_device *pdev, pm_message_t state)
|
|
{
|
|
struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
|
|
struct mmc_host *mmc = cb710_slot_to_mmc(slot);
|
|
int err;
|
|
|
|
err = mmc_suspend_host(mmc, state);
|
|
if (err)
|
|
return err;
|
|
|
|
cb710_mmc_enable_irq(slot, 0, ~0);
|
|
return 0;
|
|
}
|
|
|
|
static int cb710_mmc_resume(struct platform_device *pdev)
|
|
{
|
|
struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
|
|
struct mmc_host *mmc = cb710_slot_to_mmc(slot);
|
|
|
|
cb710_mmc_enable_irq(slot, 0, ~0);
|
|
|
|
return mmc_resume_host(mmc);
|
|
}
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
static int __devinit cb710_mmc_init(struct platform_device *pdev)
|
|
{
|
|
struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
|
|
struct cb710_chip *chip = cb710_slot_to_chip(slot);
|
|
struct mmc_host *mmc;
|
|
struct cb710_mmc_reader *reader;
|
|
int err;
|
|
u32 val;
|
|
|
|
mmc = mmc_alloc_host(sizeof(*reader), cb710_slot_dev(slot));
|
|
if (!mmc)
|
|
return -ENOMEM;
|
|
|
|
dev_set_drvdata(&pdev->dev, mmc);
|
|
|
|
/* harmless (maybe) magic */
|
|
pci_read_config_dword(chip->pdev, 0x48, &val);
|
|
val = cb710_src_freq_mhz[(val >> 16) & 0xF];
|
|
dev_dbg(cb710_slot_dev(slot), "source frequency: %dMHz\n", val);
|
|
val *= 1000000;
|
|
|
|
mmc->ops = &cb710_mmc_host;
|
|
mmc->f_max = val;
|
|
mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
|
|
mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
|
|
mmc->caps = MMC_CAP_4_BIT_DATA;
|
|
|
|
reader = mmc_priv(mmc);
|
|
|
|
tasklet_init(&reader->finish_req_tasklet,
|
|
cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
|
|
spin_lock_init(&reader->irq_lock);
|
|
cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
|
|
|
|
cb710_mmc_enable_irq(slot, 0, ~0);
|
|
cb710_set_irq_handler(slot, cb710_mmc_irq_handler);
|
|
|
|
err = mmc_add_host(mmc);
|
|
if (unlikely(err))
|
|
goto err_free_mmc;
|
|
|
|
dev_dbg(cb710_slot_dev(slot), "mmc_hostname is %s\n",
|
|
mmc_hostname(mmc));
|
|
|
|
cb710_mmc_enable_irq(slot, CB710_MMC_IE_CARD_INSERTION_STATUS, 0);
|
|
|
|
return 0;
|
|
|
|
err_free_mmc:
|
|
dev_dbg(cb710_slot_dev(slot), "mmc_add_host() failed: %d\n", err);
|
|
|
|
mmc_free_host(mmc);
|
|
return err;
|
|
}
|
|
|
|
static int __devexit cb710_mmc_exit(struct platform_device *pdev)
|
|
{
|
|
struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
|
|
struct mmc_host *mmc = cb710_slot_to_mmc(slot);
|
|
struct cb710_mmc_reader *reader = mmc_priv(mmc);
|
|
|
|
cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_CARD_INSERTION_STATUS);
|
|
|
|
mmc_remove_host(mmc);
|
|
|
|
/* IRQs should be disabled now, but let's stay on the safe side */
|
|
cb710_mmc_enable_irq(slot, 0, ~0);
|
|
cb710_set_irq_handler(slot, NULL);
|
|
|
|
/* clear config ports - just in case */
|
|
cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0);
|
|
cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0);
|
|
|
|
tasklet_kill(&reader->finish_req_tasklet);
|
|
|
|
mmc_free_host(mmc);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver cb710_mmc_driver = {
|
|
.driver.name = "cb710-mmc",
|
|
.probe = cb710_mmc_init,
|
|
.remove = __devexit_p(cb710_mmc_exit),
|
|
#ifdef CONFIG_PM
|
|
.suspend = cb710_mmc_suspend,
|
|
.resume = cb710_mmc_resume,
|
|
#endif
|
|
};
|
|
|
|
static int __init cb710_mmc_init_module(void)
|
|
{
|
|
return platform_driver_register(&cb710_mmc_driver);
|
|
}
|
|
|
|
static void __exit cb710_mmc_cleanup_module(void)
|
|
{
|
|
platform_driver_unregister(&cb710_mmc_driver);
|
|
}
|
|
|
|
module_init(cb710_mmc_init_module);
|
|
module_exit(cb710_mmc_cleanup_module);
|
|
|
|
MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
|
|
MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:cb710-mmc");
|