1*ac1dc6b2SThomas 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
max3355_id_irq(int irq,void * dev_id)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
max3355_probe(struct platform_device * pdev)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
max3355_remove(struct platform_device * pdev)11508a0a4f9SSergei Shtylyov static int 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 return 0;
12208a0a4f9SSergei Shtylyov }
12308a0a4f9SSergei Shtylyov
12408a0a4f9SSergei Shtylyov static const struct of_device_id max3355_match_table[] = {
12508a0a4f9SSergei Shtylyov { .compatible = "maxim,max3355", },
12608a0a4f9SSergei Shtylyov { }
12708a0a4f9SSergei Shtylyov };
12808a0a4f9SSergei Shtylyov MODULE_DEVICE_TABLE(of, max3355_match_table);
12908a0a4f9SSergei Shtylyov
13008a0a4f9SSergei Shtylyov static struct platform_driver max3355_driver = {
13108a0a4f9SSergei Shtylyov .probe = max3355_probe,
13208a0a4f9SSergei Shtylyov .remove = max3355_remove,
13308a0a4f9SSergei Shtylyov .driver = {
13408a0a4f9SSergei Shtylyov .name = "extcon-max3355",
13508a0a4f9SSergei Shtylyov .of_match_table = max3355_match_table,
13608a0a4f9SSergei Shtylyov },
13708a0a4f9SSergei Shtylyov };
13808a0a4f9SSergei Shtylyov
13908a0a4f9SSergei Shtylyov module_platform_driver(max3355_driver);
14008a0a4f9SSergei Shtylyov
14108a0a4f9SSergei Shtylyov MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>");
14208a0a4f9SSergei Shtylyov MODULE_DESCRIPTION("Maxim MAX3355 extcon driver");
14308a0a4f9SSergei Shtylyov MODULE_LICENSE("GPL v2");
144