/* * Copyright (c) 2006-2018, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2020-04-15 Jonne first version for s3c2440 mmc controller */ #include #include #include #include #include #define S3C_PCLK 50000000 static void s3c_mmc_set_clk(struct rt_mmcsd_host *host, rt_uint32_t clock) { rt_uint32_t prescale; rt_uint32_t realClk; for(prescale = 0; prescale < 256; ++prescale) { realClk = S3C_PCLK / (1 + prescale); if(realClk <= clock) { break; } } SDIPRE = prescale; host->io_cfg.clock = realClk; } static rt_uint32_t s3c_mmc_send_cmd(struct rt_mmcsd_host *host, struct rt_mmcsd_cmd *cmd) { rt_uint32_t ccon; rt_uint32_t cmdSta; SDICARG = cmd->arg; ccon = cmd->cmd_code & 0x3f; ccon |= (0 << 7) | (1 << 6); /* two start bits*/ ccon |= (1 << 8);/* command start*/ if(cmd->flags & 0xF) { // Need response ccon |= (1 << 9); } if((cmd->flags & 0xF) == RESP_R2) { // R2 need 136bit response ccon |= (1 << 10); } SDICCON = ccon; /* start cmd */ if(cmd->flags & 0xF) { cmdSta = SDICSTA; while((cmdSta & 0x200) != 0x200 && (cmdSta & 0x400) != 0x400) { cmdSta = SDICSTA; } if((cmdSta & 0x1000) == 0x1000 && (cmd->flags & 0xF) != RESP_R3 && (cmd->flags & 0xF) != RESP_R4) { // crc error, but R3 R4 ignore it SDICSTA = cmdSta; return -RT_ERROR; } if((cmdSta & 0xF00) != 0xa00) { SDICSTA = cmdSta; return -RT_ERROR; } cmd->resp[0] = SDIRSP0; if((cmd->flags & 0xF) == RESP_R2) { cmd->resp[1] = SDIRSP1; cmd->resp[2] = SDIRSP2; cmd->resp[3] = SDIRSP3; } } else { cmdSta = SDICSTA; while((cmdSta & 0x800) != 0x800) { cmdSta = SDICSTA; } } SDICSTA = cmdSta; // clear current status return RT_EOK; } static rt_uint32_t s3c_mmc_xfer_data(struct rt_mmcsd_data *data) { rt_uint32_t status; rt_uint32_t xfer_size; rt_uint32_t handled_size = 0; rt_uint32_t *pBuf = RT_NULL; if(data == RT_NULL) { return -RT_ERROR; } xfer_size = data->blks * data->blksize; pBuf = data->buf; if(data->flags & DATA_DIR_READ) { while(handled_size < xfer_size) { if ((SDIDSTA & 0x20) == 0x20) { SDIDSTA = (0x1 << 0x5); break; } status = SDIFSTA; if ((status & 0x1000) == 0x1000) { *pBuf++ = SDIDAT; handled_size += 4; } } } else { while(handled_size < xfer_size) { status = SDIFSTA; if ((status & 0x2000) == 0x2000) { SDIDAT = *pBuf++; handled_size += 4; } } } // wait for end status = SDIDSTA; while((status & 0x30) == 0) { status = SDIDSTA; } SDIDSTA = status; if ((status & 0xfc) != 0x10) { return -RT_ERROR; } SDIDCON = SDIDCON & ~(7<<12); SDIFSTA = SDIFSTA & 0x200; SDIDSTA = 0x10; return RT_EOK; } static void mmc_request(struct rt_mmcsd_host *host, struct rt_mmcsd_req *req) { rt_uint32_t ret; struct rt_mmcsd_cmd *cmd; struct rt_mmcsd_data *data; rt_uint32_t val; rt_uint32_t tryCnt = 0; if(req->cmd == RT_NULL) { goto out; } cmd = req->cmd; /* prepare for data transfer*/ if(req->data != RT_NULL) { SDIFSTA = SDIFSTA | (1<<16); // reset fifo while(SDIDSTA & 0x03) { if(tryCnt++ > 500) { break; SDIDSTA = SDIDSTA; } } data = req->data; if((data->blksize & 0x3) != 0) { goto out; } val = (2 << 22) //word transfer | (1 << 20) // transmet after response | (1 << 19) // reciveve after command sent | (1 << 17) // block data transfer | (1 << 14); // data start if(host->io_cfg.bus_width == MMCSD_BUS_WIDTH_4) { val |= (1 << 16); // wide bus mode(4bit data) } if(data->flags & DATA_DIR_READ) { // for data read val |= (2 << 12); } else { val |= (3 << 12); } val |= (data->blks & 0xFFF); SDIDCON = val; SDIBSIZE = data->blksize; SDIDTIMER = 0x7fffff; } ret = s3c_mmc_send_cmd(host,req->cmd); if(ret != RT_EOK) { cmd->err = ret; goto out; } if(req->data != RT_NULL) { /*do transfer data*/ ret = s3c_mmc_xfer_data(data); if(ret != RT_EOK) { data->err = ret; goto out; } } out: mmcsd_req_complete(host); } static void mmc_set_iocfg(struct rt_mmcsd_host *host, struct rt_mmcsd_io_cfg *io_cfg) { switch (io_cfg->power_mode) { case MMCSD_POWER_ON: case MMCSD_POWER_UP: /* Enable PCLK into SDI Block */ CLKCON |= 1 << 9; /* Setup GPIO as SD and SDCMD, SDDAT[3:0] Pull up En */ GPEUP = GPEUP & (~(0x3f << 5)) | (0x01 << 5); GPECON = GPECON & (~(0xfff << 10)) | (0xaaa << 10); break; case MMCSD_POWER_OFF: default: break; } s3c_mmc_set_clk(host, io_cfg->clock); SDICON = 1; } static rt_int32_t mmc_get_card_status(struct rt_mmcsd_host *host) { return RT_EOK; } static void mmc_enable_sdio_irq(struct rt_mmcsd_host *host, rt_int32_t en) { } static const struct rt_mmcsd_host_ops ops = { mmc_request, mmc_set_iocfg, mmc_get_card_status, mmc_enable_sdio_irq }; int s3c_sdio_init(void) { struct rt_mmcsd_host * host = RT_NULL; host = mmcsd_alloc_host(); if (!host) { goto err; } host->ops = &ops; host->freq_min = 300000; host->freq_max = 50000000; host->valid_ocr = VDD_32_33 | VDD_33_34; host->flags = MMCSD_MUTBLKWRITE | MMCSD_SUP_HIGHSPEED | MMCSD_SUP_SDIO_IRQ | MMCSD_BUSWIDTH_4; host->max_seg_size = 2048; host->max_dma_segs = 10; host->max_blk_size = 512; host->max_blk_count = 4096; mmcsd_change(host); return RT_EOK; err: if(host) rt_free(host); return -RT_EIO; } INIT_DEVICE_EXPORT(s3c_sdio_init);