15c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman/* 25c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman * linux/drivers/mmc/sdio_ops.c 35c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman * 45c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman * Copyright 2006-2007 Pierre Ossman 55c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman * 65c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman * This program is free software; you can redistribute it and/or modify 75c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman * it under the terms of the GNU General Public License as published by 85c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman * the Free Software Foundation; either version 2 of the License, or (at 95c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman * your option) any later version. 105c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman */ 115c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 12112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman#include <linux/scatterlist.h> 13112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 145c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman#include <linux/mmc/host.h> 15b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman#include <linux/mmc/card.h> 165c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman#include <linux/mmc/mmc.h> 175c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman#include <linux/mmc/sdio.h> 185c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 195c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman#include "core.h" 20e70aa3fac1ac50c7a75ac676a1489dd1ea3b4be5Adrian Bunk#include "sdio_ops.h" 215c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 225c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossmanint mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) 235c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman{ 241278dba167f01bb3c6626d16450d31129d041087Chris Ball struct mmc_command cmd = {0}; 255c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman int i, err = 0; 265c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 275c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman BUG_ON(!host); 285c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 295c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman cmd.opcode = SD_IO_SEND_OP_COND; 305c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman cmd.arg = ocr; 31af51715079e7fb6b290e1881d63d815dc4de5011David Brownell cmd.flags = MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR; 325c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 335c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman for (i = 100; i; i--) { 345c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); 355c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman if (err) 365c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman break; 375c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 38af51715079e7fb6b290e1881d63d815dc4de5011David Brownell /* if we're just probing, do a single pass */ 39af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (ocr == 0) 405c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman break; 415c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 42af51715079e7fb6b290e1881d63d815dc4de5011David Brownell /* otherwise wait until reset completes */ 43af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (mmc_host_is_spi(host)) { 44af51715079e7fb6b290e1881d63d815dc4de5011David Brownell /* 45af51715079e7fb6b290e1881d63d815dc4de5011David Brownell * Both R1_SPI_IDLE and MMC_CARD_BUSY indicate 46af51715079e7fb6b290e1881d63d815dc4de5011David Brownell * an initialized card under SPI, but some cards 47af51715079e7fb6b290e1881d63d815dc4de5011David Brownell * (Marvell's) only behave when looking at this 48af51715079e7fb6b290e1881d63d815dc4de5011David Brownell * one. 49af51715079e7fb6b290e1881d63d815dc4de5011David Brownell */ 50af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (cmd.resp[1] & MMC_CARD_BUSY) 51af51715079e7fb6b290e1881d63d815dc4de5011David Brownell break; 52af51715079e7fb6b290e1881d63d815dc4de5011David Brownell } else { 53af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (cmd.resp[0] & MMC_CARD_BUSY) 54af51715079e7fb6b290e1881d63d815dc4de5011David Brownell break; 55af51715079e7fb6b290e1881d63d815dc4de5011David Brownell } 56af51715079e7fb6b290e1881d63d815dc4de5011David Brownell 575c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman err = -ETIMEDOUT; 585c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 595c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman mmc_delay(10); 605c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman } 615c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 625c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman if (rocr) 63af51715079e7fb6b290e1881d63d815dc4de5011David Brownell *rocr = cmd.resp[mmc_host_is_spi(host) ? 1 : 0]; 645c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 655c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman return err; 665c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman} 675c4e6f1301649d5b29dd0f70e6da83e728ab5ca5Pierre Ossman 68516a82422209e078345d0ca54b16793d7bfd4782Albert Herranzstatic int mmc_io_rw_direct_host(struct mmc_host *host, int write, unsigned fn, 69516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz unsigned addr, u8 in, u8 *out) 70b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman{ 711278dba167f01bb3c6626d16450d31129d041087Chris Ball struct mmc_command cmd = {0}; 72b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman int err; 73b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman 74516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz BUG_ON(!host); 75b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman BUG_ON(fn > 7); 76b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman 77be6f19fc24c937112d251232b3dae7e05e96aad1Pierre Ossman /* sanity check */ 78be6f19fc24c937112d251232b3dae7e05e96aad1Pierre Ossman if (addr & ~0x1FFFF) 79be6f19fc24c937112d251232b3dae7e05e96aad1Pierre Ossman return -EINVAL; 80be6f19fc24c937112d251232b3dae7e05e96aad1Pierre Ossman 81b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman cmd.opcode = SD_IO_RW_DIRECT; 82b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman cmd.arg = write ? 0x80000000 : 0x00000000; 83b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman cmd.arg |= fn << 28; 84b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman cmd.arg |= (write && out) ? 0x08000000 : 0x00000000; 85b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman cmd.arg |= addr << 9; 86b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman cmd.arg |= in; 87af51715079e7fb6b290e1881d63d815dc4de5011David Brownell cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC; 88b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman 89516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz err = mmc_wait_for_cmd(host, &cmd, 0); 90b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman if (err) 91b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman return err; 92b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman 93516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz if (mmc_host_is_spi(host)) { 94af51715079e7fb6b290e1881d63d815dc4de5011David Brownell /* host driver already reported errors */ 95af51715079e7fb6b290e1881d63d815dc4de5011David Brownell } else { 96af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (cmd.resp[0] & R5_ERROR) 97af51715079e7fb6b290e1881d63d815dc4de5011David Brownell return -EIO; 98af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (cmd.resp[0] & R5_FUNCTION_NUMBER) 99af51715079e7fb6b290e1881d63d815dc4de5011David Brownell return -EINVAL; 100af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (cmd.resp[0] & R5_OUT_OF_RANGE) 101af51715079e7fb6b290e1881d63d815dc4de5011David Brownell return -ERANGE; 102af51715079e7fb6b290e1881d63d815dc4de5011David Brownell } 103b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman 104af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (out) { 105516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz if (mmc_host_is_spi(host)) 106af51715079e7fb6b290e1881d63d815dc4de5011David Brownell *out = (cmd.resp[0] >> 8) & 0xFF; 107af51715079e7fb6b290e1881d63d815dc4de5011David Brownell else 108af51715079e7fb6b290e1881d63d815dc4de5011David Brownell *out = cmd.resp[0] & 0xFF; 109af51715079e7fb6b290e1881d63d815dc4de5011David Brownell } 110b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman 111b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman return 0; 112b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman} 113b2bcc798bbb482b2909801280f3c4aff8cbbf5bePierre Ossman 114516a82422209e078345d0ca54b16793d7bfd4782Albert Herranzint mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, 115516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz unsigned addr, u8 in, u8 *out) 116516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz{ 117516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz BUG_ON(!card); 118516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz return mmc_io_rw_direct_host(card->host, write, fn, addr, in, out); 119516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz} 120516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz 121112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossmanint mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, 122eb6594689226663968ef0a9fd71ec5e1e4e04f9cDavid Vrabel unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz) 123112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman{ 124ad5fd97288655b5628052c1fa906419417c86100Venkatraman S struct mmc_request mrq = {NULL}; 1251278dba167f01bb3c6626d16450d31129d041087Chris Ball struct mmc_command cmd = {0}; 126a61ad2b49bfce94dfddce828cd9222e4b9e7825bChris Ball struct mmc_data data = {0}; 127968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim struct scatterlist sg, *sg_ptr; 128968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim struct sg_table sgtable; 129968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim unsigned int nents, left_size, i; 130968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim unsigned int seg_size = card->host->max_seg_size; 131112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 132112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman BUG_ON(!card); 133112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman BUG_ON(fn > 7); 134eb6594689226663968ef0a9fd71ec5e1e4e04f9cDavid Vrabel WARN_ON(blksz == 0); 135112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 136be6f19fc24c937112d251232b3dae7e05e96aad1Pierre Ossman /* sanity check */ 137be6f19fc24c937112d251232b3dae7e05e96aad1Pierre Ossman if (addr & ~0x1FFFF) 138be6f19fc24c937112d251232b3dae7e05e96aad1Pierre Ossman return -EINVAL; 139be6f19fc24c937112d251232b3dae7e05e96aad1Pierre Ossman 140112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman mrq.cmd = &cmd; 141112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman mrq.data = &data; 142112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 143112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman cmd.opcode = SD_IO_RW_EXTENDED; 144112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman cmd.arg = write ? 0x80000000 : 0x00000000; 145112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman cmd.arg |= fn << 28; 146eb6594689226663968ef0a9fd71ec5e1e4e04f9cDavid Vrabel cmd.arg |= incr_addr ? 0x04000000 : 0x00000000; 147112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman cmd.arg |= addr << 9; 148052d81da6e6f0f8839ef6d5a46f215fc8cd99d5aStefan Nilsson XK if (blocks == 0) 149052d81da6e6f0f8839ef6d5a46f215fc8cd99d5aStefan Nilsson XK cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */ 150eb6594689226663968ef0a9fd71ec5e1e4e04f9cDavid Vrabel else 151eb6594689226663968ef0a9fd71ec5e1e4e04f9cDavid Vrabel cmd.arg |= 0x08000000 | blocks; /* block mode */ 152af51715079e7fb6b290e1881d63d815dc4de5011David Brownell cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; 153112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 154eb6594689226663968ef0a9fd71ec5e1e4e04f9cDavid Vrabel data.blksz = blksz; 155052d81da6e6f0f8839ef6d5a46f215fc8cd99d5aStefan Nilsson XK /* Code in host drivers/fwk assumes that "blocks" always is >=1 */ 156052d81da6e6f0f8839ef6d5a46f215fc8cd99d5aStefan Nilsson XK data.blocks = blocks ? blocks : 1; 157112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; 158112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 159968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim left_size = data.blksz * data.blocks; 160968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim nents = (left_size - 1) / seg_size + 1; 161968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim if (nents > 1) { 162968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim if (sg_alloc_table(&sgtable, nents, GFP_KERNEL)) 163968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim return -ENOMEM; 164968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim 165968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim data.sg = sgtable.sgl; 166968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim data.sg_len = nents; 167968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim 168968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim for_each_sg(data.sg, sg_ptr, data.sg_len, i) { 169968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim sg_set_page(sg_ptr, virt_to_page(buf + (i * seg_size)), 170968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim min(seg_size, left_size), 171968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim offset_in_page(buf + (i * seg_size))); 172968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim left_size = left_size - seg_size; 173968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim } 174968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim } else { 175968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim data.sg = &sg; 176968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim data.sg_len = 1; 177968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim 178968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim sg_init_one(&sg, buf, left_size); 179968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim } 180112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 181112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman mmc_set_data_timeout(&data, card); 182112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 183112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman mmc_wait_for_req(card->host, &mrq); 184112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 185968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim if (nents > 1) 186968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim sg_free_table(&sgtable); 187968a64ea638bbd48839b41981ff50197f3412676Kyoungil Kim 188112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman if (cmd.error) 189112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman return cmd.error; 190112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman if (data.error) 191112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman return data.error; 192112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 193af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (mmc_host_is_spi(card->host)) { 194af51715079e7fb6b290e1881d63d815dc4de5011David Brownell /* host driver already reported errors */ 195af51715079e7fb6b290e1881d63d815dc4de5011David Brownell } else { 196af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (cmd.resp[0] & R5_ERROR) 197af51715079e7fb6b290e1881d63d815dc4de5011David Brownell return -EIO; 198af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (cmd.resp[0] & R5_FUNCTION_NUMBER) 199af51715079e7fb6b290e1881d63d815dc4de5011David Brownell return -EINVAL; 200af51715079e7fb6b290e1881d63d815dc4de5011David Brownell if (cmd.resp[0] & R5_OUT_OF_RANGE) 201af51715079e7fb6b290e1881d63d815dc4de5011David Brownell return -ERANGE; 202af51715079e7fb6b290e1881d63d815dc4de5011David Brownell } 203112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 204112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman return 0; 205112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman} 206112c9db91ee6bf19eca7cbb6854be3127381c229Pierre Ossman 207516a82422209e078345d0ca54b16793d7bfd4782Albert Herranzint sdio_reset(struct mmc_host *host) 208516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz{ 209516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz int ret; 210516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz u8 abort; 211516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz 212516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz /* SDIO Simplified Specification V2.0, 4.4 Reset for SDIO */ 213516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz 214516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz ret = mmc_io_rw_direct_host(host, 0, 0, SDIO_CCCR_ABORT, 0, &abort); 215516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz if (ret) 216516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz abort = 0x08; 217516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz else 218516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz abort |= 0x08; 219516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz 220516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz ret = mmc_io_rw_direct_host(host, 1, 0, SDIO_CCCR_ABORT, abort, NULL); 221516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz return ret; 222516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz} 223516a82422209e078345d0ca54b16793d7bfd4782Albert Herranz 224