18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Cavium ThunderX i2c driver. 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2015,2016 Cavium Inc. 58c2ecf20Sopenharmony_ci * Authors: Fred Martin <fmartin@caviumnetworks.com> 68c2ecf20Sopenharmony_ci * Jan Glauber <jglauber@cavium.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 98c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any 108c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/acpi.h> 148c2ecf20Sopenharmony_ci#include <linux/clk.h> 158c2ecf20Sopenharmony_ci#include <linux/delay.h> 168c2ecf20Sopenharmony_ci#include <linux/i2c.h> 178c2ecf20Sopenharmony_ci#include <linux/i2c-smbus.h> 188c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 198c2ecf20Sopenharmony_ci#include <linux/kernel.h> 208c2ecf20Sopenharmony_ci#include <linux/module.h> 218c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 228c2ecf20Sopenharmony_ci#include <linux/pci.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include "i2c-octeon-core.h" 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define DRV_NAME "i2c-thunderx" 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define PCI_DEVICE_ID_THUNDER_TWSI 0xa012 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define SYS_FREQ_DEFAULT 700000000 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define TWSI_INT_ENA_W1C 0x1028 338c2ecf20Sopenharmony_ci#define TWSI_INT_ENA_W1S 0x1030 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/* 368c2ecf20Sopenharmony_ci * Enable the CORE interrupt. 378c2ecf20Sopenharmony_ci * The interrupt will be asserted when there is non-STAT_IDLE state in the 388c2ecf20Sopenharmony_ci * SW_TWSI_EOP_TWSI_STAT register. 398c2ecf20Sopenharmony_ci */ 408c2ecf20Sopenharmony_cistatic void thunder_i2c_int_enable(struct octeon_i2c *i2c) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci octeon_i2c_writeq_flush(TWSI_INT_CORE_INT, 438c2ecf20Sopenharmony_ci i2c->twsi_base + TWSI_INT_ENA_W1S); 448c2ecf20Sopenharmony_ci} 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci/* 478c2ecf20Sopenharmony_ci * Disable the CORE interrupt. 488c2ecf20Sopenharmony_ci */ 498c2ecf20Sopenharmony_cistatic void thunder_i2c_int_disable(struct octeon_i2c *i2c) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci octeon_i2c_writeq_flush(TWSI_INT_CORE_INT, 528c2ecf20Sopenharmony_ci i2c->twsi_base + TWSI_INT_ENA_W1C); 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic void thunder_i2c_hlc_int_enable(struct octeon_i2c *i2c) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci octeon_i2c_writeq_flush(TWSI_INT_ST_INT | TWSI_INT_TS_INT, 588c2ecf20Sopenharmony_ci i2c->twsi_base + TWSI_INT_ENA_W1S); 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic void thunder_i2c_hlc_int_disable(struct octeon_i2c *i2c) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci octeon_i2c_writeq_flush(TWSI_INT_ST_INT | TWSI_INT_TS_INT, 648c2ecf20Sopenharmony_ci i2c->twsi_base + TWSI_INT_ENA_W1C); 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic u32 thunderx_i2c_functionality(struct i2c_adapter *adap) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) | 708c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_SMBUS_BLOCK_PROC_CALL; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic const struct i2c_algorithm thunderx_i2c_algo = { 748c2ecf20Sopenharmony_ci .master_xfer = octeon_i2c_xfer, 758c2ecf20Sopenharmony_ci .functionality = thunderx_i2c_functionality, 768c2ecf20Sopenharmony_ci}; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic const struct i2c_adapter thunderx_i2c_ops = { 798c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 808c2ecf20Sopenharmony_ci .name = "ThunderX adapter", 818c2ecf20Sopenharmony_ci .algo = &thunderx_i2c_algo, 828c2ecf20Sopenharmony_ci}; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic void thunder_i2c_clock_enable(struct device *dev, struct octeon_i2c *i2c) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci int ret; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci if (acpi_disabled) { 898c2ecf20Sopenharmony_ci /* DT */ 908c2ecf20Sopenharmony_ci i2c->clk = clk_get(dev, NULL); 918c2ecf20Sopenharmony_ci if (IS_ERR(i2c->clk)) { 928c2ecf20Sopenharmony_ci i2c->clk = NULL; 938c2ecf20Sopenharmony_ci goto skip; 948c2ecf20Sopenharmony_ci } 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci ret = clk_prepare_enable(i2c->clk); 978c2ecf20Sopenharmony_ci if (ret) 988c2ecf20Sopenharmony_ci goto skip; 998c2ecf20Sopenharmony_ci i2c->sys_freq = clk_get_rate(i2c->clk); 1008c2ecf20Sopenharmony_ci } else { 1018c2ecf20Sopenharmony_ci /* ACPI */ 1028c2ecf20Sopenharmony_ci device_property_read_u32(dev, "sclk", &i2c->sys_freq); 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ciskip: 1068c2ecf20Sopenharmony_ci if (!i2c->sys_freq) 1078c2ecf20Sopenharmony_ci i2c->sys_freq = SYS_FREQ_DEFAULT; 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic void thunder_i2c_clock_disable(struct device *dev, struct clk *clk) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci if (!clk) 1138c2ecf20Sopenharmony_ci return; 1148c2ecf20Sopenharmony_ci clk_disable_unprepare(clk); 1158c2ecf20Sopenharmony_ci clk_put(clk); 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic int thunder_i2c_smbus_setup_of(struct octeon_i2c *i2c, 1198c2ecf20Sopenharmony_ci struct device_node *node) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci struct i2c_client *ara; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci if (!node) 1248c2ecf20Sopenharmony_ci return -EINVAL; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci i2c->alert_data.irq = irq_of_parse_and_map(node, 0); 1278c2ecf20Sopenharmony_ci if (!i2c->alert_data.irq) 1288c2ecf20Sopenharmony_ci return -EINVAL; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci ara = i2c_new_smbus_alert_device(&i2c->adap, &i2c->alert_data); 1318c2ecf20Sopenharmony_ci if (IS_ERR(ara)) 1328c2ecf20Sopenharmony_ci return PTR_ERR(ara); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci i2c->ara = ara; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci return 0; 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cistatic int thunder_i2c_smbus_setup(struct octeon_i2c *i2c, 1408c2ecf20Sopenharmony_ci struct device_node *node) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci /* TODO: ACPI support */ 1438c2ecf20Sopenharmony_ci if (!acpi_disabled) 1448c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci return thunder_i2c_smbus_setup_of(i2c, node); 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_cistatic void thunder_i2c_smbus_remove(struct octeon_i2c *i2c) 1508c2ecf20Sopenharmony_ci{ 1518c2ecf20Sopenharmony_ci i2c_unregister_device(i2c->ara); 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic int thunder_i2c_probe_pci(struct pci_dev *pdev, 1558c2ecf20Sopenharmony_ci const struct pci_device_id *ent) 1568c2ecf20Sopenharmony_ci{ 1578c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1588c2ecf20Sopenharmony_ci struct octeon_i2c *i2c; 1598c2ecf20Sopenharmony_ci int ret; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); 1628c2ecf20Sopenharmony_ci if (!i2c) 1638c2ecf20Sopenharmony_ci return -ENOMEM; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci i2c->roff.sw_twsi = 0x1000; 1668c2ecf20Sopenharmony_ci i2c->roff.twsi_int = 0x1010; 1678c2ecf20Sopenharmony_ci i2c->roff.sw_twsi_ext = 0x1018; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci i2c->dev = dev; 1708c2ecf20Sopenharmony_ci pci_set_drvdata(pdev, i2c); 1718c2ecf20Sopenharmony_ci ret = pcim_enable_device(pdev); 1728c2ecf20Sopenharmony_ci if (ret) 1738c2ecf20Sopenharmony_ci return ret; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci ret = pci_request_regions(pdev, DRV_NAME); 1768c2ecf20Sopenharmony_ci if (ret) 1778c2ecf20Sopenharmony_ci return ret; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci i2c->twsi_base = pcim_iomap(pdev, 0, pci_resource_len(pdev, 0)); 1808c2ecf20Sopenharmony_ci if (!i2c->twsi_base) 1818c2ecf20Sopenharmony_ci return -EINVAL; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci thunder_i2c_clock_enable(dev, i2c); 1848c2ecf20Sopenharmony_ci ret = device_property_read_u32(dev, "clock-frequency", &i2c->twsi_freq); 1858c2ecf20Sopenharmony_ci if (ret) 1868c2ecf20Sopenharmony_ci i2c->twsi_freq = I2C_MAX_STANDARD_MODE_FREQ; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci init_waitqueue_head(&i2c->queue); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci i2c->int_enable = thunder_i2c_int_enable; 1918c2ecf20Sopenharmony_ci i2c->int_disable = thunder_i2c_int_disable; 1928c2ecf20Sopenharmony_ci i2c->hlc_int_enable = thunder_i2c_hlc_int_enable; 1938c2ecf20Sopenharmony_ci i2c->hlc_int_disable = thunder_i2c_hlc_int_disable; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX); 1968c2ecf20Sopenharmony_ci if (ret < 0) 1978c2ecf20Sopenharmony_ci goto error; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci ret = devm_request_irq(dev, pci_irq_vector(pdev, 0), octeon_i2c_isr, 0, 2008c2ecf20Sopenharmony_ci DRV_NAME, i2c); 2018c2ecf20Sopenharmony_ci if (ret) 2028c2ecf20Sopenharmony_ci goto error; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci ret = octeon_i2c_init_lowlevel(i2c); 2058c2ecf20Sopenharmony_ci if (ret) 2068c2ecf20Sopenharmony_ci goto error; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci octeon_i2c_set_clock(i2c); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci i2c->adap = thunderx_i2c_ops; 2118c2ecf20Sopenharmony_ci i2c->adap.retries = 5; 2128c2ecf20Sopenharmony_ci i2c->adap.class = I2C_CLASS_HWMON; 2138c2ecf20Sopenharmony_ci i2c->adap.bus_recovery_info = &octeon_i2c_recovery_info; 2148c2ecf20Sopenharmony_ci i2c->adap.dev.parent = dev; 2158c2ecf20Sopenharmony_ci i2c->adap.dev.of_node = pdev->dev.of_node; 2168c2ecf20Sopenharmony_ci i2c->adap.dev.fwnode = dev->fwnode; 2178c2ecf20Sopenharmony_ci snprintf(i2c->adap.name, sizeof(i2c->adap.name), 2188c2ecf20Sopenharmony_ci "Cavium ThunderX i2c adapter at %s", dev_name(dev)); 2198c2ecf20Sopenharmony_ci i2c_set_adapdata(&i2c->adap, i2c); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci ret = i2c_add_adapter(&i2c->adap); 2228c2ecf20Sopenharmony_ci if (ret) 2238c2ecf20Sopenharmony_ci goto error; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci dev_info(i2c->dev, "Probed. Set system clock to %u\n", i2c->sys_freq); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci ret = thunder_i2c_smbus_setup(i2c, pdev->dev.of_node); 2288c2ecf20Sopenharmony_ci if (ret) 2298c2ecf20Sopenharmony_ci dev_info(dev, "SMBUS alert not active on this bus\n"); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci return 0; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_cierror: 2348c2ecf20Sopenharmony_ci thunder_i2c_clock_disable(dev, i2c->clk); 2358c2ecf20Sopenharmony_ci return ret; 2368c2ecf20Sopenharmony_ci} 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cistatic void thunder_i2c_remove_pci(struct pci_dev *pdev) 2398c2ecf20Sopenharmony_ci{ 2408c2ecf20Sopenharmony_ci struct octeon_i2c *i2c = pci_get_drvdata(pdev); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci thunder_i2c_smbus_remove(i2c); 2438c2ecf20Sopenharmony_ci thunder_i2c_clock_disable(&pdev->dev, i2c->clk); 2448c2ecf20Sopenharmony_ci i2c_del_adapter(&i2c->adap); 2458c2ecf20Sopenharmony_ci} 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_cistatic const struct pci_device_id thunder_i2c_pci_id_table[] = { 2488c2ecf20Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_THUNDER_TWSI) }, 2498c2ecf20Sopenharmony_ci { 0, } 2508c2ecf20Sopenharmony_ci}; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, thunder_i2c_pci_id_table); 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic struct pci_driver thunder_i2c_pci_driver = { 2558c2ecf20Sopenharmony_ci .name = DRV_NAME, 2568c2ecf20Sopenharmony_ci .id_table = thunder_i2c_pci_id_table, 2578c2ecf20Sopenharmony_ci .probe = thunder_i2c_probe_pci, 2588c2ecf20Sopenharmony_ci .remove = thunder_i2c_remove_pci, 2598c2ecf20Sopenharmony_ci}; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cimodule_pci_driver(thunder_i2c_pci_driver); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2648c2ecf20Sopenharmony_ciMODULE_AUTHOR("Fred Martin <fmartin@caviumnetworks.com>"); 2658c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("I2C-Bus adapter for Cavium ThunderX SOC"); 266