root/usr.bin/sdiotool/cam_sdio.c
/*-
 * Copyright (c) 2017 Ilya Bakulin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#include "cam_sdio.h"

/* Use CMD52 to read or write a single byte */
int
sdio_rw_direct(struct cam_device *dev,
               uint8_t func_number,
               uint32_t addr,
               uint8_t is_write,
               uint8_t *data, uint8_t *resp) {
        union ccb *ccb;
        uint32_t flags;
        uint32_t arg;
        int retval = 0;

        ccb = cam_getccb(dev);
        if (ccb == NULL) {
                warnx("%s: error allocating CCB", __func__);
                return (-1);
        }
        bzero(&(&ccb->ccb_h)[1],
              sizeof(union ccb) - sizeof(struct ccb_hdr));

        flags = MMC_RSP_R5 | MMC_CMD_AC;
        arg = SD_IO_RW_FUNC(func_number) | SD_IO_RW_ADR(addr);
        if (is_write)
                arg |= SD_IO_RW_WR | SD_IO_RW_RAW | SD_IO_RW_DAT(*data);

        cam_fill_mmcio(&ccb->mmcio,
                       /*retries*/ 0,
                       /*cbfcnp*/ NULL,
                       /*flags*/ CAM_DIR_NONE,
                       /*mmc_opcode*/ SD_IO_RW_DIRECT,
                       /*mmc_arg*/ arg,
                       /*mmc_flags*/ flags,
                       /*mmc_data*/ 0,
                       /*timeout*/ 5000);

        if (((retval = cam_send_ccb(dev, ccb)) < 0)
            || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
                const char warnstr[] = "error sending command";

                if (retval < 0)
                        warn(warnstr);
                else
                        warnx(warnstr);
                return (-1);
        }

        *resp = ccb->mmcio.cmd.resp[0] & 0xFF;
        cam_freeccb(ccb);
        return (retval);
}

/*
 * CMD53 -- IO_RW_EXTENDED
 * Use to read or write memory blocks
 *
 * is_increment=1: FIFO mode
 * blk_count > 0: block mode
 */
int
sdio_rw_extended(struct cam_device *dev,
                 uint8_t func_number,
                 uint32_t addr,
                 uint8_t is_write,
                 caddr_t data, size_t datalen,
                 uint8_t is_increment,
                 uint16_t blk_count) {
        union ccb *ccb;
        uint32_t flags;
        uint32_t arg;
        uint32_t cam_flags;
        uint8_t resp;
        struct mmc_data mmcd;
        int retval = 0;

        if (blk_count != 0) {
                warnx("%s: block mode is not supported yet", __func__);
                return (-1);
        }

        ccb = cam_getccb(dev);
        if (ccb == NULL) {
                warnx("%s: error allocating CCB", __func__);
                return (-1);
        }
        bzero(&(&ccb->ccb_h)[1],
              sizeof(union ccb) - sizeof(struct ccb_hdr));

        flags = MMC_RSP_R5 | MMC_CMD_ADTC;
        arg = SD_IO_RW_FUNC(func_number) | SD_IO_RW_ADR(addr) |
                SD_IOE_RW_LEN(datalen);

        if (is_increment)
                arg |= SD_IO_RW_INCR;

        mmcd.data = data;
        mmcd.len = datalen;
        mmcd.xfer_len = 0; /* not used by MMCCAM */
        mmcd.mrq = NULL; /* not used by MMCCAM */

        if (is_write) {
                arg |= SD_IO_RW_WR;
                cam_flags = CAM_DIR_OUT;
                mmcd.flags = MMC_DATA_WRITE;
        } else {
                cam_flags = CAM_DIR_IN;
                mmcd.flags = MMC_DATA_READ;
        }
        cam_fill_mmcio(&ccb->mmcio,
                       /*retries*/ 0,
                       /*cbfcnp*/ NULL,
                       /*flags*/ cam_flags,
                       /*mmc_opcode*/ SD_IO_RW_EXTENDED,
                       /*mmc_arg*/ arg,
                       /*mmc_flags*/ flags,
                       /*mmc_data*/ &mmcd,
                       /*timeout*/ 5000);

        if (((retval = cam_send_ccb(dev, ccb)) < 0)
            || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
                const char warnstr[] = "error sending command";

                if (retval < 0)
                        warn(warnstr);
                else
                        warnx(warnstr);
                return (-1);
        }

        resp = ccb->mmcio.cmd.resp[0] & 0xFF;
        if (resp != 0)
                warn("Response from CMD53 is not 0?!");
        cam_freeccb(ccb);
        return (retval);
}


int
sdio_read_bool_for_func(struct cam_device *dev, uint32_t addr, uint8_t func_number, uint8_t *is_enab) {
        uint8_t resp;
        int ret;

        ret = sdio_rw_direct(dev, 0, addr, 0, NULL, &resp);
        if (ret < 0)
                return ret;

        *is_enab = (resp & (1 << func_number)) > 0 ? 1 : 0;

        return (0);
}

