Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b065c4dd authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "qup_i2c: Improve recovery from core and slave bad state"

parents e5e4bea9 ce2c83e3
Loading
Loading
Loading
Loading
+55 −121
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ enum {
	QUP_IN_FIFO_BASE        = 0x218,
	QUP_I2C_CLK_CTL         = 0x400,
	QUP_I2C_STATUS          = 0x404,
	QUP_I2C_MASTER_BUS_CLR  = 0x40C,
};

/* QUP States and reset values */
@@ -150,12 +151,6 @@ enum msm_i2c_state {
		(((reg_val) & ~(0x3 << 26)) | (((noise_rej_val) & 0x3) << 26))
static char const * const i2c_rsrcs[] = {"i2c_clk", "i2c_sda"};

static struct gpiomux_setting recovery_config = {
	.func = GPIOMUX_FUNC_GPIO,
	.drv = GPIOMUX_DRV_8MA,
	.pull = GPIOMUX_PULL_NONE,
};

/**
 * qup_i2c_clk_path_vote: data to use bus scaling driver for clock path vote
 *
@@ -896,113 +891,75 @@ qup_set_wr_mode(struct qup_i2c_dev *dev, int rem)
	return ret;
}


static void qup_i2c_recover_bus_busy(struct qup_i2c_dev *dev)
static int qup_i2c_reset(struct qup_i2c_dev *dev)
{
	int i;
	int gpio_clk;
	int gpio_dat;
	bool gpio_clk_status = false;
	uint32_t status = readl_relaxed(dev->base + QUP_I2C_STATUS);
	struct gpiomux_setting old_gpio_setting[ARRAY_SIZE(i2c_rsrcs)];
	int ret;

	if (dev->pdata->msm_i2c_config_gpio)
		return;
	/* sw reset */
	writel_relaxed(1, dev->base + QUP_SW_RESET);
	ret = qup_i2c_poll_state(dev, QUP_RESET_STATE, false);
	if (ret) {
		dev_err(dev->dev, "QUP Busy:Trying to recover\n");
		return ret;
	}

	if (!(status & (I2C_STATUS_BUS_ACTIVE)) ||
		(status & (I2C_STATUS_BUS_MASTER)))
		return;
	/* Initialize QUP registers */
	writel_relaxed(0, dev->base + QUP_CONFIG);
	writel_relaxed(QUP_OPERATIONAL_RESET, dev->base + QUP_OPERATIONAL);
	writel_relaxed(QUP_STATUS_ERROR_FLAGS, dev->base + QUP_ERROR_FLAGS_EN);

	gpio_clk = dev->i2c_gpios[0];
	gpio_dat = dev->i2c_gpios[1];
	writel_relaxed(I2C_MINI_CORE | I2C_N_VAL, dev->base + QUP_CONFIG);

	if ((gpio_clk == -1) && (gpio_dat == -1)) {
		dev_err(dev->dev, "Recovery failed due to undefined GPIO's\n");
		return;
	}
	/* Initialize I2C mini core registers */
	writel_relaxed(0, dev->base + QUP_I2C_CLK_CTL);
	writel_relaxed(QUP_I2C_STATUS_RESET, dev->base + QUP_I2C_STATUS);

	disable_irq(dev->err_irq);
	for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) {
		if (msm_gpiomux_write(dev->i2c_gpios[i], GPIOMUX_ACTIVE,
				&recovery_config, &old_gpio_setting[i])) {
			dev_err(dev->dev, "GPIO pins have no active setting\n");
			goto recovery_end;
		}
	/* Make sure QUP I2C core reset registers are written */
	wmb();
	return ret;
}

	dev_warn(dev->dev, "i2c_scl: %d, i2c_sda: %d\n",
		 gpio_get_value(gpio_clk), gpio_get_value(gpio_dat));
static int qup_i2c_recover_bus_busy(struct qup_i2c_dev *dev)
{
	int ret;
	u32 status;
	ulong min_sleep_usec;

	for (i = 0; i < 9; i++) {
		if (gpio_get_value(gpio_dat) && gpio_clk_status)
			break;
		gpio_direction_output(gpio_clk, 0);
		udelay(5);
		gpio_direction_output(gpio_dat, 0);
		udelay(5);
		gpio_direction_input(gpio_clk);
		udelay(5);
		if (!gpio_get_value(gpio_clk))
			udelay(20);
		if (!gpio_get_value(gpio_clk))
			usleep_range(10000, 10000);
		gpio_clk_status = gpio_get_value(gpio_clk);
		gpio_direction_input(gpio_dat);
		udelay(5);
	}

	/* Configure ALT funciton to QUP I2C*/
	for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) {
		msm_gpiomux_write(dev->i2c_gpios[i], GPIOMUX_ACTIVE,
				&old_gpio_setting[i], NULL);
	}
	disable_irq(dev->err_irq);
	dev_info(dev->dev, "Executing bus recovery procedure (9 clk pulse)\n");

	udelay(10);
	qup_i2c_reset(dev);

	status = readl_relaxed(dev->base + QUP_I2C_STATUS);
	if (!(status & I2C_STATUS_BUS_ACTIVE)) {
		dev_info(dev->dev, "Bus busy cleared after %d clock cycles, "
			 "status %x\n",
			 i, status);
	ret = qup_update_state(dev, QUP_RUN_STATE);
	if (ret < 0) {
		dev_err(dev->dev, "error: bus clear fail to set run state\n");
		goto recovery_end;
	}

	dev_warn(dev->dev, "Bus still busy, status %x\n", status);
	writel_relaxed(dev->clk_ctl, dev->base + QUP_I2C_CLK_CTL);

recovery_end:
	enable_irq(dev->err_irq);
}
	/* Make sure QUP I2C core reset registers are written */
	wmb();

