162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Cavium ThunderX i2c driver. 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 2015,2016 Cavium Inc. 562306a36Sopenharmony_ci * Authors: Fred Martin <fmartin@caviumnetworks.com> 662306a36Sopenharmony_ci * Jan Glauber <jglauber@cavium.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 962306a36Sopenharmony_ci * License version 2. This program is licensed "as is" without any 1062306a36Sopenharmony_ci * warranty of any kind, whether express or implied. 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/acpi.h> 1462306a36Sopenharmony_ci#include <linux/clk.h> 1562306a36Sopenharmony_ci#include <linux/delay.h> 1662306a36Sopenharmony_ci#include <linux/i2c.h> 1762306a36Sopenharmony_ci#include <linux/i2c-smbus.h> 1862306a36Sopenharmony_ci#include <linux/interrupt.h> 1962306a36Sopenharmony_ci#include <linux/kernel.h> 2062306a36Sopenharmony_ci#include <linux/module.h> 2162306a36Sopenharmony_ci#include <linux/of_irq.h> 2262306a36Sopenharmony_ci#include <linux/pci.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include "i2c-octeon-core.h" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define DRV_NAME "i2c-thunderx" 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define PCI_DEVICE_ID_THUNDER_TWSI 0xa012 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define SYS_FREQ_DEFAULT 700000000 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define TWSI_INT_ENA_W1C 0x1028 3362306a36Sopenharmony_ci#define TWSI_INT_ENA_W1S 0x1030 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/* 3662306a36Sopenharmony_ci * Enable the CORE interrupt. 3762306a36Sopenharmony_ci * The interrupt will be asserted when there is non-STAT_IDLE state in the 3862306a36Sopenharmony_ci * SW_TWSI_EOP_TWSI_STAT register. 3962306a36Sopenharmony_ci */ 4062306a36Sopenharmony_cistatic void thunder_i2c_int_enable(struct octeon_i2c *i2c) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci octeon_i2c_writeq_flush(TWSI_INT_CORE_INT, 4362306a36Sopenharmony_ci i2c->twsi_base + TWSI_INT_ENA_W1S); 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/* 4762306a36Sopenharmony_ci * Disable the CORE interrupt. 4862306a36Sopenharmony_ci */ 4962306a36Sopenharmony_cistatic void thunder_i2c_int_disable(struct octeon_i2c *i2c) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci octeon_i2c_writeq_flush(TWSI_INT_CORE_INT, 5262306a36Sopenharmony_ci i2c->twsi_base + TWSI_INT_ENA_W1C); 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic void thunder_i2c_hlc_int_enable(struct octeon_i2c *i2c) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci octeon_i2c_writeq_flush(TWSI_INT_ST_INT | TWSI_INT_TS_INT, 5862306a36Sopenharmony_ci i2c->twsi_base + TWSI_INT_ENA_W1S); 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic void thunder_i2c_hlc_int_disable(struct octeon_i2c *i2c) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci octeon_i2c_writeq_flush(TWSI_INT_ST_INT | TWSI_INT_TS_INT, 6462306a36Sopenharmony_ci i2c->twsi_base + TWSI_INT_ENA_W1C); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic u32 thunderx_i2c_functionality(struct i2c_adapter *adap) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) | 7062306a36Sopenharmony_ci I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_SMBUS_BLOCK_PROC_CALL; 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic const struct i2c_algorithm thunderx_i2c_algo = { 7462306a36Sopenharmony_ci .master_xfer = octeon_i2c_xfer, 7562306a36Sopenharmony_ci .functionality = thunderx_i2c_functionality, 7662306a36Sopenharmony_ci}; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic const struct i2c_adapter thunderx_i2c_ops = { 7962306a36Sopenharmony_ci .owner = THIS_MODULE, 8062306a36Sopenharmony_ci .name = "ThunderX adapter", 8162306a36Sopenharmony_ci .algo = &thunderx_i2c_algo, 8262306a36Sopenharmony_ci}; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic void thunder_i2c_clock_enable(struct device *dev, struct octeon_i2c *i2c) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci int ret; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci if (acpi_disabled) { 8962306a36Sopenharmony_ci /* DT */ 9062306a36Sopenharmony_ci i2c->clk = clk_get(dev, NULL); 9162306a36Sopenharmony_ci if (IS_ERR(i2c->clk)) { 9262306a36Sopenharmony_ci i2c->clk = NULL; 9362306a36Sopenharmony_ci goto skip; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci ret = clk_prepare_enable(i2c->clk); 9762306a36Sopenharmony_ci if (ret) 9862306a36Sopenharmony_ci goto skip; 9962306a36Sopenharmony_ci i2c->sys_freq = clk_get_rate(i2c->clk); 10062306a36Sopenharmony_ci } else { 10162306a36Sopenharmony_ci /* ACPI */ 10262306a36Sopenharmony_ci device_property_read_u32(dev, "sclk", &i2c->sys_freq); 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ciskip: 10662306a36Sopenharmony_ci if (!i2c->sys_freq) 10762306a36Sopenharmony_ci i2c->sys_freq = SYS_FREQ_DEFAULT; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic void thunder_i2c_clock_disable(struct device *dev, struct clk *clk) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci if (!clk) 11362306a36Sopenharmony_ci return; 11462306a36Sopenharmony_ci clk_disable_unprepare(clk); 11562306a36Sopenharmony_ci clk_put(clk); 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic int thunder_i2c_smbus_setup_of(struct octeon_i2c *i2c, 11962306a36Sopenharmony_ci struct device_node *node) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci struct i2c_client *ara; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (!node) 12462306a36Sopenharmony_ci return -EINVAL; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci i2c->alert_data.irq = irq_of_parse_and_map(node, 0); 12762306a36Sopenharmony_ci if (!i2c->alert_data.irq) 12862306a36Sopenharmony_ci return -EINVAL; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci ara = i2c_new_smbus_alert_device(&i2c->adap, &i2c->alert_data); 13162306a36Sopenharmony_ci if (IS_ERR(ara)) 13262306a36Sopenharmony_ci return PTR_ERR(ara); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci i2c->ara = ara; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci return 0; 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistatic int thunder_i2c_smbus_setup(struct octeon_i2c *i2c, 14062306a36Sopenharmony_ci struct device_node *node) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci /* TODO: ACPI support */ 14362306a36Sopenharmony_ci if (!acpi_disabled) 14462306a36Sopenharmony_ci return -EOPNOTSUPP; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci return thunder_i2c_smbus_setup_of(i2c, node); 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic void thunder_i2c_smbus_remove(struct octeon_i2c *i2c) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci i2c_unregister_device(i2c->ara); 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic int thunder_i2c_probe_pci(struct pci_dev *pdev, 15562306a36Sopenharmony_ci const struct pci_device_id *ent) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci struct device *dev = &pdev->dev; 15862306a36Sopenharmony_ci struct octeon_i2c *i2c; 15962306a36Sopenharmony_ci int ret; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); 16262306a36Sopenharmony_ci if (!i2c) 16362306a36Sopenharmony_ci return -ENOMEM; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci i2c->roff.sw_twsi = 0x1000; 16662306a36Sopenharmony_ci i2c->roff.twsi_int = 0x1010; 16762306a36Sopenharmony_ci i2c->roff.sw_twsi_ext = 0x1018; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci i2c->dev = dev; 17062306a36Sopenharmony_ci pci_set_drvdata(pdev, i2c); 17162306a36Sopenharmony_ci ret = pcim_enable_device(pdev); 17262306a36Sopenharmony_ci if (ret) 17362306a36Sopenharmony_ci return ret; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci ret = pci_request_regions(pdev, DRV_NAME); 17662306a36Sopenharmony_ci if (ret) 17762306a36Sopenharmony_ci return ret; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci i2c->twsi_base = pcim_iomap(pdev, 0, pci_resource_len(pdev, 0)); 18062306a36Sopenharmony_ci if (!i2c->twsi_base) 18162306a36Sopenharmony_ci return -EINVAL; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci thunder_i2c_clock_enable(dev, i2c); 18462306a36Sopenharmony_ci ret = device_property_read_u32(dev, "clock-frequency", &i2c->twsi_freq); 18562306a36Sopenharmony_ci if (ret) 18662306a36Sopenharmony_ci i2c->twsi_freq = I2C_MAX_STANDARD_MODE_FREQ; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci init_waitqueue_head(&i2c->queue); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci i2c->int_enable = thunder_i2c_int_enable; 19162306a36Sopenharmony_ci i2c->int_disable = thunder_i2c_int_disable; 19262306a36Sopenharmony_ci i2c->hlc_int_enable = thunder_i2c_hlc_int_enable; 19362306a36Sopenharmony_ci i2c->hlc_int_disable = thunder_i2c_hlc_int_disable; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX); 19662306a36Sopenharmony_ci if (ret < 0) 19762306a36Sopenharmony_ci goto error; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci ret = devm_request_irq(dev, pci_irq_vector(pdev, 0), octeon_i2c_isr, 0, 20062306a36Sopenharmony_ci DRV_NAME, i2c); 20162306a36Sopenharmony_ci if (ret) 20262306a36Sopenharmony_ci goto error; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci ret = octeon_i2c_init_lowlevel(i2c); 20562306a36Sopenharmony_ci if (ret) 20662306a36Sopenharmony_ci goto error; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci octeon_i2c_set_clock(i2c); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci i2c->adap = thunderx_i2c_ops; 21162306a36Sopenharmony_ci i2c->adap.retries = 5; 21262306a36Sopenharmony_ci i2c->adap.class = I2C_CLASS_HWMON; 21362306a36Sopenharmony_ci i2c->adap.bus_recovery_info = &octeon_i2c_recovery_info; 21462306a36Sopenharmony_ci i2c->adap.dev.parent = dev; 21562306a36Sopenharmony_ci i2c->adap.dev.of_node = pdev->dev.of_node; 21662306a36Sopenharmony_ci i2c->adap.dev.fwnode = dev->fwnode; 21762306a36Sopenharmony_ci snprintf(i2c->adap.name, sizeof(i2c->adap.name), 21862306a36Sopenharmony_ci "Cavium ThunderX i2c adapter at %s", dev_name(dev)); 21962306a36Sopenharmony_ci i2c_set_adapdata(&i2c->adap, i2c); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci ret = i2c_add_adapter(&i2c->adap); 22262306a36Sopenharmony_ci if (ret) 22362306a36Sopenharmony_ci goto error; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci dev_info(i2c->dev, "Probed. Set system clock to %u\n", i2c->sys_freq); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci ret = thunder_i2c_smbus_setup(i2c, pdev->dev.of_node); 22862306a36Sopenharmony_ci if (ret) 22962306a36Sopenharmony_ci dev_info(dev, "SMBUS alert not active on this bus\n"); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci return 0; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cierror: 23462306a36Sopenharmony_ci thunder_i2c_clock_disable(dev, i2c->clk); 23562306a36Sopenharmony_ci return ret; 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic void thunder_i2c_remove_pci(struct pci_dev *pdev) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci struct octeon_i2c *i2c = pci_get_drvdata(pdev); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci thunder_i2c_smbus_remove(i2c); 24362306a36Sopenharmony_ci thunder_i2c_clock_disable(&pdev->dev, i2c->clk); 24462306a36Sopenharmony_ci i2c_del_adapter(&i2c->adap); 24562306a36Sopenharmony_ci} 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_cistatic const struct pci_device_id thunder_i2c_pci_id_table[] = { 24862306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_THUNDER_TWSI) }, 24962306a36Sopenharmony_ci { 0, } 25062306a36Sopenharmony_ci}; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, thunder_i2c_pci_id_table); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cistatic struct pci_driver thunder_i2c_pci_driver = { 25562306a36Sopenharmony_ci .name = DRV_NAME, 25662306a36Sopenharmony_ci .id_table = thunder_i2c_pci_id_table, 25762306a36Sopenharmony_ci .probe = thunder_i2c_probe_pci, 25862306a36Sopenharmony_ci .remove = thunder_i2c_remove_pci, 25962306a36Sopenharmony_ci}; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_cimodule_pci_driver(thunder_i2c_pci_driver); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 26462306a36Sopenharmony_ciMODULE_AUTHOR("Fred Martin <fmartin@caviumnetworks.com>"); 26562306a36Sopenharmony_ciMODULE_DESCRIPTION("I2C-Bus adapter for Cavium ThunderX SOC"); 266