Skip to content
Snippets Groups Projects
sil-sii8620.c 58.9 KiB
Newer Older
	sii8620_mt_read_devcap(ctx, false);
}

static void sii8620_irq_tdm(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_TRXINTH);
	u8 tdm = sii8620_readb(ctx, REG_TRXSTA2);

	if ((tdm & MSK_TDM_SYNCHRONIZED) == VAL_TDM_SYNCHRONIZED) {
		ctx->mode = CM_ECBUS_S;
		ctx->burst.rx_ack = 0;
		ctx->burst.r_size = SII8620_BURST_BUF_LEN;
		sii8620_burst_tx_rbuf_info(ctx, SII8620_BURST_BUF_LEN);
		sii8620_mt_read_devcap(ctx, true);
		sii8620_mt_set_cont(ctx, sii8620_got_xdevcap);
	} else {
		sii8620_write_seq_static(ctx,
			REG_MHL_PLL_CTL2, 0,
			REG_MHL_PLL_CTL2, BIT_MHL_PLL_CTL2_CLKDETECT_EN
		);
	}

	sii8620_write(ctx, REG_TRXINTH, stat);
}

static void sii8620_irq_block(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_EMSCINTR);

	if (stat & BIT_EMSCINTR_SPI_DVLD) {
		u8 bstat = sii8620_readb(ctx, REG_SPIBURSTSTAT);

		if (bstat & BIT_SPIBURSTSTAT_EMSC_NORMAL_MODE)
			sii8620_burst_receive(ctx);
	}

	sii8620_write(ctx, REG_EMSCINTR, stat);
}

static void sii8620_irq_ddc(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_INTR3);

	if (stat & BIT_DDC_CMD_DONE) {
		sii8620_write(ctx, REG_INTR3_MASK, 0);
		if (sii8620_is_mhl3(ctx))
			sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE),
					   MHL_INT_RC_FEAT_REQ);
		else
			sii8620_edid_read(ctx, 0);
	}
	sii8620_write(ctx, REG_INTR3, stat);
}

/* endian agnostic, non-volatile version of test_bit */
static bool sii8620_test_bit(unsigned int nr, const u8 *addr)
{
	return 1 & (addr[nr / BITS_PER_BYTE] >> (nr % BITS_PER_BYTE));
}

static irqreturn_t sii8620_irq_thread(int irq, void *data)
{
	static const struct {
		int bit;
		void (*handler)(struct sii8620 *ctx);
	} irq_vec[] = {
		{ BIT_FAST_INTR_STAT_DISC, sii8620_irq_disc },
		{ BIT_FAST_INTR_STAT_G2WB, sii8620_irq_g2wb },
		{ BIT_FAST_INTR_STAT_COC, sii8620_irq_coc },
		{ BIT_FAST_INTR_STAT_TDM, sii8620_irq_tdm },
		{ BIT_FAST_INTR_STAT_MSC, sii8620_irq_msc },
		{ BIT_FAST_INTR_STAT_MERR, sii8620_irq_merr },
		{ BIT_FAST_INTR_STAT_BLOCK, sii8620_irq_block },
		{ BIT_FAST_INTR_STAT_EDID, sii8620_irq_edid },
		{ BIT_FAST_INTR_STAT_DDC, sii8620_irq_ddc },
		{ BIT_FAST_INTR_STAT_SCDT, sii8620_irq_scdt },
		{ BIT_FAST_INTR_STAT_INFR, sii8620_irq_infr },
	};
	struct sii8620 *ctx = data;
	u8 stats[LEN_FAST_INTR_STAT];
	int i, ret;

	mutex_lock(&ctx->lock);

	sii8620_read_buf(ctx, REG_FAST_INTR_STAT, stats, ARRAY_SIZE(stats));
	for (i = 0; i < ARRAY_SIZE(irq_vec); ++i)
		if (sii8620_test_bit(irq_vec[i].bit, stats))
			irq_vec[i].handler(ctx);

	sii8620_mt_work(ctx);

	ret = sii8620_clear_error(ctx);
	if (ret) {
		dev_err(ctx->dev, "Error during IRQ handling, %d.\n", ret);
		sii8620_mhl_disconnected(ctx);
	}
	mutex_unlock(&ctx->lock);

	return IRQ_HANDLED;
}

