029c02a3ae
It's now possible to use Broadcom functions to read the I/O registers of SDIO card. The functions were copied from the BSD-licensed Broadcom Linux driver as-is. To make it possible, a small Linux compatibility layer was introduced. Currently the card responds with the correct version number ("magic") when reading the corresponding address. Approved by: imp (mentor) Differential Revision: https://reviews.freebsd.org/D12111
441 lines
11 KiB
C
441 lines
11 KiB
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.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#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");
|
|
}
|