| /* |
| * muic_task.c |
| * |
| * Copyright (C) 2014 Samsung Electronics |
| * Thomas Ryu <smilesr.ryu@samsung.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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/gpio.h> |
| #include <linux/i2c.h> |
| #include <linux/interrupt.h> |
| #include <linux/slab.h> |
| #include <linux/platform_device.h> |
| #include <linux/module.h> |
| #include <linux/delay.h> |
| #include <linux/host_notify.h> |
| |
| #include <linux/muic/muic.h> |
| #include <linux/mfd/muic_mfd.h> |
| |
| #if defined(CONFIG_MUIC_NOTIFIER) |
| #include <linux/muic/muic_notifier.h> |
| #endif /* CONFIG_MUIC_NOTIFIER */ |
| |
| #if defined(CONFIG_VBUS_NOTIFIER) |
| #include <linux/vbus_notifier.h> |
| #endif /* CONFIG_VBUS_NOTIFIER */ |
| |
| #if defined (CONFIG_OF) |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #endif /* CONFIG_OF */ |
| |
| #include "muic-internal.h" |
| #include "muic_apis.h" |
| #include "muic_state.h" |
| #include "muic_vps.h" |
| #include "muic_i2c.h" |
| #include "muic_sysfs.h" |
| #include "muic_debug.h" |
| #include "muic_dt.h" |
| #include "muic_regmap.h" |
| #include "muic_coagent.h" |
| |
| #if defined(CONFIG_MUIC_HV) |
| #include "muic_hv.h" |
| #include "muic_hv_max77854.h" |
| #endif |
| |
| #if defined(CONFIG_MUIC_SUPPORT_CCIC) |
| #include "muic_ccic.h" |
| #endif |
| |
| #if defined(CONFIG_USB_EXTERNAL_NOTIFY) |
| #include "muic_usb.h" |
| #endif |
| |
| #define MUIC_INT_DETACH_MASK (0x1 << 1) |
| #define MUIC_INT_ATTACH_MASK (0x1 << 0) |
| #define MUIC_INT_OVP_EN_MASK (0x1 << 5) |
| |
| #define MUIC_REG_CTRL 0x02 |
| #define MUIC_REG_INT1 0x03 |
| #define MUIC_REG_INT2 0x04 |
| |
| /* Interrupt type */ |
| enum { |
| INT_REQ_ATTACH = 1<<0, |
| INT_REQ_DETACH = 1<<1, |
| INT_REQ_OVP = 1<<2, |
| INT_REQ_RESET = 1<<3, |
| INT_REQ_DONE = 1<<4, |
| INT_REQ_DISCARD = 1<<5, |
| }; |
| |
| extern struct muic_platform_data muic_pdata; |
| |
| extern void muic_send_dock_intent(int type); |
| |
| static bool muic_online = false; |
| |
| /* |
| * 0 : normal, 1: abnormal |
| * This flag is set by USB for an abnormal HMT and |
| * cleared by MUIC on detachment. |
| */ |
| static int muic_hmt_status; |
| |
| bool muic_is_online(void) |
| { |
| return muic_online; |
| } |
| static int muic_irq_handler(muic_data_t *pmuic, int irq) |
| { |
| struct i2c_client *i2c = pmuic->i2c; |
| int intr1, intr2; |
| |
| pr_info("%s:%s irq(%d)\n", pmuic->chip_name, __func__, irq); |
| |
| /* read and clear interrupt status bits */ |
| intr1 = muic_i2c_read_byte(i2c, MUIC_REG_INT1); |
| intr2 = muic_i2c_read_byte(i2c, MUIC_REG_INT2); |
| |
| if ((intr1 < 0) || (intr2 < 0)) { |
| pr_err("%s: err read interrupt status [1:0x%x, 2:0x%x]\n", |
| __func__, intr1, intr2); |
| return INT_REQ_DISCARD; |
| } |
| |
| if (intr1 & MUIC_INT_ATTACH_MASK) |
| { |
| int intr_tmp; |
| intr_tmp = muic_i2c_read_byte(i2c, MUIC_REG_INT1); |
| if (intr_tmp & 0x2) |
| { |
| pr_info("%s:%s attach/detach interrupt occurred\n", |
| pmuic->chip_name, __func__); |
| intr1 &= 0xFE; |
| } |
| intr1 |= intr_tmp; |
| } |
| |
| pr_info("%s:%s intr[1:0x%x, 2:0x%x]\n", pmuic->chip_name, __func__, |
| intr1, intr2); |
| |
| /* check for muic reset and recover for every interrupt occurred */ |
| if ((intr1 & MUIC_INT_OVP_EN_MASK) || |
| ((intr1 == 0) && (intr2 == 0) && (irq != -1))) |
| { |
| int ctrl; |
| ctrl = muic_i2c_read_byte(i2c, MUIC_REG_CTRL); |
| if (ctrl == 0x1F) |
| { |
| /* CONTROL register is reset to 1F */ |
| muic_print_reg_log(); |
| muic_print_reg_dump(pmuic); |
| pr_err("%s: err muic could have been reseted. Initilize!!\n", |
| __func__); |
| muic_reg_init(pmuic); |
| muic_print_reg_dump(pmuic); |
| |
| /* MUIC Interrupt On */ |
| set_int_mask(pmuic, false); |
| } |
| |
| if ((intr1 & MUIC_INT_ATTACH_MASK) == 0) |
| return INT_REQ_DISCARD; |
| } |
| |
| pmuic->intr.intr1 = intr1; |
| pmuic->intr.intr2 = intr2; |
| |
| return INT_REQ_DONE; |
| } |
| |
| enum max_intr_bits{ |
| INTR1_ADC1K_MASK = (1<<3), |
| INTR1_ADCERR_MASK = (1<<2), |
| INTR1_ADC_MASK = (1<<0), |
| |
| INTR2_VBVOLT_MASK = (1<<4), |
| INTR2_OVP_MASK = (1<<3), |
| INTR2_DCTTMR_MASK = (1<<2), |
| INTR2_CHGDETRUN_MASK = (1<<1), |
| INTR2_CHGTYP_MASK = (1<<0), |
| }; |
| |
| /* Fixme. */ |
| enum irq_num { |
| MAX77854_MUIC_IRQ_BASE = 10, |
| /* MUIC INT1 */ |
| MAX77854_MUIC_IRQ_INT1_ADC, |
| MAX77854_MUIC_IRQ_INT1_ADCERR, |
| MAX77854_MUIC_IRQ_INT1_ADC1K, |
| /* MUIC INT2 */ |
| MAX77854_MUIC_IRQ_INT2_CHGTYP, |
| MAX77854_MUIC_IRQ_INT2_CHGDETREUN, |
| MAX77854_MUIC_IRQ_INT2_DCDTMR, |
| MAX77854_MUIC_IRQ_INT2_DXOVP, |
| MAX77854_MUIC_IRQ_INT2_VBVOLT, |
| }; |
| |
| #define MAX77854_REG_INT1 (0x01) |
| #define MAX77854_REG_INT2 (0x02) |
| |
| static inline bool muis_is_vbus_int(muic_data_t *pmuic, int irq) |
| { |
| int local_irq; |
| |
| if(irq < 0) |
| return false; |
| |
| local_irq = irq - pmuic->pdata->irq_gpio; |
| if (local_irq == MAX77854_MUIC_IRQ_INT2_VBVOLT) |
| return true; |
| |
| return false; |
| } |
| |
| void muic_set_hmt_status(int status) |
| { |
| |
| pr_info("%s:%s hmt_status=[%s]\n", MUIC_DEV_NAME, __func__, |
| status ? "abnormal" : "normal"); |
| |
| muic_hmt_status = status; |
| |
| if (status) |
| muic_send_dock_intent(MUIC_DOCK_ABNORMAL); |
| } |
| |
| #if 0 |
| static int muic_get_hmt_status(void) |
| { |
| return muic_hmt_status; |
| } |
| #endif |
| |
| #if defined(CONFIG_VBUS_NOTIFIER) |
| static void muic_handle_vbus(muic_data_t *pmuic) |
| { |
| vbus_status_t status = pmuic->vps.t.vbvolt ? |
| STATUS_VBUS_HIGH: STATUS_VBUS_LOW; |
| |
| pr_info("%s:%s <%d>\n", MUIC_DEV_NAME, __func__, pmuic->vps.t.vbvolt); |
| |
| #if 0 |
| if (pmuic->attached_dev == ATTACHED_DEV_HMT_MUIC) { |
| if (muic_get_hmt_status()) { |
| pr_info("%s:%s Abnormal HMT -> VBUS_UNKNOWN Noti.\n", |
| MUIC_DEV_NAME, __func__); |
| status = STATUS_VBUS_UNKNOWN; |
| } |
| } |
| #endif |
| |
| vbus_notifier_handle(status); |
| |
| return; |
| } |
| #else |
| static void muic_handle_vbus(muic_data_t *pmuic) |
| { |
| pr_info("%s:%s <%d> Not implemented.\n", MUIC_DEV_NAME, |
| __func__, pmuic->vps.t.vbvolt); |
| } |
| #endif |
| |
| static irqreturn_t max77854_muic_irq_handler(muic_data_t *pmuic, int irq) |
| { |
| int local_irq; |
| |
| pr_info("%s:%s irq:%d\n", MUIC_DEV_NAME, __func__, irq); |
| |
| if (irq < 0) |
| return INT_REQ_DONE; |
| |
| local_irq = irq - pmuic->pdata->irq_gpio; |
| |
| switch (local_irq) { |
| case MAX77854_MUIC_IRQ_INT2_DXOVP: |
| pr_info("%s OVP interrupt occured\n",__func__); |
| return INT_REQ_OVP; |
| |
| case MAX77854_MUIC_IRQ_INT2_DCDTMR: |
| pr_info("%s DCTTMR interrupt occured\n",__func__); |
| pmuic->is_dcdtmr_intr = true; |
| break; |
| default: |
| break; |
| } |
| |
| return INT_REQ_DONE; |
| } |
| |
| static irqreturn_t muic_irq_thread(int irq, void *data) |
| { |
| muic_data_t *pmuic = data; |
| |
| mutex_lock(&pmuic->muic_mutex); |
| |
| if(pmuic->vps_table == VPS_TYPE_TABLE) { |
| if(max77854_muic_irq_handler(pmuic, irq) & INT_REQ_DONE) { |
| muic_detect_dev(pmuic, irq); |
| if (muis_is_vbus_int(pmuic, irq)) |
| muic_handle_vbus(pmuic); |
| } |
| } else{ |
| if (muic_irq_handler(pmuic, irq) & INT_REQ_DONE) |
| muic_detect_dev(pmuic, irq); |
| } |
| |
| mutex_unlock(&pmuic->muic_mutex); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void muic_init_detect(muic_data_t *pmuic) |
| { |
| pr_info("%s:%s\n", pmuic->chip_name, __func__); |
| |
| pmuic->is_muic_ready = true; |
| muic_irq_thread(-1, pmuic); |
| } |
| |
| static int muic_irq_init(muic_data_t *pmuic) |
| { |
| struct muic_platform_data *pdata = pmuic->pdata; |
| int ret = 0; |
| |
| pr_info("%s:%s\n", pmuic->chip_name, __func__); |
| |
| if (!pdata->irq_gpio) { |
| pr_warn("%s:%s No interrupt specified\n", pmuic->chip_name, |
| __func__); |
| return -ENXIO; |
| } |
| |
| if(!strcmp(pmuic->chip_name,"max,max77854")) { |
| int irq_adc1k, irq_adcerr, irq_adc, irq_chgtyp, irq_vbvolt; |
| #if defined(CONFIG_MUIC_SUPPORT_CCIC) |
| /* type C needs checking dcd tmr interrupt */ |
| int irq_dcdtmr; |
| #endif |
| |
| irq_adc1k = pdata->irq_gpio + MAX77854_MUIC_IRQ_INT1_ADC1K; |
| printk(" %s requesting irq no = %d\n",__func__,irq_adc1k); |
| ret = request_threaded_irq(irq_adc1k, NULL, |
| muic_irq_thread, (IRQF_TRIGGER_FALLING | IRQF_ONESHOT), |
| "muic-irq", pmuic); |
| |
| irq_adcerr = pdata->irq_gpio + MAX77854_MUIC_IRQ_INT1_ADCERR; |
| printk(" %s requesting irq no = %d\n",__func__,irq_adcerr); |
| ret = request_threaded_irq(irq_adcerr, NULL, |
| muic_irq_thread, (IRQF_TRIGGER_FALLING | IRQF_ONESHOT), |
| "muic-irq", pmuic); |
| irq_adc = pdata->irq_gpio + MAX77854_MUIC_IRQ_INT1_ADC; |
| printk(" %s requesting irq no = %d\n",__func__,irq_adc); |
| ret = request_threaded_irq(irq_adc, NULL, |
| muic_irq_thread, (IRQF_TRIGGER_FALLING | IRQF_ONESHOT), |
| "muic-irq", pmuic); |
| irq_chgtyp = pdata->irq_gpio + MAX77854_MUIC_IRQ_INT2_CHGTYP; |
| printk(" %s requesting irq no = %d\n",__func__,irq_chgtyp); |
| ret = request_threaded_irq(irq_chgtyp, NULL, |
| muic_irq_thread, (IRQF_TRIGGER_FALLING | IRQF_ONESHOT), |
| "muic-irq", pmuic); |
| irq_vbvolt = pdata->irq_gpio + MAX77854_MUIC_IRQ_INT2_VBVOLT; |
| printk(" %s requesting irq no = %d\n",__func__,irq_vbvolt); |
| ret = request_threaded_irq(irq_vbvolt, NULL, |
| muic_irq_thread, (IRQF_TRIGGER_FALLING | IRQF_ONESHOT), |
| "muic-irq", pmuic); |
| #if defined(CONFIG_MUIC_SUPPORT_CCIC) |
| irq_dcdtmr = pdata->irq_gpio + MAX77854_MUIC_IRQ_INT2_DCDTMR; |
| printk(" %s requesting irq no = %d\n",__func__,irq_dcdtmr); |
| ret = request_threaded_irq(irq_dcdtmr, NULL, |
| muic_irq_thread, (IRQF_TRIGGER_FALLING | IRQF_ONESHOT), |
| "muic-irq", pmuic); |
| #endif |
| |
| pmuic->irqs.irq_adc1k = irq_adc1k; |
| pmuic->irqs.irq_adcerr = irq_adcerr; |
| pmuic->irqs.irq_adc = irq_adc; |
| pmuic->irqs.irq_chgtyp = irq_chgtyp; |
| pmuic->irqs.irq_vbvolt = irq_vbvolt; |
| #if defined(CONFIG_MUIC_SUPPORT_CCIC) |
| pmuic->irqs.irq_dcdtmr = irq_dcdtmr; |
| #endif |
| |
| } |
| else |
| ret = request_threaded_irq(pdata->irq_gpio, NULL, |
| muic_irq_thread, |
| (IRQF_TRIGGER_FALLING | IRQF_ONESHOT), |
| "muic-irq", pmuic); |
| if (ret < 0) { |
| pr_err("%s:%s failed to reqeust IRQ(%d)\n", |
| pmuic->chip_name, __func__, pdata->irq_gpio); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| #define FREE_IRQ(_irq, _dev_id, _name) \ |
| do { \ |
| if (_irq) { \ |
| free_irq(_irq, _dev_id); \ |
| pr_info("%s:%s IRQ(%d):%s free done\n", MUIC_DEV_NAME, \ |
| __func__, _irq, _name); \ |
| } \ |
| } while (0) |
| static void muic_free_irqs(muic_data_t *pmuic) |
| { |
| pr_info("%s:%s\n", MUIC_DEV_NAME, __func__); |
| |
| /* free MUIC IRQ */ |
| FREE_IRQ(pmuic->irqs.irq_vbvolt, pmuic, "muic-vbvolt"); |
| FREE_IRQ(pmuic->irqs.irq_chgtyp, pmuic, "muic-chgtyp"); |
| FREE_IRQ(pmuic->irqs.irq_adc, pmuic, "muic-adc"); |
| FREE_IRQ(pmuic->irqs.irq_adcerr, pmuic, "muic-adcerr"); |
| FREE_IRQ(pmuic->irqs.irq_adc1k, pmuic, "muic-adc1k"); |
| #if defined(CONFIG_MUIC_SUPPORT_CCIC) |
| FREE_IRQ(pmuic->irqs.irq_dcdtmr, pmuic, "muic-dcdtmr"); |
| #endif |
| } |
| |
| static int muic_probe(struct platform_device *pdev) |
| { |
| struct muic_mfd_dev *pmfd = dev_get_drvdata(pdev->dev.parent); |
| struct muic_mfd_platform_data *mfd_pdata = dev_get_platdata(pmfd->dev); |
| struct muic_platform_data *pdata = &muic_pdata; |
| muic_data_t *pmuic; |
| struct regmap_desc *pdesc = NULL; |
| struct regmap_ops *pops = NULL; |
| int ret = 0; |
| |
| pr_info("%s:%s\n", MUIC_DEV_NAME, __func__); |
| |
| pmuic = kzalloc(sizeof(muic_data_t), GFP_KERNEL); |
| if (!pmuic) { |
| pr_err("%s: failed to allocate driver data\n", __func__); |
| ret = -ENOMEM; |
| goto err_return; |
| } |
| |
| |
| #if defined(CONFIG_OF) |
| ret = of_muic_dt(pmfd->i2c, pdata, pmuic); |
| if (ret < 0) { |
| pr_err("%s:%s Failed to get device of_node \n", |
| MUIC_DEV_NAME, __func__); |
| goto err_io; |
| } |
| |
| of_update_supported_list(pmfd->i2c, pdata); |
| vps_show_table(); |
| |
| #endif /* CONFIG_OF */ |
| if (!pdata) { |
| pr_err("%s: failed to get i2c platform data\n", __func__); |
| ret = -ENODEV; |
| goto err_io; |
| } |
| |
| mutex_init(&pmuic->muic_mutex); |
| |
| pmuic->pdata = pdata; |
| |
| pdata->irq_gpio = mfd_pdata->irq_base; |
| pr_info("%s: muic irq_gpio = %d\n", __func__, pdata->irq_gpio); |
| pr_info("%s: muic i2c client %s at 0x%02x\n", __func__, pmfd->muic->name, pmfd->muic->addr); |
| pr_info("%s: mfd i2c client %s at 0x%02x\n", __func__, pmfd->i2c->name, pmfd->i2c->addr); |
| pr_info("%s: fuel i2c client %s at 0x%02x\n", __func__, pmfd->fuel->name, pmfd->fuel->addr); |
| pr_info("%s: chg i2c client %s at 0x%02x\n", __func__, pmfd->chg->name, pmfd->chg->addr); |
| pmuic->i2c = pmfd->muic; |
| |
| pmuic->is_factory_start = false; |
| pmuic->is_otg_test = false; |
| pmuic->attached_dev = ATTACHED_DEV_UNKNOWN_MUIC; |
| pmuic->is_gamepad = false; |
| pmuic->is_dcdtmr_intr = false; |
| |
| if(!strcmp(pmuic->chip_name,"max,max77854")) |
| pmuic->vps_table = VPS_TYPE_TABLE; |
| else |
| pmuic->vps_table = VPS_TYPE_SCATTERED; |
| |
| pr_info("%s: VPS_TYPE=%d\n", __func__, pmuic->vps_table); |
| |
| if (!pdata->set_gpio_uart_sel) { |
| if (pmuic->gpio_uart_sel) { |
| pr_info("%s: gpio_uart_sel registered.\n", __func__); |
| pdata->set_gpio_uart_sel = muic_set_gpio_uart_sel; |
| } else |
| pr_info("%s: gpio_uart_sel is not supported.\n", __func__); |
| } |
| |
| if (pmuic->pdata->init_gpio_cb) { |
| ret = pmuic->pdata->init_gpio_cb(get_switch_sel()); |
| if (ret) { |
| pr_err("%s: failed to init gpio(%d)\n", __func__, ret); |
| goto fail_init_gpio; |
| } |
| } |
| |
| if (pmuic->pdata->init_switch_dev_cb) |
| pmuic->pdata->init_switch_dev_cb(); |
| |
| pr_info(" switch_sel : 0x%04x\n", get_switch_sel()); |
| |
| if (!(get_switch_sel() & SWITCH_SEL_RUSTPROOF_MASK)) { |
| pr_info(" Enable rustproof mode\n"); |
| pmuic->is_rustproof = true; |
| } else { |
| pr_info(" Disable rustproof mode\n"); |
| pmuic->is_rustproof = false; |
| } |
| |
| if (get_afc_mode() == CH_MODE_AFC_DISABLE_VAL) { |
| pr_info(" AFC mode disabled\n"); |
| pmuic->pdata->afc_disable = true; |
| } else { |
| pr_info(" AFC mode enabled\n"); |
| pmuic->pdata->afc_disable = false; |
| } |
| |
| /* Register chipset register map. */ |
| muic_register_regmap(&pdesc, pmuic); |
| pdesc->muic = pmuic; |
| pops = pdesc->regmapops; |
| pmuic->regmapdesc = pdesc; |
| |
| #if defined(CONFIG_MUIC_SUPPORT_CCIC) |
| pmuic->opmode = OPMODE_CCIC; |
| #endif |
| /* set switch device's driver_data */ |
| dev_set_drvdata(switch_device, pmuic); |
| dev_set_drvdata(&pdev->dev, pmuic); |
| /* create sysfs group */ |
| ret = sysfs_create_group(&switch_device->kobj, &muic_sysfs_group); |
| if (ret) { |
| pr_err("%s: failed to create sm5703 muic attribute group\n", |
| __func__); |
| goto fail; |
| } |
| |
| ret = pops->revision(pdesc); |
| if (ret) { |
| pr_err("%s: failed to init muic rev register(%d)\n", __func__, |
| ret); |
| goto fail; |
| } |
| |
| ret = muic_reg_init(pmuic); |
| if (ret) { |
| pr_err("%s: failed to init muic register(%d)\n", __func__, ret); |
| goto fail; |
| } |
| |
| pops->update(pdesc); |
| pops->show(pdesc); |
| |
| #if defined(CONFIG_MUIC_HV) |
| hv_initialize(pmuic, &pmuic->phv); |
| of_muic_hv_dt(pmuic); |
| hv_configure_AFC(pmuic->phv); |
| #endif |
| |
| coagent_update_ctx(pmuic); |
| |
| muic_irq_init(pmuic); |
| |
| /* initial cable detection */ |
| muic_init_detect(pmuic); |
| |
| #if defined(CONFIG_MUIC_HV) |
| pmuic->phv->is_muic_ready = true; |
| |
| printk("%s: is_afc_muic_ready=%d\n", __func__, pmuic->phv->is_afc_muic_ready); |
| if (pmuic->phv->is_afc_muic_ready) |
| max77854_hv_muic_init_detect(pmuic->phv); |
| #endif |
| |
| #ifdef DEBUG_MUIC |
| INIT_DELAYED_WORK(&pmuic->usb_work, muic_show_debug_info); |
| schedule_delayed_work(&pmuic->usb_work, msecs_to_jiffies(10000)); |
| #endif |
| |
| |
| #if defined(CONFIG_MUIC_SUPPORT_CCIC) |
| muic_is_ccic_supported_jig(pmuic, pmuic->attached_dev); |
| |
| if (pmuic->opmode & OPMODE_CCIC) |
| muic_register_ccic_notifier(pmuic); |
| else |
| pr_info("%s: OPMODE_MUIC. CCIC is not used.\n", __func__); |
| #endif |
| |
| #if defined(CONFIG_USB_EXTERNAL_NOTIFY) |
| muic_register_usb_notifier(pmuic); |
| #endif |
| muic_online = true; |
| |
| return 0; |
| |
| fail: |
| if (pmuic->pdata->cleanup_switch_dev_cb) |
| pmuic->pdata->cleanup_switch_dev_cb(); |
| sysfs_remove_group(&switch_device->kobj, &muic_sysfs_group); |
| mutex_unlock(&pmuic->muic_mutex); |
| mutex_destroy(&pmuic->muic_mutex); |
| fail_init_gpio: |
| |
| err_io: |
| kfree(pmuic); |
| err_return: |
| return ret; |
| } |
| |
| static int muic_remove(struct platform_device *pdev) |
| { |
| muic_data_t *pmuic = platform_get_drvdata(pdev); |
| sysfs_remove_group(&switch_device->kobj, &muic_sysfs_group); |
| |
| if (pmuic) { |
| pr_info("%s:%s\n", pmuic->chip_name, __func__); |
| |
| #if defined(CONFIG_MUIC_HV) |
| max77854_hv_muic_remove(pmuic->phv); |
| #endif |
| cancel_delayed_work(&pmuic->init_work); |
| cancel_delayed_work(&pmuic->usb_work); |
| disable_irq_wake(pmuic->i2c->irq); |
| free_irq(pmuic->i2c->irq, pmuic); |
| |
| if (pmuic->pdata->cleanup_switch_dev_cb) |
| pmuic->pdata->cleanup_switch_dev_cb(); |
| |
| #if defined(CONFIG_USB_EXTERNAL_NOTIFY) |
| muic_unregister_usb_notifier(pmuic); |
| #endif |
| mutex_destroy(&pmuic->muic_mutex); |
| i2c_set_clientdata(pmuic->i2c, NULL); |
| kfree(pmuic); |
| } |
| muic_online = false; |
| return 0; |
| } |
| |
| #if defined(CONFIG_OF) |
| static struct of_device_id muic_i2c_dt_ids[] = { |
| { .compatible = "muic-universal" }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, muic_i2c_dt_ids); |
| #endif /* CONFIG_OF */ |
| |
| static void muic_shutdown(struct device *dev) |
| { |
| muic_data_t *pmuic = dev_get_drvdata(dev); |
| int ret; |
| if(pmuic == NULL) |
| { |
| printk("sabin pmuic is NULL\n"); |
| return; |
| } |
| printk("sabin muic_shutdown start\n"); |
| pr_info("%s:%s\n", pmuic->chip_name, __func__); |
| if (!pmuic->i2c) { |
| pr_err("%s:%s no muic i2c client\n", pmuic->chip_name, __func__); |
| return; |
| } |
| |
| muic_free_irqs(pmuic); |
| |
| #if defined(CONFIG_MUIC_HV) |
| max77854_hv_muic_remove(pmuic->phv); |
| #endif |
| |
| if (cancel_delayed_work(&pmuic->usb_work)) |
| pr_info("%s: usb_work canceled.\n", __func__); |
| else |
| pr_info("%s: usb_work is not pending.\n", __func__); |
| |
| |
| if (cancel_delayed_work(&pmuic->init_work)) |
| pr_info("%s: init_work canceled.\n", __func__); |
| else |
| pr_info("%s: init_work is not pending.\n", __func__); |
| pr_info("%s:%s open D+,D-\n", pmuic->chip_name, __func__); |
| ret = com_to_open_with_vbus(pmuic); |
| if (ret < 0) |
| pr_err("%s:%s fail to open mansw1 reg\n", pmuic->chip_name, |
| __func__); |
| |
| /* set auto sw mode before shutdown to make sure device goes into */ |
| /* LPM charging when TA or USB is connected during off state */ |
| pr_info("%s:%s muic auto detection enable\n", pmuic->chip_name, __func__); |
| set_switch_mode(pmuic,SWMODE_AUTO); |
| |
| if (pmuic->pdata && pmuic->pdata->cleanup_switch_dev_cb) |
| pmuic->pdata->cleanup_switch_dev_cb(); |
| |
| muic_online = false; |
| pr_info("%s:%s -\n", MUIC_DEV_NAME, __func__); |
| } |
| |
| #if defined(CONFIG_PM) |
| static int muic_suspend(struct device *dev) |
| { |
| muic_data_t *pmuic = dev_get_drvdata(dev); |
| |
| cancel_delayed_work(&pmuic->usb_work); |
| |
| return 0; |
| } |
| |
| static int muic_resume(struct device *dev) |
| { |
| muic_data_t *pmuic = dev_get_drvdata(dev); |
| |
| schedule_delayed_work(&pmuic->usb_work, msecs_to_jiffies(1000)); |
| |
| return 0; |
| } |
| |
| const struct dev_pm_ops muic_pm = { |
| .suspend = muic_suspend, |
| .resume = muic_resume, |
| }; |
| #endif /* CONFIG_PM */ |
| |
| static struct platform_driver muic_driver = { |
| .driver = { |
| .name = MUIC_DEV_NAME, |
| .owner = THIS_MODULE, |
| .shutdown = muic_shutdown, |
| #if defined(CONFIG_OF) |
| .of_match_table = muic_i2c_dt_ids, |
| #endif /* CONFIG_OF */ |
| #if defined(CONFIG_PM) |
| .pm = &muic_pm, |
| #endif /* CONFIG_PM */ |
| }, |
| .probe = muic_probe, |
| .remove = muic_remove, |
| }; |
| |
| static int __init muic_init(void) |
| { |
| pr_info("%s:%s\n", MUIC_DEV_NAME, __func__); |
| return platform_driver_register(&muic_driver); |
| } |
| module_init(muic_init); |
| |
| static void muic_exit(void) |
| { |
| pr_info("%s:%s\n", MUIC_DEV_NAME, __func__); |
| platform_driver_unregister(&muic_driver); |
| } |
| module_exit(muic_exit); |
| |
| MODULE_DESCRIPTION("MUIC driver"); |
| MODULE_AUTHOR("<smilesr.ryu@samsung.com>"); |
| MODULE_LICENSE("GPL"); |