static void sii8620_cable_in(struct sii8620 *ctx)
{
	struct device *dev = ctx->dev;
	u8 ver[5];
	int ret;

	ret = sii8620_hw_on(ctx);
	if (ret) {
		dev_err(dev, "Error powering on, %d.\n", ret);
		return;
	}
	sii8620_hw_reset(ctx);

	sii8620_read_buf(ctx, REG_VND_IDL, ver, ARRAY_SIZE(ver));
	ret = sii8620_clear_error(ctx);
	if (ret) {
		dev_err(dev, "Error accessing I2C bus, %d.\n", ret);
		return;
	}

	dev_info(dev, "ChipID %02x%02x:%02x%02x rev %02x.\n", ver[1], ver[0],
		 ver[3], ver[2], ver[4]);

	sii8620_write(ctx, REG_DPD,
		      BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | BIT_DPD_OSC_EN);

	sii8620_xtal_set_rate(ctx);
	sii8620_disconnect(ctx);

	sii8620_write_seq_static(ctx,
		REG_MHL_CBUS_CTL0, VAL_MHL_CBUS_CTL0_CBUS_DRV_SEL_STRONG
			| VAL_MHL_CBUS_CTL0_CBUS_RGND_VBIAS_734,
		REG_MHL_CBUS_CTL1, VAL_MHL_CBUS_CTL1_1115_OHM,
		REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | BIT_DPD_OSC_EN,
	);

	ret = sii8620_clear_error(ctx);
	if (ret) {
		dev_err(dev, "Error accessing I2C bus, %d.\n", ret);
		return;
	}

	enable_irq(to_i2c_client(ctx->dev)->irq);
}

static void sii8620_init_rcp_input_dev(struct sii8620 *ctx)
{
	struct rc_dev *rc_dev;
	int ret;

	rc_dev = rc_allocate_device(RC_DRIVER_SCANCODE);
	if (!rc_dev) {
		dev_err(ctx->dev, "Failed to allocate RC device\n");
		ctx->error = -ENOMEM;
		return;
	}

	rc_dev->input_phys = "sii8620/input0";
	rc_dev->input_id.bustype = BUS_VIRTUAL;
	rc_dev->map_name = RC_MAP_CEC;
	rc_dev->allowed_protocols = RC_PROTO_BIT_CEC;
	rc_dev->driver_name = "sii8620";
	rc_dev->device_name = "sii8620";

	ret = rc_register_device(rc_dev);

	if (ret) {
		dev_err(ctx->dev, "Failed to register RC device\n");
		ctx->error = ret;
		rc_free_device(ctx->rc_dev);
		return;
	}
	ctx->rc_dev = rc_dev;
}

static void sii8620_cable_out(struct sii8620 *ctx)
{
	disable_irq(to_i2c_client(ctx->dev)->irq);
	sii8620_hw_off(ctx);
}

static void sii8620_extcon_work(struct work_struct *work)
{
	struct sii8620 *ctx =
		container_of(work, struct sii8620, extcon_wq);
	int state = extcon_get_state(ctx->extcon, EXTCON_DISP_MHL);

	if (state == ctx->cable_state)
		return;

	ctx->cable_state = state;

	if (state > 0)
		sii8620_cable_in(ctx);
	else
		sii8620_cable_out(ctx);
}

static int sii8620_extcon_notifier(struct notifier_block *self,
			unsigned long event, void *ptr)
{
	struct sii8620 *ctx =
		container_of(self, struct sii8620, extcon_nb);

	schedule_work(&ctx->extcon_wq);

	return NOTIFY_DONE;
}

