xref: /linux/drivers/extcon/extcon-max3355.c (revision 36ec807b627b4c0a0a382f0ae48eac7187d14b2b)
1ac1dc6b2SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
208a0a4f9SSergei Shtylyov /*
308a0a4f9SSergei Shtylyov  * Maxim Integrated MAX3355 USB OTG chip extcon driver
408a0a4f9SSergei Shtylyov  *
508a0a4f9SSergei Shtylyov  * Copyright (C)  2014-2015 Cogent Embedded, Inc.
608a0a4f9SSergei Shtylyov  * Author: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>
708a0a4f9SSergei Shtylyov  */
808a0a4f9SSergei Shtylyov 
9176aa360SChanwoo Choi #include <linux/extcon-provider.h>
1008a0a4f9SSergei Shtylyov #include <linux/gpio/consumer.h>
1108a0a4f9SSergei Shtylyov #include <linux/interrupt.h>
1208a0a4f9SSergei Shtylyov #include <linux/module.h>
13c9c159b2SArnd Bergmann #include <linux/mod_devicetable.h>
1408a0a4f9SSergei Shtylyov #include <linux/platform_device.h>
1508a0a4f9SSergei Shtylyov 
1608a0a4f9SSergei Shtylyov struct max3355_data {
1708a0a4f9SSergei Shtylyov 	struct extcon_dev *edev;
1808a0a4f9SSergei Shtylyov 	struct gpio_desc *id_gpiod;
1908a0a4f9SSergei Shtylyov 	struct gpio_desc *shdn_gpiod;
2008a0a4f9SSergei Shtylyov };
2108a0a4f9SSergei Shtylyov 
2208a0a4f9SSergei Shtylyov static const unsigned int max3355_cable[] = {
2308a0a4f9SSergei Shtylyov 	EXTCON_USB,
2408a0a4f9SSergei Shtylyov 	EXTCON_USB_HOST,
2508a0a4f9SSergei Shtylyov 	EXTCON_NONE,
2608a0a4f9SSergei Shtylyov };
2708a0a4f9SSergei Shtylyov 
2808a0a4f9SSergei Shtylyov static irqreturn_t max3355_id_irq(int irq, void *dev_id)
2908a0a4f9SSergei Shtylyov {
3008a0a4f9SSergei Shtylyov 	struct max3355_data *data = dev_id;
3108a0a4f9SSergei Shtylyov 	int id = gpiod_get_value_cansleep(data->id_gpiod);
3208a0a4f9SSergei Shtylyov 
3308a0a4f9SSergei Shtylyov 	if (id) {
3408a0a4f9SSergei Shtylyov 		/*
3508a0a4f9SSergei Shtylyov 		 * ID = 1 means USB HOST cable detached.
3608a0a4f9SSergei Shtylyov 		 * As we don't have event for USB peripheral cable attached,
3708a0a4f9SSergei Shtylyov 		 * we simulate USB peripheral attach here.
3808a0a4f9SSergei Shtylyov 		 */
398670b459SChanwoo Choi 		extcon_set_state_sync(data->edev, EXTCON_USB_HOST, false);
408670b459SChanwoo Choi 		extcon_set_state_sync(data->edev, EXTCON_USB, true);
4108a0a4f9SSergei Shtylyov 	} else {
4208a0a4f9SSergei Shtylyov 		/*
4308a0a4f9SSergei Shtylyov 		 * ID = 0 means USB HOST cable attached.
4408a0a4f9SSergei Shtylyov 		 * As we don't have event for USB peripheral cable detached,
4508a0a4f9SSergei Shtylyov 		 * we simulate USB peripheral detach here.
4608a0a4f9SSergei Shtylyov 		 */
478670b459SChanwoo Choi 		extcon_set_state_sync(data->edev, EXTCON_USB, false);
488670b459SChanwoo Choi 		extcon_set_state_sync(data->edev, EXTCON_USB_HOST, true);
4908a0a4f9SSergei Shtylyov 	}
5008a0a4f9SSergei Shtylyov 
5108a0a4f9SSergei Shtylyov 	return IRQ_HANDLED;
5208a0a4f9SSergei Shtylyov }
5308a0a4f9SSergei Shtylyov 
5408a0a4f9SSergei Shtylyov static int max3355_probe(struct platform_device *pdev)
5508a0a4f9SSergei Shtylyov {
5608a0a4f9SSergei Shtylyov 	struct max3355_data *data;
5708a0a4f9SSergei Shtylyov 	struct gpio_desc *gpiod;
5808a0a4f9SSergei Shtylyov 	int irq, err;
5908a0a4f9SSergei Shtylyov 
6008a0a4f9SSergei Shtylyov 	data = devm_kzalloc(&pdev->dev, sizeof(struct max3355_data),
6108a0a4f9SSergei Shtylyov 			    GFP_KERNEL);
6208a0a4f9SSergei Shtylyov 	if (!data)
6308a0a4f9SSergei Shtylyov 		return -ENOMEM;
6408a0a4f9SSergei Shtylyov 
6508a0a4f9SSergei Shtylyov 	gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN);
6608a0a4f9SSergei Shtylyov 	if (IS_ERR(gpiod)) {
6708a0a4f9SSergei Shtylyov 		dev_err(&pdev->dev, "failed to get ID_OUT GPIO\n");
6808a0a4f9SSergei Shtylyov 		return PTR_ERR(gpiod);
6908a0a4f9SSergei Shtylyov 	}
7008a0a4f9SSergei Shtylyov 	data->id_gpiod = gpiod;
7108a0a4f9SSergei Shtylyov 
7208a0a4f9SSergei Shtylyov 	gpiod = devm_gpiod_get(&pdev->dev, "maxim,shdn", GPIOD_OUT_HIGH);
7308a0a4f9SSergei Shtylyov 	if (IS_ERR(gpiod)) {
7408a0a4f9SSergei Shtylyov 		dev_err(&pdev->dev, "failed to get SHDN# GPIO\n");
7508a0a4f9SSergei Shtylyov 		return PTR_ERR(gpiod);
7608a0a4f9SSergei Shtylyov 	}
7708a0a4f9SSergei Shtylyov 	data->shdn_gpiod = gpiod;
7808a0a4f9SSergei Shtylyov 
7908a0a4f9SSergei Shtylyov 	data->edev = devm_extcon_dev_allocate(&pdev->dev, max3355_cable);
8008a0a4f9SSergei Shtylyov 	if (IS_ERR(data->edev)) {
8108a0a4f9SSergei Shtylyov 		dev_err(&pdev->dev, "failed to allocate extcon device\n");
8208a0a4f9SSergei Shtylyov 		return PTR_ERR(data->edev);
8308a0a4f9SSergei Shtylyov 	}
8408a0a4f9SSergei Shtylyov 
8508a0a4f9SSergei Shtylyov 	err = devm_extcon_dev_register(&pdev->dev, data->edev);
8608a0a4f9SSergei Shtylyov 	if (err < 0) {
8708a0a4f9SSergei Shtylyov 		dev_err(&pdev->dev, "failed to register extcon device\n");
8808a0a4f9SSergei Shtylyov 		return err;
8908a0a4f9SSergei Shtylyov 	}
9008a0a4f9SSergei Shtylyov 
9108a0a4f9SSergei Shtylyov 	irq = gpiod_to_irq(data->id_gpiod);
9208a0a4f9SSergei Shtylyov 	if (irq < 0) {
9308a0a4f9SSergei Shtylyov 		dev_err(&pdev->dev, "failed to translate ID_OUT GPIO to IRQ\n");
9408a0a4f9SSergei Shtylyov 		return irq;
9508a0a4f9SSergei Shtylyov 	}
9608a0a4f9SSergei Shtylyov 
9708a0a4f9SSergei Shtylyov 	err = devm_request_threaded_irq(&pdev->dev, irq, NULL, max3355_id_irq,
9808a0a4f9SSergei Shtylyov 					IRQF_ONESHOT | IRQF_NO_SUSPEND |
9908a0a4f9SSergei Shtylyov 					IRQF_TRIGGER_RISING |
10008a0a4f9SSergei Shtylyov 					IRQF_TRIGGER_FALLING,
10108a0a4f9SSergei Shtylyov 					pdev->name, data);
10208a0a4f9SSergei Shtylyov 	if (err < 0) {
10308a0a4f9SSergei Shtylyov 		dev_err(&pdev->dev, "failed to request ID_OUT IRQ\n");
10408a0a4f9SSergei Shtylyov 		return err;
10508a0a4f9SSergei Shtylyov 	}
10608a0a4f9SSergei Shtylyov 
10708a0a4f9SSergei Shtylyov 	platform_set_drvdata(pdev, data);
10808a0a4f9SSergei Shtylyov 
10908a0a4f9SSergei Shtylyov 	/* Perform initial detection */
11008a0a4f9SSergei Shtylyov 	max3355_id_irq(irq, data);
11108a0a4f9SSergei Shtylyov 
11208a0a4f9SSergei Shtylyov 	return 0;
11308a0a4f9SSergei Shtylyov }
11408a0a4f9SSergei Shtylyov 
115*ba6985eaSUwe Kleine-König static void max3355_remove(struct platform_device *pdev)
11608a0a4f9SSergei Shtylyov {
11708a0a4f9SSergei Shtylyov 	struct max3355_data *data = platform_get_drvdata(pdev);
11808a0a4f9SSergei Shtylyov 
11908a0a4f9SSergei Shtylyov 	gpiod_set_value_cansleep(data->shdn_gpiod, 0);
12008a0a4f9SSergei Shtylyov }
12108a0a4f9SSergei Shtylyov 
12208a0a4f9SSergei Shtylyov static const struct of_device_id max3355_match_table[] = {
12308a0a4f9SSergei Shtylyov 	{ .compatible = "maxim,max3355", },
12408a0a4f9SSergei Shtylyov 	{ }
12508a0a4f9SSergei Shtylyov };
12608a0a4f9SSergei Shtylyov MODULE_DEVICE_TABLE(of, max3355_match_table);
12708a0a4f9SSergei Shtylyov 
12808a0a4f9SSergei Shtylyov static struct platform_driver max3355_driver = {
12908a0a4f9SSergei Shtylyov 	.probe		= max3355_probe,
130*ba6985eaSUwe Kleine-König 	.remove_new	= max3355_remove,
13108a0a4f9SSergei Shtylyov 	.driver		= {
13208a0a4f9SSergei Shtylyov 		.name	= "extcon-max3355",
13308a0a4f9SSergei Shtylyov 		.of_match_table = max3355_match_table,
13408a0a4f9SSergei Shtylyov 	},
13508a0a4f9SSergei Shtylyov };
13608a0a4f9SSergei Shtylyov 
13708a0a4f9SSergei Shtylyov module_platform_driver(max3355_driver);
13808a0a4f9SSergei Shtylyov 
13908a0a4f9SSergei Shtylyov MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>");
14008a0a4f9SSergei Shtylyov MODULE_DESCRIPTION("Maxim MAX3355 extcon driver");
14108a0a4f9SSergei Shtylyov MODULE_LICENSE("GPL v2");
142