/*
|
/*
|
* WISHBONE SD Card Controller IP Core
|
* WISHBONE SD Card Controller IP Core
|
*
|
*
|
* ocsdc.c
|
* ocsdc.c
|
*
|
*
|
* This file is part of the WISHBONE SD Card
|
* This file is part of the WISHBONE SD Card
|
* Controller IP Core project
|
* Controller IP Core project
|
* http://www.opencores.org/cores/xxx/
|
* http://opencores.org/project,sd_card_controller
|
*
|
*
|
* Description
|
* Description
|
* Driver for the WISHBONE SD Card Controller IP Core.
|
* Driver for the WISHBONE SD Card Controller IP Core.
|
*
|
*
|
* Author(s):
|
* Author(s):
|
* - Marek Czerski, ma.czerski@gmail.com
|
* - Marek Czerski, ma.czerski@gmail.com
|
*/
|
*/
|
/*
|
/*
|
*
|
*
|
* Copyright (C) 2013 Authors
|
* Copyright (C) 2013 Authors
|
*
|
*
|
* This source file may be used and distributed without
|
* This source file may be used and distributed without
|
* restriction provided that this copyright statement is not
|
* restriction provided that this copyright statement is not
|
* removed from the file and that any derivative work contains
|
* removed from the file and that any derivative work contains
|
* the original copyright notice and the associated disclaimer.
|
* the original copyright notice and the associated disclaimer.
|
*
|
*
|
* This source file is free software; you can redistribute it
|
* This source file is free software; you can redistribute it
|
* and/or modify it under the terms of the GNU Lesser General
|
* and/or modify it under the terms of the GNU Lesser General
|
* Public License as published by the Free Software Foundation;
|
* Public License as published by the Free Software Foundation;
|
* either version 2.1 of the License, or (at your option) any
|
* either version 2.1 of the License, or (at your option) any
|
* later version.
|
* later version.
|
*
|
*
|
* This source is distributed in the hope that it will be
|
* This source is distributed in the hope that it will be
|
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
* PURPOSE. See the GNU Lesser General Public License for more
|
* PURPOSE. See the GNU Lesser General Public License for more
|
* details.
|
* details.
|
*
|
*
|
* You should have received a copy of the GNU Lesser General
|
* You should have received a copy of the GNU Lesser General
|
* Public License along with this source; if not, download it
|
* Public License along with this source; if not, download it
|
* from http://www.opencores.org/lgpl.shtml
|
* from http://www.opencores.org/lgpl.shtml
|
*/
|
*/
|
|
|
#include "mmc.h"
|
#include "mmc.h"
|
#include <malloc.h>
|
#include <malloc.h>
|
#include <stdint.h>
|
#include <stdint.h>
|
#include <stdlib.h>
|
#include <stdlib.h>
|
#include <stdio.h>
|
#include <stdio.h>
|
#include <string.h>
|
#include <string.h>
|
#include <or1k-support.h>
|
#include <or1k-support.h>
|
|
|
// Register space
|
// Register space
|
#define OCSDC_ARGUMENT 0x00
|
#define OCSDC_ARGUMENT 0x00
|
#define OCSDC_COMMAND 0x04
|
#define OCSDC_COMMAND 0x04
|
#define OCSDC_RESPONSE_1 0x08
|
#define OCSDC_RESPONSE_1 0x08
|
#define OCSDC_RESPONSE_2 0x0c
|
#define OCSDC_RESPONSE_2 0x0c
|
#define OCSDC_RESPONSE_3 0x10
|
#define OCSDC_RESPONSE_3 0x10
|
#define OCSDC_RESPONSE_4 0x14
|
#define OCSDC_RESPONSE_4 0x14
|
#define OCSDC_CONTROL 0x1C
|
#define OCSDC_CONTROL 0x1C
|
#define OCSDC_TIMEOUT 0x20
|
#define OCSDC_TIMEOUT 0x20
|
#define OCSDC_CLOCK_DIVIDER 0x24
|
#define OCSDC_CLOCK_DIVIDER 0x24
|
#define OCSDC_SOFTWARE_RESET 0x28
|
#define OCSDC_SOFTWARE_RESET 0x28
|
#define OCSDC_POWER_CONTROL 0x2C
|
#define OCSDC_POWER_CONTROL 0x2C
|
#define OCSDC_CAPABILITY 0x30
|
#define OCSDC_CAPABILITY 0x30
|
#define OCSDC_CMD_INT_STATUS 0x34
|
#define OCSDC_CMD_INT_STATUS 0x34
|
#define OCSDC_CMD_INT_ENABLE 0x38
|
#define OCSDC_CMD_INT_ENABLE 0x38
|
#define OCSDC_DAT_INT_STATUS 0x3C
|
#define OCSDC_DAT_INT_STATUS 0x3C
|
#define OCSDC_DAT_INT_ENABLE 0x40
|
#define OCSDC_DAT_INT_ENABLE 0x40
|
#define OCSDC_BLOCK_SIZE 0x44
|
#define OCSDC_BLOCK_SIZE 0x44
|
#define OCSDC_BLOCK_COUNT 0x48
|
#define OCSDC_BLOCK_COUNT 0x48
|
#define OCSDC_DST_SRC_ADDR 0x60
|
#define OCSDC_DST_SRC_ADDR 0x60
|
|
|
// OCSDC_CMD_INT_STATUS bits
|
// OCSDC_CMD_INT_STATUS bits
|
#define OCSDC_CMD_INT_STATUS_CC 0x0001
|
#define OCSDC_CMD_INT_STATUS_CC 0x0001
|
#define OCSDC_CMD_INT_STATUS_EI 0x0002
|
#define OCSDC_CMD_INT_STATUS_EI 0x0002
|
#define OCSDC_CMD_INT_STATUS_CTE 0x0004
|
#define OCSDC_CMD_INT_STATUS_CTE 0x0004
|
#define OCSDC_CMD_INT_STATUS_CCRC 0x0008
|
#define OCSDC_CMD_INT_STATUS_CCRC 0x0008
|
#define OCSDC_CMD_INT_STATUS_CIE 0x0010
|
#define OCSDC_CMD_INT_STATUS_CIE 0x0010
|
|
|
// SDCMSC_DAT_INT_STATUS
|
// SDCMSC_DAT_INT_STATUS
|
#define SDCMSC_DAT_INT_STATUS_TRS 0x01
|
#define SDCMSC_DAT_INT_STATUS_TRS 0x01
|
#define SDCMSC_DAT_INT_STATUS_CRC 0x02
|
#define SDCMSC_DAT_INT_STATUS_CRC 0x02
|
#define SDCMSC_DAT_INT_STATUS_OV 0x04
|
#define SDCMSC_DAT_INT_STATUS_OV 0x04
|
|
|
struct ocsdc {
|
struct ocsdc {
|
int iobase;
|
int iobase;
|
int clk_freq;
|
int clk_freq;
|
};
|
};
|
|
|
#define readl(addr) (*(volatile unsigned int *) (addr))
|
#define readl(addr) (*(volatile unsigned int *) (addr))
|
#define writel(b, addr) ((*(volatile unsigned int *) (addr)) = (b))
|
#define writel(b, addr) ((*(volatile unsigned int *) (addr)) = (b))
|
|
|
void flush_dcache_range(void * start, void * end) {
|
void flush_dcache_range(void * start, void * end) {
|
while (start < end) {
|
while (start < end) {
|
or1k_dcache_flush((unsigned long)start);
|
or1k_dcache_flush((unsigned long)start);
|
start += 4;
|
start += 4;
|
}
|
}
|
}
|
}
|
|
|
static inline uint32_t ocsdc_read(struct ocsdc *dev, int offset)
|
static inline uint32_t ocsdc_read(struct ocsdc *dev, int offset)
|
{
|
{
|
return readl(dev->iobase + offset);
|
return readl(dev->iobase + offset);
|
}
|
}
|
|
|
static inline void ocsdc_write(struct ocsdc *dev, int offset, uint32_t data)
|
static inline void ocsdc_write(struct ocsdc *dev, int offset, uint32_t data)
|
{
|
{
|
writel(data, dev->iobase + offset);
|
writel(data, dev->iobase + offset);
|
}
|
}
|
|
|
static void ocsdc_set_buswidth(struct ocsdc * dev, uint width) {
|
static void ocsdc_set_buswidth(struct ocsdc * dev, uint width) {
|
if (width == 4)
|
if (width == 4)
|
ocsdc_write(dev, OCSDC_CONTROL, 1);
|
ocsdc_write(dev, OCSDC_CONTROL, 1);
|
else if (width == 1)
|
else if (width == 1)
|
ocsdc_write(dev, OCSDC_CONTROL, 0);
|
ocsdc_write(dev, OCSDC_CONTROL, 0);
|
}
|
}
|
|
|
/* Set clock prescalar value based on the required clock in HZ */
|
/* Set clock prescalar value based on the required clock in HZ */
|
static void ocsdc_set_clock(struct ocsdc * dev, uint clock)
|
static void ocsdc_set_clock(struct ocsdc * dev, uint clock)
|
{
|
{
|
int clk_div = dev->clk_freq / (2.0 * clock) - 1;
|
int clk_div = dev->clk_freq / (2.0 * clock) - 1;
|
|
|
printf("ocsdc_set_clock %d, div %d\n\r", clock, clk_div);
|
printf("ocsdc_set_clock %d, div %d\n\r", clock, clk_div);
|
//software reset
|
//software reset
|
ocsdc_write(dev, OCSDC_SOFTWARE_RESET, 1);
|
ocsdc_write(dev, OCSDC_SOFTWARE_RESET, 1);
|
//set clock devider
|
//set clock devider
|
ocsdc_write(dev, OCSDC_CLOCK_DIVIDER, clk_div);
|
ocsdc_write(dev, OCSDC_CLOCK_DIVIDER, clk_div);
|
//clear software reset
|
//clear software reset
|
ocsdc_write(dev, OCSDC_SOFTWARE_RESET, 0);
|
ocsdc_write(dev, OCSDC_SOFTWARE_RESET, 0);
|
}
|
}
|
|
|
static int ocsdc_finish(struct ocsdc * dev, struct mmc_cmd *cmd) {
|
static int ocsdc_finish(struct ocsdc * dev, struct mmc_cmd *cmd) {
|
|
|
int retval = 0;
|
int retval = 0;
|
while (1) {
|
while (1) {
|
int r2 = ocsdc_read(dev, OCSDC_CMD_INT_STATUS);
|
int r2 = ocsdc_read(dev, OCSDC_CMD_INT_STATUS);
|
//printf("ocsdc_finish: cmd %d, status %x\n", cmd->cmdidx, r2);
|
//printf("ocsdc_finish: cmd %d, status %x\n", cmd->cmdidx, r2);
|
if (r2 & OCSDC_CMD_INT_STATUS_EI) {
|
if (r2 & OCSDC_CMD_INT_STATUS_EI) {
|
//clear interrupts
|
//clear interrupts
|
ocsdc_write(dev, OCSDC_CMD_INT_STATUS, 0);
|
ocsdc_write(dev, OCSDC_CMD_INT_STATUS, 0);
|
printf("ocsdc_finish: cmd %d, status %x\n\r", cmd->cmdidx, r2);
|
printf("ocsdc_finish: cmd %d, status %x\n\r", cmd->cmdidx, r2);
|
retval = -1;
|
retval = -1;
|
break;
|
break;
|
}
|
}
|
else if (r2 & OCSDC_CMD_INT_STATUS_CC) {
|
else if (r2 & OCSDC_CMD_INT_STATUS_CC) {
|
//clear interrupts
|
//clear interrupts
|
ocsdc_write(dev, OCSDC_CMD_INT_STATUS, 0);
|
ocsdc_write(dev, OCSDC_CMD_INT_STATUS, 0);
|
//get response
|
//get response
|
cmd->response[0] = ocsdc_read(dev, OCSDC_RESPONSE_1);
|
cmd->response[0] = ocsdc_read(dev, OCSDC_RESPONSE_1);
|
if (cmd->resp_type & MMC_RSP_136) {
|
if (cmd->resp_type & MMC_RSP_136) {
|
cmd->response[1] = ocsdc_read(dev, OCSDC_RESPONSE_2);
|
cmd->response[1] = ocsdc_read(dev, OCSDC_RESPONSE_2);
|
cmd->response[2] = ocsdc_read(dev, OCSDC_RESPONSE_3);
|
cmd->response[2] = ocsdc_read(dev, OCSDC_RESPONSE_3);
|
cmd->response[3] = ocsdc_read(dev, OCSDC_RESPONSE_4);
|
cmd->response[3] = ocsdc_read(dev, OCSDC_RESPONSE_4);
|
}
|
}
|
printf("ocsdc_finish: %d ok\n\r", cmd->cmdidx);
|
printf("ocsdc_finish: %d ok\n\r", cmd->cmdidx);
|
retval = 0;
|
retval = 0;
|
|
|
break;
|
break;
|
}
|
}
|
//else if (!(r2 & OCSDC_CMD_INT_STATUS_CIE)) {
|
//else if (!(r2 & OCSDC_CMD_INT_STATUS_CIE)) {
|
// printf("ocsdc_finish: cmd %d no exec %x\n", cmd->cmdidx, r2);
|
// printf("ocsdc_finish: cmd %d no exec %x\n", cmd->cmdidx, r2);
|
//}
|
//}
|
}
|
}
|
return retval;
|
return retval;
|
}
|
}
|
|
|
static int ocsdc_data_finish(struct ocsdc * dev) {
|
static int ocsdc_data_finish(struct ocsdc * dev) {
|
int status;
|
int status;
|
|
|
while ((status = ocsdc_read(dev, OCSDC_DAT_INT_STATUS)) == 0);
|
while ((status = ocsdc_read(dev, OCSDC_DAT_INT_STATUS)) == 0);
|
ocsdc_write(dev, OCSDC_DAT_INT_STATUS, 0);
|
ocsdc_write(dev, OCSDC_DAT_INT_STATUS, 0);
|
|
|
if (status & SDCMSC_DAT_INT_STATUS_TRS) {
|
if (status & SDCMSC_DAT_INT_STATUS_TRS) {
|
printf("ocsdc_data_finish: ok\n\r");
|
printf("ocsdc_data_finish: ok\n\r");
|
return 0;
|
return 0;
|
}
|
}
|
else {
|
else {
|
printf("ocsdc_data_finish: status %x\n\r", status);
|
printf("ocsdc_data_finish: status %x\n\r", status);
|
return -1;
|
return -1;
|
}
|
}
|
}
|
}
|
|
|
static void ocsdc_setup_data_xfer(struct ocsdc * dev, struct mmc_cmd *cmd, struct mmc_data *data) {
|
static void ocsdc_setup_data_xfer(struct ocsdc * dev, struct mmc_cmd *cmd, struct mmc_data *data) {
|
|
|
//invalidate cache
|
//invalidate cache
|
if (data->flags & MMC_DATA_READ) {
|
if (data->flags & MMC_DATA_READ) {
|
flush_dcache_range(data->dest, data->dest+data->blocksize*data->blocks);
|
flush_dcache_range(data->dest, data->dest+data->blocksize*data->blocks);
|
ocsdc_write(dev, OCSDC_DST_SRC_ADDR, (uint32_t)data->dest);
|
ocsdc_write(dev, OCSDC_DST_SRC_ADDR, (uint32_t)data->dest);
|
}
|
}
|
else {
|
else {
|
flush_dcache_range((void *)data->src, (void *)data->src+data->blocksize*data->blocks);
|
flush_dcache_range((void *)data->src, (void *)data->src+data->blocksize*data->blocks);
|
ocsdc_write(dev, OCSDC_DST_SRC_ADDR, (uint32_t)data->src);
|
ocsdc_write(dev, OCSDC_DST_SRC_ADDR, (uint32_t)data->src);
|
}
|
}
|
ocsdc_write(dev, OCSDC_BLOCK_SIZE, data->blocksize);
|
ocsdc_write(dev, OCSDC_BLOCK_SIZE, data->blocksize);
|
ocsdc_write(dev, OCSDC_BLOCK_COUNT, data->blocks-1);
|
ocsdc_write(dev, OCSDC_BLOCK_COUNT, data->blocks-1);
|
|
|
//printf("ocsdc_setup_read: addr: %x\n", (uint32_t)data->dest);
|
//printf("ocsdc_setup_read: addr: %x\n", (uint32_t)data->dest);
|
|
|
}
|
}
|
|
|
static int ocsdc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
|
static int ocsdc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
|
{
|
{
|
struct ocsdc * dev = mmc->priv;
|
struct ocsdc * dev = mmc->priv;
|
|
|
int command = (cmd->cmdidx << 8);
|
int command = (cmd->cmdidx << 8);
|
if (cmd->resp_type & MMC_RSP_PRESENT) {
|
if (cmd->resp_type & MMC_RSP_PRESENT) {
|
if (cmd->resp_type & MMC_RSP_136)
|
if (cmd->resp_type & MMC_RSP_136)
|
command |= 2;
|
command |= 2;
|
else {
|
else {
|
command |= 1;
|
command |= 1;
|
}
|
}
|
}
|
}
|
if (cmd->resp_type & MMC_RSP_BUSY)
|
if (cmd->resp_type & MMC_RSP_BUSY)
|
command |= (1 << 2);
|
command |= (1 << 2);
|
if (cmd->resp_type & MMC_RSP_CRC)
|
if (cmd->resp_type & MMC_RSP_CRC)
|
command |= (1 << 3);
|
command |= (1 << 3);
|
if (cmd->resp_type & MMC_RSP_OPCODE)
|
if (cmd->resp_type & MMC_RSP_OPCODE)
|
command |= (1 << 4);
|
command |= (1 << 4);
|
|
|
if (data && ((data->flags & MMC_DATA_READ) || ((data->flags & MMC_DATA_WRITE))) && data->blocks) {
|
if (data && ((data->flags & MMC_DATA_READ) || ((data->flags & MMC_DATA_WRITE))) && data->blocks) {
|
if (data->flags & MMC_DATA_READ)
|
if (data->flags & MMC_DATA_READ)
|
command |= (1 << 5);
|
command |= (1 << 5);
|
if (data->flags & MMC_DATA_WRITE)
|
if (data->flags & MMC_DATA_WRITE)
|
command |= (1 << 6);
|
command |= (1 << 6);
|
ocsdc_setup_data_xfer(dev, cmd, data);
|
ocsdc_setup_data_xfer(dev, cmd, data);
|
}
|
}
|
|
|
printf("ocsdc_send_cmd %04x\n\r", command);
|
printf("ocsdc_send_cmd %04x\n\r", command);
|
|
|
// getc();
|
// getc();
|
|
|
ocsdc_write(dev, OCSDC_COMMAND, command);
|
ocsdc_write(dev, OCSDC_COMMAND, command);
|
ocsdc_write(dev, OCSDC_ARGUMENT, cmd->cmdarg);
|
ocsdc_write(dev, OCSDC_ARGUMENT, cmd->cmdarg);
|
|
|
if (ocsdc_finish(dev, cmd) < 0) return -1;
|
if (ocsdc_finish(dev, cmd) < 0) return -1;
|
if (data && data->blocks) return ocsdc_data_finish(dev);
|
if (data && data->blocks) return ocsdc_data_finish(dev);
|
else return 0;
|
else return 0;
|
}
|
}
|
|
|
/* Initialize ocsdc controller */
|
/* Initialize ocsdc controller */
|
static int ocsdc_init(struct mmc *mmc)
|
static int ocsdc_init(struct mmc *mmc)
|
{
|
{
|
struct ocsdc * dev = mmc->priv;
|
struct ocsdc * dev = mmc->priv;
|
|
|
//set timeout
|
//set timeout
|
ocsdc_write(dev, OCSDC_TIMEOUT, 0x7FFF);
|
ocsdc_write(dev, OCSDC_TIMEOUT, 0x7FFF);
|
//disable all interrupts
|
//disable all interrupts
|
ocsdc_write(dev, OCSDC_CMD_INT_ENABLE, 0);
|
ocsdc_write(dev, OCSDC_CMD_INT_ENABLE, 0);
|
ocsdc_write(dev, OCSDC_DAT_INT_ENABLE, 0);
|
ocsdc_write(dev, OCSDC_DAT_INT_ENABLE, 0);
|
//clear all interrupts
|
//clear all interrupts
|
ocsdc_write(dev, OCSDC_CMD_INT_STATUS, 0);
|
ocsdc_write(dev, OCSDC_CMD_INT_STATUS, 0);
|
ocsdc_write(dev, OCSDC_DAT_INT_STATUS, 0);
|
ocsdc_write(dev, OCSDC_DAT_INT_STATUS, 0);
|
//set clock to maximum (devide by 2)
|
//set clock to maximum (devide by 2)
|
ocsdc_set_clock(dev, dev->clk_freq/2);
|
ocsdc_set_clock(dev, dev->clk_freq/2);
|
|
|
return 0;
|
return 0;
|
}
|
}
|
|
|
static void ocsdc_set_ios(struct mmc *mmc)
|
static void ocsdc_set_ios(struct mmc *mmc)
|
{
|
{
|
/* Support only 4 bit if */
|
/* Support only 4 bit if */
|
ocsdc_set_buswidth(mmc->priv, mmc->bus_width);
|
ocsdc_set_buswidth(mmc->priv, mmc->bus_width);
|
|
|
/* Set clock speed */
|
/* Set clock speed */
|
if (mmc->clock)
|
if (mmc->clock)
|
ocsdc_set_clock(mmc->priv, mmc->clock);
|
ocsdc_set_clock(mmc->priv, mmc->clock);
|
}
|
}
|
|
|
struct mmc * ocsdc_mmc_init(int base_addr, int clk_freq)
|
struct mmc * ocsdc_mmc_init(int base_addr, int clk_freq)
|
{
|
{
|
struct mmc *mmc;
|
struct mmc *mmc;
|
struct ocsdc *priv;
|
struct ocsdc *priv;
|
|
|
mmc = malloc(sizeof(struct mmc));
|
mmc = malloc(sizeof(struct mmc));
|
if (!mmc) goto MMC_ALLOC;
|
if (!mmc) goto MMC_ALLOC;
|
priv = malloc(sizeof(struct ocsdc));
|
priv = malloc(sizeof(struct ocsdc));
|
if (!priv) goto OCSDC_ALLOC;
|
if (!priv) goto OCSDC_ALLOC;
|
|
|
memset(mmc, 0, sizeof(struct mmc));
|
memset(mmc, 0, sizeof(struct mmc));
|
memset(priv, 0, sizeof(struct ocsdc));
|
memset(priv, 0, sizeof(struct ocsdc));
|
|
|
priv->iobase = base_addr;
|
priv->iobase = base_addr;
|
priv->clk_freq = clk_freq;
|
priv->clk_freq = clk_freq;
|
|
|
sprintf(mmc->name, "ocsdc");
|
sprintf(mmc->name, "ocsdc");
|
mmc->priv = priv;
|
mmc->priv = priv;
|
mmc->send_cmd = ocsdc_send_cmd;
|
mmc->send_cmd = ocsdc_send_cmd;
|
mmc->set_ios = ocsdc_set_ios;
|
mmc->set_ios = ocsdc_set_ios;
|
mmc->init = ocsdc_init;
|
mmc->init = ocsdc_init;
|
mmc->getcd = NULL;
|
mmc->getcd = NULL;
|
|
|
mmc->f_min = priv->clk_freq/6; /*maximum clock division 64 */
|
mmc->f_min = priv->clk_freq/6; /*maximum clock division 64 */
|
mmc->f_max = priv->clk_freq/2; /*minimum clock division 2 */
|
mmc->f_max = priv->clk_freq/2; /*minimum clock division 2 */
|
mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
|
mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
|
mmc->host_caps = MMC_MODE_4BIT;//MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_4BIT;
|
mmc->host_caps = MMC_MODE_4BIT;//MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_4BIT;
|
|
|
mmc->b_max = 256;
|
mmc->b_max = 256;
|
|
|
return mmc;
|
return mmc;
|
|
|
OCSDC_ALLOC:
|
OCSDC_ALLOC:
|
free(mmc);
|
free(mmc);
|
MMC_ALLOC:
|
MMC_ALLOC:
|
return NULL;
|
return NULL;
|
}
|
}
|
|
|