mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	mmc: renesas_sdhi: add eMMC HS400 mode support
This patch adds processing for selecting HS400 mode. Signed-off-by: Masaharu Hayakawa <masaharu.hayakawa.ry@renesas.com> Signed-off-by: Simon Horman <horms+renesas@verge.net.au> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
		
							parent
							
								
									db924bba47
								
							
						
					
					
						commit
						26eb2607fa
					
				
					 3 changed files with 138 additions and 26 deletions
				
			
		| 
						 | 
				
			
			@ -212,6 +212,7 @@ static int renesas_sdhi_start_signal_voltage_switch(struct mmc_host *mmc,
 | 
			
		|||
#define SH_MOBILE_SDHI_SCC_CKSEL	0x006
 | 
			
		||||
#define SH_MOBILE_SDHI_SCC_RVSCNTL	0x008
 | 
			
		||||
#define SH_MOBILE_SDHI_SCC_RVSREQ	0x00A
 | 
			
		||||
#define SH_MOBILE_SDHI_SCC_TMPPORT2	0x00E
 | 
			
		||||
 | 
			
		||||
/* Definitions for values the SH_MOBILE_SDHI_SCC_DTCNTL register */
 | 
			
		||||
#define SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN		BIT(0)
 | 
			
		||||
| 
						 | 
				
			
			@ -224,6 +225,9 @@ static int renesas_sdhi_start_signal_voltage_switch(struct mmc_host *mmc,
 | 
			
		|||
#define SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN	BIT(0)
 | 
			
		||||
/* Definitions for values the SH_MOBILE_SDHI_SCC_RVSREQ register */
 | 
			
		||||
#define SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR	BIT(2)
 | 
			
		||||
/* Definitions for values the SH_MOBILE_SDHI_SCC_TMPPORT2 register */
 | 
			
		||||
#define SH_MOBILE_SDHI_SCC_TMPPORT2_HS400OSEL	BIT(4)
 | 
			
		||||
#define SH_MOBILE_SDHI_SCC_TMPPORT2_HS400EN	BIT(31)
 | 
			
		||||
 | 
			
		||||
static inline u32 sd_scc_read32(struct tmio_mmc_host *host,
 | 
			
		||||
				struct renesas_sdhi *priv, int addr)
 | 
			
		||||
| 
						 | 
				
			
			@ -244,33 +248,30 @@ static unsigned int renesas_sdhi_init_tuning(struct tmio_mmc_host *host)
 | 
			
		|||
 | 
			
		||||
	priv = host_to_priv(host);
 | 
			
		||||
 | 
			
		||||
	/* set sampling clock selection range */
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL,
 | 
			
		||||
		       0x8 << SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT);
 | 
			
		||||
 | 
			
		||||
	/* Initialize SCC */
 | 
			
		||||
	sd_ctrl_write32_as_16_and_16(host, CTL_STATUS, 0x0);
 | 
			
		||||
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL,
 | 
			
		||||
		       SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN |
 | 
			
		||||
		       sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL));
 | 
			
		||||
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
 | 
			
		||||
	/* set sampling clock selection range */
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL,
 | 
			
		||||
		       SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN |
 | 
			
		||||
		       0x8 << SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT);
 | 
			
		||||
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL,
 | 
			
		||||
		       SH_MOBILE_SDHI_SCC_CKSEL_DTSEL |
 | 
			
		||||
		       sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL));
 | 
			
		||||
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
 | 
			
		||||
		       ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &
 | 
			
		||||
		       sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL));
 | 
			
		||||
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DT2FF, priv->scc_tappos);
 | 
			
		||||
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
 | 
			
		||||
	/* Read TAPNUM */
 | 
			
		||||
	return (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL) >>
 | 
			
		||||
		SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT) &
 | 
			
		||||