static void i2c_qup_dump_gpio_conf(struct qup_i2c_dev *dev, int gpio_num,
		const char *gpio_name, enum msm_gpiomux_setting setting,
		const char *setting_name)
{
	struct gpiomux_setting cur_conf;
	if (msm_gpiomux_write(gpio_num, setting, NULL, &cur_conf)) {
		dev_err(dev->dev, "error reading %s-gpio:#%d %s setting\n",
					gpio_name, gpio_num, setting_name);
		return;
	}
	dev_info(dev->dev,
		"dump %s-gpio:#%d state:%s func:%d drv:%d pull:%d dir:%d\n",
		gpio_name, gpio_num, setting_name, cur_conf.func, cur_conf.drv,
		cur_conf.pull, cur_conf.dir);
	writel_relaxed(0x1, dev->base + QUP_I2C_MASTER_BUS_CLR);

	if (msm_gpiomux_write(gpio_num, setting, &cur_conf, NULL))
		dev_err(dev->dev, "error restoring %s-gpio:#%d %s setting\n",
					gpio_name, gpio_num, setting_name);
}
	/*
	 * wait for bus clear (9 clock pulse cycles) to complete.
	 * min_time = 9 clock *10  (1000% margin)
	 * max_time = 10* min_time
	 */
	min_sleep_usec =
		max_t(ulong, (9 * 10 * USEC_PER_SEC) / dev->pdata->clk_freq,
			100);
	usleep_range(min_sleep_usec, min_sleep_usec * 10);

static void i2c_qup_dump_gpios(struct qup_i2c_dev *dev)
{
	i2c_qup_dump_gpio_conf(dev, dev->i2c_gpios[0], i2c_rsrcs[0],
						GPIOMUX_ACTIVE, "active");
	i2c_qup_dump_gpio_conf(dev, dev->i2c_gpios[0], i2c_rsrcs[0],
						GPIOMUX_SUSPENDED, "suspended");
	i2c_qup_dump_gpio_conf(dev, dev->i2c_gpios[1], i2c_rsrcs[1],
						GPIOMUX_ACTIVE, "active");
	i2c_qup_dump_gpio_conf(dev, dev->i2c_gpios[1], i2c_rsrcs[1],
						GPIOMUX_SUSPENDED, "suspended");
	status = readl_relaxed(dev->base + QUP_I2C_STATUS);
	dev_info(dev->dev, "Bus recovery %s\n",
		(status & I2C_STATUS_BUS_ACTIVE) ? "fail" : "success");

recovery_end:
	enable_irq(dev->err_irq);
	return ret;
}

static int
@@ -1094,12 +1051,8 @@ qup_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
				dev->out_blk_sz, dev->out_fifo_sz);
	}

	writel_relaxed(1, dev->base + QUP_SW_RESET);
	ret = qup_i2c_poll_state(dev, QUP_RESET_STATE, false);
	if (ret) {
		dev_err(dev->dev, "QUP Busy:Trying to recover\n");
		goto out_err;
	}
	if (qup_i2c_reset(dev))
		dev_err(dev->dev, "warning: QUP reset before a xfer failed\n");

	if (dev->num_irqs == 3) {
		enable_irq(dev->in_irq);
@@ -1107,17 +1060,6 @@ qup_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
	}
	enable_irq(dev->err_irq);

	/* Initialize QUP registers */
	writel_relaxed(0, dev->base + QUP_CONFIG);
	writel_relaxed(QUP_OPERATIONAL_RESET, dev->base + QUP_OPERATIONAL);
	writel_relaxed(QUP_STATUS_ERROR_FLAGS, dev->base + QUP_ERROR_FLAGS_EN);

	writel_relaxed(I2C_MINI_CORE | I2C_N_VAL, dev->base + QUP_CONFIG);

	/* Initialize I2C mini core registers */
	writel_relaxed(0, dev->base + QUP_I2C_CLK_CTL);
	writel_relaxed(QUP_I2C_STATUS_RESET, dev->base + QUP_I2C_STATUS);

	while (rem) {
		bool filled = false;

@@ -1246,16 +1188,10 @@ qup_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
				dev_err(dev->dev,
					"Transaction timed out, SL-AD = 0x%x\n",
					dev->msg->addr);
				i2c_qup_dump_gpios(dev);

				dev_err(dev->dev, "I2C Status: %x\n", istatus);
				dev_err(dev->dev, "QUP Status: %x\n", qstatus);
				dev_err(dev->dev, "OP Flags: %x\n", op_flgs);
				writel_relaxed(1, dev->base + QUP_SW_RESET);
				/* Make sure that the write has gone through
				 * before returning from the function
				 */
				mb();
				ret = -ETIMEDOUT;
				goto out_err;
			}
@@ -1270,11 +1206,9 @@ timeout_err:
				} else if (dev->err < 0) {
					dev_err(dev->dev,
					"QUP data xfer error %d\n", dev->err);
					i2c_qup_dump_gpios(dev);
					ret = dev->err;
					goto out_err;
				} else if (dev->err > 0) {
					i2c_qup_dump_gpios(dev);
					/*
					 * ISR returns +ve error if error code
					 * is I2C related, e.g. unexpected start