static int sii8620_extcon_init(struct sii8620 *ctx)
{
	struct extcon_dev *edev;
	struct device_node *musb, *muic;
	int ret;

	/* get micro-USB connector node */
	musb = of_graph_get_remote_node(ctx->dev->of_node, 1, -1);
	/* next get micro-USB Interface Controller node */
	muic = of_get_next_parent(musb);

	if (!muic) {
		dev_info(ctx->dev, "no extcon found, switching to 'always on' mode\n");
		return 0;
	}

	edev = extcon_find_edev_by_node(muic);
	of_node_put(muic);
	if (IS_ERR(edev)) {
		if (PTR_ERR(edev) == -EPROBE_DEFER)
			return -EPROBE_DEFER;
		dev_err(ctx->dev, "Invalid or missing extcon\n");
		return PTR_ERR(edev);
	}

	ctx->extcon = edev;
	ctx->extcon_nb.notifier_call = sii8620_extcon_notifier;
	INIT_WORK(&ctx->extcon_wq, sii8620_extcon_work);
	ret = extcon_register_notifier(edev, EXTCON_DISP_MHL, &ctx->extcon_nb);
	if (ret) {
		dev_err(ctx->dev, "failed to register notifier for MHL\n");
		return ret;
	}

	return 0;
}

static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge)
{
	return container_of(bridge, struct sii8620, bridge);
}

static int sii8620_attach(struct drm_bridge *bridge)
{
	struct sii8620 *ctx = bridge_to_sii8620(bridge);

	sii8620_init_rcp_input_dev(ctx);

	return sii8620_clear_error(ctx);
}

static void sii8620_detach(struct drm_bridge *bridge)
{
	struct sii8620 *ctx = bridge_to_sii8620(bridge);

	rc_unregister_device(ctx->rc_dev);
}

static enum drm_mode_status sii8620_mode_valid(struct drm_bridge *bridge,
					 const struct drm_display_mode *mode)
{
	struct sii8620 *ctx = bridge_to_sii8620(bridge);
	bool can_pack = ctx->devcap[MHL_DCAP_VID_LINK_MODE] &
			MHL_DCAP_VID_LINK_PPIXEL;
	unsigned int max_pclk = sii8620_is_mhl3(ctx) ? MHL3_MAX_LCLK :
						       MHL1_MAX_LCLK;
	max_pclk /= can_pack ? 2 : 3;

	return (mode->clock > max_pclk) ? MODE_CLOCK_HIGH : MODE_OK;
}

static bool sii8620_mode_fixup(struct drm_bridge *bridge,
			       const struct drm_display_mode *mode,
			       struct drm_display_mode *adjusted_mode)
{
	struct sii8620 *ctx = bridge_to_sii8620(bridge);
	int max_lclk;
	bool ret = true;
	mutex_lock(&ctx->lock);
	max_lclk = sii8620_is_mhl3(ctx) ? MHL3_MAX_LCLK : MHL1_MAX_LCLK;
	if (max_lclk > 3 * adjusted_mode->clock) {
		ctx->use_packed_pixel = 0;
		goto end;
	}
	if ((ctx->devcap[MHL_DCAP_VID_LINK_MODE] & MHL_DCAP_VID_LINK_PPIXEL) &&
	    max_lclk > 2 * adjusted_mode->clock) {
		ctx->use_packed_pixel = 1;
		goto end;
	}
	ret = false;
end:
	if (ret) {
		u8 vic = drm_match_cea_mode(adjusted_mode);

		if (!vic) {
			union hdmi_infoframe frm;
			u8 mhl_vic[] = { 0, 95, 94, 93, 98 };

			/* FIXME: We need the connector here */
			drm_hdmi_vendor_infoframe_from_display_mode(
				&frm.vendor.hdmi, NULL, adjusted_mode);
			vic = frm.vendor.hdmi.vic;
			if (vic >= ARRAY_SIZE(mhl_vic))
				vic = 0;
			vic = mhl_vic[vic];
		}
		ctx->video_code = vic;
		ctx->pixel_clock = adjusted_mode->clock;
	}
	mutex_unlock(&ctx->lock);
	return ret;
}

