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