#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/disk.h>
#include <sys/device.h>
#include <sys/devicestat.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/spinlock.h>
#include <sys/buf2.h>
#include <bus/mmc/bridge.h>
#include <bus/mmc/mmcvar.h>
#include <bus/mmc/mmcreg.h>
#include "mmcbus_if.h"
struct mmcsd_softc {
device_t dev;
cdev_t dev_t;
struct lock sc_lock;
struct disk disk;
struct devstat device_stats;
struct thread *td;
struct bio_queue_head bio_queue;
daddr_t eblock, eend;
int running;
int suspend;
};
static int mmcsd_probe(device_t dev);
static int mmcsd_attach(device_t dev);
static int mmcsd_detach(device_t dev);
static d_open_t mmcsd_open;
static d_close_t mmcsd_close;
static d_strategy_t mmcsd_strategy;
static d_dump_t mmcsd_dump;
static void mmcsd_task(void *arg);
static const char *mmcsd_card_name(device_t dev);
static int mmcsd_bus_bit_width(device_t dev);
#define MMCSD_LOCK(_sc) lockmgr(&(_sc)->sc_lock, LK_EXCLUSIVE)
#define MMCSD_UNLOCK(_sc) lockmgr(&(_sc)->sc_lock, LK_RELEASE)
#define MMCSD_LOCK_INIT(_sc) lockinit(&(_sc)->sc_lock, "mmcsd", 0, LK_CANRECURSE)
#define MMCSD_LOCK_DESTROY(_sc) lockuninit(&(_sc)->sc_lock);
#define MMCSD_ASSERT_LOCKED(_sc) KKASSERT(lockowned(&(_sc)->sc_lock));
#define MMCSD_ASSERT_UNLOCKED(_sc) KKASSERT(!lockowned(&(_sc)->sc_lock));
static struct dev_ops mmcsd_ops = {
{ "mmcsd", 0, D_DISK | D_MPSAFE },
.d_open = mmcsd_open,
.d_close = mmcsd_close,
.d_strategy = mmcsd_strategy,
.d_dump = mmcsd_dump,
};
static int
mmcsd_probe(device_t dev)
{
device_quiet(dev);
device_set_desc(dev, "MMC/SD Memory Card");
return (0);
}
static int
mmcsd_attach(device_t dev)
{
struct mmcsd_softc *sc;
struct disk_info info;
cdev_t dsk;
intmax_t mb;
char unit;
int sector_size;
sc = device_get_softc(dev);
sc->dev = dev;
MMCSD_LOCK_INIT(sc);
sector_size = mmc_get_sector_size(dev);
devstat_add_entry(&sc->device_stats, "mmcsd", device_get_unit(dev),
sector_size, DEVSTAT_NO_ORDERED_TAGS,
DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_OTHER,
DEVSTAT_PRIORITY_DISK);
bioq_init(&sc->bio_queue);
sc->running = 1;
sc->suspend = 0;
sc->eblock = sc->eend = 0;
kthread_create(mmcsd_task, sc, &sc->td, "mmc/sd card task");
bzero(&info, sizeof(info));
info.d_media_blksize = sector_size;
info.d_media_blocks = mmc_get_media_size(dev);
info.d_secpertrack = 1024;
info.d_nheads = 1;
info.d_secpercyl = info.d_secpertrack * info.d_nheads;
info.d_ncylinders = (u_int)(info.d_media_blocks / info.d_secpercyl);
mb = (info.d_media_blksize * info.d_media_blocks) >> 20;
unit = 'M';
if (mb >= 10240) {
unit = 'G';
mb /= 1024;
}
device_printf(dev, "%ju%cB <%s Memory Card>%s at %s %dMHz/%dbit\n",
mb, unit, mmcsd_card_name(dev),
mmc_get_read_only(dev) ? " (read-only)" : "",
device_get_nameunit(device_get_parent(dev)),
mmc_get_tran_speed(dev) / 1000000, mmcsd_bus_bit_width(dev));
dsk = disk_create(device_get_unit(dev), &sc->disk, &mmcsd_ops);
dsk->si_drv1 = sc;
sc->dev_t = dsk;
dsk->si_iosize_max = 4*1024*1024;
disk_setdiskinfo(&sc->disk, &info);
return (0);
}
static int
mmcsd_detach(device_t dev)
{
struct mmcsd_softc *sc = device_get_softc(dev);
struct buf *q_bp;
struct bio *q_bio;
MMCSD_LOCK(sc);
sc->suspend = 0;
if (sc->running > 0) {
sc->running = 0;
wakeup(sc);
while (sc->running != -1)
lksleep(sc, &sc->sc_lock, 0, "detach", 0);
}
MMCSD_UNLOCK(sc);
while ((q_bio = bioq_takefirst(&sc->bio_queue)) != NULL) {
q_bp = q_bio->bio_buf;
q_bp->b_resid = q_bp->b_bcount;
q_bp->b_error = ENXIO;
q_bp->b_flags |= B_ERROR;
biodone(q_bio);
}
disk_destroy(&sc->disk);
devstat_remove_entry(&sc->device_stats);
MMCSD_LOCK_DESTROY(sc);
return (0);
}
static int
mmcsd_suspend(device_t dev)
{
struct mmcsd_softc *sc = device_get_softc(dev);
MMCSD_LOCK(sc);
sc->suspend = 1;
if (sc->running > 0) {
sc->running = 0;
wakeup(sc);
while (sc->running != -1)
lksleep(sc, &sc->sc_lock, 0, "detach", 0);
}
MMCSD_UNLOCK(sc);
return (0);
}
static int
mmcsd_resume(device_t dev)
{
struct mmcsd_softc *sc = device_get_softc(dev);
MMCSD_LOCK(sc);
sc->suspend = 0;
if (sc->running <= 0) {
sc->running = 1;
MMCSD_UNLOCK(sc);
kthread_create(mmcsd_task, sc, &sc->td, "mmc/sd card task");
} else
MMCSD_UNLOCK(sc);
return (0);
}
static int
mmcsd_open(struct dev_open_args *ap __unused)
{
struct mmcsd_softc *sc;
sc = (struct mmcsd_softc *)ap->a_head.a_dev->si_drv1;
if ((ap->a_oflags & FWRITE) && mmc_get_read_only(sc->dev))
return (EACCES);
return (0);
}
static int
mmcsd_close(struct dev_close_args *ap __unused)
{
return (0);
}
static int
mmcsd_strategy(struct dev_strategy_args *ap)
{
struct mmcsd_softc *sc;
struct bio *bio = ap->a_bio;
struct buf *bp = bio->bio_buf;
sc = (struct mmcsd_softc *)ap->a_head.a_dev->si_drv1;
MMCSD_LOCK(sc);
if (sc->running > 0 || sc->suspend > 0) {
bioqdisksort(&sc->bio_queue, bio);
MMCSD_UNLOCK(sc);
wakeup(sc);
} else {
MMCSD_UNLOCK(sc);
bp->b_error = ENXIO;
bp->b_flags |= B_ERROR;
bp->b_resid = bp->b_bcount;
biodone(bio);
}
return (0);
}
static daddr_t
mmcsd_rw(struct mmcsd_softc *sc, struct bio *bio)
{
daddr_t block, end;
struct mmc_command cmd;
struct mmc_command stop;
struct mmc_request req;
struct mmc_data data;
device_t dev = sc->dev;
int sz = sc->disk.d_info.d_media_blksize;
struct buf *bp = bio->bio_buf;
block = bio->bio_offset / sz;
end = block + (bp->b_bcount / sz);
while (block < end) {
char *vaddr = bp->b_data +
(block - (bio->bio_offset / sz)) * sz;
int numblocks = min(end - block, mmc_get_max_data(dev));
memset(&req, 0, sizeof(req));
memset(&cmd, 0, sizeof(cmd));
memset(&stop, 0, sizeof(stop));
req.cmd = &cmd;
cmd.data = &data;
if (bp->b_cmd == BUF_CMD_READ) {
if (numblocks > 1)
cmd.opcode = MMC_READ_MULTIPLE_BLOCK;
else
cmd.opcode = MMC_READ_SINGLE_BLOCK;
} else {
if (numblocks > 1)
cmd.opcode = MMC_WRITE_MULTIPLE_BLOCK;
else
cmd.opcode = MMC_WRITE_BLOCK;
}
cmd.arg = block;
if (!mmc_get_high_cap(dev))
cmd.arg <<= 9;
cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
data.data = vaddr;
data.mrq = &req;
if (bp->b_cmd == BUF_CMD_READ)
data.flags = MMC_DATA_READ;
else
data.flags = MMC_DATA_WRITE;
data.len = numblocks * sz;
if (numblocks > 1) {
data.flags |= MMC_DATA_MULTI;
stop.opcode = MMC_STOP_TRANSMISSION;
stop.arg = 0;
stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
req.stop = &stop;
}
MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, &req);
if (req.cmd->error != MMC_ERR_NONE)
break;
block += numblocks;
}
return (block);
}
static daddr_t
mmcsd_delete(struct mmcsd_softc *sc, struct bio *bio)
{
daddr_t block, end, start, stop;
struct mmc_command cmd;
struct mmc_request req;
device_t dev = sc->dev;
int sz = sc->disk.d_info.d_media_blksize;
int erase_sector;
struct buf *bp = bio->bio_buf;
block = bio->bio_offset / sz;
end = block + (bp->b_bcount / sz);
if (block > sc->eblock && block <= sc->eend)
block = sc->eblock;
if (end >= sc->eblock && end < sc->eend)
end = sc->eend;
erase_sector = mmc_get_erase_sector(dev);
start = block + erase_sector - 1;
start -= start % erase_sector;
stop = end;
stop -= end % erase_sector;
if (start >= stop) {
sc->eblock = block;
sc->eend = end;
return (end);
}
memset(&req, 0, sizeof(req));
memset(&cmd, 0, sizeof(cmd));
req.cmd = &cmd;
if (mmc_get_card_type(dev) == mode_sd)
cmd.opcode = SD_ERASE_WR_BLK_START;
else
cmd.opcode = MMC_ERASE_GROUP_START;
cmd.arg = start;
if (!mmc_get_high_cap(dev))
cmd.arg <<= 9;
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, &req);
if (req.cmd->error != MMC_ERR_NONE) {
kprintf("erase err1: %d\n", req.cmd->error);
return (block);
}
memset(&req, 0, sizeof(req));
memset(&cmd, 0, sizeof(cmd));
req.cmd = &cmd;
if (mmc_get_card_type(dev) == mode_sd)
cmd.opcode = SD_ERASE_WR_BLK_END;
else
cmd.opcode = MMC_ERASE_GROUP_END;
cmd.arg = stop;
if (!mmc_get_high_cap(dev))
cmd.arg <<= 9;
cmd.arg--;
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, &req);
if (req.cmd->error != MMC_ERR_NONE) {
kprintf("erase err2: %d\n", req.cmd->error);
return (block);
}
memset(&req, 0, sizeof(req));
memset(&cmd, 0, sizeof(cmd));
req.cmd = &cmd;
cmd.opcode = MMC_ERASE;
cmd.arg = 0;
cmd.flags = MMC_RSP_R1B | MMC_CMD_AC;
MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, &req);
if (req.cmd->error != MMC_ERR_NONE) {
kprintf("erase err3 %d\n", req.cmd->error);
return (block);
}
if ((bio->bio_offset / sz) >= sc->eblock || block == start) {
sc->eblock = stop;
sc->eend = end;
} else {
sc->eblock = block;
sc->eend = start;
}
return (end);
}
static int
mmcsd_dump(struct dev_dump_args *ap)
{
#if 0
cdev_t cdev = ap->a_head.a_dev;
struct mmcsd_softc *sc = (struct mmcsd_softc *)cdev->si_drv1;
device_t dev = sc->dev;
struct bio bp;
daddr_t block, end;
int length = ap->a_length;
if (!length)
return (0);
bzero(&bp, sizeof(struct bio));
bp.bio_driver_info = cdev;
bp.bio_pblkno = offset / sc->disk->d_sectorsize;
bp.bio_bcount = length;
bp.bio_data = virtual;
bp.bio_cmd = BIO_WRITE;
end = bp.bio_pblkno + bp.bio_bcount / sc->disk.d_info.d_media_blksize;
MMCBUS_ACQUIRE_BUS(device_get_parent(dev), dev);
block = mmcsd_rw(sc, &bp);
MMCBUS_RELEASE_BUS(device_get_parent(dev), dev);
return ((end < block) ? EIO : 0);
#endif
return EIO;
}
static void
mmcsd_task(void *arg)
{
struct mmcsd_softc *sc = (struct mmcsd_softc*)arg;
struct bio *bio;
struct buf *bp;
int sz;
daddr_t block, end;
device_t dev;
dev = sc->dev;
while (1) {
MMCSD_LOCK(sc);
do {
if (sc->running == 0)
goto out;
bio = bioq_takefirst(&sc->bio_queue);
if (bio == NULL)
lksleep(sc, &sc->sc_lock, 0, "jobqueue", 0);
} while (bio == NULL);
MMCSD_UNLOCK(sc);
bp = bio->bio_buf;
devstat_start_transaction(&sc->device_stats);
MMCBUS_ACQUIRE_BUS(device_get_parent(dev), dev);
sz = sc->disk.d_info.d_media_blksize;
block = bio->bio_offset / sz;
end = block + (bp->b_bcount / sz);
if (bp->b_cmd == BUF_CMD_READ ||
bp->b_cmd == BUF_CMD_WRITE) {
if (block < sc->eend && end > sc->eblock)
sc->eblock = sc->eend = 0;
block = mmcsd_rw(sc, bio);
} else if (bp->b_cmd == BUF_CMD_FREEBLKS) {
block = mmcsd_delete(sc, bio);
}
MMCBUS_RELEASE_BUS(device_get_parent(dev), dev);
if (block < end) {
bp->b_error = EIO;
bp->b_resid = (end - block) * sz;
bp->b_flags |= B_ERROR;
} else {
bp->b_resid = 0;
}
devstat_end_transaction_buf(&sc->device_stats, bp);
biodone(bio);
}
out:
sc->running = -1;
MMCSD_UNLOCK(sc);
wakeup(sc);
}
static const char *
mmcsd_card_name(device_t dev)
{
if (mmc_get_card_type(dev) == mode_mmc)
return ("MMC");
if (mmc_get_high_cap(dev))
return ("SDHC");
return ("SD");
}
static int
mmcsd_bus_bit_width(device_t dev)
{
if (mmc_get_bus_width(dev) == bus_width_1)
return (1);
if (mmc_get_bus_width(dev) == bus_width_4)
return (4);
return (8);
}
static device_method_t mmcsd_methods[] = {
DEVMETHOD(device_probe, mmcsd_probe),
DEVMETHOD(device_attach, mmcsd_attach),
DEVMETHOD(device_detach, mmcsd_detach),
DEVMETHOD(device_suspend, mmcsd_suspend),
DEVMETHOD(device_resume, mmcsd_resume),
DEVMETHOD_END
};
static driver_t mmcsd_driver = {
"mmcsd",
mmcsd_methods,
sizeof(struct mmcsd_softc),
};
static devclass_t mmcsd_devclass;
DRIVER_MODULE(mmcsd, mmc, mmcsd_driver, mmcsd_devclass, NULL, NULL);