static const struct drm_bridge_funcs sii8620_bridge_funcs = {
	.attach = sii8620_attach,
	.detach = sii8620_detach,
	.mode_fixup = sii8620_mode_fixup,
	.mode_valid = sii8620_mode_valid,
};

static int sii8620_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	struct device *dev = &client->dev;
	struct sii8620 *ctx;
	int ret;

	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
	if (!ctx)
		return -ENOMEM;

	ctx->dev = dev;
	mutex_init(&ctx->lock);
	INIT_LIST_HEAD(&ctx->mt_queue);

	ctx->clk_xtal = devm_clk_get(dev, "xtal");
	if (IS_ERR(ctx->clk_xtal)) {
		dev_err(dev, "failed to get xtal clock from DT\n");
		return PTR_ERR(ctx->clk_xtal);
	}

	if (!client->irq) {
		dev_err(dev, "no irq provided\n");
		return -EINVAL;
	}
	irq_set_status_flags(client->irq, IRQ_NOAUTOEN);
	ret = devm_request_threaded_irq(dev, client->irq, NULL,
					sii8620_irq_thread,
					IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
					"sii8620", ctx);
	if (ret < 0) {
		dev_err(dev, "failed to install IRQ handler\n");
		return ret;
	}

	ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
	if (IS_ERR(ctx->gpio_reset)) {
		dev_err(dev, "failed to get reset gpio from DT\n");
		return PTR_ERR(ctx->gpio_reset);
	}

	ctx->supplies[0].supply = "cvcc10";
	ctx->supplies[1].supply = "iovcc18";
	ret = devm_regulator_bulk_get(dev, 2, ctx->supplies);
	if (ret)
		return ret;

	ret = sii8620_extcon_init(ctx);
	if (ret < 0) {
		dev_err(ctx->dev, "failed to initialize EXTCON\n");
		return ret;
	}

	i2c_set_clientdata(client, ctx);

	ctx->bridge.funcs = &sii8620_bridge_funcs;
	ctx->bridge.of_node = dev->of_node;
	drm_bridge_add(&ctx->bridge);

	if (!ctx->extcon)
		sii8620_cable_in(ctx);

	return 0;
}

static int sii8620_remove(struct i2c_client *client)
{
	struct sii8620 *ctx = i2c_get_clientdata(client);

	if (ctx->extcon) {
		extcon_unregister_notifier(ctx->extcon, EXTCON_DISP_MHL,
					   &ctx->extcon_nb);
		flush_work(&ctx->extcon_wq);
		if (ctx->cable_state > 0)
			sii8620_cable_out(ctx);
	} else {
		sii8620_cable_out(ctx);
	}
	drm_bridge_remove(&ctx->bridge);

	return 0;
}

static const struct of_device_id sii8620_dt_match[] = {
	{ .compatible = "sil,sii8620" },
	{ },
};
MODULE_DEVICE_TABLE(of, sii8620_dt_match);

static const struct i2c_device_id sii8620_id[] = {
	{ "sii8620", 0 },
	{ },
};

MODULE_DEVICE_TABLE(i2c, sii8620_id);
static struct i2c_driver sii8620_driver = {
	.driver = {
		.name	= "sii8620",
		.of_match_table = of_match_ptr(sii8620_dt_match),
	},
	.probe		= sii8620_probe,
	.remove		= sii8620_remove,
	.id_table = sii8620_id,
};

module_i2c_driver(sii8620_driver);
MODULE_LICENSE("GPL v2");