int
sdio_set_bool_for_func(struct cam_device *dev, uint32_t addr, uint8_t func_number, int enable) {
        uint8_t resp;
        int ret;
        uint8_t is_enabled;

        ret = sdio_rw_direct(dev, 0, addr, 0, NULL, &resp);
        if (ret != 0)
                return ret;

        is_enabled = resp & (1 << func_number);
        if ((is_enabled !=0 && enable == 1) || (is_enabled == 0 && enable == 0))
                return 0;

        if (enable)
                resp |= 1 << func_number;
        else
                resp &= ~ (1 << func_number);

        ret = sdio_rw_direct(dev, 0, addr, 1, &resp, &resp);

        return ret;
}

/* Conventional I/O functions */
uint8_t
sdio_read_1(struct cam_device *dev, uint8_t func_number, uint32_t addr, int *ret) {
        uint8_t val;
        *ret = sdio_rw_direct(dev, func_number, addr, 0, NULL, &val);
        return val;
}

int
sdio_write_1(struct cam_device *dev, uint8_t func_number, uint32_t addr, uint8_t val) {
        uint8_t _val;
        return sdio_rw_direct(dev, func_number, addr, 0, &val, &_val);
}

uint16_t
sdio_read_2(struct cam_device *dev, uint8_t func_number, uint32_t addr, int *ret) {
        uint16_t val;
        *ret = sdio_rw_extended(dev, func_number, addr,
                                /* is_write */ 0,
                                /* data */ (caddr_t) &val,
                                /* datalen */ sizeof(val),
                                /* is_increment */ 1,
                                /* blk_count */ 0
                );
        return val;
}


int
sdio_write_2(struct cam_device *dev, uint8_t func_number, uint32_t addr, uint16_t val) {
        return sdio_rw_extended(dev, func_number, addr,
                                /* is_write */ 1,
                                /* data */ (caddr_t) &val,
                                /* datalen */ sizeof(val),
                                /* is_increment */ 1,
                                /* blk_count */ 0
                );
}

uint32_t
sdio_read_4(struct cam_device *dev, uint8_t func_number, uint32_t addr, int *ret) {
        uint32_t val;
        *ret = sdio_rw_extended(dev, func_number, addr,
                                /* is_write */ 0,
                                /* data */ (caddr_t) &val,
                                /* datalen */ sizeof(val),
                                /* is_increment */ 1,
                                /* blk_count */ 0
                );
        return val;
}


int
sdio_write_4(struct cam_device *dev, uint8_t func_number, uint32_t addr, uint32_t val) {
        return sdio_rw_extended(dev, func_number, addr,
                                /* is_write */ 1,
                                /* data */ (caddr_t) &val,
                                /* datalen */ sizeof(val),
                                /* is_increment */ 1,
                                /* blk_count */ 0
                );
}

/* Higher-level wrappers for certain management operations */
int
sdio_is_func_ready(struct cam_device *dev, uint8_t func_number, uint8_t *is_enab) {
        return sdio_read_bool_for_func(dev, SD_IO_CCCR_FN_READY, func_number, is_enab);
}

int
sdio_is_func_enabled(struct cam_device *dev, uint8_t func_number, uint8_t *is_enab) {
        return sdio_read_bool_for_func(dev, SD_IO_CCCR_FN_ENABLE, func_number, is_enab);
}

int
sdio_func_enable(struct cam_device *dev, uint8_t func_number, int enable) {
        return sdio_set_bool_for_func(dev, SD_IO_CCCR_FN_ENABLE, func_number, enable);
}

int
sdio_is_func_intr_enabled(struct cam_device *dev, uint8_t func_number, uint8_t *is_enab) {
        return sdio_read_bool_for_func(dev, SD_IO_CCCR_INT_ENABLE, func_number, is_enab);
}

int
sdio_func_intr_enable(struct cam_device *dev, uint8_t func_number, int enable) {
        return sdio_set_bool_for_func(dev, SD_IO_CCCR_INT_ENABLE, func_number, enable);
}

int
sdio_card_set_bus_width(struct cam_device *dev, enum mmc_bus_width bw) {
        int ret;
        uint8_t ctl_val;
        ret = sdio_rw_direct(dev, 0, SD_IO_CCCR_BUS_WIDTH, 0, NULL, &ctl_val);
        if (ret < 0) {
                warn("Error getting CCCR_BUS_WIDTH value");
                return ret;
        }
        ctl_val &= ~0x3;
        switch (bw) {
        case bus_width_1:
                /* Already set to 1-bit */
                break;
        case bus_width_4:
                ctl_val |= CCCR_BUS_WIDTH_4;
                break;
        case bus_width_8:
                warn("Cannot do 8-bit on SDIO yet");
                return -1;
                break;
        }
        ret = sdio_rw_direct(dev, 0, SD_IO_CCCR_BUS_WIDTH, 1, &ctl_val, &ctl_val);
        if (ret < 0) {
                warn("Error setting CCCR_BUS_WIDTH value");
                return ret;
        }
        return ret;
}

