| /* |
| * extcon-smsc375x.c - SMSC375x extcon driver |
| * |
| * Copyright (C) 2013 Intel Corporation |
| * Ramakrishna Pallala <ramakrishna.pallala@intel.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/i2c.h> |
| #include <linux/slab.h> |
| #include <linux/gpio.h> |
| #include <linux/interrupt.h> |
| #include <linux/err.h> |
| #include <linux/device.h> |
| #include <linux/workqueue.h> |
| #include <linux/usb/otg.h> |
| #include <linux/notifier.h> |
| #include <linux/extcon.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/acpi.h> |
| #include <linux/acpi_gpio.h> |
| #include <linux/power_supply.h> |
| #include <linux/wakelock.h> |
| #include <linux/extcon/extcon-smsc375x.h> |
| |
| /* SMSC375x I2C registers */ |
| #define SMSC375X_REG_STAT 0x00 |
| #define SMSC375X_REG_CFG 0x01 |
| #define SMSC375X_REG_CHRG_CFG 0x02 |
| #define SMSC375X_REG_CHRG_STAT 0x03 |
| |
| /* Status */ |
| #define STAT_OVLO_STAT (1 << 0) |
| #define STAT_OVLO_LATCH (1 << 1) |
| #define STAT_OVP_SWTCH_STAT (1 << 2) |
| #define STAT_CUR_LMT_STAT (1 << 3) |
| #define STAT_CHRG_DET_DONE (1 << 4) |
| #define STAT_CHRG_TYPE_MASK (7 << 5) |
| #define STAT_CHRG_TYPE_DCP (1 << 5) |
| #define STAT_CHRG_TYPE_CDP (2 << 5) |
| #define STAT_CHRG_TYPE_SDP (3 << 5) |
| #define STAT_CHRG_TYPE_SE1L (4 << 5) |
| #define STAT_CHRG_TYPE_SE1H (5 << 5) |
| |
| /* Config */ |
| #define CFG_EN_OVP_SWITCH (1 << 0) |
| #define CFG_EN_CUR_LMT (1 << 1) |
| #define CFG_OVERRIDE_VBUS (1 << 2) |
| #define CFG_OVERRIDE_CUR_LMT (1 << 3) |
| #define CFG_EN_MUX1 (1 << 5) |
| #define CFG_EN_MUX2 (1 << 6) |
| #define CFG_SOFT_POR (1 << 7) |
| |
| /* Charger Config */ |
| #define CHRG_CFG_EN_SNG_RX (1 << 0) |
| #define CHRG_CFG_EN_CON_DET (1 << 1) |
| #define CHRG_CFG_EN_VDAT_SRC (1 << 2) |
| #define CHRG_CFG_EN_HOST_CHRG (1 << 3) |
| #define CHRG_CFG_EN_IDAT_SINK (1 << 4) |
| #define CHRG_CFG_EN_DP_PDOWN (1 << 5) |
| #define CHRG_CFG_EN_DM_PDOWN (1 << 6) |
| #define CHRG_CFG_I2C_CNTL (1 << 7) |
| |
| |
| /* Charger Config */ |
| #define CHRG_STAT_VDAT_DET (1 << 0) |
| #define CHRG_STAT_DP_SNG_RX (1 << 1) |
| #define CHRG_STAT_DM_SNG_RX (1 << 2) |
| #define CHRG_STAT_RX_HIGH_CUR (1 << 3) |
| |
| |
| #define SMSC_CHARGE_CUR_DCP 2000 |
| #define SMSC_CHARGE_CUR_CDP 1500 |
| #define SMSC_CHARGE_CUR_SDP_100 100 |
| #define SMSC_CHARGE_CUR_SDP_500 500 |
| |
| #define SMSC375X_EXTCON_USB "USB" |
| #define SMSC375X_EXTCON_SDP "CHARGER_USB_SDP" |
| #define SMSC375X_EXTCON_DCP "CHARGER_USB_DCP" |
| #define SMSC375X_EXTCON_CDP "CHARGER_USB_CDP" |
| |
| static const char *smsc375x_extcon_cable[] = { |
| SMSC375X_EXTCON_SDP, |
| SMSC375X_EXTCON_DCP, |
| SMSC375X_EXTCON_CDP, |
| NULL, |
| }; |
| |
| struct smsc375x_chip { |
| struct i2c_client *client; |
| struct smsc375x_pdata *pdata; |
| struct usb_phy *otg; |
| struct work_struct otg_work; |
| struct notifier_block id_nb; |
| bool id_short; |
| struct extcon_specific_cable_nb cable_obj; |
| struct notifier_block vbus_nb; |
| struct work_struct vbus_work; |
| struct extcon_dev *edev; |
| struct wake_lock wakelock; |
| bool is_sdp; |
| }; |
| |
| static struct smsc375x_chip *chip_ptr; |
| extern void *smsc375x_platform_data(void); |
| |
| static int smsc375x_write_reg(struct i2c_client *client, |
| int reg, int value) |
| { |
| int ret; |
| |
| ret = i2c_smbus_write_byte_data(client, reg, value); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static int smsc375x_read_reg(struct i2c_client *client, int reg) |
| { |
| int ret; |
| |
| ret = i2c_smbus_read_byte_data(client, reg); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static int smsc375x_detect_dev(struct smsc375x_chip *chip) |
| { |
| struct i2c_client *client = chip->client; |
| static bool notify_otg, notify_charger; |
| static char *cable; |
| static struct power_supply_cable_props cable_props; |
| int stat, cfg, ret, vbus_mask = 0; |
| u8 chrg_type; |
| bool vbus_attach = false; |
| |
| dev_info(&chip->client->dev, "%s\n", __func__); |
| /* |
| * get VBUS status from external IC like |
| * PMIC or Charger as SMSC375x chip can not |
| * be accessed with out VBUS. |
| */ |
| ret = chip->pdata->is_vbus_online(); |
| if (ret < 0) { |
| dev_info(&chip->client->dev, "get vbus stat error\n"); |
| return ret; |
| } |
| |
| if (ret) { |
| dev_info(&chip->client->dev, "VBUS present\n"); |
| vbus_attach = true; |
| } else { |
| dev_info(&chip->client->dev, "VBUS NOT present\n"); |
| vbus_attach = false; |
| cable_props.ma = 0; |
| cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_DISCONNECT; |
| goto notify_otg_em; |
| } |
| |
| /* dont proceed with charger detection in host mode */ |
| if (chip->id_short) { |
| /* |
| * only after reading the status register |
| * MUX path is being closed. And by default |
| * MUX is to connected Host mode path. |
| */ |
| ret = smsc375x_read_reg(client, SMSC375X_REG_STAT); |
| return ret; |
| } |
| /* check charger detection completion status */ |
| ret = smsc375x_read_reg(client, SMSC375X_REG_STAT); |
| if (ret < 0) |
| goto dev_det_i2c_failed; |
| else |
| stat = ret; |
| |
| if (!(stat & STAT_CHRG_DET_DONE)) { |
| dev_info(&chip->client->dev, "DET failed"); |
| return -EOPNOTSUPP; |
| } |
| |
| ret = smsc375x_read_reg(client, SMSC375X_REG_CFG); |
| if (ret < 0) |
| goto dev_det_i2c_failed; |
| else |
| cfg = ret; |
| |
| dev_info(&client->dev, "Stat:%x, Cfg:%x\n", stat, cfg); |
| |
| chrg_type = stat & STAT_CHRG_TYPE_MASK; |
| chip->is_sdp = false; |
| |
| /* Enabling the OVP switch on VBUS to draw maximum current */ |
| ret = smsc375x_write_reg(client, SMSC375X_REG_CFG, |
| (cfg | (CFG_OVERRIDE_VBUS | CFG_EN_OVP_SWITCH))); |
| if (ret < 0) |
| goto dev_det_i2c_failed; |
| |
| if (chrg_type == STAT_CHRG_TYPE_SDP) { |
| dev_info(&chip->client->dev, |
| "SDP cable connecetd\n"); |
| notify_otg = true; |
| vbus_mask = 1; |
| notify_charger = true; |
| chip->is_sdp = true; |
| cable = SMSC375X_EXTCON_SDP; |
| cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT; |
| cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_SDP; |
| if (chip->pdata->charging_compliance_override) |
| cable_props.ma = SMSC_CHARGE_CUR_SDP_500; |
| else |
| cable_props.ma = SMSC_CHARGE_CUR_SDP_100; |
| } else if (chrg_type == STAT_CHRG_TYPE_CDP) { |
| dev_info(&chip->client->dev, |
| "CDP cable connecetd\n"); |
| notify_otg = true; |
| vbus_mask = 1; |
| notify_charger = true; |
| cable = SMSC375X_EXTCON_CDP; |
| cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT; |
| cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_CDP; |
| cable_props.ma = SMSC_CHARGE_CUR_CDP; |
| } else if ((chrg_type == STAT_CHRG_TYPE_DCP) || |
| (chrg_type == STAT_CHRG_TYPE_SE1L) || |
| (chrg_type == STAT_CHRG_TYPE_SE1H)) { |
| dev_info(&chip->client->dev, |
| "DCP/SE1 cable connecetd\n"); |
| /* Driving Vdat_src pin as the PET expects the voltage on DP |
| * to remain >0.5V for the duration of the time VBUS is valid |
| */ |
| ret = smsc375x_write_reg(client, SMSC375X_REG_CHRG_CFG, |
| (CHRG_CFG_I2C_CNTL | CHRG_CFG_EN_VDAT_SRC)); |
| if (ret < 0) |
| goto dev_det_i2c_failed; |
| notify_charger = true; |
| cable = SMSC375X_EXTCON_DCP; |
| cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT; |
| cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_DCP; |
| cable_props.ma = SMSC_CHARGE_CUR_DCP; |
| if (!wake_lock_active(&chip->wakelock)) |
| wake_lock(&chip->wakelock); |
| } else { |
| dev_warn(&chip->client->dev, |
| "disconnect or unknown or ID event\n"); |
| cable_props.ma = 0; |
| cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_DISCONNECT; |
| } |
| |
| notify_otg_em: |
| if (!vbus_attach) { /* disconnevt event */ |
| if (notify_otg) { |
| atomic_notifier_call_chain(&chip->otg->notifier, |
| USB_EVENT_VBUS, &vbus_mask); |
| notify_otg = false; |
| } |
| if (notify_charger) { |
| /* |
| * not supporting extcon events currently. |
| * extcon_set_cable_state(chip->edev, cable, false); |
| */ |
| atomic_notifier_call_chain(&power_supply_notifier, |
| POWER_SUPPLY_CABLE_EVENT, &cable_props); |
| notify_charger = false; |
| cable = NULL; |
| } |
| if (wake_lock_active(&chip->wakelock)) |
| wake_unlock(&chip->wakelock); |
| } else { |
| if (notify_otg) { |
| /* close mux path to enable device mode */ |
| ret = smsc375x_write_reg(client, SMSC375X_REG_CFG, |
| (cfg & ~CFG_EN_MUX1) | CFG_EN_MUX2); |
| if (ret < 0) |
| goto dev_det_i2c_failed; |
| atomic_notifier_call_chain(&chip->otg->notifier, |
| USB_EVENT_VBUS, &vbus_mask); |
| } |
| |
| if (notify_charger) { |
| /* |
| * not supporting extcon events currently. |
| * extcon_set_cable_state(chip->edev, cable, true); |
| */ |
| atomic_notifier_call_chain(&power_supply_notifier, |
| POWER_SUPPLY_CABLE_EVENT, &cable_props); |
| } |
| } |
| |
| return 0; |
| |
| dev_det_i2c_failed: |
| if (chip->pdata->is_vbus_online()) |
| dev_err(&chip->client->dev, |
| "vbus present: i2c read failed:%d\n", ret); |
| else |
| dev_info(&chip->client->dev, |
| "vbus removed: i2c read failed:%d\n", ret); |
| return ret; |
| } |
| |
| static irqreturn_t smsc375x_irq_handler(int irq, void *data) |
| { |
| struct smsc375x_chip *chip = data; |
| |
| pm_runtime_get_sync(&chip->client->dev); |
| |
| dev_info(&chip->client->dev, "SMSC USB INT!\n"); |
| |
| smsc375x_detect_dev(chip); |
| |
| pm_runtime_put_sync(&chip->client->dev); |
| return IRQ_HANDLED; |
| } |
| |
| static void smsc375x_otg_event_worker(struct work_struct *work) |
| { |
| struct smsc375x_chip *chip = |
| container_of(work, struct smsc375x_chip, otg_work); |
| int ret; |
| |
| pm_runtime_get_sync(&chip->client->dev); |
| |
| if (chip->id_short) |
| ret = chip->pdata->enable_vbus(); |
| else |
| ret = chip->pdata->disable_vbus(); |
| if (ret < 0) |
| dev_warn(&chip->client->dev, "id vbus control failed\n"); |
| |
| /* |
| * As we are not getting SMSC INT in case |
| * 5V boost enablement. |
| * Follwoing WA is added to enable Host mode |
| * on CR V2.1 by invoking the VBUS worker. |
| */ |
| msleep(5000); |
| schedule_work(&chip->vbus_work); |
| |
| pm_runtime_put_sync(&chip->client->dev); |
| } |
| |
| static int smsc375x_handle_otg_notification(struct notifier_block *nb, |
| unsigned long event, void *param) |
| { |
| struct smsc375x_chip *chip = |
| container_of(nb, struct smsc375x_chip, id_nb); |
| struct power_supply_cable_props cable_props; |
| int *val = (int *)param; |
| int ret; |
| |
| if (!val || ((event != USB_EVENT_ID) && |
| (event != USB_EVENT_ENUMERATED))) |
| return NOTIFY_DONE; |
| |
| dev_info(&chip->client->dev, |
| "[OTG notification]evt:%lu val:%d\n", event, *val); |
| |
| switch (event) { |
| case USB_EVENT_ID: |
| /* |
| * in case of ID short(*id = 0) |
| * enable vbus else disable vbus. |
| */ |
| if (*val) |
| chip->id_short = false; |
| else |
| chip->id_short = true; |
| schedule_work(&chip->otg_work); |
| break; |
| case USB_EVENT_ENUMERATED: |
| /* |
| * ignore cable plug/unplug events as SMSC |
| * had already send those event notifications. |
| * Also only handle notifications for SDP case. |
| */ |
| /* No need to change SDP inlimit based on enumeration status |
| * if platform can voilate charging_compliance. |
| */ |
| if (chip->pdata->charging_compliance_override || |
| !chip->is_sdp || |
| (*val == SMSC_CHARGE_CUR_SDP_100)) |
| break; |
| /* |
| * if current limit is < 100mA |
| * treat it as suspend event. |
| */ |
| if (*val < SMSC_CHARGE_CUR_SDP_100) |
| cable_props.chrg_evt = |
| POWER_SUPPLY_CHARGER_EVENT_SUSPEND; |
| else |
| cable_props.chrg_evt = |
| POWER_SUPPLY_CHARGER_EVENT_CONNECT; |
| cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_SDP; |
| cable_props.ma = *val; |
| atomic_notifier_call_chain(&power_supply_notifier, |
| POWER_SUPPLY_CABLE_EVENT, &cable_props); |
| break; |
| default: |
| dev_warn(&chip->client->dev, "invalid OTG event\n"); |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static void smsc375x_pwrsrc_event_worker(struct work_struct *work) |
| { |
| struct smsc375x_chip *chip = |
| container_of(work, struct smsc375x_chip, vbus_work); |
| int ret; |
| |
| pm_runtime_get_sync(&chip->client->dev); |
| |
| /* |
| * Sometimes SMSC INT triggering is only |
| * happening after reading the status bits. |
| * So we are reading the status register as WA |
| * to invoke teh MUX INT in case of connect events. |
| */ |
| if (!chip->pdata->is_vbus_online()) { |
| ret = smsc375x_detect_dev(chip); |
| } else { |
| /** |
| * To guarantee SDP detection in SMSC, need 75mSec delay before |
| * sending an I2C command. So added 50mSec delay here. |
| */ |
| mdelay(50); |
| ret = smsc375x_read_reg(chip->client, SMSC375X_REG_STAT); |
| } |
| if (ret < 0) |
| dev_warn(&chip->client->dev, "pwrsrc evt error\n"); |
| |
| pm_runtime_put_sync(&chip->client->dev); |
| } |
| |
| static int smsc375x_handle_pwrsrc_notification(struct notifier_block *nb, |
| unsigned long event, void *param) |
| { |
| struct smsc375x_chip *chip = |
| container_of(nb, struct smsc375x_chip, vbus_nb); |
| |
| dev_info(&chip->client->dev, "[PWRSRC notification]: %lu\n", event); |
| |
| schedule_work(&chip->vbus_work); |
| |
| return NOTIFY_OK; |
| } |
| |
| static int smsc375x_irq_init(struct smsc375x_chip *chip) |
| { |
| struct i2c_client *client = chip->client; |
| int ret, gpio_num; |
| struct acpi_gpio_info gpio_info; |
| |
| /* get kernel GPIO number */ |
| gpio_num = acpi_get_gpio_by_index(&client->dev, 0, &gpio_info); |
| if (gpio_num < 0) { |
| gpio_num = acpi_get_gpio("\\_SB.GPO2", 0x1); |
| if (gpio_num < 0) |
| dev_err(&client->dev, "failed to get GPIO\n"); |
| } |
| /* get irq number */ |
| chip->client->irq = gpio_to_irq(gpio_num); |
| if (client->irq) { |
| ret = request_threaded_irq(client->irq, NULL, |
| smsc375x_irq_handler, |
| IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
| "smsc375x", chip); |
| if (ret) { |
| dev_err(&client->dev, "failed to reqeust IRQ\n"); |
| return ret; |
| } |
| enable_irq_wake(client->irq); |
| } else { |
| dev_err(&client->dev, "IRQ not set\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int smsc375x_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct smsc375x_chip *chip; |
| int ret = 0, id_val = -1; |
| |
| chip = kzalloc(sizeof(struct smsc375x_chip), GFP_KERNEL); |
| if (!chip) { |
| dev_err(&client->dev, "failed to allocate driver data\n"); |
| return -ENOMEM; |
| } |
| |
| chip->client = client; |
| #ifdef CONFIG_ACPI |
| chip->pdata = smsc375x_platform_data(); |
| #else |
| chip->pdata = dev->platform_data; |
| #endif |
| i2c_set_clientdata(client, chip); |
| wake_lock_init(&chip->wakelock, WAKE_LOCK_SUSPEND, |
| "smsc375x_wakelock"); |
| |
| /* register with extcon */ |
| chip->edev = kzalloc(sizeof(struct extcon_dev), GFP_KERNEL); |
| if (!chip->edev) { |
| dev_err(&client->dev, "mem alloc failed\n"); |
| ret = -ENOMEM; |
| goto extcon_mem_failed; |
| } |
| chip->edev->name = "smsc375x"; |
| chip->edev->supported_cable = smsc375x_extcon_cable; |
| ret = extcon_dev_register(chip->edev, &client->dev); |
| if (ret) { |
| dev_err(&client->dev, "extcon registration failed!!\n"); |
| goto extcon_reg_failed; |
| } |
| |
| /* register for EXTCON USB notification */ |
| INIT_WORK(&chip->vbus_work, smsc375x_pwrsrc_event_worker); |
| chip->vbus_nb.notifier_call = smsc375x_handle_pwrsrc_notification; |
| ret = extcon_register_interest(&chip->cable_obj, NULL, |
| SMSC375X_EXTCON_USB, &chip->vbus_nb); |
| |
| /* OTG notification */ |
| chip->otg = usb_get_phy(USB_PHY_TYPE_USB2); |
| if (!chip->otg) { |
| dev_warn(&client->dev, "Failed to get otg transceiver!!\n"); |
| goto otg_reg_failed; |
| } |
| |
| INIT_WORK(&chip->otg_work, smsc375x_otg_event_worker); |
| chip->id_nb.notifier_call = smsc375x_handle_otg_notification; |
| ret = usb_register_notifier(chip->otg, &chip->id_nb); |
| if (ret) { |
| dev_err(&chip->client->dev, |
| "failed to register otg notifier\n"); |
| goto id_reg_failed; |
| } |
| |
| ret = smsc375x_irq_init(chip); |
| if (ret) |
| goto intr_reg_failed; |
| |
| chip_ptr = chip; |
| |
| if (chip->otg->get_id_status) { |
| ret = chip->otg->get_id_status(chip->otg, &id_val); |
| if (ret < 0) { |
| dev_warn(&client->dev, |
| "otg get ID status failed:%d\n", ret); |
| ret = 0; |
| } |
| } |
| |
| if (!id_val && !chip->id_short) |
| atomic_notifier_call_chain(&chip->otg->notifier, |
| USB_EVENT_ID, &id_val); |
| else |
| smsc375x_detect_dev(chip); |
| |
| /* Init Runtime PM State */ |
| pm_runtime_put_noidle(&chip->client->dev); |
| pm_schedule_suspend(&chip->client->dev, MSEC_PER_SEC); |
| |
| return 0; |
| |
| intr_reg_failed: |
| usb_unregister_notifier(chip->otg, &chip->id_nb); |
| id_reg_failed: |
| usb_put_phy(chip->otg); |
| otg_reg_failed: |
| extcon_dev_unregister(chip->edev); |
| extcon_reg_failed: |
| kfree(chip->edev); |
| extcon_mem_failed: |
| kfree(chip); |
| return ret; |
| } |
| |
| static int smsc375x_remove(struct i2c_client *client) |
| { |
| struct smsc375x_chip *chip = i2c_get_clientdata(client); |
| |
| free_irq(client->irq, chip); |
| usb_put_phy(chip->otg); |
| extcon_dev_unregister(chip->edev); |
| kfree(chip->edev); |
| pm_runtime_get_noresume(&chip->client->dev); |
| kfree(chip); |
| return 0; |
| } |
| |
| static void smsc375x_shutdown(struct i2c_client *client) |
| { |
| dev_dbg(&client->dev, "smsc375x shutdown\n"); |
| |
| if (client->irq > 0) |
| disable_irq(client->irq); |
| return; |
| } |
| |
| static int smsc375x_suspend(struct device *dev) |
| { |
| struct smsc375x_chip *chip = dev_get_drvdata(dev); |
| |
| if (chip->client->irq > 0) |
| disable_irq(chip->client->irq); |
| |
| dev_dbg(dev, "%s called\n", __func__); |
| return 0; |
| } |
| |
| static int smsc375x_resume(struct device *dev) |
| { |
| struct smsc375x_chip *chip = dev_get_drvdata(dev); |
| |
| if (chip->client->irq > 0) |
| enable_irq(chip->client->irq); |
| |
| dev_dbg(dev, "%s called\n", __func__); |
| return 0; |
| } |
| |
| static int smsc375x_runtime_suspend(struct device *dev) |
| { |
| dev_dbg(dev, "%s called\n", __func__); |
| return 0; |
| } |
| |
| static int smsc375x_runtime_resume(struct device *dev) |
| { |
| dev_dbg(dev, "%s called\n", __func__); |
| return 0; |
| } |
| |
| static int smsc375x_runtime_idle(struct device *dev) |
| { |
| dev_dbg(dev, "%s called\n", __func__); |
| return 0; |
| } |
| |
| static const struct dev_pm_ops smsc375x_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(smsc375x_suspend, |
| smsc375x_resume) |
| SET_RUNTIME_PM_OPS(smsc375x_runtime_suspend, |
| smsc375x_runtime_resume, |
| smsc375x_runtime_idle) |
| }; |
| |
| static const struct i2c_device_id smsc375x_id[] = { |
| {"smsc375x", }, |
| {"SMSC3750", }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(i2c, smsc375x_id); |
| |
| static const struct acpi_device_id acpi_smsc375x_id[] = { |
| {"SMSC3750", }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(acpi, acpi_smsc375x_id); |
| |
| static struct i2c_driver smsc375x_i2c_driver = { |
| .driver = { |
| .name = "smsc375x", |
| .owner = THIS_MODULE, |
| .pm = &smsc375x_pm_ops, |
| .acpi_match_table = ACPI_PTR(acpi_smsc375x_id), |
| }, |
| .probe = smsc375x_probe, |
| .remove = smsc375x_remove, |
| .id_table = smsc375x_id, |
| .shutdown = smsc375x_shutdown, |
| }; |
| |
| /* |
| * Module stuff |
| */ |
| |
| static int __init smsc375x_extcon_init(void) |
| { |
| int ret = i2c_add_driver(&smsc375x_i2c_driver); |
| return ret; |
| } |
| late_initcall(smsc375x_extcon_init); |
| |
| static void __exit smsc375x_extcon_exit(void) |
| { |
| i2c_del_driver(&smsc375x_i2c_driver); |
| } |
| module_exit(smsc375x_extcon_exit); |
| |
| MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>"); |
| MODULE_DESCRIPTION("SMSC375x extcon driver"); |
| MODULE_LICENSE("GPL"); |