/* sfp_phy.c - Puma SFP Phy module * * Copyright (C) 2016-2017 Intel Corporation. All rights reserved. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "sfp_phy.h" #include #include static void sfp_service_handler(struct work_struct *cmd_work) { struct puma7_sfp_phy *sfp_hdlr; int txfault = 0, los = 0, present = 0, servicelevel = 0; sfp_hdlr = container_of(cmd_work, struct puma7_sfp_phy, cmd_work); mutex_lock(&sfp_hdlr->event_lock); if (atomic_read(&sfp_hdlr->txfault_event)) { atomic_set(&sfp_hdlr->txfault_event, 0); txfault = PROCESS_TXFAULT; } if (atomic_read(&sfp_hdlr->los_event)) { atomic_set(&sfp_hdlr->los_event, 0); los = PROCESS_LOS; } if (atomic_read(&sfp_hdlr->present_event)) { atomic_set(&sfp_hdlr->present_event, 0); present = PROCESS_TXFAULT; } if (atomic_read(&sfp_hdlr->servicelevel_event)) { atomic_set(&sfp_hdlr->servicelevel_event, 0); if (atomic_read( &sfp_hdlr->service_level_value) == SFP_EVENT_AC) servicelevel = PROCESS_ONAC; else if (atomic_read( &sfp_hdlr->service_level_value) == SFP_EVENT_ONBAT) servicelevel = PROCESS_ONBAT; } /* Process TxFault ISR deferred handling logic */ if (txfault) { if (sfp_hdlr->state == SFP_STATE_ACTIVE) sfp_hdlr->txfault_counter += 1; } /* Process LOS ISR deferred handling logic */ if (los) { if (sfp_hdlr->state == SFP_STATE_ACTIVE) sfp_hdlr->los_counter += 1; } /* Process Present ISR deferred handling logic */ if (present) { if (sfp_hdlr->state == SFP_STATE_ON) sfp_hdlr->state = SFP_STATE_ACTIVE; if (sfp_hdlr->state == SFP_STATE_ACTIVE) { int gpiopresent; gpiopresent = gpiod_get_value_cansleep( sfp_hdlr->gpio_present ); if (gpiopresent == SFP_PRESENT) { gpiod_set_value_cansleep( sfp_hdlr->gpio_tx_disable, SFP_TX_ENABLE); /* Trigger a NetLink SFP active event * to Power Manager */ acpi_bus_generate_netlink_event( PUMA_SFP_CLASS, PUMA_SFP_DEVICE_NAME, PUMA_ACPI_NOTIFY_SFP_EVENT, SFP_PRESENT); } else { sfp_hdlr->state = SFP_STATE_ON; /* Trigger a NetLink SFP inactive * event to Power Manager */ acpi_bus_generate_netlink_event( PUMA_SFP_CLASS, PUMA_SFP_DEVICE_NAME, PUMA_ACPI_NOTIFY_SFP_EVENT, SFP_NOT_PRESENT); } } } /* Process service_Level OnAC sysfs deferred handling logic */ if (servicelevel == PROCESS_ONAC) { if (sfp_hdlr->state == SFP_STATE_OFF) sfp_hdlr->state = SFP_STATE_ON; if (sfp_hdlr->state == SFP_STATE_ON) { gpiod_set_value_cansleep(sfp_hdlr->gpio_power, SFP_POWER_ON); if (gpiod_get_value_cansleep( sfp_hdlr->gpio_present) == 0) { sfp_hdlr->state = SFP_STATE_ACTIVE; gpiod_set_value_cansleep( sfp_hdlr->gpio_tx_disable, SFP_TX_ENABLE); acpi_bus_generate_netlink_event( PUMA_SFP_CLASS, PUMA_SFP_DEVICE_NAME, PUMA_ACPI_NOTIFY_SFP_EVENT, SFP_PRESENT); } } } /* Process service_Level OnBat sysfs deferred handling logic */ if (servicelevel == PROCESS_ONBAT) { if ((sfp_hdlr->state == SFP_STATE_ON) || (sfp_hdlr->state == SFP_STATE_ACTIVE)) { sfp_hdlr->state = SFP_STATE_OFF; sfp_hdlr->los_counter = 0; sfp_hdlr->txfault_counter = 0; } } mutex_unlock(&sfp_hdlr->event_lock); } /* SFP interrupt handlers */ static irqreturn_t sfp_txfault_isr(int irq, void *data) { struct puma7_sfp_phy *sfp_tx = data; atomic_set(&sfp_tx->txfault_event, 1); queue_work(sfp_tx->work, &sfp_tx->cmd_work); return IRQ_HANDLED; } static irqreturn_t sfp_los_isr(int irq, void *data) { struct puma7_sfp_phy *sfp_los = data; atomic_set(&sfp_los->los_event, 1); queue_work(sfp_los->work, &sfp_los->cmd_work); return IRQ_HANDLED; } static irqreturn_t sfp_present_isr(int irq, void *data) { struct puma7_sfp_phy *sfp_pst = data; atomic_set(&sfp_pst->present_event, 1); queue_work(sfp_pst->work, &sfp_pst->cmd_work); return IRQ_HANDLED; } /* Sysfs attributes for SFP: * Show/Store Service_level from Power Manager * TxFault counter * Los counter * Read SFP state variable */ static ssize_t show_service_level(struct device *dev, struct device_attribute *dattr, char *buf) { struct puma7_sfp_phy *sfp_sysfs = dev_get_drvdata(dev); return sprintf(buf, "0x%08X\n", atomic_read(&sfp_sysfs->service_level_value)); } static ssize_t store_service_level(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long service_level; struct puma7_sfp_phy *sfp_sysfs = dev_get_drvdata(dev); int ret; ret = kstrtoul(buf, 0, &service_level); if(ret != 0) return ret; atomic_set(&sfp_sysfs->service_level_value, service_level); atomic_set(&sfp_sysfs->servicelevel_event, 1); queue_work(sfp_sysfs->work, &sfp_sysfs->cmd_work); return count; } static ssize_t show_txfault_count(struct device *dev, struct device_attribute *dattr, char *buf) { struct puma7_sfp_phy *sfp_sysfs = dev_get_drvdata(dev); return sprintf(buf, "0x%08X\n", (unsigned int)sfp_sysfs->txfault_counter); } static ssize_t show_los_count(struct device *dev, struct device_attribute *dattr, char *buf) { struct puma7_sfp_phy *sfp_sysfs = dev_get_drvdata(dev); return sprintf(buf, "0x%08X\n", (unsigned int)sfp_sysfs->los_counter); } static ssize_t show_read_state(struct device *dev, struct device_attribute *dattr, char *buf) { struct puma7_sfp_phy *sfp_sysfs = dev_get_drvdata(dev); return sprintf(buf, "0x%08X\n", (unsigned int)sfp_sysfs->state); } static DEVICE_ATTR(service_level, S_IWUSR | S_IWGRP | S_IRUGO, show_service_level, store_service_level); static DEVICE_ATTR(txfault_count, S_IRUGO, show_txfault_count, NULL); static DEVICE_ATTR(los_count, S_IRUGO, show_los_count, NULL); static DEVICE_ATTR(read_state, S_IRUGO, show_read_state, NULL); static struct attribute *sfp_attributes[] = { &dev_attr_service_level.attr, &dev_attr_txfault_count.attr, &dev_attr_los_count.attr, &dev_attr_read_state.attr, NULL, }; static const struct attribute_group sfp_attr_group = { .name = NULL, .attrs = sfp_attributes, }; void sfp_free_resources(struct puma7_sfp_phy *data, int resource_index, struct device *dev) { switch (resource_index) { case PUMA_SFP_POWER_GPIO: gpiod_put(data->gpio_power); fallthrough; case PUMA_SFP_TXDISABLE_GPIO: gpiod_put(data->gpio_tx_disable); fallthrough; case PUMA_SFP_PRESENT_IRQ: devm_free_irq(dev, data->present_irq, data); fallthrough; case PUMA_SFP_PRESENT_GPIO: gpiod_put(data->gpio_present); fallthrough; case PUMA_SFP_LOS_IRQ: devm_free_irq(dev, data->los_irq, data); fallthrough; case PUMA_SFP_LOS_GPIO: gpiod_put(data->gpio_los); fallthrough; case PUMA_SFP_TXFAULT_IRQ: devm_free_irq(dev, data->txfault_irq, data); fallthrough; case PUMA_SFP_TXFAULT_GPIO: gpiod_put(data->gpio_txfault); fallthrough; case PUMA_SFP_FREE_WQ: cancel_work_sync(&data->cmd_work); flush_workqueue(data->work); destroy_workqueue(data->work); fallthrough; case PUMA_SFP_SYSFS: sysfs_remove_group(&dev->kobj, &sfp_attr_group); fallthrough; case PUMA_SFP_MUTEX: mutex_destroy(&data->event_lock); fallthrough; case PUMA_SFP_MEM: devm_kfree(dev, data); break; default: pr_err("Puma SFP invalid resource index %d\n", resource_index); } } static int sfp_phy_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct puma7_sfp_phy *sfp; struct device *dev = &client->dev; int ret = -EINVAL; /*points to the last resource successfully allocated*/ int resource_index; sfp = devm_kzalloc(dev, sizeof(struct puma7_sfp_phy), GFP_KERNEL); if (!sfp) { dev_err(&client->dev, "sfp_phy_probe alloc error! %s\n", __func__); return -ENOMEM; } resource_index = PUMA_SFP_MEM; sfp->client = client; sfp->handle = ACPI_HANDLE(dev); i2c_set_clientdata(client, sfp); sfp->client->flags |= I2C_FUNC_10BIT_ADDR; atomic_set(&sfp->txfault_event, 0); mutex_init(&sfp->event_lock); resource_index = PUMA_SFP_MUTEX; /* Create and register sysfs attributes */ ret = sysfs_create_group(&client->dev.kobj, &sfp_attr_group); if (ret) { dev_err(&sfp->client->dev, "sfp_phy_probe sysfs_create_group error! %s\n", __func__); goto sfp_probe_error; } resource_index = PUMA_SFP_SYSFS; /* The variable when set indicates that an event * has happened in SFP.SFP event handling thread sleeps * in the work queue until this variable is non-zero */ sfp->state = SFP_STATE_OFF; sfp->work = create_singlethread_workqueue( "sfp_service_handler"); INIT_WORK(&sfp->cmd_work, sfp_service_handler); resource_index = PUMA_SFP_FREE_WQ; /* SFP TX FAULT GPIO IRQ */ sfp->gpio_txfault = devm_gpiod_get_index(dev, NULL, TXFAULT_IDX, GPIOD_IN); if (IS_ERR(sfp->gpio_txfault)) { ret = PTR_ERR(sfp->gpio_txfault); dev_err(dev, "GPIO INT resource not found for %s:%s\n", strs[TXFAULT_IDX], __func__); goto sfp_probe_error; } resource_index = PUMA_SFP_TXFAULT_GPIO; sfp->txfault_irq = gpiod_to_irq(sfp->gpio_txfault); if (sfp->txfault_irq < 0) { ret = sfp->txfault_irq; dev_err(dev, "Failed to get IRQ number for GPIO TxFault: %d\n", desc_to_gpio(sfp->gpio_txfault)); goto sfp_probe_error; } ret = devm_request_irq(dev, sfp->txfault_irq, sfp_txfault_isr, IRQF_TRIGGER_RISING, PUMA_SFP_DEVICE_NAME, sfp); if (ret < 0) { dev_err(dev, "Failed to request TxFault IRQ: %d\n", sfp->txfault_irq); goto sfp_probe_error; } resource_index = PUMA_SFP_TXFAULT_IRQ; /* SFP LOS GPIO IRQ */ sfp->gpio_los = devm_gpiod_get_index(dev, NULL, LOS_IDX, GPIOD_IN); if (IS_ERR(sfp->gpio_los)) { ret = PTR_ERR(sfp->gpio_los); dev_err(dev, "GPIO INT resource not found for %s:%s\n", strs[LOS_IDX], __func__); goto sfp_probe_error; } resource_index = PUMA_SFP_LOS_GPIO; sfp->los_irq = gpiod_to_irq(sfp->gpio_los); if (sfp->los_irq < 0) { ret = sfp->los_irq; dev_err(dev, "Failed to get IRQ number for GPIO LOS: %d\n", desc_to_gpio(sfp->gpio_los)); goto sfp_probe_error; } ret = devm_request_irq(dev, sfp->los_irq, sfp_los_isr, IRQF_TRIGGER_RISING, PUMA_SFP_DEVICE_NAME, sfp); if (ret < 0) { dev_err(dev, "Failed to request LOS IRQ:%d\n", sfp->los_irq); goto sfp_probe_error; } resource_index = PUMA_SFP_LOS_IRQ; /* SFP PRESENT GPIO IRQ */ sfp->gpio_present = devm_gpiod_get_index(dev, NULL, PRESENT_IDX, GPIOD_IN); if (IS_ERR(sfp->gpio_present)) { ret = PTR_ERR(sfp->gpio_present); dev_err(dev, "GPIO INT resource not found for %s:%s\n", strs[PRESENT_IDX], __func__); goto sfp_probe_error; } resource_index = PUMA_SFP_PRESENT_GPIO; sfp->present_irq = gpiod_to_irq(sfp->gpio_present); if (sfp->present_irq < 0) { ret = sfp->present_irq; dev_err(dev, "Failed to get IRQ number for GPIO Present: %d\n", desc_to_gpio(sfp->gpio_present)); goto sfp_probe_error; } ret = devm_request_irq(dev, sfp->present_irq, sfp_present_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, PUMA_SFP_DEVICE_NAME, sfp); if (ret < 0) { dev_err(dev, "Failed to request SFP Present IRQ: %d:\n", sfp->present_irq); goto sfp_probe_error; } resource_index = PUMA_SFP_PRESENT_IRQ; /* SFP Tx Disable GPIO */ sfp->gpio_tx_disable = devm_gpiod_get_index(dev, NULL, DISABLE_IDX, GPIOD_OUT_HIGH); if (IS_ERR(sfp->gpio_tx_disable)) { ret = PTR_ERR(sfp->gpio_tx_disable); dev_err(dev, "GPIO INT resource not found for %s:%s\n", strs[DISABLE_IDX], __func__); goto sfp_probe_error; } resource_index = PUMA_SFP_TXDISABLE_GPIO; /* SFP Power GPIO */ sfp->gpio_power = devm_gpiod_get_index(dev, NULL, POWER_IDX, GPIOD_OUT_LOW); if (IS_ERR(sfp->gpio_power)) { ret = PTR_ERR(sfp->gpio_power); dev_err(dev, "GPIO INT resource not found for %s:%s\n", strs[POWER_IDX], __func__); goto sfp_probe_error; } resource_index = PUMA_SFP_POWER_GPIO; printk(KERN_INFO "Initializing SFP driver\n"); return 0; sfp_probe_error: sfp_free_resources(sfp, resource_index, dev); pr_err("Failed to load Puma SFP driver! ret=%d\n", ret); return ret; } static int sfp_phy_remove(struct i2c_client *client) { struct device *dev = &client->dev; struct puma7_sfp_phy *sfp_rm = dev_get_drvdata(dev); sfp_free_resources(sfp_rm, PUMA_SFP_POWER_GPIO, dev); return 0; } static const struct i2c_device_id puma7_sfp_id[] = { { .name = PUMA_SFP_DEVICE_NAME, .driver_data = 0 }, { } }; MODULE_DEVICE_TABLE(i2c, puma7_sfp_id); static struct acpi_device_id puma7_sfp_acpi_match[] = { { PUMA_SFP_DEVICE_NAME, 0}, { }, }; MODULE_DEVICE_TABLE(acpi, puma7_sfp_acpi_match); static struct i2c_driver sfp_phy_driver = { .probe = sfp_phy_probe, .remove = sfp_phy_remove, .driver = { .name = PUMA_SFP_DEVICE_NAME, .owner = THIS_MODULE, .acpi_match_table = ACPI_PTR(puma7_sfp_acpi_match), }, .id_table = puma7_sfp_id, }; module_i2c_driver(sfp_phy_driver); MODULE_DESCRIPTION("SFP Phy driver"); MODULE_LICENSE("GPL v2");