int
sdio_func_read_cis(struct cam_device *dev, uint8_t func_number,
                   uint32_t cis_addr, struct cis_info *info) {
        uint8_t tuple_id, tuple_len, tuple_count;
        uint32_t addr;

        char *cis1_info[4];
        int start, i, ch, count, ret;
        char cis1_info_buf[256];

        tuple_count = 0; /* Use to prevent infinite loop in case of parse errors */
        memset(cis1_info_buf, 0, 256);
        do {
                addr = cis_addr;
                tuple_id = sdio_read_1(dev, 0, addr++, &ret);
                if (tuple_id == SD_IO_CISTPL_END)
                        break;
                if (tuple_id == 0) {
                        cis_addr++;
                        continue;
                }
                tuple_len = sdio_read_1(dev, 0, addr++, &ret);
                if (tuple_len == 0 && tuple_id != 0x00) {
                        warn("Parse error: 0-length tuple %02X\n", tuple_id);
                        return -1;
                }

                switch (tuple_id) {
                case SD_IO_CISTPL_VERS_1:
                        addr += 2;
                        for (count = 0, start = 0, i = 0;
                             (count < 4) && ((i + 4) < 256); i++) {
                                ch = sdio_read_1(dev, 0, addr + i, &ret);
                                printf("count=%d, start=%d, i=%d, Got %c (0x%02x)\n", count, start, i, ch, ch);
                                if (ch == 0xff)
                                        break;
                                cis1_info_buf[i] = ch;
                                if (ch == 0) {
                                        cis1_info[count] =
                                                cis1_info_buf + start;
                                        start = i + 1;
                                        count++;
                                }
                        }
                        printf("Card info:");
                        for (i=0; i<4; i++)
                                if (cis1_info[i])
                                        printf(" %s", cis1_info[i]);
                        printf("\n");
                        break;
                case SD_IO_CISTPL_MANFID:
                        info->man_id =  sdio_read_1(dev, 0, addr++, &ret);
                        info->man_id |= sdio_read_1(dev, 0, addr++, &ret) << 8;

                        info->prod_id =  sdio_read_1(dev, 0, addr++, &ret);
                        info->prod_id |= sdio_read_1(dev, 0, addr++, &ret) << 8;
                        break;
                case SD_IO_CISTPL_FUNCID:
                        /* not sure if we need to parse it? */
                        break;
                case SD_IO_CISTPL_FUNCE:
                        if (tuple_len < 4) {
                                printf("FUNCE is too short: %d\n", tuple_len);
                                break;
                        }
                        if (func_number == 0) {
                                /* skip extended_data */
                                addr++;
                                info->max_block_size  = sdio_read_1(dev, 0, addr++, &ret);
                                info->max_block_size |= sdio_read_1(dev, 0, addr++, &ret) << 8;
                        } else {
                                info->max_block_size  = sdio_read_1(dev, 0, addr + 0xC, &ret);
                                info->max_block_size |= sdio_read_1(dev, 0, addr + 0xD, &ret) << 8;
                        }
                        break;
                default:
                        warnx("Skipping tuple ID %02X len %02X\n", tuple_id, tuple_len);
                }
                cis_addr += tuple_len + 2;
                tuple_count++;
        } while (tuple_count < 20);

        return 0;
}

uint32_t
sdio_get_common_cis_addr(struct cam_device *dev) {
        uint32_t addr;
        int ret;

        addr =  sdio_read_1(dev, 0, SD_IO_CCCR_CISPTR, &ret);
        addr |= sdio_read_1(dev, 0, SD_IO_CCCR_CISPTR + 1, &ret) << 8;
        addr |= sdio_read_1(dev, 0, SD_IO_CCCR_CISPTR + 2, &ret) << 16;

        if (addr < SD_IO_CIS_START || addr > SD_IO_CIS_START + SD_IO_CIS_SIZE) {
                warn("Bad CIS address: %04X\n", addr);
                addr = 0;
        }

        return addr;
}

void sdio_card_reset(struct cam_device *dev) {
        int ret;
        uint8_t ctl_val;
        ret = sdio_rw_direct(dev, 0, SD_IO_CCCR_CTL, 0, NULL, &ctl_val);
        if (ret < 0)
                errx(1, "Error getting CCCR_CTL value");
        ctl_val |= CCCR_CTL_RES;
        ret = sdio_rw_direct(dev, 0, SD_IO_CCCR_CTL, 1, &ctl_val, &ctl_val);
        if (ret < 0)
                errx(1, "Error setting CCCR_CTL value");
}