2011-10-29 17:57:23 +08:00
|
|
|
/*
|
2017-06-22 18:17:33 +08:00
|
|
|
* Synopsys DesignWare I2C adapter driver.
|
2011-10-29 17:57:23 +08:00
|
|
|
*
|
|
|
|
* Based on the TI DAVINCI I2C adapter driver.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2006 Texas Instruments.
|
|
|
|
* Copyright (C) 2007 MontaVista Software Inc.
|
|
|
|
* Copyright (C) 2009 Provigent Ltd.
|
|
|
|
*
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
*/
|
2017-06-14 18:43:21 +08:00
|
|
|
#include <linux/acpi.h>
|
|
|
|
#include <linux/clk-provider.h>
|
|
|
|
#include <linux/clk.h>
|
2011-10-29 17:57:23 +08:00
|
|
|
#include <linux/delay.h>
|
2015-09-24 17:06:54 +08:00
|
|
|
#include <linux/dmi.h>
|
2011-10-29 17:57:23 +08:00
|
|
|
#include <linux/err.h>
|
2017-06-14 18:43:21 +08:00
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/i2c.h>
|
2011-10-29 17:57:23 +08:00
|
|
|
#include <linux/interrupt.h>
|
2017-06-14 18:43:21 +08:00
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
2013-06-26 16:55:06 +08:00
|
|
|
#include <linux/of.h>
|
2017-06-14 18:43:21 +08:00
|
|
|
#include <linux/platform_data/i2c-designware.h>
|
2011-10-29 17:57:23 +08:00
|
|
|
#include <linux/platform_device.h>
|
2012-02-24 19:31:15 +08:00
|
|
|
#include <linux/pm.h>
|
2013-01-17 18:31:06 +08:00
|
|
|
#include <linux/pm_runtime.h>
|
2015-11-30 23:11:44 +08:00
|
|
|
#include <linux/property.h>
|
2016-12-27 22:22:40 +08:00
|
|
|
#include <linux/reset.h>
|
2017-06-14 18:43:21 +08:00
|
|
|
#include <linux/sched.h>
|
2011-10-29 17:57:23 +08:00
|
|
|
#include <linux/slab.h>
|
PM: i2c-designware-platdrv: Optimize power management
Optimize the power management in i2c-designware-platdrv by making it
set the DPM_FLAG_SMART_SUSPEND and DPM_FLAG_LEAVE_SUSPENDED which
allows some code to be dropped from its PM callbacks.
First, setting DPM_FLAG_SMART_SUSPEND causes the intel-lpss driver
to avoid resuming i2c-designware-platdrv devices in its ->prepare
callback, so they can stay in runtime suspend after that point even
if the direct-complete feature is not used for them.
It also causes the ACPI PM domain and the PM core to avoid invoking
"late" and "noirq" suspend callbacks for these devices if they are
in runtime suspend at the beginning of the "late" phase of device
suspend during system suspend. That guarantees dw_i2c_plat_suspend()
to be called for a device only if it is not in runtime suspend.
Moreover, it causes the device's runtime PM status to be set to
"active" after calling dw_i2c_plat_resume() for it, so the
driver doesn't need internal flags to avoid invoking either
dw_i2c_plat_suspend() or dw_i2c_plat_resume() twice in a row.
Second, setting DPM_FLAG_LEAVE_SUSPENDED enables the optimization
allowing the device to stay suspended after system resume under
suitable conditions, so again the driver doesn't need to take
care of that by itself.
Accordingly, the internal "suspended" and "skip_resume" flags
used by the driver are not necessary any more, so drop them and
simplify the driver's PM callbacks.
Additionally, notice that dw_i2c_plat_complete() only needs to
schedule runtime PM resume for the device if platform firmware
has been involved in resuming the system, so make it call
pm_resume_via_firmware() to check that. Also make it check the
runtime PM status of the device instead of its direct_complete
flag which also works if the device remained suspended due to
the DPM_FLAG_LEAVE_SUSPENDED driver flag.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Acked-by: Wolfram Sang <wsa@the-dreams.de>
Tested-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
2018-01-03 08:37:34 +08:00
|
|
|
#include <linux/suspend.h>
|
2017-06-14 18:43:21 +08:00
|
|
|
|
2011-10-29 17:57:23 +08:00
|
|
|
#include "i2c-designware-core.h"
|
|
|
|
|
2011-10-07 02:26:30 +08:00
|
|
|
static u32 i2c_dw_get_clk_rate_khz(struct dw_i2c_dev *dev)
|
|
|
|
{
|
|
|
|
return clk_get_rate(dev->clk)/1000;
|
|
|
|
}
|
2011-10-29 17:57:23 +08:00
|
|
|
|
2013-01-17 18:31:07 +08:00
|
|
|
#ifdef CONFIG_ACPI
|
2015-09-24 17:06:54 +08:00
|
|
|
/*
|
|
|
|
* The HCNT/LCNT information coming from ACPI should be the most accurate
|
|
|
|
* for given platform. However, some systems get it wrong. On such systems
|
|
|
|
* we get better results by calculating those based on the input clock.
|
|
|
|
*/
|
|
|
|
static const struct dmi_system_id dw_i2c_no_acpi_params[] = {
|
|
|
|
{
|
|
|
|
.ident = "Dell Inspiron 7348",
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
|
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7348"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2013-08-19 20:07:54 +08:00
|
|
|
static void dw_i2c_acpi_params(struct platform_device *pdev, char method[],
|
|
|
|
u16 *hcnt, u16 *lcnt, u32 *sda_hold)
|
|
|
|
{
|
|
|
|
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER };
|
|
|
|
acpi_handle handle = ACPI_HANDLE(&pdev->dev);
|
|
|
|
union acpi_object *obj;
|
|
|
|
|
2015-09-24 17:06:54 +08:00
|
|
|
if (dmi_check_system(dw_i2c_no_acpi_params))
|
|
|
|
return;
|
|
|
|
|
2013-08-19 20:07:54 +08:00
|
|
|
if (ACPI_FAILURE(acpi_evaluate_object(handle, method, NULL, &buf)))
|
|
|
|
return;
|
|
|
|
|
|
|
|
obj = (union acpi_object *)buf.pointer;
|
|
|
|
if (obj->type == ACPI_TYPE_PACKAGE && obj->package.count == 3) {
|
|
|
|
const union acpi_object *objs = obj->package.elements;
|
|
|
|
|
|
|
|
*hcnt = (u16)objs[0].integer.value;
|
|
|
|
*lcnt = (u16)objs[1].integer.value;
|
2017-03-28 16:48:02 +08:00
|
|
|
*sda_hold = (u32)objs[2].integer.value;
|
2013-08-19 20:07:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
kfree(buf.pointer);
|
|
|
|
}
|
|
|
|
|
2013-01-17 18:31:07 +08:00
|
|
|
static int dw_i2c_acpi_configure(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct dw_i2c_dev *dev = platform_get_drvdata(pdev);
|
2018-07-25 22:39:26 +08:00
|
|
|
struct i2c_timings *t = &dev->timings;
|
2017-05-22 13:46:55 +08:00
|
|
|
u32 ss_ht = 0, fp_ht = 0, hs_ht = 0, fs_ht = 0;
|
2017-03-14 06:25:10 +08:00
|
|
|
acpi_handle handle = ACPI_HANDLE(&pdev->dev);
|
2015-12-11 20:02:53 +08:00
|
|
|
const struct acpi_device_id *id;
|
2017-03-14 06:25:10 +08:00
|
|
|
struct acpi_device *adev;
|
|
|
|
const char *uid;
|
2013-01-17 18:31:07 +08:00
|
|
|
|
|
|
|
dev->adapter.nr = -1;
|
|
|
|
dev->tx_fifo_depth = 32;
|
|
|
|
dev->rx_fifo_depth = 32;
|
2013-08-19 20:07:54 +08:00
|
|
|
|
|
|
|
/*
|
2017-03-28 16:48:02 +08:00
|
|
|
* Try to get SDA hold time and *CNT values from an ACPI method for
|
|
|
|
* selected speed modes.
|
2013-08-19 20:07:54 +08:00
|
|
|
*/
|
2017-05-19 16:56:40 +08:00
|
|
|
dw_i2c_acpi_params(pdev, "SSCN", &dev->ss_hcnt, &dev->ss_lcnt, &ss_ht);
|
|
|
|
dw_i2c_acpi_params(pdev, "FPCN", &dev->fp_hcnt, &dev->fp_lcnt, &fp_ht);
|
|
|
|
dw_i2c_acpi_params(pdev, "HSCN", &dev->hs_hcnt, &dev->hs_lcnt, &hs_ht);
|
|
|
|
dw_i2c_acpi_params(pdev, "FMCN", &dev->fs_hcnt, &dev->fs_lcnt, &fs_ht);
|
|
|
|
|
2018-07-25 22:39:26 +08:00
|
|
|
switch (t->bus_freq_hz) {
|
2017-03-28 16:48:02 +08:00
|
|
|
case 100000:
|
2017-05-19 16:56:40 +08:00
|
|
|
dev->sda_hold_time = ss_ht;
|
2017-03-28 16:48:02 +08:00
|
|
|
break;
|
|
|
|
case 1000000:
|
2017-05-19 16:56:40 +08:00
|
|
|
dev->sda_hold_time = fp_ht;
|
2017-03-28 16:48:02 +08:00
|
|
|
break;
|
|
|
|
case 3400000:
|
2017-05-19 16:56:40 +08:00
|
|
|
dev->sda_hold_time = hs_ht;
|
2017-03-28 16:48:02 +08:00
|
|
|
break;
|
|
|
|
case 400000:
|
|
|
|
default:
|
2017-05-19 16:56:40 +08:00
|
|
|
dev->sda_hold_time = fs_ht;
|
2017-03-28 16:48:02 +08:00
|
|
|
break;
|
|
|
|
}
|
2013-08-19 20:07:54 +08:00
|
|
|
|
2015-12-11 20:02:53 +08:00
|
|
|
id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev);
|
|
|
|
if (id && id->driver_data)
|
2017-02-10 18:27:53 +08:00
|
|
|
dev->flags |= (u32)id->driver_data;
|
2015-12-11 20:02:53 +08:00
|
|
|
|
2017-03-14 06:25:10 +08:00
|
|
|
if (acpi_bus_get_device(handle, &adev))
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Cherrytrail I2C7 gets used for the PMIC which gets accessed
|
|
|
|
* through ACPI opregions during late suspend / early resume
|
|
|
|
* disable pm for it.
|
|
|
|
*/
|
|
|
|
uid = adev->pnp.unique_id;
|
|
|
|
if ((dev->flags & MODEL_CHERRYTRAIL) && !strcmp(uid, "7"))
|
|
|
|
dev->pm_disabled = true;
|
|
|
|
|
2013-01-17 18:31:07 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct acpi_device_id dw_i2c_acpi_match[] = {
|
|
|
|
{ "INT33C2", 0 },
|
|
|
|
{ "INT33C3", 0 },
|
2013-11-12 17:57:30 +08:00
|
|
|
{ "INT3432", 0 },
|
|
|
|
{ "INT3433", 0 },
|
2013-05-13 08:54:31 +08:00
|
|
|
{ "80860F41", 0 },
|
2017-02-10 18:27:58 +08:00
|
|
|
{ "808622C1", MODEL_CHERRYTRAIL },
|
2015-12-11 20:02:53 +08:00
|
|
|
{ "AMD0010", ACCESS_INTR_MASK },
|
2016-03-10 19:34:52 +08:00
|
|
|
{ "AMDI0010", ACCESS_INTR_MASK },
|
2015-12-16 05:55:53 +08:00
|
|
|
{ "AMDI0510", 0 },
|
2015-12-11 05:19:17 +08:00
|
|
|
{ "APMC0D0F", 0 },
|
2017-04-22 11:23:44 +08:00
|
|
|
{ "HISI02A1", 0 },
|
|
|
|
{ "HISI02A2", 0 },
|
2013-01-17 18:31:07 +08:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(acpi, dw_i2c_acpi_match);
|
|
|
|
#else
|
|
|
|
static inline int dw_i2c_acpi_configure(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-06-14 18:43:22 +08:00
|
|
|
static void i2c_dw_configure_master(struct dw_i2c_dev *dev)
|
|
|
|
{
|
2018-07-25 22:39:26 +08:00
|
|
|
struct i2c_timings *t = &dev->timings;
|
|
|
|
|
2017-06-22 18:17:33 +08:00
|
|
|
dev->functionality = I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY;
|
|
|
|
|
2017-06-14 18:43:22 +08:00
|
|
|
dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE |
|
|
|
|
DW_IC_CON_RESTART_EN;
|
|
|
|
|
2017-06-22 18:17:33 +08:00
|
|
|
dev->mode = DW_IC_MASTER;
|
|
|
|
|
2018-07-25 22:39:26 +08:00
|
|
|
switch (t->bus_freq_hz) {
|
2017-06-14 18:43:22 +08:00
|
|
|
case 100000:
|
|
|
|
dev->master_cfg |= DW_IC_CON_SPEED_STD;
|
|
|
|
break;
|
|
|
|
case 3400000:
|
|
|
|
dev->master_cfg |= DW_IC_CON_SPEED_HIGH;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev->master_cfg |= DW_IC_CON_SPEED_FAST;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-22 18:17:33 +08:00
|
|
|
static void i2c_dw_configure_slave(struct dw_i2c_dev *dev)
|
|
|
|
{
|
|
|
|
dev->functionality = I2C_FUNC_SLAVE | DW_IC_DEFAULT_FUNCTIONALITY;
|
|
|
|
|
|
|
|
dev->slave_cfg = DW_IC_CON_RX_FIFO_FULL_HLD_CTRL |
|
2017-08-09 20:24:44 +08:00
|
|
|
DW_IC_CON_RESTART_EN | DW_IC_CON_STOP_DET_IFADDRESSED;
|
2017-06-22 18:17:33 +08:00
|
|
|
|
|
|
|
dev->mode = DW_IC_SLAVE;
|
2016-01-04 23:17:35 +08:00
|
|
|
}
|
|
|
|
|
2016-12-14 17:23:58 +08:00
|
|
|
static void dw_i2c_set_fifo_size(struct dw_i2c_dev *dev, int id)
|
|
|
|
{
|
|
|
|
u32 param, tx_fifo_depth, rx_fifo_depth;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Try to detect the FIFO depth if not set by interface driver,
|
|
|
|
* the depth could be from 2 to 256 from HW spec.
|
|
|
|
*/
|
|
|
|
param = i2c_dw_read_comp_param(dev);
|
|
|
|
tx_fifo_depth = ((param >> 16) & 0xff) + 1;
|
|
|
|
rx_fifo_depth = ((param >> 8) & 0xff) + 1;
|
|
|
|
if (!dev->tx_fifo_depth) {
|
|
|
|
dev->tx_fifo_depth = tx_fifo_depth;
|
|
|
|
dev->rx_fifo_depth = rx_fifo_depth;
|
|
|
|
dev->adapter.nr = id;
|
|
|
|
} else if (tx_fifo_depth >= 2) {
|
|
|
|
dev->tx_fifo_depth = min_t(u32, dev->tx_fifo_depth,
|
|
|
|
tx_fifo_depth);
|
|
|
|
dev->rx_fifo_depth = min_t(u32, dev->rx_fifo_depth,
|
|
|
|
rx_fifo_depth);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 05:10:06 +08:00
|
|
|
static void dw_i2c_plat_pm_cleanup(struct dw_i2c_dev *dev)
|
|
|
|
{
|
|
|
|
pm_runtime_disable(dev->dev);
|
|
|
|
|
|
|
|
if (dev->pm_disabled)
|
|
|
|
pm_runtime_put_noidle(dev->dev);
|
|
|
|
}
|
|
|
|
|
2015-08-31 22:31:32 +08:00
|
|
|
static int dw_i2c_plat_probe(struct platform_device *pdev)
|
2011-10-29 17:57:23 +08:00
|
|
|
{
|
2015-11-30 23:11:44 +08:00
|
|
|
struct dw_i2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
2011-10-29 17:57:23 +08:00
|
|
|
struct i2c_adapter *adap;
|
2017-06-14 18:43:21 +08:00
|
|
|
struct dw_i2c_dev *dev;
|
2018-07-25 22:39:26 +08:00
|
|
|
struct i2c_timings *t;
|
|
|
|
u32 acpi_speed;
|
2017-06-14 18:43:21 +08:00
|
|
|
struct resource *mem;
|
2017-08-29 20:08:35 +08:00
|
|
|
int i, irq, ret;
|
2017-09-22 06:30:07 +08:00
|
|
|
static const int supported_speeds[] = {
|
|
|
|
0, 100000, 400000, 1000000, 3400000
|
|
|
|
};
|
2011-10-29 17:57:23 +08:00
|
|
|
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
2015-03-09 17:03:12 +08:00
|
|
|
if (irq < 0)
|
|
|
|
return irq;
|
2011-10-29 17:57:23 +08:00
|
|
|
|
2013-04-10 08:36:36 +08:00
|
|
|
dev = devm_kzalloc(&pdev->dev, sizeof(struct dw_i2c_dev), GFP_KERNEL);
|
|
|
|
if (!dev)
|
|
|
|
return -ENOMEM;
|
2011-10-29 17:57:23 +08:00
|
|
|
|
2013-05-10 16:16:54 +08:00
|
|
|
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
2013-04-10 08:36:36 +08:00
|
|
|
dev->base = devm_ioremap_resource(&pdev->dev, mem);
|
|
|
|
if (IS_ERR(dev->base))
|
|
|
|
return PTR_ERR(dev->base);
|
2011-10-29 17:57:23 +08:00
|
|
|
|
2013-04-10 08:36:36 +08:00
|
|
|
dev->dev = &pdev->dev;
|
2011-10-29 17:57:23 +08:00
|
|
|
dev->irq = irq;
|
|
|
|
platform_set_drvdata(pdev, dev);
|
|
|
|
|
2016-12-27 22:22:40 +08:00
|
|
|
dev->rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL);
|
|
|
|
if (IS_ERR(dev->rst)) {
|
|
|
|
if (PTR_ERR(dev->rst) == -EPROBE_DEFER)
|
|
|
|
return -EPROBE_DEFER;
|
|
|
|
} else {
|
|
|
|
reset_control_deassert(dev->rst);
|
|
|
|
}
|
|
|
|
|
2018-07-25 22:39:26 +08:00
|
|
|
t = &dev->timings;
|
|
|
|
if (pdata)
|
|
|
|
t->bus_freq_hz = pdata->i2c_scl_freq;
|
|
|
|
else
|
|
|
|
i2c_parse_fw_timings(&pdev->dev, t, false);
|
2015-11-30 23:11:44 +08:00
|
|
|
|
2016-08-12 22:02:54 +08:00
|
|
|
acpi_speed = i2c_acpi_find_bus_speed(&pdev->dev);
|
2017-08-29 20:08:35 +08:00
|
|
|
/*
|
|
|
|
* Some DSTDs use a non standard speed, round down to the lowest
|
|
|
|
* standard speed.
|
|
|
|
*/
|
|
|
|
for (i = 1; i < ARRAY_SIZE(supported_speeds); i++) {
|
|
|
|
if (acpi_speed < supported_speeds[i])
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
acpi_speed = supported_speeds[i - 1];
|
|
|
|
|
2016-11-10 19:37:20 +08:00
|
|
|
/*
|
|
|
|
* Find bus speed from the "clock-frequency" device property, ACPI
|
|
|
|
* or by using fast mode if neither is set.
|
|
|
|
*/
|
2018-07-25 22:39:26 +08:00
|
|
|
if (acpi_speed && t->bus_freq_hz)
|
|
|
|
t->bus_freq_hz = min(t->bus_freq_hz, acpi_speed);
|
|
|
|
else if (acpi_speed || t->bus_freq_hz)
|
|
|
|
t->bus_freq_hz = max(t->bus_freq_hz, acpi_speed);
|
2016-11-10 19:37:20 +08:00
|
|
|
else
|
2018-07-25 22:39:26 +08:00
|
|
|
t->bus_freq_hz = 400000;
|
2016-08-12 22:02:54 +08:00
|
|
|
|
2015-11-30 23:11:44 +08:00
|
|
|
if (has_acpi_companion(&pdev->dev))
|
|
|
|
dw_i2c_acpi_configure(pdev);
|
|
|
|
|
|
|
|
/*
|
2016-08-12 22:02:49 +08:00
|
|
|
* Only standard mode at 100kHz, fast mode at 400kHz,
|
2016-08-12 22:02:51 +08:00
|
|
|
* fast mode plus at 1MHz and high speed mode at 3.4MHz are supported.
|
2015-11-30 23:11:44 +08:00
|
|
|
*/
|
2018-07-25 22:39:26 +08:00
|
|
|
if (t->bus_freq_hz != 100000 && t->bus_freq_hz != 400000 &&
|
|
|
|
t->bus_freq_hz != 1000000 && t->bus_freq_hz != 3400000) {
|
2016-08-12 22:02:49 +08:00
|
|
|
dev_err(&pdev->dev,
|
2017-07-13 21:45:01 +08:00
|
|
|
"%d Hz is unsupported, only 100kHz, 400kHz, 1MHz and 3.4MHz are supported\n",
|
2018-07-25 22:39:26 +08:00
|
|
|
t->bus_freq_hz);
|
2017-06-14 18:43:21 +08:00
|
|
|
ret = -EINVAL;
|
2016-12-27 22:22:40 +08:00
|
|
|
goto exit_reset;
|
2013-06-26 16:55:06 +08:00
|
|
|
}
|
|
|
|
|
2017-06-14 18:43:21 +08:00
|
|
|
ret = i2c_dw_probe_lock_support(dev);
|
|
|
|
if (ret)
|
2016-12-27 22:22:40 +08:00
|
|
|
goto exit_reset;
|
2015-01-15 17:12:17 +08:00
|
|
|
|
2017-06-22 18:17:33 +08:00
|
|
|
if (i2c_detect_slave_mode(&pdev->dev))
|
|
|
|
i2c_dw_configure_slave(dev);
|
|
|
|
else
|
|
|
|
i2c_dw_configure_master(dev);
|
2011-10-07 02:26:31 +08:00
|
|
|
|
2014-09-30 18:04:54 +08:00
|
|
|
dev->clk = devm_clk_get(&pdev->dev, NULL);
|
2017-11-02 10:40:26 +08:00
|
|
|
if (!i2c_dw_prepare_clk(dev, true)) {
|
2018-07-25 22:39:26 +08:00
|
|
|
u64 clk_khz;
|
|
|
|
|
2016-01-04 23:17:35 +08:00
|
|
|
dev->get_clk_rate_khz = i2c_dw_get_clk_rate_khz;
|
2018-07-25 22:39:26 +08:00
|
|
|
clk_khz = dev->get_clk_rate_khz(dev);
|
2014-09-30 18:04:54 +08:00
|
|
|
|
2018-07-25 22:39:26 +08:00
|
|
|
if (!dev->sda_hold_time && t->sda_hold_ns)
|
|
|
|
dev->sda_hold_time =
|
|
|
|
div_u64(clk_khz * t->sda_hold_ns + 500000, 1000000);
|
2014-09-30 18:04:54 +08:00
|
|
|
}
|
|
|
|
|
2016-12-14 17:23:58 +08:00
|
|
|
dw_i2c_set_fifo_size(dev, pdev->id);
|
2011-10-29 17:57:23 +08:00
|
|
|
|
|
|
|
adap = &dev->adapter;
|
|
|
|
adap->owner = THIS_MODULE;
|
2014-07-10 19:46:26 +08:00
|
|
|
adap->class = I2C_CLASS_DEPRECATED;
|
2015-10-24 03:27:07 +08:00
|
|
|
ACPI_COMPANION_SET(&adap->dev, ACPI_COMPANION(&pdev->dev));
|
2011-11-09 04:43:47 +08:00
|
|
|
adap->dev.of_node = pdev->dev.of_node;
|
2011-10-29 17:57:23 +08:00
|
|
|
|
PM: i2c-designware-platdrv: Optimize power management
Optimize the power management in i2c-designware-platdrv by making it
set the DPM_FLAG_SMART_SUSPEND and DPM_FLAG_LEAVE_SUSPENDED which
allows some code to be dropped from its PM callbacks.
First, setting DPM_FLAG_SMART_SUSPEND causes the intel-lpss driver
to avoid resuming i2c-designware-platdrv devices in its ->prepare
callback, so they can stay in runtime suspend after that point even
if the direct-complete feature is not used for them.
It also causes the ACPI PM domain and the PM core to avoid invoking
"late" and "noirq" suspend callbacks for these devices if they are
in runtime suspend at the beginning of the "late" phase of device
suspend during system suspend. That guarantees dw_i2c_plat_suspend()
to be called for a device only if it is not in runtime suspend.
Moreover, it causes the device's runtime PM status to be set to
"active" after calling dw_i2c_plat_resume() for it, so the
driver doesn't need internal flags to avoid invoking either
dw_i2c_plat_suspend() or dw_i2c_plat_resume() twice in a row.
Second, setting DPM_FLAG_LEAVE_SUSPENDED enables the optimization
allowing the device to stay suspended after system resume under
suitable conditions, so again the driver doesn't need to take
care of that by itself.
Accordingly, the internal "suspended" and "skip_resume" flags
used by the driver are not necessary any more, so drop them and
simplify the driver's PM callbacks.
Additionally, notice that dw_i2c_plat_complete() only needs to
schedule runtime PM resume for the device if platform firmware
has been involved in resuming the system, so make it call
pm_resume_via_firmware() to check that. Also make it check the
runtime PM status of the device instead of its direct_complete
flag which also works if the device remained suspended due to
the DPM_FLAG_LEAVE_SUSPENDED driver flag.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Acked-by: Wolfram Sang <wsa@the-dreams.de>
Tested-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
2018-01-03 08:37:34 +08:00
|
|
|
dev_pm_set_driver_flags(&pdev->dev,
|
|
|
|
DPM_FLAG_SMART_PREPARE |
|
|
|
|
DPM_FLAG_SMART_SUSPEND |
|
|
|
|
DPM_FLAG_LEAVE_SUSPENDED);
|
2018-01-03 08:35:54 +08:00
|
|
|
|
2017-09-26 05:10:06 +08:00
|
|
|
/* The code below assumes runtime PM to be disabled. */
|
|
|
|
WARN_ON(pm_runtime_enabled(&pdev->dev));
|
|
|
|
|
|
|
|
pm_runtime_set_autosuspend_delay(&pdev->dev, 1000);
|
|
|
|
pm_runtime_use_autosuspend(&pdev->dev);
|
|
|
|
pm_runtime_set_active(&pdev->dev);
|
|
|
|
|
|
|
|
if (dev->pm_disabled)
|
|
|
|
pm_runtime_get_noresume(&pdev->dev);
|
|
|
|
|
|
|
|
pm_runtime_enable(&pdev->dev);
|
2013-01-17 18:31:06 +08:00
|
|
|
|
2017-06-22 18:17:33 +08:00
|
|
|
if (dev->mode == DW_IC_SLAVE)
|
|
|
|
ret = i2c_dw_probe_slave(dev);
|
|
|
|
else
|
|
|
|
ret = i2c_dw_probe(dev);
|
|
|
|
|
2017-06-14 18:43:21 +08:00
|
|
|
if (ret)
|
2016-12-27 22:22:40 +08:00
|
|
|
goto exit_probe;
|
2015-10-09 17:39:24 +08:00
|
|
|
|
2017-06-14 18:43:21 +08:00
|
|
|
return ret;
|
2016-12-27 22:22:40 +08:00
|
|
|
|
|
|
|
exit_probe:
|
2017-09-26 05:10:06 +08:00
|
|
|
dw_i2c_plat_pm_cleanup(dev);
|
2016-12-27 22:22:40 +08:00
|
|
|
exit_reset:
|
|
|
|
if (!IS_ERR_OR_NULL(dev->rst))
|
|
|
|
reset_control_assert(dev->rst);
|
2017-06-14 18:43:21 +08:00
|
|
|
return ret;
|
2011-10-29 17:57:23 +08:00
|
|
|
}
|
|
|
|
|
2015-08-31 22:31:32 +08:00
|
|
|
static int dw_i2c_plat_remove(struct platform_device *pdev)
|
2011-10-29 17:57:23 +08:00
|
|
|
{
|
|
|
|
struct dw_i2c_dev *dev = platform_get_drvdata(pdev);
|
|
|
|
|
2013-01-17 18:31:06 +08:00
|
|
|
pm_runtime_get_sync(&pdev->dev);
|
|
|
|
|
2011-10-29 17:57:23 +08:00
|
|
|
i2c_del_adapter(&dev->adapter);
|
|
|
|
|
2017-06-14 18:43:23 +08:00
|
|
|
dev->disable(dev);
|
2011-10-29 17:57:23 +08:00
|
|
|
|
2015-06-17 17:08:38 +08:00
|
|
|
pm_runtime_dont_use_autosuspend(&pdev->dev);
|
|
|
|
pm_runtime_put_sync(&pdev->dev);
|
2017-09-26 05:10:06 +08:00
|
|
|
dw_i2c_plat_pm_cleanup(dev);
|
|
|
|
|
2016-12-27 22:22:40 +08:00
|
|
|
if (!IS_ERR_OR_NULL(dev->rst))
|
|
|
|
reset_control_assert(dev->rst);
|
2013-01-17 18:31:06 +08:00
|
|
|
|
2017-02-10 18:27:56 +08:00
|
|
|
i2c_dw_remove_lock_support(dev);
|
|
|
|
|
2011-10-29 17:57:23 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-11-09 04:43:47 +08:00
|
|
|
#ifdef CONFIG_OF
|
|
|
|
static const struct of_device_id dw_i2c_of_match[] = {
|
|
|
|
{ .compatible = "snps,designware-i2c", },
|
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, dw_i2c_of_match);
|
|
|
|
#endif
|
|
|
|
|
2015-05-20 22:33:13 +08:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
2015-08-31 22:31:32 +08:00
|
|
|
static int dw_i2c_plat_prepare(struct device *dev)
|
2015-05-20 22:33:13 +08:00
|
|
|
{
|
2018-01-03 08:35:54 +08:00
|
|
|
/*
|
|
|
|
* If the ACPI companion device object is present for this device, it
|
|
|
|
* may be accessed during suspend and resume of other devices via I2C
|
|
|
|
* operation regions, so tell the PM core and middle layers to avoid
|
|
|
|
* skipping system suspend/resume callbacks for it in that case.
|
|
|
|
*/
|
|
|
|
return !has_acpi_companion(dev);
|
2015-05-20 22:33:13 +08:00
|
|
|
}
|
|
|
|
|
2015-08-31 22:31:32 +08:00
|
|
|
static void dw_i2c_plat_complete(struct device *dev)
|
2015-05-20 22:33:13 +08:00
|
|
|
{
|
PM: i2c-designware-platdrv: Optimize power management
Optimize the power management in i2c-designware-platdrv by making it
set the DPM_FLAG_SMART_SUSPEND and DPM_FLAG_LEAVE_SUSPENDED which
allows some code to be dropped from its PM callbacks.
First, setting DPM_FLAG_SMART_SUSPEND causes the intel-lpss driver
to avoid resuming i2c-designware-platdrv devices in its ->prepare
callback, so they can stay in runtime suspend after that point even
if the direct-complete feature is not used for them.
It also causes the ACPI PM domain and the PM core to avoid invoking
"late" and "noirq" suspend callbacks for these devices if they are
in runtime suspend at the beginning of the "late" phase of device
suspend during system suspend. That guarantees dw_i2c_plat_suspend()
to be called for a device only if it is not in runtime suspend.
Moreover, it causes the device's runtime PM status to be set to
"active" after calling dw_i2c_plat_resume() for it, so the
driver doesn't need internal flags to avoid invoking either
dw_i2c_plat_suspend() or dw_i2c_plat_resume() twice in a row.
Second, setting DPM_FLAG_LEAVE_SUSPENDED enables the optimization
allowing the device to stay suspended after system resume under
suitable conditions, so again the driver doesn't need to take
care of that by itself.
Accordingly, the internal "suspended" and "skip_resume" flags
used by the driver are not necessary any more, so drop them and
simplify the driver's PM callbacks.
Additionally, notice that dw_i2c_plat_complete() only needs to
schedule runtime PM resume for the device if platform firmware
has been involved in resuming the system, so make it call
pm_resume_via_firmware() to check that. Also make it check the
runtime PM status of the device instead of its direct_complete
flag which also works if the device remained suspended due to
the DPM_FLAG_LEAVE_SUSPENDED driver flag.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Acked-by: Wolfram Sang <wsa@the-dreams.de>
Tested-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
2018-01-03 08:37:34 +08:00
|
|
|
/*
|
|
|
|
* The device can only be in runtime suspend at this point if it has not
|
|
|
|
* been resumed throughout the ending system suspend/resume cycle, so if
|
|
|
|
* the platform firmware might mess up with it, request the runtime PM
|
|
|
|
* framework to resume it.
|
|
|
|
*/
|
|
|
|
if (pm_runtime_suspended(dev) && pm_resume_via_firmware())
|
2015-05-20 22:33:13 +08:00
|
|
|
pm_request_resume(dev);
|
|
|
|
}
|
|
|
|
#else
|
2015-10-21 15:09:17 +08:00
|
|
|
#define dw_i2c_plat_prepare NULL
|
|
|
|
#define dw_i2c_plat_complete NULL
|
2015-05-20 22:33:13 +08:00
|
|
|
#endif
|
|
|
|
|
2014-05-15 22:37:23 +08:00
|
|
|
#ifdef CONFIG_PM
|
PM: i2c-designware-platdrv: Suspend/resume at the late/early stages
As reported by Rajat Jain, there are problems when ACPI operation
region handlers or similar, called at the ->resume_early() time, for
I2C client devices try to access an I2C controller that has already
been suspended at that point. To avoid that, move the suspend/resume
of i2c-designware-platdrv to the late/early stages, respectively.
While at it, avoid resuming the device from runtime suspend in the
driver's ->suspend callback which isn't particularly nice. [A better
approach would be to make the driver track the PM state of the device
so that it doesn't need to resume it in ->suspend, so implement it.]
First, drop dw_i2c_plat_suspend() added by commit a23318feeff6 (i2c:
designware: Fix system suspend) and rename dw_i2c_plat_runtime_suspend()
back to dw_i2c_plat_suspend().
Second, point the driver's ->late_suspend and ->early_resume
callbacks, rather than its ->suspend and ->resume callbacks,
to dw_i2c_plat_suspend() and dw_i2c_plat_resume(), respectively,
so that they are not executed in parallel with each other, for
example if runtime resume of the device takes place during system
suspend.
Finally, add "suspended" and "skip_resume" flags to struct dw_i2c_dev
and make dw_i2c_plat_suspend() and dw_i2c_plat_resume() use them to
avoid suspending or resuming the device twice in a row and to avoid
resuming a previously runtime-suspended device during system resume.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Tested-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Tested-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Tested-by: Johannes Stezenbach <js@sig21.net>
Tested-by: Rajat Jain <rajatja@google.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
2017-09-25 07:30:51 +08:00
|
|
|
static int dw_i2c_plat_suspend(struct device *dev)
|
2012-02-24 19:31:15 +08:00
|
|
|
{
|
2017-07-28 00:16:24 +08:00
|
|
|
struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
|
2012-02-24 19:31:15 +08:00
|
|
|
|
2017-06-14 18:43:23 +08:00
|
|
|
i_dev->disable(i_dev);
|
2017-11-02 10:40:26 +08:00
|
|
|
i2c_dw_prepare_clk(i_dev, false);
|
2012-02-24 19:31:15 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-08-31 22:31:32 +08:00
|
|
|
static int dw_i2c_plat_resume(struct device *dev)
|
2012-02-24 19:31:15 +08:00
|
|
|
{
|
2017-07-28 00:16:24 +08:00
|
|
|
struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
|
2012-02-24 19:31:15 +08:00
|
|
|
|
2017-11-02 10:40:26 +08:00
|
|
|
i2c_dw_prepare_clk(i_dev, true);
|
2017-06-14 18:43:23 +08:00
|
|
|
i_dev->init(i_dev);
|
2012-02-24 19:31:15 +08:00
|
|
|
|
PM: i2c-designware-platdrv: Suspend/resume at the late/early stages
As reported by Rajat Jain, there are problems when ACPI operation
region handlers or similar, called at the ->resume_early() time, for
I2C client devices try to access an I2C controller that has already
been suspended at that point. To avoid that, move the suspend/resume
of i2c-designware-platdrv to the late/early stages, respectively.
While at it, avoid resuming the device from runtime suspend in the
driver's ->suspend callback which isn't particularly nice. [A better
approach would be to make the driver track the PM state of the device
so that it doesn't need to resume it in ->suspend, so implement it.]
First, drop dw_i2c_plat_suspend() added by commit a23318feeff6 (i2c:
designware: Fix system suspend) and rename dw_i2c_plat_runtime_suspend()
back to dw_i2c_plat_suspend().
Second, point the driver's ->late_suspend and ->early_resume
callbacks, rather than its ->suspend and ->resume callbacks,
to dw_i2c_plat_suspend() and dw_i2c_plat_resume(), respectively,
so that they are not executed in parallel with each other, for
example if runtime resume of the device takes place during system
suspend.
Finally, add "suspended" and "skip_resume" flags to struct dw_i2c_dev
and make dw_i2c_plat_suspend() and dw_i2c_plat_resume() use them to
avoid suspending or resuming the device twice in a row and to avoid
resuming a previously runtime-suspended device during system resume.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Tested-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Tested-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Tested-by: Johannes Stezenbach <js@sig21.net>
Tested-by: Rajat Jain <rajatja@google.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
2017-09-25 07:30:51 +08:00
|
|
|
return 0;
|
i2c: designware: Fix system suspend
The commit 8503ff166504 ("i2c: designware: Avoid unnecessary resuming
during system suspend"), may suggest to the PM core to try out the so
called direct_complete path for system sleep. In this path, the PM core
treats a runtime suspended device as it's already in a proper low power
state for system sleep, which makes it skip calling the system sleep
callbacks for the device, except for the ->prepare() and the ->complete()
callbacks.
However, the PM core may unset the direct_complete flag for a parent
device, in case its child device are being system suspended before. In this
scenario, the PM core invokes the system sleep callbacks, no matter if the
device is runtime suspended or not.
Particularly in cases of an existing i2c slave device, the above path is
triggered, which breaks the assumption that the i2c device is always
runtime resumed whenever the dw_i2c_plat_suspend() is being called.
More precisely, dw_i2c_plat_suspend() calls clk_core_disable() and
clk_core_unprepare(), for an already disabled/unprepared clock, leading to
a splat in the log about clocks calls being wrongly balanced and breaking
system sleep.
To still allow the direct_complete path in cases when it's possible, but
also to keep the fix simple, let's runtime resume the i2c device in the
->suspend() callback, before continuing to put the device into low power
state.
Note, in cases when the i2c device is attached to the ACPI PM domain, this
problem doesn't occur, because ACPI's ->suspend() callback, assigned to
acpi_subsys_suspend(), already calls pm_runtime_resume() for the device.
It should also be noted that this change does not fix commit 8503ff166504
("i2c: designware: Avoid unnecessary resuming during system suspend").
Because for the non-ACPI case, the system sleep support was already broken
prior that point.
Cc: <stable@vger.kernel.org> # v4.4+
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Tested-by: John Stultz <john.stultz@linaro.org>
Tested-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Acked-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
2017-08-09 21:28:22 +08:00
|
|
|
}
|
|
|
|
|
2015-05-20 22:33:13 +08:00
|
|
|
static const struct dev_pm_ops dw_i2c_dev_pm_ops = {
|
2015-08-31 22:31:32 +08:00
|
|
|
.prepare = dw_i2c_plat_prepare,
|
|
|
|
.complete = dw_i2c_plat_complete,
|
PM: i2c-designware-platdrv: Suspend/resume at the late/early stages
As reported by Rajat Jain, there are problems when ACPI operation
region handlers or similar, called at the ->resume_early() time, for
I2C client devices try to access an I2C controller that has already
been suspended at that point. To avoid that, move the suspend/resume
of i2c-designware-platdrv to the late/early stages, respectively.
While at it, avoid resuming the device from runtime suspend in the
driver's ->suspend callback which isn't particularly nice. [A better
approach would be to make the driver track the PM state of the device
so that it doesn't need to resume it in ->suspend, so implement it.]
First, drop dw_i2c_plat_suspend() added by commit a23318feeff6 (i2c:
designware: Fix system suspend) and rename dw_i2c_plat_runtime_suspend()
back to dw_i2c_plat_suspend().
Second, point the driver's ->late_suspend and ->early_resume
callbacks, rather than its ->suspend and ->resume callbacks,
to dw_i2c_plat_suspend() and dw_i2c_plat_resume(), respectively,
so that they are not executed in parallel with each other, for
example if runtime resume of the device takes place during system
suspend.
Finally, add "suspended" and "skip_resume" flags to struct dw_i2c_dev
and make dw_i2c_plat_suspend() and dw_i2c_plat_resume() use them to
avoid suspending or resuming the device twice in a row and to avoid
resuming a previously runtime-suspended device during system resume.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Tested-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Tested-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Tested-by: Johannes Stezenbach <js@sig21.net>
Tested-by: Rajat Jain <rajatja@google.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
2017-09-25 07:30:51 +08:00
|
|
|
SET_LATE_SYSTEM_SLEEP_PM_OPS(dw_i2c_plat_suspend, dw_i2c_plat_resume)
|
|
|
|
SET_RUNTIME_PM_OPS(dw_i2c_plat_suspend, dw_i2c_plat_resume, NULL)
|
2015-05-20 22:33:13 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
#define DW_I2C_DEV_PMOPS (&dw_i2c_dev_pm_ops)
|
|
|
|
#else
|
|
|
|
#define DW_I2C_DEV_PMOPS NULL
|
|
|
|
#endif
|
2014-05-15 22:37:23 +08:00
|
|
|
|
2017-06-14 18:43:21 +08:00
|
|
|
/* Work with hotplug and coldplug */
|
2011-10-29 17:57:23 +08:00
|
|
|
MODULE_ALIAS("platform:i2c_designware");
|
|
|
|
|
|
|
|
static struct platform_driver dw_i2c_driver = {
|
2015-08-31 22:31:32 +08:00
|
|
|
.probe = dw_i2c_plat_probe,
|
|
|
|
.remove = dw_i2c_plat_remove,
|
2011-10-29 17:57:23 +08:00
|
|
|
.driver = {
|
|
|
|
.name = "i2c_designware",
|
2011-11-09 04:43:47 +08:00
|
|
|
.of_match_table = of_match_ptr(dw_i2c_of_match),
|
2013-01-17 18:31:07 +08:00
|
|
|
.acpi_match_table = ACPI_PTR(dw_i2c_acpi_match),
|
2015-05-20 22:33:13 +08:00
|
|
|
.pm = DW_I2C_DEV_PMOPS,
|
2011-10-29 17:57:23 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init dw_i2c_init_driver(void)
|
|
|
|
{
|
2013-10-09 04:35:33 +08:00
|
|
|
return platform_driver_register(&dw_i2c_driver);
|
2011-10-29 17:57:23 +08:00
|
|
|
}
|
2012-02-29 14:57:46 +08:00
|
|
|
subsys_initcall(dw_i2c_init_driver);
|
2011-10-29 17:57:23 +08:00
|
|
|
|
|
|
|
static void __exit dw_i2c_exit_driver(void)
|
|
|
|
{
|
|
|
|
platform_driver_unregister(&dw_i2c_driver);
|
|
|
|
}
|
|
|
|
module_exit(dw_i2c_exit_driver);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>");
|
|
|
|
MODULE_DESCRIPTION("Synopsys DesignWare I2C bus adapter");
|
|
|
|
MODULE_LICENSE("GPL");
|