| 
						 | 
				
			
			@ -286,13 +287,95 @@ static void renesas_sdhi_prepare_tuning(struct tmio_mmc_host *host,
 | 
			
		|||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, tap);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void renesas_sdhi_hs400_complete(struct tmio_mmc_host *host)
 | 
			
		||||
{
 | 
			
		||||
	struct renesas_sdhi *priv = host_to_priv(host);
 | 
			
		||||
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
 | 
			
		||||
		sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
 | 
			
		||||
	/* Set HS400 mode */
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SDIF_MODE, 0x0001 |
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SDIF_MODE));
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TMPPORT2,
 | 
			
		||||
		       (SH_MOBILE_SDHI_SCC_TMPPORT2_HS400EN |
 | 
			
		||||
			SH_MOBILE_SDHI_SCC_TMPPORT2_HS400OSEL) |
 | 
			
		||||
			sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_TMPPORT2));
 | 
			
		||||
 | 
			
		||||
	/* Set the sampling clock selection range of HS400 mode */
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL,
 | 
			
		||||
		       SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN |
 | 
			
		||||
		       0x4 << SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	if (host->pdata->flags & TMIO_MMC_HAVE_4TAP_HS400)
 | 
			
		||||
		sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET,
 | 
			
		||||
			       host->tap_set / 2);
 | 
			
		||||
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL,
 | 
			
		||||
		       SH_MOBILE_SDHI_SCC_CKSEL_DTSEL |
 | 
			
		||||
		       sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL));
 | 
			
		||||
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void renesas_sdhi_reset_scc(struct tmio_mmc_host *host,
 | 
			
		||||
				   struct renesas_sdhi *priv)
 | 
			
		||||
{
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL,
 | 
			
		||||
		       ~SH_MOBILE_SDHI_SCC_CKSEL_DTSEL &
 | 
			
		||||
		       sd_scc_read32(host, priv,
 | 
			
		||||
				     SH_MOBILE_SDHI_SCC_CKSEL));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void renesas_sdhi_disable_scc(struct tmio_mmc_host *host)
 | 
			
		||||
{
 | 
			
		||||
	struct renesas_sdhi *priv = host_to_priv(host);
 | 
			
		||||
 | 
			
		||||
	renesas_sdhi_reset_scc(host, priv);
 | 
			
		||||
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL,
 | 
			
		||||
		       ~SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN &
 | 
			
		||||
		       sd_scc_read32(host, priv,
 | 
			
		||||
				     SH_MOBILE_SDHI_SCC_DTCNTL));
 | 
			
		||||
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void renesas_sdhi_reset_hs400_mode(struct tmio_mmc_host *host,
 | 
			
		||||
					  struct renesas_sdhi *priv)
 | 
			
		||||
{
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
 | 
			
		||||
	/* Reset HS400 mode */
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SDIF_MODE, ~0x0001 &
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SDIF_MODE));
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TMPPORT2,
 | 
			
		||||
		       ~(SH_MOBILE_SDHI_SCC_TMPPORT2_HS400EN |
 | 
			
		||||
			 SH_MOBILE_SDHI_SCC_TMPPORT2_HS400OSEL) &
 | 
			
		||||
			sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_TMPPORT2));
 | 
			
		||||
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void renesas_sdhi_prepare_hs400_tuning(struct tmio_mmc_host *host)
 | 
			
		||||
{
 | 
			
		||||
	renesas_sdhi_reset_hs400_mode(host, host_to_priv(host));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define SH_MOBILE_SDHI_MAX_TAP 3
 | 
			
		||||
 | 
			
		||||
static int renesas_sdhi_select_tuning(struct tmio_mmc_host *host)
 | 
			
		||||
{
 | 
			
		||||
	struct renesas_sdhi *priv = host_to_priv(host);
 | 
			
		||||
	unsigned long tap_cnt;  /* counter of tuning success */
 | 
			
		||||
	unsigned long tap_set;  /* tap position */
 | 
			
		||||
	unsigned long tap_start;/* start position of tuning success */
 | 
			
		||||
	unsigned long tap_end;  /* end position of tuning success */
 | 
			
		||||
	unsigned long ntap;     /* temporary counter of tuning success */
 | 
			
		||||
| 
						 | 
				
			
			@ -330,12 +413,12 @@ static int renesas_sdhi_select_tuning(struct tmio_mmc_host *host)
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	if (tap_cnt >= SH_MOBILE_SDHI_MAX_TAP)
 | 
			
		||||
		tap_set = (tap_start + tap_end) / 2 % host->tap_num;
 | 
			
		||||
		host->tap_set = (tap_start + tap_end) / 2 % host->tap_num;
 | 
			
		||||
	else
 | 
			
		||||
		return -EIO;
 | 
			
		||||
 | 
			
		||||
	/* Set SCC */
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, tap_set);
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, host->tap_set);
 | 
			
		||||
 | 
			
		||||
	/* Enable auto re-tuning */
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
 | 
			
		||||
