diff options
Diffstat (limited to 'drivers/staging/media/atomisp/platform')
8 files changed, 1386 insertions, 0 deletions
diff --git a/drivers/staging/media/atomisp/platform/Makefile b/drivers/staging/media/atomisp/platform/Makefile new file mode 100644 index 0000000..df15763 --- /dev/null +++ b/drivers/staging/media/atomisp/platform/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for camera drivers. +# + +obj-$(CONFIG_INTEL_ATOMISP) += clock/ +obj-$(CONFIG_INTEL_ATOMISP) += intel-mid/ diff --git a/drivers/staging/media/atomisp/platform/clock/Makefile b/drivers/staging/media/atomisp/platform/clock/Makefile new file mode 100644 index 0000000..82fbe8b --- /dev/null +++ b/drivers/staging/media/atomisp/platform/clock/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for clock devices. +# + +obj-$(CONFIG_INTEL_ATOMISP) += vlv2_plat_clock.o +obj-$(CONFIG_INTEL_ATOMISP) += platform_vlv2_plat_clk.o diff --git a/drivers/staging/media/atomisp/platform/clock/platform_vlv2_plat_clk.c b/drivers/staging/media/atomisp/platform/clock/platform_vlv2_plat_clk.c new file mode 100644 index 0000000..0aae9b0 --- /dev/null +++ b/drivers/staging/media/atomisp/platform/clock/platform_vlv2_plat_clk.c @@ -0,0 +1,40 @@ +/* + * platform_vlv2_plat_clk.c - VLV2 platform clock driver + * Copyright (C) 2013 Intel Corporation + * + * Author: Asutosh Pathak <asutosh.pathak@intel.com> + * Author: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com> + * Author: Sergio Aguirre <sergio.a.aguirre.rodriguez@intel.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; version 2 of the License. + * + * 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. + * + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/printk.h> + +static int __init vlv2_plat_clk_init(void) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("vlv2_plat_clk", -1, NULL, 0); + if (IS_ERR(pdev)) { + pr_err("platform_vlv2_plat_clk:register failed: %ld\n", + PTR_ERR(pdev)); + return PTR_ERR(pdev); + } + + return 0; +} + +device_initcall(vlv2_plat_clk_init); diff --git a/drivers/staging/media/atomisp/platform/clock/platform_vlv2_plat_clk.h b/drivers/staging/media/atomisp/platform/clock/platform_vlv2_plat_clk.h new file mode 100644 index 0000000..b730ab0 --- /dev/null +++ b/drivers/staging/media/atomisp/platform/clock/platform_vlv2_plat_clk.h @@ -0,0 +1,27 @@ +/* + * platform_vlv2_plat_clk.h: platform clock driver library header file + * Copyright (C) 2013 Intel Corporation + * + * Author: Asutosh Pathak <asutosh.pathak@intel.com> + * Author: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com> + * Author: Sergio Aguirre <sergio.a.aguirre.rodriguez@intel.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; version 2 of the License. + * + * 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. + * + */ +#ifndef _PLATFORM_VLV2_PLAT_CLK_H_ +#define _PLATFORM_VLV2_PLAT_CLK_H_ + +#include <linux/sfi.h> +#include <asm/intel-mid.h> + +extern void __init *vlv2_plat_clk_device_platform_data( + void *info) __attribute__((weak)); +#endif diff --git a/drivers/staging/media/atomisp/platform/clock/vlv2_plat_clock.c b/drivers/staging/media/atomisp/platform/clock/vlv2_plat_clock.c new file mode 100644 index 0000000..f96789a --- /dev/null +++ b/drivers/staging/media/atomisp/platform/clock/vlv2_plat_clock.c @@ -0,0 +1,247 @@ +/* + * vlv2_plat_clock.c - VLV2 platform clock driver + * Copyright (C) 2013 Intel Corporation + * + * Author: Asutosh Pathak <asutosh.pathak@intel.com> + * Author: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.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; version 2 of the License. + * + * 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/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include "../../include/linux/vlv2_plat_clock.h" + +/* NOTE: Most of below constants could come from platform data. + * To be fixed when appropriate ACPI support comes. + */ +#define VLV2_PMC_CLK_BASE_ADDRESS 0xfed03060 +#define PLT_CLK_CTL_OFFSET(x) (0x04 * (x)) + +#define CLK_CONFG_BIT_POS 0 +#define CLK_CONFG_BIT_LEN 2 +#define CLK_CONFG_D3_GATED 0 +#define CLK_CONFG_FORCE_ON 1 +#define CLK_CONFG_FORCE_OFF 2 + +#define CLK_FREQ_TYPE_BIT_POS 2 +#define CLK_FREQ_TYPE_BIT_LEN 1 +#define CLK_FREQ_TYPE_XTAL 0 /* 25 MHz */ +#define CLK_FREQ_TYPE_PLL 1 /* 19.2 MHz */ + +#define MAX_CLK_COUNT 5 + +/* Helper macros to manipulate bitfields */ +#define REG_MASK(n) (((1 << (n##_BIT_LEN)) - 1) << (n##_BIT_POS)) +#define REG_SET_FIELD(r, n, v) (((r) & ~REG_MASK(n)) | \ + (((v) << (n##_BIT_POS)) & REG_MASK(n))) +#define REG_GET_FIELD(r, n) (((r) & REG_MASK(n)) >> n##_BIT_POS) +/* + * vlv2 platform has 6 platform clocks, controlled by 4 byte registers + * Total size required for mapping is 6*4 = 24 bytes + */ +#define PMC_MAP_SIZE 24 + +static DEFINE_MUTEX(clk_mutex); +static void __iomem *pmc_base; + +/* + * vlv2_plat_set_clock_freq - Set clock frequency to a specified platform clock + * @clk_num: Platform clock number (i.e. 0, 1, 2, ...,5) + * @freq_type: Clock frequency (0-25 MHz(XTAL), 1-19.2 MHz(PLL) ) + */ +int vlv2_plat_set_clock_freq(int clk_num, int freq_type) +{ + void __iomem *addr; + + if (clk_num < 0 || clk_num >= MAX_CLK_COUNT) { + pr_err("Clock number out of range (%d)\n", clk_num); + return -EINVAL; + } + + if (freq_type != CLK_FREQ_TYPE_XTAL && + freq_type != CLK_FREQ_TYPE_PLL) { + pr_err("wrong clock type\n"); + return -EINVAL; + } + + if (!pmc_base) { + pr_err("memio map is not set\n"); + return -EINVAL; + } + + addr = pmc_base + PLT_CLK_CTL_OFFSET(clk_num); + + mutex_lock(&clk_mutex); + writel(REG_SET_FIELD(readl(addr), CLK_FREQ_TYPE, freq_type), addr); + mutex_unlock(&clk_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(vlv2_plat_set_clock_freq); + +/* + * vlv2_plat_get_clock_freq - Get the status of specified platform clock + * @clk_num: Platform clock number (i.e. 0, 1, 2, ...,5) + * + * Returns 0 for 25 MHz(XTAL) and 1 for 19.2 MHz(PLL) + */ +int vlv2_plat_get_clock_freq(int clk_num) +{ + u32 ret; + + if (clk_num < 0 || clk_num >= MAX_CLK_COUNT) { + pr_err("Clock number out of range (%d)\n", clk_num); + return -EINVAL; + } + + if (!pmc_base) { + pr_err("memio map is not set\n"); + return -EINVAL; + } + + mutex_lock(&clk_mutex); + ret = REG_GET_FIELD(readl(pmc_base + PLT_CLK_CTL_OFFSET(clk_num)), + CLK_FREQ_TYPE); + mutex_unlock(&clk_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(vlv2_plat_get_clock_freq); + +/* + * vlv2_plat_configure_clock - Configure the specified platform clock + * @clk_num: Platform clock number (i.e. 0, 1, 2, ...,5) + * @conf: Clock gating: + * 0 - Clock gated on D3 state + * 1 - Force on + * 2,3 - Force off + */ +int vlv2_plat_configure_clock(int clk_num, u32 conf) +{ + void __iomem *addr; + + if (clk_num < 0 || clk_num >= MAX_CLK_COUNT) { + pr_err("Clock number out of range (%d)\n", clk_num); + return -EINVAL; + } + + if (conf != CLK_CONFG_D3_GATED && + conf != CLK_CONFG_FORCE_ON && + conf != CLK_CONFG_FORCE_OFF) { + pr_err("Invalid clock configuration requested\n"); + return -EINVAL; + } + + if (!pmc_base) { + pr_err("memio map is not set\n"); + return -EINVAL; + } + + addr = pmc_base + PLT_CLK_CTL_OFFSET(clk_num); + + mutex_lock(&clk_mutex); + writel(REG_SET_FIELD(readl(addr), CLK_CONFG, conf), addr); + mutex_unlock(&clk_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(vlv2_plat_configure_clock); + +/* + * vlv2_plat_get_clock_status - Get the status of specified platform clock + * @clk_num: Platform clock number (i.e. 0, 1, 2, ...,5) + * + * Returns 1 - On, 0 - Off + */ +int vlv2_plat_get_clock_status(int clk_num) +{ + int ret; + + if (clk_num < 0 || clk_num >= MAX_CLK_COUNT) { + pr_err("Clock number out of range (%d)\n", clk_num); + return -EINVAL; + } + + if (!pmc_base) { + pr_err("memio map is not set\n"); + return -EINVAL; + } + + mutex_lock(&clk_mutex); + ret = (int)REG_GET_FIELD(readl(pmc_base + PLT_CLK_CTL_OFFSET(clk_num)), + CLK_CONFG); + mutex_unlock(&clk_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(vlv2_plat_get_clock_status); + +static int vlv2_plat_clk_probe(struct platform_device *pdev) +{ + int i = 0; + + pmc_base = ioremap_nocache(VLV2_PMC_CLK_BASE_ADDRESS, PMC_MAP_SIZE); + if (!pmc_base) { + dev_err(&pdev->dev, "I/O memory remapping failed\n"); + return -ENOMEM; + } + + /* Initialize all clocks as disabled */ + for (i = 0; i < MAX_CLK_COUNT; i++) + vlv2_plat_configure_clock(i, CLK_CONFG_FORCE_OFF); + + dev_info(&pdev->dev, "vlv2_plat_clk initialized\n"); + return 0; +} + +static const struct platform_device_id vlv2_plat_clk_id[] = { + {"vlv2_plat_clk", 0}, + {} +}; + +static int vlv2_resume(struct device *device) +{ + int i; + + /* Initialize all clocks as disabled */ + for (i = 0; i < MAX_CLK_COUNT; i++) + vlv2_plat_configure_clock(i, CLK_CONFG_FORCE_OFF); + + return 0; +} + +static int vlv2_suspend(struct device *device) +{ + return 0; +} + +static const struct dev_pm_ops vlv2_pm_ops = { + .suspend = vlv2_suspend, + .resume = vlv2_resume, +}; + +static struct platform_driver vlv2_plat_clk_driver = { + .probe = vlv2_plat_clk_probe, + .id_table = vlv2_plat_clk_id, + .driver = { + .name = "vlv2_plat_clk", + .pm = &vlv2_pm_ops, + }, +}; + +static int __init vlv2_plat_clk_init(void) +{ + return platform_driver_register(&vlv2_plat_clk_driver); +} +arch_initcall(vlv2_plat_clk_init); diff --git a/drivers/staging/media/atomisp/platform/intel-mid/Makefile b/drivers/staging/media/atomisp/platform/intel-mid/Makefile new file mode 100644 index 0000000..4621261 --- /dev/null +++ b/drivers/staging/media/atomisp/platform/intel-mid/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for intel-mid devices. +# +obj-$(CONFIG_INTEL_ATOMISP) += intel_mid_pcihelpers.o +obj-$(CONFIG_INTEL_ATOMISP) += atomisp_gmin_platform.o diff --git a/drivers/staging/media/atomisp/platform/intel-mid/atomisp_gmin_platform.c b/drivers/staging/media/atomisp/platform/intel-mid/atomisp_gmin_platform.c new file mode 100644 index 0000000..5b4506a --- /dev/null +++ b/drivers/staging/media/atomisp/platform/intel-mid/atomisp_gmin_platform.c @@ -0,0 +1,758 @@ +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/dmi.h> +#include <linux/efi.h> +#include <linux/pci.h> +#include <linux/acpi.h> +#include <linux/delay.h> +#include <media/v4l2-subdev.h> +#include <linux/mfd/intel_soc_pmic.h> +#include "../../include/linux/vlv2_plat_clock.h" +#include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include "../../include/linux/atomisp_platform.h" +#include "../../include/linux/atomisp_gmin_platform.h" + +#define MAX_SUBDEVS 8 + +/* Should be defined in vlv2_plat_clock API, isn't: */ +#define VLV2_CLK_PLL_19P2MHZ 1 +#define VLV2_CLK_XTAL_19P2MHZ 0 +#define VLV2_CLK_ON 1 +#define VLV2_CLK_OFF 2 +#define ELDO1_SEL_REG 0x19 +#define ELDO1_1P8V 0x16 +#define ELDO1_CTRL_SHIFT 0x00 +#define ELDO2_SEL_REG 0x1a +#define ELDO2_1P8V 0x16 +#define ELDO2_CTRL_SHIFT 0x01 + +struct gmin_subdev { + struct v4l2_subdev *subdev; + int clock_num; + int clock_src; + struct gpio_desc *gpio0; + struct gpio_desc *gpio1; + struct regulator *v1p8_reg; + struct regulator *v2p8_reg; + struct regulator *v1p2_reg; + struct regulator *v2p8_vcm_reg; + enum atomisp_camera_port csi_port; + unsigned int csi_lanes; + enum atomisp_input_format csi_fmt; + enum atomisp_bayer_order csi_bayer; + bool v1p8_on; + bool v2p8_on; + bool v1p2_on; + bool v2p8_vcm_on; +}; + +static struct gmin_subdev gmin_subdevs[MAX_SUBDEVS]; + +static enum { PMIC_UNSET = 0, PMIC_REGULATOR, PMIC_AXP, PMIC_TI , + PMIC_CRYSTALCOVE } pmic_id; + +/* The atomisp uses type==0 for the end-of-list marker, so leave space. */ +static struct intel_v4l2_subdev_table pdata_subdevs[MAX_SUBDEVS + 1]; + +static const struct atomisp_platform_data pdata = { + .subdevs = pdata_subdevs, +}; + +/* + * Something of a hack. The ECS E7 board drives camera 2.8v from an + * external regulator instead of the PMIC. There's a gmin_CamV2P8 + * config variable that specifies the GPIO to handle this particular + * case, but this needs a broader architecture for handling camera + * power. + */ +enum { V2P8_GPIO_UNSET = -2, V2P8_GPIO_NONE = -1 }; +static int v2p8_gpio = V2P8_GPIO_UNSET; + +/* + * Something of a hack. The CHT RVP board drives camera 1.8v from an + * external regulator instead of the PMIC just like ECS E7 board, see the + * comments above. + */ +enum { V1P8_GPIO_UNSET = -2, V1P8_GPIO_NONE = -1 }; +static int v1p8_gpio = V1P8_GPIO_UNSET; + +static LIST_HEAD(vcm_devices); +static DEFINE_MUTEX(vcm_lock); + +static struct gmin_subdev *find_gmin_subdev(struct v4l2_subdev *subdev); + +/* + * Legacy/stub behavior copied from upstream platform_camera.c. The + * atomisp driver relies on these values being non-NULL in a few + * places, even though they are hard-coded in all current + * implementations. + */ +const struct atomisp_camera_caps *atomisp_get_default_camera_caps(void) +{ + static const struct atomisp_camera_caps caps = { + .sensor_num = 1, + .sensor = { + { .stream_num = 1, }, + }, + }; + return ∩︀ +} +EXPORT_SYMBOL_GPL(atomisp_get_default_camera_caps); + +const struct atomisp_platform_data *atomisp_get_platform_data(void) +{ + return &pdata; +} +EXPORT_SYMBOL_GPL(atomisp_get_platform_data); + +static int af_power_ctrl(struct v4l2_subdev *subdev, int flag) +{ + struct gmin_subdev *gs = find_gmin_subdev(subdev); + + if (gs && gs->v2p8_vcm_on == flag) + return 0; + gs->v2p8_vcm_on = flag; + + /* + * The power here is used for dw9817, + * regulator is from rear sensor + */ + if (gs->v2p8_vcm_reg) { + if (flag) + return regulator_enable(gs->v2p8_vcm_reg); + else + return regulator_disable(gs->v2p8_vcm_reg); + } + return 0; +} + +/* + * Used in a handful of modules. Focus motor control, I think. Note + * that there is no configurability in the API, so this needs to be + * fixed where it is used. + * + * struct camera_af_platform_data { + * int (*power_ctrl)(struct v4l2_subdev *subdev, int flag); + * }; + * + * Note that the implementation in MCG platform_camera.c is stubbed + * out anyway (i.e. returns zero from the callback) on BYT. So + * neither needed on gmin platforms or supported upstream. + */ +const struct camera_af_platform_data *camera_get_af_platform_data(void) +{ + static struct camera_af_platform_data afpd = { + .power_ctrl = af_power_ctrl, + }; + return &afpd; +} +EXPORT_SYMBOL_GPL(camera_get_af_platform_data); + +int atomisp_register_i2c_module(struct v4l2_subdev *subdev, + struct camera_sensor_platform_data *plat_data, + enum intel_v4l2_subdev_type type) +{ + int i; + struct i2c_board_info *bi; + struct gmin_subdev *gs; + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct acpi_device *adev; + + dev_info(&client->dev, "register atomisp i2c module type %d\n", type); + + /* The windows driver model (and thus most BIOSes by default) + * uses ACPI runtime power management for camera devices, but + * we don't. Disable it, or else the rails will be needlessly + * tickled during suspend/resume. This has caused power and + * performance issues on multiple devices. */ + adev = ACPI_COMPANION(&client->dev); + if (adev) + adev->power.flags.power_resources = 0; + + for (i=0; i < MAX_SUBDEVS; i++) + if (!pdata.subdevs[i].type) + break; + + if (pdata.subdevs[i].type) + return -ENOMEM; + + /* Note subtlety of initialization order: at the point where + * this registration API gets called, the platform data + * callbacks have probably already been invoked, so the + * gmin_subdev struct is already initialized for us. */ + gs = find_gmin_subdev(subdev); + + pdata.subdevs[i].type = type; + pdata.subdevs[i].port = gs->csi_port; + pdata.subdevs[i].subdev = subdev; + pdata.subdevs[i].v4l2_subdev.i2c_adapter_id = client->adapter->nr; + + /* Convert i2c_client to i2c_board_info */ + bi = &pdata.subdevs[i].v4l2_subdev.board_info; + memcpy(bi->type, client->name, I2C_NAME_SIZE); + bi->flags = client->flags; + bi->addr = client->addr; + bi->irq = client->irq; + bi->platform_data = plat_data; + + return 0; +} +EXPORT_SYMBOL_GPL(atomisp_register_i2c_module); + +struct v4l2_subdev *atomisp_gmin_find_subdev(struct i2c_adapter *adapter, + struct i2c_board_info *board_info) +{ + int i; + for (i=0; i < MAX_SUBDEVS && pdata.subdevs[i].type; i++) { + struct intel_v4l2_subdev_table *sd = &pdata.subdevs[i]; + if (sd->v4l2_subdev.i2c_adapter_id == adapter->nr && + sd->v4l2_subdev.board_info.addr == board_info->addr) + return sd->subdev; + } + return NULL; +} +EXPORT_SYMBOL_GPL(atomisp_gmin_find_subdev); + +int atomisp_gmin_remove_subdev(struct v4l2_subdev *sd) +{ + int i, j; + + if (!sd) + return 0; + + for (i = 0; i < MAX_SUBDEVS; i++) { + if (pdata.subdevs[i].subdev == sd) { + for (j = i + 1; j <= MAX_SUBDEVS; j++) + pdata.subdevs[j - 1] = pdata.subdevs[j]; + } + if (gmin_subdevs[i].subdev == sd) { + if (gmin_subdevs[i].gpio0) + gpiod_put(gmin_subdevs[i].gpio0); + gmin_subdevs[i].gpio0 = NULL; + if (gmin_subdevs[i].gpio1) + gpiod_put(gmin_subdevs[i].gpio1); + gmin_subdevs[i].gpio1 = NULL; + if (pmic_id == PMIC_REGULATOR) { + regulator_put(gmin_subdevs[i].v1p8_reg); + regulator_put(gmin_subdevs[i].v2p8_reg); + regulator_put(gmin_subdevs[i].v1p2_reg); + regulator_put(gmin_subdevs[i].v2p8_vcm_reg); + } + gmin_subdevs[i].subdev = NULL; + } + } + return 0; +} +EXPORT_SYMBOL_GPL(atomisp_gmin_remove_subdev); + +struct gmin_cfg_var { + const char *name, *val; +}; + +static const struct gmin_cfg_var ffrd8_vars[] = { + { "INTCF1B:00_ImxId", "0x134" }, + { "INTCF1B:00_CsiPort", "1" }, + { "INTCF1B:00_CsiLanes", "4" }, + { "INTCF1B:00_CamClk", "0" }, + {}, +}; + +/* Cribbed from MCG defaults in the mt9m114 driver, not actually verified + * vs. T100 hardware */ +static const struct gmin_cfg_var t100_vars[] = { + { "INT33F0:00_CsiPort", "0" }, + { "INT33F0:00_CsiLanes", "1" }, + { "INT33F0:00_CamClk", "1" }, + {}, +}; + +static const struct gmin_cfg_var mrd7_vars[] = { + {"INT33F8:00_CamType", "1"}, + {"INT33F8:00_CsiPort", "1"}, + {"INT33F8:00_CsiLanes","2"}, + {"INT33F8:00_CsiFmt","13"}, + {"INT33F8:00_CsiBayer", "0"}, + {"INT33F8:00_CamClk", "0"}, + {"INT33F9:00_CamType", "1"}, + {"INT33F9:00_CsiPort", "0"}, + {"INT33F9:00_CsiLanes","1"}, + {"INT33F9:00_CsiFmt","13"}, + {"INT33F9:00_CsiBayer", "0"}, + {"INT33F9:00_CamClk", "1"}, + {}, +}; + +static const struct gmin_cfg_var ecs7_vars[] = { + {"INT33BE:00_CsiPort", "1"}, + {"INT33BE:00_CsiLanes","2"}, + {"INT33BE:00_CsiFmt","13"}, + {"INT33BE:00_CsiBayer", "2"}, + {"INT33BE:00_CamClk", "0"}, + {"INT33F0:00_CsiPort", "0"}, + {"INT33F0:00_CsiLanes","1"}, + {"INT33F0:00_CsiFmt","13"}, + {"INT33F0:00_CsiBayer", "0"}, + {"INT33F0:00_CamClk", "1"}, + {"gmin_V2P8GPIO","402"}, + {}, +}; + + +static const struct gmin_cfg_var i8880_vars[] = { + {"XXOV2680:00_CsiPort", "1"}, + {"XXOV2680:00_CsiLanes","1"}, + {"XXOV2680:00_CamClk","0"}, + {"XXGC0310:00_CsiPort", "0"}, + {"XXGC0310:00_CsiLanes", "1"}, + {"XXGC0310:00_CamClk", "1"}, + {}, +}; + +static const struct { + const char *dmi_board_name; + const struct gmin_cfg_var *vars; +} hard_vars[] = { + { "BYT-T FFD8", ffrd8_vars }, + { "T100TA", t100_vars }, + { "MRD7", mrd7_vars }, + { "ST70408", ecs7_vars }, + { "VTA0803", i8880_vars }, +}; + + +#define GMIN_CFG_VAR_EFI_GUID EFI_GUID(0xecb54cd9, 0xe5ae, 0x4fdc, \ + 0xa9, 0x71, 0xe8, 0x77, \ + 0x75, 0x60, 0x68, 0xf7) + +#define CFG_VAR_NAME_MAX 64 + +static int gmin_platform_init(struct i2c_client *client) +{ + return 0; +} + +static int gmin_platform_deinit(void) +{ + return 0; +} + +static struct gmin_subdev *gmin_subdev_add(struct v4l2_subdev *subdev) +{ + int i, ret; + struct device *dev; + struct i2c_client *client = v4l2_get_subdevdata(subdev); + + if (!pmic_id) { + + pmic_id = PMIC_REGULATOR; + } + + if (!client) + return NULL; + + dev = &client->dev; + + for (i=0; i < MAX_SUBDEVS && gmin_subdevs[i].subdev; i++) + ; + if (i >= MAX_SUBDEVS) + return NULL; + + dev_info(dev, + "gmin: initializing atomisp module subdev data.PMIC ID %d\n", + pmic_id); + + gmin_subdevs[i].subdev = subdev; + gmin_subdevs[i].clock_num = gmin_get_var_int(dev, "CamClk", 0); + /*WA:CHT requires XTAL clock as PLL is not stable.*/ + gmin_subdevs[i].clock_src = gmin_get_var_int(dev, "ClkSrc", + VLV2_CLK_PLL_19P2MHZ); + gmin_subdevs[i].csi_port = gmin_get_var_int(dev, "CsiPort", 0); + gmin_subdevs[i].csi_lanes = gmin_get_var_int(dev, "CsiLanes", 1); + gmin_subdevs[i].gpio0 = gpiod_get_index(dev, NULL, 0, GPIOD_OUT_LOW); + gmin_subdevs[i].gpio1 = gpiod_get_index(dev, NULL, 1, GPIOD_OUT_LOW); + + if (!IS_ERR(gmin_subdevs[i].gpio0)) { + ret = gpiod_direction_output(gmin_subdevs[i].gpio0, 0); + if (ret) + dev_err(dev, "gpio0 set output failed: %d\n", ret); + } else { + gmin_subdevs[i].gpio0 = NULL; + } + + if (!IS_ERR(gmin_subdevs[i].gpio1)) { + ret = gpiod_direction_output(gmin_subdevs[i].gpio1, 0); + if (ret) + dev_err(dev, "gpio1 set output failed: %d\n", ret); + } else { + gmin_subdevs[i].gpio1 = NULL; + } + + if (pmic_id == PMIC_REGULATOR) { + gmin_subdevs[i].v1p8_reg = regulator_get(dev, "V1P8SX"); + gmin_subdevs[i].v2p8_reg = regulator_get(dev, "V2P8SX"); + gmin_subdevs[i].v1p2_reg = regulator_get(dev, "V1P2A"); + gmin_subdevs[i].v2p8_vcm_reg = regulator_get(dev, "VPROG4B"); + + /* Note: ideally we would initialize v[12]p8_on to the + * output of regulator_is_enabled(), but sadly that + * API is broken with the current drivers, returning + * "1" for a regulator that will then emit a + * "unbalanced disable" WARNing if we try to disable + * it. */ + } + + return &gmin_subdevs[i]; +} + +static struct gmin_subdev *find_gmin_subdev(struct v4l2_subdev *subdev) +{ + int i; + for (i=0; i < MAX_SUBDEVS; i++) + if (gmin_subdevs[i].subdev == subdev) + return &gmin_subdevs[i]; + return gmin_subdev_add(subdev); +} + +static int gmin_gpio0_ctrl(struct v4l2_subdev *subdev, int on) +{ + struct gmin_subdev *gs = find_gmin_subdev(subdev); + if (gs && gs->gpio0) { + gpiod_set_value(gs->gpio0, on); + return 0; + } + return -EINVAL; +} + +static int gmin_gpio1_ctrl(struct v4l2_subdev *subdev, int on) +{ + struct gmin_subdev *gs = find_gmin_subdev(subdev); + if (gs && gs->gpio1) { + gpiod_set_value(gs->gpio1, on); + return 0; + } + return -EINVAL; +} + +int gmin_v1p2_ctrl(struct v4l2_subdev *subdev, int on) +{ + struct gmin_subdev *gs = find_gmin_subdev(subdev); + + if (gs && gs->v1p2_on == on) + return 0; + gs->v1p2_on = on; + + if (gs->v1p2_reg) { + if (on) + return regulator_enable(gs->v1p2_reg); + else + return regulator_disable(gs->v1p2_reg); + } + + /*TODO:v1p2 needs to extend to other PMICs*/ + + return -EINVAL; +} +int gmin_v1p8_ctrl(struct v4l2_subdev *subdev, int on) +{ + struct gmin_subdev *gs = find_gmin_subdev(subdev); + int ret; + + if (v1p8_gpio == V1P8_GPIO_UNSET) { + v1p8_gpio = gmin_get_var_int(NULL, "V1P8GPIO", V1P8_GPIO_NONE); + if (v1p8_gpio != V1P8_GPIO_NONE) { + pr_info("atomisp_gmin_platform: 1.8v power on GPIO %d\n", + v1p8_gpio); + ret = gpio_request(v1p8_gpio, "camera_v1p8_en"); + if (!ret) + ret = gpio_direction_output(v1p8_gpio, 0); + if (ret) + pr_err("V1P8 GPIO initialization failed\n"); + } + } + + if (gs && gs->v1p8_on == on) + return 0; + gs->v1p8_on = on; + + if (v1p8_gpio >= 0) + gpio_set_value(v1p8_gpio, on); + + if (gs->v1p8_reg) { + regulator_set_voltage(gs->v1p8_reg, 1800000, 1800000); + if (on) + return regulator_enable(gs->v1p8_reg); + else + return regulator_disable(gs->v1p8_reg); + } + + return -EINVAL; +} + +int gmin_v2p8_ctrl(struct v4l2_subdev *subdev, int on) +{ + struct gmin_subdev *gs = find_gmin_subdev(subdev); + int ret; + + if (v2p8_gpio == V2P8_GPIO_UNSET) { + v2p8_gpio = gmin_get_var_int(NULL, "V2P8GPIO", V2P8_GPIO_NONE); + if (v2p8_gpio != V2P8_GPIO_NONE) { + pr_info("atomisp_gmin_platform: 2.8v power on GPIO %d\n", + v2p8_gpio); + ret = gpio_request(v2p8_gpio, "camera_v2p8"); + if (!ret) + ret = gpio_direction_output(v2p8_gpio, 0); + if (ret) + pr_err("V2P8 GPIO initialization failed\n"); + } + } + + if (gs && gs->v2p8_on == on) + return 0; + gs->v2p8_on = on; + + if (v2p8_gpio >= 0) + gpio_set_value(v2p8_gpio, on); + + if (gs->v2p8_reg) { + regulator_set_voltage(gs->v2p8_reg, 2900000, 2900000); + if (on) + return regulator_enable(gs->v2p8_reg); + else + return regulator_disable(gs->v2p8_reg); + } + + return -EINVAL; +} + +int gmin_flisclk_ctrl(struct v4l2_subdev *subdev, int on) +{ + int ret = 0; + struct gmin_subdev *gs = find_gmin_subdev(subdev); + if (on) + ret = vlv2_plat_set_clock_freq(gs->clock_num, gs->clock_src); + if (ret) + return ret; + return vlv2_plat_configure_clock(gs->clock_num, + on ? VLV2_CLK_ON : VLV2_CLK_OFF); +} + +static int gmin_csi_cfg(struct v4l2_subdev *sd, int flag) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct gmin_subdev *gs = find_gmin_subdev(sd); + + if (!client || !gs) + return -ENODEV; + + return camera_sensor_csi(sd, gs->csi_port, gs->csi_lanes, + gs->csi_fmt, gs->csi_bayer, flag); +} + +static struct camera_vcm_control *gmin_get_vcm_ctrl(struct v4l2_subdev *subdev, + char *camera_module) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct gmin_subdev *gs = find_gmin_subdev(subdev); + struct camera_vcm_control *vcm; + + if (client == NULL || gs == NULL) + return NULL; + + if (!camera_module) + return NULL; + + mutex_lock(&vcm_lock); + list_for_each_entry(vcm, &vcm_devices, list) { + if (!strcmp(camera_module, vcm->camera_module)) { + mutex_unlock(&vcm_lock); + return vcm; + } + } + + mutex_unlock(&vcm_lock); + return NULL; +} + +static struct camera_sensor_platform_data gmin_plat = { + .gpio0_ctrl = gmin_gpio0_ctrl, + .gpio1_ctrl = gmin_gpio1_ctrl, + .v1p8_ctrl = gmin_v1p8_ctrl, + .v2p8_ctrl = gmin_v2p8_ctrl, + .v1p2_ctrl = gmin_v1p2_ctrl, + .flisclk_ctrl = gmin_flisclk_ctrl, + .platform_init = gmin_platform_init, + .platform_deinit = gmin_platform_deinit, + .csi_cfg = gmin_csi_cfg, + .get_vcm_ctrl = gmin_get_vcm_ctrl, +}; + +struct camera_sensor_platform_data *gmin_camera_platform_data( + struct v4l2_subdev *subdev, + enum atomisp_input_format csi_format, + enum atomisp_bayer_order csi_bayer) +{ + struct gmin_subdev *gs = find_gmin_subdev(subdev); + gs->csi_fmt = csi_format; + gs->csi_bayer = csi_bayer; + + return &gmin_plat; +} +EXPORT_SYMBOL_GPL(gmin_camera_platform_data); + +int atomisp_gmin_register_vcm_control(struct camera_vcm_control *vcmCtrl) +{ + if (!vcmCtrl) + return -EINVAL; + + mutex_lock(&vcm_lock); + list_add_tail(&vcmCtrl->list, &vcm_devices); + mutex_unlock(&vcm_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(atomisp_gmin_register_vcm_control); + +/* Retrieves a device-specific configuration variable. The dev + * argument should be a device with an ACPI companion, as all + * configuration is based on firmware ID. */ +int gmin_get_config_var(struct device *dev, const char *var, char *out, size_t *out_len) +{ + char var8[CFG_VAR_NAME_MAX]; + efi_char16_t var16[CFG_VAR_NAME_MAX]; + struct efivar_entry *ev; + u32 efiattr_dummy; + int i, j, ret; + unsigned long efilen; + + if (dev && ACPI_COMPANION(dev)) + dev = &ACPI_COMPANION(dev)->dev; + + if (dev) + ret = snprintf(var8, sizeof(var8), "%s_%s", dev_name(dev), var); + else + ret = snprintf(var8, sizeof(var8), "gmin_%s", var); + + if (ret < 0 || ret >= sizeof(var8) - 1) + return -EINVAL; + + /* First check a hard-coded list of board-specific variables. + * Some device firmwares lack the ability to set EFI variables at + * runtime. */ + for (i = 0; i < ARRAY_SIZE(hard_vars); i++) { + if (dmi_match(DMI_BOARD_NAME, hard_vars[i].dmi_board_name)) { + for (j = 0; hard_vars[i].vars[j].name; j++) { + size_t vl; + const struct gmin_cfg_var *gv; + + gv = &hard_vars[i].vars[j]; + vl = strlen(gv->val); + + if (strcmp(var8, gv->name)) + continue; + if (vl > *out_len - 1) + return -ENOSPC; + + memcpy(out, gv->val, min(*out_len, vl+1)); + out[*out_len-1] = 0; + *out_len = vl; + + return 0; + } + } + } + + /* Our variable names are ASCII by construction, but EFI names + * are wide chars. Convert and zero-pad. */ + memset(var16, 0, sizeof(var16)); + for (i = 0; i < sizeof(var8) && var8[i]; i++) + var16[i] = var8[i]; + + /* To avoid owerflows when calling the efivar API */ + if (*out_len > ULONG_MAX) + return -EINVAL; + + /* Not sure this API usage is kosher; efivar_entry_get()'s + * implementation simply uses VariableName and VendorGuid from + * the struct and ignores the rest, but it seems like there + * ought to be an "official" efivar_entry registered + * somewhere? */ + ev = kzalloc(sizeof(*ev), GFP_KERNEL); + if (!ev) + return -ENOMEM; + memcpy(&ev->var.VariableName, var16, sizeof(var16)); + ev->var.VendorGuid = GMIN_CFG_VAR_EFI_GUID; + + efilen = *out_len; + ret = efivar_entry_get(ev, &efiattr_dummy, &efilen, out); + + kfree(ev); + *out_len = efilen; + + if (ret) + dev_warn(dev, "Failed to find gmin variable %s\n", var8); + + return ret; +} +EXPORT_SYMBOL_GPL(gmin_get_config_var); + +int gmin_get_var_int(struct device *dev, const char *var, int def) +{ + char val[CFG_VAR_NAME_MAX]; + size_t len = sizeof(val); + long result; + int ret; + + ret = gmin_get_config_var(dev, var, val, &len); + if (!ret) { + val[len] = 0; + ret = kstrtol(val, 0, &result); + } + + return ret ? def : result; +} +EXPORT_SYMBOL_GPL(gmin_get_var_int); + +int camera_sensor_csi(struct v4l2_subdev *sd, u32 port, + u32 lanes, u32 format, u32 bayer_order, int flag) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct camera_mipi_info *csi = NULL; + + if (flag) { + csi = kzalloc(sizeof(*csi), GFP_KERNEL); + if (!csi) { + dev_err(&client->dev, "out of memory\n"); + return -ENOMEM; + } + csi->port = port; + csi->num_lanes = lanes; + csi->input_format = format; + csi->raw_bayer_order = bayer_order; + v4l2_set_subdev_hostdata(sd, (void *)csi); + csi->metadata_format = ATOMISP_INPUT_FORMAT_EMBEDDED; + csi->metadata_effective_width = NULL; + dev_info(&client->dev, + "camera pdata: port: %d lanes: %d order: %8.8x\n", + port, lanes, bayer_order); + } else { + csi = v4l2_get_subdev_hostdata(sd); + kfree(csi); + } + + return 0; +} +EXPORT_SYMBOL_GPL(camera_sensor_csi); + +/* PCI quirk: The BYT ISP advertises PCI runtime PM but it doesn't + * work. Disable so the kernel framework doesn't hang the device + * trying. The driver itself does direct calls to the PUNIT to manage + * ISP power. */ +static void isp_pm_cap_fixup(struct pci_dev *dev) +{ + dev_info(&dev->dev, "Disabling PCI power management on camera ISP\n"); + dev->pm_cap = 0; +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x0f38, isp_pm_cap_fixup); diff --git a/drivers/staging/media/atomisp/platform/intel-mid/intel_mid_pcihelpers.c b/drivers/staging/media/atomisp/platform/intel-mid/intel_mid_pcihelpers.c new file mode 100644 index 0000000..a6c0f5f --- /dev/null +++ b/drivers/staging/media/atomisp/platform/intel-mid/intel_mid_pcihelpers.c @@ -0,0 +1,297 @@ +#include <linux/export.h> +#include <linux/pci.h> +#include <linux/pm_qos.h> +#include <linux/delay.h> + +/* G-Min addition: "platform_is()" lives in intel_mid_pm.h in the MCG + * tree, but it's just platform ID info and we don't want to pull in + * the whole SFI-based PM architecture. */ +#define INTEL_ATOM_MRST 0x26 +#define INTEL_ATOM_MFLD 0x27 +#define INTEL_ATOM_CLV 0x35 +#define INTEL_ATOM_MRFLD 0x4a +#define INTEL_ATOM_BYT 0x37 +#define INTEL_ATOM_MOORFLD 0x5a +#define INTEL_ATOM_CHT 0x4c +/* synchronization for sharing the I2C controller */ +#define PUNIT_PORT 0x04 +#define PUNIT_DOORBELL_OPCODE (0xE0) +#define PUNIT_DOORBELL_REG (0x0) +#ifndef CSTATE_EXIT_LATENCY +#define CSTATE_EXIT_LATENCY_C1 1 +#endif +static inline int platform_is(u8 model) +{ + return (boot_cpu_data.x86_model == model); +} + +#include "../../include/asm/intel_mid_pcihelpers.h" + +/* Unified message bus read/write operation */ +static DEFINE_SPINLOCK(msgbus_lock); + +static struct pci_dev *pci_root; +static struct pm_qos_request pm_qos; +int qos; + +#define DW_I2C_NEED_QOS (platform_is(INTEL_ATOM_BYT)) + +static int intel_mid_msgbus_init(void) +{ + pci_root = pci_get_bus_and_slot(0, PCI_DEVFN(0, 0)); + if (!pci_root) { + pr_err("%s: Error: msgbus PCI handle NULL\n", __func__); + return -ENODEV; + } + + if (DW_I2C_NEED_QOS) { + pm_qos_add_request(&pm_qos, + PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + } + return 0; +} +fs_initcall(intel_mid_msgbus_init); + +u32 intel_mid_msgbus_read32_raw(u32 cmd) +{ + unsigned long irq_flags; + u32 data; + + spin_lock_irqsave(&msgbus_lock, irq_flags); + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd); + pci_read_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, &data); + spin_unlock_irqrestore(&msgbus_lock, irq_flags); + + return data; +} +EXPORT_SYMBOL(intel_mid_msgbus_read32_raw); + +/* + * GU: this function is only used by the VISA and 'VXD' drivers. + */ +u32 intel_mid_msgbus_read32_raw_ext(u32 cmd, u32 cmd_ext) +{ + unsigned long irq_flags; + u32 data; + + spin_lock_irqsave(&msgbus_lock, irq_flags); + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_EXT_REG, cmd_ext); + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd); + pci_read_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, &data); + spin_unlock_irqrestore(&msgbus_lock, irq_flags); + + return data; +} +EXPORT_SYMBOL(intel_mid_msgbus_read32_raw_ext); + +void intel_mid_msgbus_write32_raw(u32 cmd, u32 data) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&msgbus_lock, irq_flags); + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, data); + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd); + spin_unlock_irqrestore(&msgbus_lock, irq_flags); +} +EXPORT_SYMBOL(intel_mid_msgbus_write32_raw); + +/* + * GU: this function is only used by the VISA and 'VXD' drivers. + */ +void intel_mid_msgbus_write32_raw_ext(u32 cmd, u32 cmd_ext, u32 data) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&msgbus_lock, irq_flags); + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, data); + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_EXT_REG, cmd_ext); + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd); + spin_unlock_irqrestore(&msgbus_lock, irq_flags); +} +EXPORT_SYMBOL(intel_mid_msgbus_write32_raw_ext); + +u32 intel_mid_msgbus_read32(u8 port, u32 addr) +{ + unsigned long irq_flags; + u32 data; + u32 cmd; + u32 cmdext; + + cmd = (PCI_ROOT_MSGBUS_READ << 24) | (port << 16) | + ((addr & 0xff) << 8) | PCI_ROOT_MSGBUS_DWORD_ENABLE; + cmdext = addr & 0xffffff00; + + spin_lock_irqsave(&msgbus_lock, irq_flags); + + if (cmdext) { + /* This resets to 0 automatically, no need to write 0 */ + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_EXT_REG, + cmdext); + } + + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd); + pci_read_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, &data); + spin_unlock_irqrestore(&msgbus_lock, irq_flags); + + return data; +} + +EXPORT_SYMBOL(intel_mid_msgbus_read32); +void intel_mid_msgbus_write32(u8 port, u32 addr, u32 data) +{ + unsigned long irq_flags; + u32 cmd; + u32 cmdext; + + cmd = (PCI_ROOT_MSGBUS_WRITE << 24) | (port << 16) | + ((addr & 0xFF) << 8) | PCI_ROOT_MSGBUS_DWORD_ENABLE; + cmdext = addr & 0xffffff00; + + spin_lock_irqsave(&msgbus_lock, irq_flags); + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, data); + + if (cmdext) { + /* This resets to 0 automatically, no need to write 0 */ + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_EXT_REG, + cmdext); + } + + pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd); + spin_unlock_irqrestore(&msgbus_lock, irq_flags); +} +EXPORT_SYMBOL(intel_mid_msgbus_write32); + +/* called only from where is later then fs_initcall */ +u32 intel_mid_soc_stepping(void) +{ + return pci_root->revision; +} +EXPORT_SYMBOL(intel_mid_soc_stepping); + +static bool is_south_complex_device(struct pci_dev *dev) +{ + unsigned base_class = dev->class >> 16; + unsigned sub_class = (dev->class & SUB_CLASS_MASK) >> 8; + + /* other than camera, pci bridges and display, + * everything else are south complex devices. + */ + if (((base_class == PCI_BASE_CLASS_MULTIMEDIA) && + (sub_class == ISP_SUB_CLASS)) || + (base_class == PCI_BASE_CLASS_BRIDGE) || + ((base_class == PCI_BASE_CLASS_DISPLAY) && !sub_class)) + return false; + else + return true; +} + +/* In BYT platform, d3_delay for internal south complex devices, + * they are not subject to 10 ms d3 to d0 delay required by pci spec. + */ +static void pci_d3_delay_fixup(struct pci_dev *dev) +{ + if (platform_is(INTEL_ATOM_BYT) || + platform_is(INTEL_ATOM_CHT)) { + /* All internal devices are in bus 0. */ + if (dev->bus->number == 0 && is_south_complex_device(dev)) { + dev->d3_delay = INTERNAL_PCI_PM_D3_WAIT; + dev->d3cold_delay = INTERNAL_PCI_PM_D3_WAIT; + } + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_d3_delay_fixup); + +#define PUNIT_SEMAPHORE (platform_is(INTEL_ATOM_BYT) ? 0x7 : 0x10E) +#define GET_SEM() (intel_mid_msgbus_read32(PUNIT_PORT, PUNIT_SEMAPHORE) & 0x1) + +static void reset_semaphore(void) +{ + u32 data; + + data = intel_mid_msgbus_read32(PUNIT_PORT, PUNIT_SEMAPHORE); + smp_mb(); + data = data & 0xfffffffc; + intel_mid_msgbus_write32(PUNIT_PORT, PUNIT_SEMAPHORE, data); + smp_mb(); + +} + +int intel_mid_dw_i2c_acquire_ownership(void) +{ + u32 ret = 0; + u32 data = 0; /* data sent to PUNIT */ + u32 cmd; + u32 cmdext; + int timeout = 1000; + + if (DW_I2C_NEED_QOS) + pm_qos_update_request(&pm_qos, CSTATE_EXIT_LATENCY_C1 - 1); + + /* + * We need disable irq. Otherwise, the main thread + * might be preempted and the other thread jumps to + * disable irq for a long time. Another case is + * some irq handlers might trigger power voltage change + */ + BUG_ON(irqs_disabled()); + local_irq_disable(); + + /* host driver writes 0x2 to side band register 0x7 */ + intel_mid_msgbus_write32(PUNIT_PORT, PUNIT_SEMAPHORE, 0x2); + smp_mb(); + + /* host driver sends 0xE0 opcode to PUNIT and writes 0 register */ + cmd = (PUNIT_DOORBELL_OPCODE << 24) | (PUNIT_PORT << 16) | + ((PUNIT_DOORBELL_REG & 0xFF) << 8) | PCI_ROOT_MSGBUS_DWORD_ENABLE; + cmdext = PUNIT_DOORBELL_REG & 0xffffff00; + + if (cmdext) + intel_mid_msgbus_write32_raw_ext(cmd, cmdext, data); + else + intel_mid_msgbus_write32_raw(cmd, data); + + /* host driver waits for bit 0 to be set in side band 0x7 */ + while (GET_SEM() != 0x1) { + udelay(100); + timeout--; + if (timeout <= 0) { + pr_err("Timeout: semaphore timed out, reset sem\n"); + ret = -ETIMEDOUT; + reset_semaphore(); + /*Delay 1ms in case race with punit*/ + udelay(1000); + if (GET_SEM() != 0) { + /*Reset again as kernel might race with punit*/ + reset_semaphore(); + } + pr_err("PUNIT SEM: %d\n", + intel_mid_msgbus_read32(PUNIT_PORT, + PUNIT_SEMAPHORE)); + local_irq_enable(); + + if (DW_I2C_NEED_QOS) { + pm_qos_update_request(&pm_qos, + PM_QOS_DEFAULT_VALUE); + } + + return ret; + } + } + smp_mb(); + + return ret; +} +EXPORT_SYMBOL(intel_mid_dw_i2c_acquire_ownership); + +int intel_mid_dw_i2c_release_ownership(void) +{ + reset_semaphore(); + local_irq_enable(); + + if (DW_I2C_NEED_QOS) + pm_qos_update_request(&pm_qos, PM_QOS_DEFAULT_VALUE); + + return 0; +} +EXPORT_SYMBOL(intel_mid_dw_i2c_release_ownership); |