Skip to content
Snippets Groups Projects
sil-sii8620.c 56.9 KiB
Newer Older
	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 },
	};
	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_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;

	if (!IS_ENABLED(CONFIG_RC_CORE))
		return;

	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;
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,
			  enum drm_bridge_attach_flags flags)
{
	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);

	if (!IS_ENABLED(CONFIG_RC_CORE))
		return;

	rc_unregister_device(ctx->rc_dev);
}

static int sii8620_is_packing_required(struct sii8620 *ctx,
				       const struct drm_display_mode *mode)
{
	int max_pclk, max_pclk_pp_mode;

	if (sii8620_is_mhl3(ctx)) {
		max_pclk = MHL3_MAX_PCLK;
		max_pclk_pp_mode = MHL3_MAX_PCLK_PP_MODE;
	} else {
		max_pclk = MHL1_MAX_PCLK;
		max_pclk_pp_mode = MHL1_MAX_PCLK_PP_MODE;
	}

	if (mode->clock < max_pclk)
		return 0;
	else if (mode->clock < max_pclk_pp_mode)
		return 1;
	else
		return -1;
}

static enum drm_mode_status sii8620_mode_valid(struct drm_bridge *bridge,
					 const struct drm_display_info *info,
					 const struct drm_display_mode *mode)
{
	struct sii8620 *ctx = bridge_to_sii8620(bridge);
	int pack_required = sii8620_is_packing_required(ctx, mode);
	bool can_pack = ctx->devcap[MHL_DCAP_VID_LINK_MODE] &
			MHL_DCAP_VID_LINK_PPIXEL;

	switch (pack_required) {
	case 0:
		return MODE_OK;
	case 1:
		return (can_pack) ? MODE_OK : MODE_CLOCK_HIGH;
	default:
		return MODE_CLOCK_HIGH;
	}
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);

	mutex_lock(&ctx->lock);
	ctx->use_packed_pixel = sii8620_is_packing_required(ctx, adjusted_mode);

	mutex_unlock(&ctx->lock);
}

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)
{
	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))
		return dev_err_probe(dev, PTR_ERR(ctx->clk_xtal),
				     "failed to get xtal clock from DT\n");

	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)
		return dev_err_probe(dev, ret,
				     "failed to install IRQ handler\n");

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

	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);
static void 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);
}

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 = sii8620_dt_match,
	.probe		= sii8620_probe,
	.remove		= sii8620_remove,
	.id_table = sii8620_id,
};

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