| 
						 | 
				
			
			@ -368,13 +451,8 @@ static void renesas_sdhi_hw_reset(struct tmio_mmc_host *host)
 | 
			
		|||
 | 
			
		||||
	priv = host_to_priv(host);
 | 
			
		||||
 | 
			
		||||
	/* Reset SCC */
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
 | 
			
		||||
	sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL,
 | 
			
		||||
		       ~SH_MOBILE_SDHI_SCC_CKSEL_DTSEL &
 | 
			
		||||
		       sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL));
 | 
			
		||||
	renesas_sdhi_reset_scc(host, priv);
 | 
			
		||||
	renesas_sdhi_reset_hs400_mode(host, priv);
 | 
			
		||||
 | 
			
		||||
	sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
 | 
			
		||||
			sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
 | 
			
		||||
| 
						 | 
				
			
			@ -592,7 +670,8 @@ int renesas_sdhi_probe(struct platform_device *pdev,
 | 
			
		|||
	/* Enable tuning iff we have an SCC and a supported mode */
 | 
			
		||||
	if (of_data && of_data->scc_offset &&
 | 
			
		||||
	    (host->mmc->caps & MMC_CAP_UHS_SDR104 ||
 | 
			
		||||
	     host->mmc->caps2 & MMC_CAP2_HS200_1_8V_SDR)) {
 | 
			
		||||
	     host->mmc->caps2 & (MMC_CAP2_HS200_1_8V_SDR |
 | 
			
		||||
				 MMC_CAP2_HS400_1_8V))) {
 | 
			
		||||
		const struct renesas_sdhi_scc *taps = of_data->taps;
 | 
			
		||||
		bool hit = false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -616,6 +695,10 @@ int renesas_sdhi_probe(struct platform_device *pdev,
 | 
			
		|||
		host->select_tuning = renesas_sdhi_select_tuning;
 | 
			
		||||
		host->check_scc_error = renesas_sdhi_check_scc_error;
 | 
			
		||||
		host->hw_reset = renesas_sdhi_hw_reset;
 | 
			
		||||
		host->prepare_hs400_tuning =
 | 
			
		||||
			renesas_sdhi_prepare_hs400_tuning;
 | 
			
		||||
		host->hs400_downgrade = renesas_sdhi_disable_scc;
 | 
			
		||||
		host->hs400_complete = renesas_sdhi_hs400_complete;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i = 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -82,6 +82,22 @@ static struct renesas_sdhi_scc rcar_gen3_scc_taps[] = {
 | 
			
		|||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct renesas_sdhi_of_data of_rcar_r8a7795_compatible = {
 | 
			
		||||
	.tmio_flags	= TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_CLK_ACTUAL |
 | 
			
		||||
			  TMIO_MMC_HAVE_CBSY | TMIO_MMC_MIN_RCAR2 |
 | 
			
		||||
			  TMIO_MMC_HAVE_4TAP_HS400,
 | 
			
		||||
	.capabilities	= MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ |
 | 
			
		||||
			  MMC_CAP_CMD23,
 | 
			
		||||
	.capabilities2	= MMC_CAP2_NO_WRITE_PROTECT,
 | 
			
		||||
	.bus_shift	= 2,
 | 
			
		||||
	.scc_offset	= 0x1000,
 | 
			
		||||
	.taps		= rcar_gen3_scc_taps,
 | 
			
		||||
	.taps_num	= ARRAY_SIZE(rcar_gen3_scc_taps),
 | 
			
		||||
	/* DMAC can handle 0xffffffff blk count but only 1 segment */
 | 
			
		||||
	.max_blk_count	= 0xffffffff,
 | 
			
		||||
	.max_segs	= 1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct renesas_sdhi_of_data of_rcar_gen3_compatible = {
 | 
			
		||||
	.tmio_flags	= TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_CLK_ACTUAL |
 | 
			
		||||
			  TMIO_MMC_HAVE_CBSY | TMIO_MMC_MIN_RCAR2,
 | 
			
		||||
| 
						 | 
				
			
			@ -98,8 +114,8 @@ static const struct renesas_sdhi_of_data of_rcar_gen3_compatible = {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
static const struct of_device_id renesas_sdhi_internal_dmac_of_match[] = {
 | 
			
		||||
	{ .compatible = "renesas,sdhi-r8a7795", .data = &of_rcar_gen3_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,sdhi-r8a7796", .data = &of_rcar_gen3_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,sdhi-r8a7795", .data = &of_rcar_r8a7795_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,sdhi-r8a7796", .data = &of_rcar_r8a7795_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,rcar-gen3-sdhi", .data = &of_rcar_gen3_compatible, },
 | 
			
		||||
	{},
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,6 +78,19 @@ static struct renesas_sdhi_scc rcar_gen3_scc_taps[] = {
 | 
			
		|||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct renesas_sdhi_of_data of_rcar_r8a7795_compatible = {
 | 
			
		||||
	.tmio_flags	= TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_CLK_ACTUAL |
 | 
			
		||||
			  TMIO_MMC_HAVE_CBSY | TMIO_MMC_MIN_RCAR2 |
 | 
			
		||||
			  TMIO_MMC_HAVE_4TAP_HS400,
 | 
			
		||||
	.capabilities	= MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ |
 | 
			
		||||
			  MMC_CAP_CMD23,
 | 
			
		||||
	.capabilities2	= MMC_CAP2_NO_WRITE_PROTECT,
 | 
			
		||||
	.bus_shift	= 2,
 | 
			
		||||
	.scc_offset	= 0x1000,
 | 
			
		||||
	.taps		= rcar_gen3_scc_taps,
 | 
			
		||||
	.taps_num	= ARRAY_SIZE(rcar_gen3_scc_taps),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct renesas_sdhi_of_data of_rcar_gen3_compatible = {
 | 
			
		||||
	.tmio_flags	= TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_CLK_ACTUAL |
 | 
			
		||||
			  TMIO_MMC_HAVE_CBSY | TMIO_MMC_MIN_RCAR2,
 | 
			
		||||
| 
						 | 
				
			
			@ -104,8 +117,8 @@ static const struct of_device_id renesas_sdhi_sys_dmac_of_match[] = {
 | 
			
		|||
	{ .compatible = "renesas,sdhi-r8a7792", .data = &of_rcar_gen2_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,sdhi-r8a7793", .data = &of_rcar_gen2_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,sdhi-r8a7794", .data = &of_rcar_gen2_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,sdhi-r8a7795", .data = &of_rcar_gen3_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,sdhi-r8a7796", .data = &of_rcar_gen3_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,sdhi-r8a7795", .data = &of_rcar_r8a7795_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,sdhi-r8a7796", .data = &of_rcar_r8a7795_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,rcar-gen1-sdhi", .data = &of_rcar_gen1_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,rcar-gen2-sdhi", .data = &of_rcar_gen2_compatible, },
 | 
			
		||||
	{ .compatible = "renesas,rcar-gen3-sdhi", .data = &of_rcar_gen3_compatible, },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue