diff options
Diffstat (limited to 'drivers/staging')
231 files changed, 66321 insertions, 82 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 9e634724978..350f6df2bf4 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -24,6 +24,16 @@ menuconfig STAGING if STAGING +config AB5500_SIM + bool "ST-Ericsson AB5500 SIM Interface driver" + depends on AB5500_CORE + help + SIM Interface driver provides interface to configure + various parameters of AB5550 SIM Level Shifter.Support provided are: + Configure Pull up on sim lines + Configure Operation Mode + Notify Sim Insert/Extract Interrupt + source "drivers/staging/serial/Kconfig" source "drivers/staging/et131x/Kconfig" @@ -38,6 +48,8 @@ source "drivers/staging/wlan-ng/Kconfig" source "drivers/staging/echo/Kconfig" +source "drivers/staging/cg2900/Kconfig" + source "drivers/staging/comedi/Kconfig" source "drivers/staging/olpc_dcon/Kconfig" @@ -128,4 +140,12 @@ source "drivers/staging/omapdrm/Kconfig" source "drivers/staging/android/Kconfig" +source "drivers/staging/cw1200/Kconfig" + +source "drivers/staging/mmio/Kconfig" + +source "drivers/staging/nmf-cm/Kconfig" + +source "drivers/staging/camera_flash/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 943e1483075..f1f5eaad334 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_USBIP_CORE) += usbip/ obj-$(CONFIG_W35UND) += winbond/ obj-$(CONFIG_PRISM2_USB) += wlan-ng/ obj-$(CONFIG_ECHO) += echo/ +obj-$(CONFIG_CG2900) += cg2900/ obj-$(CONFIG_COMEDI) += comedi/ obj-$(CONFIG_FB_OLPC_DCON) += olpc_dcon/ obj-$(CONFIG_ASUS_OLED) += asus_oled/ @@ -55,3 +56,8 @@ obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_MFD_NVEC) += nvec/ obj-$(CONFIG_DRM_OMAP) += omapdrm/ obj-$(CONFIG_ANDROID) += android/ +obj-$(CONFIG_CW1200) += cw1200/ +obj-$(CONFIG_U8500_MMIO) += mmio/ +obj-$(CONFIG_U8500_FLASH) += camera_flash/ +obj-$(CONFIG_U8500_CM) += nmf-cm/ +obj-$(CONFIG_AB5500_SIM) += ab5500_sim/ diff --git a/drivers/staging/ab5500_sim/Makefile b/drivers/staging/ab5500_sim/Makefile new file mode 100644 index 00000000000..520717e4dd7 --- /dev/null +++ b/drivers/staging/ab5500_sim/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_AB5500_SIM) += ab5500-sim.o diff --git a/drivers/staging/ab5500_sim/ab5500-sim.c b/drivers/staging/ab5500_sim/ab5500-sim.c new file mode 100644 index 00000000000..d222a22ed24 --- /dev/null +++ b/drivers/staging/ab5500_sim/ab5500-sim.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) ST Ericsson SA 2010 + * + * Sim Interface driver for AB5500 + * + * License Terms: GNU General Public License v2 + * Author: Bibek Basu <bibek.basu@stericsson.com> + */ +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> +#include <linux/kobject.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/io.h> +#include <linux/err.h> + +#define USIM_SUP2_REG 0x13 +#define USIM_SUP_REG 0x14 +#define USIM_SIMCTRL_REG 0x17 +#define USIM_SIMCTRL2_REG 0x18 +#define USIM_USBUICC_REG 0x19 +#define USIM_USBUICC2_REG 0x20 +#define SIM_DAT_PULLUP_10K 0x0F +#define SIM_LDO_1_8V 1875000 +#define SIM_LDO_2_8V 2800000 +#define SIM_LDO_2_9V 2900000 + +enum shift { + SHIFT0, + SHIFT1, + SHIFT2, + SHIFT3, + SHIFT4, + SHIFT5, + SHIFT6, + SHIFT7, +}; + +enum mask { + MASK1 = 1, + MASK3 = 3, + MASK7 = 7, +}; + +enum sim_mode { + OFF_MODE, + LOW_PWR, + PWRCTRL, + FULL_PWR, +}; +/** + * struct ab5500_sim - ab5500 Sim Interface device information + * @dev: pointer to the structure device + * @lock: mutex lock + * @sim_int_status: Sim presence status + * @irq_base: Base of the two irqs + */ +struct ab5500_sim { + struct device *dev; + struct mutex lock; + bool sim_int_status; + u8 irq_base; +}; + +/* Exposure to the sysfs interface */ +int ab5500_sim_weak_pulldforce(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + unsigned long user_val; + int err; + bool enable; + + err = strict_strtoul(user_buf, 0, &user_val); + if (err) + return -EINVAL; + enable = user_val ? true : false; + err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM, + USIM_USBUICC2_REG, MASK1 << SHIFT5, user_val << SHIFT5); + if (err) + return -EINVAL; + return count; +} + +int ab5500_sim_load_sel(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + unsigned long user_val; + int err; + bool enable; + + err = strict_strtoul(user_buf, 0, &user_val); + if (err) + return -EINVAL; + enable = user_val ? true : false; + err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM, + USIM_USBUICC_REG, MASK1 << SHIFT1, user_val << SHIFT1); + if (err) + return -EINVAL; + return count; +} + +int ab5500_sim_mode_sel(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + unsigned long user_val; + int err; + + err = strict_strtoul(user_buf, 0, &user_val); + if (err) + return -EINVAL; + err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM, + USIM_SIMCTRL2_REG, MASK3 << SHIFT4, user_val << SHIFT4); + if (err) + return -EINVAL; + return count; +} + +int ab5500_sim_dat_pullup(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + unsigned long user_val; + int err; + + err = strict_strtoul(user_buf, 0, &user_val); + if (err) + return -EINVAL; + err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM, + USIM_SIMCTRL_REG, MASK7, user_val); + if (err) + return -EINVAL; + return count; +} + +int ab5500_sim_enable_pullup(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + unsigned long user_val; + int err; + bool enable; + + err = strict_strtoul(user_buf, 0, &user_val); + if (err) + return -EINVAL; + enable = user_val ? true : false; + err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM, + USIM_SIMCTRL_REG, MASK1 << SHIFT3, enable << SHIFT3); + if (err) + return -EINVAL; + return count; +} + +static ssize_t ab5500_simoff_int(struct device *dev, + struct device_attribute *devattr, char *user_buf) +{ + struct ab5500_sim *di = dev_get_drvdata(dev); + int len; + + mutex_lock(&di->lock); + len = sprintf(user_buf, "%d\n", di->sim_int_status); + mutex_unlock(&di->lock); + return len; +} + +static DEVICE_ATTR(enable_pullup, S_IWUSR, NULL, ab5500_sim_enable_pullup); +static DEVICE_ATTR(dat_pullup, S_IWUSR, NULL, ab5500_sim_dat_pullup); +static DEVICE_ATTR(mode_sel, S_IWUSR, NULL, ab5500_sim_mode_sel); +static DEVICE_ATTR(load_sel, S_IWUSR, NULL, ab5500_sim_load_sel); +static DEVICE_ATTR(weak_pulldforce, S_IWUSR, NULL, ab5500_sim_weak_pulldforce); +static DEVICE_ATTR(simoff_int, S_IRUGO, ab5500_simoff_int, NULL); + +static struct attribute *ab5500_sim_attributes[] = { + &dev_attr_enable_pullup.attr, + &dev_attr_dat_pullup.attr, + &dev_attr_mode_sel.attr, + &dev_attr_load_sel.attr, + &dev_attr_weak_pulldforce.attr, + &dev_attr_simoff_int.attr, + NULL +}; + +static const struct attribute_group ab5500sim_attr_grp = { + .attrs = ab5500_sim_attributes, +}; + +static irqreturn_t ab5500_sim_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + struct ab5500_sim *data = platform_get_drvdata(pdev); + + if (irq == data->irq_base) + data->sim_int_status = true; + else + data->sim_int_status = false; + sysfs_notify(&pdev->dev.kobj, NULL, "simoff_int"); + + return IRQ_HANDLED; +} + +static int __devexit ab5500_sim_remove(struct platform_device *pdev) +{ + struct ab5500_sim *di = platform_get_drvdata(pdev); + int irq = platform_get_irq_byname(pdev, "SIMOFF"); + + if (irq >= 0) { + free_irq(irq, di); + irq++; + free_irq(irq, di); + } + sysfs_remove_group(&pdev->dev.kobj, &ab5500sim_attr_grp); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab5500_sim_probe(struct platform_device *pdev) +{ + int ret = 0; + int irq; + struct ab5500_sim *di = + kzalloc(sizeof(struct ab5500_sim), GFP_KERNEL); + if (!di) { + ret = -ENOMEM; + goto error_alloc; + } + dev_info(&pdev->dev, "ab5500_sim_driver PROBE\n"); + irq = platform_get_irq_byname(pdev, "SIMOFF"); + if (irq < 0) { + dev_err(&pdev->dev, "Get irq by name failed\n"); + ret = irq; + goto exit; + } + di->irq_base = irq; + di->dev = &pdev->dev; + mutex_init(&di->lock); + platform_set_drvdata(pdev, di); + /* sysfs interface to configure sim reg from user space */ + if (sysfs_create_group(&pdev->dev.kobj, &ab5500sim_attr_grp) < 0) { + dev_err(&pdev->dev, " Failed creating sysfs group\n"); + ret = -ENOMEM; + goto error_sysfs; + } + ret = request_threaded_irq(irq, NULL, ab5500_sim_irq_handler, + IRQF_NO_SUSPEND , "ab5500-sim", pdev); + if (ret < 0) { + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + goto error_irq; + } + /* this is the contiguous irq for sim removal,falling edge */ + irq = irq + 1; + ret = request_threaded_irq(irq, NULL, ab5500_sim_irq_handler, + IRQF_NO_SUSPEND , "ab5500-sim", pdev); + if (ret < 0) { + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + free_irq(--irq, di); + goto error_irq; + } + return ret; +error_irq: + sysfs_remove_group(&pdev->dev.kobj, &ab5500sim_attr_grp); +error_sysfs: + platform_set_drvdata(pdev, NULL); +exit: + kfree(di); +error_alloc: + return ret; +} + +static struct platform_driver ab5500_sim_driver = { + .probe = ab5500_sim_probe, + .remove = __devexit_p(ab5500_sim_remove), + .driver = { + .name = "ab5500-sim", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_sim_init(void) +{ + return platform_driver_register(&ab5500_sim_driver); +} + +static void __exit ab5500_sim_exit(void) +{ + platform_driver_unregister(&ab5500_sim_driver); +} + +module_init(ab5500_sim_init); +module_exit(ab5500_sim_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bibek Basu"); +MODULE_ALIAS("platform:ab5500-sim"); +MODULE_DESCRIPTION("AB5500 sim interface driver"); diff --git a/drivers/staging/ab5500_sim/sysfs-sim b/drivers/staging/ab5500_sim/sysfs-sim new file mode 100644 index 00000000000..b809b21e39e --- /dev/null +++ b/drivers/staging/ab5500_sim/sysfs-sim @@ -0,0 +1,83 @@ +What: /sys/devices/platform/ab5500-core.0/ab5500-sim.4/ +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4 directory contains attributes + allowing the user space to check and configure ab5500 sim level + shifter interface caracteristics for communication to SIM card + +What: /sys/devices/.../enable_pullup +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/enable_pullup attribute allows + the user space to configure if internal pull up in SIMIO lines + has to be enabled or disabled. For enabling write 1 to the file + and 0 for disabling + + +What: /sys/devices/.../dat_pullup +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/dat_pullup attribute allows + the user space to configure the resistance value for internal + pull up in SIMIO lines. Following value can be written on the file + 0 SIM_DAT pull-up disabled + 1 SIM_DAT pull-up 4kOhm + 2 SIM_DAT pull-up 5kOhm + 3 SIM_DAT pull-up 6kOhm + 4 SIM_DAT pull-up 7kOhm + 5 SIM_DAT pull-up 8kOhm + 6 SIM_DAT pull-up 9kOhm + 7 SIM_DAT pull-up 10kOhm + +What: /sys/devices/.../mode_sel +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/mode_sel attribute allows + the user space to configure the mode at which the level shifter + will work. Following value can be written on the file + 0 TG mode and LI mode off + 1 TG mode on + 2 LI mode on + 3 TG mode and LI mode off + +What: /sys/devices/.../load_sel +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/load_sel attribute allows + the user space to configure the load on the USBUICC lines. + Following value can be written on the file. + 0 Data line load < 21pF + 1 Data line load 21-30pF + +What: /sys/devices/.../weak_pulldforce +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/weak_pulldforce attribute allows + the user space to configure the weak pull down on the USBUICC lines. + Following value can be written on the file. + 0 USB-UICC data lines weak pull down active + 1 USB-UICC data lines weak pull down not active + +What: /sys/devices/.../simoff_int +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/simoff_int attribute allows + the user space to poll this file and get notified in case a sim + hot swap has happened. a zero means sim extracetd and a one means + inserted. + + diff --git a/drivers/staging/camera_flash/Kconfig b/drivers/staging/camera_flash/Kconfig new file mode 100644 index 00000000000..187217d763f --- /dev/null +++ b/drivers/staging/camera_flash/Kconfig @@ -0,0 +1,7 @@ + +config U8500_FLASH + bool "ST-Ericsson Flash (Camera) Driver" + depends on ARCH_U8500 + help + Adds ST-Ericsson Flash (Camera) Driver + diff --git a/drivers/staging/camera_flash/Makefile b/drivers/staging/camera_flash/Makefile new file mode 100644 index 00000000000..bf2f5aa2dd3 --- /dev/null +++ b/drivers/staging/camera_flash/Makefile @@ -0,0 +1,5 @@ +export ADP1653_SUPPORT +EXTRA_CFLAGS += -DADP1653_SUPPORT +obj-$(CONFIG_U8500_FLASH) := camera_flash.o +camera_flash-y := flash_common.o +camera_flash-y += adp1653.o diff --git a/drivers/staging/camera_flash/adp1653.c b/drivers/staging/camera_flash/adp1653.c new file mode 100644 index 00000000000..f7483eac11f --- /dev/null +++ b/drivers/staging/camera_flash/adp1653.c @@ -0,0 +1,537 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * adp1653: Driver Adp1653 HPLED flash driver chip. This driver + * currently support I2C interface, 2bit interface is not supported. + * Author: Pankaj Chauhan/pankaj.chauhan@stericsson.com for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/version.h> +#include <asm/mach-types.h> +#include "flash_common.h" +#include "adp1653.h" +#include "camera_flash.h" +#include "adp1653_plat.h" + +/* This data is platform specific for 8500 href-v1 platform, + * Ideally this should be supplied from platform code + */ + +static int adapter_i2c2 = 2; +static int flash_position = 0; +module_param(adapter_i2c2, int, S_IRUGO); +MODULE_PARM_DESC(adapter_i2c2, "use the given I2C adaptater to communicate with the chip"); +module_param(flash_position, int, S_IRUGO); +MODULE_PARM_DESC(flash_position, "the position of the flash chip (0=PRIMARY, 1=SECONDARY)"); + + +int __flash_gpio_to_irq(int gpio) +{ + + return NOMADIK_GPIO_TO_IRQ(gpio); +} + +#define DEBUG_LOG(...) printk(KERN_DEBUG "Adp1653 flash driver: " __VA_ARGS__) + +#define ADP1653_SUPPORTED_MODES (FLASH_MODE_VIDEO_LED | FLASH_MODE_STILL_LED | \ + FLASH_MODE_STILL_LED_EXTERNAL_STROBE | \ + FLASH_MODE_AF_ASSISTANT | FLASH_MODE_INDICATOR) + +#define ADP1653_SELFTEST_SUPPORTED_MODES (FLASH_SELFTEST_CONNECTION | FLASH_SELFTEST_FLASH_WITH_STROBE | \ + FLASH_SELFTEST_VIDEO_LIGHT | FLASH_SELFTEST_AF_LIGHT | FLASH_SELFTEST_INDICATOR | FLASH_SELFTEST_TORCH_LIGHT) + +static int adp1653_trigger_strobe(void *priv_data, int enable); + +static int adp1653_get_modes(void *priv_data,unsigned long *modes) +{ + int err; + struct adp1653_priv_data *priv_p = (struct adp1653_priv_data *)priv_data; + err = i2c_smbus_read_byte_data(priv_p->i2c_client, FAULT_STATUS_REG); + if (err) + *modes = 0x0; + else + *modes = ADP1653_SUPPORTED_MODES; + return 0; +} + +static int adp1653_get_mode_details(void *priv_data, unsigned long mode, +struct flash_mode_details *details_p) +{ + int err = 0; + memset(details_p,0,sizeof(struct flash_mode_details)); + + details_p->led_type = 2; + + /* Still LED settings*/ + details_p->nbFaultRegisters = 1; + if(mode & (FLASH_MODE_STILL_LED | FLASH_MODE_STILL_LED_EXTERNAL_STROBE)){ + details_p->max_intensity_uAmp = FLASH_MAX_INTENSITY; + details_p->min_intensity_uAmp = FLASH_MIN_INTENSITY; + details_p->max_strobe_duration_uSecs = FLASH_MAX_STROBE_DURATION; + details_p->feature_bitmap = INTENSITY_PROGRAMMABLE | DURATION_PROGRAMMABLE; + goto out; + } + /*Video LED settings*/ + if(mode & FLASH_MODE_VIDEO_LED){ + details_p->max_intensity_uAmp = TORCH_MAX_INTENSITY; + details_p->min_intensity_uAmp = TORCH_MIN_INTENSITY; + details_p->max_strobe_duration_uSecs = 0; + details_p->feature_bitmap = INTENSITY_PROGRAMMABLE; + goto out; + } + /*Privacy Indicator settings */ + if(mode & FLASH_MODE_INDICATOR){ + details_p->max_intensity_uAmp = ILED_MAX_INTENSITY; + details_p->min_intensity_uAmp = ILED_MIN_INTENSITY; + details_p->max_strobe_duration_uSecs = 0; + details_p->feature_bitmap = INTENSITY_PROGRAMMABLE; + goto out; + } + DEBUG_LOG("Mode %lx, not supported\n",mode); + err = EINVAL; +out: + return err; +} + +static int adp1653_enable_flash_mode(void *priv_data, + unsigned long mode, int enable) +{ + int err = 0; + struct adp1653_priv_data *priv_p = (struct adp1653_priv_data *)priv_data; + + if(enable){ + + if((!(mode & ADP1653_SUPPORTED_MODES)) && + (mode != FLASH_MODE_NONE)) { + DEBUG_LOG("Unsupported mode %lx\n",mode); + err = -EINVAL; + goto out; + } + /*Nothing to be done in enabling, just set current mode and return*/ + /*May be enable disable can be done here but why not enable in + *probe and keep it on always + */ + adp1653_trigger_strobe(priv_p,0); + priv_p->curr_mode = mode; + }else{ + adp1653_trigger_strobe(priv_p,0); + priv_p->curr_mode =0; + } +out: + return err; +} + +static int adp1653_configure_flash_mode(void *priv,unsigned long mode, +struct flash_mode_params *params_p) +{ + int err = 0; + unsigned char intensity_code; + struct adp1653_priv_data *priv_p = (struct adp1653_priv_data *)priv; + + if(!(mode & ADP1653_SUPPORTED_MODES)){ + DEBUG_LOG("Mode %lx not supported\n",mode); + err = -EINVAL; + goto out; + } + switch(mode){ + case FLASH_MODE_STILL_LED: + case FLASH_MODE_STILL_LED_EXTERNAL_STROBE: + { + FLASH_UAMP_TO_CODE(intensity_code,params_p->intensity_uAmp); + if(params_p->duration_uSecs){ + DURATION_USEC_TO_CODE(priv_p->flash_duration, + params_p->duration_uSecs); + DEBUG_LOG("Duration %lu, code 0x%x\n",params_p->duration_uSecs, + priv_p->flash_duration); + priv_p->flash_duration |= TIMER_ENABLE; + }else{ + priv_p->flash_duration = 0; + } + priv_p->flash_intensity = intensity_code << 3; + } + break; + case FLASH_MODE_VIDEO_LED: + { + TORCH_UAMP_TO_CODE(intensity_code,params_p->intensity_uAmp); + DEBUG_LOG("Torch mode setting intensity 0x%x, current(uA) %lu\n", + intensity_code,params_p->intensity_uAmp); + priv_p->torch_intensity = intensity_code << 3; + } + break; + case FLASH_MODE_INDICATOR: + { + ILED_UAMP_TO_CODE(intensity_code,params_p->intensity_uAmp); + DEBUG_LOG("ILED setting intensity 0x%x, current(uA) %lu\n", + intensity_code,params_p->intensity_uAmp); + priv_p->indicator_intensity = intensity_code; + } + break; + default: + err = -EINVAL; + DEBUG_LOG("Unsupported mode %lx\n",mode); + break; + } + + if((mode == FLASH_MODE_STILL_LED_EXTERNAL_STROBE) || (mode == FLASH_MODE_STILL_LED)) + { + adp1653_trigger_strobe(priv_p,0); + DEBUG_LOG("CONFIG_TIMER_REG : 0x%x\n",priv_p->flash_duration); + DEBUG_LOG("OUTPUT_SEL_REG : 0x%x\n",priv_p->flash_intensity); + + /*TimeOut Must be programmed before Intensity*/ + err = i2c_smbus_write_byte_data(priv_p->i2c_client,CONFIG_TIMER_REG, + priv_p->flash_duration); + if(err){ + DEBUG_LOG("I2C: Unsable to write timer config, err %d\n",err); + goto out; + } + err = i2c_smbus_write_byte_data(priv_p->i2c_client,OUTPUT_SEL_REG, + priv_p->flash_intensity); + if(err){ + DEBUG_LOG("I2C: Unable to write OUTPUT_SEL_REG , err %d\n",err); + goto out; + } + } +out: + return err; +} + +static int adp1653_set_intensity(struct adp1653_priv_data *priv_p, uint8_t intensity) +{ + return i2c_smbus_write_byte_data(priv_p->i2c_client,OUTPUT_SEL_REG,intensity); +} + +static int adp1653_strobe_still_led(struct adp1653_priv_data *priv_p,int enable) +{ + int err=0,gpio_val; + uint8_t intensity,duration; + + if(enable){ + intensity = priv_p->flash_intensity; + duration = priv_p->flash_duration; + gpio_val = 1; + }else{ + intensity = 0; + duration = 0; + gpio_val = 0; + } + + err = adp1653_set_intensity(priv_p,intensity); + if(err){ + DEBUG_LOG("I2C: Unable to write OUTPUT_SEL_REG reg, err %d\n",err); + goto out; + } + + /*TimeOut Must be programmed before Intensity*/ + err = i2c_smbus_write_byte_data(priv_p->i2c_client,CONFIG_TIMER_REG, + priv_p->flash_duration); + if(err){ + DEBUG_LOG("I2C: Unsable to write timer config, err %d\n",err); + goto out; + } + err = i2c_smbus_write_byte_data(priv_p->i2c_client,OUTPUT_SEL_REG,intensity); + if(err){ + DEBUG_LOG("I2C: Unable to write OUTPUT_SEL_REG, err %d\n",err); + goto out; + } + +out: + return err; +} + +static int adp1653_trigger_strobe(void *priv, int enable) +{ + int err = 0; + uint8_t intensity; + struct adp1653_priv_data *priv_p = (struct adp1653_priv_data *)priv; + + switch(priv_p->curr_mode){ + case FLASH_MODE_STILL_LED: + case FLASH_MODE_STILL_LED_EXTERNAL_STROBE: + err = adp1653_strobe_still_led(priv_p,enable); + break; + case FLASH_MODE_VIDEO_LED: + { + if(enable) + intensity = priv_p->torch_intensity; + else + intensity = 0; + err = adp1653_set_intensity(priv_p,intensity); + } + break; + case FLASH_MODE_INDICATOR: + { + if(enable) + intensity = priv_p->indicator_intensity; + else + intensity =0; + err = adp1653_set_intensity(priv_p,intensity); + } + break; + default: + DEBUG_LOG("Unsupported mode %lx\n",priv_p->curr_mode); + goto out; + } + if(err){ + DEBUG_LOG("Unable to enable/disable %d, strobe. Mode %lx, err %d\n",enable, + priv_p->curr_mode,err); + goto out; + } + disable_irq(priv_p->i2c_client->irq); + if(enable) + SET_FLASH_STATUS(priv_p->status,FLASH_STATUS_LIT); + else + CLR_FLASH_STATUS(priv_p->status,FLASH_STATUS_LIT); + + enable_irq(priv_p->i2c_client->irq); + +out: + return err; +} +#define FLASH_ERR_ALL (FLASH_ERR_OVER_CHARGE |FLASH_ERR_OVER_HEAT | \ + FLASH_ERR_SHORT_CIRCUIT | FLASH_ERR_TIMEOUT | \ + FLASH_ERR_OVER_VOLTAGE) +int adp1653_get_status(void *priv_data,unsigned long *status) +{ + struct adp1653_priv_data *priv_p= (struct adp1653_priv_data *)priv_data; + disable_irq(priv_p->i2c_client->irq); + if(priv_p->fault){ + if(priv_p->fault & OVER_VOLTAGE_FAULT) + SET_FLASH_ERROR(priv_p->status,FLASH_ERR_OVER_VOLTAGE); + if(priv_p->fault & TIMEOUT_FAULT) + SET_FLASH_ERROR(priv_p->status,FLASH_ERR_TIMEOUT); + if(priv_p->fault & OVER_TEMPERATURE_FAULT) + SET_FLASH_ERROR(priv_p->status,FLASH_ERR_OVER_HEAT); + if(priv_p->fault & SHORT_CIRCUIT_FAULT){ + CLR_FLASH_STATUS(priv_p->status,FLASH_STATUS_READY); + SET_FLASH_STATUS(priv_p->status,FLASH_STATUS_BROKEN); + SET_FLASH_ERROR(priv_p->status,FLASH_ERR_SHORT_CIRCUIT); + } + priv_p->fault =0; + }else{ + CLR_FLASH_ERROR(priv_p->status,FLASH_ERR_ALL); + } + enable_irq(priv_p->i2c_client->irq); + *status = priv_p->status; + return 0; +} + +int adp1653_get_selftest_modes(void *priv_data, unsigned long *modes) +{ + int err; + struct adp1653_priv_data *priv_p = (struct adp1653_priv_data *)priv_data; + err = i2c_smbus_read_byte_data(priv_p->i2c_client, FAULT_STATUS_REG); + if (err) *modes = 0x0; + else *modes = ADP1653_SELFTEST_SUPPORTED_MODES; + return 0; +} + +int adp1653_get_fault_registers(void *priv_data, unsigned long mode, unsigned long *status) +{ + int err = 0; + struct adp1653_priv_data *priv_p = (struct adp1653_priv_data *)priv_data; + + *status = i2c_smbus_read_byte_data(priv_p->i2c_client, FAULT_STATUS_REG); + + /* clear fault register */ + err = i2c_smbus_write_byte_data(priv_p->i2c_client,OUTPUT_SEL_REG,0); + if(0 != err) + { + DEBUG_LOG("Unable to write OUTPUT_SEL_REG, err %d\n",err); + } + return err; +} + +struct flash_chip_ops adp1653_ops = { + .get_modes = adp1653_get_modes, + .get_mode_details = adp1653_get_mode_details, + .get_status = adp1653_get_status, + .enable_flash_mode = adp1653_enable_flash_mode, + .configure_flash_mode = adp1653_configure_flash_mode, + .trigger_strobe = adp1653_trigger_strobe, + .get_selftest_modes = adp1653_get_selftest_modes, + .get_fault_registers = adp1653_get_fault_registers +}; + +static irqreturn_t adp1653_irq_hdlr(int irq_no,void *data) +{ + int err; + struct adp1653_priv_data *priv_p= (struct adp1653_priv_data *)data; + + priv_p->fault = i2c_smbus_read_byte_data(priv_p->i2c_client, + FAULT_STATUS_REG); + DEBUG_LOG("Got Fault, status 0x%x\n",priv_p->fault); + /*Writing 0 to OUTPUT_SEL_REG clears the interrtup + *and FAULT_STATUS_REG register + */ + err = i2c_smbus_write_byte_data(priv_p->i2c_client,OUTPUT_SEL_REG,0); + if(err) + DEBUG_LOG("Unable to write OUTPUT_SEL_REG to clr intr, err %d\n",err); + /*TBD: send even to user process*/ + return IRQ_HANDLED; +} +static int __devinit adp1653_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err = 0; + struct flash_chip *flash_chip_p=NULL; + struct adp1653_priv_data *priv_p=NULL; + struct adp1653_platform_data *pdata = client->dev.platform_data; + + DEBUG_LOG("> adp1653_probe\n"); + + priv_p = kzalloc(sizeof(struct adp1653_priv_data),GFP_KERNEL); + if(!priv_p){ + DEBUG_LOG("Kmalloc failed for priv data\n"); + err = ENOMEM; + goto err_priv; + } + priv_p->i2c_client = client; + flash_chip_p = kzalloc(sizeof(struct flash_chip),GFP_KERNEL); + if(!flash_chip_p){ + DEBUG_LOG("Kmalloc failed for flash_chip_p"); + err = ENOMEM; + goto err_flash_chip_alloc; + } + + if (!pdata) { + dev_err(&client->dev, + "%s: No platform data supplied.\n", __func__); + err = -EINVAL; + goto err_pdata; + } + + flash_chip_p->priv_data = priv_p; + flash_chip_p->ops = &adp1653_ops; + SET_FLASHCHIP_TYPE(flash_chip_p,FLASH_TYPE_HPLED); + SET_FLASHCHIP_ID(flash_chip_p,ADP1653_ID); + + strncpy(flash_chip_p->name,"Adp1653",FLASH_NAME_SIZE); + + i2c_set_clientdata(client,priv_p); + /*Request GPIO and Register IRQ if supported by platform and flash chip*/ + + err = gpio_request(pdata->enable_gpio,"Camera LED flash Enable"); + if(err){ + DEBUG_LOG("Unable to get GPIO %d, for enable\n",pdata->enable_gpio); + goto err_pdata; + } + + err = gpio_direction_output(pdata->enable_gpio, 1); + if(err){ + DEBUG_LOG("Unable to set GPIO %u in output mode, err %d\n",pdata->enable_gpio,err); + gpio_free(pdata->enable_gpio); + goto err_gpio_set; + } + gpio_set_value_cansleep(pdata->enable_gpio, 1); + + err = request_threaded_irq(gpio_to_irq(pdata->irq_no),NULL,adp1653_irq_hdlr, + IRQF_ONESHOT|IRQF_TRIGGER_FALLING, + "Adp1653 flash",priv_p); + if(err){ + DEBUG_LOG("Unable to register flash IRQ handler, irq %d, err %d\n", + pdata->irq_no,err); + goto err_irq; + } + + err = register_flash_chip(flash_position,flash_chip_p); + if(err){ + DEBUG_LOG("Failed to register Adp1653 as flash for %s camera\n", + (flash_position?"Primary":"Secondary")); + goto err_register; + } + SET_FLASH_STATUS(priv_p->status,FLASH_STATUS_READY); + DEBUG_LOG("< adp1653_probe ok\n"); + return err; +err_register: + if(pdata->irq_no) + free_irq(pdata->irq_no,NULL); +err_irq: + gpio_set_value_cansleep(pdata->enable_gpio, 0); +err_gpio_set: + if(pdata->enable_gpio) + gpio_free(pdata->enable_gpio); +err_pdata: + if(flash_chip_p) + kfree(flash_chip_p); +err_flash_chip_alloc: + if(priv_p) + kfree(priv_p); +err_priv: + DEBUG_LOG("< adp1653_probe (%d)\n", err); + return err; +} + +static int __devexit adp1653_remove(struct i2c_client *client) +{ + int err=0; + /*Nothing here yet, implement it later.*/ + return err; +} +static const struct i2c_device_id adp1653_id[] = { + { "adp1653", 0}, + {} +}; +static struct i2c_driver adp1653_i2c_driver = { + .driver = { + .name = "adp1653", + .owner = THIS_MODULE, + }, + .probe = adp1653_probe, + .remove = __devexit_p(adp1653_remove), + .id_table = adp1653_id, +}; + +int adp1653_init(void){ + int err = 0; + struct i2c_adapter *adap_p; + struct i2c_board_info info; + + /* Registration of I2C flash device is platform specific code + * Ideally it should be done from kernel (arch/arm/mach-XXX). + * Do it locally till the time it gets into platform code + * OR This portion (registration of device) and flash chip init + * Routine can be moved to Flash chip module init. */ + DEBUG_LOG("getting I2C adaptor %d\n",adapter_i2c2); + adap_p = i2c_get_adapter(adapter_i2c2); + if(!adap_p){ + DEBUG_LOG("Unable to get I2C adaptor\n"); + goto out; + } + memset(&info,0,sizeof( struct i2c_board_info)); + + strcpy(&info.type[0],"adp1653"); + DEBUG_LOG("trying to register %s at position %d\n", + info.type, + flash_position); + + /* I2C framework expects least significant 7 bits as address, not complete + * 8 bits with bit 0 (read/write bit) + */ + info.addr = 0x60 >> 1; + + err = i2c_add_driver(&adp1653_i2c_driver); + if(err) + { + DEBUG_LOG("Failed to register i2c driver\n"); + goto out; + } + + DEBUG_LOG("Initialized adp1653\n"); + if(!i2c_new_device(adap_p,&info)){ + DEBUG_LOG("Unable to add i2c dev: %s (err=%d)\n",info.type, err); + goto out; + } +out: + return err; +} + +/* +MODULE_DEPEND +*/ diff --git a/drivers/staging/camera_flash/adp1653.h b/drivers/staging/camera_flash/adp1653.h new file mode 100755 index 00000000000..3035ab56d99 --- /dev/null +++ b/drivers/staging/camera_flash/adp1653.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * License terms: GNU General Public License (GPL), version 2. + */ +#ifndef __ADP1653_H__ +#define __ADP1653_H__ + +#include <linux/types.h> +#define ADP1653_ID (0) /*chip does not give any id :) so be it zero!*/ + +#define OUTPUT_SEL_REG (0x00) +#define CONFIG_TIMER_REG (0x01) +#define SW_STROBE_REG (0x02) +#define FAULT_STATUS_REG (0x03) + +/* Fault codes, FALUT_STATUS_REG bits */ +#define OVER_VOLTAGE_FAULT (0x01) +#define TIMEOUT_FAULT (0x02) +#define OVER_TEMPERATURE_FAULT (0x04) +#define SHORT_CIRCUIT_FAULT (0x08) + +/*CONFIG_TIMER_REG bits*/ +#define TIMER_ENABLE (0x10) + +struct adp1653_priv_data{ + struct i2c_client *i2c_client; + unsigned long curr_mode; + unsigned long enable_gpio; + unsigned long strobe_gpio; + unsigned long irq_no; + unsigned long status; + uint8_t fault; + uint8_t flash_intensity; + uint8_t flash_duration; + uint8_t torch_intensity; + uint8_t indicator_intensity; +}; + +/*Intensity current limits in Micro Amps*/ +/* over 250mA flash current is reduced */ +/* do not know why, neither really care about */ +//#define FLASH_MAX_INTENSITY (500000) /*code - 31*/ +#define FLASH_MAX_INTENSITY (250000) +#define FLASH_MIN_INTENSITY (215000) /*code - 12*/ +#define TORCH_MAX_INTENSITY (200000) /*code - 11*/ +#define TORCH_MIN_INTENSITY (50000) /*code - 1*/ +#define ILED_MAX_INTENSITY (17500) /*Code - 7*/ +#define ILED_MIN_INTENSITY (2500) /*code - 1*/ + +#define FLASH_MAX_STROBE_DURATION (820000) /*820 uSec*/ + +#define DURATION_USEC_TO_CODE(_code,_duration) do{ \ + if(_duration > FLASH_MAX_STROBE_DURATION) \ + _duration = FLASH_MAX_STROBE_DURATION; \ + _code = (FLASH_MAX_STROBE_DURATION - _duration) / 54600;\ +}while(0); + +#define HPLED_UAMP_TO_CODE(_current) ((_current - 35000) / 15000) + +#define FLASH_UAMP_TO_CODE(_code,_current){ \ + if(_current > FLASH_MAX_INTENSITY) \ + _current = FLASH_MAX_INTENSITY; \ + if(_current < FLASH_MIN_INTENSITY) \ + _current = FLASH_MIN_INTENSITY; \ + _code = HPLED_UAMP_TO_CODE(_current); \ +}while(0) + +#define TORCH_UAMP_TO_CODE(_code,_current){ \ + if(_current > TORCH_MAX_INTENSITY) \ + _current = TORCH_MAX_INTENSITY; \ + if(_current < TORCH_MIN_INTENSITY) \ + _current = TORCH_MIN_INTENSITY; \ + _code = HPLED_UAMP_TO_CODE(_current); \ +}while(0) + +#define ILED_UAMP_TO_CODE(_code,_current) do { \ + if(_current > ILED_MAX_INTENSITY) \ + _current = ILED_MAX_INTENSITY; \ + _code = _current / ILED_MIN_INTENSITY; /* Min current: 2.5mA/2500uA*/ \ +}while(0) + +#endif diff --git a/drivers/staging/camera_flash/adp1653_plat.h b/drivers/staging/camera_flash/adp1653_plat.h new file mode 100755 index 00000000000..325097aa2a8 --- /dev/null +++ b/drivers/staging/camera_flash/adp1653_plat.h @@ -0,0 +1,24 @@ +/* + * adp1653_plat.h + * ADP1653 Led Flash Driver platform specific structures + * + * Copyright (C) ST-Ericsson SA 2011 + * Author: Rajat Verma <rajat.verma@stericsson.com> + * + * License Terms: GNU General Public License v2 + */ + +#ifndef __LINUX_I2C_ADP1653_PLAT_H__ +#define __LINUX_I2C_ADP1653_PLAT_H__ + +/** + * struct adp1653_platform_data - platform data structure for adp1653 + * @enable_gpio: gpio for chip enable/disable + * @irq_no: interrupt line for flash ic + */ +struct adp1653_platform_data { + u32 enable_gpio; + u32 irq_no; +}; + +#endif //__LINUX_I2C_ADP1653_PLAT_H__ diff --git a/drivers/staging/camera_flash/camera_flash.h b/drivers/staging/camera_flash/camera_flash.h new file mode 100644 index 00000000000..15faf706dc9 --- /dev/null +++ b/drivers/staging/camera_flash/camera_flash.h @@ -0,0 +1,74 @@ +#ifndef __CAMERA_FLASH_H__ +#define __CAMERA_FLASH_H__ + +#define FLASH_NAME_SIZE (20) + +struct flash_mode_details { + unsigned long led_type; + unsigned long max_intensity_uAmp; + unsigned long min_intensity_uAmp; + unsigned long max_strobe_duration_uSecs; + unsigned long feature_bitmap; + unsigned char nbFaultRegisters; +}; + +/*feature_bitmap (in struct flash_mode_details) bit values*/ +#define INTENSITY_PROGRAMMABLE (0x01) +#define DURATION_PROGRAMMABLE (0x02) +#define TIMEOUT_PROGRAMMABLE (0x04) + +/*Status word returned by driver has status in lower 16 bits + *and Error in higher 16 bits. definition of status and error + *bits are there in flash_bitfields.h + */ +#define SET_FLASH_STATUS(_bitmap, _status) (_bitmap |= (_status & 0xffff)) +#define CLR_FLASH_STATUS(_bitmap, _status) (_bitmap &= ~(_status & 0xffff)) +#define SET_FLASH_ERROR(_bitmap, _status) (_bitmap |= (_status << 16)) +#define CLR_FLASH_ERROR(_bitmap, _status) (_bitmap &= ~(_status << 16)) +#define GET_FLASH_STATUS(_bitmap) (_bitmap & 0xffff) +#define GET_FLASH_ERROR(_bitmap) (_bitmap >> 16) + +struct flash_mode_params { + unsigned long duration_uSecs; + unsigned long intensity_uAmp; + unsigned long timeout_uSecs; +}; + +struct flash_ioctl_args_t { + unsigned long flash_mode; + unsigned long cam; + unsigned long status; + union mode_arg{ + struct flash_mode_details details; + struct flash_mode_params params; + unsigned long strobe_enable; + } mode_arg; +}; + +#define FLASH_MAGIC_NUMBER 0x17 +#define FLASH_GET_MODES _IOR(FLASH_MAGIC_NUMBER, 1,\ +struct flash_ioctl_args_t *) +#define FLASH_GET_MODE_DETAILS _IOWR(FLASH_MAGIC_NUMBER, 2,\ +struct flash_ioctl_args_t *) +#define FLASH_ENABLE_MODE _IOW(FLASH_MAGIC_NUMBER, 3,\ +struct flash_ioctl_args_t *) +#define FLASH_DISABLE_MODE _IOW(FLASH_MAGIC_NUMBER, 4,\ +struct flash_ioctl_args_t *) +#define FLASH_CONFIGURE_MODE _IOW(FLASH_MAGIC_NUMBER, 5,\ +struct flash_ioctl_args_t *) +#define FLASH_TRIGGER_STROBE _IOW(FLASH_MAGIC_NUMBER, 6,\ +struct flash_ioctl_args_t *) +#define FLASH_GET_STATUS _IOW(FLASH_MAGIC_NUMBER, 7,\ +struct flash_ioctl_args_t *) +#define FLASH_GET_LIFE_COUNTER _IOW(FLASH_MAGIC_NUMBER, 8,\ +struct flash_ioctl_args_t *) +#define FLASH_GET_SELF_TEST_MODES _IOR(FLASH_MAGIC_NUMBER, 9,\ +struct flash_ioctl_args_t *) +#define FLASH_SELF_TEST _IOW(FLASH_MAGIC_NUMBER, 10,\ +struct flash_ioctl_args_t *) +#define FLASH_GET_FAULT_REGISTERS _IOR(FLASH_MAGIC_NUMBER, 11,\ +struct flash_ioctl_args_t *) +#define FLASH_GET_SELF_TEST_RESULT _IOR(FLASH_MAGIC_NUMBER, 12,\ +struct flash_ioctl_args_t *) + +#endif diff --git a/drivers/staging/camera_flash/camera_flash_bitfields.h b/drivers/staging/camera_flash/camera_flash_bitfields.h new file mode 100644 index 00000000000..05da9c5ef58 --- /dev/null +++ b/drivers/staging/camera_flash/camera_flash_bitfields.h @@ -0,0 +1,83 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + */ +/** +* \file camera_flash_bitfields.h +* \brief Define some constants for the flash drivers API. +* \author ST-Ericsson +*/ +#ifndef __CAMERA_FLASH_BITFIELDS_H__ +#define __CAMERA_FLASH_BITFIELDS_H__ + +/* Flash Mode definitions */ +/* All Operating Modes are off (shutdown low power state)*/ +#define FLASH_MODE_NONE (0x000) +/* Enables the xenon driver. Strobe is managed by the flash driver itself. +Charges the xenon. Automatic periodic recharge is abstracted by the driver */ +#define FLASH_MODE_XENON (0x001) +/* Enables the xenon driver. Strobe is managed externally to the driver */ +#define FLASH_MODE_XENON_EXTERNAL_STROBE (0x002) +/* Enables the video led driver. Strobing is managed by the driver */ +#define FLASH_MODE_VIDEO_LED (0x004) +/* Enables the video led driver. Strobing is managed externally to driver */ +#define FLASH_MODE_VIDEO_LED_EXTERNAL_STROBE (0x008) +/* Enables the still LED driver. Strobing is managed by the driver itself */ +#define FLASH_MODE_STILL_LED (0x010) +/* Enables the still LED driver. Strobe is managed externally to the driver */ +#define FLASH_MODE_STILL_LED_EXTERNAL_STROBE (0x020) +/* Enables the AF assistant driver. Strobe is managed by the driver */ +#define FLASH_MODE_AF_ASSISTANT (0x040) +/* Enable the driver. Strobe is managed by the driver */ +#define FLASH_MODE_INDICATOR (0x080) +/* Enables the still HP LED driver. Strobing is managed by the driver itself */ +#define FLASH_MODE_STILL_HPLED (0x100) +/* Enables the still HP LED driver. Strobe is managed externally to the +driver */ +#define FLASH_MODE_STILL_HPLED_EXTERNAL_STROBE (0x200) + + +/* The flash is not usable anymore */ +#define FLASH_STATUS_BROKEN (0x00) +/* The flash is ready to be fired and unlit */ +#define FLASH_STATUS_READY (0x01) +/* The flash is discharged and by construction, charging; usually an +application shall not try to fire it in that state (although possible +typically in sport mode flash) */ +#define FLASH_STATUS_NOT_READY (0x02) +/* The flash is in shutdown state */ +#define FLASH_STATUS_SHUTDOWN (0x04) +/* Intermediate state that may exist where I2C registers can be programmed */ +#define FLASH_STATUS_STANDBY (0x08) +/* The flash is already strobing */ +#define FLASH_STATUS_LIT (0x10) + +#define FLASH_SELFTEST_NONE 0x000 +/* tests connections to flash driver ICs */ +#define FLASH_SELFTEST_CONNECTION 0x001 +/* tests capture flash without using strobe signal from camera */ +#define FLASH_SELFTEST_FLASH 0x002 +/* tests capture flash using strobe signal from camera: ONLY this one needs to +be done in idle state from flash tests cases */ +#define FLASH_SELFTEST_FLASH_WITH_STROBE 0x004 +/* tests video light */ +#define FLASH_SELFTEST_VIDEO_LIGHT 0x008 +/* tests AF assistance light */ +#define FLASH_SELFTEST_AF_LIGHT 0x010 +/* tests capture indicator light */ +#define FLASH_SELFTEST_INDICATOR 0x020 +/* tests flash in torch mode */ +#define FLASH_SELFTEST_TORCH_LIGHT 0x040 + +/** \brief Flash Error */ +enum TFlashError { + FLASH_ERR_NONE , /* None */ + FLASH_ERR_OVER_CHARGE , /* Error happened during the charge */ + FLASH_ERR_OVER_HEAT , /* Over temperature */ + FLASH_ERR_SHORT_CIRCUIT , /* Short circuit */ + FLASH_ERR_TIMEOUT , /* Timeout */ + FLASH_ERR_OVER_VOLTAGE /* Over voltage */ +} ; + +#endif diff --git a/drivers/staging/camera_flash/flash_common.c b/drivers/staging/camera_flash/flash_common.c new file mode 100644 index 00000000000..fc59879a170 --- /dev/null +++ b/drivers/staging/camera_flash/flash_common.c @@ -0,0 +1,460 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * camera flash: Flash driver to export camera flash to user space application. + * It supports two flashes, one for primary and one for secondary camera + * Author: Pankaj Chauhan/pankaj.chauhan@stericsson.com for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/kthread.h> +#include <linux/jiffies.h> +#include <linux/miscdevice.h> +#include "camera_flash.h" +#include "flash_common.h" + +#define DEBUG_LOG(...) printk(KERN_DEBUG "Camera Flash driver: " __VA_ARGS__) + +#define PRIMARY_CAMERA (0) +#define SECONDARY_CAMERA (1) +static struct miscdevice misc_dev; +struct flash_chip *flash_chips[2]; +struct fasync_struct * async_queue; +struct task_struct* ptaskStruct; +wait_queue_head_t waitQueue; +int waitCondition = 0; +struct flash_ioctl_args_t flashArg; + +#define COPY_ARG_FROM_USER(_to,_from_usr) do{ \ + memset((_to),0,sizeof(struct flash_ioctl_args_t)); \ + if (copy_from_user((_to), (struct flash_ioctl_args_t*) (_from_usr), sizeof(struct flash_ioctl_args_t))) { \ + DEBUG_LOG("Could not copy data from userspace successfully\n"); \ + break; \ + } \ +}while(0) + +#define COPY_ARG_TO_USER(_to_usr,_from) do{ \ + if (copy_to_user((struct flash_ioctl_args_t *)(_to_usr), (_from), sizeof(struct flash_ioctl_args_t))) { \ + DEBUG_LOG("Could not copy data from userspace successfully\n"); \ + break; \ + } \ +}while(0) + + +static long flash_ioctl(struct file *file_p, unsigned int cmd, unsigned long arg) +{ + int err=0; + struct flash_chip *flash_p = NULL; + struct flash_chip_ops *ops = NULL; + char *my_name=NULL; + struct flash_ioctl_args_t flash_arg; + + if (_IOC_TYPE(cmd) != FLASH_MAGIC_NUMBER) { + printk(KERN_ALERT "Flash driver: Not an ioctl for this module\n"); + err = -EINVAL; + } + + COPY_ARG_FROM_USER(&flash_arg,arg); + + if(flash_arg.cam == SECONDARY_CAMERA || flash_arg.cam == PRIMARY_CAMERA) + flash_p = flash_chips[flash_arg.cam]; + else{ + DEBUG_LOG("unsupported cam %lu\n",flash_arg.cam); + err = -ENODEV; + goto out; + } + my_name = flash_arg.cam ?"Secondary":"Primary"; + + if (flash_arg.cam == PRIMARY_CAMERA) + { + ops = flash_p->ops; + } + + switch(cmd){ + case FLASH_GET_MODES: + { + if (flash_arg.cam == PRIMARY_CAMERA) + { + err = ops->get_modes(flash_p->priv_data,&flash_arg.flash_mode); + if(!err){ + DEBUG_LOG("Supported flash modes for %s camera: %lx\n", + my_name,flash_arg.flash_mode); + COPY_ARG_TO_USER(arg,&flash_arg); + }else{ + DEBUG_LOG("unable to get supported modes for %s camera\n",my_name); + } + } + else + { + flash_arg.flash_mode = FLASH_MODE_NONE; + COPY_ARG_TO_USER(arg,&flash_arg); + } + } + break; + case FLASH_GET_MODE_DETAILS: + { + err = ops->get_mode_details(flash_p->priv_data,flash_arg.flash_mode, + &flash_arg.mode_arg.details); + if(!err){ + COPY_ARG_TO_USER(arg,&flash_arg); + }else{ + DEBUG_LOG("Unable to get mode details for %s camera, flash mode %lx\n", + my_name,flash_arg.flash_mode); + } + } + break; + case FLASH_ENABLE_MODE: + case FLASH_DISABLE_MODE: + { + int enable=0; + if(cmd == FLASH_ENABLE_MODE){ + enable = 1; + } + err = ops->enable_flash_mode(flash_p->priv_data,flash_arg.flash_mode,enable); + if(err){ + DEBUG_LOG("Unable to %s: %s camera, flash mode %lx\n", + (enable ?"Enable":"Disable"), my_name,flash_arg.flash_mode); + } + } + break; + case FLASH_CONFIGURE_MODE: + err = ops->configure_flash_mode(flash_p->priv_data,flash_arg.flash_mode, + &flash_arg.mode_arg.params); + if(err){ + DEBUG_LOG("Unable to configure %s camera, flash mode %lx\n", + my_name,flash_arg.flash_mode); + } + break; + case FLASH_TRIGGER_STROBE: + err = ops->trigger_strobe(flash_p->priv_data,flash_arg.mode_arg.strobe_enable); + if(err){ + DEBUG_LOG("Unable to %s: %s camera strobe trigger, mode %lx\n", + (arg ?"Enable":"Disable"), my_name,flash_arg.flash_mode); + } + break; + case FLASH_GET_STATUS: + err = ops->get_status(flash_p->priv_data,&flash_arg.status); + if(!err){ + COPY_ARG_TO_USER(arg,&flash_arg); + }else{ + DEBUG_LOG("Unable to get status details for %s camera, flash mode %lx\n", + my_name,flash_arg.flash_mode); + } + break; + case FLASH_GET_LIFE_COUNTER: + DEBUG_LOG("Not Implemented\n"); + break; + case FLASH_SELF_TEST: + flashArg = flash_arg; + if (0 != (flashArg.cam & (FLASH_SELFTEST_FLASH | FLASH_SELFTEST_FLASH_WITH_STROBE))) + { + err = ENODEV; + } + else + { + /* wake up worker thread */ + waitCondition = 1; + wake_up_interruptible(&waitQueue); + } + break; + case FLASH_GET_SELF_TEST_MODES: + { + if (flash_arg.cam == PRIMARY_CAMERA) + { + err = ops->get_selftest_modes(flash_p->priv_data,&flash_arg.flash_mode); + if(!err){ + COPY_ARG_TO_USER(arg,&flash_arg); + }else{ + DEBUG_LOG("unable to get supported modes for %s camera\n",my_name); + } + } + else + { + flash_arg.flash_mode = FLASH_SELFTEST_NONE; + COPY_ARG_TO_USER(arg,&flash_arg); + } + break; + } + case FLASH_GET_FAULT_REGISTERS: + { + err = ops->get_fault_registers(flash_p->priv_data,flash_arg.flash_mode,&flash_arg.status); + if(!err){ + COPY_ARG_TO_USER(arg,&flash_arg); + }else{ + DEBUG_LOG("unable to get supported modes for %s camera\n",my_name); + } + + break; + } + case FLASH_GET_SELF_TEST_RESULT: + { + COPY_ARG_TO_USER(arg,&flashArg); + DEBUG_LOG("FLASH_GET_SELF_TEST_RESULT arg : 0x%lx\n", flashArg.status); + break; + } + default: + DEBUG_LOG("Unknown command %x\n",cmd); + + } +out: + return err; +} + +int worker_thread (void* data) +{ + int err = 0; + struct flash_chip *flash_p=NULL; + struct flash_chip_ops *ops=NULL; + struct flash_mode_params params; + struct flash_mode_details details; + + while (1) + { + /* waiting for some job to do */ + wait_event_interruptible(waitQueue, (waitCondition != 0)); + waitCondition = 0; + + DEBUG_LOG("worker_thread wakes up\n"); + /* do we need to stop ? */ + err = kthread_should_stop(); + if (0 != err) + { + DEBUG_LOG("worker_thread stops\n"); + break; + } + + /* do the job */ + flash_p = flash_chips[flashArg.cam]; + ops = flash_p->ops; + + /* clear fault registers */ + err = ops->get_fault_registers(flash_p->priv_data, FLASH_MODE_INDICATOR, &flashArg.status); + if (0 != err) + { + flashArg.status = flashArg.flash_mode; + flashArg.flash_mode = 0; + } + flashArg.status = 0; + + /* do all selftests */ + while (flashArg.flash_mode != FLASH_SELFTEST_NONE) + { + if (0 != (flashArg.flash_mode & FLASH_SELFTEST_CONNECTION)) + { + err = ops->get_mode_details(flash_p->priv_data, FLASH_MODE_INDICATOR, &details); + if (0 != err) + { + DEBUG_LOG("not able to get mode FLASH_MODE_INDICATOR details\n"); + flashArg.status |= FLASH_SELFTEST_CONNECTION; + } + flashArg.flash_mode &= ~FLASH_SELFTEST_CONNECTION; + } + else if (0 != (flashArg.flash_mode & (FLASH_SELFTEST_FLASH | FLASH_SELFTEST_FLASH_WITH_STROBE))) + { + if (0 != (flashArg.flash_mode & FLASH_SELFTEST_FLASH)) + { + flashArg.status |= FLASH_SELFTEST_FLASH; + flashArg.flash_mode &= ~FLASH_SELFTEST_FLASH; + } + else + { + flashArg.status |= FLASH_SELFTEST_FLASH_WITH_STROBE; + flashArg.flash_mode &= ~FLASH_SELFTEST_FLASH_WITH_STROBE; + } + } + /* FLASH_SELFTEST_VIDEO_LIGHT | FLASH_SELFTEST_AF_LIGHT | FLASH_SELFTEST_INDICATOR | FLASH_SELFTEST_TORCH_LIGHT */ + else + { + unsigned long currentSelftest = FLASH_SELFTEST_NONE; + unsigned long currentFlashMode = FLASH_MODE_NONE; + + if (0 != (flashArg.flash_mode & FLASH_SELFTEST_VIDEO_LIGHT)) + { + currentSelftest = FLASH_SELFTEST_VIDEO_LIGHT; + currentFlashMode = FLASH_MODE_VIDEO_LED; + } + else if (0 != (flashArg.flash_mode & FLASH_SELFTEST_AF_LIGHT)) + { + currentSelftest = FLASH_SELFTEST_AF_LIGHT; + currentFlashMode = FLASH_MODE_AF_ASSISTANT; + } + else if (0 != (flashArg.flash_mode & FLASH_SELFTEST_INDICATOR)) + { + currentSelftest = FLASH_SELFTEST_INDICATOR; + currentFlashMode = FLASH_MODE_INDICATOR; + } + else + { + currentSelftest = FLASH_SELFTEST_TORCH_LIGHT; + currentFlashMode = FLASH_MODE_VIDEO_LED; + } + + err = ops->get_mode_details(flash_p->priv_data, currentFlashMode, &details); + if (0 != err) + { + DEBUG_LOG("not able to get mode 0x%lx details\n",currentFlashMode); + flashArg.status |= currentSelftest; + flashArg.flash_mode &= ~currentSelftest; + continue; + } + + err = ops->enable_flash_mode(flash_p->priv_data, currentFlashMode, 1); + if (0 != err) + { + DEBUG_LOG("not able to enable flash mode 0x%lx\n",currentFlashMode); + flashArg.status |= currentSelftest; + flashArg.flash_mode &= ~currentSelftest; + continue; + } + + params.duration_uSecs = 0; + params.intensity_uAmp = details.max_intensity_uAmp; + params.timeout_uSecs = 0; + err = ops->configure_flash_mode(flash_p->priv_data, currentFlashMode, ¶ms); + if (0 != err) + { + DEBUG_LOG("not able to configure flash mode 0x%lx\n",currentFlashMode); + flashArg.status |= currentSelftest; + flashArg.flash_mode &= ~currentSelftest; + continue; + } + + err = ops->trigger_strobe(flash_p->priv_data,1); + if (0 != err) + { + DEBUG_LOG("not able to strobe, mode : 0x%lx\n",currentFlashMode); + flashArg.status |= currentSelftest; + flashArg.flash_mode &= ~currentSelftest; + continue; + } + + wait_event_timeout(waitQueue, 0, msecs_to_jiffies(1000)); + + err = ops->trigger_strobe(flash_p->priv_data,0); + if (0 != err) + { + DEBUG_LOG("not able to strobe, mode : 0x%lx\n",currentFlashMode); + flashArg.status |= currentSelftest; + flashArg.flash_mode &= ~currentSelftest; + continue; + } + flashArg.flash_mode &= ~currentSelftest; + } + } + + /* job's done ! */ + flash_async_notify(); + } + return 0; +} + +int flash_open(struct inode *node, struct file *file_p) +{ + // init sleep queue + init_waitqueue_head(&waitQueue); + + // start worker thread + ptaskStruct = kthread_run (&worker_thread, NULL, "flashDriverWorker"); + + return 0; +} + +int register_flash_chip(unsigned int cam, struct flash_chip *flash_p) +{ + int err =0; + DEBUG_LOG("Registering cam %d\n", cam); + DEBUG_LOG("flash_p: name=%s\n", flash_p->name); + if(cam > 1 || !flash_p){ + DEBUG_LOG("Registration: something is wrong! cam %d, flash_p %x \n",cam,(int)flash_p); + err = EINVAL; + goto out; + } + if(!flash_chips[cam]){ + flash_chips[cam] = flash_p; + DEBUG_LOG("Registered flash: id %lx, %s for camera %d\n", + flash_p->id,flash_p->name,cam); + }else{ + DEBUG_LOG("%s flash already registered for camera %d, ignore flash %s\n", + flash_chips[cam]->name,cam, flash_p->name); + } +out: + return err; +} + +int flash_async_notify () +{ + kill_fasync(&async_queue, SIGIO, POLL_IN); + return 0; +} + +static int flash_fasync(int fd, struct file *filp, int mode) +{ + DEBUG_LOG("registered async notification on %d fd\n",fd); + return fasync_helper(fd, filp, mode, &async_queue); +} + +static int flash_release(struct inode *node, struct file *file_p) +{ + int err = 0; + + fasync_helper(-1, file_p, 0, &async_queue); + + // stop worker thread + waitCondition = 1; + err = kthread_stop(ptaskStruct); + return err; +} + +static struct file_operations flash_fops = { + owner:THIS_MODULE, + unlocked_ioctl:flash_ioctl, + open:flash_open, + release:flash_release, + fasync:flash_fasync, +}; + +int major_device_number; + +/*Temporary here (adp_init)*/ +extern int adp1653_init(void); +static int __init flash_init(void) +{ + int err = 0; + err = adp1653_init(); + if(err){ + DEBUG_LOG("Unable to initialize adp1653, err %d\n",err); + goto out; + } + /* Register misc device */ + misc_dev.minor = MISC_DYNAMIC_MINOR; + misc_dev.name = "camera_flash"; + misc_dev.fops = &flash_fops; + err = misc_register(&misc_dev); + if (err < 0) { + printk(KERN_INFO "camera_flash driver misc_register failed (%d)\n", err); + return err; + } else { + major_device_number = err; + printk(KERN_INFO "camera_flash driver initialized with minor=%d\n", misc_dev.minor); + } +out: + return err; +} + +static void __exit flash_exit(void) +{ + misc_deregister(&misc_dev); + printk(KERN_INFO"Camera flash driver unregistered\n"); +} + +module_init(flash_init); +module_exit(flash_exit); +MODULE_LICENSE("GPL"); +EXPORT_SYMBOL(register_flash_chip); +EXPORT_SYMBOL(flash_async_notify); diff --git a/drivers/staging/camera_flash/flash_common.h b/drivers/staging/camera_flash/flash_common.h new file mode 100755 index 00000000000..d1f63631e82 --- /dev/null +++ b/drivers/staging/camera_flash/flash_common.h @@ -0,0 +1,57 @@ +#ifndef __FLASH_COMMON_H__ +#define __FLASH_COMMON_H__ + +#include "camera_flash_bitfields.h" +#include "camera_flash.h" + +struct flash_chip_ops{ + int (*get_modes)( void *priv_data, unsigned long *modes); + int (*get_mode_details)(void *priv_data,unsigned long mode, + struct flash_mode_details *details_p); + int (*enable_flash_mode) (void *priv_data,unsigned long mode, + int enable); + int (*configure_flash_mode) (void *priv_data, unsigned long mode, + struct flash_mode_params *params_p); + int (*trigger_strobe) (void *priv_data, int enable); + int (*get_life_counter) (void *priv_data); + int (*get_status) (void *priv_data, unsigned long *status); + int (*get_selftest_modes) (void *priv_data, + unsigned long *modes); + int (*get_fault_registers) (void *priv_data, unsigned long mode, + unsigned long *status); +}; + +#define FLASH_TYPE_XENON (0x1) +#define FLASH_TYPE_HPLED (0x2) + +#define SET_FLASHCHIP_TYPE(flash_chip_p,_TYPE) ((flash_chip_p)->id |= _TYPE) +#define GET_FLASHHIP_TYPE(flash_chip_p) ((flash_chip_p)->id & 0xffff) +#define GET_FLASHCHIP_ID(flash_chip_p) ((flash_chip_p)->id >> 16) +#define SET_FLASHCHIP_ID(flash_chip_p,_ID) ((flash_chip_p)->id |= (_ID << 16)) + +struct flash_chip { + unsigned long id; + struct flash_chip_ops *ops; + void *priv_data; + unsigned char name[FLASH_NAME_SIZE]; +}; + +/** + * struct flash_platform_data: + * platform specific data For flash chip driver + * @cam : 0 - primary, 1 - secondary + * @strobe_gpio: GPIO used as strobe + * @enable_gpio: GPIO used for enable/reset input + */ +struct flash_platform_data{ + unsigned long cam; + unsigned long strobe_gpio; + unsigned long strobe_gpio_alt_func; + unsigned long enable_gpio; + unsigned long enable_gpio_alt_func; +}; + +extern int register_flash_chip(unsigned int cam, struct flash_chip *flash_p); +extern int flash_async_notify (void ); + +#endif diff --git a/drivers/staging/cg2900/Kconfig b/drivers/staging/cg2900/Kconfig new file mode 100644 index 00000000000..92046b9bf76 --- /dev/null +++ b/drivers/staging/cg2900/Kconfig @@ -0,0 +1,73 @@ +# +# CG2900 +# + +config CG2900 + tristate "Support ST-Ericsson CG2900 main structure" + depends on NET && HAS_IOMEM + select MFD_CORE + help + ST-Ericsson CG2900 Connectivity Combo controller main + structure. + Supports multiple functionalities muxed over a Bluetooth HCI H:4 + interface. + CG2900 support Bluetooth, FM radio, and GPS. + +config CG2900_CHIP + tristate "Support CG2900 Connectivity controller" + depends on CG2900 + help + ST-Ericsson CG2900 Connectivity Controller chip handler. + Contains chip handler performing driver initialization + such as patchdownload and also instantiates the supported + MFD devices. + +config STLC2690_CHIP + tristate "Support STLC2690 Connectivity controller" + depends on CG2900 + help + ST-Ericsson STLC2690 Connectivity Controller chip handler. + Contains chip handler performing driver initialization + such as patchdownload and also instantiates the supported + MFD devices. + +config CG2900_UART + tristate "Support CG2900 UART transport" + depends on CG2900 + select BT + select BT_HCIUART + help + UART driver for ST-Ericsson CG2900 Connectivity Controller. + Contains functions for setting baud rate and to transport + data to and from the CG2900 controller over UART. + Also handles low power handling for the CG2900 when using UART as + transport. + +config CG2900_AUDIO + tristate "Support CG2900 audio interface" + depends on CG2900 + help + ST-Ericsson CG2900 Connectivity audio interface driver. + Gives a module the ability to setup audio paths + within the CG2900 controller. + Supports both a normal function API and using character device + from user space. + +config CG2900_TEST + tristate "Support CG2900 Test Char Device" + depends on CG2900 + help + ST-Ericsson CG2900 Test Character Device driver. + Creates a character device which can be used by + a test framework in user space to emulate a connected chip. + Note that this is used to test the chip handler driver, + not to test the connected chip. + +config BT_CG2900 + tristate "ST-Ericsson CG2900 Bluetooth driver" + depends on CG2900 && BT + help + Select if ST-Ericsson CG2900 Connectivity controller shall be used as + Bluetooth controller for BlueZ. + This driver registers to the Bluetooth stack and when opened, + enables the CG2900 controller in a proper way. diff --git a/drivers/staging/cg2900/Makefile b/drivers/staging/cg2900/Makefile new file mode 100644 index 00000000000..14e2847bacf --- /dev/null +++ b/drivers/staging/cg2900/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for ST-Ericsson CG2900 connectivity combo controller +# + +ccflags-y := \ + -Idrivers/staging/cg2900/include \ + -Iarch/arm/mach-ux500 + +obj-$(CONFIG_CG2900) += devices-cg2900.o \ + devices-cg2900-ux500.o \ + board-ux500-cg2900.o \ + clock-cg2900.o + +obj-y += mfd/ +obj-y += bluetooth/ diff --git a/drivers/staging/cg2900/TODO b/drivers/staging/cg2900/TODO new file mode 100644 index 00000000000..e122eba6d53 --- /dev/null +++ b/drivers/staging/cg2900/TODO @@ -0,0 +1,23 @@ +TODO +---- + + - Decide upon main driver architecture. + + - Decide if the CG2900 driver should be a separate driver as today or if it + should be a sub-driver using the TI-ST (Shared Transport) driver that is also + written for a combo connectivity controller. + + - Decide if cg2900_uart should register on top of hci_ldisc.c (as now) or if it + should instead register on top of hci_h4.c thereby reusing hci_h4 + implementation. + + - Update the hci_ldisc.c so that it will allow drivers to be registered without + registering them directly to the Bluetooth stack. Also extend the hci_ldisc.c + with more functions to abstract the tty API in a conformative way (currently + sometimes the tty API used, sometimes the hci_ldisc interface). + + - Decide if the CG2900 driver should use imported structs and defines to create + Bluetooth packets as today or if the Bluetooth stack in the Kernel should be + extended so it is possible to use generic functions to send and receive + commands and events both from the Bluetooth stack itself and from external + drivers such as the CG2900 driver. diff --git a/drivers/staging/cg2900/bluetooth/Makefile b/drivers/staging/cg2900/bluetooth/Makefile new file mode 100644 index 00000000000..936a4a257da --- /dev/null +++ b/drivers/staging/cg2900/bluetooth/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ST-Ericsson CG2900 connectivity combo controller +# + +ccflags-y := \ + -Idrivers/staging/cg2900/include + +obj-$(CONFIG_BT_CG2900) += btcg2900.o +obj-$(CONFIG_CG2900_UART) += cg2900_uart.o hci_ldisc.o diff --git a/drivers/staging/cg2900/bluetooth/btcg2900.c b/drivers/staging/cg2900/bluetooth/btcg2900.c new file mode 100644 index 00000000000..07aae9a32ca --- /dev/null +++ b/drivers/staging/cg2900/bluetooth/btcg2900.c @@ -0,0 +1,1198 @@ +/* + * Bluetooth driver for ST-Ericsson CG2900 connectivity controller. + * + * Copyright (C) ST-Ericsson SA 2010 + * + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) + * Henrik Possung (henrik.possung@stericsson.com) + * Josef Kindberg (josef.kindberg@stericsson.com) + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) + * Kjell Andersson (kjell.k.andersson@stericsson.com) + * + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <asm/byteorder.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/platform_device.h> +#include <linux/skbuff.h> +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/mfd/core.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> +#include <net/bluetooth/hci_core.h> + +#include "cg2900.h" + +#define BT_HEADER_LENGTH 0x03 + +#define STLC2690_HCI_REV 0x0600 +#define CG2900_PG1_HCI_REV 0x0101 +#define CG2900_PG2_HCI_REV 0x0200 +#define CG2900_PG1_SPECIAL_HCI_REV 0x0700 + +#define NAME "BTCG2900 " + +/* Wait for 5 seconds for a response to our requests */ +#define RESP_TIMEOUT 5000 + +/* Bluetooth error codes */ +#define HCI_ERR_NO_ERROR 0x00 +#define HCI_ERR_CMD_DISALLOWED 0x0C + +/** + * enum reset_state - RESET-states of the HCI driver. + * + * @RESET_IDLE: No reset in progress. + * @RESET_ACTIVATED: Reset in progress. + * @RESET_UNREGISTERED: hdev is unregistered. + */ + +enum reset_state { + RESET_IDLE, + RESET_ACTIVATED, + RESET_UNREGISTERED +}; + +/** + * enum enable_state - ENABLE-states of the HCI driver. + * + * @ENABLE_IDLE: The HCI driver is loaded but not opened. + * @ENABLE_WAITING_BT_ENABLED_CC: The HCI driver is waiting for a command + * complete event from the BT chip as a + * response to a BT Enable (true) command. + * @ENABLE_BT_ENABLED: The BT chip is enabled. + * @ENABLE_WAITING_BT_DISABLED_CC: The HCI driver is waiting for a command + * complete event from the BT chip as a + * response to a BT Enable (false) command. + * @ENABLE_BT_DISABLED: The BT chip is disabled. + * @ENABLE_BT_ERROR: The HCI driver is in a bad state, some + * thing has failed and is not expected to + * work properly. + */ +enum enable_state { + ENABLE_IDLE, + ENABLE_WAITING_BT_ENABLED_CC, + ENABLE_BT_ENABLED, + ENABLE_WAITING_BT_DISABLED_CC, + ENABLE_BT_DISABLED, + ENABLE_BT_ERROR +}; + +/* Defines which state the driver has when BT is active */ +#define BTCG2900_ACTIVE_STATE ENABLE_BT_ENABLED + +/** + * struct btcg2900_info - Specifies HCI driver private data. + * + * This type specifies CG2900 HCI driver private data. + * + * @list: list_head struct. + * @parent: Parent to this BT device. All BT channels will have + * common parent. + * @cmd: Device structure for BT command channel. + * @evt: Device structure for BT event channel. + * @acl: Device structure for BT ACL channel. + * @pdev: Device structure for platform device. + * @hdev: Device structure for HCI device. + * @reset_state: Device enum for HCI driver reset state. + * @enable_state: Device enum for HCI driver BT enable state. + */ +struct btcg2900_info { + struct list_head list; + struct device *parent; + struct device *cmd; + struct device *evt; + struct device *acl; + struct hci_dev *hdev; + enum reset_state reset_state; + enum enable_state enable_state; +}; + +/** + * struct enable_info - Specifies data for sending enable commands. + * + * @enable: True if command should enable the functionality. + * @name: Name of the command, only informative. + * @get_cmd: Function for retrieving command. + * @success: State to set upon success. + * @awaiting_cc: State to set while waiting for response. + * @failed: State to set upon failure. + */ +struct enable_info { + bool enable; + char *name; + struct sk_buff* (*get_cmd)(struct btcg2900_info *info, bool enable); + enum enable_state success; + enum enable_state awaiting_cc; + enum enable_state failed; +}; + +/** + * struct dev_info - Specifies private data used when receiving callbacks from CG2900 driver. + * + * @hci_data_type: Type of data according to BlueZ. + */ +struct dev_info { + u8 hci_data_type; +}; + +/* Defines for vs_bt_enable_cmd */ +#define BT_VS_BT_ENABLE 0xFF10 +#define VS_BT_DISABLE 0x00 +#define VS_BT_ENABLE 0x01 + +/** + * struct vs_bt_enable_cmd - Specifies HCI VS Bluetooth_Enable command. + * + * @op_code: HCI command op code. + * @len: Parameter length of command. + * @enable: 0 for disable BT, 1 for enable BT. + */ +struct vs_bt_enable_cmd { + __le16 op_code; + u8 len; + u8 enable; +} __packed; + +/* + * hci_wait_queue - Main Wait Queue in HCI driver. + */ +static DECLARE_WAIT_QUEUE_HEAD(hci_wait_queue); + +/* + * btcg2900_devices - List of active CG2900 BT devices. + */ +static LIST_HEAD(btcg2900_devices); + +/* Internal function declarations */ +static int register_bluetooth(struct btcg2900_info *info); + +/* Internal functions */ + +/** + * get_bt_enable_cmd() - Get HCI BT enable command. + * @info: Device info structure. + * @bt_enable: true if Bluetooth IP shall be enabled, false otherwise. + * + * Returns: + * NULL if no command shall be sent, + * sk_buffer with command otherwise. + */ +static struct sk_buff *get_bt_enable_cmd(struct btcg2900_info *info, + bool bt_enable) +{ + struct sk_buff *skb; + struct vs_bt_enable_cmd *cmd; + struct cg2900_rev_data rev_data; + struct cg2900_user_data *pf_data; + + pf_data = dev_get_platdata(info->cmd); + + if (!pf_data->get_local_revision(pf_data, &rev_data)) { + BT_ERR(NAME "Couldn't get revision"); + return NULL; + } + + /* If connected chip does not support the command return NULL */ + if (CG2900_PG1_SPECIAL_HCI_REV != rev_data.revision && + CG2900_PG1_HCI_REV != rev_data.revision && + CG2900_PG2_HCI_REV != rev_data.revision) + return NULL; + + /* CG2900 used */ + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + BT_ERR(NAME "Could not allocate skb"); + return NULL; + } + + cmd = (struct vs_bt_enable_cmd *)skb_put(skb, sizeof(*cmd)); + cmd->op_code = cpu_to_le16(BT_VS_BT_ENABLE); + cmd->len = sizeof(*cmd) - BT_HEADER_LENGTH; + if (bt_enable) + cmd->enable = VS_BT_ENABLE; + else + cmd->enable = VS_BT_DISABLE; + + return skb; +} + +/** + * close_bt_users() - Close all BT channels. + * @info: HCI driver info structure. + */ +static void close_bt_users(struct btcg2900_info *info) +{ + struct cg2900_user_data *pf_data; + + pf_data = dev_get_platdata(info->cmd); + if (pf_data->opened) + pf_data->close(pf_data); + + pf_data = dev_get_platdata(info->acl); + if (pf_data->opened) + pf_data->close(pf_data); + + pf_data = dev_get_platdata(info->evt); + if (pf_data->opened) + pf_data->close(pf_data); +} + +/** + * handle_bt_enable_comp() - Handle received BtEnable Complete event. + * @info: Info structure. + * @skb: Buffer with data coming from device. + * + * Returns: + * true if data has been handled internally, + * false otherwise. + */ +static bool handle_bt_enable_comp(struct btcg2900_info *info, u8 status) +{ + if (info->enable_state != ENABLE_WAITING_BT_ENABLED_CC && + info->enable_state != ENABLE_WAITING_BT_DISABLED_CC) + return false; + /* + * This is the command complete event for + * the HCI_Cmd_VS_Bluetooth_Enable. + * Check result and update state. + * + * The BT chip is enabled/disabled. Either it was enabled/ + * disabled now (status NO_ERROR) or it was already enabled/ + * disabled (assuming status CMD_DISALLOWED is already enabled/ + * disabled). + */ + if (status != HCI_ERR_NO_ERROR && status != HCI_ERR_CMD_DISALLOWED) { + BT_ERR(NAME "Could not enable/disable BT core (0x%X)", + status); + BT_DBG("New enable_state: ENABLE_BT_ERROR"); + info->enable_state = ENABLE_BT_ERROR; + goto finished; + } + + if (info->enable_state == ENABLE_WAITING_BT_ENABLED_CC) { + BT_DBG("New enable_state: ENABLE_BT_ENABLED"); + info->enable_state = ENABLE_BT_ENABLED; + BT_INFO("CG2900 BT core is enabled"); + } else { + BT_DBG("New enable_state: ENABLE_BT_DISABLED"); + info->enable_state = ENABLE_BT_DISABLED; + BT_INFO("CG2900 BT core is disabled"); + } + +finished: + /* Wake up whoever is waiting for this result. */ + wake_up_all(&hci_wait_queue); + return true; +} + +/** + * handle_bt_enable_stat() - Handle received BtEnable Status event. + * @info: Info structure. + * @skb: Buffer with data coming from device. + * + * Returns: + * true if data has been handled internally, + * false otherwise. + */ +static bool handle_bt_enable_stat(struct btcg2900_info *info, u8 status) +{ + if (info->enable_state != ENABLE_WAITING_BT_DISABLED_CC && + info->enable_state != ENABLE_WAITING_BT_ENABLED_CC) + return false; + + BT_DBG("HCI Driver received Command Status (BT enable): 0x%X", status); + /* + * This is the command status event for the HCI_Cmd_VS_Bluetooth_Enable. + * Just free the packet. + */ + return true; +} + +/** + * handle_rx_evt() - Check if received data is response to internal command. + * @info: Info structure. + * @skb: Buffer with data coming from device. + * + * Returns: + * true if data has been handled internally, + * false otherwise. + */ +static bool handle_rx_evt(struct btcg2900_info *info, struct sk_buff *skb) +{ + struct hci_event_hdr *evt = (struct hci_event_hdr *)skb->data; + struct hci_ev_cmd_complete *cmd_complete; + struct hci_ev_cmd_status *cmd_status; + u16 op_code; + u8 status; + bool pkt_handled = false; + + /* If BT is active no internal packets shall be generated */ + if (info->enable_state == BTCG2900_ACTIVE_STATE) + return false; + + if (evt->evt == HCI_EV_CMD_COMPLETE) { + cmd_complete = (struct hci_ev_cmd_complete *)(evt + 1); + status = *((u8 *)(cmd_complete + 1)); + op_code = le16_to_cpu(cmd_complete->opcode); + + if (op_code == BT_VS_BT_ENABLE) + pkt_handled = handle_bt_enable_comp(info, status); + } else if (evt->evt == HCI_EV_CMD_STATUS) { + cmd_status = (struct hci_ev_cmd_status *)(evt + 1); + op_code = le16_to_cpu(cmd_status->opcode); + status = cmd_status->status; + + if (op_code == BT_VS_BT_ENABLE) + pkt_handled = handle_bt_enable_stat(info, status); + } + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +/** + * hci_read_cb() - Callback for handling data received from CG2900 driver. + * @dev: Device receiving data. + * @skb: Buffer with data coming from device. + */ +static void hci_read_cb(struct cg2900_user_data *user, struct sk_buff *skb) +{ + int err = 0; + struct dev_info *dev_info; + struct btcg2900_info *info; + + dev_info = cg2900_get_usr(user); + info = dev_get_drvdata(user->dev); + + if (user->dev != info->evt || !handle_rx_evt(info, skb)) { + bt_cb(skb)->pkt_type = dev_info->hci_data_type; + skb->dev = (struct net_device *)info->hdev; + /* Update BlueZ stats */ + info->hdev->stat.byte_rx += skb->len; + if (bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) + info->hdev->stat.acl_rx++; + else + info->hdev->stat.evt_rx++; + + BT_DBG("Data receive %d bytes", skb->len); + + /* Provide BlueZ with received frame*/ + err = hci_recv_frame(skb); + /* If err, skb have been freed in hci_recv_frame() */ + if (err) + BT_ERR(NAME "Failed in supplying packet to Bluetooth" + " stack (%d)", err); + } +} + +/** + * hci_reset_cb() - Callback for handling reset from CG2900 driver. + * @dev: CPD device resetting. + */ +static void hci_reset_cb(struct cg2900_user_data *dev) +{ + int err; + struct btcg2900_info *info; + struct cg2900_user_data *pf_data; + + BT_INFO(NAME "hci_reset_cb"); + + info = dev_get_drvdata(dev->dev); + + BT_DBG("New reset_state: RESET_ACTIVATED"); + info->reset_state = RESET_ACTIVATED; + + /* + * Continue to deregister hdev if all channels has been reset else + * return. + */ + pf_data = dev_get_platdata(info->acl); + if (pf_data->opened) + return; + pf_data = dev_get_platdata(info->cmd); + if (pf_data->opened) + return; + pf_data = dev_get_platdata(info->evt); + if (pf_data->opened) + return; + + /* + * Deregister HCI device. Close and Destruct functions should + * in turn be called by BlueZ. + */ + BT_DBG("Deregister HCI device"); + hci_unregister_dev(info->hdev); + + wait_event_timeout(hci_wait_queue, + (RESET_UNREGISTERED == info->reset_state), + msecs_to_jiffies(RESP_TIMEOUT)); + if (RESET_UNREGISTERED != info->reset_state) + /* + * Now we are in trouble. Try to register a new hdev + * anyway even though this will cost some memory. + */ + BT_ERR(NAME "Timeout expired. Could not deregister HCI device"); + + /* Init and register hdev */ + BT_DBG("Register HCI device"); + err = register_bluetooth(info); + if (err) + BT_ERR(NAME "HCI Device registration error (%d)", err); +} + +/** + * send_enable_cmd() - Send a command with only enable/disable functionality. + * @info: Info structure. + * @en_info: Enable info structure. + * + * Returns: + * 0 if successful, + * -EACCES if correct response to command is not received, + * Error codes from CG2900 write. + */ +static int send_enable_cmd(struct btcg2900_info *info, + struct enable_info *en_info) +{ + struct sk_buff *enable_cmd; + int err; + struct cg2900_user_data *pf_data; + + /* + * Call function that returns the chip dependent enable HCI command. + * If NULL is returned, then no bt_enable command should be sent to the + * chip. + */ + enable_cmd = en_info->get_cmd(info, en_info->enable); + if (!enable_cmd) { + BT_DBG("%s New enable_state: %d", en_info->name, + en_info->success); + info->enable_state = en_info->success; + return 0; + } + + /* Set the HCI state before sending command to chip. */ + BT_DBG("%s New enable_state: %d", en_info->name, en_info->awaiting_cc); + info->enable_state = en_info->awaiting_cc; + + /* Send command to chip */ + pf_data = dev_get_platdata(info->cmd); + err = pf_data->write(pf_data, enable_cmd); + if (err) { + BT_ERR("Couldn't send %s command (%d)", en_info->name, err); + kfree_skb(enable_cmd); + info->enable_state = en_info->failed; + return err; + } + + /* + * Wait for callback to receive command complete and then wake us up + * again. + */ + wait_event_timeout(hci_wait_queue, + info->enable_state == en_info->success, + msecs_to_jiffies(RESP_TIMEOUT)); + /* Check the current state to see if it worked */ + if (info->enable_state != en_info->success) { + BT_ERR("Could not change %s state (%d)", + en_info->name, info->enable_state); + BT_DBG("%s New enable_state: %d", en_info->name, + en_info->failed); + info->enable_state = en_info->failed; + return -EACCES; + } + + return 0; +} + +/** + * btcg2900_open() - Open HCI interface. + * @hdev: HCI device being opened. + * + * BlueZ callback function for opening HCI interface to device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied. + * -EOPNOTSUPP if supplied packet type is not supported. + * -EBUSY if device is already opened. + * -EACCES if opening of channels failed. + */ +static int btcg2900_open(struct hci_dev *hdev) +{ + struct btcg2900_info *info; + struct cg2900_user_data *pf_data; + int err; + struct enable_info en_info; + + BT_INFO("Open ST-Ericsson CG2900 driver"); + + if (!hdev) { + BT_ERR(NAME "NULL supplied for hdev"); + return -EINVAL; + } + + info = (struct btcg2900_info *)hdev->driver_data; + if (!info) { + BT_ERR(NAME "NULL supplied for driver_data"); + return -EINVAL; + } + + if (test_and_set_bit(HCI_RUNNING, &(hdev->flags))) { + BT_ERR(NAME "Device already opened!"); + return -EBUSY; + } + + pf_data = dev_get_platdata(info->acl); + err = pf_data->open(pf_data); + if (err) { + BT_ERR("Couldn't open BT ACL channel (%d)", err); + goto handle_error; + } + + pf_data = dev_get_platdata(info->cmd); + err = pf_data->open(pf_data); + if (err) { + BT_ERR("Couldn't open BT CMD channel (%d)", err); + goto handle_error; + } + + pf_data = dev_get_platdata(info->evt); + err = pf_data->open(pf_data); + if (err) { + BT_ERR("Couldn't open BT EVT channel (%d)", err); + goto handle_error; + } + + if (info->reset_state == RESET_ACTIVATED) { + BT_DBG("New reset_state: RESET_IDLE"); + info->reset_state = RESET_IDLE; + } + + /* First enable the BT core */ + en_info.enable = true; + en_info.get_cmd = get_bt_enable_cmd; + en_info.name = "VS BT Enable (true)"; + en_info.success = ENABLE_BT_ENABLED; + en_info.awaiting_cc = ENABLE_WAITING_BT_ENABLED_CC; + en_info.failed = ENABLE_BT_DISABLED; + + err = send_enable_cmd(info, &en_info); + if (err) { + BT_ERR("Couldn't enable BT core (%d)", err); + goto handle_error; + } + + return 0; + +handle_error: + close_bt_users(info); + clear_bit(HCI_RUNNING, &(hdev->flags)); + return err; + +} + +/** + * btcg2900_close() - Close HCI interface. + * @hdev: HCI device being closed. + * + * BlueZ callback function for closing HCI interface. + * It flushes the interface first. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied. + * -EOPNOTSUPP if supplied packet type is not supported. + * -EBUSY if device is not opened. + */ +static int btcg2900_close(struct hci_dev *hdev) +{ + struct btcg2900_info *info = NULL; + int err; + struct enable_info en_info; + + BT_DBG("btcg2900_close"); + + if (!hdev) { + BT_ERR(NAME "NULL supplied for hdev"); + return -EINVAL; + } + + info = (struct btcg2900_info *)hdev->driver_data; + if (!info) { + BT_ERR(NAME "NULL supplied for driver_data"); + return -EINVAL; + } + + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) { + BT_ERR(NAME "Device already closed!"); + return -EBUSY; + } + + /* Do not do this if there is an reset ongoing */ + if (info->reset_state == RESET_ACTIVATED) + goto remove_users; + + /* Now disable the BT core */ + en_info.enable = false; + en_info.get_cmd = get_bt_enable_cmd; + en_info.name = "VS BT Enable (false)"; + en_info.success = ENABLE_BT_DISABLED; + en_info.awaiting_cc = ENABLE_WAITING_BT_DISABLED_CC; + en_info.failed = ENABLE_BT_ENABLED; + + err = send_enable_cmd(info, &en_info); + if (err) + BT_ERR("Couldn't disable BT core (%d)", err); + +remove_users: + /* Finally deregister all users and free allocated data */ + close_bt_users(info); + return 0; +} + +/** + * btcg2900_send() - Send packet to device. + * @skb: sk buffer to be sent. + * + * BlueZ callback function for sending sk buffer. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied. + * -EOPNOTSUPP if supplied packet type is not supported. + * Error codes from cg2900_write. + */ +static int btcg2900_send(struct sk_buff *skb) +{ + struct hci_dev *hdev; + struct btcg2900_info *info; + struct cg2900_user_data *pf_data; + int err = 0; + + if (!skb) { + BT_ERR(NAME "NULL supplied for skb"); + return -EINVAL; + } + + hdev = (struct hci_dev *)(skb->dev); + if (!hdev) { + BT_ERR(NAME "NULL supplied for hdev"); + return -EINVAL; + } + + info = (struct btcg2900_info *)hdev->driver_data; + if (!info) { + BT_ERR(NAME "NULL supplied for info"); + return -EINVAL; + } + + /* Update BlueZ stats */ + hdev->stat.byte_tx += skb->len; + + BT_DBG("Data transmit %d bytes", skb->len); + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + BT_DBG("Sending HCI_COMMAND_PKT"); + pf_data = dev_get_platdata(info->cmd); + err = pf_data->write(pf_data, skb); + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + BT_DBG("Sending HCI_ACLDATA_PKT"); + pf_data = dev_get_platdata(info->acl); + err = pf_data->write(pf_data, skb); + hdev->stat.acl_tx++; + break; + default: + BT_ERR(NAME "Trying to transmit unsupported packet type" + " (0x%.2X)", bt_cb(skb)->pkt_type); + err = -EOPNOTSUPP; + break; + }; + + return err; +} + +/** + * btcg2900_destruct() - Destruct HCI interface. + * @hdev: HCI device being destructed. + */ +static void btcg2900_destruct(struct hci_dev *hdev) +{ + struct btcg2900_info *info; + + BT_DBG("btcg2900_destruct"); + + info = hdev->driver_data; + if (!info) { + BT_ERR(NAME "NULL supplied for info"); + return; + } + + /* + * When destruct is called it means that the Bluetooth stack is done + * with the HCI device and we can now free it. + * Normally we do this only when removing the whole module through + * btcg2900_remove(), but when being reset we free the device here and + * we then set the reset state so that the reset handler can allocate a + * new HCI device and then register it to the Bluetooth stack. + */ + if (info->reset_state == RESET_ACTIVATED) { + if (info->hdev) { + hci_free_dev(info->hdev); + info->hdev = NULL; + } + BT_DBG("New reset_state: RESET_UNREGISTERED"); + info->reset_state = RESET_UNREGISTERED; + wake_up_all(&hci_wait_queue); + } +} + +/** + * get_info() - Return info structure for this device. + * @dev: Current device. + * + * Returns: + * Pointer to info struct if there is no error. + * ERR_PTR(-ENOMEM) if allocation fails. + */ +static struct btcg2900_info *get_info(struct device *dev) +{ + struct list_head *cursor; + struct btcg2900_info *tmp; + struct btcg2900_info *info = NULL; + + /* Find the info structure */ + list_for_each(cursor, &btcg2900_devices) { + tmp = list_entry(cursor, struct btcg2900_info, list); + if (tmp->parent == dev->parent) { + info = tmp; + break; + } + } + + if (info) + return info; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + BT_ERR("Could not allocate info struct"); + return ERR_PTR(-ENOMEM); + } + info->parent = dev->parent; + list_add_tail(&info->list, &btcg2900_devices); + BT_DBG("CG2900 device added"); + return info; +} + +/** + * device_removed() - Remove device from list if there are no channels left. + * @info: BTCG2900 info structure. + */ +static void device_removed(struct btcg2900_info *info) +{ + struct list_head *cursor; + struct btcg2900_info *tmp; + + if (info->acl || info->cmd || info->evt) + /* There are still devices active */ + return; + + /* Find the info structure and delete it */ + list_for_each(cursor, &btcg2900_devices) { + tmp = list_entry(cursor, struct btcg2900_info, list); + if (tmp == info) { + list_del(cursor); + break; + } + } + kfree(info); + BT_DBG("CG2900 device removed"); +} + +/** + * register_bluetooth() - Initialize module. + * + * Alloc, init, and register HCI device to BlueZ. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * Error codes from hci_register_dev. + */ +static int register_bluetooth(struct btcg2900_info *info) +{ + int err; + struct cg2900_user_data *pf_data; + + /* Check if all channels have been probed */ + if (!info->acl || !info->cmd || !info->evt) + return 0; + + pf_data = dev_get_platdata(info->cmd); + + info->hdev = hci_alloc_dev(); + if (!info->hdev) { + BT_ERR("Could not allocate mem for CG2900 BT driver"); + return -ENOMEM; + } + + SET_HCIDEV_DEV(info->hdev, info->parent); + info->hdev->bus = pf_data->channel_data.bt_bus; + info->hdev->driver_data = info; + info->hdev->owner = THIS_MODULE; + info->hdev->open = btcg2900_open; + info->hdev->close = btcg2900_close; + info->hdev->send = btcg2900_send; + info->hdev->destruct = btcg2900_destruct; + + err = hci_register_dev(info->hdev); + if (err) { + BT_ERR("Can not register BTCG2900 HCI device (%d)", err); + hci_free_dev(info->hdev); + info->hdev = NULL; + } + + BT_INFO("CG2900 registered"); + + BT_DBG("New enable_state: ENABLE_IDLE"); + info->enable_state = ENABLE_IDLE; + BT_DBG("New reset_state: RESET_IDLE"); + info->reset_state = RESET_IDLE; + + return err; +} + +/** + * probe_common() - Initialize channel and register to BT stack. + * @dev: Current device. + * @info: BTCG2900 info structure. + * @hci_data_type: Data type of this channel, e.g. ACL. + * + * Allocate and initialize private data. Register to Bluetooth stack. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * Error codes from register_bluetooth. + */ +static int probe_common(struct platform_device *pdev, + struct btcg2900_info *info, + u8 hci_data_type) +{ + int err; + struct cg2900_user_data *pf_data; + struct dev_info *dev_info; + struct device *dev = &pdev->dev; + + dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL); + if (!dev_info) { + BT_ERR("Could not allocate dev_info"); + return -ENOMEM; + } + + dev_set_drvdata(dev, info); + + pf_data = dev_get_platdata(dev); + pf_data->dev = dev; + pf_data->read_cb = hci_read_cb; + pf_data->reset_cb = hci_reset_cb; + + /* Init and register hdev */ + err = register_bluetooth(info); + if (err) { + BT_ERR("HCI Device registration error (%d)", err); + kfree(dev_info); + return err; + } + dev_info->hci_data_type = hci_data_type; + cg2900_set_usr(pf_data, dev_info); + + return 0; +} + +/** + * btcg2900_cmd_probe() - Initialize command channel. + * @pdev: Platform device. + * + * Allocate and initialize private data. + * + * Returns: + * 0 if there is no error. + * Error codes from get_info and probe_common. + */ +static int __devinit btcg2900_cmd_probe(struct platform_device *pdev) +{ + int err; + struct btcg2900_info *info; + + BT_DBG("Starting CG2900 Command channel"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->cmd = &pdev->dev; + + err = probe_common(pdev, info, HCI_COMMAND_PKT); + if (err) { + BT_ERR("Failed to initialize channel"); + info->cmd = NULL; + device_removed(info); + return err; + } + + return 0; +} + +/** + * btcg2900_acl_probe() - Initialize command channel. + * @pdev: Platform device. + * + * Allocate and initialize private data. + * + * Returns: + * 0 if there is no error. + * Error codes from get_info and probe_common. + */ +static int __devinit btcg2900_acl_probe(struct platform_device *pdev) +{ + int err; + struct btcg2900_info *info; + + BT_DBG("Starting CG2900 ACL channel"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->acl = &pdev->dev; + + err = probe_common(pdev, info, HCI_ACLDATA_PKT); + if (err) { + BT_ERR("Failed to initialize channel"); + info->acl = NULL; + device_removed(info); + return err; + } + + return 0; +} + +/** + * btcg2900_evt_probe() - Initialize event channel. + * @pdev: Platform device. + * + * Allocate and initialize private data. + * + * Returns: + * 0 if there is no error. + * Error codes from get_info and probe_common. + */ +static int __devinit btcg2900_evt_probe(struct platform_device *pdev) +{ + int err; + struct btcg2900_info *info; + + BT_DBG("Starting CG2900 Event channel"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->evt = &pdev->dev; + + err = probe_common(pdev, info, HCI_EVENT_PKT); + if (err) { + BT_ERR("Failed to initialize channel"); + info->evt = NULL; + device_removed(info); + return err; + } + + return 0; +} + +/** + * remove_common() - Remove channel. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * Error codes from hci_unregister_dev. + */ +static int remove_common(struct platform_device *pdev, + struct btcg2900_info *info) +{ + int err = 0; + struct cg2900_user_data *pf_data; + struct dev_info *dev_info; + + pf_data = dev_get_platdata(&pdev->dev); + dev_info = cg2900_get_usr(pf_data); + + kfree(dev_info); + cg2900_set_usr(pf_data, NULL); + + if (!info->hdev) + goto finished; + + BT_INFO("Unregistering CG2900"); + info->hdev->driver_data = NULL; + hci_unregister_dev(info->hdev); + hci_free_dev(info->hdev); + info->hdev = NULL; + +finished: + device_removed(info); + return err; +} + +/** + * btcg2900_cmd_remove() - Remove command channel. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * Error codes from remove_common. + */ +static int __devexit btcg2900_cmd_remove(struct platform_device *pdev) +{ + struct btcg2900_info *info; + + BT_DBG("Removing CG2900 Command channel"); + + info = dev_get_drvdata(&pdev->dev); + info->cmd = NULL; + return remove_common(pdev, info); +} + +/** + * btcg2900_acl_remove() - Remove ACL channel. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * Error codes from remove_common. + */ +static int __devexit btcg2900_acl_remove(struct platform_device *pdev) +{ + struct btcg2900_info *info; + + BT_DBG("Removing CG2900 ACL channel"); + + info = dev_get_drvdata(&pdev->dev); + info->acl = NULL; + return remove_common(pdev, info); +} + +/** + * btcg2900_evt_remove() - Remove event channel. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * Error codes from remove_common. + */ +static int __devexit btcg2900_evt_remove(struct platform_device *pdev) +{ + struct btcg2900_info *info; + + BT_DBG("Removing CG2900 Event channel"); + + info = dev_get_drvdata(&pdev->dev); + info->evt = NULL; + return remove_common(pdev, info); +} + +static struct platform_driver btcg2900_cmd_driver = { + .driver = { + .name = "cg2900-btcmd", + .owner = THIS_MODULE, + }, + .probe = btcg2900_cmd_probe, + .remove = __devexit_p(btcg2900_cmd_remove), +}; + +static struct platform_driver btcg2900_acl_driver = { + .driver = { + .name = "cg2900-btacl", + .owner = THIS_MODULE, + }, + .probe = btcg2900_acl_probe, + .remove = __devexit_p(btcg2900_acl_remove), +}; + +static struct platform_driver btcg2900_evt_driver = { + .driver = { + .name = "cg2900-btevt", + .owner = THIS_MODULE, + }, + .probe = btcg2900_evt_probe, + .remove = __devexit_p(btcg2900_evt_remove), +}; + +/** + * btcg2900_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init btcg2900_init(void) +{ + int err; + + BT_DBG("btcg2900_init"); + + err = platform_driver_register(&btcg2900_cmd_driver); + if (err) { + BT_ERR("Failed to register cmd (%d)", err); + return err; + } + err = platform_driver_register(&btcg2900_acl_driver); + if (err) { + BT_ERR("Failed to register acl (%d)", err); + return err; + } + err = platform_driver_register(&btcg2900_evt_driver); + if (err) { + BT_ERR("Failed to register evt (%d)", err); + return err; + } + return err; +} + +/** + * btcg2900_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit btcg2900_exit(void) +{ + BT_DBG("btcg2900_exit"); + platform_driver_unregister(&btcg2900_cmd_driver); + platform_driver_unregister(&btcg2900_acl_driver); + platform_driver_unregister(&btcg2900_evt_driver); +} + +module_init(btcg2900_init); +module_exit(btcg2900_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_AUTHOR("Henrik Possung ST-Ericsson"); +MODULE_AUTHOR("Josef Kindberg ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 Driver for ST-Ericsson controller"); diff --git a/drivers/staging/cg2900/bluetooth/cg2900_uart.c b/drivers/staging/cg2900/bluetooth/cg2900_uart.c new file mode 100644 index 00000000000..6cde9d75c21 --- /dev/null +++ b/drivers/staging/cg2900/bluetooth/cg2900_uart.c @@ -0,0 +1,2169 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * Lukasz Rymanowski (lukasz.rymanowski@tieto.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth UART Driver for ST-Ericsson CG2900 connectivity controller. + */ +#define NAME "cg2900_uart" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <asm/byteorder.h> +#include <linux/atomic.h> +#include <linux/device.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_qos.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/timer.h> +#include <linux/regulator/consumer.h> +#include <linux/tty.h> +#include <linux/tty_ldisc.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> + +#include "cg2900.h" + +#include "hci_uart.h" + +#define MAIN_DEV (uart_info->dev) + +/* Workqueues' names */ +#define UART_WQ_NAME "cg2900_uart_wq" +#define UART_NAME "cg2900_uart" + +/* + * A BT command complete event without any parameters is the defined size plus + * 1 byte extra for the status field which is always present in a + * command complete event. + */ +#define HCI_BT_CMD_COMPLETE_LEN (sizeof(struct hci_ev_cmd_complete) + 1) + +/* Timers used in milliseconds */ +#define UART_TX_TIMEOUT 100 +#define UART_RX_TIMEOUT 20 +#define UART_RESP_TIMEOUT 1000 +#define UART_RESUME_TIMEOUT 20 + +/* Max latency in microseconds for PM QoS to achieve max throughput */ +#define CG2900_PM_QOS_LATENCY 30 + +/* Number of bytes to reserve at start of sk_buffer when receiving packet */ +#define RX_SKB_RESERVE 8 +/* Max size of received packet (not including reserved bytes) */ +#define RX_SKB_MAX_SIZE 1024 + +/* Size of the header in the different packets */ +#define HCI_BT_EVT_HDR_SIZE 2 +#define HCI_BT_ACL_HDR_SIZE 4 +#define HCI_FM_RADIO_HDR_SIZE 1 +#define HCI_GNSS_HDR_SIZE 3 + +/* Position of length field in the different packets */ +#define HCI_EVT_LEN_POS 2 +#define HCI_ACL_LEN_POS 3 +#define FM_RADIO_LEN_POS 1 +#define GNSS_LEN_POS 2 + +/* Baud rate defines */ +#define ZERO_BAUD_RATE 0 +#define DEFAULT_BAUD_RATE 115200 +#define HIGH_BAUD_RATE 3000000 + +#define BT_SIZE_OF_HDR (sizeof(__le16) + sizeof(__u8)) +#define BT_PARAM_LEN(__pkt_len) (__pkt_len - BT_SIZE_OF_HDR) + +/* Standardized Bluetooth H:4 channels */ +#define HCI_BT_CMD_H4_CHANNEL 0x01 +#define HCI_BT_ACL_H4_CHANNEL 0x02 +#define HCI_BT_SCO_H4_CHANNEL 0x03 +#define HCI_BT_EVT_H4_CHANNEL 0x04 + +#define BT_BDADDR_SIZE 6 + +/* Reserve 1 byte for the HCI H:4 header */ +#define HCI_H4_SIZE 1 +#define CG2900_SKB_RESERVE HCI_H4_SIZE + +/* Default H4 channels which may change depending on connected controller */ +#define HCI_FM_RADIO_H4_CHANNEL 0x08 +#define HCI_GNSS_H4_CHANNEL 0x09 + +/* Bluetooth error codes */ +#define HCI_BT_ERROR_NO_ERROR 0x00 + +/* Bytes in the command Hci_Cmd_ST_Set_Uart_Baud_Rate */ +#define CG2900_BAUD_RATE_57600 0x03 +#define CG2900_BAUD_RATE_115200 0x02 +#define CG2900_BAUD_RATE_230400 0x01 +#define CG2900_BAUD_RATE_460800 0x00 +#define CG2900_BAUD_RATE_921600 0x20 +#define CG2900_BAUD_RATE_2000000 0x25 +#define CG2900_BAUD_RATE_3000000 0x27 +#define CG2900_BAUD_RATE_3250000 0x28 +#define CG2900_BAUD_RATE_4000000 0x2B + +/* GNSS */ +struct gnss_hci_hdr { + __u8 op_code; + __le16 plen; +} __packed; + +/* FM legacy command packet */ +struct fm_leg_cmd { + __u8 length; + __u8 opcode; + __u8 read_write; + __u8 fm_function; + union { /* Payload varies with function */ + __le16 irqmask; + struct fm_leg_fm_cmd { + __le16 head; + __le16 data[]; + } fm_cmd; + }; +} __packed; + +/* FM legacy command complete packet */ +struct fm_leg_cmd_cmpl { + __u8 param_length; + __u8 status; + __u8 opcode; + __u8 read_write; + __u8 cmd_status; + __u8 fm_function; + __le16 response_head; + __le16 data[]; +} __packed; + +/* FM legacy interrupt packet, PG2 style */ +struct fm_leg_irq_v2 { + __u8 param_length; + __u8 status; + __u8 opcode; + __u8 event_type; + __u8 event_id; + __le16 irq; +} __packed; + +/* FM legacy interrupt packet, PG1 style */ +struct fm_leg_irq_v1 { + __u8 param_length; + __u8 opcode; + __u8 event_id; + __le16 irq; +} __packed; + +union fm_leg_evt_or_irq { + __u8 param_length; + struct fm_leg_cmd_cmpl evt; + struct fm_leg_irq_v2 irq_v2; + struct fm_leg_irq_v1 irq_v1; +} __packed; + +/* BT VS SetBaudRate command */ +#define CG2900_BT_OP_VS_SET_BAUD_RATE 0xFC09 +struct bt_vs_set_baud_rate_cmd { + __le16 opcode; + __u8 plen; + __u8 baud_rate; +} __packed; + +/** + * enum uart_rx_state - UART RX-state for UART. + * @W4_PACKET_TYPE: Waiting for packet type. + * @W4_EVENT_HDR: Waiting for BT event header. + * @W4_ACL_HDR: Waiting for BT ACL header. + * @W4_FM_RADIO_HDR: Waiting for FM header. + * @W4_GNSS_HDR: Waiting for GNSS header. + * @W4_DATA: Waiting for data in rest of the packet (after header). + */ +enum uart_rx_state { + W4_PACKET_TYPE, + W4_EVENT_HDR, + W4_ACL_HDR, + W4_FM_RADIO_HDR, + W4_GNSS_HDR, + W4_DATA +}; + +/** + * enum sleep_state - Sleep-state for UART. + * @CHIP_AWAKE: Chip is awake. + * @CHIP_FALLING_ASLEEP: Chip is falling asleep. + * @CHIP_ASLEEP: Chip is asleep. + * @CHIP_SUSPENDED: Chip in suspend state. + * @CHIP_RESUMING: Chip is going back from suspend state. + * @CHIP_POWERED_DOWN: Chip is off. + */ +enum sleep_state { + CHIP_AWAKE, + CHIP_FALLING_ASLEEP, + CHIP_ASLEEP, + CHIP_SUSPENDED, + CHIP_RESUMING, + CHIP_POWERED_DOWN +}; + +/** + * enum baud_rate_change_state - Baud rate-state for UART. + * @BAUD_IDLE: No baud rate change is ongoing. + * @BAUD_SENDING_RESET: HCI reset has been sent. Waiting for command complete + * event. + * @BAUD_START: Set baud rate cmd scheduled for sending. + * @BAUD_SENDING: Set baud rate cmd sending in progress. + * @BAUD_WAITING: Set baud rate cmd sent, waiting for command complete + * event. + * @BAUD_SUCCESS: Baud rate change has succeeded. + * @BAUD_FAIL: Baud rate change has failed. + */ +enum baud_rate_change_state { + BAUD_IDLE, + BAUD_SENDING_RESET, + BAUD_START, + BAUD_SENDING, + BAUD_WAITING, + BAUD_SUCCESS, + BAUD_FAIL +}; + +/** + * struct uart_work_struct - Work structure for UART module. + * @work: Work structure. + * @data: Pointer to private data. + * + * This structure is used to pack work for work queue. + */ +struct uart_work_struct { + struct work_struct work; + void *data; +}; + +/** + * struct uart_delayed_work_struct - Work structure for UART module. + * @delayed_work: Work structure. + * @data: Pointer to private data. + * + * This structure is used to pack work for work queue. + */ +struct uart_delayed_work_struct { + struct delayed_work work; + void *data; +}; + +/** + * struct uart_info - Main UART info structure. + * @rx_state: Current RX state. + * @rx_count: Number of bytes left to receive. + * @rx_skb: SK_buffer to store the received data into. + * @tx_queue: TX queue for sending data to chip. + * @rx_skb_lock Spin lock to protect rx_skb. + * @hu: Hci uart structure. + * @wq: UART work queue. + * @baud_rate_state: UART baud rate change state. + * @baud_rate: Current baud rate setting. + * @sleep_state: UART sleep state. + * @sleep_work: Delayed sleep work struct. + * @wakeup_work: Wake-up work struct. + * @restart_sleep_work: Reschedule sleep_work and wake-up work struct. + * @sleep_state_lock: Used to protect chip state. + * @sleep_allowed: Indicates if tty has functions needed for sleep mode. + * @tx_in_progress: Indicates data sending in progress. + * @rx_in_progress: Indicates data receiving in progress. + * @transmission_lock: Spin_lock to protect tx/rx_in_progress. + * @regulator: Regulator. + * @regulator_enabled: True if regulator is enabled. + * @dev: Pointer to CG2900 uart device. + * @chip_dev: Chip device for current UART transport. + * @cts_irq: CTS interrupt for this UART. + * @cts_gpio: CTS GPIO for this UART. + * @suspend_blocked: True if suspend operation is blocked in the framework. + * @pm_qos_latency: PM QoS structure. + */ +struct uart_info { + enum uart_rx_state rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; + struct sk_buff_head tx_queue; + spinlock_t rx_skb_lock; + + struct hci_uart *hu; + + struct workqueue_struct *wq; + enum baud_rate_change_state baud_rate_state; + int baud_rate; + enum sleep_state sleep_state; + struct uart_delayed_work_struct sleep_work; + struct uart_work_struct wakeup_work; + struct uart_work_struct restart_sleep_work; + struct mutex sleep_state_lock; + bool sleep_allowed; + bool tx_in_progress; + bool rx_in_progress; + spinlock_t transmission_lock; + struct regulator *regulator; + bool regulator_enabled; + struct device *dev; + struct cg2900_chip_dev chip_dev; + int cts_irq; + int cts_gpio; + bool suspend_blocked; + struct pm_qos_request pm_qos_latency; +}; + +/* Module parameters */ +static int uart_default_baud = DEFAULT_BAUD_RATE; +static int uart_high_baud = HIGH_BAUD_RATE; +static int uart_debug; + +static DECLARE_WAIT_QUEUE_HEAD(uart_wait_queue); + +static void wake_up_chip(struct uart_info *uart_info); + +/** + * is_chip_flow_off() - Check if chip has set flow off. + * @tty: Pointer to tty. + * + * Returns: + * true - chip flows off. + * false - chip flows on. + */ +static bool is_chip_flow_off(struct uart_info *uart_info) +{ + int lines = 0; + + if (uart_info->hu) + lines = hci_uart_tiocmget(uart_info->hu); + + if (lines & TIOCM_CTS) + return false; + else + return true; +} + +/** + * create_work_item() - Create work item and add it to the work queue. + * @uart_info: Main Uart structure. + * @work_func: Work function. + * + * Returns: + * 0 if there is no error. + * -EBUSY if not possible to queue work. + * -ENOMEM if allocation fails. + */ +static int create_work_item(struct uart_info *uart_info, + work_func_t work_func) +{ + struct uart_work_struct *new_work; + int res; + + new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC); + if (!new_work) { + dev_err(MAIN_DEV, + "Failed to alloc memory for uart_work_struct\n"); + return -ENOMEM; + } + + new_work->data = uart_info; + INIT_WORK(&new_work->work, work_func); + + res = queue_work(uart_info->wq, &new_work->work); + if (!res) { + dev_err(MAIN_DEV, + "Failed to queue work_struct because it's already " + "in the queue\n"); + kfree(new_work); + return -EBUSY; + } + + return 0; +} + +/** + * handle_cts_irq() - Called to handle CTS interrupt in work context. + * @work: work which needs to be done. + * + * The handle_cts_irq() function is a work handler called if interrupt on CTS + * occurred. It wakes up the transport. + */ +static void handle_cts_irq(struct work_struct *work) +{ + struct uart_work_struct *current_work = + container_of(work, struct uart_work_struct, work); + struct uart_info *uart_info = (struct uart_info *)current_work->data; + + pm_qos_update_request(&uart_info->pm_qos_latency, + CG2900_PM_QOS_LATENCY); + + spin_lock_bh(&(uart_info->transmission_lock)); + /* Mark that there is an ongoing transfer. */ + uart_info->rx_in_progress = true; + spin_unlock_bh(&(uart_info->transmission_lock)); + + /* Cancel pending sleep work if there is any. */ + cancel_delayed_work_sync(&uart_info->sleep_work.work); + + mutex_lock(&(uart_info->sleep_state_lock)); + + if (uart_info->sleep_state == CHIP_SUSPENDED) { + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_RESUMING\n"); + uart_info->sleep_state = CHIP_RESUMING; + mutex_unlock(&(uart_info->sleep_state_lock)); + } else { + mutex_unlock(&(uart_info->sleep_state_lock)); + wake_up_chip(uart_info); + } + + kfree(current_work); +} + +/** + * cts_interrupt() - Called to handle CTS interrupt. + * @irq: Interrupt that occurred. + * @dev_id: Device ID where interrupt occurred. + * + * The cts_interrupt() function is called if interrupt on CTS occurred. + * It disables the interrupt and starts a new work thread to handle + * the interrupt. + */ +static irqreturn_t cts_interrupt(int irq, void *dev_id) +{ + struct uart_info *uart_info = dev_get_drvdata(dev_id); +#ifdef CONFIG_PM + disable_irq_wake(irq); +#endif + disable_irq_nosync(irq); + + /* Create work and leave IRQ context. */ + (void)create_work_item(uart_info, handle_cts_irq); + + return IRQ_HANDLED; +} + +/** + * set_cts_irq() - Enable interrupt on CTS. + * @uart_info: Main Uart structure. + * + * Returns: + * 0 if there is no error. + * Error codes from request_irq and disable_uart. + */ +static int set_cts_irq(struct uart_info *uart_info) +{ + int err; + int cts_val = 0; + + /* Set IRQ on CTS. */ + err = request_irq(uart_info->cts_irq, + cts_interrupt, + IRQF_TRIGGER_FALLING, + UART_NAME, + uart_info->dev); + if (err) { + dev_err(MAIN_DEV, "Could not request CTS IRQ (%d)\n", err); + return err; + } + + /* + * It may happen that there was already an interrupt on CTS just before + * the enable_irq() call above. If the CTS line is low now it means that + * it's happened, so disable the CTS interrupt and return -ECANCELED. + */ + cts_val = gpio_get_value(uart_info->cts_gpio); + if (!cts_val) { + dev_dbg(MAIN_DEV, "Missed interrupt, going back to " + "awake state\n"); + free_irq(uart_info->cts_irq, uart_info->dev); + return -ECANCELED; + } + +#ifdef CONFIG_PM + enable_irq_wake(uart_info->cts_irq); +#endif + return 0; +} + +/** + * disable_uart_pins() - Disable the UART pins. + * @uart_info: Main Uart structure. + */ +static void disable_uart_pins(struct uart_info *uart_info) +{ + struct cg2900_platform_data *pf_data; + + pf_data = dev_get_platdata(uart_info->dev); + + if (pf_data->uart.disable_uart) { + int err = pf_data->uart.disable_uart(&uart_info->chip_dev); + if (err) + dev_err(MAIN_DEV, + "Unable to disable UART Hardware (%d)\n", err); + } +} + +/** + * enable_uart_pins() - Enable the UART pins. + * @uart_info: Main Uart structure. + */ +static void enable_uart_pins(struct uart_info *uart_info) +{ + struct cg2900_platform_data *pf_data; + + pf_data = dev_get_platdata(uart_info->dev); + + if (pf_data->uart.enable_uart) { + int err = pf_data->uart.enable_uart(&uart_info->chip_dev); + if (err) + dev_err(MAIN_DEV, + "Unable to enable UART Hardware (%d)\n", err); + } +} + +/** + * unset_cts_irq() - Disable interrupt on CTS. + * @uart_info: Main Uart structure. + */ +static void unset_cts_irq(struct uart_info *uart_info) +{ + /* Free CTS interrupt */ + free_irq(uart_info->cts_irq, uart_info->dev); +} + +/** + * get_sleep_timeout() - Get sleep timeout. + * @uart_info: Main Uart structure. + * + * Check all conditions for sleep and return sleep timeout. + * Return: + * 0: sleep not allowed. + * other: Timeout value in ms. + */ +static unsigned long get_sleep_timeout(struct uart_info *uart_info) +{ + unsigned long timeout_jiffies = cg2900_get_sleep_timeout(); + + if (timeout_jiffies && + uart_info->hu && + uart_info->hu->fd && + uart_info->sleep_allowed) + return timeout_jiffies; + + return 0; +} + +/** + * work_wake_up_chip() - Called to wake up of the transport in work context. + * @work: work which needs to be done. + */ +static void work_wake_up_chip(struct work_struct *work) +{ + struct uart_work_struct *current_work = + container_of(work, struct uart_work_struct, work); + struct uart_info *uart_info = (struct uart_info *)current_work->data; + + wake_up_chip(uart_info); +} + +/** + * wake_up_chip() - Wakes up the chip and transport. + * @work: pointer to a work struct if the function was called that way. + * + * Depending on the current sleep state it may wake up the transport. + */ +static void wake_up_chip(struct uart_info *uart_info) +{ + unsigned long timeout_jiffies = get_sleep_timeout(uart_info); + + /* Resuming state is special. Need to get back chip to awake state. */ + if (!timeout_jiffies && uart_info->sleep_state != CHIP_RESUMING) + return; + + if (!uart_info->hu) { + dev_err(MAIN_DEV, "wake_up_chip: UART not open\n"); + return; + } + + mutex_lock(&(uart_info->sleep_state_lock)); + + /* + * If chip is powered down we cannot wake it up here. It has to be woken + * up through a call to uart_set_chip_power() + */ + if (CHIP_POWERED_DOWN == uart_info->sleep_state) + goto finished; + + if (!uart_info->suspend_blocked) { + uart_info->suspend_blocked = true; + pm_qos_update_request(&uart_info->pm_qos_latency, + CG2900_PM_QOS_LATENCY); + } + + /* + * This function indicates data is transmitted. + * Therefore see to that the chip is awake. + */ + if (CHIP_AWAKE == uart_info->sleep_state) + goto finished; + + if (CHIP_ASLEEP == uart_info->sleep_state || + CHIP_RESUMING == uart_info->sleep_state) { + /* Wait before disabling IRQ */ + schedule_timeout_killable( + msecs_to_jiffies(UART_RESUME_TIMEOUT)); + + /* Disable IRQ only when it was enabled. */ + unset_cts_irq(uart_info); + (void)hci_uart_set_baudrate(uart_info->hu, + uart_info->baud_rate); + + enable_uart_pins(uart_info); + + /* + * Wait before flowing on. Otherwise UART might not be ready in + * time + */ + schedule_timeout_killable( + msecs_to_jiffies(UART_RESUME_TIMEOUT)); + + /* Set FLOW on. */ + hci_uart_flow_ctrl(uart_info->hu, FLOW_ON); + } + + /* Unset BREAK. */ + dev_dbg(MAIN_DEV, "wake_up_chip: Clear break\n"); + hci_uart_set_break(uart_info->hu, BREAK_OFF); + + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_AWAKE\n"); + uart_info->sleep_state = CHIP_AWAKE; + +finished: + mutex_unlock(&(uart_info->sleep_state_lock)); +} + +/** + * set_chip_sleep_mode() - Put the chip and transport to sleep mode. + * @work: pointer to work_struct. + * + * The set_chip_sleep_mode() function is called if there are no ongoing data + * transmissions. It tries to put the chip in sleep mode. + * + */ +static void set_chip_sleep_mode(struct work_struct *work) +{ + int err = 0; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct uart_delayed_work_struct *current_work = container_of( + delayed_work, struct uart_delayed_work_struct, work); + struct uart_info *uart_info = (struct uart_info *)current_work->data; + unsigned long timeout_jiffies = get_sleep_timeout(uart_info); + int chars_in_buffer; + + if (!timeout_jiffies) + return; + + if (!uart_info->hu) { + dev_err(MAIN_DEV, "set_chip_sleep_mode: UART not open\n"); + return; + } + + if (uart_info->tx_in_progress || uart_info->rx_in_progress) { + dev_dbg(MAIN_DEV, "Not going to sleep, TX/RX in progress\n"); + return; + } + + mutex_lock(&(uart_info->sleep_state_lock)); + + switch (uart_info->sleep_state) { + case CHIP_FALLING_ASLEEP: + if (!is_chip_flow_off(uart_info)) { + dev_dbg(MAIN_DEV, "Chip flow is on, it's not ready to" + "sleep yet\n"); + goto schedule_sleep_work; + } + + /* Flow OFF. */ + hci_uart_flow_ctrl(uart_info->hu, FLOW_OFF); + + disable_uart_pins(uart_info); + + /* + * Set baud zero. + * This cause shut off UART clock as well. + */ + (void)hci_uart_set_baudrate(uart_info->hu, ZERO_BAUD_RATE); + err = set_cts_irq(uart_info); + if (err < 0) { + enable_uart_pins(uart_info); + (void)hci_uart_set_baudrate(uart_info->hu, + uart_info->baud_rate); + hci_uart_flow_ctrl(uart_info->hu, FLOW_ON); + hci_uart_set_break(uart_info->hu, BREAK_OFF); + + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_AWAKE\n"); + uart_info->sleep_state = CHIP_AWAKE; + + if (err == -ECANCELED) + goto finished; + else { + dev_err(MAIN_DEV, "Can not set interrupt on " + "CTS, err:%d\n", err); + goto error; + } + } + + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_ASLEEP\n"); + uart_info->sleep_state = CHIP_ASLEEP; + if (uart_info->suspend_blocked) { + uart_info->suspend_blocked = false; + pm_qos_update_request(&uart_info->pm_qos_latency, + PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE); + } + break; + case CHIP_AWAKE: + chars_in_buffer = hci_uart_chars_in_buffer(uart_info->hu); + if (chars_in_buffer) { + dev_dbg(MAIN_DEV, "sleep_timer_expired: " + "tx not finished, stay awake and " + "restart the sleep timer\n"); + goto schedule_sleep_work; + } + + dev_dbg(MAIN_DEV, "sleep_timer_expired: Set break\n"); + hci_uart_set_break(uart_info->hu, BREAK_ON); + + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_FALLING_ASLEEP\n"); + uart_info->sleep_state = CHIP_FALLING_ASLEEP; + goto schedule_sleep_work; + + case CHIP_POWERED_DOWN: + case CHIP_SUSPENDED: + case CHIP_ASLEEP: /* Fallthrough. */ + default: + dev_dbg(MAIN_DEV, + "Chip sleeps, is suspended or powered down\n"); + break; + } + + mutex_unlock(&(uart_info->sleep_state_lock)); + + return; + +finished: + mutex_unlock(&(uart_info->sleep_state_lock)); + return; +schedule_sleep_work: + mutex_unlock(&(uart_info->sleep_state_lock)); + if (timeout_jiffies) + queue_delayed_work(uart_info->wq, &uart_info->sleep_work.work, + timeout_jiffies); + return; +error: + /* Disable sleep mode.*/ + dev_err(MAIN_DEV, "Disable sleep mode\n"); + uart_info->sleep_allowed = false; + mutex_unlock(&(uart_info->sleep_state_lock)); +} + +#ifdef CONFIG_PM +/** + * cg2900_uart_suspend() - Called by Linux PM to put the device in a low power mode. + * @pdev: Pointer to platform device. + * @state: New state. + * + * In UART case, CG2900 driver does nothing on suspend. + * + * Returns: + * 0 - Success. + */ +static int cg2900_uart_suspend(struct platform_device *pdev, pm_message_t state) +{ + int err = 0; + struct uart_info *uart_info = dev_get_drvdata(&pdev->dev); + + mutex_lock(&(uart_info->sleep_state_lock)); + + if (uart_info->sleep_state == CHIP_POWERED_DOWN) + goto finished; + + if (uart_info->sleep_state != CHIP_ASLEEP) { + err = -EBUSY; + goto finished; + } + + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_SUSPENDED\n"); + uart_info->sleep_state = CHIP_SUSPENDED; + +finished: + mutex_unlock(&(uart_info->sleep_state_lock)); + return err; +} + +/** + * cg2900_uart_resume() - Called to bring a device back from a low power state. + * @pdev: Pointer to platform device. + * + * In UART case, CG2900 driver does nothing on resume. + * + * Returns: + * 0 - Success. + */ +static int cg2900_uart_resume(struct platform_device *pdev) +{ + struct uart_info *uart_info = dev_get_drvdata(&pdev->dev); + + mutex_lock(&(uart_info->sleep_state_lock)); + + if (uart_info->sleep_state == CHIP_RESUMING) + /* System resume because of trafic on UART. Lets wakeup.*/ + (void)queue_work(uart_info->wq, &uart_info->wakeup_work.work); + else if (uart_info->sleep_state != CHIP_POWERED_DOWN) { + /* No need to wakeup chip. Go back to Asleep state.*/ + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_ASLEEP\n"); + uart_info->sleep_state = CHIP_ASLEEP; + } + + mutex_unlock(&(uart_info->sleep_state_lock)); + return 0; +} +#endif /* CONFIG_PM */ + +/** + * cg2900_enable_regulator() - Enable regulator. + * @uart_info: Main Uart structure. + * + * Returns: + * 0 - Success. + * Error from regulator_get, regulator_enable. + */ +static int cg2900_enable_regulator(struct uart_info *uart_info) +{ +#ifdef CONFIG_REGULATOR + int err; + + /* Get and enable regulator. */ + uart_info->regulator = regulator_get(uart_info->dev, "gbf_1v8"); + if (IS_ERR(uart_info->regulator)) { + dev_err(MAIN_DEV, "Not able to find regulator\n"); + err = PTR_ERR(uart_info->regulator); + } else { + err = regulator_enable(uart_info->regulator); + if (err) + dev_err(MAIN_DEV, "Not able to enable regulator\n"); + else + uart_info->regulator_enabled = true; + } + return err; +#else + return 0; +#endif +} + +/** + * cg2900_disable_regulator() - Disable regulator. + * @uart_info: Main Uart structure. + * + */ +static void cg2900_disable_regulator(struct uart_info *uart_info) +{ +#ifdef CONFIG_REGULATOR + /* Disable and put regulator. */ + if (uart_info->regulator && uart_info->regulator_enabled) { + regulator_disable(uart_info->regulator); + uart_info->regulator_enabled = false; + } + regulator_put(uart_info->regulator); + uart_info->regulator = NULL; +#endif +} + +/** + * is_set_baud_rate_cmd() - Checks if data contains set baud rate hci cmd. + * @data: Pointer to data array to check. + * + * Returns: + * true - if cmd found; + * false - otherwise. + */ +static bool is_set_baud_rate_cmd(const char *data) +{ + struct hci_command_hdr *cmd; + + if (data[0] != HCI_BT_CMD_H4_CHANNEL) + return false; + + cmd = (struct hci_command_hdr *)&data[1]; + if (le16_to_cpu(cmd->opcode) == CG2900_BT_OP_VS_SET_BAUD_RATE && + cmd->plen == BT_PARAM_LEN(sizeof(struct bt_vs_set_baud_rate_cmd))) + return true; + + return false; +} + +/** + * is_bt_cmd_complete_no_param() - Checks if data contains command complete event for a certain command. + * @skb: sk_buffer containing the data including H:4 header. + * @opcode: Command op code. + * @status: Command status. + * + * Returns: + * true - If this is the command complete we were looking for; + * false - otherwise. + */ +static bool is_bt_cmd_complete_no_param(struct sk_buff *skb, u16 opcode, + u8 *status) +{ + struct hci_event_hdr *event; + struct hci_ev_cmd_complete *complete; + u8 *data = &(skb->data[0]); + + if (HCI_BT_EVT_H4_CHANNEL != *data) + return false; + + data += HCI_H4_SIZE; + event = (struct hci_event_hdr *)data; + if (HCI_EV_CMD_COMPLETE != event->evt || + HCI_BT_CMD_COMPLETE_LEN != event->plen) + return false; + + data += sizeof(*event); + complete = (struct hci_ev_cmd_complete *)data; + if (opcode != le16_to_cpu(complete->opcode)) + return false; + + if (status) { + /* + * All command complete have the status field at first byte of + * packet data. + */ + data += sizeof(*complete); + *status = *data; + } + return true; +} + +/** + * alloc_rx_skb() - Alloc an sk_buff structure for receiving data from controller. + * @size: Size in number of octets. + * @priority: Allocation priority, e.g. GFP_KERNEL. + * + * Returns: + * Pointer to sk_buff structure. + */ +static struct sk_buff *alloc_rx_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + RX_SKB_RESERVE, priority); + if (skb) + skb_reserve(skb, RX_SKB_RESERVE); + + return skb; +} + +/** + * finish_setting_baud_rate() - Handles sending the ste baud rate hci cmd. + * @hu: Pointer to associated Hci uart structure. + * + * finish_setting_baud_rate() makes sure that the set baud rate cmd has + * been really sent out on the wire and then switches the tty driver to new + * baud rate. + */ +static void finish_setting_baud_rate(struct hci_uart *hu) +{ + struct uart_info *uart_info = + (struct uart_info *)dev_get_drvdata(hu->proto->dev); + /* + * Give the tty driver time to send data and proceed. If it hasn't + * been sent we can't do much about it anyway. + */ + schedule_timeout_killable(msecs_to_jiffies(UART_TX_TIMEOUT)); + + /* + * Now set the termios struct to the new baudrate. Start by storing + * the old termios. + */ + if (hci_uart_set_baudrate(hu, uart_info->baud_rate) < 0) { + /* Something went wrong.*/ + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_IDLE\n"); + uart_info->baud_rate_state = BAUD_IDLE; + } else { + dev_dbg(MAIN_DEV, "Setting termios to new baud rate\n"); + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_WAITING\n"); + uart_info->baud_rate_state = BAUD_WAITING; + } + + hci_uart_flow_ctrl(hu, FLOW_ON); +} + +/** + * alloc_set_baud_rate_cmd() - Allocates new sk_buff and fills in the change baud rate hci cmd. + * @uart_info: Main Uart structure. + * @baud: (in/out) Requested new baud rate. Updated to default baud rate + * upon invalid value. + * + * Returns: + * Pointer to allocated sk_buff if successful; + * NULL otherwise. + */ +static struct sk_buff *alloc_set_baud_rate_cmd(struct uart_info *uart_info, + int *baud) +{ + struct sk_buff *skb; + u8 *h4; + struct bt_vs_set_baud_rate_cmd *cmd; + + skb = alloc_skb(sizeof(*cmd) + CG2900_SKB_RESERVE, GFP_ATOMIC); + if (!skb) { + dev_err(MAIN_DEV, + "alloc_set_baud_rate_cmd: Failed to alloc skb\n"); + return NULL; + } + skb_reserve(skb, CG2900_SKB_RESERVE); + + cmd = (struct bt_vs_set_baud_rate_cmd *)skb_put(skb, sizeof(cmd)); + + /* Create the Hci_Cmd_ST_Set_Uart_Baud_Rate packet */ + cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_SET_BAUD_RATE); + cmd->plen = BT_PARAM_LEN(sizeof(cmd)); + + switch (*baud) { + case 57600: + cmd->baud_rate = CG2900_BAUD_RATE_57600; + break; + case 115200: + cmd->baud_rate = CG2900_BAUD_RATE_115200; + break; + case 230400: + cmd->baud_rate = CG2900_BAUD_RATE_230400; + break; + case 460800: + cmd->baud_rate = CG2900_BAUD_RATE_460800; + break; + case 921600: + cmd->baud_rate = CG2900_BAUD_RATE_921600; + break; + case 2000000: + cmd->baud_rate = CG2900_BAUD_RATE_2000000; + break; + case 3000000: + cmd->baud_rate = CG2900_BAUD_RATE_3000000; + break; + case 3250000: + cmd->baud_rate = CG2900_BAUD_RATE_3250000; + break; + case 4000000: + cmd->baud_rate = CG2900_BAUD_RATE_4000000; + break; + default: + dev_err(MAIN_DEV, + "Invalid speed requested (%d), using 115200 bps " + "instead\n", *baud); + cmd->baud_rate = CG2900_BAUD_RATE_115200; + *baud = 115200; + break; + }; + + h4 = skb_push(skb, HCI_H4_SIZE); + *h4 = HCI_BT_CMD_H4_CHANNEL; + + return skb; +} + +/** + * work_do_transmit() - Transmit data packet to connectivity controller over UART. + * @work: Pointer to work info structure. Contains uart_info structure + * pointer. + */ +static void work_do_transmit(struct work_struct *work) +{ + struct uart_work_struct *current_work = + container_of(work, struct uart_work_struct, work); + struct uart_info *uart_info = (struct uart_info *)current_work->data; + + kfree(current_work); + + if (!uart_info->hu) { + dev_err(MAIN_DEV, "work_do_transmit: UART not open\n"); + return; + } + + spin_lock_bh(&(uart_info->transmission_lock)); + /* Mark that there is an ongoing transfer. */ + uart_info->tx_in_progress = true; + spin_unlock_bh(&(uart_info->transmission_lock)); + + /* Cancel pending sleep work if there is any. */ + cancel_delayed_work_sync(&uart_info->sleep_work.work); + + /* Wake up the chip and transport. */ + wake_up_chip(uart_info); + + (void)hci_uart_tx_wakeup(uart_info->hu); +} + +/** + * work_hw_deregistered() - Handle HW deregistered. + * @work: Reference to work data. + */ +static void work_hw_deregistered(struct work_struct *work) +{ + struct uart_work_struct *current_work; + struct uart_info *uart_info; + int err; + current_work = container_of(work, struct uart_work_struct, work); + uart_info = (struct uart_info *)current_work->data; + + err = cg2900_deregister_trans_driver(&uart_info->chip_dev); + if (err) + dev_err(MAIN_DEV, "Could not deregister UART from Core (%d)\n", + err); + + kfree(current_work); +} + +/** + * set_baud_rate() - Sets new baud rate for the UART. + * @hu: Pointer to hci_uart structure. + * @baud: New baud rate. + * + * This function first sends the HCI command + * Hci_Cmd_ST_Set_Uart_Baud_Rate. It then changes the baud rate in HW, and + * finally it waits for the Command Complete event for the + * Hci_Cmd_ST_Set_Uart_Baud_Rate command. + * + * Returns: + * 0 if there is no error. + * -EALREADY if baud rate change is already in progress. + * -EFAULT if one or more of the UART related structs is not allocated. + * -ENOMEM if skb allocation has failed. + * -EPERM if setting the new baud rate has failed. + * Errors from create_work_item. + */ +static int set_baud_rate(struct hci_uart *hu, int baud) +{ + int err = 0; + struct sk_buff *skb; + int old_baud_rate; + struct uart_info *uart_info = + (struct uart_info *)dev_get_drvdata(hu->proto->dev); + + dev_dbg(MAIN_DEV, "set_baud_rate (%d baud)\n", baud); + + if (uart_info->baud_rate_state != BAUD_IDLE) { + dev_err(MAIN_DEV, + "Trying to set new baud rate before old setting " + "is finished\n"); + return -EALREADY; + } + + if (!uart_info->hu) { + dev_err(MAIN_DEV, "set_baud_rate: UART not open\n"); + return -EFAULT; + } + + /* + * Wait some time to be sure that any RX process has finished (which + * flows on RTS in the end) before flowing off the RTS. + */ + schedule_timeout_killable(msecs_to_jiffies(UART_RX_TIMEOUT)); + hci_uart_flow_ctrl(uart_info->hu, FLOW_OFF); + + /* + * Store old baud rate so that we can restore it if something goes + * wrong. + */ + old_baud_rate = uart_info->baud_rate; + + skb = alloc_set_baud_rate_cmd(uart_info, &baud); + if (!skb) { + dev_err(MAIN_DEV, "alloc_set_baud_rate_cmd failed\n"); + return -ENOMEM; + } + + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_START\n"); + uart_info->baud_rate_state = BAUD_START; + uart_info->baud_rate = baud; + + /* Queue the sk_buffer... */ + skb_queue_tail(&uart_info->tx_queue, skb); + + /* ... and call the common UART TX function */ + err = create_work_item(uart_info, work_do_transmit); + if (err) { + dev_err(MAIN_DEV, + "Failed to send change baud rate cmd, freeing skb\n"); + skb = skb_dequeue_tail(&uart_info->tx_queue); + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_IDLE\n"); + uart_info->baud_rate_state = BAUD_IDLE; + uart_info->baud_rate = old_baud_rate; + kfree_skb(skb); + return err; + } + + dev_dbg(MAIN_DEV, "Set baud rate cmd scheduled for sending\n"); + + /* + * Now wait for the command complete. + * It will come at the new baudrate. + */ + wait_event_timeout(uart_wait_queue, + ((BAUD_SUCCESS == uart_info->baud_rate_state) || + (BAUD_FAIL == uart_info->baud_rate_state)), + msecs_to_jiffies(UART_RESP_TIMEOUT)); + if (BAUD_SUCCESS == uart_info->baud_rate_state) + dev_info(MAIN_DEV, "Baud rate changed to %d baud\n", baud); + else { + dev_err(MAIN_DEV, "Failed to set new baud rate (%d)\n", + uart_info->baud_rate_state); + err = -EPERM; + } + + /* Finally flush the TTY so we are sure that is no bad data there */ + hci_uart_flush_buffer(hu); + dev_dbg(MAIN_DEV, "Flushing TTY after baud rate change\n"); + /* Finished. Set state to IDLE */ + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_IDLE\n"); + uart_info->baud_rate_state = BAUD_IDLE; + + return err; +} + +/** + * uart_write() - Transmit data to CG2900 over UART. + * @dev: Transport device information. + * @skb: SK buffer to transmit. + * + * Returns: + * 0 if there is no error. + * Errors from create_work_item. + */ +static int uart_write(struct cg2900_chip_dev *dev, struct sk_buff *skb) +{ + int err; + struct uart_info *uart_info = dev_get_drvdata(dev->dev); + + if (uart_debug) + dev_dbg(MAIN_DEV, "uart_write: data len = %d\n", skb->len); + + /* Queue the sk_buffer... */ + skb_queue_tail(&uart_info->tx_queue, skb); + + /* ...and start TX operation */ + + err = create_work_item(uart_info, work_do_transmit); + if (err) + dev_err(MAIN_DEV, + "Failed to create work item (%d) uart_tty_wakeup\n", + err); + + return err; +} + +/** + * uart_open() - Open the CG2900 UART for data transfers. + * @dev: Transport device information. + * + * Returns: + * 0 if there is no error, + * -EACCES if write to transport failed, + * -EIO if chip did not answer to commands. + * Errors from set_baud_rate. + */ +static int uart_open(struct cg2900_chip_dev *dev) +{ + u8 *h4; + struct sk_buff *skb; + struct hci_command_hdr *cmd; + struct uart_info *uart_info = dev_get_drvdata(dev->dev); + + if (!uart_info->hu) { + dev_err(MAIN_DEV, "uart_open: UART not open\n"); + return -EACCES; + } + + /* + * Chip has just been started up. It has a system to autodetect + * exact baud rate and transport to use. There are only a few commands + * it will recognize and HCI Reset is one of them. + * We therefore start with sending that before actually changing + * baud rate. + * + * Create the Hci_Reset packet + */ + + skb = alloc_skb(sizeof(*cmd) + HCI_H4_SIZE, GFP_ATOMIC); + if (!skb) { + dev_err(MAIN_DEV, "Couldn't allocate sk_buff with length %d\n", + sizeof(*cmd)); + return -EACCES; + } + skb_reserve(skb, HCI_H4_SIZE); + cmd = (struct hci_command_hdr *)skb_put(skb, sizeof(*cmd)); + cmd->opcode = cpu_to_le16(HCI_OP_RESET); + cmd->plen = 0; /* No parameters for HCI reset */ + + h4 = skb_push(skb, HCI_H4_SIZE); + *h4 = HCI_BT_CMD_H4_CHANNEL; + + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_SENDING_RESET\n"); + uart_info->baud_rate_state = BAUD_SENDING_RESET; + dev_dbg(MAIN_DEV, "Sending HCI reset before baud rate change\n"); + + + /* Queue the sk_buffer... */ + skb_queue_tail(&uart_info->tx_queue, skb); + + (void)hci_uart_tx_wakeup(uart_info->hu); + + /* + * Wait for command complete. If error, exit without changing + * baud rate. + */ + wait_event_timeout(uart_wait_queue, + BAUD_IDLE == uart_info->baud_rate_state, + msecs_to_jiffies(UART_RESP_TIMEOUT)); + if (BAUD_IDLE != uart_info->baud_rate_state) { + dev_err(MAIN_DEV, "Failed to send HCI Reset\n"); + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_IDLE\n"); + uart_info->baud_rate_state = BAUD_IDLE; + return -EIO; + } + + /* Just return if there will be no change of baud rate */ + if (uart_default_baud != uart_high_baud) + return set_baud_rate(uart_info->hu, uart_high_baud); + else + return 0; +} + +/** + * uart_set_chip_power() - Enable or disable the CG2900. + * @chip_on: true if chip shall be enabled, false otherwise. + */ +static void uart_set_chip_power(struct cg2900_chip_dev *dev, bool chip_on) +{ + int uart_baudrate = uart_default_baud; + struct cg2900_platform_data *pf_data; + struct uart_info *uart_info; + + pf_data = dev_get_platdata(dev->dev); + uart_info = dev_get_drvdata(dev->dev); + + dev_info(MAIN_DEV, "Set chip power: %s\n", + (chip_on ? "ENABLE" : "DISABLE")); + + /* Cancel any ongoing works.*/ + cancel_work_sync(&uart_info->wakeup_work.work); + cancel_delayed_work_sync(&uart_info->sleep_work.work); + + mutex_lock(&uart_info->sleep_state_lock); + + if (!uart_info->hu) { + dev_err(MAIN_DEV, "Hci uart struct is not allocated\n"); + goto unlock; + } + + if (chip_on) { + if (!uart_info->suspend_blocked) { + uart_info->suspend_blocked = true; + pm_qos_update_request(&uart_info->pm_qos_latency, + CG2900_PM_QOS_LATENCY); + } + if (uart_info->sleep_state != CHIP_POWERED_DOWN) { + dev_err(MAIN_DEV, "Chip is already powered up (%d)\n", + uart_info->sleep_state); + goto unlock; + } + + if (cg2900_enable_regulator(uart_info)) + goto unlock; + + if (pf_data->enable_chip) { + pf_data->enable_chip(dev); + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_AWAKE\n"); + uart_info->sleep_state = CHIP_AWAKE; + } + + (void)hci_uart_set_baudrate(uart_info->hu, uart_baudrate); + + hci_uart_flow_ctrl(uart_info->hu, FLOW_ON); + hci_uart_set_break(uart_info->hu, BREAK_OFF); + } else { + /* Turn off the chip.*/ + switch (uart_info->sleep_state) { + case CHIP_AWAKE: + break; + case CHIP_FALLING_ASLEEP: + hci_uart_set_break(uart_info->hu, BREAK_OFF); + break; + case CHIP_SUSPENDED: + case CHIP_ASLEEP: + unset_cts_irq(uart_info); + enable_uart_pins(uart_info); + break; + default: + break; + } + + if (uart_info->suspend_blocked) { + uart_info->suspend_blocked = false; + pm_qos_update_request(&uart_info->pm_qos_latency, + PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE); + } + + if (pf_data->disable_chip) { + pf_data->disable_chip(dev); + dev_dbg(MAIN_DEV, + "New sleep_state: CHIP_POWERED_DOWN\n"); + uart_info->sleep_state = CHIP_POWERED_DOWN; + } + + cg2900_disable_regulator(uart_info); + /* + * Setting baud rate to 0 will tell UART driver to shut off its + * clocks. + */ + (void)hci_uart_set_baudrate(uart_info->hu, ZERO_BAUD_RATE); + + spin_lock_bh(&uart_info->rx_skb_lock); + if (uart_info->rx_skb) { + /* + * Reset the uart_info state so that + * next packet can be handled + * correctly by driver. + */ + dev_dbg(MAIN_DEV, "Power off in the middle of data receiving?" + "Reseting state machine.\n"); + kfree_skb(uart_info->rx_skb); + uart_info->rx_skb = NULL; + uart_info->rx_state = W4_PACKET_TYPE; + uart_info->rx_count = 0; + } + spin_unlock_bh(&uart_info->rx_skb_lock); + } + +unlock: + mutex_unlock(&(uart_info->sleep_state_lock)); +} + +/** + * uart_chip_startup_finished() - CG2900 startup finished. + * @dev: Transport device information. + */ +static void uart_chip_startup_finished(struct cg2900_chip_dev *dev) +{ + struct uart_info *uart_info = dev_get_drvdata(dev->dev); + unsigned long timeout_jiffies = get_sleep_timeout(uart_info); + + /* Schedule work to put the chip and transport to sleep. */ + if (timeout_jiffies) + queue_delayed_work(uart_info->wq, &uart_info->sleep_work.work, + timeout_jiffies); +} +/** + * uart_close() - Close the CG2900 UART for data transfers. + * @dev: Transport device information. + * + * Returns: + * 0 if there is no error. + */ +static int uart_close(struct cg2900_chip_dev *dev) +{ + /* The chip is already shut down. Power off the chip. */ + uart_set_chip_power(dev, false); + return 0; +} + +/** + * send_skb_to_core() - Sends packet received from UART to CG2900 Core. + * @skb: Received data packet. + * + * This function checks if UART is waiting for Command complete event, + * see set_baud_rate. + * If it is waiting it checks if it is the expected packet and the status. + * If not is passes the packet to CG2900 Core. + */ +static void send_skb_to_core(struct uart_info *uart_info, struct sk_buff *skb) +{ + u8 status; + + if (!skb) { + dev_err(MAIN_DEV, "send_skb_to_core: Received NULL as skb\n"); + return; + } + + if (BAUD_WAITING == uart_info->baud_rate_state) { + /* + * Should only really be one packet received now: + * the CmdComplete for the SetBaudrate command + * Let's see if this is the packet we are waiting for. + */ + if (!is_bt_cmd_complete_no_param(skb, + CG2900_BT_OP_VS_SET_BAUD_RATE, &status)) { + /* + * Received other event. Should not really happen, + * but pass the data to CG2900 Core anyway. + */ + dev_dbg(MAIN_DEV, "Sending packet to CG2900 Core while " + "waiting for BaudRate CmdComplete\n"); + uart_info->chip_dev.c_cb.data_from_chip + (&uart_info->chip_dev, skb); + return; + } + + /* + * We have received complete event for our baud rate + * change command + */ + if (HCI_BT_ERROR_NO_ERROR == status) { + dev_dbg(MAIN_DEV, "Received baud rate change complete " + "event OK\n"); + dev_dbg(MAIN_DEV, + "New baud_rate_state: BAUD_SUCCESS\n"); + uart_info->baud_rate_state = BAUD_SUCCESS; + } else { + dev_err(MAIN_DEV, + "Received baud rate change complete event " + "with status 0x%X\n", status); + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_FAIL\n"); + uart_info->baud_rate_state = BAUD_FAIL; + } + wake_up_all(&uart_wait_queue); + kfree_skb(skb); + } else if (BAUD_SENDING_RESET == uart_info->baud_rate_state) { + /* + * Should only really be one packet received now: + * the CmdComplete for the Reset command + * Let's see if this is the packet we are waiting for. + */ + if (!is_bt_cmd_complete_no_param(skb, HCI_OP_RESET, &status)) { + /* + * Received other event. Should not really happen, + * but pass the data to CG2900 Core anyway. + */ + dev_dbg(MAIN_DEV, "Sending packet to CG2900 Core while " + "waiting for Reset CmdComplete\n"); + uart_info->chip_dev.c_cb.data_from_chip + (&uart_info->chip_dev, skb); + return; + } + + /* + * We have received complete event for our baud rate + * change command + */ + if (HCI_BT_ERROR_NO_ERROR == status) { + dev_dbg(MAIN_DEV, + "Received HCI reset complete event OK\n"); + /* + * Go back to BAUD_IDLE since this was not really + * baud rate change but just a preparation of the chip + * to be ready to receive commands. + */ + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_IDLE\n"); + uart_info->baud_rate_state = BAUD_IDLE; + } else { + dev_err(MAIN_DEV, + "Received HCI reset complete event with " + "status 0x%X", status); + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_FAIL\n"); + uart_info->baud_rate_state = BAUD_FAIL; + } + wake_up_all(&uart_wait_queue); + kfree_skb(skb); + } else { + /* Just pass data to CG2900 Core */ + uart_info->chip_dev.c_cb.data_from_chip + (&uart_info->chip_dev, skb); + } +} + +/** + * check_data_len() - Check number of bytes to receive. + * @len: Number of bytes left to receive. + */ +static void check_data_len(struct uart_info *uart_info, int len) +{ + /* First get number of bytes left in the sk_buffer */ + register int room = skb_tailroom(uart_info->rx_skb); + + if (!len) { + /* No data left to receive. Transmit to CG2900 Core */ + send_skb_to_core(uart_info, uart_info->rx_skb); + } else if (len > room) { + dev_err(MAIN_DEV, "Data length is too large (%d > %d)\n", + len, room); + kfree_skb(uart_info->rx_skb); + } else { + /* + * "Normal" case. Switch to data receiving state and store + * data length. + */ + uart_info->rx_state = W4_DATA; + uart_info->rx_count = len; + return; + } + + uart_info->rx_state = W4_PACKET_TYPE; + uart_info->rx_skb = NULL; + uart_info->rx_count = 0; +} + +/** + * work_restart_sleep() - Cancel pending sleep_work, wake-up driver and + * schedule new sleep_work in a work context. + * @work: work which needs to be done. + */ +static void work_restart_sleep(struct work_struct *work) +{ + struct uart_work_struct *current_work = + container_of(work, struct uart_work_struct, work); + struct uart_info *uart_info = (struct uart_info *)current_work->data; + unsigned long timeout_jiffies = get_sleep_timeout(uart_info); + + spin_lock_bh(&(uart_info->transmission_lock)); + uart_info->rx_in_progress = false; + spin_unlock_bh(&(uart_info->transmission_lock)); + + /* Cancel pending sleep work if there is any. */ + cancel_delayed_work_sync(&uart_info->sleep_work.work); + + wake_up_chip(uart_info); + + spin_lock_bh(&(uart_info->transmission_lock)); + /* + * If there are no ongoing transfers schedule the sleep work. + */ + if (!(uart_info->tx_in_progress) && timeout_jiffies) + queue_delayed_work(uart_info->wq, + &uart_info->sleep_work.work, + timeout_jiffies); + spin_unlock_bh(&(uart_info->transmission_lock)); +} + +/** + * cg2900_hu_receive() - Handles received UART data. + * @data: Data received + * @count: Number of bytes received + * + * The cg2900_hu_receive() function handles received UART data and puts it + * together to one complete packet. + * + * Returns: + * Number of bytes not handled, i.e. 0 = no error. + */ +static int cg2900_hu_receive(struct hci_uart *hu, + void *data, int count) +{ + const u8 *r_ptr; + u8 *w_ptr; + int len; + struct hci_event_hdr *evt; + struct hci_acl_hdr *acl; + union fm_leg_evt_or_irq *fm; + struct gnss_hci_hdr *gnss; + struct uart_info *uart_info = dev_get_drvdata(hu->proto->dev); + u8 *tmp; + + r_ptr = (const u8 *)data; + + spin_lock_bh(&(uart_info->transmission_lock)); + /* Mark that there is an ongoing transfer. */ + uart_info->rx_in_progress = true; + spin_unlock_bh(&(uart_info->transmission_lock)); + + /* Cancel pending sleep work if there is any. */ + cancel_delayed_work(&uart_info->sleep_work.work); + + if (uart_debug) + print_hex_dump_bytes(NAME " RX:\t", DUMP_PREFIX_NONE, + data, count); + + spin_lock_bh(&uart_info->rx_skb_lock); + + /* Continue while there is data left to handle */ + while (count) { + /* + * If we have already received a packet we know how many bytes + * there are left. + */ + if (!uart_info->rx_count) + goto check_h4_header; + + /* First copy received data into the skb_rx */ + len = min_t(unsigned int, uart_info->rx_count, count); + memcpy(skb_put(uart_info->rx_skb, len), r_ptr, len); + /* Update counters from the length and step the data pointer */ + uart_info->rx_count -= len; + count -= len; + r_ptr += len; + + if (uart_info->rx_count) + /* + * More data to receive to current packet. Break and + * wait for next data on the UART. + */ + break; + + /* Handle the different states */ + tmp = uart_info->rx_skb->data + CG2900_SKB_RESERVE; + switch (uart_info->rx_state) { + case W4_DATA: + /* + * Whole data packet has been received. + * Transmit it to CG2900 Core. + */ + send_skb_to_core(uart_info, uart_info->rx_skb); + + uart_info->rx_state = W4_PACKET_TYPE; + uart_info->rx_skb = NULL; + continue; + + case W4_EVENT_HDR: + evt = (struct hci_event_hdr *)tmp; + check_data_len(uart_info, evt->plen); + /* Header read. Continue with next bytes */ + continue; + + case W4_ACL_HDR: + acl = (struct hci_acl_hdr *)tmp; + check_data_len(uart_info, le16_to_cpu(acl->dlen)); + /* Header read. Continue with next bytes */ + continue; + + case W4_FM_RADIO_HDR: + fm = (union fm_leg_evt_or_irq *)tmp; + check_data_len(uart_info, fm->param_length); + /* Header read. Continue with next bytes */ + continue; + + case W4_GNSS_HDR: + gnss = (struct gnss_hci_hdr *)tmp; + check_data_len(uart_info, le16_to_cpu(gnss->plen)); + /* Header read. Continue with next bytes */ + continue; + + default: + dev_err(MAIN_DEV, + "Bad state indicating memory overwrite " + "(0x%X)\n", (u8)(uart_info->rx_state)); + break; + } + +check_h4_header: + /* Check which H:4 packet this is and update RX states */ + if (*r_ptr == HCI_BT_EVT_H4_CHANNEL) { + uart_info->rx_state = W4_EVENT_HDR; + uart_info->rx_count = HCI_BT_EVT_HDR_SIZE; + } else if (*r_ptr == HCI_BT_ACL_H4_CHANNEL) { + uart_info->rx_state = W4_ACL_HDR; + uart_info->rx_count = HCI_BT_ACL_HDR_SIZE; + } else if (*r_ptr == HCI_FM_RADIO_H4_CHANNEL) { + uart_info->rx_state = W4_FM_RADIO_HDR; + uart_info->rx_count = HCI_FM_RADIO_HDR_SIZE; + } else if (*r_ptr == HCI_GNSS_H4_CHANNEL) { + uart_info->rx_state = W4_GNSS_HDR; + uart_info->rx_count = HCI_GNSS_HDR_SIZE; + } else { + dev_err(MAIN_DEV, "Unknown HCI packet type 0x%X\n", + (u8)*r_ptr); + r_ptr++; + count--; + continue; + } + + /* + * Allocate packet. We do not yet know the size and therefore + * allocate max size. + */ + uart_info->rx_skb = alloc_rx_skb(RX_SKB_MAX_SIZE, GFP_ATOMIC); + if (!uart_info->rx_skb) { + dev_err(MAIN_DEV, + "Can't allocate memory for new packet\n"); + uart_info->rx_state = W4_PACKET_TYPE; + uart_info->rx_count = 0; + + spin_lock_bh(&(uart_info->transmission_lock)); + uart_info->rx_in_progress = false; + spin_unlock_bh(&(uart_info->transmission_lock)); + + spin_unlock_bh(&uart_info->rx_skb_lock); + return 0; + } + + /* Write the H:4 header first in the sk_buffer */ + w_ptr = skb_put(uart_info->rx_skb, 1); + *w_ptr = *r_ptr; + + /* First byte (H4 header) read. Goto next byte */ + r_ptr++; + count--; + } + + (void)queue_work(uart_info->wq, &uart_info->restart_sleep_work.work); + + spin_unlock_bh(&uart_info->rx_skb_lock); + return count; +} + +/** + * cg2900_hu_open() - Called when UART line discipline changed to N_HCI. + * @hu: Pointer to associated Hci uart structure. + * + * Returns: + * 0 if there is no error. + * Errors from cg2900_register_trans_driver. + */ +static int cg2900_hu_open(struct hci_uart *hu) +{ + int err; + struct uart_info *uart_info = dev_get_drvdata(hu->proto->dev); + + if (!uart_info) + return -EACCES; + + dev_info(MAIN_DEV, "UART opened\n"); + + skb_queue_head_init(&uart_info->tx_queue); + + uart_info->hu = hu; + + /* Tell CG2900 Core that UART is connected */ + err = cg2900_register_trans_driver(&uart_info->chip_dev); + if (err) + dev_err(MAIN_DEV, "Could not register transport driver (%d)\n", + err); + + if (hu->tty->ops->tiocmget && hu->tty->ops->break_ctl) + uart_info->sleep_allowed = true; + else { + dev_err(MAIN_DEV, "Sleep mode not available\n"); + uart_info->sleep_allowed = false; + } + + return err; + +} + +/** + * cg2900_hu_close() - Close UART tty. + * @hu: Pointer to associated hci_uart structure. + * + * The uart_tty_close() function is called when the line discipline is changed + * to something else, the TTY is closed, or the TTY detects a hangup. + */ +static int cg2900_hu_close(struct hci_uart *hu) +{ + int err; + struct uart_info *uart_info = dev_get_drvdata(hu->proto->dev); + + + BUG_ON(!uart_info); + BUG_ON(!uart_info->wq); + + /* Purge any stored sk_buffers */ + skb_queue_purge(&uart_info->tx_queue); + + spin_lock_bh(&uart_info->rx_skb_lock); + if (uart_info->rx_skb) { + kfree_skb(uart_info->rx_skb); + uart_info->rx_skb = NULL; + } + spin_unlock_bh(&uart_info->rx_skb_lock); + + dev_info(MAIN_DEV, "UART closed\n"); + err = create_work_item(uart_info, work_hw_deregistered); + if (err) + dev_err(MAIN_DEV, "Failed to create work item (%d) " + "work_hw_deregistered\n", err); + + uart_info->hu = NULL; + + return 0; +} + +/** + * cg2900_hu_dequeue() - Get new skbuff. + * @hu: Pointer to associated hci_uart structure. + * + * The uart_tty_close() function is called when the line discipline is changed + * to something else, the TTY is closed, or the TTY detects a hangup. + */ +static struct sk_buff *cg2900_hu_dequeue(struct hci_uart *hu) +{ + struct sk_buff *skb; + struct uart_info *uart_info = dev_get_drvdata(hu->proto->dev); + unsigned long timeout_jiffies = get_sleep_timeout(uart_info); + + spin_lock_bh(&(uart_info->transmission_lock)); + + skb = skb_dequeue(&uart_info->tx_queue); + + if (!skb) + uart_info->tx_in_progress = false; + + /* + * If there are no ongoing transfers schedule the sleep work. + */ + if (!(uart_info->rx_in_progress) && timeout_jiffies && !skb) + queue_delayed_work(uart_info->wq, + &uart_info->sleep_work.work, + timeout_jiffies); + + spin_unlock_bh(&(uart_info->transmission_lock)); + + if (BAUD_SENDING == uart_info->baud_rate_state && !skb) + finish_setting_baud_rate(hu); + /* + * If it's set baud rate cmd set correct baud state and after + * sending is finished inform the tty driver about the new + * baud rate. + */ + if ((BAUD_START == uart_info->baud_rate_state) && + skb && (is_set_baud_rate_cmd(skb->data))) { + dev_dbg(MAIN_DEV, "UART set baud rate cmd found\n"); + uart_info->baud_rate_state = BAUD_SENDING; + } + + if (uart_debug && skb) + print_hex_dump_bytes(NAME " TX:\t", DUMP_PREFIX_NONE, + skb->data, skb->len); + + return skb; +} + +/** + * cg2900_hu_flush() - Flush buffers. + * @hu: Pointer to associated hci_uart structure. + * + */ +static int cg2900_hu_flush(struct hci_uart *hu) +{ + struct uart_info *uart_info = dev_get_drvdata(hu->proto->dev); + + dev_dbg(MAIN_DEV, "ui %p", uart_info); + skb_queue_purge(&uart_info->tx_queue); + return 0; +} + +/** + * cg2900_uart_probe() - Initialize CG2900 UART resources. + * @pdev: Platform device. + * + * This function initializes the module and registers to the UART framework. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * -ECHILD for failed work queue creation. + * Error codes generated by tty_register_ldisc. + */ +static int __devinit cg2900_uart_probe(struct platform_device *pdev) +{ + int err = 0; + struct uart_info *uart_info; + struct hci_uart_proto *p; + struct resource *resource; + + pr_debug("cg2900_uart_probe"); + + uart_info = kzalloc(sizeof(*uart_info), GFP_KERNEL); + if (!uart_info) { + pr_err("Couldn't allocate uart_info"); + return -ENOMEM; + } + + uart_info->sleep_state = CHIP_POWERED_DOWN; + mutex_init(&(uart_info->sleep_state_lock)); + + spin_lock_init(&(uart_info->transmission_lock)); + spin_lock_init(&(uart_info->rx_skb_lock)); + + uart_info->chip_dev.t_cb.open = uart_open; + uart_info->chip_dev.t_cb.close = uart_close; + uart_info->chip_dev.t_cb.write = uart_write; + uart_info->chip_dev.t_cb.set_chip_power = uart_set_chip_power; + uart_info->chip_dev.t_cb.chip_startup_finished = + uart_chip_startup_finished; + uart_info->chip_dev.pdev = pdev; + uart_info->chip_dev.dev = &pdev->dev; + uart_info->chip_dev.t_data = uart_info; + + pm_qos_add_request(&uart_info->pm_qos_latency, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE); + + resource = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "cts_irq"); + if (!resource) { + dev_err(&pdev->dev, "CTS IRQ does not exist\n"); + err = -EINVAL; + goto error_handling_free; + } + uart_info->cts_irq = resource->start; + + resource = platform_get_resource_byname(pdev, IORESOURCE_IO, + "cts_gpio"); + if (!resource) { + dev_err(&pdev->dev, "CTS GPIO does not exist\n"); + err = -EINVAL; + goto error_handling_free; + } + uart_info->cts_gpio = resource->start; + + /* Init UART TX work queue */ + uart_info->wq = create_singlethread_workqueue(UART_WQ_NAME); + if (!uart_info->wq) { + dev_err(MAIN_DEV, "Could not create workqueue\n"); + err = -ECHILD; /* No child processes */ + goto error_handling_free; + } + + /* Initialize sleep work data */ + uart_info->sleep_work.data = uart_info; + INIT_DELAYED_WORK(&uart_info->sleep_work.work, set_chip_sleep_mode); + + /* Initialize wake-up work data */ + uart_info->wakeup_work.data = uart_info; + INIT_WORK(&uart_info->wakeup_work.work, work_wake_up_chip); + + /* Initialize after_receive work data */ + uart_info->restart_sleep_work.data = uart_info; + INIT_WORK(&uart_info->restart_sleep_work.work, work_restart_sleep); + + uart_info->dev = &pdev->dev; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) { + dev_err(MAIN_DEV, "cg2900_uart_probe: Could not allocate p\n"); + goto error_handling_wq; + } + + p->dev = uart_info->dev; + p->id = HCI_UART_STE; + p->open = &cg2900_hu_open; + p->close = &cg2900_hu_close; + p->recv = &cg2900_hu_receive; + p->dequeue = &cg2900_hu_dequeue; + p->flush = &cg2900_hu_flush; + + dev_set_drvdata(uart_info->dev, (void *)uart_info); + + err = hci_uart_register_proto(p); + if (err) { + dev_err(MAIN_DEV, "cg2900_uart_probe: Can not register " + "protocol\n"); + kfree(p); + goto error_handling_wq; + } + + goto finished; + +error_handling_wq: + destroy_workqueue(uart_info->wq); +error_handling_free: + kfree(uart_info); + uart_info = NULL; +finished: + return err; +} + +/** + * cg2900_uart_remove() - Release CG2900 UART resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * Error codes generated by tty_unregister_ldisc. + */ +static int __devexit cg2900_uart_remove(struct platform_device *pdev) +{ + struct uart_info *uart_info = dev_get_drvdata(&pdev->dev); + + pr_debug("cg2900_uart_remove"); + + if (!uart_info) + return -ECHILD; + + if (uart_info->hu) + hci_uart_unregister_proto(uart_info->hu->proto); + + pm_qos_remove_request(&uart_info->pm_qos_latency); + destroy_workqueue(uart_info->wq); + + dev_info(MAIN_DEV, "CG2900 UART removed\n"); + kfree(uart_info); + uart_info = NULL; + return 0; +} + +static struct platform_driver cg2900_uart_driver = { + .driver = { + .name = "cg2900-uart", + .owner = THIS_MODULE, + }, + .probe = cg2900_uart_probe, + .remove = __devexit_p(cg2900_uart_remove), +#ifdef CONFIG_PM + .suspend = cg2900_uart_suspend, + .resume = cg2900_uart_resume +#endif +}; + + +/** + * cg2900_uart_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_uart_init(void) +{ + pr_debug("cg2900_uart_init"); + return platform_driver_register(&cg2900_uart_driver); +} + +/** + * cg2900_uart_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_uart_exit(void) +{ + pr_debug("cg2900_uart_exit"); + platform_driver_unregister(&cg2900_uart_driver); +} + +module_init(cg2900_uart_init); +module_exit(cg2900_uart_exit); + +module_param(uart_default_baud, int, S_IRUGO); +MODULE_PARM_DESC(uart_default_baud, + "Default UART baud rate, e.g. 115200. If not set 115200 will " + "be used."); + +module_param(uart_high_baud, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(uart_high_baud, + "High speed UART baud rate, e.g. 4000000. If not set 3000000 " + "will be used."); + +module_param(uart_debug, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(uart_debug, "Enable/Disable debug. 0 means Debug disabled."); +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ST-Ericsson CG2900 UART Driver"); diff --git a/drivers/staging/cg2900/bluetooth/hci_ldisc.c b/drivers/staging/cg2900/bluetooth/hci_ldisc.c new file mode 100644 index 00000000000..0ceb5e74255 --- /dev/null +++ b/drivers/staging/cg2900/bluetooth/hci_ldisc.c @@ -0,0 +1,657 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * This file is a staging solution and shall be integrated into + * /drivers/bluetooth/hci_ldisc.c. + * + * Original hci_ldisc.c file: + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> + */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/poll.h> + +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/signal.h> +#include <linux/ioctl.h> +#include <linux/skbuff.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "hci_uart.h" + +#define VERSION "2.3" + +#define TTY_BREAK_ON (-1) +#define TTY_BREAK_OFF (0) + +static bool reset; + +static struct hci_uart_proto *hup[HCI_UART_MAX_PROTO]; + +int cg2900_hci_uart_register_proto(struct hci_uart_proto *p) +{ + if (p->id >= HCI_UART_MAX_PROTO) + return -EINVAL; + + if (hup[p->id]) + return -EEXIST; + + hup[p->id] = p; + + return 0; +} + +int cg2900_hci_uart_unregister_proto(struct hci_uart_proto *p) +{ + if (p->id >= HCI_UART_MAX_PROTO) + return -EINVAL; + + if (!hup[p->id]) + return -EINVAL; + + hup[p->id] = NULL; + + return 0; +} + +static struct hci_uart_proto *hci_uart_get_proto(unsigned int id) +{ + if (id >= HCI_UART_MAX_PROTO) + return NULL; + + return hup[id]; +} + +static inline void hci_uart_tx_complete(struct hci_uart *hu, int pkt_type) +{ + struct hci_dev *hdev = hu->hdev; + + if (!hdev) + return; + + /* Update HCI stat counters */ + switch (pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } +} + +static inline struct sk_buff *hci_uart_dequeue(struct hci_uart *hu) +{ + struct sk_buff *skb = hu->tx_skb; + + if (!skb) + skb = hu->proto->dequeue(hu); + else + hu->tx_skb = NULL; + + return skb; +} + +int cg2900_hci_uart_tx_wakeup(struct hci_uart *hu) +{ + struct tty_struct *tty = hu->tty; + struct hci_dev *hdev = hu->hdev; + struct sk_buff *skb; + + if (test_and_set_bit(HCI_UART_SENDING, &hu->tx_state)) { + set_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + return 0; + } + + BT_DBG(""); + +restart: + clear_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + + while ((skb = hci_uart_dequeue(hu))) { + int len; + + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + len = tty->ops->write(tty, skb->data, skb->len); + if (hdev) + hdev->stat.byte_tx += len; + + skb_pull(skb, len); + if (skb->len) { + hu->tx_skb = skb; + break; + } + + hci_uart_tx_complete(hu, bt_cb(skb)->pkt_type); + kfree_skb(skb); + } + + if (test_bit(HCI_UART_TX_WAKEUP, &hu->tx_state)) + goto restart; + + clear_bit(HCI_UART_SENDING, &hu->tx_state); + return 0; +} + +int cg2900_hci_uart_set_break(struct hci_uart *hu, bool break_on) +{ + struct tty_struct *tty = hu->tty; + int state = TTY_BREAK_OFF; + + if (break_on) + state = TTY_BREAK_ON; + + if (tty->ops->break_ctl) + return tty->ops->break_ctl(tty, state); + else + return -EOPNOTSUPP; +} + +void cg2900_hci_uart_flow_ctrl(struct hci_uart *hu, bool flow_on) +{ + if (flow_on) + tty_unthrottle(hu->tty); + else + tty_throttle(hu->tty); +} + +int cg2900_hci_uart_set_baudrate(struct hci_uart *hu, int baud) +{ + struct ktermios old_termios; + struct tty_struct *tty = hu->tty; + + if (!tty->ops->set_termios) + return -EOPNOTSUPP; + + mutex_lock(&(tty->termios_mutex)); + /* Start by storing the old termios. */ + memcpy(&old_termios, tty->termios, sizeof(old_termios)); + + tty_encode_baud_rate(tty, baud, baud); + + /* Finally inform the driver */ + tty->ops->set_termios(tty, &old_termios); + + mutex_unlock(&(tty->termios_mutex)); + + return 0; +} + +int cg2900_hci_uart_tiocmget(struct hci_uart *hu) +{ + struct tty_struct *tty = hu->tty; + + if (!tty->ops->tiocmget || !hu->fd) + return -EOPNOTSUPP; + + return tty->ops->tiocmget(tty); +} + +void cg2900_hci_uart_flush_buffer(struct hci_uart *hu) +{ + tty_driver_flush_buffer(hu->tty); +} + +int cg2900_hci_uart_chars_in_buffer(struct hci_uart *hu) +{ + return tty_chars_in_buffer(hu->tty); +} + +/* ------- Interface to HCI layer ------ */ +/* Initialize device */ +static int hci_uart_open(struct hci_dev *hdev) +{ + BT_DBG("%s %p", hdev->name, hdev); + + /* Nothing to do for UART driver */ + + set_bit(HCI_RUNNING, &hdev->flags); + + return 0; +} + +/* Reset device */ +static int hci_uart_flush(struct hci_dev *hdev) +{ + struct hci_uart *hu = (struct hci_uart *) hdev->driver_data; + struct tty_struct *tty = hu->tty; + + BT_DBG("hdev %p tty %p", hdev, tty); + + if (hu->tx_skb) { + kfree_skb(hu->tx_skb); hu->tx_skb = NULL; + } + + /* Flush any pending characters in the driver and discipline. */ + tty_ldisc_flush(tty); + tty_driver_flush_buffer(tty); + + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + hu->proto->flush(hu); + + return 0; +} + +/* Close device */ +static int hci_uart_close(struct hci_dev *hdev) +{ + BT_DBG("hdev %p", hdev); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + hci_uart_flush(hdev); + hdev->flush = NULL; + return 0; +} + +/* Send frames from HCI layer */ +static int hci_uart_send_frame(struct sk_buff *skb) +{ + struct hci_dev* hdev = (struct hci_dev *) skb->dev; + struct hci_uart *hu; + + if (!hdev) { + BT_ERR("Frame for unknown device (hdev=NULL)"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + hu = (struct hci_uart *) hdev->driver_data; + + BT_DBG("%s: type %d len %d", hdev->name, bt_cb(skb)->pkt_type, + skb->len); + + hu->proto->enqueue(hu, skb); + + hci_uart_tx_wakeup(hu); + + return 0; +} + +static void hci_uart_destruct(struct hci_dev *hdev) +{ + if (!hdev) + return; + + BT_DBG("%s", hdev->name); + kfree(hdev->driver_data); +} + +/* ------ LDISC part ------ */ +/* hci_uart_tty_open + * + * Called when line discipline changed to HCI_UART. + * + * Arguments: + * tty pointer to tty info structure + * Return Value: + * 0 if success, otherwise error code + */ +static int hci_uart_tty_open(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *) tty->disc_data; + + BT_DBG("tty %p", tty); + + /* FIXME: This btw is bogus, nothing requires the old ldisc to clear + the pointer */ + if (hu) + return -EEXIST; + + /* Error if the tty has no write op instead of leaving an exploitable + hole */ + if (tty->ops->write == NULL) + return -EOPNOTSUPP; + + hu = kzalloc(sizeof(struct hci_uart), GFP_KERNEL); + if (!hu) { + BT_ERR("Can't allocate control structure"); + return -ENFILE; + } + + tty->disc_data = hu; + hu->tty = tty; + tty->receive_room = 65536; + + spin_lock_init(&hu->rx_lock); + + /* Flush any pending characters in the driver and line discipline. */ + + /* FIXME: why is this needed. Note don't use ldisc_ref here as the + open path is before the ldisc is referencable */ + + if (tty->ldisc->ops->flush_buffer) + tty->ldisc->ops->flush_buffer(tty); + tty_driver_flush_buffer(tty); + + return 0; +} + +/* hci_uart_tty_close() + * + * Called when the line discipline is changed to something + * else, the tty is closed, or the tty detects a hangup. + */ +static void hci_uart_tty_close(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + BT_DBG("tty %p", tty); + + /* Detach from the tty */ + tty->disc_data = NULL; + + if (hu) { + struct hci_dev *hdev = hu->hdev; + + if (hdev) + hci_uart_close(hdev); + + if (test_and_clear_bit(HCI_UART_PROTO_SET, &hu->flags)) { + hu->proto->close(hu); + if (hdev) { + hci_unregister_dev(hdev); + hci_free_dev(hdev); + } + } + } +} + +/* hci_uart_tty_wakeup() + * + * Callback for transmit wakeup. Called when low level + * device driver can accept more send data. + * + * Arguments: tty pointer to associated tty instance data + * Return Value: None + */ +static void hci_uart_tty_wakeup(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + BT_DBG(""); + + if (!hu) + return; + + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + + if (tty != hu->tty) + return; + + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + hci_uart_tx_wakeup(hu); +} + +/* hci_uart_tty_receive() + * + * Called by tty low level driver when receive data is + * available. + * + * Arguments: tty pointer to tty isntance data + * data pointer to received data + * flags pointer to flags for data + * count count of received data in bytes + * + * Return Value: None + */ +static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data, + char *flags, int count) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + if (!hu || tty != hu->tty) + return; + + if (!test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return; + + spin_lock(&hu->rx_lock); + hu->proto->recv(hu, (void *) data, count); + if (hu->hdev) + hu->hdev->stat.byte_rx += count; + spin_unlock(&hu->rx_lock); + + tty_unthrottle(tty); +} + +static int hci_uart_register_dev(struct hci_uart *hu) +{ + struct hci_dev *hdev; + + BT_DBG(""); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + hu->hdev = hdev; + + hdev->bus = HCI_UART; + hdev->driver_data = hu; + + hdev->open = hci_uart_open; + hdev->close = hci_uart_close; + hdev->flush = hci_uart_flush; + hdev->send = hci_uart_send_frame; + hdev->destruct = hci_uart_destruct; + + hdev->owner = THIS_MODULE; + + if (!reset) + set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); + + if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags)) + set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks); + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + +static int hci_uart_set_proto(struct hci_uart *hu, int id) +{ + struct hci_uart_proto *p; + int err; + + p = hci_uart_get_proto(id); + if (!p) + return -EPROTONOSUPPORT; + + hu->proto = p; + + err = p->open(hu); + if (err) + return err; + + /* + * Protocol might register hdev by itself. + * In that case, there is no need to register it here. + */ + if (!hu->proto->register_hci_dev) + return 0; + + err = hci_uart_register_dev(hu); + if (err) { + p->close(hu); + return err; + } + + return 0; +} + +/* hci_uart_tty_ioctl() + * + * Process IOCTL system call for the tty device. + * + * Arguments: + * + * tty pointer to tty instance data + * file pointer to open file object for device + * cmd IOCTL command code + * arg argument for IOCTL call (cmd dependent) + * + * Return Value: Command dependent + */ +static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + struct hci_uart *hu = (void *)tty->disc_data; + int err = 0; + + BT_DBG(""); + + /* Verify the status of the device */ + if (!hu) + return -EBADF; + + switch (cmd) { + case HCIUARTSETPROTO: + if (!test_and_set_bit(HCI_UART_PROTO_SET, &hu->flags)) { + err = hci_uart_set_proto(hu, arg); + if (err) { + clear_bit(HCI_UART_PROTO_SET, &hu->flags); + return err; + } + /* Keep file descriptor.*/ + hu->fd = file; + } else + return -EBUSY; + break; + + case HCIUARTGETPROTO: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return hu->proto->id; + return -EUNATCH; + + case HCIUARTGETDEVICE: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) { + if (hu->hdev) + return hu->hdev->id; + else + return -ENOMSG; + } + return -EUNATCH; + + case HCIUARTSETFLAGS: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return -EBUSY; + hu->hdev_flags = arg; + break; + + case HCIUARTGETFLAGS: + return hu->hdev_flags; + + default: + err = n_tty_ioctl_helper(tty, file, cmd, arg); + break; + }; + + return err; +} + +/* + * We don't provide read/write/poll interface for user space. + */ +static ssize_t hci_uart_tty_read(struct tty_struct *tty, struct file *file, + unsigned char __user *buf, size_t nr) +{ + return 0; +} + +static ssize_t hci_uart_tty_write(struct tty_struct *tty, struct file *file, + const unsigned char *data, size_t count) +{ + return 0; +} + +static unsigned int hci_uart_tty_poll(struct tty_struct *tty, + struct file *filp, poll_table *wait) +{ + return 0; +} + +static int __init cg2900_hci_uart_init(void) +{ + static struct tty_ldisc_ops hci_uart_ldisc; + int err; + + BT_INFO("HCI UART driver ver %s", VERSION); + + /* Register the tty discipline */ + + memset(&hci_uart_ldisc, 0, sizeof(hci_uart_ldisc)); + hci_uart_ldisc.magic = TTY_LDISC_MAGIC; + hci_uart_ldisc.name = "n_cg2900_hci"; + hci_uart_ldisc.open = hci_uart_tty_open; + hci_uart_ldisc.close = hci_uart_tty_close; + hci_uart_ldisc.read = hci_uart_tty_read; + hci_uart_ldisc.write = hci_uart_tty_write; + hci_uart_ldisc.ioctl = hci_uart_tty_ioctl; + hci_uart_ldisc.poll = hci_uart_tty_poll; + hci_uart_ldisc.receive_buf = hci_uart_tty_receive; + hci_uart_ldisc.write_wakeup = hci_uart_tty_wakeup; + hci_uart_ldisc.owner = THIS_MODULE; + + err = tty_register_ldisc(N_CG2900_HCI, &hci_uart_ldisc); + if (err) { + BT_ERR("HCI line discipline registration failed. (%d)", err); + return err; + } + + return 0; +} + +static void __exit cg2900_hci_uart_exit(void) +{ + int err; + + /* Release tty registration of line discipline */ + err = tty_unregister_ldisc(N_CG2900_HCI); + if (err) + BT_ERR("Can't unregister HCI line discipline (%d)", err); +} + +module_init(cg2900_hci_uart_init); +module_exit(cg2900_hci_uart_exit); + +module_param(reset, bool, 0644); +MODULE_PARM_DESC(reset, "Send HCI reset command on initialization"); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>"); +MODULE_DESCRIPTION("CG2900 Staging Bluetooth HCI UART driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_CG2900_HCI); diff --git a/drivers/staging/cg2900/bluetooth/hci_uart.h b/drivers/staging/cg2900/bluetooth/hci_uart.h new file mode 100644 index 00000000000..23a69519ccd --- /dev/null +++ b/drivers/staging/cg2900/bluetooth/hci_uart.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * This file is a staging solution and shall be integrated into + * /drivers/bluetooth/hci_uart.h. + * + * Original hci_uart.h file: + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> + */ + +/* + * Staging CG2900 Bluetooth HCI UART. Will be replaced by normal N_HCI when + * moved to normal driver folder. + */ +#ifndef N_CG2900_HCI +#define N_CG2900_HCI 23 +#endif /* N_CG2900_HCI */ + +/* Ioctls */ +#define HCIUARTSETPROTO _IOW('U', 200, int) +#define HCIUARTGETPROTO _IOR('U', 201, int) +#define HCIUARTGETDEVICE _IOR('U', 202, int) +#define HCIUARTSETFLAGS _IOW('U', 203, int) +#define HCIUARTGETFLAGS _IOR('U', 204, int) + +/* UART protocols */ +#define HCI_UART_MAX_PROTO 7 + +#define HCI_UART_H4 0 +#define HCI_UART_BCSP 1 +#define HCI_UART_3WIRE 2 +#define HCI_UART_H4DS 3 +#define HCI_UART_LL 4 +#define HCI_UART_ATH3K 5 +#define HCI_UART_STE 6 + +#define HCI_UART_RAW_DEVICE 0 + +/* UART break and flow control parameters */ +#define BREAK_ON true +#define BREAK_OFF false +#define FLOW_ON true +#define FLOW_OFF false + +struct hci_uart; + +struct hci_uart_proto { + unsigned int id; + int (*open)(struct hci_uart *hu); + int (*close)(struct hci_uart *hu); + int (*flush)(struct hci_uart *hu); + int (*recv)(struct hci_uart *hu, void *data, int len); + int (*enqueue)(struct hci_uart *hu, struct sk_buff *skb); + struct sk_buff *(*dequeue)(struct hci_uart *hu); + bool register_hci_dev; + struct device *dev; +}; + +struct hci_uart { + struct tty_struct *tty; + struct hci_dev *hdev; + unsigned long flags; + unsigned long hdev_flags; + + struct hci_uart_proto *proto; + void *priv; + + struct sk_buff *tx_skb; + unsigned long tx_state; + spinlock_t rx_lock; + + struct file *fd; +}; + +/* HCI_UART proto flag bits */ +#define HCI_UART_PROTO_SET 0 + +/* TX states */ +#define HCI_UART_SENDING 1 +#define HCI_UART_TX_WAKEUP 2 + +int cg2900_hci_uart_register_proto(struct hci_uart_proto *p); +int cg2900_hci_uart_unregister_proto(struct hci_uart_proto *p); +int cg2900_hci_uart_tx_wakeup(struct hci_uart *hu); +int cg2900_hci_uart_set_baudrate(struct hci_uart *hu, int baud); +int cg2900_hci_uart_set_break(struct hci_uart *hu, bool break_on); +int cg2900_hci_uart_tiocmget(struct hci_uart *hu); +void cg2900_hci_uart_flush_buffer(struct hci_uart *hu); +void cg2900_hci_uart_flow_ctrl(struct hci_uart *hu, bool flow_on); +int cg2900_hci_uart_chars_in_buffer(struct hci_uart *hu); + +#define hci_uart_register_proto cg2900_hci_uart_register_proto +#define hci_uart_unregister_proto cg2900_hci_uart_unregister_proto +#define hci_uart_tx_wakeup cg2900_hci_uart_tx_wakeup +#define hci_uart_set_baudrate cg2900_hci_uart_set_baudrate +#define hci_uart_set_break cg2900_hci_uart_set_break +#define hci_uart_tiocmget cg2900_hci_uart_tiocmget +#define hci_uart_flush_buffer cg2900_hci_uart_flush_buffer +#define hci_uart_flow_ctrl cg2900_hci_uart_flow_ctrl +#define hci_uart_chars_in_buffer cg2900_hci_uart_chars_in_buffer diff --git a/drivers/staging/cg2900/board-ux500-cg2900.c b/drivers/staging/cg2900/board-ux500-cg2900.c new file mode 100644 index 00000000000..ca3902b4e2d --- /dev/null +++ b/drivers/staging/cg2900/board-ux500-cg2900.c @@ -0,0 +1,366 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com> + * Author: Hemant Gupta <hemant.gupta@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + */ + +#include <asm/mach-types.h> +#include <linux/gpio.h> +#include <linux/gpio/nomadik.h> +#include <linux/ioport.h> +#include <linux/mfd/abx500/ab8500-gpio.h> +#include <linux/platform_device.h> +#include <mach/gpio.h> +#include <mach/id.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> +#include <plat/pincfg.h> + +#include "board-mop500.h" +#include "cg2900.h" +#include "devices-cg2900.h" +#include "pins-db5500.h" +#include "pins-db8500.h" +#include "pins.h" + +#define CG2900_BT_ENABLE_GPIO 170 +#define CG2900_GBF_ENA_RESET_GPIO 171 +#define WLAN_PMU_EN_GPIO 226 +#define WLAN_PMU_EN_GPIO_SNOWBALL 161 +#define WLAN_PMU_EN_GPIO_U9500 AB8500_PIN_GPIO11 +#define CG2900_UX500_BT_CTS_GPIO 0 +#define CG2900_U5500_BT_CTS_GPIO 168 + +enum cg2900_gpio_pull_sleep ux500_cg2900_sleep_gpio[21] = { + CG2900_NO_PULL, /* GPIO 0: PTA_CONFX */ + CG2900_PULL_DN, /* GPIO 1: PTA_STATUS */ + CG2900_NO_PULL, /* GPIO 2: UART_CTSN */ + CG2900_PULL_UP, /* GPIO 3: UART_RTSN */ + CG2900_PULL_UP, /* GPIO 4: UART_TXD */ + CG2900_NO_PULL, /* GPIO 5: UART_RXD */ + CG2900_PULL_DN, /* GPIO 6: IOM_DOUT */ + CG2900_NO_PULL, /* GPIO 7: IOM_FSC */ + CG2900_NO_PULL, /* GPIO 8: IOM_CLK */ + CG2900_NO_PULL, /* GPIO 9: IOM_DIN */ + CG2900_PULL_DN, /* GPIO 10: PWR_REQ */ + CG2900_PULL_DN, /* GPIO 11: HOST_WAKEUP */ + CG2900_PULL_DN, /* GPIO 12: IIS_DOUT */ + CG2900_NO_PULL, /* GPIO 13: IIS_WS */ + CG2900_NO_PULL, /* GPIO 14: IIS_CLK */ + CG2900_NO_PULL, /* GPIO 15: IIS_DIN */ + CG2900_PULL_DN, /* GPIO 16: PTA_FREQ */ + CG2900_PULL_DN, /* GPIO 17: PTA_RF_ACTIVE */ + CG2900_NO_PULL, /* GPIO 18: NotConnected (J6428) */ + CG2900_NO_PULL, /* GPIO 19: EXT_DUTY_CYCLE */ + CG2900_NO_PULL, /* GPIO 20: EXT_FRM_SYNCH */ +}; + +static struct platform_device ux500_cg2900_device = { + .name = "cg2900", +}; + +static struct platform_device ux500_cg2900_chip_device = { + .name = "cg2900-chip", + .dev = { + .parent = &ux500_cg2900_device.dev, + }, +}; + +static struct platform_device ux500_stlc2690_chip_device = { + .name = "stlc2690-chip", + .dev = { + .parent = &ux500_cg2900_device.dev, + }, +}; + +static struct cg2900_platform_data ux500_cg2900_test_platform_data = { + .bus = HCI_VIRTUAL, + .gpio_sleep = ux500_cg2900_sleep_gpio, +}; + +static struct platform_device ux500_cg2900_test_device = { + .name = "cg2900-test", + .dev = { + .parent = &ux500_cg2900_device.dev, + .platform_data = &ux500_cg2900_test_platform_data, + }, +}; + +static struct resource cg2900_uart_resources_pre_v60[] = { + { + .start = CG2900_GBF_ENA_RESET_GPIO, + .end = CG2900_GBF_ENA_RESET_GPIO, + .flags = IORESOURCE_IO, + .name = "gbf_ena_reset", + }, + { + .start = CG2900_BT_ENABLE_GPIO, + .end = CG2900_BT_ENABLE_GPIO, + .flags = IORESOURCE_IO, + .name = "bt_enable", + }, + { + .start = CG2900_UX500_BT_CTS_GPIO, + .end = CG2900_UX500_BT_CTS_GPIO, + .flags = IORESOURCE_IO, + .name = "cts_gpio", + }, + { + .start = NOMADIK_GPIO_TO_IRQ(CG2900_UX500_BT_CTS_GPIO), + .end = NOMADIK_GPIO_TO_IRQ(CG2900_UX500_BT_CTS_GPIO), + .flags = IORESOURCE_IRQ, + .name = "cts_irq", + }, +}; + +static struct resource cg2900_uart_resources_u5500[] = { + { + .start = CG2900_U5500_BT_CTS_GPIO, + .end = CG2900_U5500_BT_CTS_GPIO, + .flags = IORESOURCE_IO, + .name = "cts_gpio", + }, + { + .start = NOMADIK_GPIO_TO_IRQ(CG2900_U5500_BT_CTS_GPIO), + .end = NOMADIK_GPIO_TO_IRQ(CG2900_U5500_BT_CTS_GPIO), + .flags = IORESOURCE_IRQ, + .name = "cts_irq", + }, +}; + +static struct resource cg2900_uart_resources_u8500[] = { + { + .start = CG2900_GBF_ENA_RESET_GPIO, + .end = CG2900_GBF_ENA_RESET_GPIO, + .flags = IORESOURCE_IO, + .name = "gbf_ena_reset", + }, + { + .start = WLAN_PMU_EN_GPIO, + .end = WLAN_PMU_EN_GPIO, + .flags = IORESOURCE_IO, + .name = "pmu_en", + }, + { + .start = CG2900_UX500_BT_CTS_GPIO, + .end = CG2900_UX500_BT_CTS_GPIO, + .flags = IORESOURCE_IO, + .name = "cts_gpio", + }, + { + .start = NOMADIK_GPIO_TO_IRQ(CG2900_UX500_BT_CTS_GPIO), + .end = NOMADIK_GPIO_TO_IRQ(CG2900_UX500_BT_CTS_GPIO), + .flags = IORESOURCE_IRQ, + .name = "cts_irq", + }, +}; + +static struct resource cg2900_uart_resources_snowball[] = { + { + .start = CG2900_GBF_ENA_RESET_GPIO, + .end = CG2900_GBF_ENA_RESET_GPIO, + .flags = IORESOURCE_IO, + .name = "gbf_ena_reset", + }, + { + .start = WLAN_PMU_EN_GPIO_SNOWBALL, + .end = WLAN_PMU_EN_GPIO_SNOWBALL, + .flags = IORESOURCE_IO, + .name = "pmu_en", + }, + { + .start = CG2900_UX500_BT_CTS_GPIO, + .end = CG2900_UX500_BT_CTS_GPIO, + .flags = IORESOURCE_IO, + .name = "cts_gpio", + }, + { + .start = NOMADIK_GPIO_TO_IRQ(CG2900_UX500_BT_CTS_GPIO), + .end = NOMADIK_GPIO_TO_IRQ(CG2900_UX500_BT_CTS_GPIO), + .flags = IORESOURCE_IRQ, + .name = "cts_irq", + }, +}; + + +static struct resource cg2900_uart_resources_u9500[] = { + { + .start = CG2900_GBF_ENA_RESET_GPIO, + .end = CG2900_GBF_ENA_RESET_GPIO, + .flags = IORESOURCE_IO, + .name = "gbf_ena_reset", + }, + { + .start = WLAN_PMU_EN_GPIO_U9500, + .end = WLAN_PMU_EN_GPIO_U9500, + .flags = IORESOURCE_IO, + .name = "pmu_en", + }, + { + .start = CG2900_UX500_BT_CTS_GPIO, + .end = CG2900_UX500_BT_CTS_GPIO, + .flags = IORESOURCE_IO, + .name = "cts_gpio", + }, + { + .start = NOMADIK_GPIO_TO_IRQ(CG2900_UX500_BT_CTS_GPIO), + .end = NOMADIK_GPIO_TO_IRQ(CG2900_UX500_BT_CTS_GPIO), + .flags = IORESOURCE_IRQ, + .name = "cts_irq", + }, +}; + +static pin_cfg_t u5500_cg2900_uart_enabled[] = { + GPIO165_U3_RXD | PIN_INPUT_PULLUP, + GPIO166_U3_TXD | PIN_OUTPUT_HIGH, + GPIO167_U3_RTSn | PIN_OUTPUT_HIGH, + GPIO168_U3_CTSn | PIN_INPUT_PULLUP, +}; + +static pin_cfg_t u5500_cg2900_uart_disabled[] = { + GPIO165_GPIO | PIN_INPUT_PULLUP, /* RX pull down. */ + GPIO166_GPIO | PIN_OUTPUT_LOW, /* TX low - break on. */ + GPIO167_GPIO | PIN_OUTPUT_HIGH, /* RTS high-flow off. */ + GPIO168_GPIO | PIN_INPUT_PULLUP, /* CTS pull up. */ +}; + +static pin_cfg_t ux500_cg2900_uart_enabled[] = { + GPIO0_U0_CTSn | PIN_INPUT_PULLUP, + GPIO1_U0_RTSn | PIN_OUTPUT_HIGH, + GPIO2_U0_RXD | PIN_INPUT_PULLUP, + GPIO3_U0_TXD | PIN_OUTPUT_HIGH +}; + +static pin_cfg_t ux500_cg2900_uart_disabled[] = { + GPIO0_GPIO | PIN_INPUT_PULLUP, /* CTS pull up. */ + GPIO1_GPIO | PIN_OUTPUT_HIGH, /* RTS high-flow off. */ + GPIO2_GPIO | PIN_INPUT_PULLUP, /* RX pull down. */ + GPIO3_GPIO | PIN_OUTPUT_LOW /* TX low - break on. */ +}; + +static struct cg2900_platform_data ux500_cg2900_uart_platform_data = { + .bus = HCI_UART, + .gpio_sleep = ux500_cg2900_sleep_gpio, + .uart = { + .n_uart_gpios = 4, + }, +}; + +static struct platform_device ux500_cg2900_uart_device = { + .name = "cg2900-uart", + .dev = { + .platform_data = &ux500_cg2900_uart_platform_data, + .parent = &ux500_cg2900_device.dev, + }, +}; + +static bool mach_supported(void) +{ + if (machine_is_u8500() || + machine_is_u5500() || + machine_is_hrefv60() || + machine_is_nomadik() || + machine_is_snowball()) + return true; + + return false; +} + +static int __init board_cg2900_init(void) +{ + int err; + + if (!mach_supported()) + return 0; + + dcg2900_init_platdata(&ux500_cg2900_test_platform_data); + if (machine_is_u5500()) { + ux500_cg2900_uart_platform_data.uart.uart_enabled = + u5500_cg2900_uart_enabled; + ux500_cg2900_uart_platform_data.uart.uart_disabled = + u5500_cg2900_uart_disabled; + } else { + ux500_cg2900_uart_platform_data.uart.uart_enabled = + ux500_cg2900_uart_enabled; + ux500_cg2900_uart_platform_data.uart.uart_disabled = + ux500_cg2900_uart_disabled; + ux500_cg2900_uart_platform_data.regulator_id = "vdd"; + } + dcg2900_init_platdata(&ux500_cg2900_uart_platform_data); + + if (pins_for_u9500()) { + /* u9500 */ + ux500_cg2900_uart_device.num_resources = + ARRAY_SIZE(cg2900_uart_resources_u9500); + ux500_cg2900_uart_device.resource = + cg2900_uart_resources_u9500; + } else if (cpu_is_u8500()) { + if (machine_is_hrefv60()) { + /* u8500 */ + ux500_cg2900_uart_device.num_resources = + ARRAY_SIZE(cg2900_uart_resources_u8500); + ux500_cg2900_uart_device.resource = + cg2900_uart_resources_u8500; + } else if (machine_is_snowball()) { + /* snowball have diffrent PMU_EN gpio */ + ux500_cg2900_uart_device.num_resources = + ARRAY_SIZE(cg2900_uart_resources_snowball); + ux500_cg2900_uart_device.resource = + cg2900_uart_resources_snowball; + } else { + /* u8500 pre v60*/ + ux500_cg2900_uart_device.num_resources = + ARRAY_SIZE(cg2900_uart_resources_pre_v60); + ux500_cg2900_uart_device.resource = + cg2900_uart_resources_pre_v60; + } + } else if (cpu_is_u5500()) { + /* u5500 */ + ux500_cg2900_uart_device.num_resources = + ARRAY_SIZE(cg2900_uart_resources_u5500); + ux500_cg2900_uart_device.resource = + cg2900_uart_resources_u5500; + } + + err = platform_device_register(&ux500_cg2900_device); + if (err) + return err; + err = platform_device_register(&ux500_cg2900_uart_device); + if (err) + return err; + err = platform_device_register(&ux500_cg2900_test_device); + if (err) + return err; + err = platform_device_register(&ux500_cg2900_chip_device); + if (err) + return err; + err = platform_device_register(&ux500_stlc2690_chip_device); + if (err) + return err; + + dev_info(&ux500_cg2900_device.dev, "CG2900 initialized\n"); + return 0; +} + +static void __exit board_cg2900_exit(void) +{ + if (!mach_supported()) + return; + + platform_device_unregister(&ux500_stlc2690_chip_device); + platform_device_unregister(&ux500_cg2900_chip_device); + platform_device_unregister(&ux500_cg2900_test_device); + platform_device_unregister(&ux500_cg2900_uart_device); + platform_device_unregister(&ux500_cg2900_device); + + dev_info(&ux500_cg2900_device.dev, "CG2900 removed\n"); +} + +module_init(board_cg2900_init); +module_exit(board_cg2900_exit); diff --git a/drivers/staging/cg2900/clock-cg2900.c b/drivers/staging/cg2900/clock-cg2900.c new file mode 100644 index 00000000000..6e3c738e49d --- /dev/null +++ b/drivers/staging/cg2900/clock-cg2900.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Hemant Gupta <hemant.gupta@stericsson.com> + * Author: Tomasz Hliwiak <tomasz.hliwiak@tieto.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + */ + +#include <asm/mach-types.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/skbuff.h> +#include "clock.h" +#include "cg2900.h" + +static DEFINE_MUTEX(cg2900_clk_mutex); + +static struct cg2900_user_data *pf_data; +static struct clk_lookup *cg2900_clk_lookup; + +/** + * cg2900_clk_enable() - Enables CG2900 Clock + * + * Enables CG2900 Clock by starting CG2900. + * + * Returns: + * 0 if success. + * -EINVAL if stored pf_data is NULL. + * Error codes generated by open. + */ + +static int cg2900_clk_enable(struct clk *clk) +{ + int err = -EINVAL; + if (pf_data) + err = pf_data->open(pf_data); + + return err; +} + +/** + * cg2900_clk_disable() - Disables CG2900 Clock + * + * Disables CG2900 Clock by switching off CG2900. + */ +static void cg2900_clk_disable(struct clk *clk) +{ + if (pf_data) + pf_data->close(pf_data); +} + +static struct clkops cg2900_clk_ops = { + .enable = cg2900_clk_enable, + .disable = cg2900_clk_disable, +}; + +static struct clk cg2900_clk = { + .name = "cg2900_clk", + .ops = &cg2900_clk_ops, + .mutex = &cg2900_clk_mutex, +}; + +/** + * cg2900_read_cb() - Dummy callback for cg2900 core read. + * + * Function is required by cg2900_core->open(). + */ +static void cg2900_read_cb(struct cg2900_user_data *user, struct sk_buff *skb) +{ + kfree_skb(skb); +} + +/** + * cg2900_core_probe() - Initialize resources. + * + * Function initializes pf_data structure and also adds the cg2900 + * clock source. + */ +static int __devinit cg2900_core_probe(struct platform_device *pdev) +{ + cg2900_clk_lookup = clkdev_alloc(&cg2900_clk, "sys_clk_out", + "cw1200_wlan"); + + if (!cg2900_clk_lookup) + return -ENOMEM; + + clkdev_add(cg2900_clk_lookup); + pf_data = dev_get_platdata(&pdev->dev); + pf_data->dev = &pdev->dev; + pf_data->read_cb = cg2900_read_cb; + + return 0; +} + +/** + * cg2900_core_remove() - Clean resources. + * + * Function cleans pf_data structure and removes the clock source. + */ +static int __devexit cg2900_core_remove(struct platform_device *pdev) +{ + clkdev_drop(cg2900_clk_lookup); + pf_data = NULL; + + return 0; +} + +static struct platform_driver cg2900_core_ctrl_driver = { + .driver = { + .name = "cg2900-core", + .owner = THIS_MODULE, + }, + .probe = cg2900_core_probe, + .remove = __devexit_p(cg2900_core_remove), +}; + +/** + * clock_cg2900_init() - Register Platform Data + * + * Registers the platform data. + */ +static int __init clock_cg2900_init(void) +{ + return platform_driver_register(&cg2900_core_ctrl_driver); +} + +/** + * clock_cg2900_exit() - Unregister Platform Data + * + * Unregister Platform Data + */ +static void __exit clock_cg2900_exit(void) +{ + platform_driver_unregister(&cg2900_core_ctrl_driver); +} + +module_init(clock_cg2900_init); +module_exit(clock_cg2900_exit); diff --git a/drivers/staging/cg2900/devices-cg2900-ux500.c b/drivers/staging/cg2900/devices-cg2900-ux500.c new file mode 100644 index 00000000000..7e7c12ce4a0 --- /dev/null +++ b/drivers/staging/cg2900/devices-cg2900-ux500.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * Hemant Gupta (hemant.gupta@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Board specific device support for the Linux Bluetooth HCI H:4 Driver + * for ST-Ericsson connectivity controller. + */ + +#include <asm/byteorder.h> +#include <asm-generic/errno-base.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/types.h> +#include <plat/pincfg.h> + +#include "devices-cg2900.h" + +void dcg2900_u8500_enable_chip(struct cg2900_chip_dev *dev) +{ + struct dcg2900_info *info = dev->b_data; + + if (info->gbf_gpio == -1) + return; + + /* + * Due to a bug in CG2900 we cannot just set GPIO high to enable + * the chip. We must wait more than 100 msecs before enabling the + * chip. + * - Set PDB to low. + * - Wait for 100 msecs + * - Set PDB to high. + */ + gpio_set_value(info->gbf_gpio, 0); + schedule_timeout_uninterruptible(msecs_to_jiffies( + CHIP_ENABLE_PDB_LOW_TIMEOUT)); + + if (info->pmuen_gpio != -1) { + /* + * We must first set PMU_EN pin high and then wait 300 us before + * setting the GBF_EN high. + */ + gpio_set_value(info->pmuen_gpio, 1); + udelay(CHIP_ENABLE_PMU_EN_TIMEOUT); + } + + gpio_set_value(info->gbf_gpio, 1); +} + +void dcg2900_u8500_disable_chip(struct cg2900_chip_dev *dev) +{ + struct dcg2900_info *info = dev->b_data; + + if (info->gbf_gpio != -1) + gpio_set_value(info->gbf_gpio, 0); + if (info->pmuen_gpio != -1) + gpio_set_value(info->pmuen_gpio, 0); +} + +int dcg2900_u8500_setup(struct cg2900_chip_dev *dev, + struct dcg2900_info *info) +{ + int err = 0; + struct resource *resource; + const char *gbf_name; + const char *bt_name = NULL; + const char *pmuen_name = NULL; + + resource = platform_get_resource_byname(dev->pdev, IORESOURCE_IO, + "gbf_ena_reset"); + if (!resource) { + dev_err(dev->dev, "GBF GPIO does not exist\n"); + err = -EINVAL; + goto err_handling; + } + + info->gbf_gpio = resource->start; + gbf_name = resource->name; + + resource = platform_get_resource_byname(dev->pdev, IORESOURCE_IO, + "bt_enable"); + /* BT Enable GPIO may not exist */ + if (resource) { + info->bt_gpio = resource->start; + bt_name = resource->name; + } + + resource = platform_get_resource_byname(dev->pdev, IORESOURCE_IO, + "pmu_en"); + /* PMU_EN GPIO may not exist */ + if (resource) { + info->pmuen_gpio = resource->start; + pmuen_name = resource->name; + } + + /* Now setup the GPIOs */ + err = gpio_request(info->gbf_gpio, gbf_name); + if (err < 0) { + dev_err(dev->dev, "gpio_request %s failed with err: %d\n", + gbf_name, err); + goto err_handling; + } + + err = gpio_direction_output(info->gbf_gpio, 0); + if (err < 0) { + dev_err(dev->dev, + "gpio_direction_output %s failed with err: %d\n", + gbf_name, err); + goto err_handling_free_gpio_gbf; + } + + if (!pmuen_name) + goto set_bt_gpio; + + err = gpio_request(info->pmuen_gpio, pmuen_name); + if (err < 0) { + dev_err(dev->dev, "gpio_request %s failed with err: %d\n", + pmuen_name, err); + goto err_handling_free_gpio_gbf; + } + + err = gpio_direction_output(info->pmuen_gpio, 0); + if (err < 0) { + dev_err(dev->dev, + "gpio_direction_output %s failed with err: %d\n", + pmuen_name, err); + goto err_handling_free_gpio_pmuen; + } + +set_bt_gpio: + if (!bt_name) + goto finished; + + err = gpio_request(info->bt_gpio, bt_name); + if (err < 0) { + dev_err(dev->dev, "gpio_request %s failed with err: %d\n", + bt_name, err); + goto err_handling_free_gpio_pmuen; + } + + err = gpio_direction_output(info->bt_gpio, 1); + if (err < 0) { + dev_err(dev->dev, + "gpio_direction_output %s failed with err: %d\n", + bt_name, err); + goto err_handling_free_gpio_bt; + } + +finished: + + return 0; + +err_handling_free_gpio_bt: + gpio_free(info->bt_gpio); + info->bt_gpio = -1; +err_handling_free_gpio_pmuen: + if (info->pmuen_gpio != -1) { + gpio_free(info->pmuen_gpio); + info->pmuen_gpio = -1; + } +err_handling_free_gpio_gbf: + gpio_free(info->gbf_gpio); + info->gbf_gpio = -1; +err_handling: + + return err; +} + +/* prcmu resout1 pin is used for CG2900 reset*/ +void dcg2900_u5500_enable_chip(struct cg2900_chip_dev *dev) +{ + struct dcg2900_info *info = dev->b_data; + + clk_enable(info->lpoclk); + /* + * Due to a bug in CG2900 we cannot just set GPIO high to enable + * the chip. We must wait more than 100 msecs before enbling the + * chip. + * - Set PDB to low. + * - Wait for 100 msecs + * - Set PDB to high. + */ + prcmu_resetout(1, 0); + schedule_timeout_uninterruptible(msecs_to_jiffies( + CHIP_ENABLE_PDB_LOW_TIMEOUT)); + prcmu_resetout(1, 1); +} + +void dcg2900_u5500_disable_chip(struct cg2900_chip_dev *dev) +{ + struct dcg2900_info *info = dev->b_data; + + prcmu_resetout(1, 0); + clk_disable(info->lpoclk); +} + +int dcg2900_u5500_setup(struct cg2900_chip_dev *dev, + struct dcg2900_info *info) +{ + info->lpoclk = clk_get(dev->dev, "lpoclk"); + if (IS_ERR(info->lpoclk)) + return PTR_ERR(info->lpoclk); + + return 0; +} + diff --git a/drivers/staging/cg2900/devices-cg2900.c b/drivers/staging/cg2900/devices-cg2900.c new file mode 100644 index 00000000000..e6703c96aa9 --- /dev/null +++ b/drivers/staging/cg2900/devices-cg2900.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * Hemant Gupta (hemant.gupta@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Board specific device support for the Linux Bluetooth HCI H:4 Driver + * for ST-Ericsson connectivity controller. + */ + +#define NAME "devices-cg2900" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <asm/byteorder.h> +#include <asm-generic/errno-base.h> +#include <asm/mach-types.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/regulator/consumer.h> +#include <mach/id.h> +#include <plat/pincfg.h> +#include "cg2900.h" +#include "devices-cg2900.h" + +#define BT_VS_POWER_SWITCH_OFF 0xFD40 + +#define H4_HEADER_LENGTH 0x01 +#define BT_HEADER_LENGTH 0x03 + +#define STLC2690_HCI_REV 0x0600 +#define CG2900_PG1_HCI_REV 0x0101 +#define CG2900_PG2_HCI_REV 0x0200 +#define CG2900_PG1_SPECIAL_HCI_REV 0x0700 + +struct vs_power_sw_off_cmd { + __le16 op_code; + u8 len; + u8 gpio_0_7_pull_up; + u8 gpio_8_15_pull_up; + u8 gpio_16_20_pull_up; + u8 gpio_0_7_pull_down; + u8 gpio_8_15_pull_down; + u8 gpio_16_20_pull_down; +} __packed; + +static struct sk_buff *dcg2900_get_power_switch_off_cmd + (struct cg2900_chip_dev *dev, u16 *op_code) +{ + struct sk_buff *skb; + struct vs_power_sw_off_cmd *cmd; + struct dcg2900_info *info; + int i; + + /* If connected chip does not support the command return NULL */ + if (CG2900_PG1_SPECIAL_HCI_REV != dev->chip.hci_revision && + CG2900_PG1_HCI_REV != dev->chip.hci_revision && + CG2900_PG2_HCI_REV != dev->chip.hci_revision) + return NULL; + + dev_dbg(dev->dev, "Generating PowerSwitchOff command\n"); + + info = dev->b_data; + + skb = alloc_skb(sizeof(*cmd) + H4_HEADER_LENGTH, GFP_KERNEL); + if (!skb) { + dev_err(dev->dev, "Could not allocate skb\n"); + return NULL; + } + + skb_reserve(skb, H4_HEADER_LENGTH); + cmd = (struct vs_power_sw_off_cmd *)skb_put(skb, sizeof(*cmd)); + cmd->op_code = cpu_to_le16(BT_VS_POWER_SWITCH_OFF); + cmd->len = sizeof(*cmd) - BT_HEADER_LENGTH; + /* + * Enter system specific GPIO settings here: + * Section data[3-5] is GPIO pull-up selection + * Section data[6-8] is GPIO pull-down selection + * Each section is a bitfield where + * - byte 0 bit 0 is GPIO 0 + * - byte 0 bit 1 is GPIO 1 + * - up to + * - byte 2 bit 4 which is GPIO 20 + * where each bit means: + * - 0: No pull-up / no pull-down + * - 1: Pull-up / pull-down + * All GPIOs are set as input. + */ + if (!info->sleep_gpio_set) { + struct cg2900_platform_data *pf_data; + + pf_data = dev_get_platdata(dev->dev); + for (i = 0; i < 8; i++) { + if (pf_data->gpio_sleep[i] == CG2900_PULL_UP) + info->gpio_0_7_pull_up |= (1 << i); + else if (pf_data->gpio_sleep[i] == CG2900_PULL_DN) + info->gpio_0_7_pull_down |= (1 << i); + } + for (i = 8; i < 16; i++) { + if (pf_data->gpio_sleep[i] == CG2900_PULL_UP) + info->gpio_8_15_pull_up |= (1 << (i - 8)); + else if (pf_data->gpio_sleep[i] == CG2900_PULL_DN) + info->gpio_8_15_pull_down |= (1 << (i - 8)); + } + for (i = 16; i < 21; i++) { + if (pf_data->gpio_sleep[i] == CG2900_PULL_UP) + info->gpio_16_20_pull_up |= (1 << (i - 16)); + else if (pf_data->gpio_sleep[i] == CG2900_PULL_DN) + info->gpio_16_20_pull_down |= (1 << (i - 16)); + } + info->sleep_gpio_set = true; + } + cmd->gpio_0_7_pull_up = info->gpio_0_7_pull_up; + cmd->gpio_8_15_pull_up = info->gpio_8_15_pull_up; + cmd->gpio_16_20_pull_up = info->gpio_16_20_pull_up; + cmd->gpio_0_7_pull_down = info->gpio_0_7_pull_down; + cmd->gpio_8_15_pull_down = info->gpio_8_15_pull_down; + cmd->gpio_16_20_pull_down = info->gpio_16_20_pull_down; + + + if (op_code) + *op_code = BT_VS_POWER_SWITCH_OFF; + + return skb; +} + +static int dcg2900_init(struct cg2900_chip_dev *dev) +{ + int err = 0; + struct dcg2900_info *info; + struct cg2900_platform_data *pdata = dev_get_platdata(dev->dev); + + /* First retrieve and save the resources */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Could not allocate dcg2900_info\n"); + return -ENOMEM; + } + + info->gbf_gpio = -1; + info->pmuen_gpio = -1; + info->bt_gpio = -1; + + if (!dev->pdev->num_resources) { + dev_dbg(dev->dev, "No resources available\n"); + goto finished; + } + + if (cpu_is_u5500()) + err = dcg2900_u5500_setup(dev, info); + else + err = dcg2900_u8500_setup(dev, info); + + if (err) + goto err_handling; + + /* + * Enable the power on snowball + */ + if (machine_is_snowball()) { + /* Take the regulator */ + if (pdata->regulator_id) { + info->regulator_wlan = regulator_get(dev->dev, + pdata->regulator_id); + if (IS_ERR(info->regulator_wlan)) { + err = PTR_ERR(info->regulator_wlan); + dev_warn(dev->dev, + "%s: Failed to get regulator '%s'\n", + __func__, pdata->regulator_id); + info->regulator_wlan = NULL; + goto err_handling_free_gpios; + } + /* Enable it also */ + err = regulator_enable(info->regulator_wlan); + if (err < 0) { + dev_warn(dev->dev, "%s: regulator_enable failed\n", + __func__); + goto err_handling_put_reg; + } + } else { + dev_warn(dev->dev, "%s: no regulator defined for snowball.\n", + __func__); + } + } + +finished: + dev->b_data = info; + return 0; +err_handling_put_reg: + regulator_put(info->regulator_wlan); +err_handling_free_gpios: + if (info->bt_gpio != -1) + gpio_free(info->bt_gpio); + if (info->pmuen_gpio != -1) + gpio_free(info->pmuen_gpio); + if (info->gbf_gpio != -1) + gpio_free(info->gbf_gpio); + +err_handling: + kfree(info); + return err; +} + +static void dcg2900_exit(struct cg2900_chip_dev *dev) +{ + struct dcg2900_info *info = dev->b_data; + + if (machine_is_snowball()) { + /* Turn off power if we have any */ + if (info->regulator_wlan) { + regulator_disable(info->regulator_wlan); + regulator_put(info->regulator_wlan); + } + } + + if (cpu_is_u5500()) + dcg2900_u5500_disable_chip(dev); + else + dcg2900_u8500_disable_chip(dev); + + if (info->bt_gpio != -1) + gpio_free(info->bt_gpio); + if (info->pmuen_gpio != -1) + gpio_free(info->pmuen_gpio); + if (info->gbf_gpio != -1) + gpio_free(info->gbf_gpio); + kfree(info); + dev->b_data = NULL; +} + +static int dcg2900_disable_uart(struct cg2900_chip_dev *dev) +{ + int err; + struct cg2900_platform_data *pdata = dev_get_platdata(dev->dev); + + /* + * Without this delay we get interrupt on CTS immediately + * due to some turbulences on this line. + */ + mdelay(4); + + /* Disable UART functions. */ + err = nmk_config_pins(pdata->uart.uart_disabled, + pdata->uart.n_uart_gpios); + if (err) + goto error; + + return 0; + +error: + (void)nmk_config_pins(pdata->uart.uart_enabled, + pdata->uart.n_uart_gpios); + dev_err(dev->dev, "Cannot set interrupt (%d)\n", err); + return err; +} + +static int dcg2900_enable_uart(struct cg2900_chip_dev *dev) +{ + int err; + struct cg2900_platform_data *pdata = dev_get_platdata(dev->dev); + + /* Restore UART settings. */ + err = nmk_config_pins(pdata->uart.uart_enabled, + pdata->uart.n_uart_gpios); + if (err) + dev_err(dev->dev, "Unable to enable UART (%d)\n", err); + + return err; +} + +void dcg2900_init_platdata(struct cg2900_platform_data *data) +{ + data->init = dcg2900_init; + data->exit = dcg2900_exit; + + if (cpu_is_u5500()) { + data->enable_chip = dcg2900_u5500_enable_chip; + data->disable_chip = dcg2900_u5500_disable_chip; + } else { + data->enable_chip = dcg2900_u8500_enable_chip; + data->disable_chip = dcg2900_u8500_disable_chip; + } + data->get_power_switch_off_cmd = dcg2900_get_power_switch_off_cmd; + + data->uart.enable_uart = dcg2900_enable_uart; + data->uart.disable_uart = dcg2900_disable_uart; +} diff --git a/drivers/staging/cg2900/devices-cg2900.h b/drivers/staging/cg2900/devices-cg2900.h new file mode 100644 index 00000000000..5ca95e1e0a1 --- /dev/null +++ b/drivers/staging/cg2900/devices-cg2900.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com> + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef __DEVICES_CG2900_H +#define __DEVICES_CG2900_H + +#include "cg2900.h" +#include <linux/clk.h> + +#define CHIP_ENABLE_PDB_LOW_TIMEOUT 100 /* ms */ +#define CHIP_ENABLE_PMU_EN_TIMEOUT 300 /* us */ + +struct dcg2900_info { + int gbf_gpio; + int pmuen_gpio; + int bt_gpio; + bool sleep_gpio_set; + u8 gpio_0_7_pull_up; + u8 gpio_8_15_pull_up; + u8 gpio_16_20_pull_up; + u8 gpio_0_7_pull_down; + u8 gpio_8_15_pull_down; + u8 gpio_16_20_pull_down; + struct clk *lpoclk; + struct regulator *regulator_wlan; +}; + +extern void dcg2900_u8500_enable_chip(struct cg2900_chip_dev *dev); +extern void dcg2900_u8500_disable_chip(struct cg2900_chip_dev *dev); +extern int dcg2900_u8500_setup(struct cg2900_chip_dev *dev, + struct dcg2900_info *info); +extern void dcg2900_u5500_enable_chip(struct cg2900_chip_dev *dev); +extern void dcg2900_u5500_disable_chip(struct cg2900_chip_dev *dev); +extern int dcg2900_u5500_setup(struct cg2900_chip_dev *dev, + struct dcg2900_info *info); + +/** + * enum cg2900_gpio_pull_sleep - GPIO pull setting in sleep. + * @CG2900_NO_PULL: Normal input in sleep (no pull up or down). + * @CG2900_PULL_UP: Pull up in sleep. + * @CG2900_PULL_DN: Pull down in sleep. + */ +enum cg2900_gpio_pull_sleep { + CG2900_NO_PULL, + CG2900_PULL_UP, + CG2900_PULL_DN +}; + +/** + * dcg2900_init_platdata() - Initializes platform data with callback functions. + * @data: Platform data. + */ +extern void dcg2900_init_platdata(struct cg2900_platform_data *data); + +#endif /* __DEVICES_CG2900_H */ diff --git a/drivers/staging/cg2900/include/cg2900.h b/drivers/staging/cg2900/include/cg2900.h new file mode 100644 index 00000000000..bd165c0048b --- /dev/null +++ b/drivers/staging/cg2900/include/cg2900.h @@ -0,0 +1,280 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 connectivity + * controller. + */ + +#ifndef _CG2900_H_ +#define _CG2900_H_ + +#include <linux/types.h> + +/* Perform reset. No parameters used */ +#define CG2900_CHAR_DEV_IOCTL_RESET _IOW('U', 210, int) +/* Check for reset */ +#define CG2900_CHAR_DEV_IOCTL_CHECK4RESET _IOR('U', 212, int) +/* Retrieve revision info */ +#define CG2900_CHAR_DEV_IOCTL_GET_REVISION _IOR('U', 213, \ + struct cg2900_rev_data) + +#define CG2900_CHAR_DEV_IOCTL_EVENT_IDLE 0 +#define CG2900_CHAR_DEV_IOCTL_EVENT_RESET 1 + +/** + * struct cg2900_rev_data - Contains revision data for the local controller. + * @revision: Revision of the controller, e.g. to indicate that it is + * a CG2900 controller. + * @sub_version: Subversion of the controller, e.g. to indicate a certain + * tape-out of the controller. + * + * The values to match retrieved values to each controller may be retrieved from + * the manufacturer. + */ +struct cg2900_rev_data { + int revision; + int sub_version; +}; + +#ifdef __KERNEL__ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/skbuff.h> + +/* Temporary solution while in staging directory */ +#include "cg2900_hci.h" + +/** + * struct cg2900_chip_rev_info - Chip info structure. + * @manufacturer: Chip manufacturer. + * @hci_version: Bluetooth version supported over HCI. + * @hci_revision: Chip revision, i.e. which chip is this. + * @lmp_pal_version: Bluetooth version supported over air. + * @hci_sub_version: Chip sub-version, i.e. which tape-out is this. + * + * Note that these values match the Bluetooth Assigned Numbers, + * see http://www.bluetooth.org/ + */ +struct cg2900_chip_rev_info { + u16 manufacturer; + u8 hci_version; + u16 hci_revision; + u8 lmp_pal_version; + u16 hci_sub_version; +}; + +struct cg2900_chip_dev; + +/** + * struct cg2900_id_callbacks - Chip handler identification callbacks. + * @check_chip_support: Called when chip is connected. If chip is supported by + * driver, return true and fill in @callbacks in @dev. + * + * Note that the callback may be NULL. It must always be NULL checked before + * calling. + */ +struct cg2900_id_callbacks { + bool (*check_chip_support)(struct cg2900_chip_dev *dev); +}; + +/** + * struct cg2900_chip_callbacks - Callback functions registered by chip handler. + * @data_from_chip: Called when data shall be transmitted to user. + * @chip_removed: Called when chip is removed. + * + * Note that some callbacks may be NULL. They must always be NULL checked before + * calling. + */ +struct cg2900_chip_callbacks { + void (*data_from_chip)(struct cg2900_chip_dev *dev, + struct sk_buff *skb); + void (*chip_removed)(struct cg2900_chip_dev *dev); +}; + +/** + * struct cg2900_trans_callbacks - Callback functions registered by transport. + * @open: CG2900 Core needs a transport. + * @close: CG2900 Core does not need a transport. + * @write: CG2900 Core transmits to the chip. + * @set_chip_power: CG2900 Core enables or disables the chip. + * @chip_startup_finished: CG2900 Chip startup finished notification. + * + * Note that some callbacks may be NULL. They must always be NULL checked before + * calling. + */ +struct cg2900_trans_callbacks { + int (*open)(struct cg2900_chip_dev *dev); + int (*close)(struct cg2900_chip_dev *dev); + int (*write)(struct cg2900_chip_dev *dev, struct sk_buff *skb); + void (*set_chip_power)(struct cg2900_chip_dev *dev, bool chip_on); + void (*chip_startup_finished)(struct cg2900_chip_dev *dev); +}; + +/** + * struct cg2900_chip_dev - Chip handler info structure. + * @dev: Device associated with this chip. + * @pdev: Platform device associated with this chip. + * @chip: Chip info such as manufacturer. + * @c_cb: Callback structure for the chip handler. + * @t_cb: Callback structure for the transport. + * @c_data: Arbitrary data set by chip handler. + * @t_data: Arbitrary data set by transport. + * @b_data: Arbitrary data set by board handler. + * @prv_data: Arbitrary data set by CG2900 Core. + */ +struct cg2900_chip_dev { + struct device *dev; + struct platform_device *pdev; + struct cg2900_chip_rev_info chip; + struct cg2900_chip_callbacks c_cb; + struct cg2900_trans_callbacks t_cb; + void *c_data; + void *t_data; + void *b_data; + void *prv_data; +}; + +/** + * struct cg2900_platform_data - Contains platform data for CG2900. + * @init: Callback called upon system start. + * @exit: Callback called upon system shutdown. + * @enable_chip: Callback called for enabling CG2900 chip. + * @disable_chip: Callback called for disabling CG2900 chip. + * @get_power_switch_off_cmd: Callback called to retrieve + * HCI VS_Power_Switch_Off command (command + * HCI requires platform specific GPIO data). + * @regulator_id: Id of the regulator that powers on the chip + * @bus: Transport used, see @include/net/bluetooth/hci.h. + * @gpio_sleep: Array of GPIO sleep settings. + * @enable_uart: Callback called when switching from UART GPIO to + * UART HW. + * @disable_uart: Callback called when switching from UART HW to + * UART GPIO. + * @n_uart_gpios: Number of UART GPIOs. + * @uart_enabled: Array of size @n_uart_gpios with GPIO setting for + * enabling UART HW (switching from GPIO mode). + * @uart_disabled: Array of size @n_uart_gpios with GPIO setting for + * disabling UART HW (switching to GPIO mode). + * @uart: Platform data structure for UART transport. + * + * Any callback may be NULL if not needed. + */ +struct cg2900_platform_data { + int (*init)(struct cg2900_chip_dev *dev); + void (*exit)(struct cg2900_chip_dev *dev); + void (*enable_chip)(struct cg2900_chip_dev *dev); + void (*disable_chip)(struct cg2900_chip_dev *dev); + struct sk_buff* (*get_power_switch_off_cmd)(struct cg2900_chip_dev *dev, + u16 *op_code); + + char *regulator_id; + __u8 bus; + enum cg2900_gpio_pull_sleep *gpio_sleep; + + struct { + int (*enable_uart)(struct cg2900_chip_dev *dev); + int (*disable_uart)(struct cg2900_chip_dev *dev); + int n_uart_gpios; + unsigned long *uart_enabled; + unsigned long *uart_disabled; + } uart; +}; + +/** + * struct cg2900_user_data - Contains platform data for CG2900 user. + * @dev: Current device. Set by CG2900 user upon probe. + * @opened: True if channel is opened. + * @user_data: Data set and used by CG2900 user. + * @private_data: Data set and used by CG2900 driver. + * @h4_channel: H4 channel. Set by CG2900 driver. + * @is_audio: True if this channel is an audio channel. Set by CG2900 + * driver. + * @chip_independent: True if this channel does not require chip to be + * powered. Set by CG2900 driver. + * @bt_bus: Transport used, see @include/net/bluetooth/hci.h. + * @char_dev_name: Name to be used for character device. + * @channel_data: Input data specific to current device. + * @open: Open device channel. Set by CG2900 driver. + * @close: Close device channel. Set by CG2900 driver. + * @reset: Reset connectivity controller. Set by CG2900 driver. + * @alloc_skb: Alloc sk_buffer. Set by CG2900 driver. + * @write: Write to device channel. Set by CG2900 driver. + * @get_local_revision: Get revision data of conncected chip. Set by CG2900 + * driver. + * @read_cb: Callback function called when data is received on the + * device channel. Set by CG2900 user. Mandatory. + * @reset_cb: Callback function called when the connectivity + * controller has been reset. Set by CG2900 user. + * + * Any callback may be NULL if not needed. + */ +struct cg2900_user_data { + struct device *dev; + bool opened; + + void *user_data; + void *private_data; + + int h4_channel; + bool is_audio; + bool chip_independent; + + union { + __u8 bt_bus; + char *char_dev_name; + } channel_data; + + int (*open)(struct cg2900_user_data *user_data); + void (*close)(struct cg2900_user_data *user_data); + int (*reset)(struct cg2900_user_data *user_data); + struct sk_buff * (*alloc_skb)(unsigned int size, gfp_t priority); + int (*write)(struct cg2900_user_data *user_data, struct sk_buff *skb); + bool (*get_local_revision)(struct cg2900_user_data *user_data, + struct cg2900_rev_data *rev_data); + + void (*read_cb)(struct cg2900_user_data *user_data, + struct sk_buff *skb); + void (*reset_cb)(struct cg2900_user_data *user_data); +}; + +static inline void *cg2900_get_usr(struct cg2900_user_data *dev) +{ + if (dev) + return dev->user_data; + return NULL; +} + +static inline void cg2900_set_usr(struct cg2900_user_data *dev, void *data) +{ + if (dev) + dev->user_data = data; +} + +static inline void *cg2900_get_prv(struct cg2900_user_data *dev) +{ + if (dev) + return dev->private_data; + return NULL; +} + +static inline void cg2900_set_prv(struct cg2900_user_data *dev, void *data) +{ + if (dev) + dev->private_data = data; +} + +extern int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb); +extern void cg2900_deregister_chip_driver(struct cg2900_id_callbacks *cb); +extern int cg2900_register_trans_driver(struct cg2900_chip_dev *dev); +extern int cg2900_deregister_trans_driver(struct cg2900_chip_dev *dev); +extern unsigned long cg2900_get_sleep_timeout(void); + +#endif /* __KERNEL__ */ +#endif /* _CG2900_H_ */ diff --git a/drivers/staging/cg2900/include/cg2900_audio.h b/drivers/staging/cg2900/include/cg2900_audio.h new file mode 100644 index 00000000000..ff0f053fa53 --- /dev/null +++ b/drivers/staging/cg2900/include/cg2900_audio.h @@ -0,0 +1,473 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth Audio Driver for ST-Ericsson controller. + */ + +#ifndef _CG2900_AUDIO_H_ +#define _CG2900_AUDIO_H_ + +#include <linux/types.h> + +/* + * Digital Audio Interface configuration types + */ + +/** CG2900_A2DP_MAX_AVDTP_HDR_LEN - Max length of a AVDTP header. + * Max length of a AVDTP header for an A2DP packet. + */ +#define CG2900_A2DP_MAX_AVDTP_HDR_LEN 25 + +/* + * Op codes used when writing commands to the audio interface from user space + * using the char device. + */ +#define CG2900_OPCODE_SET_DAI_CONF 0x01 +#define CG2900_OPCODE_GET_DAI_CONF 0x02 +#define CG2900_OPCODE_CONFIGURE_ENDPOINT 0x03 +#define CG2900_OPCODE_START_STREAM 0x04 +#define CG2900_OPCODE_STOP_STREAM 0x05 + +/** + * enum cg2900_dai_dir - Contains the DAI port directions alternatives. + * @DAI_DIR_B_RX_A_TX: Port B as Rx and port A as Tx. + * @DAI_DIR_B_TX_A_RX: Port B as Tx and port A as Rx. + */ +enum cg2900_dai_dir { + DAI_DIR_B_RX_A_TX = 0x00, + DAI_DIR_B_TX_A_RX = 0x01 +}; + +/** + * enum cg2900_dai_mode - DAI mode alternatives. + * @DAI_MODE_SLAVE: Slave. + * @DAI_MODE_MASTER: Master. + */ +enum cg2900_dai_mode { + DAI_MODE_SLAVE = 0x00, + DAI_MODE_MASTER = 0x01 +}; + +/** + * enum cg2900_dai_stream_ratio - Voice stream ratio alternatives. + * @STREAM_RATIO_FM16_VOICE16: FM 16kHz, Voice 16kHz. + * @STREAM_RATIO_FM16_VOICE8: FM 16kHz, Voice 8kHz. + * @STREAM_RATIO_FM48_VOICE16: FM 48kHz, Voice 16Khz. + * @STREAM_RATIO_FM48_VOICE8: FM 48kHz, Voice 8kHz. + * + * Contains the alternatives for the voice stream ratio between the Audio stream + * sample rate and the Voice stream sample rate. + */ +enum cg2900_dai_stream_ratio { + STREAM_RATIO_FM16_VOICE16 = 0x01, + STREAM_RATIO_FM16_VOICE8 = 0x02, + STREAM_RATIO_FM48_VOICE16 = 0x03, + STREAM_RATIO_FM48_VOICE8 = 0x06 +}; + +/** + * enum cg2900_dai_fs_duration - Frame sync duration alternatives. + * @SYNC_DURATION_8: 8 frames sync duration. + * @SYNC_DURATION_16: 16 frames sync duration. + * @SYNC_DURATION_24: 24 frames sync duration. + * @SYNC_DURATION_32: 32 frames sync duration. + * @SYNC_DURATION_48: 48 frames sync duration. + * @SYNC_DURATION_50: 50 frames sync duration. + * @SYNC_DURATION_64: 64 frames sync duration. + * @SYNC_DURATION_75: 75 frames sync duration. + * @SYNC_DURATION_96: 96 frames sync duration. + * @SYNC_DURATION_125: 125 frames sync duration. + * @SYNC_DURATION_128: 128 frames sync duration. + * @SYNC_DURATION_150: 150 frames sync duration. + * @SYNC_DURATION_192: 192 frames sync duration. + * @SYNC_DURATION_250: 250 frames sync duration. + * @SYNC_DURATION_256: 256 frames sync duration. + * @SYNC_DURATION_300: 300 frames sync duration. + * @SYNC_DURATION_384: 384 frames sync duration. + * @SYNC_DURATION_500: 500 frames sync duration. + * @SYNC_DURATION_512: 512 frames sync duration. + * @SYNC_DURATION_600: 600 frames sync duration. + * @SYNC_DURATION_768: 768 frames sync duration. + * + * This parameter sets the PCM frame sync duration. It is calculated as the + * ratio between the bit clock and the frame rate. For example, if the bit + * clock is 512 kHz and the stream sample rate is 8 kHz, the PCM frame sync + * duration is 512 / 8 = 64. + */ +enum cg2900_dai_fs_duration { + SYNC_DURATION_8 = 0, + SYNC_DURATION_16 = 1, + SYNC_DURATION_24 = 2, + SYNC_DURATION_32 = 3, + SYNC_DURATION_48 = 4, + SYNC_DURATION_50 = 5, + SYNC_DURATION_64 = 6, + SYNC_DURATION_75 = 7, + SYNC_DURATION_96 = 8, + SYNC_DURATION_125 = 9, + SYNC_DURATION_128 = 10, + SYNC_DURATION_150 = 11, + SYNC_DURATION_192 = 12, + SYNC_DURATION_250 = 13, + SYNC_DURATION_256 = 14, + SYNC_DURATION_300 = 15, + SYNC_DURATION_384 = 16, + SYNC_DURATION_500 = 17, + SYNC_DURATION_512 = 18, + SYNC_DURATION_600 = 19, + SYNC_DURATION_768 = 20 +}; + +/** + * enum cg2900_dai_bit_clk - Bit Clock alternatives. + * @BIT_CLK_128: 128 Kbits clock. + * @BIT_CLK_256: 256 Kbits clock. + * @BIT_CLK_512: 512 Kbits clock. + * @BIT_CLK_768: 768 Kbits clock. + * @BIT_CLK_1024: 1024 Kbits clock. + * @BIT_CLK_1411_76: 1411.76 Kbits clock. + * @BIT_CLK_1536: 1536 Kbits clock. + * @BIT_CLK_2000: 2000 Kbits clock. + * @BIT_CLK_2048: 2048 Kbits clock. + * @BIT_CLK_2400: 2400 Kbits clock. + * @BIT_CLK_2823_52: 2823.52 Kbits clock. + * @BIT_CLK_3072: 3072 Kbits clock. + * + * This parameter sets the bit clock speed. This is the clocking of the actual + * data. A usual parameter for eSCO voice is 512 kHz. + */ +enum cg2900_dai_bit_clk { + BIT_CLK_128 = 0x00, + BIT_CLK_256 = 0x01, + BIT_CLK_512 = 0x02, + BIT_CLK_768 = 0x03, + BIT_CLK_1024 = 0x04, + BIT_CLK_1411_76 = 0x05, + BIT_CLK_1536 = 0x06, + BIT_CLK_2000 = 0x07, + BIT_CLK_2048 = 0x08, + BIT_CLK_2400 = 0x09, + BIT_CLK_2823_52 = 0x0A, + BIT_CLK_3072 = 0x0B +}; + +/** + * enum cg2900_dai_sample_rate - Sample rates alternatives. + * @SAMPLE_RATE_8: 8 kHz sample rate. + * @SAMPLE_RATE_16: 16 kHz sample rate. + * @SAMPLE_RATE_44_1: 44.1 kHz sample rate. + * @SAMPLE_RATE_48: 48 kHz sample rate. + */ +enum cg2900_dai_sample_rate { + SAMPLE_RATE_8 = 0, + SAMPLE_RATE_16 = 1, + SAMPLE_RATE_44_1 = 2, + SAMPLE_RATE_48 = 3 +}; + +/** + * enum cg2900_dai_port_protocol - Port protocol alternatives. + * @PORT_PROTOCOL_PCM: Protocol PCM. + * @PORT_PROTOCOL_I2S: Protocol I2S. + */ +enum cg2900_dai_port_protocol { + PORT_PROTOCOL_PCM = 0x00, + PORT_PROTOCOL_I2S = 0x01 +}; + +/** + * enum cg2900_dai_channel_sel - The channel selection alternatives. + * @CHANNEL_SELECTION_RIGHT: Right channel used. + * @CHANNEL_SELECTION_LEFT: Left channel used. + * @CHANNEL_SELECTION_BOTH: Both channels used. + */ +enum cg2900_dai_channel_sel { + CHANNEL_SELECTION_RIGHT = 0x00, + CHANNEL_SELECTION_LEFT = 0x01, + CHANNEL_SELECTION_BOTH = 0x02 +}; + +/** + * struct cg2900_dai_conf_i2s_pcm - Port configuration structure. + * @mode: Operational mode of the port configured. + * @i2s_channel_sel: I2S channels used. Only valid if used in I2S mode. + * @slot_0_used: True if SCO slot 0 is used. + * @slot_1_used: True if SCO slot 1 is used. + * @slot_2_used: True if SCO slot 2 is used. + * @slot_3_used: True if SCO slot 3 is used. + * @slot_0_dir: Direction of slot 0. + * @slot_1_dir: Direction of slot 1. + * @slot_2_dir: Direction of slot 2. + * @slot_3_dir: Direction of slot 3. + * @slot_0_start: Slot 0 start (relative to the PCM frame sync). + * @slot_1_start: Slot 1 start (relative to the PCM frame sync) + * @slot_2_start: Slot 2 start (relative to the PCM frame sync) + * @slot_3_start: Slot 3 start (relative to the PCM frame sync) + * @ratio: Voice stream ratio between the Audio stream sample rate + * and the Voice stream sample rate. + * @protocol: Protocol used on port. + * @duration: Frame sync duration. + * @clk: Bit clock. + * @sample_rate: Sample rate. + */ +struct cg2900_dai_conf_i2s_pcm { + enum cg2900_dai_mode mode; + enum cg2900_dai_channel_sel i2s_channel_sel; + bool slot_0_used; + bool slot_1_used; + bool slot_2_used; + bool slot_3_used; + enum cg2900_dai_dir slot_0_dir; + enum cg2900_dai_dir slot_1_dir; + enum cg2900_dai_dir slot_2_dir; + enum cg2900_dai_dir slot_3_dir; + __u8 slot_0_start; + __u8 slot_1_start; + __u8 slot_2_start; + __u8 slot_3_start; + enum cg2900_dai_stream_ratio ratio; + enum cg2900_dai_port_protocol protocol; + enum cg2900_dai_fs_duration duration; + enum cg2900_dai_bit_clk clk; + enum cg2900_dai_sample_rate sample_rate; +}; + +/** + * enum cg2900_dai_half_period - Half period duration alternatives. + * @HALF_PER_DUR_8: 8 Bits. + * @HALF_PER_DUR_16: 16 Bits. + * @HALF_PER_DUR_24: 24 Bits. + * @HALF_PER_DUR_25: 25 Bits. + * @HALF_PER_DUR_32: 32 Bits. + * @HALF_PER_DUR_48: 48 Bits. + * @HALF_PER_DUR_64: 64 Bits. + * @HALF_PER_DUR_75: 75 Bits. + * @HALF_PER_DUR_96: 96 Bits. + * @HALF_PER_DUR_128: 128 Bits. + * @HALF_PER_DUR_150: 150 Bits. + * @HALF_PER_DUR_192: 192 Bits. + * + * This parameter sets the number of bits contained in each I2S half period, + * i.e. each channel slot. A usual value is 16 bits. + */ +enum cg2900_dai_half_period { + HALF_PER_DUR_8 = 0x00, + HALF_PER_DUR_16 = 0x01, + HALF_PER_DUR_24 = 0x02, + HALF_PER_DUR_25 = 0x03, + HALF_PER_DUR_32 = 0x04, + HALF_PER_DUR_48 = 0x05, + HALF_PER_DUR_64 = 0x06, + HALF_PER_DUR_75 = 0x07, + HALF_PER_DUR_96 = 0x08, + HALF_PER_DUR_128 = 0x09, + HALF_PER_DUR_150 = 0x0A, + HALF_PER_DUR_192 = 0x0B +}; + +/** + * enum cg2900_dai_word_width - Word width alternatives. + * @WORD_WIDTH_16: 16 bits words. + * @WORD_WIDTH_32: 32 bits words. + */ +enum cg2900_dai_word_width { + WORD_WIDTH_16 = 0x00, + WORD_WIDTH_32 = 0x01 +}; + +/** + * struct cg2900_dai_conf_i2s - Port configuration struct for I2S. + * @mode: Operational mode of the port. + * @half_period: Half period duration. + * @channel_sel: Channel selection. + * @sample_rate: Sample rate. + * @word_width: Word width. + */ +struct cg2900_dai_conf_i2s { + enum cg2900_dai_mode mode; + enum cg2900_dai_half_period half_period; + enum cg2900_dai_channel_sel channel_sel; + enum cg2900_dai_sample_rate sample_rate; + enum cg2900_dai_word_width word_width; +}; + +/** + * union cg2900_dai_port_conf - DAI port configuration union. + * @i2s: The configuration struct for a port supporting only I2S. + * @i2s_pcm: The configuration struct for a port supporting both PCM and I2S. + */ +union cg2900_dai_port_conf { + struct cg2900_dai_conf_i2s i2s; + struct cg2900_dai_conf_i2s_pcm i2s_pcm; +}; + +/** + * enum cg2900_dai_ext_port_id - DAI external port id alternatives. + * @PORT_0_I2S: Port id is 0 and it supports only I2S. + * @PORT_1_I2S_PCM: Port id is 1 and it supports both I2S and PCM. + */ +enum cg2900_dai_ext_port_id { + PORT_0_I2S, + PORT_1_I2S_PCM +}; + +/** + * enum cg2900_audio_endpoint_id - Audio endpoint id alternatives. + * @ENDPOINT_PORT_0_I2S: Internal audio endpoint of the external I2S + * interface. + * @ENDPOINT_PORT_1_I2S_PCM: Internal audio endpoint of the external I2S/PCM + * interface. + * @ENDPOINT_SLIMBUS_VOICE: Internal audio endpoint of the external Slimbus + * voice interface. (Currently not supported) + * @ENDPOINT_SLIMBUS_AUDIO: Internal audio endpoint of the external Slimbus + * audio interface. (Currently not supported) + * @ENDPOINT_BT_SCO_INOUT: Bluetooth SCO bidirectional. + * @ENDPOINT_BT_A2DP_SRC: Bluetooth A2DP source. + * @ENDPOINT_BT_A2DP_SNK: Bluetooth A2DP sink. + * @ENDPOINT_FM_RX: FM receive. + * @ENDPOINT_FM_TX: FM transmit. + * @ENDPOINT_ANALOG_OUT: Analog out. + * @ENDPOINT_DSP_AUDIO_IN: DSP audio in. + * @ENDPOINT_DSP_AUDIO_OUT: DSP audio out. + * @ENDPOINT_DSP_VOICE_IN: DSP voice in. + * @ENDPOINT_DSP_VOICE_OUT: DSP voice out. + * @ENDPOINT_DSP_TONE_IN: DSP tone in. + * @ENDPOINT_BURST_BUFFER_IN: Burst buffer in. + * @ENDPOINT_BURST_BUFFER_OUT: Burst buffer out. + * @ENDPOINT_MUSIC_DECODER: Music decoder. + * @ENDPOINT_HCI_AUDIO_IN: HCI audio in. + */ +enum cg2900_audio_endpoint_id { + ENDPOINT_PORT_0_I2S, + ENDPOINT_PORT_1_I2S_PCM, + ENDPOINT_SLIMBUS_VOICE, + ENDPOINT_SLIMBUS_AUDIO, + ENDPOINT_BT_SCO_INOUT, + ENDPOINT_BT_A2DP_SRC, + ENDPOINT_BT_A2DP_SNK, + ENDPOINT_FM_RX, + ENDPOINT_FM_TX, + ENDPOINT_ANALOG_OUT, + ENDPOINT_DSP_AUDIO_IN, + ENDPOINT_DSP_AUDIO_OUT, + ENDPOINT_DSP_VOICE_IN, + ENDPOINT_DSP_VOICE_OUT, + ENDPOINT_DSP_TONE_IN, + ENDPOINT_BURST_BUFFER_IN, + ENDPOINT_BURST_BUFFER_OUT, + ENDPOINT_MUSIC_DECODER, + ENDPOINT_HCI_AUDIO_IN +}; + +/** + * struct cg2900_dai_config - Configuration struct for Digital Audio Interface. + * @port: The port id to configure. Acts as a discriminator for @conf parameter + * which is a union. + * @conf: The configuration union that contains the parameters for the port. + */ +struct cg2900_dai_config { + enum cg2900_dai_ext_port_id port; + union cg2900_dai_port_conf conf; +}; + +/* + * Endpoint configuration types + */ + +/** + * enum cg2900_endpoint_sample_rate - Audio endpoint configuration sample rate alternatives. + * + * This enum defines the same values as @cg2900_dai_sample_rate, but + * is kept to preserve the API. + * + * @ENDPOINT_SAMPLE_RATE_8_KHZ: 8 kHz sample rate. + * @ENDPOINT_SAMPLE_RATE_16_KHZ: 16 kHz sample rate. + * @ENDPOINT_SAMPLE_RATE_44_1_KHZ: 44.1 kHz sample rate. + * @ENDPOINT_SAMPLE_RATE_48_KHZ: 48 kHz sample rate. + */ +enum cg2900_endpoint_sample_rate { + ENDPOINT_SAMPLE_RATE_8_KHZ = SAMPLE_RATE_8, + ENDPOINT_SAMPLE_RATE_16_KHZ = SAMPLE_RATE_16, + ENDPOINT_SAMPLE_RATE_44_1_KHZ = SAMPLE_RATE_44_1, + ENDPOINT_SAMPLE_RATE_48_KHZ = SAMPLE_RATE_48 +}; + + +/** + * struct cg2900_endpoint_config_a2dp_src - A2DP source audio endpoint configurations. + * @sample_rate: Sample rate. + * @channel_count: Number of channels. + */ +struct cg2900_endpoint_config_a2dp_src { + enum cg2900_endpoint_sample_rate sample_rate; + unsigned int channel_count; +}; + +/** + * struct cg2900_endpoint_config_fm - Configuration parameters for an FM endpoint. + * @sample_rate: The sample rate alternatives for the FM audio endpoints. + */ +struct cg2900_endpoint_config_fm { + enum cg2900_endpoint_sample_rate sample_rate; +}; + + +/** + * struct cg2900_endpoint_config_sco_in_out - SCO audio endpoint configuration structure. + * @sample_rate: Sample rate, valid values are + * * ENDPOINT_SAMPLE_RATE_8_KHZ + * * ENDPOINT_SAMPLE_RATE_16_KHZ. + */ +struct cg2900_endpoint_config_sco_in_out { + enum cg2900_endpoint_sample_rate sample_rate; +}; + +/** + * union cg2900_endpoint_config - Different audio endpoint configurations. + * @sco: SCO audio endpoint configuration structure. + * @a2dp_src: A2DP source audio endpoint configuration structure. + * @fm: FM audio endpoint configuration structure. + */ +union cg2900_endpoint_config_union { + struct cg2900_endpoint_config_sco_in_out sco; + struct cg2900_endpoint_config_a2dp_src a2dp_src; + struct cg2900_endpoint_config_fm fm; +}; + +/** + * struct cg2900_endpoint_config - Audio endpoint configuration. + * @endpoint_id: Identifies the audio endpoint. Works as a discriminator + * for the config union. + * @config: Union holding the configuration parameters for + * the endpoint. + */ +struct cg2900_endpoint_config { + enum cg2900_audio_endpoint_id endpoint_id; + union cg2900_endpoint_config_union config; +}; + +#ifdef __KERNEL__ +#include <linux/device.h> + +int cg2900_audio_get_devices(struct device *devices[], __u8 size); +int cg2900_audio_open(unsigned int *session, struct device *parent); +int cg2900_audio_close(unsigned int *session); +int cg2900_audio_set_dai_config(unsigned int session, + struct cg2900_dai_config *config); +int cg2900_audio_get_dai_config(unsigned int session, + struct cg2900_dai_config *config); +int cg2900_audio_config_endpoint(unsigned int session, + struct cg2900_endpoint_config *config); +int cg2900_audio_start_stream(unsigned int session, + enum cg2900_audio_endpoint_id ep_1, + enum cg2900_audio_endpoint_id ep_2, + unsigned int *stream_handle); +int cg2900_audio_stop_stream(unsigned int session, + unsigned int stream_handle); + +#endif /* __KERNEL__ */ +#endif /* _CG2900_AUDIO_H_ */ diff --git a/drivers/staging/cg2900/include/cg2900_hci.h b/drivers/staging/cg2900/include/cg2900_hci.h new file mode 100644 index 00000000000..e094a9dddbd --- /dev/null +++ b/drivers/staging/cg2900/include/cg2900_hci.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * This file is a staging solution and shall be integrated into + * /include/net/bluetooth/hci.h. + */ + +#ifndef __CG2900_HCI_H +#define __CG2900_HCI_H + +#define HCI_EV_HW_ERROR 0x10 +struct hci_ev_hw_error { + __u8 hw_code; +} __packed; + +#endif /* __CG2900_HCI_H */ diff --git a/drivers/staging/cg2900/mfd/Makefile b/drivers/staging/cg2900/mfd/Makefile new file mode 100644 index 00000000000..bdbd8de90ee --- /dev/null +++ b/drivers/staging/cg2900/mfd/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for ST-Ericsson CG2900 connectivity combo controller +# + +ccflags-y := \ + -Idrivers/staging/cg2900/include + +obj-$(CONFIG_CG2900) += cg2900_core.o cg2900_lib.o +export-objs := cg2900_core.o cg2900_lib.o + +obj-$(CONFIG_CG2900) += cg2900_char_devices.o + +obj-$(CONFIG_CG2900_TEST) += cg2900_test.o + +obj-$(CONFIG_CG2900_CHIP) += cg2900_chip.o +obj-$(CONFIG_STLC2690_CHIP) += stlc2690_chip.o + +obj-$(CONFIG_CG2900_AUDIO) += cg2900_audio.o diff --git a/drivers/staging/cg2900/mfd/cg2900_audio.c b/drivers/staging/cg2900/mfd/cg2900_audio.c new file mode 100644 index 00000000000..2f00d79ed1e --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_audio.c @@ -0,0 +1,3486 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth Audio Driver for ST-Ericsson CG2900 controller. + */ +#define NAME "cg2900_audio" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/types.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> + +#include "cg2900.h" +#include "cg2900_audio.h" +#include "cg2900_chip.h" + +#define MAX_NBR_OF_USERS 10 +#define FIRST_USER 1 + +/* + * This is a default ACL handle. It is necessary to provide to the chip, but + * does not actually do anything. + */ +#define DEFAULT_ACL_HANDLE 0x0001 + +/* Use a timeout of 5 seconds when waiting for a command response */ +#define RESP_TIMEOUT 5000 + +#define BT_DEV (info->dev_bt) +#define FM_DEV (info->dev_fm) + +/* Bluetooth error codes */ +#define HCI_BT_ERROR_NO_ERROR 0x00 + +/* Used to select proper API, ignoring subrevisions etc */ +enum chip_revision { + CHIP_REV_PG1, + CHIP_REV_PG2 +}; + +/** + * enum chip_resp_state - State when communicating with the CG2900 controller. + * @IDLE: No outstanding packets to the controller. + * @WAITING: Packet has been sent to the controller. Waiting for + * response. + * @RESP_RECEIVED: Response from controller has been received but not yet + * handled. + */ +enum chip_resp_state { + IDLE, + WAITING, + RESP_RECEIVED +}; + +/** + * enum main_state - Main state for the CG2900 Audio driver. + * @OPENED: Audio driver has registered to CG2900 Core. + * @CLOSED: Audio driver is not registered to CG2900 Core. + * @RESET: A reset of CG2900 Core has occurred and no user has re-opened + * the audio driver. + */ +enum main_state { + OPENED, + CLOSED, + RESET +}; + +/** + * struct endpoint_list - List for storing endpoint configuration nodes. + * @ep_list: Pointer to first node in list. + * @management_mutex: Mutex for handling access to list. + */ +struct endpoint_list { + struct list_head ep_list; + struct mutex management_mutex; +}; + +/** + * struct endpoint_config_node - Node for storing endpoint configuration. + * @list: list_head struct. + * @endpoint_id: Endpoint ID. + * @config: Stored configuration for this endpoint. + */ +struct endpoint_config_node { + struct list_head list; + enum cg2900_audio_endpoint_id endpoint_id; + union cg2900_endpoint_config_union config; +}; + +/** + * struct audio_info - Main CG2900 Audio driver info structure. + * @list: list_head struct. + * @state: Current state of the CG2900 Audio driver. + * @revision: Chip revision, used to select API. + * @misc_dev: The misc device created by this driver. + * @misc_registered: True if misc device is registered. + * @parent: Parent device. + * @dev_bt: Device registered by this driver for the BT + * audio channel. + * @dev_fm: Device registered by this driver for the FM + * audio channel. + * @filp: Current char device file pointer. + * @management_mutex: Mutex for handling access to CG2900 Audio driver + * management. + * @bt_mutex: Mutex for handling access to BT audio channel. + * @fm_mutex: Mutex for handling access to FM audio channel. + * @nbr_of_users_active: Number of sessions open in the CG2900 Audio + * driver. + * @i2s_config: DAI I2S configuration. + * @i2s_pcm_config: DAI PCM_I2S configuration. + * @i2s_config_known: @true if @i2s_config has been set, + * @false otherwise. + * @i2s_pcm_config_known: @true if @i2s_pcm_config has been set, + * @false otherwise. + * @endpoints: List containing the endpoint configurations. + * @stream_ids: Bitmask for in-use stream ids (only used with + * PG2 chip API). + */ +struct audio_info { + struct list_head list; + enum main_state state; + enum chip_revision revision; + struct miscdevice misc_dev; + bool misc_registered; + struct device *parent; + struct device *dev_bt; + struct device *dev_fm; + struct file *filp; + struct mutex management_mutex; + struct mutex bt_mutex; + struct mutex fm_mutex; + int nbr_of_users_active; + struct cg2900_dai_conf_i2s i2s_config; + struct cg2900_dai_conf_i2s_pcm i2s_pcm_config; + bool i2s_config_known; + bool i2s_pcm_config_known; + struct endpoint_list endpoints; + u8 stream_ids[16]; +}; + +/** + * struct audio_user - CG2900 audio user info structure. + * @session: Stored session for the char device. + * @resp_state: State for controller communications. + * @info: CG2900 audio info structure. + */ +struct audio_user { + int session; + enum chip_resp_state resp_state; + struct audio_info *info; +}; + +/** + * struct audio_cb_info - Callback info structure registered in @user_data. + * @user: Audio user currently awaiting data on the channel. + * @wq: Wait queue for this channel. + * @skb_queue: Sk buffer queue. + */ +struct audio_cb_info { + struct audio_user *user; + wait_queue_head_t wq; + struct sk_buff_head skb_queue; +}; + +/** + * struct char_dev_info - CG2900 character device info structure. + * @session: Stored session for the char device. + * @stored_data: Data returned when executing last command, if any. + * @stored_data_len: Length of @stored_data in bytes. + * @management_mutex: Mutex for handling access to char dev management. + * @rw_mutex: Mutex for handling access to char dev writes and reads. + * @info: CG2900 audio info struct. + * @rx_queue: Data queue. + */ +struct char_dev_info { + int session; + u8 *stored_data; + int stored_data_len; + struct mutex management_mutex; + struct mutex rw_mutex; + struct audio_info *info; + struct sk_buff_head rx_queue; +}; + +/* + * cg2900_audio_devices - List of active CG2900 audio devices. + */ +LIST_HEAD(cg2900_audio_devices); + +/* + * cg2900_audio_sessions - Pointers to currently opened sessions (maps + * session ID to user info). + */ +static struct audio_user *cg2900_audio_sessions[MAX_NBR_OF_USERS]; + +/* + * Internal conversion functions + * + * Since the CG2900 APIs uses several different ways to encode the + * same parameter in different cases, we have to use translator + * functions. + */ + +/** + * session_config_sample_rate() - Convert sample rate to format used in VS_Set_SessionConfiguration. + * @rate: Sample rate in API encoding. + */ +static u8 session_config_sample_rate(enum cg2900_endpoint_sample_rate rate) +{ + static const u8 codes[] = { + [ENDPOINT_SAMPLE_RATE_8_KHZ] = CG2900_BT_SESSION_RATE_8K, + [ENDPOINT_SAMPLE_RATE_16_KHZ] = CG2900_BT_SESSION_RATE_16K, + [ENDPOINT_SAMPLE_RATE_44_1_KHZ] = CG2900_BT_SESSION_RATE_44_1K, + [ENDPOINT_SAMPLE_RATE_48_KHZ] = CG2900_BT_SESSION_RATE_48K + }; + + return codes[rate]; +} + +/** + * mc_i2s_sample_rate() - Convert sample rate to format used in VS_Port_Config for I2S. + * @rate: Sample rate in API encoding. + */ +static u8 mc_i2s_sample_rate(enum cg2900_dai_sample_rate rate) +{ + static const u8 codes[] = { + [SAMPLE_RATE_8] = CG2900_MC_I2S_SAMPLE_RATE_8, + [SAMPLE_RATE_16] = CG2900_MC_I2S_SAMPLE_RATE_16, + [SAMPLE_RATE_44_1] = CG2900_MC_I2S_SAMPLE_RATE_44_1, + [SAMPLE_RATE_48] = CG2900_MC_I2S_SAMPLE_RATE_48 + }; + + return codes[rate]; +} + +/** + * mc_pcm_sample_rate() - Convert sample rate to format used in VS_Port_Config for PCM/I2S. + * @rate: Sample rate in API encoding. + */ +static u8 mc_pcm_sample_rate(enum cg2900_dai_sample_rate rate) +{ + static const u8 codes[] = { + [SAMPLE_RATE_8] = CG2900_MC_PCM_SAMPLE_RATE_8, + [SAMPLE_RATE_16] = CG2900_MC_PCM_SAMPLE_RATE_16, + [SAMPLE_RATE_44_1] = CG2900_MC_PCM_SAMPLE_RATE_44_1, + [SAMPLE_RATE_48] = CG2900_MC_PCM_SAMPLE_RATE_48 + }; + + return codes[rate]; +} + +/** + * mc_i2s_channel_select() - Convert channel selection to format used in VS_Port_Config. + * @sel: Channel selection in API encoding. + */ +static u8 mc_i2s_channel_select(enum cg2900_dai_channel_sel sel) +{ + static const u8 codes[] = { + [CHANNEL_SELECTION_RIGHT] = CG2900_MC_I2S_RIGHT_CHANNEL, + [CHANNEL_SELECTION_LEFT] = CG2900_MC_I2S_LEFT_CHANNEL, + [CHANNEL_SELECTION_BOTH] = CG2900_MC_I2S_BOTH_CHANNELS + }; + return codes[sel]; +} + +/** + * get_fs_duration() - Convert framesync-enumeration to real value. + * @duration: Framsync duration (API encoding). + * + * Returns: + * Duration in bits. + */ +static u16 get_fs_duration(enum cg2900_dai_fs_duration duration) +{ + static const u16 values[] = { + [SYNC_DURATION_8] = 8, + [SYNC_DURATION_16] = 16, + [SYNC_DURATION_24] = 24, + [SYNC_DURATION_32] = 32, + [SYNC_DURATION_48] = 48, + [SYNC_DURATION_50] = 50, + [SYNC_DURATION_64] = 64, + [SYNC_DURATION_75] = 75, + [SYNC_DURATION_96] = 96, + [SYNC_DURATION_125] = 125, + [SYNC_DURATION_128] = 128, + [SYNC_DURATION_150] = 150, + [SYNC_DURATION_192] = 192, + [SYNC_DURATION_250] = 250, + [SYNC_DURATION_256] = 256, + [SYNC_DURATION_300] = 300, + [SYNC_DURATION_384] = 384, + [SYNC_DURATION_500] = 500, + [SYNC_DURATION_512] = 512, + [SYNC_DURATION_600] = 600, + [SYNC_DURATION_768] = 768 + }; + return values[duration]; +} + +/** + * mc_i2s_role() - Convert master/slave encoding to format for I2S-ports. + * @mode: Master/slave in API encoding. + */ +static u8 mc_i2s_role(enum cg2900_dai_mode mode) +{ + if (mode == DAI_MODE_SLAVE) + return CG2900_I2S_MODE_SLAVE; + else + return CG2900_I2S_MODE_MASTER; +} + +/** + * mc_pcm_role() - Convert master/slave encoding to format for PCM/I2S-port. + * @mode: Master/slave in API encoding. + */ +static u8 mc_pcm_role(enum cg2900_dai_mode mode) +{ + if (mode == DAI_MODE_SLAVE) + return CG2900_PCM_MODE_SLAVE; + else + return CG2900_PCM_MODE_MASTER; +} + +/** + * fm_get_conversion() - Convert sample rate to convert up/down used in X_Set_Control FM commands. + * @srate: Sample rate. + */ +static u16 fm_get_conversion(enum cg2900_endpoint_sample_rate srate) +{ + if (srate >= ENDPOINT_SAMPLE_RATE_44_1_KHZ) + return CG2900_FM_CMD_SET_CTRL_CONV_UP; + else + return CG2900_FM_CMD_SET_CTRL_CONV_DOWN; +} + +/** + * get_info() - Return info structure for this device. + * @dev: Current device. + * + * This function returns the info structure on the following basis: + * * If dev is NULL return first info struct found. If none is found return + * NULL. + * * If dev is valid we will return corresponding info struct if dev is the + * parent of the info struct or if dev's parent is the parent of the info + * struct. + * * If dev is valid and no info structure is found, a new info struct is + * allocated, initialized, and returned. + * + * Returns: + * Pointer to info struct if there is no error. + * NULL if NULL was supplied and no info structure exist. + * ERR_PTR(-ENOMEM) if allocation fails. + */ +static struct audio_info *get_info(struct device *dev) +{ + struct list_head *cursor; + struct audio_info *tmp; + struct audio_info *info = NULL; + + /* + * Find the info structure for dev. If NULL is supplied for dev + * just return first device found. + */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + if (!dev || tmp->parent == dev->parent || tmp->parent == dev) { + info = tmp; + break; + } + } + + if (!dev || info) + return info; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev, "Could not allocate info struct\n"); + return ERR_PTR(-ENOMEM); + } + info->parent = dev->parent; + + /* Initiate the mutexes */ + mutex_init(&(info->management_mutex)); + mutex_init(&(info->bt_mutex)); + mutex_init(&(info->fm_mutex)); + mutex_init(&(info->endpoints.management_mutex)); + + /* Initiate the endpoint list */ + INIT_LIST_HEAD(&info->endpoints.ep_list); + + list_add_tail(&info->list, &cg2900_audio_devices); + + dev_info(dev, "CG2900 device added\n"); + return info; +} + +/** + * flush_endpoint_list() - Deletes all stored endpoints in @list. + * @list: List of endpoints. + */ +static void flush_endpoint_list(struct endpoint_list *list) +{ + struct list_head *cursor, *next; + struct endpoint_config_node *tmp; + + mutex_lock(&list->management_mutex); + list_for_each_safe(cursor, next, &(list->ep_list)) { + tmp = list_entry(cursor, struct endpoint_config_node, list); + list_del(cursor); + kfree(tmp); + } + mutex_unlock(&list->management_mutex); +} + +/** + * device_removed() - Remove device from list if there are no channels left. + * @info: CG2900 audio info structure. + */ +static void device_removed(struct audio_info *info) +{ + struct list_head *cursor; + struct audio_info *tmp; + + if (info->dev_bt || info->dev_fm) + /* There are still devices active */ + return; + + /* Find the stored info structure */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + if (tmp == info) { + list_del(cursor); + break; + } + } + + flush_endpoint_list(&info->endpoints); + + mutex_destroy(&info->management_mutex); + mutex_destroy(&info->bt_mutex); + mutex_destroy(&info->fm_mutex); + mutex_destroy(&info->endpoints.management_mutex); + + kfree(info); + pr_info("CG2900 Audio device removed"); +} + +/** + * read_cb() - Handle data received from STE connectivity driver. + * @dev: Device receiving data. + * @skb: Buffer with data coming form device. + */ +static void read_cb(struct cg2900_user_data *dev, struct sk_buff *skb) +{ + struct audio_cb_info *cb_info; + + cb_info = cg2900_get_usr(dev); + + if (!(cb_info->user)) { + dev_err(dev->dev, "NULL supplied as cb_info->user\n"); + return; + } + + /* Mark that packet has been received */ + dev_dbg(dev->dev, "New resp_state: RESP_RECEIVED"); + cb_info->user->resp_state = RESP_RECEIVED; + skb_queue_tail(&cb_info->skb_queue, skb); + wake_up_all(&cb_info->wq); +} + +/** + * reset_cb() - Reset callback function. + * @dev: CG2900_Core device resetting. + */ +static void reset_cb(struct cg2900_user_data *dev) +{ + struct audio_info *info; + + dev_dbg(dev->dev, "reset_cb\n"); + + info = dev_get_drvdata(dev->dev); + mutex_lock(&info->management_mutex); + info->nbr_of_users_active = 0; + info->state = RESET; + mutex_unlock(&info->management_mutex); +} + +/** + * get_session_user() - Check that supplied session is within valid range. + * @session: Session ID. + * + * Returns: + * Audio_user if there is no error. + * NULL for bad session ID. + */ +static struct audio_user *get_session_user(int session) +{ + struct audio_user *audio_user; + + if (session < FIRST_USER || session >= MAX_NBR_OF_USERS) { + pr_err("Calling with invalid session %d", session); + return NULL; + } + + audio_user = cg2900_audio_sessions[session]; + if (!audio_user) + pr_err("Calling with non-opened session %d", session); + return audio_user; +} + +/** + * del_endpoint_private() - Deletes an endpoint from @list. + * @endpoint_id: Endpoint ID. + * @list: List of endpoints. + * + * Deletes an endpoint from the supplied endpoint list. + * This function is not protected by any semaphore. + */ +static void del_endpoint_private(enum cg2900_audio_endpoint_id endpoint_id, + struct endpoint_list *list) +{ + struct list_head *cursor, *next; + struct endpoint_config_node *tmp; + + list_for_each_safe(cursor, next, &(list->ep_list)) { + tmp = list_entry(cursor, struct endpoint_config_node, list); + if (tmp->endpoint_id == endpoint_id) { + list_del(cursor); + kfree(tmp); + } + } +} + +/** + * add_endpoint() - Add endpoint node to @list. + * @ep_config: Endpoint configuration. + * @list: List of endpoints. + * + * Add endpoint node to the supplied list and copies supplied config to node. + * If a node already exists for the supplied endpoint, the old node is removed + * and replaced by the new node. + */ +static void add_endpoint(struct cg2900_endpoint_config *ep_config, + struct endpoint_list *list) +{ + struct endpoint_config_node *item; + + item = kzalloc(sizeof(*item), GFP_KERNEL); + if (!item) { + pr_err("add_endpoint: Failed to alloc memory"); + return; + } + + /* Store values */ + item->endpoint_id = ep_config->endpoint_id; + memcpy(&(item->config), &(ep_config->config), sizeof(item->config)); + + mutex_lock(&(list->management_mutex)); + + /* + * Check if endpoint ID already exist in list. + * If that is the case, remove it. + */ + if (!list_empty(&(list->ep_list))) + del_endpoint_private(ep_config->endpoint_id, list); + + list_add_tail(&(item->list), &(list->ep_list)); + + mutex_unlock(&(list->management_mutex)); +} + +/** + * find_endpoint() - Finds endpoint identified by @endpoint_id in @list. + * @endpoint_id: Endpoint ID. + * @list: List of endpoints. + * + * Returns: + * Endpoint configuration if there is no error. + * NULL if no configuration can be found for @endpoint_id. + */ +static union cg2900_endpoint_config_union * +find_endpoint(enum cg2900_audio_endpoint_id endpoint_id, + struct endpoint_list *list) +{ + struct list_head *cursor, *next; + struct endpoint_config_node *tmp; + struct endpoint_config_node *ret_ep = NULL; + + mutex_lock(&list->management_mutex); + list_for_each_safe(cursor, next, &(list->ep_list)) { + tmp = list_entry(cursor, struct endpoint_config_node, list); + if (tmp->endpoint_id == endpoint_id) { + ret_ep = tmp; + break; + } + } + mutex_unlock(&list->management_mutex); + + if (ret_ep) + return &(ret_ep->config); + else + return NULL; +} + +/** + * new_stream_id() - Allocate a new stream id. + * @info: Current audio info struct. + * + * Returns: + * 0-127 new valid id. + * -ENOMEM if no id is available. + */ +static s8 new_stream_id(struct audio_info *info) +{ + int r; + + mutex_lock(&info->management_mutex); + + r = find_first_zero_bit(info->stream_ids, + 8 * sizeof(info->stream_ids)); + + if (r >= 8 * sizeof(info->stream_ids)) { + r = -ENOMEM; + goto out; + } + + set_bit(r, (unsigned long int *)info->stream_ids); + +out: + mutex_unlock(&info->management_mutex); + return r; +} + +/** + * release_stream_id() - Release a stream id. + * @info: Current audio info struct. + * @id: Stream to release. + */ +static void release_stream_id(struct audio_info *info, u8 id) +{ + if (id >= 8 * sizeof(info->stream_ids)) + return; + + mutex_lock(&info->management_mutex); + clear_bit(id, (unsigned long int *)info->stream_ids); + mutex_unlock(&info->management_mutex); +} + +/** + * receive_fm_write_response() - Wait for and handle the response to an FM Legacy WriteCommand request. + * @audio_user: Audio user to check for. + * @command: FM command to wait for. + * + * This function first waits (up to 5 seconds) for a response to an FM + * write command and when one arrives, it checks that it is the one we + * are waiting for and also that no error has occurred. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int receive_fm_write_response(struct audio_user *audio_user, + u16 command) +{ + int err = 0; + int res; + struct sk_buff *skb; + struct fm_leg_cmd_cmpl *pkt; + u16 rsp_cmd; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = audio_user->info; + pf_data = dev_get_platdata(info->dev_fm); + cb_info = cg2900_get_usr(pf_data); + + /* + * Wait for callback to receive command complete and then wake us up + * again. + */ + res = wait_event_timeout(cb_info->wq, + audio_user->resp_state == RESP_RECEIVED, + msecs_to_jiffies(RESP_TIMEOUT)); + if (!res) { + dev_err(FM_DEV, "Timeout while waiting for return packet\n"); + return -ECOMM; + } else if (res < 0) { + dev_err(FM_DEV, + "Error %d occurred while waiting for return packet\n", + res); + return -ECOMM; + } + + /* OK, now we should have received answer. Let's check it. */ + skb = skb_dequeue_tail(&cb_info->skb_queue); + if (!skb) { + dev_err(FM_DEV, "No skb in queue when it should be there\n"); + return -EIO; + } + + pkt = (struct fm_leg_cmd_cmpl *)skb->data; + + /* Check if we received the correct event */ + if (pkt->opcode != CG2900_FM_GEN_ID_LEGACY) { + dev_err(FM_DEV, + "Received unknown FM packet. 0x%X %X %X %X %X\n", + skb->data[0], skb->data[1], skb->data[2], + skb->data[3], skb->data[4]); + err = -EIO; + goto error_handling_free_skb; + } + + /* FM Legacy Command complete event */ + rsp_cmd = cg2900_get_fm_cmd_id(le16_to_cpu(pkt->response_head)); + + if (pkt->fm_function != CG2900_FM_CMD_PARAM_WRITECOMMAND || + rsp_cmd != command) { + dev_err(FM_DEV, + "Received unexpected packet func 0x%X cmd 0x%04X\n", + pkt->fm_function, rsp_cmd); + err = -EIO; + goto error_handling_free_skb; + } + + if (pkt->cmd_status != CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED) { + dev_err(FM_DEV, "FM Command failed (%d)\n", pkt->cmd_status); + err = -EIO; + goto error_handling_free_skb; + } + /* Operation succeeded. We are now done */ + +error_handling_free_skb: + kfree_skb(skb); + return err; +} + +/** + * receive_bt_cmd_complete() - Wait for and handle an BT Command Complete event. + * @audio_user: Audio user to check for. + * @rsp: Opcode of BT command to wait for. + * @data: Pointer to buffer if any received data should be stored (except + * status). + * @data_len: Length of @data in bytes. + * + * This function first waits for BT Command Complete event (up to 5 seconds) + * and when one arrives, it checks that it is the one we are waiting for and + * also that no error has occurred. + * If @data is supplied it also copies received data into @data. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int receive_bt_cmd_complete(struct audio_user *audio_user, u16 rsp, + void *data, int data_len) +{ + int err = 0; + int res; + struct sk_buff *skb; + struct bt_cmd_cmpl_event *evt; + u16 opcode; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = audio_user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + /* + * Wait for callback to receive command complete and then wake us up + * again. + */ + res = wait_event_timeout(cb_info->wq, + audio_user->resp_state == RESP_RECEIVED, + msecs_to_jiffies(RESP_TIMEOUT)); + if (!res) { + dev_err(BT_DEV, "Timeout while waiting for return packet\n"); + return -ECOMM; + } else if (res < 0) { + /* We timed out or an error occurred */ + dev_err(BT_DEV, + "Error %d occurred while waiting for return packet\n", + res); + return -ECOMM; + } + + /* OK, now we should have received answer. Let's check it. */ + skb = skb_dequeue_tail(&cb_info->skb_queue); + if (!skb) { + dev_err(BT_DEV, "No skb in queue when it should be there\n"); + return -EIO; + } + + evt = (struct bt_cmd_cmpl_event *)skb->data; + if (evt->eventcode != HCI_EV_CMD_COMPLETE) { + dev_err(BT_DEV, + "We did not receive the event we expected (0x%X)\n", + evt->eventcode); + err = -EIO; + goto error_handling_free_skb; + } + + opcode = le16_to_cpu(evt->opcode); + if (opcode != rsp) { + dev_err(BT_DEV, + "Received cmd complete for unexpected command: " + "0x%04X\n", opcode); + err = -EIO; + goto error_handling_free_skb; + } + + if (evt->status != HCI_BT_ERROR_NO_ERROR) { + dev_err(BT_DEV, "Received command complete with err %d\n", + evt->status); + err = -EIO; + /* + * In data there might be more detailed error code. + * Let's copy it. + */ + } + + /* + * Copy the rest of the parameters if a buffer has been supplied. + * The caller must have set the length correctly. + */ + if (data) + memcpy(data, evt->data, data_len); + + /* Operation succeeded. We are now done */ + +error_handling_free_skb: + kfree_skb(skb); + return err; +} + +/** + * send_vs_delete_stream() - Delete an audio stream defined by @stream_handle. + * @audio_user: Audio user to check for. + * @stream_handle: Handle of the audio stream. + * + * This function is used to delete an audio stream defined by a stream + * handle. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * Errors from @cg2900_write. + * -EIO for other errors. + */ +static int send_vs_delete_stream(struct audio_user *audio_user, + unsigned int stream_handle) +{ + int err = 0; + struct sk_buff *skb; + u16 opcode; + struct audio_info *info = audio_user->info; + struct cg2900_user_data *pf_data = dev_get_platdata(info->dev_bt); + struct audio_cb_info *cb_info = cg2900_get_usr(pf_data); + + /* Now delete the stream - format command... */ + if (info->revision == CHIP_REV_PG1) { + struct bt_vs_reset_session_cfg_cmd *cmd; + + dev_dbg(BT_DEV, "BT: HCI_VS_Reset_Session_Configuration\n"); + + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "Could not allocate skb\n"); + err = -ENOMEM; + return err; + } + + cmd = (struct bt_vs_reset_session_cfg_cmd *) + skb_put(skb, sizeof(*cmd)); + + opcode = CG2900_BT_VS_RESET_SESSION_CONFIG; + cmd->opcode = cpu_to_le16(opcode); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd)); + cmd->id = (u8)stream_handle; + } else { + struct mc_vs_delete_stream_cmd *cmd; + + dev_dbg(BT_DEV, "BT: HCI_VS_Delete_Stream\n"); + + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "Could not allocate skb\n"); + err = -ENOMEM; + return err; + } + + cmd = (struct mc_vs_delete_stream_cmd *) + skb_put(skb, sizeof(*cmd)); + + opcode = CG2900_MC_VS_DELETE_STREAM; + cmd->opcode = cpu_to_le16(opcode); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd)); + cmd->stream = (u8)stream_handle; + } + + /* ...and send it */ + cb_info->user = audio_user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + audio_user->resp_state = WAITING; + + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + goto error_handling_free_skb; + } + + /* wait for response */ + if (info->revision == CHIP_REV_PG1) { + err = receive_bt_cmd_complete(audio_user, opcode, NULL, 0); + } else { + u8 vs_err; + + /* All commands in PG2 API returns one byte extra status */ + err = receive_bt_cmd_complete(audio_user, opcode, + &vs_err, sizeof(vs_err)); + + if (err) + dev_err(BT_DEV, + "VS_DELETE_STREAM - failed with error 0x%02X\n", + vs_err); + else + release_stream_id(info, stream_handle); + } + + return err; + +error_handling_free_skb: + kfree_skb(skb); + return err; +} + +/** + * send_vs_session_ctrl() - Formats an sends a CG2900_BT_VS_SESSION_CTRL command. + * @user: Audio user this command belongs to. + * @stream_handle: Handle to stream. + * @command: Command to execute on stream, should be one of + * CG2900_BT_SESSION_START, CG2900_BT_SESSION_STOP, + * CG2900_BT_SESSION_PAUSE, CG2900_BT_SESSION_RESUME. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_session_ctrl(struct audio_user *user, + u8 stream_handle, u8 command) +{ + int err = 0; + struct bt_vs_session_ctrl_cmd *pkt; + struct sk_buff *skb; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "BT: HCI_VS_Session_Control handle: %d cmd: %d\n", + stream_handle, command); + + skb = pf_data->alloc_skb(sizeof(*pkt), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, + "send_vs_session_ctrl: Could not allocate skb\n"); + return -ENOMEM; + } + + /* Enter data into the skb */ + pkt = (struct bt_vs_session_ctrl_cmd *) skb_put(skb, sizeof(*pkt)); + + pkt->opcode = cpu_to_le16(CG2900_BT_VS_SESSION_CTRL); + pkt->plen = BT_PARAM_LEN(sizeof(*pkt)); + pkt->id = stream_handle; + pkt->control = command; /* Start/stop etc */ + + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + err = receive_bt_cmd_complete(user, CG2900_BT_VS_SESSION_CTRL, + NULL, 0); +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * send_vs_session_config() - Formats an sends a CG2900_BT_VS_SESSION_CONFIG command. + * @user: Audio user this command belongs to. + * @config_stream: Custom function for configuring the stream. + * @priv_data: Private data passed to @config_stream untouched. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Space is allocated for one stream and a custom function is used to + * fill in the stream configuration. + * + * Returns: + * 0-255 stream handle if no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_session_config(struct audio_user *user, + void(*config_stream)(struct audio_info *, void *, + struct session_config_stream *), + void *priv_data) +{ + int err = 0; + struct sk_buff *skb; + struct bt_vs_session_config_cmd *pkt; + u8 session_id; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "BT: HCI_VS_Set_Session_Configuration\n"); + + skb = pf_data->alloc_skb(sizeof(*pkt), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, + "send_vs_session_config: Could not allocate skb\n"); + return -ENOMEM; + } + + pkt = (struct bt_vs_session_config_cmd *)skb_put(skb, sizeof(*pkt)); + /* zero the packet so we don't have to set all reserved fields */ + memset(pkt, 0, sizeof(*pkt)); + + /* Common parameters */ + pkt->opcode = cpu_to_le16(CG2900_BT_VS_SET_SESSION_CONFIG); + pkt->plen = BT_PARAM_LEN(sizeof(*pkt)); + pkt->n_streams = 1; /* 1 stream configuration supplied */ + + /* Let the custom-function fill in the rest */ + config_stream(info, priv_data, &pkt->stream); + + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + err = receive_bt_cmd_complete(user, + CG2900_BT_VS_SET_SESSION_CONFIG, + &session_id, sizeof(session_id)); + /* Return session id/stream handle if success */ + if (!err) + err = session_id; + +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * send_fm_write_1_param() - Formats and sends an FM legacy write command with one parameter. + * @user: Audio user this command belongs to. + * @command: Command. + * @param: Parameter for command. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the fm_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_fm_write_1_param(struct audio_user *user, + u16 command, u16 param) +{ + int err = 0; + struct sk_buff *skb; + struct fm_leg_cmd *cmd; + size_t len; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_fm); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(FM_DEV, "send_fm_write_1_param cmd 0x%X param 0x%X\n", + command, param); + + /* base package + one parameter */ + len = sizeof(*cmd) + sizeof(cmd->fm_cmd.data[0]); + + skb = pf_data->alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(FM_DEV, + "send_fm_write_1_param: Could not allocate skb\n"); + return -ENOMEM; + } + + cmd = (struct fm_leg_cmd *)skb_put(skb, len); + + cmd->length = CG2900_FM_CMD_PARAM_LEN(len); + cmd->opcode = CG2900_FM_GEN_ID_LEGACY; + cmd->read_write = CG2900_FM_CMD_LEG_PARAM_WRITE; + cmd->fm_function = CG2900_FM_CMD_PARAM_WRITECOMMAND; + /* one parameter - builtin assumption for this function */ + cmd->fm_cmd.head = cpu_to_le16(cg2900_make_fm_cmd_id(command, 1)); + cmd->fm_cmd.data[0] = cpu_to_le16(param); + + cb_info->user = user; + dev_dbg(FM_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(FM_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + err = receive_fm_write_response(user, command); +finished: + dev_dbg(FM_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * send_vs_stream_ctrl() - Formats an sends a CG2900_MC_VS_STREAM_CONTROL command. + * @user: Audio user this command belongs to. + * @stream: Stream id. + * @command: Start/stop etc. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * While the HCI command allows for multiple streams in one command, + * this function only handles one. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_stream_ctrl(struct audio_user *user, u8 stream, u8 command) +{ + int err = 0; + struct sk_buff *skb; + struct mc_vs_stream_ctrl_cmd *cmd; + size_t len; + u8 vs_err; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "send_vs_stream_ctrl stream %d command %d\n", stream, + command); + + /* basic length + one stream */ + len = sizeof(*cmd) + sizeof(cmd->stream[0]); + + skb = pf_data->alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "send_vs_stream_ctrl:Could not allocate skb\n"); + return -ENOMEM; + } + + cmd = (struct mc_vs_stream_ctrl_cmd *)skb_put(skb, len); + + cmd->opcode = cpu_to_le16(CG2900_MC_VS_STREAM_CONTROL); + cmd->plen = BT_PARAM_LEN(len); + cmd->command = command; + + /* one stream */ + cmd->n_streams = 1; + cmd->stream[0] = stream; + + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + /* All commands in PG2 API returns one byte with extra status */ + err = receive_bt_cmd_complete(user, + CG2900_MC_VS_STREAM_CONTROL, + &vs_err, sizeof(vs_err)); + if (err) + dev_err(BT_DEV, + "VS_STREAM_CONTROL - failed with error 0x%02x\n", + vs_err); + +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * send_vs_create_stream() - Formats an sends a CG2900_MC_VS_CREATE_STREAM command. + * @user: Audio user this command belongs to. + * @inport: Stream id. + * @outport: Start/stop etc. + * @order: Activation order. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_create_stream(struct audio_user *user, u8 inport, + u8 outport, u8 order) +{ + int err = 0; + struct sk_buff *skb; + struct mc_vs_create_stream_cmd *cmd; + s8 id; + u8 vs_err; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, + "send_vs_create_stream inport %d outport %d order %d\n", + inport, outport, order); + + id = new_stream_id(info); + if (id < 0) { + dev_err(BT_DEV, "No free stream id\n"); + err = -EIO; + goto finished; + } + + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, + "send_vs_create_stream: Could not allocate skb\n"); + err = -ENOMEM; + goto finished_release_id; + } + + cmd = (struct mc_vs_create_stream_cmd *)skb_put(skb, sizeof(*cmd)); + + cmd->opcode = cpu_to_le16(CG2900_MC_VS_CREATE_STREAM); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd)); + cmd->id = (u8)id; + cmd->inport = inport; + cmd->outport = outport; + cmd->order = order; + + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished_release_id; + } + + /* All commands in PG2 API returns one byte with extra status */ + err = receive_bt_cmd_complete(user, + CG2900_MC_VS_CREATE_STREAM, + &vs_err, sizeof(vs_err)); + if (err) { + dev_err(BT_DEV, + "VS_CREATE_STREAM - failed with error 0x%02x\n", + vs_err); + goto finished_release_id; + } + + err = id; + goto finished; + +finished_release_id: + release_stream_id(info, id); +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * send_vs_port_cfg() - Formats an sends a CG2900_MC_VS_PORT_CONFIG command. + * @user: Audio user this command belongs to. + * @port: Port id to configure. + * @cfg: Pointer to specific configuration. + * @cfglen: Length of configuration. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_port_cfg(struct audio_user *user, u8 port, + const void *cfg, size_t cfglen) +{ + int err = 0; + struct sk_buff *skb; + struct mc_vs_port_cfg_cmd *cmd; + void *ptr; + u8 vs_err; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "send_vs_port_cfg len %d\n", cfglen); + + skb = pf_data->alloc_skb(sizeof(*cmd) + cfglen, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "send_vs_port_cfg: Could not allocate skb\n"); + return -ENOMEM; + } + + /* Fill in common part */ + cmd = (struct mc_vs_port_cfg_cmd *) skb_put(skb, sizeof(*cmd)); + cmd->opcode = cpu_to_le16(CG2900_MC_VS_PORT_CONFIG); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd) + cfglen); + cmd->type = port; + + /* Copy specific configuration */ + ptr = skb_put(skb, cfglen); + memcpy(ptr, cfg, cfglen); + + /* Send */ + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + /* All commands in PG2 API returns one byte with extra status */ + err = receive_bt_cmd_complete(user, CG2900_MC_VS_PORT_CONFIG, + &vs_err, sizeof(vs_err)); + if (err) + dev_err(BT_DEV, "VS_PORT_CONFIG - failed with error 0x%02x\n", + vs_err); + +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * set_dai_config_pg1() - Internal implementation of @cg2900_audio_set_dai_config for PG1 hardware. + * @audio_user: Pointer to audio user struct. + * @config: Pointer to the configuration to set. + * + * Sets the Digital Audio Interface (DAI) configuration for PG1 + * hardware. This is and internal function and basic + * argument-verification should have been done by the caller. + * + * Returns: + * 0 if there is no error. + * -EACCESS if port is not supported. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int set_dai_config_pg1(struct audio_user *audio_user, + struct cg2900_dai_config *config) +{ + int err = 0; + struct cg2900_dai_conf_i2s_pcm *i2s_pcm; + struct sk_buff *skb = NULL; + struct bt_vs_set_hw_cfg_cmd_i2s *i2s_cmd; + struct bt_vs_set_hw_cfg_cmd_pcm *pcm_cmd; + struct audio_info *info = audio_user->info; + struct cg2900_user_data *pf_data = dev_get_platdata(info->dev_bt); + struct audio_cb_info *cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "set_dai_config_pg1 port %d\n", config->port); + + /* + * Use mutex to assure that only ONE command is sent at any time on + * each channel. + */ + mutex_lock(&info->bt_mutex); + + /* Allocate the sk_buffer. The length is actually a max length since + * length varies depending on logical transport. + */ + skb = pf_data->alloc_skb(CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG, + GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "set_dai_config_pg1: Could not allocate skb\n"); + err = -ENOMEM; + goto finished_unlock_mutex; + } + + /* Fill in hci-command according to received configuration */ + switch (config->port) { + case PORT_0_I2S: + i2s_cmd = (struct bt_vs_set_hw_cfg_cmd_i2s *) + skb_put(skb, sizeof(*i2s_cmd)); + + i2s_cmd->opcode = cpu_to_le16(CG2900_BT_VS_SET_HARDWARE_CONFIG); + i2s_cmd->plen = BT_PARAM_LEN(sizeof(*i2s_cmd)); + + i2s_cmd->vp_type = PORT_PROTOCOL_I2S; + i2s_cmd->port_id = 0x00; /* First/only I2S port */ + i2s_cmd->half_period = config->conf.i2s.half_period; + + i2s_cmd->master_slave = mc_i2s_role(config->conf.i2s.mode); + + /* Store the new configuration */ + mutex_lock(&info->management_mutex); + memcpy(&info->i2s_config, &config->conf.i2s, + sizeof(config->conf.i2s)); + info->i2s_config_known = true; + mutex_unlock(&info->management_mutex); + break; + + case PORT_1_I2S_PCM: + pcm_cmd = (struct bt_vs_set_hw_cfg_cmd_pcm *) + skb_put(skb, sizeof(*pcm_cmd)); + + pcm_cmd->opcode = cpu_to_le16(CG2900_BT_VS_SET_HARDWARE_CONFIG); + pcm_cmd->plen = BT_PARAM_LEN(sizeof(*pcm_cmd)); + + i2s_pcm = &config->conf.i2s_pcm; + + /* + * PG1 chips don't support I2S over the PCM/I2S bus, + * and PG2 chips don't use this command + */ + if (i2s_pcm->protocol != PORT_PROTOCOL_PCM) { + dev_err(BT_DEV, + "I2S not supported over the PCM/I2S bus\n"); + err = -EACCES; + goto error_handling_free_skb; + } + + pcm_cmd->vp_type = PORT_PROTOCOL_PCM; + pcm_cmd->port_id = 0x00; /* First/only PCM port */ + + HWCONFIG_PCM_SET_MODE(pcm_cmd, mc_pcm_role(i2s_pcm->mode)); + + HWCONFIG_PCM_SET_DIR(pcm_cmd, 0, i2s_pcm->slot_0_dir); + HWCONFIG_PCM_SET_DIR(pcm_cmd, 1, i2s_pcm->slot_1_dir); + HWCONFIG_PCM_SET_DIR(pcm_cmd, 2, i2s_pcm->slot_2_dir); + HWCONFIG_PCM_SET_DIR(pcm_cmd, 3, i2s_pcm->slot_3_dir); + + pcm_cmd->bit_clock = i2s_pcm->clk; + pcm_cmd->frame_len = + cpu_to_le16(get_fs_duration(i2s_pcm->duration)); + + /* Store the new configuration */ + mutex_lock(&info->management_mutex); + memcpy(&info->i2s_pcm_config, &config->conf.i2s_pcm, + sizeof(config->conf.i2s_pcm)); + info->i2s_pcm_config_known = true; + mutex_unlock(&info->management_mutex); + break; + + default: + dev_err(BT_DEV, "Unknown port configuration %d\n", + config->port); + err = -EACCES; + goto error_handling_free_skb; + }; + + cb_info->user = audio_user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + audio_user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + goto error_handling_free_skb; + } + + err = receive_bt_cmd_complete(audio_user, + CG2900_BT_VS_SET_HARDWARE_CONFIG, + NULL, 0); + + goto finished_unlock_mutex; + +error_handling_free_skb: + kfree_skb(skb); +finished_unlock_mutex: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + return err; +} + +/** + * set_dai_config_pg2() - Internal implementation of @cg2900_audio_set_dai_config for PG2 hardware. + * @audio_user: Pointer to audio user struct. + * @config: Pointer to the configuration to set. + * + * Sets the Digital Audio Interface (DAI) configuration for PG2 + * hardware. This is an internal function and basic + * argument-verification should have been done by the caller. + * + * Returns: + * 0 if there is no error. + * -EACCESS if port is not supported. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int set_dai_config_pg2(struct audio_user *audio_user, + struct cg2900_dai_config *config) +{ + int err = 0; + struct cg2900_dai_conf_i2s *i2s; + struct cg2900_dai_conf_i2s_pcm *i2s_pcm; + + struct mc_vs_port_cfg_i2s i2s_cfg; + struct mc_vs_port_cfg_pcm_i2s pcm_cfg; + struct audio_info *info = audio_user->info; + + dev_dbg(BT_DEV, "set_dai_config_pg2 port %d\n", config->port); + + /* + * Use mutex to assure that only ONE command is sent at any time on + * each channel. + */ + mutex_lock(&info->bt_mutex); + + switch (config->port) { + case PORT_0_I2S: + i2s = &config->conf.i2s; + + memset(&i2s_cfg, 0, sizeof(i2s_cfg)); /* just to be safe */ + + /* master/slave */ + PORTCFG_I2S_SET_ROLE(i2s_cfg, mc_i2s_role(i2s->mode)); + + PORTCFG_I2S_SET_HALFPERIOD(i2s_cfg, i2s->half_period); + PORTCFG_I2S_SET_CHANNELS(i2s_cfg, + mc_i2s_channel_select(i2s->channel_sel)); + PORTCFG_I2S_SET_SRATE(i2s_cfg, + mc_i2s_sample_rate(i2s->sample_rate)); + switch (i2s->word_width) { + case WORD_WIDTH_16: + PORTCFG_I2S_SET_WORDLEN(i2s_cfg, CG2900_MC_I2S_WORD_16); + break; + case WORD_WIDTH_32: + PORTCFG_I2S_SET_WORDLEN(i2s_cfg, CG2900_MC_I2S_WORD_32); + break; + } + + /* Store the new configuration */ + mutex_lock(&info->management_mutex); + memcpy(&(info->i2s_config), &(config->conf.i2s), + sizeof(config->conf.i2s)); + info->i2s_config_known = true; + mutex_unlock(&info->management_mutex); + + /* Send */ + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_I2S, + &i2s_cfg, sizeof(i2s_cfg)); + break; + + case PORT_1_I2S_PCM: + i2s_pcm = &config->conf.i2s_pcm; + + memset(&pcm_cfg, 0, sizeof(pcm_cfg)); /* just to be safe */ + + /* master/slave */ + PORTCFG_PCM_SET_ROLE(pcm_cfg, mc_pcm_role(i2s_pcm->mode)); + + /* set direction for all 4 slots */ + PORTCFG_PCM_SET_DIR(pcm_cfg, 0, i2s_pcm->slot_0_dir); + PORTCFG_PCM_SET_DIR(pcm_cfg, 1, i2s_pcm->slot_1_dir); + PORTCFG_PCM_SET_DIR(pcm_cfg, 2, i2s_pcm->slot_2_dir); + PORTCFG_PCM_SET_DIR(pcm_cfg, 3, i2s_pcm->slot_3_dir); + + /* set used SCO slots, other use cases not supported atm */ + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 0, i2s_pcm->slot_0_used); + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 1, i2s_pcm->slot_1_used); + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 2, i2s_pcm->slot_2_used); + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 3, i2s_pcm->slot_3_used); + + /* slot starts */ + pcm_cfg.slot_start[0] = i2s_pcm->slot_0_start; + pcm_cfg.slot_start[1] = i2s_pcm->slot_1_start; + pcm_cfg.slot_start[2] = i2s_pcm->slot_2_start; + pcm_cfg.slot_start[3] = i2s_pcm->slot_3_start; + + /* audio/voice sample-rate ratio */ + PORTCFG_PCM_SET_RATIO(pcm_cfg, i2s_pcm->ratio); + + /* PCM or I2S mode */ + PORTCFG_PCM_SET_MODE(pcm_cfg, i2s_pcm->protocol); + + pcm_cfg.frame_len = i2s_pcm->duration; + + PORTCFG_PCM_SET_BITCLK(pcm_cfg, i2s_pcm->clk); + PORTCFG_PCM_SET_SRATE(pcm_cfg, + mc_pcm_sample_rate(i2s_pcm->sample_rate)); + + /* Store the new configuration */ + mutex_lock(&info->management_mutex); + memcpy(&(info->i2s_pcm_config), &(config->conf.i2s_pcm), + sizeof(config->conf.i2s_pcm)); + info->i2s_pcm_config_known = true; + mutex_unlock(&info->management_mutex); + + /* Send */ + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_PCM_I2S, + &pcm_cfg, sizeof(pcm_cfg)); + break; + + default: + dev_err(BT_DEV, "Unknown port configuration %d\n", + config->port); + err = -EACCES; + }; + + mutex_unlock(&info->bt_mutex); + return err; +} + +/** + * struct i2s_fm_stream_config_priv - Helper struct for stream i2s-fm streams. + * @fm_config: FM endpoint configuration. + * @rx: true for FM-RX, false for FM-TX. + */ +struct i2s_fm_stream_config_priv { + struct cg2900_endpoint_config_fm *fm_config; + bool rx; + +}; + +/** + * config_i2s_fm_stream() - Callback for @send_vs_session_config. + * @info: Audio info structure. + * @_priv: Pointer to a @i2s_fm_stream_config_priv struct. + * @cfg: Pointer to stream config block in command packet. + * + * Fills in stream configuration for I2S-FM RX/TX. + */ + +static void config_i2s_fm_stream(struct audio_info *info, void *_priv, + struct session_config_stream *cfg) +{ + struct i2s_fm_stream_config_priv *priv = _priv; + struct session_config_vport *fm; + struct session_config_vport *i2s; + + cfg->media_type = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO; + + if (info->i2s_config.channel_sel == CHANNEL_SELECTION_BOTH) + SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_STEREO); + else + SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_MONO); + + SESSIONCFG_I2S_SET_SRATE(cfg, + session_config_sample_rate(priv->fm_config->sample_rate)); + + cfg->codec_type = CG2900_CODEC_TYPE_NONE; + /* codec mode and parameters not used */ + + if (priv->rx) { + fm = &cfg->inport; /* FM is input */ + i2s = &cfg->outport; /* I2S is output */ + } else { + i2s = &cfg->inport; /* I2S is input */ + fm = &cfg->outport; /* FM is output */ + } + + fm->type = CG2900_BT_VP_TYPE_FM; + + i2s->type = CG2900_BT_VP_TYPE_I2S; + i2s->i2s.index = CG2900_BT_SESSION_I2S_INDEX_I2S; + i2s->i2s.channel = info->i2s_config.channel_sel; +} + +/** + * conn_start_i2s_to_fm_rx() - Start an audio stream connecting FM RX to I2S. + * @audio_user: Audio user to check for. + * @stream_handle: [out] Pointer where to store the stream handle. + * + * This function sets up an FM RX to I2S stream. + * It does this by first setting the output mode and then the configuration of + * the External Sample Rate Converter. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * -EIO for other errors. + */ +static int conn_start_i2s_to_fm_rx(struct audio_user *audio_user, + unsigned int *stream_handle) +{ + int err = 0; + union cg2900_endpoint_config_union *fm_config; + struct audio_info *info = audio_user->info; + + dev_dbg(FM_DEV, "conn_start_i2s_to_fm_rx\n"); + + fm_config = find_endpoint(ENDPOINT_FM_RX, &info->endpoints); + if (!fm_config) { + dev_err(FM_DEV, "FM RX not configured before stream start\n"); + return -EIO; + } + + if (!(info->i2s_config_known)) { + dev_err(FM_DEV, + "I2S DAI not configured before stream start\n"); + return -EIO; + } + + /* + * Use mutex to assure that only ONE command is sent at any + * time on each channel. + */ + mutex_lock(&info->fm_mutex); + mutex_lock(&info->bt_mutex); + + /* + * Now set the output mode of the External Sample Rate Converter by + * sending HCI_Write command with AUP_EXT_SetMode. + */ + err = send_fm_write_1_param(audio_user, + CG2900_FM_CMD_ID_AUP_EXT_SET_MODE, + CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL); + if (err) + goto finished_unlock_mutex; + + /* + * Now configure the External Sample Rate Converter by sending + * HCI_Write command with AUP_EXT_SetControl. + */ + err = send_fm_write_1_param( + audio_user, CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL, + fm_get_conversion(fm_config->fm.sample_rate)); + if (err) + goto finished_unlock_mutex; + + /* Set up the stream */ + if (info->revision == CHIP_REV_PG1) { + struct i2s_fm_stream_config_priv stream_priv; + + /* Now send HCI_VS_Set_Session_Configuration command */ + stream_priv.fm_config = &fm_config->fm; + stream_priv.rx = true; + err = send_vs_session_config(audio_user, config_i2s_fm_stream, + &stream_priv); + } else { + struct mc_vs_port_cfg_fm fm_cfg; + + memset(&fm_cfg, 0, sizeof(fm_cfg)); + + /* Configure port FM RX */ + /* Expects 0-3 - same as user API - so no conversion needed */ + PORTCFG_FM_SET_SRATE(fm_cfg, (u8)fm_config->fm.sample_rate); + + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_FM_RX_1, + &fm_cfg, sizeof(fm_cfg)); + if (err) + goto finished_unlock_mutex; + + /* CreateStream */ + err = send_vs_create_stream(audio_user, + CG2900_MC_PORT_FM_RX_1, + CG2900_MC_PORT_I2S, + 0); /* chip doesn't care */ + } + + if (err < 0) + goto finished_unlock_mutex; + + /* Store the stream handle (used for start and stop stream) */ + *stream_handle = (u8)err; + dev_dbg(FM_DEV, "stream_handle set to %d\n", *stream_handle); + + /* Now start the stream */ + if (info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, *stream_handle, + CG2900_BT_SESSION_START); + else + err = send_vs_stream_ctrl(audio_user, *stream_handle, + CG2900_MC_STREAM_START); + /*Let's delete a stream.*/ + if (err < 0) { + dev_dbg(BT_DEV, "Could not start a stream."); + (void)send_vs_delete_stream(audio_user, *stream_handle); + } + +finished_unlock_mutex: + dev_dbg(FM_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + mutex_unlock(&info->fm_mutex); + return err; +} + +/** + * conn_start_i2s_to_fm_tx() - Start an audio stream connecting FM TX to I2S. + * @audio_user: Audio user to check for. + * @stream_handle: [out] Pointer where to store the stream handle. + * + * This function sets up an I2S to FM TX stream. + * It does this by first setting the Audio Input source and then setting the + * configuration and input source of BT sample rate converter. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * -EIO for other errors. + */ +static int conn_start_i2s_to_fm_tx(struct audio_user *audio_user, + unsigned int *stream_handle) +{ + int err = 0; + union cg2900_endpoint_config_union *fm_config; + struct audio_info *info = audio_user->info; + + dev_dbg(FM_DEV, "conn_start_i2s_to_fm_tx\n"); + + fm_config = find_endpoint(ENDPOINT_FM_TX, &info->endpoints); + if (!fm_config) { + dev_err(FM_DEV, "FM TX not configured before stream start\n"); + return -EIO; + } + + if (!(info->i2s_config_known)) { + dev_err(FM_DEV, + "I2S DAI not configured before stream start\n"); + return -EIO; + } + + /* + * Use mutex to assure that only ONE command is sent at any time + * on each channel. + */ + mutex_lock(&info->fm_mutex); + mutex_lock(&info->bt_mutex); + + /* + * Select Audio Input Source by sending HCI_Write command with + * AIP_SetMode. + */ + dev_dbg(FM_DEV, "FM: AIP_SetMode\n"); + err = send_fm_write_1_param(audio_user, CG2900_FM_CMD_ID_AIP_SET_MODE, + CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG); + if (err) + goto finished_unlock_mutex; + + /* + * Now configure the BT sample rate converter by sending HCI_Write + * command with AIP_BT_SetControl. + */ + dev_dbg(FM_DEV, "FM: AIP_BT_SetControl\n"); + err = send_fm_write_1_param( + audio_user, CG2900_FM_CMD_ID_AIP_BT_SET_CTRL, + fm_get_conversion(fm_config->fm.sample_rate)); + if (err) + goto finished_unlock_mutex; + + /* + * Now set input of the BT sample rate converter by sending HCI_Write + * command with AIP_BT_SetMode. + */ + dev_dbg(FM_DEV, "FM: AIP_BT_SetMode\n"); + err = send_fm_write_1_param(audio_user, + CG2900_FM_CMD_ID_AIP_BT_SET_MODE, + CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR); + if (err) + goto finished_unlock_mutex; + + /* Set up the stream */ + if (info->revision == CHIP_REV_PG1) { + struct i2s_fm_stream_config_priv stream_priv; + + /* Now send HCI_VS_Set_Session_Configuration command */ + stream_priv.fm_config = &fm_config->fm; + stream_priv.rx = false; + err = send_vs_session_config(audio_user, config_i2s_fm_stream, + &stream_priv); + } else { + struct mc_vs_port_cfg_fm fm_cfg; + + memset(&fm_cfg, 0, sizeof(fm_cfg)); + + /* Configure port FM TX */ + /* Expects 0-3 - same as user API - so no conversion needed */ + PORTCFG_FM_SET_SRATE(fm_cfg, (u8)fm_config->fm.sample_rate); + + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_FM_TX, + &fm_cfg, sizeof(fm_cfg)); + if (err) + goto finished_unlock_mutex; + + /* CreateStream */ + err = send_vs_create_stream(audio_user, + CG2900_MC_PORT_I2S, + CG2900_MC_PORT_FM_TX, + 0); /* chip doesn't care */ + } + + if (err < 0) + goto finished_unlock_mutex; + + /* Store the stream handle (used for start and stop stream) */ + *stream_handle = (u8)err; + dev_dbg(FM_DEV, "stream_handle set to %d\n", *stream_handle); + + /* Now start the stream */ + if (info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, *stream_handle, + CG2900_BT_SESSION_START); + else + err = send_vs_stream_ctrl(audio_user, *stream_handle, + CG2900_MC_STREAM_START); + /* Let's delete and release stream.*/ + if (err < 0) { + dev_dbg(BT_DEV, "Could not start a stream."); + (void)send_vs_delete_stream(audio_user, *stream_handle); + } + +finished_unlock_mutex: + dev_dbg(FM_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + mutex_unlock(&info->fm_mutex); + return err; +} + +/** + * config_pcm_sco_stream() - Callback for @send_vs_session_config. + * @info: Audio info structure. + * @_priv: Pointer to a @cg2900_endpoint_config_sco_in_out struct. + * @cfg: Pointer to stream config block in command packet. + * + * Fills in stream configuration for PCM-SCO. + */ +static void config_pcm_sco_stream(struct audio_info *info, void *_priv, + struct session_config_stream *cfg) +{ + struct cg2900_endpoint_config_sco_in_out *sco_ep = _priv; + + cfg->media_type = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO; + + SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_MONO); + SESSIONCFG_I2S_SET_SRATE(cfg, + session_config_sample_rate(sco_ep->sample_rate)); + + cfg->codec_type = CG2900_CODEC_TYPE_NONE; + /* codec mode and parameters not used */ + + cfg->inport.type = CG2900_BT_VP_TYPE_BT_SCO; + cfg->inport.sco.acl_handle = cpu_to_le16(DEFAULT_ACL_HANDLE); + + cfg->outport.type = CG2900_BT_VP_TYPE_PCM; + cfg->outport.pcm.index = CG2900_BT_SESSION_PCM_INDEX_PCM_I2S; + + SESSIONCFG_PCM_SET_USED(cfg->outport, 0, + info->i2s_pcm_config.slot_0_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 1, + info->i2s_pcm_config.slot_1_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 2, + info->i2s_pcm_config.slot_2_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 3, + info->i2s_pcm_config.slot_3_used); + + cfg->outport.pcm.slot_start[0] = + info->i2s_pcm_config.slot_0_start; + cfg->outport.pcm.slot_start[1] = + info->i2s_pcm_config.slot_1_start; + cfg->outport.pcm.slot_start[2] = + info->i2s_pcm_config.slot_2_start; + cfg->outport.pcm.slot_start[3] = + info->i2s_pcm_config.slot_3_start; +} + +/** + * conn_start_pcm_to_sco() - Start an audio stream connecting Bluetooth (e)SCO to PCM_I2S. + * @audio_user: Audio user to check for. + * @stream_handle: [out] Pointer where to store the stream handle. + * + * This function sets up a BT to_from PCM_I2S stream. It does this by + * first setting the Session configuration and then starting the Audio + * Stream. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * Errors from @cg2900_write + * -EIO for other errors. + */ +static int conn_start_pcm_to_sco(struct audio_user *audio_user, + unsigned int *stream_handle) +{ + int err = 0; + union cg2900_endpoint_config_union *bt_config; + struct audio_info *info = audio_user->info; + + dev_dbg(BT_DEV, "conn_start_pcm_to_sco\n"); + + bt_config = find_endpoint(ENDPOINT_BT_SCO_INOUT, &info->endpoints); + if (!bt_config) { + dev_err(BT_DEV, "BT not configured before stream start\n"); + return -EIO; + } + + if (!(info->i2s_pcm_config_known)) { + dev_err(BT_DEV, + "I2S_PCM DAI not configured before stream start\n"); + return -EIO; + } + + /* + * Use mutex to assure that only ONE command is sent at any time on each + * channel. + */ + mutex_lock(&info->bt_mutex); + + /* Set up the stream */ + if (info->revision == CHIP_REV_PG1) { + err = send_vs_session_config(audio_user, config_pcm_sco_stream, + &bt_config->sco); + } else { + struct mc_vs_port_cfg_sco sco_cfg; + + /* zero codec params etc */ + memset(&sco_cfg, 0, sizeof(sco_cfg)); + sco_cfg.acl_id = DEFAULT_ACL_HANDLE; + PORTCFG_SCO_SET_CODEC(sco_cfg, CG2900_CODEC_TYPE_NONE); + + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_BT_SCO, + &sco_cfg, sizeof(sco_cfg)); + if (err) + goto finished_unlock_mutex; + + /* CreateStream */ + err = send_vs_create_stream(audio_user, + CG2900_MC_PORT_PCM_I2S, + CG2900_MC_PORT_BT_SCO, + 0); /* chip doesn't care */ + } + + if (err < 0) + goto finished_unlock_mutex; + + /* Store the stream handle (used for start and stop stream) */ + *stream_handle = (u8)err; + dev_dbg(BT_DEV, "stream_handle set to %d\n", *stream_handle); + + /* Now start the stream */ + if (info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, *stream_handle, + CG2900_BT_SESSION_START); + else + err = send_vs_stream_ctrl(audio_user, *stream_handle, + CG2900_MC_STREAM_START); + /* Let's delete and release stream.*/ + if (err < 0) { + dev_dbg(BT_DEV, "Could not start a stream."); + (void)send_vs_delete_stream(audio_user, *stream_handle); + } + +finished_unlock_mutex: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + return err; +} + +/** + * conn_stop_stream() - Stops an audio stream defined by @stream_handle. + * @audio_user: Audio user to check for. + * @stream_handle: Handle of the audio stream. + * + * This function is used to stop an audio stream defined by a stream + * handle. It does this by first stopping the stream and then + * resetting the session/stream. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * Errors from @cg2900_write. + * -EIO for other errors. + */ +static int conn_stop_stream(struct audio_user *audio_user, + unsigned int stream_handle) +{ + int err = 0; + struct audio_info *info = audio_user->info; + + dev_dbg(BT_DEV, "conn_stop_stream handle %d\n", stream_handle); + + /* + * Use mutex to assure that only ONE command is sent at any + * time on each channel. + */ + mutex_lock(&info->bt_mutex); + + /* Now stop the stream */ + if (info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, stream_handle, + CG2900_BT_SESSION_STOP); + else + err = send_vs_stream_ctrl(audio_user, stream_handle, + CG2900_MC_STREAM_STOP); + if (err) + goto finished_unlock_mutex; + + err = send_vs_delete_stream(audio_user, stream_handle); + +finished_unlock_mutex: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + return err; +} + +/** + * cg2900_audio_get_devices() - Returns connected CG2900 Audio devices. + * @devices: Array of CG2900 Audio devices. + * @size: Max number of devices in array. + * + * Returns: + * 0 if no devices exist. + * > 0 is the number of devices inserted in the list. + * -EINVAL upon bad input parameter. + */ +int cg2900_audio_get_devices(struct device *devices[], __u8 size) +{ + struct list_head *cursor; + struct audio_info *tmp; + int i = 0; + + if (!size) { + pr_err("No space to insert devices into list\n"); + return 0; + } + + if (!devices) { + pr_err("NULL submitted as devices array\n"); + return -EINVAL; + } + + /* + * Go through and store the devices. If NULL is supplied for dev + * just return first device found. + */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + devices[i] = tmp->parent; + i++; + if (i == size) + break; + } + return i; +} +EXPORT_SYMBOL_GPL(cg2900_audio_get_devices); + +/** + * cg2900_audio_open() - Opens a session to the ST-Ericsson CG2900 Audio control interface. + * @session: [out] Address where to store the session identifier. + * Allocated by caller, must not be NULL. + * @parent: Parent device representing the CG2900 controller connected. + * If NULL is supplied the first available device is used. + * + * Returns: + * 0 if there is no error. + * -EACCES if no info structure can be found. + * -EINVAL upon bad input parameter. + * -ENOMEM upon allocation failure. + * -EMFILE if no more user session could be opened. + * -EIO upon failure to register to CG2900. + * Error codes from get_info. + */ +int cg2900_audio_open(unsigned int *session, struct device *parent) +{ + int err = 0; + int i; + struct audio_info *info; + struct cg2900_user_data *pf_data_bt; + struct cg2900_user_data *pf_data_fm; + + pr_debug("cg2900_audio_open"); + + info = get_info(parent); + if (!info) { + pr_err("No audio info exist"); + return -EACCES; + } else if (IS_ERR(info)) + return PTR_ERR(info); + + if (!session) { + pr_err("NULL supplied as session"); + return -EINVAL; + } + + mutex_lock(&info->management_mutex); + + *session = 0; + + /* + * First find a free session to use and allocate the session structure. + */ + for (i = FIRST_USER; + i < MAX_NBR_OF_USERS && cg2900_audio_sessions[i]; + i++) + ; /* Just loop until found or end reached */ + + if (i >= MAX_NBR_OF_USERS) { + pr_err("Couldn't find free user"); + err = -EMFILE; + goto finished; + } + + cg2900_audio_sessions[i] = + kzalloc(sizeof(*(cg2900_audio_sessions[0])), GFP_KERNEL); + if (!cg2900_audio_sessions[i]) { + pr_err("Could not allocate user"); + err = -ENOMEM; + goto finished; + } + pr_debug("Found free session %d", i); + *session = i; + info->nbr_of_users_active++; + + cg2900_audio_sessions[*session]->resp_state = IDLE; + cg2900_audio_sessions[*session]->session = *session; + cg2900_audio_sessions[*session]->info = info; + + pf_data_bt = dev_get_platdata(info->dev_bt); + pf_data_fm = dev_get_platdata(info->dev_fm); + + if (info->nbr_of_users_active == 1) { + struct cg2900_rev_data rev_data; + + /* + * First user so register to CG2900 Core. + * First the BT audio device. + */ + err = pf_data_bt->open(pf_data_bt); + if (err) { + dev_err(BT_DEV, "Failed to open BT audio channel\n"); + goto error_handling; + } + + /* Then the FM audio device */ + err = pf_data_fm->open(pf_data_fm); + if (err) { + dev_err(FM_DEV, "Failed to open FM audio channel\n"); + goto error_handling; + } + + /* Read chip revision data */ + if (!pf_data_bt->get_local_revision(pf_data_bt, &rev_data)) { + pr_err("Couldn't retrieve revision data"); + err = -EIO; + goto error_handling; + } + + /* Decode revision data */ + switch (rev_data.revision) { + case CG2900_PG1_REV: + case CG2900_PG1_SPECIAL_REV: + info->revision = CHIP_REV_PG1; + break; + + case CG2900_PG2_REV: + info->revision = CHIP_REV_PG2; + break; + + default: + pr_err("Chip rev 0x%04X sub 0x%04X not supported", + rev_data.revision, rev_data.sub_version); + err = -EIO; + goto error_handling; + } + + info->state = OPENED; + } + + pr_info("Session %d opened", *session); + + goto finished; + +error_handling: + if (pf_data_fm->opened) + pf_data_fm->close(pf_data_fm); + if (pf_data_bt->opened) + pf_data_bt->close(pf_data_bt); + info->nbr_of_users_active--; + kfree(cg2900_audio_sessions[*session]); + cg2900_audio_sessions[*session] = NULL; +finished: + mutex_unlock(&info->management_mutex); + return err; +} +EXPORT_SYMBOL_GPL(cg2900_audio_open); + +/** + * cg2900_audio_close() - Closes an opened session to the ST-Ericsson CG2900 audio control interface. + * @session: [in_out] Pointer to session identifier to close. + * Will be 0 after this call. + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter. + * -EIO if driver has not been opened. + * -EACCES if session has not opened. + */ +int cg2900_audio_close(unsigned int *session) +{ + int err = 0; + struct audio_user *audio_user; + struct audio_info *info; + struct cg2900_user_data *pf_data_bt; + struct cg2900_user_data *pf_data_fm; + + pr_debug("cg2900_audio_close"); + + if (!session) { + pr_err("NULL pointer supplied"); + return -EINVAL; + } + + audio_user = get_session_user(*session); + if (!audio_user) { + pr_err("Invalid session ID"); + return -EINVAL; + } + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + mutex_lock(&info->management_mutex); + + pf_data_bt = dev_get_platdata(info->dev_bt); + pf_data_fm = dev_get_platdata(info->dev_fm); + + if (!cg2900_audio_sessions[*session]) { + dev_err(BT_DEV, "Session %d not opened\n", *session); + err = -EACCES; + goto err_unlock_mutex; + } + + kfree(cg2900_audio_sessions[*session]); + cg2900_audio_sessions[*session] = NULL; + + info->nbr_of_users_active--; + if (info->nbr_of_users_active == 0) { + /* No more sessions open. Close channels */ + pf_data_fm->close(pf_data_fm); + pf_data_bt->close(pf_data_bt); + info->state = CLOSED; + } + + dev_info(BT_DEV, "Session %d closed\n", *session); + + *session = 0; + +err_unlock_mutex: + mutex_unlock(&info->management_mutex); + return err; +} +EXPORT_SYMBOL_GPL(cg2900_audio_close); + +/** + * cg2900_audio_set_dai_config() - Sets the Digital Audio Interface configuration. + * @session: Session identifier this call is related to. + * @config: Pointer to the configuration to set. + * Allocated by caller, must not be NULL. + * + * Sets the Digital Audio Interface (DAI) configuration. The DAI is the external + * interface between the combo chip and the platform. + * For example the PCM or I2S interface. + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter. + * -EIO if driver has not been opened. + * -ENOMEM upon allocation failure. + * -EACCES if trying to set unsupported configuration. + * Errors from @receive_bt_cmd_complete. + */ +int cg2900_audio_set_dai_config(unsigned int session, + struct cg2900_dai_config *config) +{ + int err = 0; + struct audio_user *audio_user; + struct audio_info *info; + + pr_debug("cg2900_audio_set_dai_config session %d", session); + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + /* Different commands are used for PG1 and PG2 */ + if (info->revision == CHIP_REV_PG1) + err = set_dai_config_pg1(audio_user, config); + else if (info->revision == CHIP_REV_PG2) + err = set_dai_config_pg2(audio_user, config); + + return err; +} +EXPORT_SYMBOL_GPL(cg2900_audio_set_dai_config); + +/** + * cg2900_audio_get_dai_config() - Gets the current Digital Audio Interface configuration. + * @session: Session identifier this call is related to. + * @config: [out] Pointer to the configuration to get. + * Allocated by caller, must not be NULL. + * + * Gets the current Digital Audio Interface configuration. Currently this method + * can only be called after some one has called + * cg2900_audio_set_dai_config(), there is today no way of getting + * the static settings file parameters from this method. + * Note that the @port parameter within @config must be set when calling this + * function so that the ST-Ericsson CG2900 Audio driver will know which + * configuration to return. + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter. + * -EIO if driver has not been opened or configuration has not been set. + */ +int cg2900_audio_get_dai_config(unsigned int session, + struct cg2900_dai_config *config) +{ + int err = 0; + struct audio_user *audio_user; + struct audio_info *info; + + pr_debug("cg2900_audio_get_dai_config session %d", session); + + if (!config) { + pr_err("NULL supplied as config structure"); + return -EINVAL; + } + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + /* + * Return DAI configuration based on the received port. + * If port has not been configured return error. + */ + switch (config->port) { + case PORT_0_I2S: + mutex_lock(&info->management_mutex); + if (info->i2s_config_known) + memcpy(&config->conf.i2s, + &info->i2s_config, + sizeof(config->conf.i2s)); + else + err = -EIO; + mutex_unlock(&info->management_mutex); + break; + + case PORT_1_I2S_PCM: + mutex_lock(&info->management_mutex); + if (info->i2s_pcm_config_known) + memcpy(&config->conf.i2s_pcm, + &info->i2s_pcm_config, + sizeof(config->conf.i2s_pcm)); + else + err = -EIO; + mutex_unlock(&info->management_mutex); + break; + + default: + dev_err(BT_DEV, "Unknown port configuration %d\n", + config->port); + err = -EIO; + break; + }; + + return err; +} +EXPORT_SYMBOL_GPL(cg2900_audio_get_dai_config); + +/** + * cg2900_audio_config_endpoint() - Configures one endpoint in the combo chip's audio system. + * @session: Session identifier this call is related to. + * @config: Pointer to the endpoint's configuration structure. + * + * Configures one endpoint in the combo chip's audio system. + * Supported @endpoint_id values are: + * * ENDPOINT_BT_SCO_INOUT + * * ENDPOINT_BT_A2DP_SRC + * * ENDPOINT_FM_RX + * * ENDPOINT_FM_TX + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter. + * -EIO if driver has not been opened. + * -EACCES if supplied cg2900_dai_config struct contains not supported + * endpoint_id. + */ +int cg2900_audio_config_endpoint(unsigned int session, + struct cg2900_endpoint_config *config) +{ + struct audio_user *audio_user; + struct audio_info *info; + + pr_debug("cg2900_audio_config_endpoint\n"); + + if (!config) { + pr_err("NULL supplied as configuration structure"); + return -EINVAL; + } + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + switch (config->endpoint_id) { + case ENDPOINT_BT_SCO_INOUT: + case ENDPOINT_BT_A2DP_SRC: + case ENDPOINT_FM_RX: + case ENDPOINT_FM_TX: + add_endpoint(config, &info->endpoints); + break; + + case ENDPOINT_PORT_0_I2S: + case ENDPOINT_PORT_1_I2S_PCM: + case ENDPOINT_SLIMBUS_VOICE: + case ENDPOINT_SLIMBUS_AUDIO: + case ENDPOINT_BT_A2DP_SNK: + case ENDPOINT_ANALOG_OUT: + case ENDPOINT_DSP_AUDIO_IN: + case ENDPOINT_DSP_AUDIO_OUT: + case ENDPOINT_DSP_VOICE_IN: + case ENDPOINT_DSP_VOICE_OUT: + case ENDPOINT_DSP_TONE_IN: + case ENDPOINT_BURST_BUFFER_IN: + case ENDPOINT_BURST_BUFFER_OUT: + case ENDPOINT_MUSIC_DECODER: + case ENDPOINT_HCI_AUDIO_IN: + default: + dev_err(BT_DEV, "Unsupported endpoint_id %d\n", + config->endpoint_id); + return -EACCES; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cg2900_audio_config_endpoint); + +static bool is_dai_port(enum cg2900_audio_endpoint_id ep) +{ + /* These are the only supported ones */ + return (ep == ENDPOINT_PORT_0_I2S) || (ep == ENDPOINT_PORT_1_I2S_PCM); +} + +/** + * cg2900_audio_start_stream() - Connects two endpoints and starts the audio stream. + * @session: Session identifier this call is related to. + * @ep_1: One of the endpoints, no relation to direction or role. + * @ep_2: The other endpoint, no relation to direction or role. + * @stream_handle: Pointer where to store the stream handle. + * Allocated by caller, must not be NULL. + * + * Connects two endpoints and starts the audio stream. + * Note that the endpoints need to be configured before the stream is started; + * DAI endpoints, such as ENDPOINT_PORT_0_I2S, are + * configured through @cg2900_audio_set_dai_config() while other + * endpoints are configured through @cg2900_audio_config_endpoint(). + * + * Supported @endpoint_id values are: + * * ENDPOINT_PORT_0_I2S + * * ENDPOINT_PORT_1_I2S_PCM + * * ENDPOINT_BT_SCO_INOUT + * * ENDPOINT_FM_RX + * * ENDPOINT_FM_TX + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter or unsupported configuration. + * -EIO if driver has not been opened. + * Errors from @conn_start_i2s_to_fm_rx, @conn_start_i2s_to_fm_tx, and + * @conn_start_pcm_to_sco. + */ +int cg2900_audio_start_stream(unsigned int session, + enum cg2900_audio_endpoint_id ep_1, + enum cg2900_audio_endpoint_id ep_2, + unsigned int *stream_handle) +{ + int err; + struct audio_user *audio_user; + struct audio_info *info; + + pr_debug("cg2900_audio_start_stream session %d ep_1 %d ep_2 %d", + session, ep_1, ep_2); + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + /* Put digital interface in ep_1 to simplify comparison below */ + if (!is_dai_port(ep_1)) { + /* Swap endpoints */ + enum cg2900_audio_endpoint_id t = ep_1; + ep_1 = ep_2; + ep_2 = t; + } + + if (ep_1 == ENDPOINT_PORT_1_I2S_PCM && ep_2 == ENDPOINT_BT_SCO_INOUT) { + err = conn_start_pcm_to_sco(audio_user, stream_handle); + } else if (ep_1 == ENDPOINT_PORT_0_I2S && ep_2 == ENDPOINT_FM_RX) { + err = conn_start_i2s_to_fm_rx(audio_user, stream_handle); + } else if (ep_1 == ENDPOINT_PORT_0_I2S && ep_2 == ENDPOINT_FM_TX) { + err = conn_start_i2s_to_fm_tx(audio_user, stream_handle); + } else { + dev_err(BT_DEV, "Endpoint config not handled: ep1: %d, " + "ep2: %d\n", ep_1, ep_2); + err = -EINVAL; + } + + return err; +} +EXPORT_SYMBOL_GPL(cg2900_audio_start_stream); + +/** + * cg2900_audio_stop_stream() - Stops a stream and disconnects the endpoints. + * @session: Session identifier this call is related to. + * @stream_handle: Handle to the stream to stop. + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter. + * -EIO if driver has not been opened. + */ +int cg2900_audio_stop_stream(unsigned int session, unsigned int stream_handle) +{ + struct audio_user *audio_user; + struct audio_info *info; + + pr_debug("cg2900_audio_stop_stream handle %d", stream_handle); + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + return conn_stop_stream(audio_user, stream_handle); +} +EXPORT_SYMBOL_GPL(cg2900_audio_stop_stream); + +/** + * audio_dev_open() - Open char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation failed. + * Errors from @cg2900_audio_open. + */ +static int audio_dev_open(struct inode *inode, struct file *filp) +{ + int err; + struct char_dev_info *char_dev_info; + int minor; + struct audio_info *info = NULL; + struct audio_info *tmp; + struct list_head *cursor; + + pr_debug("audio_dev_open"); + + minor = iminor(inode); + + /* Find the info struct for this file */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + if (tmp->misc_dev.minor == minor) { + info = tmp; + break; + } + } + if (!info) { + pr_err("Could not identify device in inode"); + return -EINVAL; + } + + /* + * Allocate the char dev info structure. It will be stored inside + * the file pointer and supplied when file_ops are called. + * It's free'd in audio_dev_release. + */ + char_dev_info = kzalloc(sizeof(*char_dev_info), GFP_KERNEL); + if (!char_dev_info) { + dev_err(BT_DEV, "Couldn't allocate char_dev_info\n"); + return -ENOMEM; + } + filp->private_data = char_dev_info; + char_dev_info->info = info; + info->filp = filp; + + mutex_init(&char_dev_info->management_mutex); + mutex_init(&char_dev_info->rw_mutex); + skb_queue_head_init(&char_dev_info->rx_queue); + + mutex_lock(&char_dev_info->management_mutex); + err = cg2900_audio_open(&char_dev_info->session, info->dev_bt->parent); + mutex_unlock(&char_dev_info->management_mutex); + if (err) { + dev_err(BT_DEV, "Failed to open CG2900 Audio driver (%d)\n", + err); + goto error_handling_free_mem; + } + + return 0; + +error_handling_free_mem: + kfree(char_dev_info); + filp->private_data = NULL; + return err; +} + +/** + * audio_dev_release() - Release char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + * -EBADF if NULL pointer was supplied in private data. + * Errors from @cg2900_audio_close. + */ +static int audio_dev_release(struct inode *inode, struct file *filp) +{ + int err = 0; + struct char_dev_info *dev = filp->private_data; + struct audio_info *info; + + if (!dev) { + pr_err("audio_dev_release: Transport closed"); + return -EBADF; + } + + info = dev->info; + + dev_dbg(BT_DEV, "audio_dev_release\n"); + + mutex_lock(&dev->management_mutex); + err = cg2900_audio_close(&dev->session); + if (err) + /* + * Just print the error. Still free the char_dev_info since we + * don't know the filp structure is valid after this call + */ + dev_err(BT_DEV, "Error %d when closing CG2900 audio driver\n", + err); + + mutex_unlock(&dev->management_mutex); + + kfree(dev); + filp->private_data = NULL; + info->filp = NULL; + + return err; +} + +/** + * audio_dev_read() - Return information to the user from last @write call. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Size of buffer. + * @f_pos: Position in buffer. + * + * The audio_dev_read() function returns information from + * the last @write call to same char device. + * The data is in the following format: + * * OpCode of command for this data + * * Data content (Length of data is determined by the command OpCode, i.e. + * fixed for each command) + * + * Returns: + * Bytes successfully read (could be 0). + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_to_user fails. + * -ENOMEM upon allocation failure. + */ +static ssize_t audio_dev_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct char_dev_info *dev = filp->private_data; + struct audio_info *info; + unsigned int bytes_to_copy; + int err = 0; + struct sk_buff *skb; + + if (!dev) { + pr_err("audio_dev_read: Transport closed"); + return -EBADF; + } + + info = dev->info; + + dev_dbg(BT_DEV, "audio_dev_read count %d\n", count); + + mutex_lock(&dev->rw_mutex); + + skb = skb_dequeue(&dev->rx_queue); + if (!skb) { + /* No data to read */ + bytes_to_copy = 0; + goto finished; + } + + bytes_to_copy = min(count, (unsigned int)(skb->len)); + + err = copy_to_user(buf, skb->data, bytes_to_copy); + if (err) { + dev_err(BT_DEV, "copy_to_user error %d\n", err); + skb_queue_head(&dev->rx_queue, skb); + err = -EFAULT; + goto error_handling; + } + + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) + skb_queue_head(&dev->rx_queue, skb); + else + kfree_skb(skb); + + goto finished; + +error_handling: + mutex_unlock(&dev->rw_mutex); + return (ssize_t)err; +finished: + mutex_unlock(&dev->rw_mutex); + return bytes_to_copy; +} + +/** + * audio_dev_write() - Call CG2900 Audio API function. + * @filp: Pointer to the file struct. + * @buf: Write buffer. + * @count: Size of the buffer write. + * @f_pos: Position of buffer. + * + * audio_dev_write() function executes supplied data and + * interprets it as if it was a function call to the CG2900 Audio API. + * The data is according to: + * * OpCode (4 bytes, see API). + * * Data according to OpCode (see API). No padding between parameters. + * + * Returns: + * Bytes successfully written (could be 0). Equals input @count if successful. + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_from_user fails. + * Error codes from all CG2900 Audio API functions. + */ +static ssize_t audio_dev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + u8 *rec_data; + struct char_dev_info *dev = filp->private_data; + struct audio_info *info; + int err = 0; + int op_code = 0; + u8 *curr_data; + unsigned int stream_handle; + struct cg2900_dai_config dai_config; + struct cg2900_endpoint_config ep_config; + enum cg2900_audio_endpoint_id ep_1; + enum cg2900_audio_endpoint_id ep_2; + int bytes_left = count; + + pr_debug("audio_dev_write count %d", count); + + if (!dev) { + pr_err("audio_dev_write: Transport closed"); + return -EBADF; + } + info = dev->info; + + rec_data = kmalloc(count, GFP_KERNEL); + if (!rec_data) { + dev_err(BT_DEV, "kmalloc failed (%d bytes)\n", count); + return -ENOMEM; + } + + mutex_lock(&dev->rw_mutex); + + err = copy_from_user(rec_data, buf, count); + if (err) { + dev_err(BT_DEV, "copy_from_user failed (%d)\n", err); + err = -EFAULT; + goto finished_mutex_unlock; + } + + /* Initialize temporary data pointer used to traverse the packet */ + curr_data = rec_data; + + op_code = curr_data[0]; + /* OpCode is int size to keep data int aligned */ + curr_data += sizeof(unsigned int); + bytes_left -= sizeof(unsigned int); + + switch (op_code) { + case CG2900_OPCODE_SET_DAI_CONF: + if (bytes_left < sizeof(dai_config)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_SET_DAI_CONF\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&dai_config, curr_data, sizeof(dai_config)); + dev_dbg(BT_DEV, "CG2900_OPCODE_SET_DAI_CONF port %d\n", + dai_config.port); + err = cg2900_audio_set_dai_config(dev->session, &dai_config); + break; + + case CG2900_OPCODE_GET_DAI_CONF: + if (bytes_left < sizeof(dai_config)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_GET_DAI_CONF\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + /* + * Only need to copy the port really, but let's copy + * like this for simplicity. It's only test functionality + * after all. + */ + memcpy(&dai_config, curr_data, sizeof(dai_config)); + dev_dbg(BT_DEV, "CG2900_OPCODE_GET_DAI_CONF port %d\n", + dai_config.port); + err = cg2900_audio_get_dai_config(dev->session, &dai_config); + if (!err) { + int len; + struct sk_buff *skb; + + /* + * Command succeeded. Store data so it can be returned + * when calling read. + */ + len = sizeof(op_code) + sizeof(dai_config); + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "CG2900_OPCODE_GET_DAI_CONF: " + "Could not allocate skb\n"); + err = -ENOMEM; + goto finished_mutex_unlock; + } + memcpy(skb_put(skb, sizeof(op_code)), &op_code, + sizeof(op_code)); + memcpy(skb_put(skb, sizeof(dai_config)), + &dai_config, sizeof(dai_config)); + skb_queue_tail(&dev->rx_queue, skb); + } + break; + + case CG2900_OPCODE_CONFIGURE_ENDPOINT: + if (bytes_left < sizeof(ep_config)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_CONFIGURE_ENDPOINT\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&ep_config, curr_data, sizeof(ep_config)); + dev_dbg(BT_DEV, "CG2900_OPCODE_CONFIGURE_ENDPOINT ep_id %d\n", + ep_config.endpoint_id); + err = cg2900_audio_config_endpoint(dev->session, &ep_config); + break; + + case CG2900_OPCODE_START_STREAM: + if (bytes_left < (sizeof(ep_1) + sizeof(ep_2))) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_START_STREAM\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&ep_1, curr_data, sizeof(ep_1)); + curr_data += sizeof(ep_1); + memcpy(&ep_2, curr_data, sizeof(ep_2)); + dev_dbg(BT_DEV, "CG2900_OPCODE_START_STREAM ep_1 %d ep_2 %d\n", + ep_1, ep_2); + + err = cg2900_audio_start_stream(dev->session, + ep_1, ep_2, &stream_handle); + if (!err) { + int len; + struct sk_buff *skb; + + /* + * Command succeeded. Store data so it can be returned + * when calling read. + */ + len = sizeof(op_code) + sizeof(stream_handle); + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "CG2900_OPCODE_START_STREAM: " + "Could not allocate skb\n"); + err = -ENOMEM; + goto finished_mutex_unlock; + } + memcpy(skb_put(skb, sizeof(op_code)), &op_code, + sizeof(op_code)); + memcpy(skb_put(skb, sizeof(stream_handle)), + &stream_handle, sizeof(stream_handle)); + skb_queue_tail(&dev->rx_queue, skb); + + dev_dbg(BT_DEV, "stream_handle %d\n", stream_handle); + } + break; + + case CG2900_OPCODE_STOP_STREAM: + if (bytes_left < sizeof(stream_handle)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_STOP_STREAM\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&stream_handle, curr_data, sizeof(stream_handle)); + dev_dbg(BT_DEV, "CG2900_OPCODE_STOP_STREAM stream_handle %d\n", + stream_handle); + err = cg2900_audio_stop_stream(dev->session, stream_handle); + break; + + default: + dev_err(BT_DEV, "Received bad op_code %d\n", op_code); + break; + }; + +finished_mutex_unlock: + kfree(rec_data); + mutex_unlock(&dev->rw_mutex); + + if (err) + return err; + else + return count; +} + +/** + * audio_dev_poll() - Handle POLL call to the interface. + * @filp: Pointer to the file struct. + * @wait: Poll table supplied to caller. + * + * This function is used by the User Space application to see if the device is + * still open and if there is any data available for reading. + * + * Returns: + * Mask of current set POLL values. + */ +static unsigned int audio_dev_poll(struct file *filp, poll_table *wait) +{ + struct char_dev_info *dev = filp->private_data; + struct audio_info *info; + unsigned int mask = 0; + + if (!dev) { + pr_err("audio_dev_poll: Transport closed"); + return POLLERR | POLLRDHUP; + } + info = dev->info; + + if (RESET == info->state) + mask |= POLLERR | POLLRDHUP | POLLPRI; + else + /* Unless RESET we can transmit */ + mask |= POLLOUT; + + if (!skb_queue_empty(&dev->rx_queue)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static const struct file_operations char_dev_fops = { + .open = audio_dev_open, + .release = audio_dev_release, + .read = audio_dev_read, + .write = audio_dev_write, + .poll = audio_dev_poll +}; + +/** + * probe_common() - Register misc device. + * @info: Audio info structure. + * @dev: Current device. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * Error codes from misc_register. + */ +static int probe_common(struct audio_info *info, struct device *dev) +{ + struct audio_cb_info *cb_info; + struct cg2900_user_data *pf_data; + int err; + + cb_info = kzalloc(sizeof(*cb_info), GFP_KERNEL); + if (!cb_info) { + dev_err(dev, "Failed to allocate cb_info\n"); + return -ENOMEM; + } + init_waitqueue_head(&cb_info->wq); + skb_queue_head_init(&cb_info->skb_queue); + + pf_data = dev_get_platdata(dev); + cg2900_set_usr(pf_data, cb_info); + pf_data->dev = dev; + pf_data->read_cb = read_cb; + pf_data->reset_cb = reset_cb; + + /* Only register misc device when both devices (BT and FM) are probed */ + if (!info->dev_bt || !info->dev_fm) + return 0; + + /* Prepare and register MISC device */ + info->misc_dev.minor = MISC_DYNAMIC_MINOR; + info->misc_dev.name = NAME; + info->misc_dev.fops = &char_dev_fops; + info->misc_dev.parent = dev; + info->misc_dev.mode = S_IRUGO | S_IWUGO; + + err = misc_register(&info->misc_dev); + if (err) { + dev_err(dev, "Error %d registering misc dev\n", err); + return err; + } + info->misc_registered = true; + + dev_info(dev, "CG2900 Audio driver started\n"); + return 0; +} + +/** + * cg2900_audio_bt_probe() - Initialize CG2900 BT audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * -EEXIST if device has already been started. + * Error codes from probe_common. + */ +static int __devinit cg2900_audio_bt_probe(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_bt_probe\n"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->dev_bt = &pdev->dev; + dev_set_drvdata(&pdev->dev, info); + + err = probe_common(info, &pdev->dev); + if (err) { + dev_err(&pdev->dev, "Could not probe audio BT (%d)\n", err); + dev_set_drvdata(&pdev->dev, NULL); + device_removed(info); + } + + return err; +} + +/** + * cg2900_audio_bt_probe() - Initialize CG2900 FM audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * -EEXIST if device has already been started. + * Error codes from probe_common. + */ +static int __devinit cg2900_audio_fm_probe(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_fm_probe\n"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->dev_fm = &pdev->dev; + dev_set_drvdata(&pdev->dev, info); + + err = probe_common(info, &pdev->dev); + if (err) { + dev_err(&pdev->dev, "Could not probe audio FM (%d)\n", err); + dev_set_drvdata(&pdev->dev, NULL); + device_removed(info); + } + + return err; +} + +/** + * common_remove() - Dergister misc device. + * @info: Audio info structure. + * @dev: Current device. + * + * Returns: + * 0 if success. + * Error codes from misc_deregister. + */ +static int common_remove(struct audio_info *info, struct device *dev) +{ + int err; + struct audio_cb_info *cb_info; + struct cg2900_user_data *pf_data; + + pf_data = dev_get_platdata(dev); + cb_info = cg2900_get_usr(pf_data); + skb_queue_purge(&cb_info->skb_queue); + wake_up_all(&cb_info->wq); + kfree(cb_info); + + if (!info->misc_registered) + return 0; + + err = misc_deregister(&info->misc_dev); + if (err) + dev_err(dev, "Error %d deregistering misc dev\n", err); + info->misc_registered = false; + + if (info->filp) + info->filp->private_data = NULL; + + dev_info(dev, "CG2900 Audio driver removed\n"); + return err; +} + +/** + * cg2900_audio_bt_remove() - Release CG2900 audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * Error codes from common_remove. + */ +static int __devexit cg2900_audio_bt_remove(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_bt_remove\n"); + + info = dev_get_drvdata(&pdev->dev); + + info->dev_bt = NULL; + + err = common_remove(info, &pdev->dev); + if (err) + dev_err(&pdev->dev, + "cg2900_audio_bt_remove:common_remove failed\n"); + + device_removed(info); + + return 0; +} + +/** + * cg2900_audio_fm_remove() - Release CG2900 audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * Error codes from common_remove. + */ +static int __devexit cg2900_audio_fm_remove(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_fm_remove\n"); + + info = dev_get_drvdata(&pdev->dev); + + info->dev_fm = NULL; + + err = common_remove(info, &pdev->dev); + if (err) + dev_err(&pdev->dev, + "cg2900_audio_fm_remove:common_remove failed\n"); + + device_removed(info); + + return 0; +} + +static struct platform_driver cg2900_audio_bt_driver = { + .driver = { + .name = "cg2900-audiobt", + .owner = THIS_MODULE, + }, + .probe = cg2900_audio_bt_probe, + .remove = __devexit_p(cg2900_audio_bt_remove), +}; + +static struct platform_driver cg2900_audio_fm_driver = { + .driver = { + .name = "cg2900-audiofm", + .owner = THIS_MODULE, + }, + .probe = cg2900_audio_fm_probe, + .remove = __devexit_p(cg2900_audio_fm_remove), +}; + +/** + * cg2900_audio_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_audio_init(void) +{ + int err; + + pr_debug("cg2900_audio_init"); + + err = platform_driver_register(&cg2900_audio_bt_driver); + if (err) + return err; + return platform_driver_register(&cg2900_audio_fm_driver); +} + +/** + * cg2900_audio_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_audio_exit(void) +{ + pr_debug("cg2900_audio_exit"); + platform_driver_unregister(&cg2900_audio_fm_driver); + platform_driver_unregister(&cg2900_audio_bt_driver); +} + +module_init(cg2900_audio_init); +module_exit(cg2900_audio_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_AUTHOR("Kjell Andersson ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth Audio ST-Ericsson controller"); diff --git a/drivers/staging/cg2900/mfd/cg2900_char_devices.c b/drivers/staging/cg2900/mfd/cg2900_char_devices.c new file mode 100644 index 00000000000..81d230227db --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_char_devices.c @@ -0,0 +1,719 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller. + */ +#define NAME "cg2900_char_dev" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <linux/compiler.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/mfd/core.h> + +#include "cg2900.h" +#include "cg2900_core.h" + +#define MAIN_DEV (dev->dev) + +/** + * struct char_dev_user - Stores device information. + * @dev: Current device. + * @miscdev: Registered device struct. + * @filp: Current file pointer. + * @name: Name of device. + * @rx_queue: Data queue. + * @rx_wait_queue: Wait queue. + * @reset_wait_queue: Reset Wait queue. + * @read_mutex: Read mutex. + * @write_mutex: Write mutex. + * @list: List header for inserting into device list. + */ +struct char_dev_user { + struct device *dev; + struct miscdevice miscdev; + struct file *filp; + char *name; + struct sk_buff_head rx_queue; + wait_queue_head_t rx_wait_queue; + wait_queue_head_t reset_wait_queue; + struct mutex read_mutex; + struct mutex write_mutex; + struct list_head list; +}; + +/** + * struct char_info - Stores all current users. + * @open_mutex: Open mutex (used for both open and release). + * @man_mutex: Management mutex. + * @dev_users: List of char dev users. + */ +struct char_info { + struct mutex open_mutex; + struct mutex man_mutex; + struct list_head dev_users; +}; + +static struct char_info *char_info; + +/** + * char_dev_read_cb() - Handle data received from controller. + * @dev: Device receiving data. + * @skb: Buffer with data coming from controller. + * + * The char_dev_read_cb() function handles data received from the CG2900 driver. + */ +static void char_dev_read_cb(struct cg2900_user_data *dev, struct sk_buff *skb) +{ + struct char_dev_user *char_dev = dev_get_drvdata(dev->dev); + + dev_dbg(dev->dev, "char_dev_read_cb len %d\n", skb->len); + + skb_queue_tail(&char_dev->rx_queue, skb); + + wake_up_interruptible(&char_dev->rx_wait_queue); +} + +/** + * char_dev_reset_cb() - Handle reset from controller. + * @dev: Device resetting. + * + * The char_dev_reset_cb() function handles reset from the CG2900 driver. + */ +static void char_dev_reset_cb(struct cg2900_user_data *dev) +{ + struct char_dev_user *char_dev = dev_get_drvdata(dev->dev); + + dev_dbg(dev->dev, "char_dev_reset_cb\n"); + + wake_up_interruptible(&char_dev->rx_wait_queue); + wake_up_interruptible(&char_dev->reset_wait_queue); +} + +/** + * char_dev_open() - Open char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * The char_dev_open() function opens the char device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if device cannot be found in device list. + * Error codes from cg2900->open. + */ +static int char_dev_open(struct inode *inode, struct file *filp) +{ + int err; + int minor; + struct char_dev_user *dev = NULL; + struct char_dev_user *tmp; + struct list_head *cursor; + struct cg2900_user_data *user; + + mutex_lock(&char_info->open_mutex); + + minor = iminor(inode); + + /* Find the device for this file */ + mutex_lock(&char_info->man_mutex); + list_for_each(cursor, &char_info->dev_users) { + tmp = list_entry(cursor, struct char_dev_user, list); + if (tmp->miscdev.minor == minor) { + dev = tmp; + break; + } + } + mutex_unlock(&char_info->man_mutex); + if (!dev) { + pr_err("Could not identify device in inode"); + err = -EINVAL; + goto error_handling; + } + + filp->private_data = dev; + dev->filp = filp; + user = dev_get_platdata(dev->dev); + + /* First initiate wait queues for this device. */ + init_waitqueue_head(&dev->rx_wait_queue); + init_waitqueue_head(&dev->reset_wait_queue); + + /* Register to CG2900 Driver */ + err = user->open(user); + if (err) { + dev_err(MAIN_DEV, + "Couldn't register to CG2900 for H:4 channel %s\n", + dev->name); + goto error_handling; + } + dev_info(MAIN_DEV, "char_dev %s opened\n", dev->name); + +error_handling: + mutex_unlock(&char_info->open_mutex); + return err; +} + +/** + * char_dev_release() - Release char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * The char_dev_release() function release the char device. + * + * Returns: + * 0 if there is no error. + * -EBADF if NULL pointer was supplied in private data. + */ +static int char_dev_release(struct inode *inode, struct file *filp) +{ + int err = 0; + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + + pr_debug("char_dev_release"); + + if (!dev) { + pr_err("char_dev_release: Calling with NULL pointer"); + return -EBADF; + } + + mutex_lock(&char_info->open_mutex); + mutex_lock(&dev->read_mutex); + mutex_lock(&dev->write_mutex); + + user = dev_get_platdata(dev->dev); + if (user->opened) + user->close(user); + + dev_info(MAIN_DEV, "char_dev %s closed\n", dev->name); + + filp->private_data = NULL; + dev->filp = NULL; + wake_up_interruptible(&dev->rx_wait_queue); + wake_up_interruptible(&dev->reset_wait_queue); + + /* Purge the queue since the device is closed now */ + skb_queue_purge(&dev->rx_queue); + + mutex_unlock(&dev->write_mutex); + mutex_unlock(&dev->read_mutex); + mutex_unlock(&char_info->open_mutex); + + return err; +} + +/** + * char_dev_read() - Queue and copy buffer to user. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Size of buffer. + * @f_pos: Position in buffer. + * + * The char_dev_read() function queues and copy the received buffer to + * the user space char device. If no data is available this function will block. + * + * Returns: + * Bytes successfully read (could be 0). + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_to_user fails. + * Error codes from wait_event_interruptible. + */ +static ssize_t char_dev_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + struct sk_buff *skb; + int bytes_to_copy; + int err = 0; + + pr_debug("char_dev_read"); + + if (!dev) { + pr_err("char_dev_read: Calling with NULL pointer"); + return -EBADF; + } + mutex_lock(&dev->read_mutex); + + user = dev_get_platdata(dev->dev); + + if (user->opened && skb_queue_empty(&dev->rx_queue)) { + err = wait_event_interruptible(dev->rx_wait_queue, + (!(skb_queue_empty(&dev->rx_queue))) || + !user->opened); + if (err) { + dev_err(MAIN_DEV, "Failed to wait for event\n"); + goto error_handling; + } + } + + if (!user->opened) { + dev_err(MAIN_DEV, "Channel has been closed\n"); + err = -EBADF; + goto error_handling; + } + + skb = skb_dequeue(&dev->rx_queue); + if (!skb) { + dev_dbg(MAIN_DEV, + "skb queue is empty - return with zero bytes\n"); + bytes_to_copy = 0; + goto finished; + } + + bytes_to_copy = min(count, skb->len); + + err = copy_to_user(buf, skb->data, bytes_to_copy); + if (err) { + dev_err(MAIN_DEV, "Error %d from copy_to_user\n", err); + skb_queue_head(&dev->rx_queue, skb); + err = -EFAULT; + goto error_handling; + } + + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) + skb_queue_head(&dev->rx_queue, skb); + else + kfree_skb(skb); + + goto finished; + +error_handling: + mutex_unlock(&dev->read_mutex); + return (ssize_t)err; +finished: + mutex_unlock(&dev->read_mutex); + return bytes_to_copy; +} + +/** + * char_dev_write() - Copy buffer from user and write to CG2900 driver. + * @filp: Pointer to the file struct. + * @buf: Write buffer. + * @count: Size of the buffer write. + * @f_pos: Position of buffer. + * + * Returns: + * Bytes successfully written (could be 0). + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_from_user fails. + */ +static ssize_t char_dev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + int err = 0; + + pr_debug("char_dev_write"); + + if (!dev) { + pr_err("char_dev_write: Calling with NULL pointer"); + return -EBADF; + } + + user = dev_get_platdata(dev->dev); + if (!user->opened) { + dev_err(MAIN_DEV, "char_dev_write: Channel not opened\n"); + return -EACCES; + } + + mutex_lock(&dev->write_mutex); + + skb = user->alloc_skb(count, GFP_ATOMIC); + if (!skb) { + dev_err(MAIN_DEV, "Couldn't allocate sk_buff with length %d\n", + count); + goto error_handling; + } + + err = copy_from_user(skb_put(skb, count), buf, count); + if (err) { + dev_err(MAIN_DEV, "Error %d from copy_from_user\n", err); + kfree_skb(skb); + err = -EFAULT; + goto error_handling; + } + + err = user->write(user, skb); + if (err) { + dev_err(MAIN_DEV, "cg2900_write failed (%d)\n", err); + kfree_skb(skb); + goto error_handling; + } + + mutex_unlock(&dev->write_mutex); + return count; + +error_handling: + mutex_unlock(&dev->write_mutex); + return err; +} + +/** + * char_dev_unlocked_ioctl() - Handle IOCTL call to the interface. + * @filp: Pointer to the file struct. + * @cmd: IOCTL command. + * @arg: IOCTL argument. + * + * Returns: + * 0 if there is no error. + * -EINVAL if supplied cmd is not supported. + * For cmd CG2900_CHAR_DEV_IOCTL_CHECK4RESET 0x01 is returned if device is + * reset and 0x02 is returned if device is closed. + */ +static long char_dev_unlocked_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + struct cg2900_rev_data rev_data; + int err = 0; + int ret_val; + void __user *user_arg = (void __user *)arg; + + if (!dev) { + pr_err("char_dev_unlocked_ioctl: Calling with NULL pointer"); + return -EBADF; + } + + dev_dbg(dev->dev, "char_dev_unlocked_ioctl for %s\n" + "\tDIR: %d\n" + "\tTYPE: %d\n" + "\tNR: %d\n" + "\tSIZE: %d", + dev->name, _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), + _IOC_SIZE(cmd)); + + user = dev_get_platdata(dev->dev); + + switch (cmd) { + case CG2900_CHAR_DEV_IOCTL_RESET: + if (!user->opened) + return -EACCES; + dev_dbg(MAIN_DEV, "ioctl reset command for device %s\n", + dev->name); + err = user->reset(user); + break; + + case CG2900_CHAR_DEV_IOCTL_CHECK4RESET: + if (user->opened) + ret_val = CG2900_CHAR_DEV_IOCTL_EVENT_IDLE; + else + ret_val = CG2900_CHAR_DEV_IOCTL_EVENT_RESET; + + dev_dbg(MAIN_DEV, "ioctl check for reset command for device %s", + dev->name); + + err = copy_to_user(user_arg, &ret_val, sizeof(ret_val)); + if (err) { + dev_err(MAIN_DEV, + "Error %d from copy_to_user for reset\n", err); + return -EFAULT; + } + break; + + case CG2900_CHAR_DEV_IOCTL_GET_REVISION: + if (!user->get_local_revision(user, &rev_data)) { + dev_err(MAIN_DEV, "No revision data available\n"); + return -EIO; + } + dev_dbg(MAIN_DEV, "ioctl check for local revision info\n" + "\trevision 0x%04X\n" + "\tsub_version 0x%04X\n", + rev_data.revision, rev_data.sub_version); + err = copy_to_user(user_arg, &rev_data, sizeof(rev_data)); + if (err) { + dev_err(MAIN_DEV, + "Error %d from copy_to_user for " + "revision\n", err); + return -EFAULT; + } + break; + + default: + dev_err(MAIN_DEV, "Unknown ioctl command %08X\n", cmd); + err = -EINVAL; + break; + }; + + return err; +} + +/** + * char_dev_poll() - Handle POLL call to the interface. + * @filp: Pointer to the file struct. + * @wait: Poll table supplied to caller. + * + * Returns: + * Mask of current set POLL values + */ +static unsigned int char_dev_poll(struct file *filp, poll_table *wait) +{ + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + unsigned int mask = 0; + + if (!dev) { + pr_debug("char_dev_poll: Device not open"); + return POLLERR | POLLRDHUP; + } + + user = dev_get_platdata(dev->dev); + + poll_wait(filp, &dev->reset_wait_queue, wait); + poll_wait(filp, &dev->rx_wait_queue, wait); + + if (!user->opened) + mask |= POLLERR | POLLRDHUP | POLLPRI; + else + mask |= POLLOUT; /* We can TX unless there is an error */ + + if (!(skb_queue_empty(&dev->rx_queue))) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +/* + * struct char_dev_fops - Char devices file operations. + * @read: Function that reads from the char device. + * @write: Function that writes to the char device. + * @unlocked_ioctl: Function that performs IO operations with + * the char device. + * @poll: Function that checks if there are possible operations + * with the char device. + * @open: Function that opens the char device. + * @release: Function that release the char device. + */ +static const struct file_operations char_dev_fops = { + .read = char_dev_read, + .write = char_dev_write, + .unlocked_ioctl = char_dev_unlocked_ioctl, + .poll = char_dev_poll, + .open = char_dev_open, + .release = char_dev_release +}; + +/** + * remove_dev() - Remove char device structure for device. + * @dev_usr: Char device user. + * + * The remove_dev() function releases the char_dev structure for this device. + */ +static void remove_dev(struct char_dev_user *dev_usr) +{ + if (!dev_usr) + return; + + dev_dbg(dev_usr->dev, + "Removing char device %s with major %d and minor %d\n", + dev_usr->name, + MAJOR(dev_usr->miscdev.this_device->devt), + MINOR(dev_usr->miscdev.this_device->devt)); + + skb_queue_purge(&dev_usr->rx_queue); + + mutex_destroy(&dev_usr->read_mutex); + mutex_destroy(&dev_usr->write_mutex); + + dev_usr->dev = NULL; + if (dev_usr->filp) + dev_usr->filp->private_data = NULL; + + /* Remove device node in file system. */ + misc_deregister(&dev_usr->miscdev); + kfree(dev_usr); +} + +/** + * cg2900_char_probe() - Initialize char device module. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * -ENOMEM if allocation fails. + * -EACCES if device already have been initiated. + */ +static int __devinit cg2900_char_probe(struct platform_device *pdev) +{ + int err = 0; + struct char_dev_user *dev_usr; + struct cg2900_user_data *user; + struct device *dev = &pdev->dev; + + dev_dbg(&pdev->dev, "cg2900_char_probe\n"); + + user = dev_get_platdata(dev); + user->dev = dev; + user->read_cb = char_dev_read_cb; + user->reset_cb = char_dev_reset_cb; + + dev_usr = kzalloc(sizeof(*dev_usr), GFP_KERNEL); + if (!dev_usr) { + dev_err(&pdev->dev, "Couldn't allocate dev_usr\n"); + return -ENOMEM; + } + + dev_set_drvdata(&pdev->dev, dev_usr); + dev_usr->dev = &pdev->dev; + + /* Store device name */ + dev_usr->name = user->channel_data.char_dev_name; + + /* Prepare miscdevice struct before registering the device */ + dev_usr->miscdev.minor = MISC_DYNAMIC_MINOR; + dev_usr->miscdev.name = dev_usr->name; + dev_usr->miscdev.nodename = dev_usr->name; + dev_usr->miscdev.fops = &char_dev_fops; + dev_usr->miscdev.parent = &pdev->dev; + dev_usr->miscdev.mode = S_IRUGO | S_IWUGO; + + err = misc_register(&dev_usr->miscdev); + if (err) { + dev_err(&pdev->dev, "Error %d registering misc dev\n", err); + goto err_free_usr; + } + + dev_dbg(&pdev->dev, "Added char device %s with major %d and minor %d\n", + dev_usr->name, MAJOR(dev_usr->miscdev.this_device->devt), + MINOR(dev_usr->miscdev.this_device->devt)); + + mutex_init(&dev_usr->read_mutex); + mutex_init(&dev_usr->write_mutex); + + skb_queue_head_init(&dev_usr->rx_queue); + + mutex_lock(&char_info->man_mutex); + list_add_tail(&dev_usr->list, &char_info->dev_users); + mutex_unlock(&char_info->man_mutex); + + return 0; + +err_free_usr: + kfree(dev_usr); + dev_set_drvdata(&pdev->dev, NULL); + return err; +} + +/** + * cg2900_char_remove() - Release the char device module. + * @pdev: Platform device. + * + * Returns: + * 0 if success (always success). + */ +static int __devexit cg2900_char_remove(struct platform_device *pdev) +{ + struct list_head *cursor, *next; + struct char_dev_user *tmp; + struct char_dev_user *user; + + dev_dbg(&pdev->dev, "cg2900_char_remove\n"); + + user = dev_get_drvdata(&pdev->dev); + + mutex_lock(&char_info->man_mutex); + list_for_each_safe(cursor, next, &char_info->dev_users) { + tmp = list_entry(cursor, struct char_dev_user, list); + if (tmp == user) { + list_del(cursor); + remove_dev(tmp); + dev_set_drvdata(&pdev->dev, NULL); + break; + } + } + mutex_unlock(&char_info->man_mutex); + return 0; +} + +static struct platform_driver cg2900_char_driver = { + .driver = { + .name = "cg2900-chardev", + .owner = THIS_MODULE, + }, + .probe = cg2900_char_probe, + .remove = __devexit_p(cg2900_char_remove), +}; + +/** + * cg2900_char_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_char_init(void) +{ + pr_debug("cg2900_char_init"); + + /* Initialize private data. */ + char_info = kzalloc(sizeof(*char_info), GFP_ATOMIC); + if (!char_info) { + pr_err("Could not alloc char_info struct"); + return -ENOMEM; + } + + mutex_init(&char_info->open_mutex); + mutex_init(&char_info->man_mutex); + INIT_LIST_HEAD(&char_info->dev_users); + + return platform_driver_register(&cg2900_char_driver); +} + +/** + * cg2900_char_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_char_exit(void) +{ + struct list_head *cursor, *next; + struct char_dev_user *tmp; + + pr_debug("cg2900_char_exit"); + + platform_driver_unregister(&cg2900_char_driver); + + if (!char_info) + return; + + list_for_each_safe(cursor, next, &char_info->dev_users) { + tmp = list_entry(cursor, struct char_dev_user, list); + list_del(cursor); + remove_dev(tmp); + } + + mutex_destroy(&char_info->open_mutex); + mutex_destroy(&char_info->man_mutex); + + kfree(char_info); + char_info = NULL; +} + +module_init(cg2900_char_init); +module_exit(cg2900_char_exit); + +MODULE_AUTHOR("Henrik Possung ST-Ericsson"); +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ST-Ericsson CG2900 Char Devices Driver"); diff --git a/drivers/staging/cg2900/mfd/cg2900_chip.c b/drivers/staging/cg2900/mfd/cg2900_chip.c new file mode 100644 index 00000000000..020f7c906ad --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_chip.c @@ -0,0 +1,3618 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ +#define NAME "cg2900_chip" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <asm/byteorder.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/mfd/core.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> + +#include "cg2900.h" +#include "cg2900_chip.h" +#include "cg2900_core.h" +#include "cg2900_lib.h" + +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + +#define MAIN_DEV (main_info->dev) +#define BOOT_DEV (info->user_in_charge->dev) + +#define WQ_NAME "cg2900_chip_wq" + +/* + * After waiting the first 500 ms we should just try to get the selftest results + * for another number of poll attempts + */ +#define MAX_NBR_OF_POLLS 50 + +#define LINE_TOGGLE_DETECT_TIMEOUT 50 /* ms */ +#define CHIP_READY_TIMEOUT 100 /* ms */ +#define CHIP_STARTUP_TIMEOUT 15000 /* ms */ +#define CHIP_SHUTDOWN_TIMEOUT 15000 /* ms */ +#define POWER_SW_OFF_WAIT 500 /* ms */ +#define SELFTEST_INITIAL 500 /* ms */ +#define SELFTEST_POLLING 20 /* ms */ + +/** CHANNEL_BT_CMD - Bluetooth HCI H:4 channel + * for Bluetooth commands in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_CMD 0x01 + +/** CHANNEL_BT_ACL - Bluetooth HCI H:4 channel + * for Bluetooth ACL data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_ACL 0x02 + +/** CHANNEL_BT_EVT - Bluetooth HCI H:4 channel + * for Bluetooth events in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_EVT 0x04 + +/** CHANNEL_FM_RADIO - Bluetooth HCI H:4 channel + * for FM radio in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_FM_RADIO 0x08 + +/** CHANNEL_GNSS - Bluetooth HCI H:4 channel + * for GNSS in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_GNSS 0x09 + +/** CHANNEL_DEBUG - Bluetooth HCI H:4 channel + * for internal debug data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_DEBUG 0x0B + +/** CHANNEL_STE_TOOLS - Bluetooth HCI H:4 channel + * for development tools data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_STE_TOOLS 0x0D + +/** CHANNEL_HCI_LOGGER - Bluetooth HCI H:4 channel + * for logging all transmitted H4 packets (on all channels). + */ +#define CHANNEL_HCI_LOGGER 0xFA + +/** CHANNEL_CORE - Bluetooth HCI H:4 channel + * for user space control of the ST-Ericsson connectivity controller. + */ +#define CHANNEL_CORE 0xFD + +/** CHANNEL_HCI_RAW - Bluetooth HCI H:4 channel + * for user space read/write on the ST-Ericsson connectivity controller. + */ +#define CHANNEL_HCI_RAW 0xFE + +/** CG2900_BT_CMD - Bluetooth HCI H4 channel for Bluetooth commands. + */ +#define CG2900_BT_CMD "cg2900_bt_cmd" + +/** CG2900_BT_ACL - Bluetooth HCI H4 channel for Bluetooth ACL data. + */ +#define CG2900_BT_ACL "cg2900_bt_acl" + +/** CG2900_BT_EVT - Bluetooth HCI H4 channel for Bluetooth events. + */ +#define CG2900_BT_EVT "cg2900_bt_evt" + +/** CG2900_FM_RADIO - Bluetooth HCI H4 channel for FM radio. + */ +#define CG2900_FM_RADIO "cg2900_fm_radio" + +/** CG2900_GNSS - Bluetooth HCI H4 channel for GNSS. + */ +#define CG2900_GNSS "cg2900_gnss" + +/** CG2900_DEBUG - Bluetooth HCI H4 channel for internal debug data. + */ +#define CG2900_DEBUG "cg2900_debug" + +/** CG2900_STE_TOOLS - Bluetooth HCI H4 channel for development tools data. + */ +#define CG2900_STE_TOOLS "cg2900_ste_tools" + +/** CG2900_HCI_LOGGER - BT channel for logging all transmitted H4 packets. + * Data read is copy of all data transferred on the other channels. + * Only write allowed is configuration of the HCI Logger. + */ +#define CG2900_HCI_LOGGER "cg2900_hci_logger" + +/** CG2900_BT_AUDIO - HCI Channel for BT audio configuration commands. + * Maps to Bluetooth command and event channels. + */ +#define CG2900_BT_AUDIO "cg2900_bt_audio" + +/** CG2900_FM_AUDIO - HCI channel for FM audio configuration commands. + * Maps to FM Radio channel. + */ +#define CG2900_FM_AUDIO "cg2900_fm_audio" + +/** CG2900_CORE- Channel for keeping ST-Ericsson CG2900 enabled. + * Opening this channel forces the chip to stay powered. + * No data can be written to or read from this channel. + */ +#define CG2900_CORE "cg2900_core" + +/** CG2900_HCI_RAW - Channel for HCI RAW data exchange. + * Opening this channel will not allow any others HCI Channels + * to be opened except Logger Channel. + */ +#define CG2900_HCI_RAW "cg2900_hci_raw" + +/** + * enum main_state - Main-state for CG2900 driver. + * @CG2900_INIT: CG2900 initializing. + * @CG2900_IDLE: No user registered to CG2900 driver. + * @CG2900_BOOTING: CG2900 booting after first user is registered. + * @CG2900_CLOSING: CG2900 closing after last user has deregistered. + * @CG2900_RESETING: CG2900 reset requested. + * @CG2900_ACTIVE: CG2900 up and running with at least one user. + */ +enum main_state { + CG2900_INIT, + CG2900_IDLE, + CG2900_BOOTING, + CG2900_CLOSING, + CG2900_RESETING, + CG2900_ACTIVE +}; + +/** + * enum boot_state - BOOT-state for CG2900 chip driver. + * @BOOT_NOT_STARTED: Boot has not yet started. + * @BOOT_SEND_BD_ADDRESS: VS Store In FS command with BD address + * has been sent. + * @BOOT_GET_FILES_TO_LOAD: CG2900 chip driver is retrieving file to + * load. + * @BOOT_DOWNLOAD_PATCH: CG2900 chip driver is downloading + * patches. + * @BOOT_ACTIVATE_PATCHES_AND_SETTINGS: CG2900 chip driver is activating patches + * and settings. + * @BOOT_READ_SELFTEST_RESULT: CG2900 is performing selftests that + * shall be read out. + * @BOOT_DISABLE_BT: Disable BT Core. + * @BOOT_READY: CG2900 chip driver boot is ready. + * @BOOT_FAILED: CG2900 chip driver boot failed. + */ +enum boot_state { + BOOT_NOT_STARTED, + BOOT_SEND_BD_ADDRESS, + BOOT_GET_FILES_TO_LOAD, + BOOT_DOWNLOAD_PATCH, + BOOT_ACTIVATE_PATCHES_AND_SETTINGS, + BOOT_READ_SELFTEST_RESULT, + BOOT_DISABLE_BT, + BOOT_READY, + BOOT_FAILED +}; + +/** + * enum closing_state - CLOSING-state for CG2900 chip driver. + * @CLOSING_RESET: HCI RESET_CMD has been sent. + * @CLOSING_POWER_SWITCH_OFF: HCI VS_POWER_SWITCH_OFF command has been sent. + * @CLOSING_SHUT_DOWN: We have now shut down the chip. + */ +enum closing_state { + CLOSING_RESET, + CLOSING_POWER_SWITCH_OFF, + CLOSING_SHUT_DOWN +}; + +/** + * enum file_load_state - BOOT_FILE_LOAD-state for CG2900 chip driver. + * @FILE_LOAD_GET_PATCH: Loading patches. + * @FILE_LOAD_GET_STATIC_SETTINGS: Loading static settings. + * @FILE_LOAD_NO_MORE_FILES: No more files to load. + * @FILE_LOAD_FAILED: File loading failed. + */ +enum file_load_state { + FILE_LOAD_GET_PATCH, + FILE_LOAD_GET_STATIC_SETTINGS, + FILE_LOAD_NO_MORE_FILES, + FILE_LOAD_FAILED +}; + +/** + * enum download_state - BOOT_DOWNLOAD state. + * @DOWNLOAD_PENDING: Download in progress. + * @DOWNLOAD_SUCCESS: Download successfully finished. + * @DOWNLOAD_FAILED: Downloading failed. + */ +enum download_state { + DOWNLOAD_PENDING, + DOWNLOAD_SUCCESS, + DOWNLOAD_FAILED +}; + +/** + * enum fm_radio_mode - FM Radio mode. + * It's needed because some FM do-commands generate interrupts only when + * the FM driver is in specific mode and we need to know if we should expect + * the interrupt. + * @FM_RADIO_MODE_IDLE: Radio mode is Idle (default). + * @FM_RADIO_MODE_FMT: Radio mode is set to FMT (transmitter). + * @FM_RADIO_MODE_FMR: Radio mode is set to FMR (receiver). + */ +enum fm_radio_mode { + FM_RADIO_MODE_IDLE = 0, + FM_RADIO_MODE_FMT = 1, + FM_RADIO_MODE_FMR = 2 +}; + + +/** + * struct cg2900_channel_item - List object for channel. + * @list: list_head struct. + * @user: User for this channel. + */ +struct cg2900_channel_item { + struct list_head list; + struct cg2900_user_data *user; +}; + +/** + * struct cg2900_delayed_work_struct - Work structure for CG2900 chip. + * @delayed_work: Work structure. + * @data: Pointer to private data. + */ +struct cg2900_delayed_work_struct { + struct delayed_work work; + void *data; +}; + +/** + * struct cg2900_skb_data - Structure for storing private data in an sk_buffer. + * @dev: CG2900 device for this sk_buffer. + */ +struct cg2900_skb_data { + struct cg2900_user_data *user; +}; +#define cg2900_skb_data(__skb) ((struct cg2900_skb_data *)((__skb)->cb)) + +/** + * struct cg2900_chip_info - Main info structure for CG2900 chip driver. + * @dev: Current device. Same as @chip_dev->dev. + * @patch_file_name: Stores patch file name. + * @settings_file_name: Stores settings file name. + * @file_info: Firmware file info (patch or settings). + * @boot_state: Current BOOT-state of CG2900 chip driver. + * @closing_state: Current CLOSING-state of CG2900 chip driver. + * @file_load_state: Current BOOT_FILE_LOAD-state of CG2900 chip + * driver. + * @download_state: Current BOOT_DOWNLOAD-state of CG2900 chip + * driver. + * @wq: CG2900 chip driver workqueue. + * @chip_dev: Chip handler info. + * @tx_bt_lock: Spinlock used to protect some global structures + * related to internal BT command flow control. + * @tx_fm_lock: Spinlock used to protect some global structures + * related to internal FM command flow control. + * @tx_fm_audio_awaiting_irpt: Indicates if an FM interrupt event related to + * audio driver command is expected. + * @fm_radio_mode: Current FM radio mode. + * @tx_nr_pkts_allowed_bt: Number of packets allowed to send on BT HCI CMD + * H4 channel. + * @audio_bt_cmd_op: Stores the OpCode of the last sent audio driver + * HCI BT CMD. + * @audio_fm_cmd_id: Stores the command id of the last sent + * HCI FM RADIO command by the fm audio user. + * @hci_fm_cmd_func: Stores the command function of the last sent + * HCI FM RADIO command by the fm radio user. + * @tx_queue_bt: TX queue for HCI BT commands when nr of commands + * allowed is 0 (CG2900 internal flow control). + * @tx_queue_fm: TX queue for HCI FM commands when nr of commands + * allowed is 0 (CG2900 internal flow control). + * @user_in_charge: User currently operating. Normally used at + * channel open and close. + * @last_user: Last user of this chip. To avoid complications + * this will never be set for bt_audio and + * fm_audio. + * @logger: Logger user of this chip. + * @hci_raw: HCI Raw user of this chip. + * @selftest_work: Delayed work for reading selftest results. + * @nbr_of_polls: Number of times we should poll for selftest + * results. + * @startup: True if system is starting up. + * @mfd_size: Number of MFD cells. + * @mfd_char_size: Number of MFD char device cells. + */ +struct cg2900_chip_info { + struct device *dev; + char *patch_file_name; + char *settings_file_name; + struct cg2900_file_info file_info; + enum main_state main_state; + enum boot_state boot_state; + enum closing_state closing_state; + enum file_load_state file_load_state; + enum download_state download_state; + struct workqueue_struct *wq; + struct cg2900_chip_dev *chip_dev; + spinlock_t tx_bt_lock; + spinlock_t tx_fm_lock; + spinlock_t rw_lock; + bool tx_fm_audio_awaiting_irpt; + enum fm_radio_mode fm_radio_mode; + int tx_nr_pkts_allowed_bt; + u16 audio_bt_cmd_op; + u16 audio_fm_cmd_id; + u16 hci_fm_cmd_func; + struct sk_buff_head tx_queue_bt; + struct sk_buff_head tx_queue_fm; + struct list_head open_channels; + struct cg2900_user_data *user_in_charge; + struct cg2900_user_data *last_user; + struct cg2900_user_data *logger; + struct cg2900_user_data *hci_raw; + struct cg2900_user_data *bt_audio; + struct cg2900_user_data *fm_audio; + struct cg2900_delayed_work_struct selftest_work; + int nbr_of_polls; + bool startup; + int mfd_size; + int mfd_char_size; +}; + +/** + * struct main_info - Main info structure for CG2900 chip driver. + * @dev: Device structure. + * @cell_base_id: Base ID for MFD cells. + * @man_mutex: Management mutex. + */ +struct main_info { + struct device *dev; + int cell_base_id; + struct mutex man_mutex; +}; + +static struct main_info *main_info; + +/* + * main_wait_queue - Main Wait Queue in CG2900 driver. + */ +static DECLARE_WAIT_QUEUE_HEAD(main_wait_queue); + +static struct mfd_cell cg2900_devs[]; +static struct mfd_cell cg2900_char_devs[]; + +static void chip_startup_finished(struct cg2900_chip_info *info, int err); +static void chip_shutdown(struct cg2900_user_data *user); + +/** + * bt_is_open() - Checks if any BT user is in open state. + * @info: CG2900 info. + * + * Returns: + * true if a BT channel is open. + * false if no BT channel is open. + */ +static bool bt_is_open(struct cg2900_chip_info *info) +{ + struct list_head *cursor; + struct cg2900_channel_item *tmp; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == CHANNEL_BT_CMD) + return true; + } + return false; +} + +/** + * fm_is_open() - Checks if any FM user is in open state. + * @info: CG2900 info. + * + * Returns: + * true if a FM channel is open. + * false if no FM channel is open. + */ +static bool fm_is_open(struct cg2900_chip_info *info) +{ + struct list_head *cursor; + struct cg2900_channel_item *tmp; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == CHANNEL_FM_RADIO) + return true; + } + return false; +} + +/** + * fm_irpt_expected() - check if this FM command will generate an interrupt. + * @cmd_id: command identifier. + * + * Returns: + * true if the command will generate an interrupt. + * false if it won't. + */ +static bool fm_irpt_expected(struct cg2900_chip_info *info, u16 cmd_id) +{ + bool retval = false; + + switch (cmd_id) { + case CG2900_FM_DO_AIP_FADE_START: + if (info->fm_radio_mode == FM_RADIO_MODE_FMT) + retval = true; + break; + + case CG2900_FM_DO_AUP_BT_FADE_START: + case CG2900_FM_DO_AUP_EXT_FADE_START: + case CG2900_FM_DO_AUP_FADE_START: + if (info->fm_radio_mode == FM_RADIO_MODE_FMR) + retval = true; + break; + + case CG2900_FM_DO_FMR_SETANTENNA: + case CG2900_FM_DO_FMR_SP_AFSWITCH_START: + case CG2900_FM_DO_FMR_SP_AFUPDATE_START: + case CG2900_FM_DO_FMR_SP_BLOCKSCAN_START: + case CG2900_FM_DO_FMR_SP_PRESETPI_START: + case CG2900_FM_DO_FMR_SP_SCAN_START: + case CG2900_FM_DO_FMR_SP_SEARCH_START: + case CG2900_FM_DO_FMR_SP_SEARCHPI_START: + case CG2900_FM_DO_FMR_SP_TUNE_SETCHANNEL: + case CG2900_FM_DO_FMR_SP_TUNE_STEPCHANNEL: + case CG2900_FM_DO_FMT_PA_SETCTRL: + case CG2900_FM_DO_FMT_PA_SETMODE: + case CG2900_FM_DO_FMT_SP_TUNE_SETCHANNEL: + case CG2900_FM_DO_GEN_ANTENNACHECK_START: + case CG2900_FM_DO_GEN_GOTOMODE: + case CG2900_FM_DO_GEN_POWERSUPPLY_SETMODE: + case CG2900_FM_DO_GEN_SELECTREFERENCECLOCK: + case CG2900_FM_DO_GEN_SETPROCESSINGCLOCK: + case CG2900_FM_DO_GEN_SETREFERENCECLOCKPLL: + case CG2900_FM_DO_TST_TX_RAMP_START: + retval = true; + break; + + default: + break; + } + + if (retval) + dev_dbg(info->dev, "Following interrupt event expected for this" + " Cmd complete evt: cmd_id = 0x%X\n", + cmd_id); + + return retval; +} + +/** + * fm_is_do_cmd_irpt() - Check if irpt_val is one of the FM DO command related interrupts. + * @irpt_val: interrupt value. + * + * Returns: + * true if it's do-command related interrupt value. + * false if it's not. + */ +static bool fm_is_do_cmd_irpt(u16 irpt_val) +{ + if ((irpt_val & CG2900_FM_IRPT_OPERATION_SUCCEEDED) || + (irpt_val & CG2900_FM_IRPT_OPERATION_FAILED)) { + dev_dbg(MAIN_DEV, "Irpt evt for FM do-command found, " + "irpt_val = 0x%X\n", irpt_val); + return true; + } + + return false; +} + +/** + * fm_reset_flow_ctrl - Clears up internal FM flow control. + * + * Resets outstanding commands and clear FM TX list and set CG2900 FM mode to + * idle. + */ +static void fm_reset_flow_ctrl(struct cg2900_chip_info *info) +{ + dev_dbg(info->dev, "fm_reset_flow_ctrl\n"); + + skb_queue_purge(&info->tx_queue_fm); + + /* Reset the fm_cmd_id. */ + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + + info->fm_radio_mode = FM_RADIO_MODE_IDLE; +} + + +/** + * fm_parse_cmd - Parses a FM command packet. + * @data: FM command packet. + * @cmd_func: Out: FM legacy command function. + * @cmd_id: Out: FM legacy command ID. + */ +static void fm_parse_cmd(u8 *data, u8 *cmd_func, u16 *cmd_id) +{ + /* Move past H4-header to start of actual package */ + struct fm_leg_cmd *pkt = (struct fm_leg_cmd *)(data + HCI_H4_SIZE); + + *cmd_func = CG2900_FM_CMD_PARAM_NONE; + *cmd_id = CG2900_FM_CMD_NONE; + + if (pkt->opcode != CG2900_FM_GEN_ID_LEGACY) { + dev_err(MAIN_DEV, "fm_parse_cmd: Not an FM legacy command " + "0x%02X\n", pkt->opcode); + return; + } + + *cmd_func = pkt->fm_function; + if (*cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) + *cmd_id = cg2900_get_fm_cmd_id(le16_to_cpu(pkt->fm_cmd.head)); +} + + +/** + * fm_parse_event - Parses a FM event packet + * @data: FM event packet. + * @event: Out: FM event. + * @cmd_func: Out: FM legacy command function. + * @cmd_id: Out: FM legacy command ID. + * @intr_val: Out: FM interrupt value. + */ +static void fm_parse_event(u8 *data, u8 *event, u8 *cmd_func, u16 *cmd_id, + u16 *intr_val) +{ + /* Move past H4-header to start of actual package */ + union fm_leg_evt_or_irq *pkt = (union fm_leg_evt_or_irq *)data; + + *cmd_func = CG2900_FM_CMD_PARAM_NONE; + *cmd_id = CG2900_FM_CMD_NONE; + *intr_val = 0; + *event = CG2900_FM_EVENT_UNKNOWN; + + if (pkt->evt.opcode == CG2900_FM_GEN_ID_LEGACY && + pkt->evt.read_write == CG2900_FM_CMD_LEG_PARAM_WRITE) { + /* Command complete */ + *event = CG2900_FM_EVENT_CMD_COMPLETE; + *cmd_func = pkt->evt.fm_function; + if (*cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) + *cmd_id = cg2900_get_fm_cmd_id( + le16_to_cpu(pkt->evt.response_head)); + } else if (pkt->irq_v2.opcode == CG2900_FM_GEN_ID_LEGACY && + pkt->irq_v2.event_type == CG2900_FM_CMD_LEG_PARAM_IRQ) { + /* Interrupt, PG2 style */ + *event = CG2900_FM_EVENT_INTERRUPT; + *intr_val = le16_to_cpu(pkt->irq_v2.irq); + } else if (pkt->irq_v1.opcode == CG2900_FM_GEN_ID_LEGACY) { + /* Interrupt, PG1 style */ + *event = CG2900_FM_EVENT_INTERRUPT; + *intr_val = le16_to_cpu(pkt->irq_v1.irq); + } else + dev_err(MAIN_DEV, "fm_parse_event: Not an FM legacy command " + "0x%X %X %X %X\n", data[0], data[1], data[2], data[3]); +} + +/** + * fm_update_mode - Updates the FM mode state machine. + * @data: FM command packet. + * + * Parses a FM command packet and updates the FM mode state machine. + */ +static void fm_update_mode(struct cg2900_chip_info *info, u8 *data) +{ + u8 cmd_func; + u16 cmd_id; + + fm_parse_cmd(data, &cmd_func, &cmd_id); + + if (cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND && + cmd_id == CG2900_FM_DO_GEN_GOTOMODE) { + /* Move past H4-header to start of actual package */ + struct fm_leg_cmd *pkt = + (struct fm_leg_cmd *)(data + HCI_H4_SIZE); + + info->fm_radio_mode = le16_to_cpu(pkt->fm_cmd.data[0]); + dev_dbg(info->dev, "FM Radio mode changed to %d\n", + info->fm_radio_mode); + } +} + + +/** + * transmit_skb_from_tx_queue_bt() - Check flow control info and transmit skb. + * + * The transmit_skb_from_tx_queue_bt() function checks if there are tickets + * available and commands waiting in the TX queue and if so transmits them + * to the controller. + * It shall always be called within spinlock_bh. + */ +static void transmit_skb_from_tx_queue_bt(struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *user; + struct cg2900_chip_info *info = dev->c_data; + struct sk_buff *skb; + + dev_dbg(dev->dev, "transmit_skb_from_tx_queue_bt\n"); + + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_bt); + while (skb) { + if (info->tx_nr_pkts_allowed_bt <= 0) { + /* + * If no more packets allowed just return, we'll get + * back here after next Command Complete/Status event. + * Put skb back at head of queue. + */ + skb_queue_head(&info->tx_queue_bt, skb); + return; + } + + (info->tx_nr_pkts_allowed_bt)--; + dev_dbg(dev->dev, "tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + user = cg2900_skb_data(skb)->user; /* user is never NULL */ + + /* + * If it's a command from audio application, store the OpCode, + * it'll be used later to decide where to dispatch + * the Command Complete event. + */ + if (info->bt_audio == user) { + struct hci_command_hdr *hdr = (struct hci_command_hdr *) + (skb->data + HCI_H4_SIZE); + + info->audio_bt_cmd_op = le16_to_cpu(hdr->opcode); + dev_dbg(user->dev, + "Sending cmd from audio driver, saving " + "OpCode = 0x%04X\n", info->audio_bt_cmd_op); + } + + cg2900_tx_to_chip(user, info->logger, skb); + + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_bt); + } +} + +/** + * transmit_skb_from_tx_queue_fm() - Check flow control info and transmit skb. + * + * The transmit_skb_from_tx_queue_fm() function checks if it possible to + * transmit and commands waiting in the TX queue and if so transmits them + * to the controller. + * It shall always be called within spinlock_bh. + */ +static void transmit_skb_from_tx_queue_fm(struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *user; + struct cg2900_chip_info *info = dev->c_data; + struct sk_buff *skb; + + dev_dbg(dev->dev, "transmit_skb_from_tx_queue_fm\n"); + + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_fm); + while (skb) { + u16 cmd_id; + u8 cmd_func; + + if (info->audio_fm_cmd_id != CG2900_FM_CMD_NONE || + info->hci_fm_cmd_func != CG2900_FM_CMD_PARAM_NONE) { + /* + * There are currently outstanding FM commands. + * Wait for them to finish. We will get back here later. + * Queue back the skb at head of list. + */ + skb_queue_head(&info->tx_queue_bt, skb); + return; + } + + user = cg2900_skb_data(skb)->user; /* user is never NULL */ + + if (!user->opened) { + /* + * Channel is not open. That means that the user that + * originally sent it has deregistered. + * Just throw it away and check the next skb in the + * queue. + */ + kfree_skb(skb); + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_fm); + continue; + } + + fm_parse_cmd(&(skb->data[0]), &cmd_func, &cmd_id); + + /* + * Store the FM command function , it'll be used later to decide + * where to dispatch the Command Complete event. + */ + if (info->fm_audio == user) { + info->audio_fm_cmd_id = cmd_id; + dev_dbg(user->dev, "Sending FM audio cmd 0x%04X\n", + info->audio_fm_cmd_id); + } else { + /* FM radio command */ + info->hci_fm_cmd_func = cmd_func; + fm_update_mode(info, &skb->data[0]); + dev_dbg(user->dev, "Sending FM radio cmd 0x%04X\n", + info->hci_fm_cmd_func); + } + + /* + * We have only one ticket on FM. Just return after + * sending the skb. + */ + cg2900_tx_to_chip(user, info->logger, skb); + return; + } +} + +/** + * update_flow_ctrl_bt() - Update number of outstanding commands for BT CMD. + * @dev: Current chip device. + * @skb: skb with received packet. + * + * The update_flow_ctrl_bt() checks if incoming data packet is + * BT Command Complete/Command Status Event and if so updates number of tickets + * and number of outstanding commands. It also calls function to send queued + * commands (if the list of queued commands is not empty). + */ +static void update_flow_ctrl_bt(struct cg2900_chip_dev *dev, + const struct sk_buff * const skb) +{ + u8 *data = skb->data; + struct hci_event_hdr *event; + struct cg2900_chip_info *info = dev->c_data; + + event = (struct hci_event_hdr *)data; + data += sizeof(*event); + + if (HCI_EV_CMD_COMPLETE == event->evt) { + struct hci_ev_cmd_complete *complete; + complete = (struct hci_ev_cmd_complete *)data; + + /* + * If it's HCI Command Complete Event then we might get some + * HCI tickets back. Also we can decrease the number outstanding + * HCI commands (if it's not NOP command or one of the commands + * that generate both Command Status Event and Command Complete + * Event). + * Check if we have any HCI commands waiting in the TX list and + * send them if there are tickets available. + */ + spin_lock_bh(&info->tx_bt_lock); + info->tx_nr_pkts_allowed_bt = complete->ncmd; + dev_dbg(dev->dev, "New tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + if (!skb_queue_empty(&info->tx_queue_bt)) + transmit_skb_from_tx_queue_bt(dev); + spin_unlock_bh(&info->tx_bt_lock); + } else if (HCI_EV_CMD_STATUS == event->evt) { + struct hci_ev_cmd_status *status; + status = (struct hci_ev_cmd_status *)data; + + /* + * If it's HCI Command Status Event then we might get some + * HCI tickets back. Also we can decrease the number outstanding + * HCI commands (if it's not NOP command). + * Check if we have any HCI commands waiting in the TX queue and + * send them if there are tickets available. + */ + spin_lock_bh(&info->tx_bt_lock); + info->tx_nr_pkts_allowed_bt = status->ncmd; + dev_dbg(dev->dev, "New tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + if (!skb_queue_empty(&info->tx_queue_bt)) + transmit_skb_from_tx_queue_bt(dev); + spin_unlock_bh(&info->tx_bt_lock); + } +} + +/** + * update_flow_ctrl_fm() - Update packets allowed for FM channel. + * @dev: Current chip device. + * @skb: skb with received packet. + * + * The update_flow_ctrl_fm() checks if incoming data packet is FM packet + * indicating that the previous command has been handled and if so update + * packets. It also calls function to send queued commands (if the list of + * queued commands is not empty). + */ +static void update_flow_ctrl_fm(struct cg2900_chip_dev *dev, + const struct sk_buff * const skb) +{ + u8 cmd_func = CG2900_FM_CMD_PARAM_NONE; + u16 cmd_id = CG2900_FM_CMD_NONE; + u16 irpt_val = 0; + u8 event = CG2900_FM_EVENT_UNKNOWN; + struct cg2900_chip_info *info = dev->c_data; + + fm_parse_event(&(skb->data[0]), &event, &cmd_func, &cmd_id, &irpt_val); + + if (event == CG2900_FM_EVENT_CMD_COMPLETE) { + /* FM legacy command complete event */ + spin_lock_bh(&info->tx_fm_lock); + /* + * Check if it's not an write command complete event, because + * then it cannot be a DO command. + * If it's a write command complete event check that is not a + * DO command complete event before setting the outstanding + * FM packets to none. + */ + if (cmd_func != CG2900_FM_CMD_PARAM_WRITECOMMAND || + !fm_irpt_expected(info, cmd_id)) { + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + dev_dbg(dev->dev, + "FM_Write: Outstanding FM commands:\n" + "\tRadio: 0x%04X\n" + "\tAudio: 0x%04X\n", + info->hci_fm_cmd_func, + info->audio_fm_cmd_id); + transmit_skb_from_tx_queue_fm(dev); + + /* + * If there was a write do command complete event check if it is + * DO command previously sent by the FM audio user. If that's + * the case we need remember that in order to be able to + * dispatch the interrupt to the correct user. + */ + } else if (cmd_id == info->audio_fm_cmd_id) { + info->tx_fm_audio_awaiting_irpt = true; + dev_dbg(dev->dev, + "FM Audio waiting for interrupt = true\n"); + } + spin_unlock_bh(&info->tx_fm_lock); + } else if (event == CG2900_FM_EVENT_INTERRUPT) { + /* FM legacy interrupt */ + if (fm_is_do_cmd_irpt(irpt_val)) { + /* + * If it is an interrupt related to a DO command update + * the outstanding flow control and transmit blocked + * FM commands. + */ + spin_lock_bh(&info->tx_fm_lock); + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + dev_dbg(dev->dev, + "FM_INT: Outstanding FM commands:\n" + "\tRadio: 0x%04X\n" + "\tAudio: 0x%04X\n", + info->hci_fm_cmd_func, + info->audio_fm_cmd_id); + info->tx_fm_audio_awaiting_irpt = false; + dev_dbg(dev->dev, + "FM Audio waiting for interrupt = false\n"); + transmit_skb_from_tx_queue_fm(dev); + spin_unlock_bh(&info->tx_fm_lock); + } + } +} + +/** + * send_bt_enable() - Send HCI VS BT Enable command to the chip. + * @info: Chip info structure. + * @bt_enable: Value for BT Enable parameter (e.g. CG2900_BT_DISABLE). + */ +static void send_bt_enable(struct cg2900_chip_info *info, u8 bt_enable) +{ + struct bt_vs_bt_enable_cmd cmd; + + cmd.op_code = cpu_to_le16(CG2900_BT_OP_VS_BT_ENABLE); + cmd.plen = BT_PARAM_LEN(sizeof(cmd)); + cmd.enable = bt_enable; + cg2900_send_bt_cmd(info->user_in_charge, info->logger, + &cmd, sizeof(cmd)); +} + +/** + * send_bd_address() - Send HCI VS command with BD address to the chip. + */ +static void send_bd_address(struct cg2900_chip_info *info) +{ + struct bt_vs_store_in_fs_cmd *cmd; + u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE; + + cmd = kmalloc(plen, GFP_KERNEL); + if (!cmd) { + dev_err(info->dev, "send_bd_address could not allocate cmd\n"); + return; + } + + cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_STORE_IN_FS); + cmd->plen = BT_PARAM_LEN(plen); + cmd->user_id = CG2900_VS_STORE_IN_FS_USR_ID_BD_ADDR; + cmd->len = BT_BDADDR_SIZE; + /* Now copy the BD address received from user space control app. */ + memcpy(cmd->data, bd_address, BT_BDADDR_SIZE); + + dev_dbg(BOOT_DEV, "New boot_state: BOOT_SEND_BD_ADDRESS\n"); + info->boot_state = BOOT_SEND_BD_ADDRESS; + + cg2900_send_bt_cmd(info->user_in_charge, info->logger, cmd, plen); + + kfree(cmd); +} + +/** + * send_settings_file() - Transmit settings file. + * + * The send_settings_file() function transmit settings file. + * The file is read in parts to fit in HCI packets. When finished, + * close the settings file and send HCI reset to activate settings and patches. + */ +static void send_settings_file(struct cg2900_chip_info *info) +{ + int bytes_sent; + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_settings_file: Error %d occurred\n", + bytes_sent); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, bytes_sent); + return; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + /* Settings file finished. Release used resources */ + dev_dbg(BOOT_DEV, "Settings file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_NO_MORE_FILES\n"); + info->file_load_state = FILE_LOAD_NO_MORE_FILES; + + /* Create and send HCI VS Store In FS command with bd address. */ + send_bd_address(info); +} + +/** + * send_patch_file - Transmit patch file. + * + * The send_patch_file() function transmit patch file. + * The file is read in parts to fit in HCI packets. When the complete file is + * transmitted, the file is closed. + * When finished, continue with settings file. + */ +static void send_patch_file(struct cg2900_chip_dev *dev) +{ + int err; + int bytes_sent; + struct cg2900_chip_info *info = dev->c_data; + int file_name_size = strlen("CG2900_XXXX_XXXX_settings.fw"); + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_patch_file: Error %d occurred\n", + bytes_sent); + err = bytes_sent; + goto error_handling; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + dev_dbg(BOOT_DEV, "Patch file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + /* + * Create the settings file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->settings_file_name, file_name_size + 1, + "CG2900_%04X_%04X_settings.fw", dev->chip.hci_revision, + dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading settings file %s\n", + info->settings_file_name); + } else { + dev_err(BOOT_DEV, "Settings file name failed! err=%d\n", err); + goto error_handling; + } + + /* Retrieve the settings file */ + err = request_firmware(&info->file_info.fw_file, + info->settings_file_name, + info->dev); + if (err) { + dev_err(BOOT_DEV, "Couldn't get settings file (%d)\n", err); + goto error_handling; + } + /* Now send the settings file */ + dev_dbg(BOOT_DEV, + "New file_load_state: FILE_LOAD_GET_STATIC_SETTINGS\n"); + info->file_load_state = FILE_LOAD_GET_STATIC_SETTINGS; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + send_settings_file(info); + return; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, err); +} + +/** + * work_power_off_chip() - Work item to power off the chip. + * @work: Reference to work data. + * + * The work_power_off_chip() function handles transmission of the HCI command + * vs_power_switch_off and then informs the CG2900 Core that this chip driver is + * finished and the Core driver can now shut off the chip. + */ +static void work_power_off_chip(struct work_struct *work) +{ + struct sk_buff *skb = NULL; + u8 *h4_header; + struct cg2900_platform_data *pf_data; + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_power_off_chip: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* + * Get the VS Power Switch Off command to use based on connected + * connectivity controller + */ + pf_data = dev_get_platdata(dev->dev); + if (pf_data->get_power_switch_off_cmd) + skb = pf_data->get_power_switch_off_cmd(dev, NULL); + + /* + * Transmit the received command. + * If no command found for the device, just continue + */ + if (!skb) { + dev_err(dev->dev, + "Could not retrieve PowerSwitchOff command\n"); + goto shut_down_chip; + } + + dev_dbg(dev->dev, + "Got power_switch_off command. Add H4 header and transmit\n"); + + /* + * Move the data pointer to the H:4 header position and store + * the H4 header + */ + h4_header = skb_push(skb, CG2900_SKB_RESERVE); + *h4_header = CHANNEL_BT_CMD; + + dev_dbg(dev->dev, "New closing_state: CLOSING_POWER_SWITCH_OFF\n"); + info->closing_state = CLOSING_POWER_SWITCH_OFF; + + if (info->user_in_charge) + cg2900_tx_to_chip(info->user_in_charge, info->logger, skb); + else + cg2900_tx_no_user(dev, skb); + + /* + * Mandatory to wait 500ms after the power_switch_off command has been + * transmitted, in order to make sure that the controller is ready. + */ + schedule_timeout_killable(msecs_to_jiffies(POWER_SW_OFF_WAIT)); + +shut_down_chip: + dev_dbg(dev->dev, "New closing_state: CLOSING_SHUT_DOWN\n"); + info->closing_state = CLOSING_SHUT_DOWN; + + /* Close the transport, which will power off the chip */ + if (dev->t_cb.close) + dev->t_cb.close(dev); + + /* Chip shut-down finished, set correct state and wake up the chip. */ + dev_dbg(dev->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + wake_up_all(&main_wait_queue); + + /* If this is called during system startup, register the devices. */ + if (info->startup) { + int err; + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, + cg2900_devs, info->mfd_size, NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add cg2900_devs (%d)\n", + err); + goto finished; + } + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, + cg2900_char_devs, info->mfd_char_size, + NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add cg2900_char_devs (%d)" + "\n", err); + mfd_remove_devices(dev->dev); + goto finished; + } + + /* + * Increase base ID so next connected transport will not get the + * same device IDs. + */ + main_info->cell_base_id += MAX(info->mfd_size, + info->mfd_char_size); + info->startup = false; + } + +finished: + kfree(my_work); +} + +/** + * work_chip_shutdown() - Shut down the chip. + * @work: Reference to work data. + */ +static void work_chip_shutdown(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_user_data *user; + + if (!work) { + dev_err(MAIN_DEV, "work_chip_shutdown: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + user = my_work->user_data; + + chip_shutdown(user); + + kfree(my_work); +} + +/** + * work_reset_after_error() - Handle reset. + * @work: Reference to work data. + * + * Handle a reset after received Command Complete event. + */ +static void work_reset_after_error(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_reset_after_error: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + chip_startup_finished(info, -EIO); + + kfree(my_work); +} + +/** + * work_load_patch_and_settings() - Start loading patches and settings. + * @work: Reference to work data. + */ +static void work_load_patch_and_settings(struct work_struct *work) +{ + int err = 0; + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int file_name_size = strlen("CG2900_XXXX_XXXX_patch.fw"); + + if (!work) { + dev_err(MAIN_DEV, + "work_load_patch_and_settings: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Check that we are in the right state */ + if (info->boot_state != BOOT_GET_FILES_TO_LOAD) + goto finished; + + /* + * Create the patch file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->patch_file_name, file_name_size + 1, + "CG2900_%04X_%04X_patch.fw", dev->chip.hci_revision, + dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading patch file %s\n", + info->patch_file_name); + } else { + dev_err(BOOT_DEV, "Patch file name failed! err=%d\n", err); + goto error_handling; + } + + /* We now all info needed */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DOWNLOAD_PATCH\n"); + info->boot_state = BOOT_DOWNLOAD_PATCH; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_GET_PATCH\n"); + info->file_load_state = FILE_LOAD_GET_PATCH; + info->file_info.chunk_id = 0; + info->file_info.file_offset = 0; + info->file_info.fw_file = NULL; + + /* OK. Now it is time to download the patches */ + err = request_firmware(&(info->file_info.fw_file), + info->patch_file_name, + dev->dev); + if (err < 0) { + dev_err(BOOT_DEV, "Couldn't get patch file (%d)\n", err); + goto error_handling; + } + send_patch_file(dev); + + goto finished; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); +finished: + kfree(my_work); +} + +/** + * work_cont_file_download() - A file block has been written. + * @work: Reference to work data. + * + * Handle a received HCI VS Write File Block Complete event. + * Normally this means continue to send files to the controller. + */ +static void work_cont_file_download(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_cont_file_download: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Continue to send patches or settings to the controller */ + if (info->file_load_state == FILE_LOAD_GET_PATCH) + send_patch_file(dev); + else if (info->file_load_state == FILE_LOAD_GET_STATIC_SETTINGS) + send_settings_file(info); + else + dev_dbg(BOOT_DEV, "No more files to load\n"); + + kfree(my_work); +} + +/** + * work_send_read_selftest_cmd() - HCI VS Read_SelfTests_Result command shall be sent. + * @work: Reference to work data. + */ +static void work_send_read_selftest_cmd(struct work_struct *work) +{ + struct delayed_work *del_work; + struct cg2900_delayed_work_struct *current_work; + struct cg2900_chip_info *info; + struct hci_command_hdr cmd; + + if (!work) { + dev_err(MAIN_DEV, + "work_send_read_selftest_cmd: work == NULL\n"); + return; + } + + del_work = to_delayed_work(work); + current_work = container_of(del_work, + struct cg2900_delayed_work_struct, work); + info = current_work->data; + + if (info->boot_state != BOOT_READ_SELFTEST_RESULT) + return; + + cmd.opcode = cpu_to_le16(CG2900_BT_OP_VS_READ_SELTESTS_RESULT); + cmd.plen = 0; /* No parameters for Read Selftests Result */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); +} + +/** + * handle_reset_cmd_complete() - Handles HCI Reset Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_reset_cmd_complete(struct cg2900_chip_dev *dev, u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, "Received Reset complete event with status 0x%X\n", + status); + + if (CG2900_CLOSING != info->main_state && + CLOSING_RESET != info->closing_state) + return false; + + if (HCI_BT_ERROR_NO_ERROR != status) { + /* + * Continue in case of error, the chip is going to be shut down + * anyway. + */ + dev_err(BOOT_DEV, "Command complete for HciReset received with " + "error 0x%X\n", status); + } + + cg2900_create_work_item(info->wq, work_power_off_chip, dev); + + return true; +} + +/** + * handle_vs_store_in_fs_cmd_complete() - Handles HCI VS StoreInFS Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_store_in_fs_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, + "Received Store_in_FS complete event with status 0x%X\n", + status); + + if (info->boot_state != BOOT_SEND_BD_ADDRESS) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) { + struct hci_command_hdr cmd; + + /* Send HCI SystemReset command to activate patches */ + dev_dbg(BOOT_DEV, + "New boot_state: BOOT_ACTIVATE_PATCHES_AND_SETTINGS\n"); + info->boot_state = BOOT_ACTIVATE_PATCHES_AND_SETTINGS; + + cmd.opcode = cpu_to_le16(CG2900_BT_OP_VS_SYSTEM_RESET); + cmd.plen = 0; /* No parameters for System Reset */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); + } else { + dev_err(BOOT_DEV, + "Command complete for StoreInFS received with error " + "0x%X\n", status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_complete() - Handles HCI VS WriteFileBlock Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) + cg2900_create_work_item(info->wq, work_cont_file_download, dev); + else { + dev_err(BOOT_DEV, + "Command complete for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_status() - Handles HCI VS WriteFileBlock Command Status event. + * @status: Returned status of WriteFileBlock command. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_status(struct cg2900_chip_dev *dev, + u8 status) +{ + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + /* + * Only do something if there is an error. Otherwise we will wait for + * CmdComplete. + */ + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, + "Command status for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_power_switch_off_cmd_complete() - Handles HCI VS PowerSwitchOff Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_power_switch_off_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (CLOSING_POWER_SWITCH_OFF != info->closing_state) + return false; + + dev_dbg(BOOT_DEV, + "handle_vs_power_switch_off_cmd_complete status %d\n", status); + + /* + * We were waiting for this but we don't need to do anything upon + * reception except warn for error status + */ + if (HCI_BT_ERROR_NO_ERROR != status) + dev_err(BOOT_DEV, + "Command Complete for PowerSwitchOff received with " + "error 0x%X", status); + + return true; +} + +/** + * handle_vs_system_reset_cmd_complete() - Handle HCI VS SystemReset Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_system_reset_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_ACTIVATE_PATCHES_AND_SETTINGS) + return false; + + dev_dbg(BOOT_DEV, "handle_vs_system_reset_cmd_complete status %d\n", + status); + + if (HCI_BT_ERROR_NO_ERROR == status) { + if (dev->chip.hci_revision == CG2900_PG2_REV) { + /* + * We must now wait for the selftest results. They will + * take a certain amount of time to finish so start a + * delayed work that will then send the command. + */ + dev_dbg(BOOT_DEV, + "New boot_state: BOOT_READ_SELFTEST_RESULT\n"); + info->boot_state = BOOT_READ_SELFTEST_RESULT; + queue_delayed_work(info->wq, &info->selftest_work.work, + msecs_to_jiffies(SELFTEST_INITIAL)); + info->nbr_of_polls = 0; + } else { + /* + * We are now almost finished. Shut off BT Core. It will + * be re-enabled by the Bluetooth driver when needed. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DISABLE_BT\n"); + info->boot_state = BOOT_DISABLE_BT; + send_bt_enable(info, CG2900_BT_DISABLE); + } + } else { + dev_err(BOOT_DEV, + "Received Reset complete event with status 0x%X\n", + status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + } + + return true; +} + +/** + * handle_vs_read_selftests_cmd_complete() - Handle HCI VS ReadSelfTestsResult Command Complete event. + * @dev: Current chip. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_read_selftests_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + struct bt_vs_read_selftests_result_evt *evt = + (struct bt_vs_read_selftests_result_evt *)data; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_READ_SELFTEST_RESULT) + return false; + + dev_dbg(BOOT_DEV, + "handle_vs_read_selftests_cmd_complete status %d result %d\n", + evt->status, evt->result); + + if (HCI_BT_ERROR_NO_ERROR != evt->status) + goto err_handling; + + if (CG2900_BT_SELFTEST_SUCCESSFUL == evt->result || + CG2900_BT_SELFTEST_FAILED == evt->result) { + if (CG2900_BT_SELFTEST_FAILED == evt->result) + dev_err(BOOT_DEV, "CG2900 self test failed\n"); + + /* + * We are now almost finished. Shut off BT Core. It will + * be re-enabled by the Bluetooth driver when needed. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DISABLE_BT\n"); + info->boot_state = BOOT_DISABLE_BT; + send_bt_enable(info, CG2900_BT_DISABLE); + return true; + } else if (CG2900_BT_SELFTEST_NOT_COMPLETED == evt->result) { + /* + * Self tests are not yet finished. Wait some more time + * before resending the command + */ + if (info->nbr_of_polls > MAX_NBR_OF_POLLS) { + dev_err(BOOT_DEV, "Selftest results reached max" + " number of polls\n"); + goto err_handling; + } + queue_delayed_work(info->wq, &info->selftest_work.work, + msecs_to_jiffies(SELFTEST_POLLING)); + info->nbr_of_polls++; + return true; + } + +err_handling: + dev_err(BOOT_DEV, + "Received Read SelfTests Result complete event with " + "status 0x%X and result 0x%X\n", + evt->status, evt->result); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + return true; +} + +/** + * handle_vs_bt_enable_cmd_status() - Handles HCI VS BtEnable Command Status event. + * @status: Returned status of BtEnable command. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_bt_enable_cmd_status(struct cg2900_chip_dev *dev, + u8 status) +{ + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DISABLE_BT) + return false; + + dev_dbg(BOOT_DEV, "handle_vs_bt_enable_cmd_status status %d\n", status); + + /* + * Only do something if there is an error. Otherwise we will wait for + * CmdComplete. + */ + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, + "Received BtEnable status event with status 0x%X\n", + status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + } + + return true; +} + +/** + * handle_vs_bt_enable_cmd_complete() - Handle HCI VS BtEnable Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_bt_enable_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DISABLE_BT) + return false; + + dev_dbg(BOOT_DEV, "handle_vs_bt_enable_cmd_complete status %d\n", + status); + + if (HCI_BT_ERROR_NO_ERROR == status) { + /* + * The boot sequence is now finished successfully. + * Set states and signal to waiting thread. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_READY\n"); + info->boot_state = BOOT_READY; + chip_startup_finished(info, 0); + } else { + dev_err(BOOT_DEV, + "Received BtEnable complete event with status 0x%X\n", + status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + } + + return true; +} + +/** + * handle_rx_data_bt_evt() - Check if received data should be handled in CG2900 chip driver. + * @skb: Data packet + * + * The handle_rx_data_bt_evt() function checks if received data should be + * handled in CG2900 chip driver. If so handle it correctly. + * Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_rx_data_bt_evt(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + bool pkt_handled = false; + /* skb cannot be NULL here so it is safe to de-reference */ + u8 *data = skb->data; + struct hci_event_hdr *evt; + u16 op_code; + + evt = (struct hci_event_hdr *)data; + data += sizeof(*evt); + + /* First check the event code. */ + if (HCI_EV_CMD_COMPLETE == evt->evt) { + struct hci_ev_cmd_complete *cmd_complete; + + cmd_complete = (struct hci_ev_cmd_complete *)data; + op_code = le16_to_cpu(cmd_complete->opcode); + dev_dbg(dev->dev, + "Received Command Complete: op_code = 0x%04X\n", + op_code); + /* Move to first byte after OCF */ + data += sizeof(*cmd_complete); + + if (op_code == HCI_OP_RESET) + pkt_handled = handle_reset_cmd_complete(dev, data); + else if (op_code == CG2900_BT_OP_VS_STORE_IN_FS) + pkt_handled = handle_vs_store_in_fs_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = + handle_vs_write_file_block_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_POWER_SWITCH_OFF) + pkt_handled = + handle_vs_power_switch_off_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_SYSTEM_RESET) + pkt_handled = handle_vs_system_reset_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_BT_ENABLE) + pkt_handled = handle_vs_bt_enable_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_READ_SELTESTS_RESULT) + pkt_handled = handle_vs_read_selftests_cmd_complete(dev, + data); + } else if (HCI_EV_CMD_STATUS == evt->evt) { + struct hci_ev_cmd_status *cmd_status; + + cmd_status = (struct hci_ev_cmd_status *)data; + + op_code = le16_to_cpu(cmd_status->opcode); + + dev_dbg(dev->dev, "Received Command Status: op_code = 0x%04X\n", + op_code); + + if (op_code == CG2900_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = handle_vs_write_file_block_cmd_status + (dev, cmd_status->status); + else if (op_code == CG2900_BT_OP_VS_BT_ENABLE) + pkt_handled = handle_vs_bt_enable_cmd_status + (dev, cmd_status->status); + } else if (HCI_EV_HW_ERROR == evt->evt) { + struct hci_ev_hw_error *hw_error; + + hw_error = (struct hci_ev_hw_error *)data; + /* + * Only do a printout. There might be a receiving stack that can + * handle this event + */ + dev_err(dev->dev, "HW Error event received with error 0x%02X\n", + hw_error->hw_code); + return false; + } else + return false; + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +/** + * transmit_skb_with_flow_ctrl_bt() - Send the BT skb to the controller if it is allowed or queue it. + * @user: Current user. + * @skb: Data packet. + * + * The transmit_skb_with_flow_ctrl_bt() function checks if there are + * tickets available and if so transmits buffer to controller. Otherwise the skb + * and user name is stored in a list for later sending. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +static void transmit_skb_with_flow_ctrl_bt(struct cg2900_user_data *user, + struct sk_buff *skb) +{ + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct cg2900_chip_info *info = dev->c_data; + + /* + * Because there are more users of some H4 channels (currently audio + * application for BT command and FM channel) we need to have an + * internal HCI command flow control in CG2900 driver. + * So check here how many tickets we have and store skb in a queue if + * there are no tickets left. The skb will be sent later when we get + * more ticket(s). + */ + spin_lock_bh(&info->tx_bt_lock); + + if (info->tx_nr_pkts_allowed_bt > 0) { + info->tx_nr_pkts_allowed_bt--; + dev_dbg(user->dev, "New tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + /* + * If it's command from audio app store the OpCode, + * it'll be used later to decide where to dispatch Command + * Complete event. + */ + if (info->bt_audio == user) { + struct hci_command_hdr *hdr = (struct hci_command_hdr *) + (skb->data + HCI_H4_SIZE); + + info->audio_bt_cmd_op = le16_to_cpu(hdr->opcode); + dev_dbg(user->dev, + "Sending cmd from audio driver, saving " + "OpCode = 0x%X\n", + info->audio_bt_cmd_op); + } + + cg2900_tx_to_chip(user, info->logger, skb); + } else { + dev_dbg(user->dev, "Not allowed to send cmd to controller, " + "storing in TX queue\n"); + + cg2900_skb_data(skb)->user = user; + skb_queue_tail(&info->tx_queue_bt, skb); + } + spin_unlock_bh(&info->tx_bt_lock); +} + +/** + * transmit_skb_with_flow_ctrl_fm() - Send the FM skb to the controller if it is allowed or queue it. + * @user: Current user. + * @skb: Data packet. + * + * The transmit_skb_with_flow_ctrl_fm() function checks if chip is available and + * if so transmits buffer to controller. Otherwise the skb and user name is + * stored in a list for later sending. + * Also it updates the FM radio mode if it's FM GOTOMODE command, this is needed + * to know how to handle some FM DO commands complete events. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +static void transmit_skb_with_flow_ctrl_fm(struct cg2900_user_data *user, + struct sk_buff *skb) +{ + u8 cmd_func = CG2900_FM_CMD_PARAM_NONE; + u16 cmd_id = CG2900_FM_CMD_NONE; + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct cg2900_chip_info *info = dev->c_data; + + fm_parse_cmd(&(skb->data[0]), &cmd_func, &cmd_id); + + /* + * If this is an FM IP disable or reset send command and also reset + * the flow control and audio user. + */ + if (cmd_func == CG2900_FM_CMD_PARAM_DISABLE || + cmd_func == CG2900_FM_CMD_PARAM_RESET) { + spin_lock_bh(&info->tx_fm_lock); + fm_reset_flow_ctrl(info); + spin_unlock_bh(&info->tx_fm_lock); + cg2900_tx_to_chip(user, info->logger, skb); + return; + } + + /* + * If this is a FM user and no FM audio user command pending just send + * FM command. It is up to the user of the FM channel to handle its own + * flow control. + */ + spin_lock_bh(&info->tx_fm_lock); + if (info->fm_audio != user && + info->audio_fm_cmd_id == CG2900_FM_CMD_NONE) { + info->hci_fm_cmd_func = cmd_func; + dev_dbg(user->dev, "Sending FM radio command 0x%04X\n", + info->hci_fm_cmd_func); + /* If a GotoMode command update FM mode */ + fm_update_mode(info, &skb->data[0]); + cg2900_tx_to_chip(user, info->logger, skb); + } else if (info->fm_audio == user && + info->hci_fm_cmd_func == CG2900_FM_CMD_PARAM_NONE && + info->audio_fm_cmd_id == CG2900_FM_CMD_NONE) { + /* + * If it's command from fm audio user store the command id. + * It'll be used later to decide where to dispatch + * command complete event. + */ + info->audio_fm_cmd_id = cmd_id; + dev_dbg(user->dev, "Sending FM audio command 0x%04X\n", + info->audio_fm_cmd_id); + cg2900_tx_to_chip(user, info->logger, skb); + } else { + dev_dbg(user->dev, + "Not allowed to send FM cmd to controller, storing in " + "TX queue\n"); + + cg2900_skb_data(skb)->user = user; + skb_queue_tail(&info->tx_queue_fm, skb); + } + spin_unlock_bh(&info->tx_fm_lock); +} + +/** + * is_bt_audio_user() - Checks if this packet is for the BT audio user. + * @info: CG2900 info. + * @h4_channel: H:4 channel for this packet. + * @skb: Packet to check. + * + * Returns: + * true if packet is for BT audio user. + * false otherwise. + */ +static bool is_bt_audio_user(struct cg2900_chip_info *info, int h4_channel, + const struct sk_buff * const skb) +{ + struct hci_event_hdr *hdr; + u8 *payload; + u16 opcode; + + if (h4_channel != CHANNEL_BT_EVT) + return false; + + hdr = (struct hci_event_hdr *)skb->data; + payload = (u8 *)(hdr + 1); /* follows header */ + + if (HCI_EV_CMD_COMPLETE == hdr->evt) + opcode = le16_to_cpu( + ((struct hci_ev_cmd_complete *)payload)->opcode); + else if (HCI_EV_CMD_STATUS == hdr->evt) + opcode = le16_to_cpu( + ((struct hci_ev_cmd_status *)payload)->opcode); + else + return false; + + if (opcode != info->audio_bt_cmd_op) + return false; + + dev_dbg(info->bt_audio->dev, "Audio BT OpCode match = 0x%04X\n", + opcode); + info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE; + return true; +} + +/** + * is_fm_audio_user() - Checks if this packet is for the FM audio user. + * @info: CG2900 info. + * @h4_channel: H:4 channel for this packet. + * @skb: Packet to check. + * + * Returns: + * true if packet is for BT audio user. + * false otherwise. + */ +static bool is_fm_audio_user(struct cg2900_chip_info *info, int h4_channel, + const struct sk_buff * const skb) +{ + u8 cmd_func; + u16 cmd_id; + u16 irpt_val; + u8 event; + + if (h4_channel != CHANNEL_FM_RADIO) + return false; + + cmd_func = CG2900_FM_CMD_PARAM_NONE; + cmd_id = CG2900_FM_CMD_NONE; + irpt_val = 0; + event = CG2900_FM_EVENT_UNKNOWN; + + fm_parse_event(&skb->data[0], &event, &cmd_func, &cmd_id, + &irpt_val); + /* Check if command complete event FM legacy interface. */ + if ((event == CG2900_FM_EVENT_CMD_COMPLETE) && + (cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) && + (cmd_id == info->audio_fm_cmd_id)) { + dev_dbg(info->fm_audio->dev, + "FM Audio Function Code match = 0x%04X\n", + cmd_id); + return true; + } + + /* Check if Interrupt legacy interface. */ + if ((event == CG2900_FM_EVENT_INTERRUPT) && + (fm_is_do_cmd_irpt(irpt_val)) && + (info->tx_fm_audio_awaiting_irpt)) + return true; + + return false; +} + +/** + * data_from_chip() - Called when data is received from the chip. + * @dev: Chip info. + * @cg2900_dev: CG2900 user for this packet. + * @skb: Packet received. + * + * The data_from_chip() function updates flow control and checks + * if packet is a response for a packet it itself has transmitted. If not it + * finds the correct user and sends the packet* to the user. + */ +static void data_from_chip(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + int h4_channel; + struct list_head *cursor; + struct cg2900_channel_item *tmp; + struct cg2900_chip_info *info = dev->c_data; + struct cg2900_user_data *user = NULL; + + spin_lock_bh(&info->rw_lock); + /* Copy RX Data into logger.*/ + if (info->logger) + cg2900_send_to_hci_logger(info->logger, skb, + LOGGER_DIRECTION_RX); + + /* + * HCI Raw user can only have exclusive access to chip, there won't be + * other users once it's opened. + */ + if (info->hci_raw && info->hci_raw->opened) { + info->hci_raw->read_cb(info->hci_raw, skb); + spin_unlock_bh(&info->rw_lock); + return; + } + + h4_channel = skb->data[0]; + skb_pull(skb, HCI_H4_SIZE); + + /* First check if it is a BT or FM audio event */ + if (is_bt_audio_user(info, h4_channel, skb)) + user = info->bt_audio; + else if (is_fm_audio_user(info, h4_channel, skb)) + user = info->fm_audio; + spin_unlock_bh(&info->rw_lock); + + /* Now check if we should update flow control */ + if (h4_channel == CHANNEL_BT_EVT) + update_flow_ctrl_bt(dev, skb); + else if (h4_channel == CHANNEL_FM_RADIO) + update_flow_ctrl_fm(dev, skb); + + /* Then check if this is a response to data we have sent */ + if (h4_channel == CHANNEL_BT_EVT && handle_rx_data_bt_evt(dev, skb)) + return; + + spin_lock_bh(&info->rw_lock); + + if (user) + goto user_found; + + /* Let's see if it is the last user */ + if (info->last_user && info->last_user->h4_channel == h4_channel) { + user = info->last_user; + goto user_found; + } + + /* + * Search through the list of all open channels to find the user. + * We skip the audio channels since they have already been checked + * earlier in this function. + */ + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == h4_channel && + !tmp->user->is_audio) { + user = tmp->user; + goto user_found; + } + } + +user_found: + if (user != info->bt_audio && user != info->fm_audio) + info->last_user = user; + + spin_unlock_bh(&info->rw_lock); + + if (user) + user->read_cb(user, skb); + else { + dev_err(dev->dev, + "Could not find corresponding user to h4_channel %d\n", + h4_channel); + kfree_skb(skb); + } +} + +/** + * chip_removed() - Called when transport has been removed. + * @dev: Chip device. + * + * Removes registered MFD devices and frees internal resources. + */ +static void chip_removed(struct cg2900_chip_dev *dev) +{ + struct cg2900_chip_info *info = dev->c_data; + + cancel_delayed_work(&info->selftest_work.work); + mfd_remove_devices(dev->dev); + kfree(info->settings_file_name); + kfree(info->patch_file_name); + destroy_workqueue(info->wq); + kfree(info); + dev->c_data = NULL; + dev->c_cb.chip_removed = NULL; + dev->c_cb.data_from_chip = NULL; +} + +/** + * last_bt_user_removed() - Called when last BT user is removed. + * @info: Chip handler info. + * + * Clears out TX queue for BT. + */ +static void last_bt_user_removed(struct cg2900_chip_info *info) +{ + spin_lock_bh(&info->tx_bt_lock); + skb_queue_purge(&info->tx_queue_bt); + + /* + * Reset number of packets allowed and number of outstanding + * BT commands. + */ + info->tx_nr_pkts_allowed_bt = 1; + /* Reset the audio_bt_cmd_op. */ + info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE; + spin_unlock_bh(&info->tx_bt_lock); +} + +/** + * last_fm_user_removed() - Called when last FM user is removed. + * @info: Chip handler info. + * + * Clears out TX queue for BT. + */ +static void last_fm_user_removed(struct cg2900_chip_info *info) +{ + spin_lock_bh(&info->tx_fm_lock); + fm_reset_flow_ctrl(info); + spin_unlock_bh(&info->tx_fm_lock); +} + +/** + * chip_shutdown() - Reset and power the chip off. + * @user: MFD device. + */ +static void chip_shutdown(struct cg2900_user_data *user) +{ + struct hci_command_hdr cmd; + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct cg2900_chip_info *info = dev->c_data; + + dev_dbg(user->dev, "chip_shutdown\n"); + + /* First do a quick power switch of the chip to assure a good state */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, false); + + /* + * Wait 50ms before continuing to be sure that the chip detects + * chip power off. + */ + schedule_timeout_killable( + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait 100ms before continuing to be sure that the chip is ready */ + schedule_timeout_killable(msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + if (user != info->bt_audio && user != info->fm_audio) + info->last_user = user; + info->user_in_charge = user; + + /* + * Transmit HCI reset command to ensure the chip is using + * the correct transport and to put BT part in reset. + */ + dev_dbg(user->dev, "New closing_state: CLOSING_RESET\n"); + info->closing_state = CLOSING_RESET; + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); +} + +/** + * chip_startup_finished() - Called when chip startup has finished. + * @info: Chip handler info. + * @err: Result of chip startup, 0 for no error. + * + * Shuts down the chip upon error, sets state to active, wakes waiting threads, + * and informs transport that startup has finished. + */ +static void chip_startup_finished(struct cg2900_chip_info *info, int err) +{ + dev_dbg(BOOT_DEV, "chip_startup_finished (%d)\n", err); + + if (err) + /* Shutdown the chip */ + cg2900_create_work_item(info->wq, work_chip_shutdown, + info->user_in_charge); + else { + dev_dbg(BOOT_DEV, "New main_state: CG2900_ACTIVE\n"); + info->main_state = CG2900_ACTIVE; + } + + wake_up_all(&main_wait_queue); + + if (err) + return; + + if (!info->chip_dev->t_cb.chip_startup_finished) + dev_dbg(BOOT_DEV, "chip_startup_finished callback not found\n"); + else + info->chip_dev->t_cb.chip_startup_finished(info->chip_dev); +} + +/** + * cg2900_open() - Called when user wants to open an H4 channel. + * @user: MFD device to open. + * + * Checks that H4 channel is not already opened. If chip is not started, starts + * up the chip. Sets channel as opened and adds user to active users. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL or read_cb is NULL. + * -EBUSY if chip is in transit state (being started or shutdown). + * -EACCES if H4 channel is already opened. + * -ENOMEM if allocation fails. + * -EIO if chip startup fails. + * Error codes generated by t_cb.open. + */ +static int cg2900_open(struct cg2900_user_data *user) +{ + int err; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + struct list_head *cursor; + struct cg2900_channel_item *tmp; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + if (!user->read_cb) { + dev_err(user->dev, "cg2900_open: read_cb missing\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + /* HCI Raw channel shall have exclusive access to chip. */ + if (info->hci_raw && user->h4_channel != CHANNEL_HCI_RAW && + user->h4_channel != CHANNEL_HCI_LOGGER) { + dev_err(user->dev, "cg2900_open: Cannot open %s " + "channel while HCI Raw channel is opened\n", + user->channel_data.char_dev_name); + return -EACCES; + } + + mutex_lock(&main_info->man_mutex); + + /* + * Add a minor wait in order to avoid CPU blocking, looping openings. + * Note there will of course be no wait if we are already in the right + * state. + */ + err = wait_event_timeout(main_wait_queue, + (CG2900_IDLE == info->main_state || + CG2900_ACTIVE == info->main_state), + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + if (err <= 0) { + if (CG2900_INIT == info->main_state) + dev_err(user->dev, "Transport not opened\n"); + else + dev_err(user->dev, "cg2900_open currently busy (0x%X). " + "Try again\n", info->main_state); + err = -EBUSY; + goto err_free_mutex; + } + + err = 0; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == user->h4_channel && + tmp->user->is_audio == user->is_audio) { + dev_err(user->dev, "Channel %d is already opened\n", + user->h4_channel); + err = -EACCES; + goto err_free_mutex; + } + } + + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) { + dev_err(user->dev, "Could not allocate tmp\n"); + err = -ENOMEM; + goto err_free_mutex; + } + tmp->user = user; + + if (CG2900_ACTIVE != info->main_state && + !user->chip_independent) { + /* Open transport and start-up the chip */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait to be sure that the chip is ready */ + schedule_timeout_killable( + msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + if (dev->t_cb.open) { + err = dev->t_cb.open(dev); + if (err) { + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, false); + goto err_free_list_item; + } + } + + /* Start the boot sequence */ + info->user_in_charge = user; + if (user != info->bt_audio && user != info->fm_audio) + info->last_user = user; + dev_dbg(user->dev, "New boot_state: BOOT_GET_FILES_TO_LOAD\n"); + info->boot_state = BOOT_GET_FILES_TO_LOAD; + dev_dbg(user->dev, "New main_state: CG2900_BOOTING\n"); + info->main_state = CG2900_BOOTING; + cg2900_create_work_item(info->wq, work_load_patch_and_settings, + dev); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to start\n"); + wait_event_timeout(main_wait_queue, + (CG2900_ACTIVE == info->main_state || + CG2900_IDLE == info->main_state), + msecs_to_jiffies(CHIP_STARTUP_TIMEOUT)); + if (CG2900_ACTIVE != info->main_state) { + dev_err(user->dev, "CG2900 driver failed to start\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + err = -EIO; + goto err_free_list_item; + } + } + + list_add_tail(&tmp->list, &info->open_channels); + + user->opened = true; + + dev_dbg(user->dev, "H:4 channel opened\n"); + + mutex_unlock(&main_info->man_mutex); + return 0; +err_free_list_item: + kfree(tmp); +err_free_mutex: + mutex_unlock(&main_info->man_mutex); + return err; +} + +/** + * cg2900_hci_log_open() - Called when user wants to open HCI logger channel. + * @user: MFD device to open. + * + * Registers user as hci_logger and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EEXIST if H4 channel is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_hci_log_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_hci_log_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_hci_log_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->logger) { + dev_err(user->dev, "HCI Logger already stored\n"); + return -EEXIST; + } + + info->logger = user; + err = cg2900_open(user); + if (err) + info->logger = NULL; + return err; +} + +/** + * cg2900_hci_raw_open() - Called when user wants to open HCI Raw channel. + * @user: MFD device to open. + * + * Registers user as hci_raw and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EEXIST if H4 channel is already opened. + * -EACCES if H4 channel iother than HCI RAW is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_hci_raw_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + struct list_head *cursor; + struct cg2900_channel_item *tmp; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_hci_raw_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_hci_raw_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->hci_raw) { + dev_err(user->dev, "HCI Raw Channel already stored\n"); + return -EEXIST; + } + + if (!list_empty(&info->open_channels)) { + /* + * Go through each open channel to check if it is logger + * channel or some other channel. + */ + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, + struct cg2900_channel_item, list); + if (tmp->user->h4_channel != CHANNEL_HCI_LOGGER) { + dev_err(user->dev, "Other channels other than " + "Logger is already opened. Cannot open " + "HCI Raw Channel\n"); + return -EACCES; + } + } + } + + info->hci_raw = user; + err = cg2900_open(user); + if (err) + info->hci_raw = NULL; + return err; +} + +/** + * cg2900_bt_audio_open() - Called when user wants to open BT audio channel. + * @user: MFD device to open. + * + * Registers user as bt_audio and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EEXIST if H4 channel is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_bt_audio_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_bt_audio_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_bt_audio_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->bt_audio) { + dev_err(user->dev, "BT Audio already stored\n"); + return -EEXIST; + } + + info->bt_audio = user; + err = cg2900_open(user); + if (err) + info->bt_audio = NULL; + return err; +} + +/** + * cg2900_fm_audio_open() - Called when user wants to open FM audio channel. + * @user: MFD device to open. + * + * Registers user as fm_audio and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EEXIST if H4 channel is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_fm_audio_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_fm_audio_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_fm_audio_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->fm_audio) { + dev_err(user->dev, "FM Audio already stored\n"); + return -EEXIST; + } + + info->fm_audio = user; + err = cg2900_open(user); + if (err) + info->fm_audio = NULL; + return err; +} + +/** + * cg2900_close() - Called when user wants to close an H4 channel. + * @user: MFD device to close. + * + * Clears up internal resources, sets channel as closed, and shuts down chip if + * this was the last user. + */ +static void cg2900_close(struct cg2900_user_data *user) +{ + bool keep_powered = false; + struct list_head *cursor, *next; + struct cg2900_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + mutex_lock(&main_info->man_mutex); + + /* + * Go through each open channel. Remove our channel and check if there + * is any other channel that want to keep the chip running + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user == user) { + list_del(cursor); + kfree(tmp); + } else if (!tmp->user->chip_independent) + keep_powered = true; + } + + if (user->h4_channel == CHANNEL_BT_CMD && !bt_is_open(info)) + last_bt_user_removed(info); + else if (user->h4_channel == CHANNEL_FM_RADIO && !fm_is_open(info)) + last_fm_user_removed(info); + + if (keep_powered) + /* This was not the last user, we're done. */ + goto finished; + + if (CG2900_IDLE == info->main_state) + /* Chip has already been shut down. */ + goto finished; + + dev_dbg(user->dev, "New main_state: CG2900_CLOSING\n"); + info->main_state = CG2900_CLOSING; + chip_shutdown(user); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to shut-down\n"); + wait_event_timeout(main_wait_queue, + (CG2900_IDLE == info->main_state), + msecs_to_jiffies(CHIP_SHUTDOWN_TIMEOUT)); + + /* Force shutdown if we timed out */ + if (CG2900_IDLE != info->main_state) { + dev_err(user->dev, + "ST-Ericsson CG2900 Core Driver was shut-down with " + "problems\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + } + +finished: + mutex_unlock(&main_info->man_mutex); + user->opened = false; + dev_dbg(user->dev, "H:4 channel closed\n"); +} + +/** + * cg2900_hci_log_close() - Called when user wants to close HCI logger channel. + * @user: MFD device to close. + * + * Clears hci_logger user and calls @cg2900_close to close the channel. + */ +static void cg2900_hci_log_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_hci_log_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_hci_log_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->logger) { + dev_err(user->dev, "cg2900_hci_log_close: Trying to remove " + "another user\n"); + return; + } + + info->logger = NULL; + cg2900_close(user); +} + +/** + * cg2900_hci_raw_close() - Called when user wants to close HCI Raw channel. + * @user: MFD device to close. + * + * Clears hci_raw user and calls @cg2900_close to close the channel. + */ +static void cg2900_hci_raw_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_hci_raw_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_hci_raw_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->hci_raw) { + dev_err(user->dev, "cg2900_hci_raw_close: Trying to remove " + "another user\n"); + return; + } + + info->hci_raw = NULL; + cg2900_close(user); +} + +/** + * cg2900_bt_audio_close() - Called when user wants to close BT audio channel. + * @user: MFD device to close. + * + * Clears bt_audio user and calls @cg2900_close to close the channel. + */ +static void cg2900_bt_audio_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_bt_audio_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_bt_audio_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->bt_audio) { + dev_err(user->dev, "cg2900_bt_audio_close: Trying to remove " + "another user\n"); + return; + } + + info->bt_audio = NULL; + cg2900_close(user); +} + +/** + * cg2900_fm_audio_close() - Called when user wants to close FM audio channel. + * @user: MFD device to close. + * + * Clears fm_audio user and calls @cg2900_close to close the channel. + */ +static void cg2900_fm_audio_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_fm_audio_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_fm_audio_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->fm_audio) { + dev_err(user->dev, "cg2900_fm_audio_close: Trying to remove " + "another user\n"); + return; + } + + info->fm_audio = NULL; + cg2900_close(user); +} + +/** + * cg2900_reset() - Called when user wants to reset the chip. + * @user: MFD device to reset. + * + * Closes down the chip and calls reset_cb for all open users. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + */ +static int cg2900_reset(struct cg2900_user_data *user) +{ + struct list_head *cursor, *next; + struct cg2900_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!user) { + dev_err(MAIN_DEV, "cg2900_reset: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_info(user->dev, "cg2900_reset\n"); + + BUG_ON(!main_info); + + mutex_lock(&main_info->man_mutex); + + dev_dbg(user->dev, "New main_state: CG2900_RESETING\n"); + info->main_state = CG2900_RESETING; + + chip_shutdown(user); + + /* + * Inform all opened channels about the reset and free the user devices + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + list_del(cursor); + tmp->user->opened = false; + tmp->user->reset_cb(tmp->user); + kfree(tmp); + } + + /* Reset finished. We are now idle until first channel is opened */ + dev_dbg(user->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + + mutex_unlock(&main_info->man_mutex); + + /* + * Send wake-up since this might have been called from a failed boot. + * No harm done if it is a CG2900 chip user who called. + */ + wake_up_all(&main_wait_queue); + + return 0; +} + +/** + * cg2900_alloc_skb() - Allocates socket buffer. + * @size: Sk_buffer size in bytes. + * @priority: GFP priorit for allocation. + * + * Allocates a sk_buffer and reserves space for H4 header. + * + * Returns: + * sk_buffer if success. + * NULL if allocation fails. + */ +static struct sk_buff *cg2900_alloc_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + dev_dbg(MAIN_DEV, "cg2900_alloc_skb size %d bytes\n", size); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + CG2900_SKB_RESERVE, priority); + if (skb) + skb_reserve(skb, CG2900_SKB_RESERVE); + + return skb; +} + +/** + * cg2900_write() - Called when user wants to write to the chip. + * @user: MFD device representing H4 channel to write to. + * @skb: Sk_buffer to transmit. + * + * Transmits the sk_buffer to the chip. If it is a BT cmd or FM audio packet it + * is checked that it is allowed to transmit the chip. + * Note that if error is returned it is up to the user to free the skb. + * + * Returns: + * 0 if success. + * -EINVAL if user or skb is NULL. + * -EACCES if channel is closed. + */ +static int cg2900_write(struct cg2900_user_data *user, struct sk_buff *skb) +{ + u8 *h4_header; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_write: Calling with NULL pointer\n"); + return -EINVAL; + } + + if (!skb) { + dev_err(user->dev, "cg2900_write with no sk_buffer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_dbg(user->dev, "cg2900_write length %d bytes\n", skb->len); + + if (!user->opened) { + dev_err(user->dev, + "Trying to transmit data on a closed channel\n"); + return -EACCES; + } + + if (user->h4_channel == CHANNEL_HCI_RAW) { + /* + * Since the data transmitted on HCI Raw channel + * can be byte by byte, flow control cannot be used. + * This should be handled by user space application + * of the HCI Raw channel, so just transmit the + * received data to chip. + */ + cg2900_tx_to_chip(user, info->logger, skb); + return 0; + } + + /* + * Move the data pointer to the H:4 header position and + * store the H4 header. + */ + h4_header = skb_push(skb, CG2900_SKB_RESERVE); + *h4_header = (u8)user->h4_channel; + + if (user->h4_channel == CHANNEL_BT_CMD) + transmit_skb_with_flow_ctrl_bt(user, skb); + else if (user->h4_channel == CHANNEL_FM_RADIO) + transmit_skb_with_flow_ctrl_fm(user, skb); + else + cg2900_tx_to_chip(user, info->logger, skb); + + return 0; +} + +/** + * cg2900_no_write() - Used for channels where it is not allowed to write. + * @user: MFD device representing H4 channel to write to. + * @skb: Sk_buffer to transmit. + * + * Returns: + * -EPERM. + */ +static int cg2900_no_write(struct cg2900_user_data *user, + __attribute__((unused)) struct sk_buff *skb) +{ + dev_err(user->dev, "Not allowed to send on this channel\n"); + return -EPERM; +} + +/** + * cg2900_get_local_revision() - Called to retrieve revision data for the chip. + * @user: MFD device to check. + * @rev_data: Revision data to fill in. + * + * Returns: + * true if success. + * false upon failure. + */ +static bool cg2900_get_local_revision(struct cg2900_user_data *user, + struct cg2900_rev_data *rev_data) +{ + struct cg2900_chip_dev *dev; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_get_local_revision: Calling with " + "NULL pointer\n"); + return false; + } + + if (!rev_data) { + dev_err(user->dev, "Calling with rev_data NULL\n"); + return false; + } + + dev = cg2900_get_prv(user); + + rev_data->revision = dev->chip.hci_revision; + rev_data->sub_version = dev->chip.hci_sub_version; + + return true; +} + +static struct cg2900_user_data btcmd_data = { + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data btacl_data = { + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data btevt_data = { + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data fm_data = { + .h4_channel = CHANNEL_FM_RADIO, +}; +static struct cg2900_user_data gnss_data = { + .h4_channel = CHANNEL_GNSS, +}; +static struct cg2900_user_data debug_data = { + .h4_channel = CHANNEL_DEBUG, +}; +static struct cg2900_user_data ste_tools_data = { + .h4_channel = CHANNEL_STE_TOOLS, +}; +static struct cg2900_user_data hci_logger_data = { + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = cg2900_no_write, + .open = cg2900_hci_log_open, + .close = cg2900_hci_log_close, +}; +static struct cg2900_user_data core_data = { + .h4_channel = CHANNEL_CORE, + .write = cg2900_no_write, +}; +static struct cg2900_user_data audio_bt_data = { + .h4_channel = CHANNEL_BT_CMD, + .is_audio = true, + .open = cg2900_bt_audio_open, + .close = cg2900_bt_audio_close, +}; +static struct cg2900_user_data audio_fm_data = { + .h4_channel = CHANNEL_FM_RADIO, + .is_audio = true, + .open = cg2900_fm_audio_open, + .close = cg2900_fm_audio_close, +}; +static struct cg2900_user_data hci_raw_data = { + .h4_channel = CHANNEL_HCI_RAW, + .open = cg2900_hci_raw_open, + .close = cg2900_hci_raw_close, +}; + +static struct mfd_cell cg2900_devs[] = { + { + .name = "cg2900-btcmd", + .platform_data = &btcmd_data, + .pdata_size = sizeof(btcmd_data), + }, + { + .name = "cg2900-btacl", + .platform_data = &btacl_data, + .pdata_size = sizeof(btacl_data), + }, + { + .name = "cg2900-btevt", + .platform_data = &btevt_data, + .pdata_size = sizeof(btevt_data), + }, + { + .name = "cg2900-fm", + .platform_data = &fm_data, + .pdata_size = sizeof(fm_data), + }, + { + .name = "cg2900-gnss", + .platform_data = &gnss_data, + .pdata_size = sizeof(gnss_data), + }, + { + .name = "cg2900-debug", + .platform_data = &debug_data, + .pdata_size = sizeof(debug_data), + }, + { + .name = "cg2900-stetools", + .platform_data = &ste_tools_data, + .pdata_size = sizeof(ste_tools_data), + }, + { + .name = "cg2900-hcilogger", + .platform_data = &hci_logger_data, + .pdata_size = sizeof(hci_logger_data), + }, + { + .name = "cg2900-core", + .platform_data = &core_data, + .pdata_size = sizeof(core_data), + }, + { + .name = "cg2900-audiobt", + .platform_data = &audio_bt_data, + .pdata_size = sizeof(audio_bt_data), + }, + { + .name = "cg2900-audiofm", + .platform_data = &audio_fm_data, + .pdata_size = sizeof(audio_fm_data), + }, + { + .name = "cg2900-hciraw", + .platform_data = &hci_raw_data, + .pdata_size = sizeof(hci_raw_data), + }, +}; + +static struct cg2900_user_data char_btcmd_data = { + .channel_data = { + .char_dev_name = CG2900_BT_CMD, + }, + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data char_btacl_data = { + .channel_data = { + .char_dev_name = CG2900_BT_ACL, + }, + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data char_btevt_data = { + .channel_data = { + .char_dev_name = CG2900_BT_EVT, + }, + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data char_fm_data = { + .channel_data = { + .char_dev_name = CG2900_FM_RADIO, + }, + .h4_channel = CHANNEL_FM_RADIO, +}; +static struct cg2900_user_data char_gnss_data = { + .channel_data = { + .char_dev_name = CG2900_GNSS, + }, + .h4_channel = CHANNEL_GNSS, +}; +static struct cg2900_user_data char_debug_data = { + .channel_data = { + .char_dev_name = CG2900_DEBUG, + }, + .h4_channel = CHANNEL_DEBUG, +}; +static struct cg2900_user_data char_ste_tools_data = { + .channel_data = { + .char_dev_name = CG2900_STE_TOOLS, + }, + .h4_channel = CHANNEL_STE_TOOLS, +}; +static struct cg2900_user_data char_hci_logger_data = { + .channel_data = { + .char_dev_name = CG2900_HCI_LOGGER, + }, + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = cg2900_no_write, + .open = cg2900_hci_log_open, + .close = cg2900_hci_log_close, +}; +static struct cg2900_user_data char_core_data = { + .channel_data = { + .char_dev_name = CG2900_CORE, + }, + .h4_channel = CHANNEL_CORE, + .write = cg2900_no_write, +}; +static struct cg2900_user_data char_audio_bt_data = { + .channel_data = { + .char_dev_name = CG2900_BT_AUDIO, + }, + .h4_channel = CHANNEL_BT_CMD, + .is_audio = true, +}; +static struct cg2900_user_data char_audio_fm_data = { + .channel_data = { + .char_dev_name = CG2900_FM_AUDIO, + }, + .h4_channel = CHANNEL_FM_RADIO, + .is_audio = true, +}; +static struct cg2900_user_data char_hci_raw_data = { + .channel_data = { + .char_dev_name = CG2900_HCI_RAW, + }, + .h4_channel = CHANNEL_HCI_RAW, + .open = cg2900_hci_raw_open, + .close = cg2900_hci_raw_close, +}; + + +static struct mfd_cell cg2900_char_devs[] = { + { + .name = "cg2900-chardev", + .id = 0, + .platform_data = &char_btcmd_data, + .pdata_size = sizeof(char_btcmd_data), + }, + { + .name = "cg2900-chardev", + .id = 1, + .platform_data = &char_btacl_data, + .pdata_size = sizeof(char_btacl_data), + }, + { + .name = "cg2900-chardev", + .id = 2, + .platform_data = &char_btevt_data, + .pdata_size = sizeof(char_btevt_data), + }, + { + .name = "cg2900-chardev", + .id = 3, + .platform_data = &char_fm_data, + .pdata_size = sizeof(char_fm_data), + }, + { + .name = "cg2900-chardev", + .id = 4, + .platform_data = &char_gnss_data, + .pdata_size = sizeof(char_gnss_data), + }, + { + .name = "cg2900-chardev", + .id = 5, + .platform_data = &char_debug_data, + .pdata_size = sizeof(char_debug_data), + }, + { + .name = "cg2900-chardev", + .id = 6, + .platform_data = &char_ste_tools_data, + .pdata_size = sizeof(char_ste_tools_data), + }, + { + .name = "cg2900-chardev", + .id = 7, + .platform_data = &char_hci_logger_data, + .pdata_size = sizeof(char_hci_logger_data), + }, + { + .name = "cg2900-chardev", + .id = 8, + .platform_data = &char_core_data, + .pdata_size = sizeof(char_core_data), + }, + { + .name = "cg2900-chardev", + .id = 9, + .platform_data = &char_audio_bt_data, + .pdata_size = sizeof(char_audio_bt_data), + }, + { + .name = "cg2900-chardev", + .id = 10, + .platform_data = &char_audio_fm_data, + .pdata_size = sizeof(char_audio_fm_data), + }, + { + .name = "cg2900-chardev", + .id = 11, + .platform_data = &char_hci_raw_data, + .pdata_size = sizeof(char_hci_raw_data), + }, +}; + +/** + * set_plat_data() - Initializes data for an MFD cell. + * @cell: MFD cell. + * @dev: Current chip. + * + * Sets each callback to default function unless already set. + */ +static void set_plat_data(struct mfd_cell *cell, struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *pf_data = cell->platform_data; + + if (!pf_data->open) + pf_data->open = cg2900_open; + if (!pf_data->close) + pf_data->close = cg2900_close; + if (!pf_data->reset) + pf_data->reset = cg2900_reset; + if (!pf_data->alloc_skb) + pf_data->alloc_skb = cg2900_alloc_skb; + if (!pf_data->write) + pf_data->write = cg2900_write; + if (!pf_data->get_local_revision) + pf_data->get_local_revision = cg2900_get_local_revision; + + cg2900_set_prv(pf_data, dev); +} + +/** + * check_chip_support() - Checks if connected chip is handled by this driver. + * @dev: Chip info structure. + * + * First check if chip is supported by this driver. If that is the case fill in + * the callbacks in @dev and initiate internal variables. Finally create MFD + * devices for all supported H4 channels. When finished power off the chip. + * + * Returns: + * true if chip is handled by this driver. + * false otherwise. + */ +static bool check_chip_support(struct cg2900_chip_dev *dev) +{ + struct cg2900_platform_data *pf_data; + struct cg2900_chip_info *info; + int i; + + dev_dbg(dev->dev, "check_chip_support\n"); + + /* + * Check if this is a CG2900 revision. + * We do not care about the sub-version at the moment. Change this if + * necessary. + */ + if ((dev->chip.manufacturer != CG2900_SUPP_MANUFACTURER) || + (dev->chip.hci_revision != CG2900_PG1_SPECIAL_REV && + (dev->chip.hci_revision < CG2900_SUPP_REVISION_MIN || + dev->chip.hci_revision > CG2900_SUPP_REVISION_MAX))) { + dev_dbg(dev->dev, "Chip not supported by CG2900 driver\n" + "\tMan: 0x%02X\n" + "\tRev: 0x%04X\n" + "\tSub: 0x%04X\n", + dev->chip.manufacturer, dev->chip.hci_revision, + dev->chip.hci_sub_version); + return false; + } + + /* Store needed data */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Couldn't allocate info struct\n"); + return false; + } + + /* Initialize all variables */ + skb_queue_head_init(&info->tx_queue_bt); + skb_queue_head_init(&info->tx_queue_fm); + + INIT_LIST_HEAD(&info->open_channels); + + spin_lock_init(&info->tx_bt_lock); + spin_lock_init(&info->tx_fm_lock); + spin_lock_init(&info->rw_lock); + + info->tx_nr_pkts_allowed_bt = 1; + info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE; + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + info->fm_radio_mode = FM_RADIO_MODE_IDLE; + info->chip_dev = dev; + info->dev = dev->dev; + + info->wq = create_singlethread_workqueue(WQ_NAME); + if (!info->wq) { + dev_err(dev->dev, "Could not create workqueue\n"); + goto err_handling_free_info; + } + + info->patch_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->patch_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffer for patch file\n"); + goto err_handling_destroy_wq; + } + + info->settings_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->settings_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffers settings file\n"); + goto err_handling_free_patch_name; + } + + info->selftest_work.data = info; + INIT_DELAYED_WORK(&info->selftest_work.work, + work_send_read_selftest_cmd); + + dev->c_data = info; + /* Set the callbacks */ + dev->c_cb.data_from_chip = data_from_chip; + dev->c_cb.chip_removed = chip_removed; + + mutex_lock(&main_info->man_mutex); + + pf_data = dev_get_platdata(dev->dev); + btcmd_data.channel_data.bt_bus = pf_data->bus; + btacl_data.channel_data.bt_bus = pf_data->bus; + btevt_data.channel_data.bt_bus = pf_data->bus; + + for (i = 0; i < ARRAY_SIZE(cg2900_devs); i++) + set_plat_data(&cg2900_devs[i], dev); + for (i = 0; i < ARRAY_SIZE(cg2900_char_devs); i++) + set_plat_data(&cg2900_char_devs[i], dev); + + info->startup = true; + info->mfd_size = ARRAY_SIZE(cg2900_devs); + info->mfd_char_size = ARRAY_SIZE(cg2900_char_devs); + + /* + * The devices will be registered when chip has been powered down, i.e. + * when the system startup is ready. + */ + + mutex_unlock(&main_info->man_mutex); + + dev_info(dev->dev, "Chip supported by the CG2900 chip driver\n"); + + /* Finish by turning off the chip */ + cg2900_create_work_item(info->wq, work_power_off_chip, dev); + + return true; + +err_handling_free_patch_name: + kfree(info->patch_file_name); +err_handling_destroy_wq: + destroy_workqueue(info->wq); +err_handling_free_info: + kfree(info); + return false; +} + +static struct cg2900_id_callbacks chip_support_callbacks = { + .check_chip_support = check_chip_support, +}; + +/** + * cg2900_chip_probe() - Initialize CG2900 chip handler resources. + * @pdev: Platform device. + * + * This function initializes the CG2900 driver, then registers to + * the CG2900 Core. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * Error codes generated by cg2900_register_chip_driver. + */ +static int __devinit cg2900_chip_probe(struct platform_device *pdev) +{ + int err; + + dev_dbg(&pdev->dev, "cg2900_chip_probe\n"); + + main_info = kzalloc(sizeof(*main_info), GFP_ATOMIC); + if (!main_info) { + dev_err(&pdev->dev, "Couldn't allocate main_info\n"); + return -ENOMEM; + } + + main_info->dev = &pdev->dev; + mutex_init(&main_info->man_mutex); + + err = cg2900_register_chip_driver(&chip_support_callbacks); + if (err) { + dev_err(&pdev->dev, + "Couldn't register chip driver (%d)\n", err); + goto error_handling; + } + + dev_info(&pdev->dev, "CG2900 chip driver started\n"); + + return 0; + +error_handling: + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return err; +} + +/** + * cg2900_chip_remove() - Release CG2900 chip handler resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success (always success). + */ +static int __devexit cg2900_chip_remove(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "CG2900 chip driver removed\n"); + + cg2900_deregister_chip_driver(&chip_support_callbacks); + + if (!main_info) + return 0; + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return 0; +} + +static struct platform_driver cg2900_chip_driver = { + .driver = { + .name = "cg2900-chip", + .owner = THIS_MODULE, + }, + .probe = cg2900_chip_probe, + .remove = __devexit_p(cg2900_chip_remove), +}; + +/** + * cg2900_chip_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_chip_init(void) +{ + pr_debug("cg2900_chip_init"); + return platform_driver_register(&cg2900_chip_driver); +} + +/** + * cg2900_chip_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_chip_exit(void) +{ + pr_debug("cg2900_chip_exit"); + platform_driver_unregister(&cg2900_chip_driver); +} + +module_init(cg2900_chip_init); +module_exit(cg2900_chip_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux CG2900 Connectivity Device Driver"); diff --git a/drivers/staging/cg2900/mfd/cg2900_chip.h b/drivers/staging/cg2900/mfd/cg2900_chip.h new file mode 100644 index 00000000000..b3fd556b7d7 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_chip.h @@ -0,0 +1,611 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#ifndef _CG2900_CHIP_H_ +#define _CG2900_CHIP_H_ + +/* + * Utility + */ + +static inline void set_low_nibble(__u8 *var, __u8 value) +{ + *var = (*var & 0xf0) | (value & 0x0f); +} + +static inline void set_high_nibble(__u8 *var, __u8 value) +{ + *var = (*var & 0x0f) | (value << 4); +} + +static inline void store_bit(__u8 *var, size_t bit, __u8 value) +{ + *var = (*var & ~(1u << bit)) | (value << bit); +} + +/* + * General chip defines + */ + +/* Supported chips */ +#define CG2900_SUPP_MANUFACTURER 0x30 +#define CG2900_SUPP_REVISION_MIN 0x0100 +#define CG2900_SUPP_REVISION_MAX 0x0200 + +/* Specific chip version data */ +#define CG2900_PG1_REV 0x0101 +#define CG2900_PG2_REV 0x0200 +#define CG2900_PG1_SPECIAL_REV 0x0700 + +/* + * Bluetooth + */ + +#define BT_SIZE_OF_HDR (sizeof(__le16) + sizeof(__u8)) +#define BT_PARAM_LEN(__pkt_len) (__pkt_len - BT_SIZE_OF_HDR) + +struct bt_cmd_cmpl_event { + __u8 eventcode; + __u8 plen; + __u8 n_commands; + __le16 opcode; + /* + * According to BT-specification what follows is "parameters" + * and unique to every command, but all commands start the + * parameters with the status field so include it here for + * convenience + */ + __u8 status; + __u8 data[]; +} __packed; + +/* BT VS Store In FS command */ +#define CG2900_BT_OP_VS_STORE_IN_FS 0xFC22 +struct bt_vs_store_in_fs_cmd { + __le16 opcode; + __u8 plen; + __u8 user_id; + __u8 len; + __u8 data[]; +} __packed; + +#define CG2900_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE + +/* BT VS Write File Block command */ +#define CG2900_BT_OP_VS_WRITE_FILE_BLOCK 0xFC2E +struct bt_vs_write_file_block_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 data[]; +} __packed; + +#define CG2900_BT_DISABLE 0x00 +#define CG2900_BT_ENABLE 0x01 + +/* BT VS BT Enable command */ +#define CG2900_BT_OP_VS_BT_ENABLE 0xFF10 +struct bt_vs_bt_enable_cmd { + __le16 op_code; + u8 plen; + u8 enable; +} __packed; + +/* Bytes in the command Hci_Cmd_ST_Set_Uart_Baud_Rate */ +#define CG2900_BAUD_RATE_57600 0x03 +#define CG2900_BAUD_RATE_115200 0x02 +#define CG2900_BAUD_RATE_230400 0x01 +#define CG2900_BAUD_RATE_460800 0x00 +#define CG2900_BAUD_RATE_921600 0x20 +#define CG2900_BAUD_RATE_2000000 0x25 +#define CG2900_BAUD_RATE_3000000 0x27 +#define CG2900_BAUD_RATE_3250000 0x28 +#define CG2900_BAUD_RATE_4000000 0x2B + +/* BT VS SetBaudRate command */ +#define CG2900_BT_OP_VS_SET_BAUD_RATE 0xFC09 +struct bt_vs_set_baud_rate_cmd { + __le16 opcode; + __u8 plen; + __u8 baud_rate; +} __packed; + +#define CG2900_BT_SELFTEST_SUCCESSFUL 0x00 +#define CG2900_BT_SELFTEST_FAILED 0x01 +#define CG2900_BT_SELFTEST_NOT_COMPLETED 0x02 + +/* BT VS ReadSelfTestsResult command & event */ +#define CG2900_BT_OP_VS_READ_SELTESTS_RESULT 0xFC10 +struct bt_vs_read_selftests_result_evt { + __u8 status; + __u8 result; +} __packed; + +/* Bluetooth Vendor Specific Opcodes */ +#define CG2900_BT_OP_VS_POWER_SWITCH_OFF 0xFD40 +#define CG2900_BT_OP_VS_SYSTEM_RESET 0xFF12 + +#define CG2900_BT_OPCODE_NONE 0xFFFF + +/* + * Common multimedia + */ + +#define CG2900_CODEC_TYPE_NONE 0x00 +#define CG2900_CODEC_TYPE_SBC 0x01 + +#define CG2900_PCM_MODE_SLAVE 0x00 +#define CG2900_PCM_MODE_MASTER 0x01 + +#define CG2900_I2S_MODE_MASTER 0x00 +#define CG2900_I2S_MODE_SLAVE 0x01 + +/* + * CG2900 PG1 multimedia API + */ + +#define CG2900_BT_VP_TYPE_PCM 0x00 +#define CG2900_BT_VP_TYPE_I2S 0x01 +#define CG2900_BT_VP_TYPE_SLIMBUS 0x02 +#define CG2900_BT_VP_TYPE_FM 0x03 +#define CG2900_BT_VP_TYPE_BT_SCO 0x04 +#define CG2900_BT_VP_TYPE_BT_A2DP 0x05 +#define CG2900_BT_VP_TYPE_ANALOG 0x07 + +#define CG2900_BT_VS_SET_HARDWARE_CONFIG 0xFD54 +/* These don't have the same length, so a union won't work */ +struct bt_vs_set_hw_cfg_cmd_pcm { + __le16 opcode; + __u8 plen; + __u8 vp_type; + __u8 port_id; + __u8 mode_dir; /* NB: mode is in bit 1 (not 0) */ + __u8 bit_clock; + __le16 frame_len; +} __packed; +#define HWCONFIG_PCM_SET_MODE(pcfg, mode) \ + set_low_nibble(&(pcfg)->mode_dir, (mode) << 1) +#define HWCONFIG_PCM_SET_DIR(pcfg, idx, dir) \ + store_bit(&(pcfg)->mode_dir, (idx) + 4, (dir)) + +struct bt_vs_set_hw_cfg_cmd_i2s { + __le16 opcode; + __u8 plen; + __u8 vp_type; + __u8 port_id; + __u8 half_period; + __u8 master_slave; +} __packed; + +/* Max length for allocating */ +#define CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG \ + (sizeof(struct bt_vs_set_hw_cfg_cmd_pcm)) + +#define CG2900_BT_VS_SET_SESSION_CONFIG 0xFD55 +struct session_config_vport { + __u8 type; + union { + struct { + __le16 acl_handle; + __u8 reserved[10]; + } sco; + struct { + __u8 reserved[12]; + } fm; + struct { + __u8 index; + __u8 slots_used; + __u8 slot_start[4]; + __u8 reserved[6]; + } pcm; + struct { + __u8 index; + __u8 channel; + __u8 reserved[10]; + } i2s; + }; +} __packed; +#define SESSIONCFG_PCM_SET_USED(port, idx, use) \ + store_bit(&(port).pcm.slots_used, (idx), (use)) + +struct session_config_stream { + __u8 media_type; + __u8 csel_srate; + __u8 codec_type; + __u8 codec_mode; + __u8 codec_params[3]; + struct session_config_vport inport; + struct session_config_vport outport; +} __packed; +#define SESSIONCFG_SET_CHANNELS(pcfg, chnl) \ + set_low_nibble(&(pcfg)->csel_srate, (chnl)) +#define SESSIONCFG_I2S_SET_SRATE(pcfg, rate) \ + set_high_nibble(&(pcfg)->csel_srate, (rate)) + +struct bt_vs_session_config_cmd { + __le16 opcode; + __u8 plen; + __u8 n_streams; /* we only support one here */ + struct session_config_stream stream; +} __packed; + +#define CG2900_BT_SESSION_MEDIA_TYPE_AUDIO 0x00 + +#define CG2900_BT_SESSION_RATE_8K 0x01 +#define CG2900_BT_SESSION_RATE_16K 0x02 +#define CG2900_BT_SESSION_RATE_44_1K 0x04 +#define CG2900_BT_SESSION_RATE_48K 0x05 + +#define CG2900_BT_MEDIA_CONFIG_MONO 0x00 +#define CG2900_BT_MEDIA_CONFIG_STEREO 0x01 +#define CG2900_BT_MEDIA_CONFIG_JOINT_STEREO 0x02 +#define CG2900_BT_MEDIA_CONFIG_DUAL_CHANNEL 0x03 + +#define CG2900_BT_SESSION_I2S_INDEX_I2S 0x00 +#define CG2900_BT_SESSION_PCM_INDEX_PCM_I2S 0x00 + + +#define CG2900_BT_VS_SESSION_CTRL 0xFD57 +struct bt_vs_session_ctrl_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 control; +} __packed; + +#define CG2900_BT_SESSION_START 0x00 +#define CG2900_BT_SESSION_STOP 0x01 +#define CG2900_BT_SESSION_PAUSE 0x02 +#define CG2900_BT_SESSION_RESUME 0x03 + +#define CG2900_BT_VS_RESET_SESSION_CONFIG 0xFD56 +struct bt_vs_reset_session_cfg_cmd { + __le16 opcode; + __u8 plen; + __u8 id; +} __packed; + +/* + * CG2900 PG2 multimedia API + */ + +#define CG2900_MC_PORT_PCM_I2S 0x00 +#define CG2900_MC_PORT_I2S 0x01 +#define CG2900_MC_PORT_BT_SCO 0x04 +#define CG2900_MC_PORT_FM_RX_0 0x07 +#define CG2900_MC_PORT_FM_RX_1 0x08 +#define CG2900_MC_PORT_FM_TX 0x09 + +#define CG2900_MC_VS_PORT_CONFIG 0xFD64 +struct mc_vs_port_cfg_cmd { + __le16 opcode; + __u8 plen; + __u8 type; + /* + * one of the following configuration structs should follow, but they + * have different lengths so a union will not work + */ +} __packed; + +struct mc_vs_port_cfg_pcm_i2s { + __u8 role_dir; + __u8 sco_a2dp_slots_used; + __u8 fm_slots_used; + __u8 ring_slots_used; + __u8 slot_start[4]; + __u8 ratio_mode; + __u8 frame_len; + __u8 bitclk_srate; +} __packed; +#define PORTCFG_PCM_SET_ROLE(cfg, role) \ + set_low_nibble(&(cfg).role_dir, (role)) +#define PORTCFG_PCM_SET_DIR(cfg, idx, dir) \ + store_bit(&(cfg).role_dir, (idx) + 4, (dir)) +static inline void portcfg_pcm_set_sco_used(struct mc_vs_port_cfg_pcm_i2s *cfg, + size_t index, __u8 use) +{ + if (use) { + /* clear corresponding slot in all cases */ + cfg->sco_a2dp_slots_used &= ~(0x11 << index); + cfg->fm_slots_used &= ~(0x11 << index); + cfg->ring_slots_used &= ~(0x11 << index); + /* set for sco */ + cfg->sco_a2dp_slots_used |= (1u << index); + } else { + /* only clear for sco */ + cfg->sco_a2dp_slots_used &= ~(1u << index); + } +} +#define PORTCFG_PCM_SET_SCO_USED(cfg, idx, use) \ + portcfg_pcm_set_sco_used(&cfg, idx, use) +#define PORTCFG_PCM_SET_RATIO(cfg, r) \ + set_low_nibble(&(cfg).ratio_mode, (r)) +#define PORTCFG_PCM_SET_MODE(cfg, mode) \ + set_high_nibble(&(cfg).ratio_mode, (mode)) +#define PORTCFG_PCM_SET_BITCLK(cfg, clk) \ + set_low_nibble(&(cfg).bitclk_srate, (clk)) +#define PORTCFG_PCM_SET_SRATE(cfg, rate) \ + set_high_nibble(&(cfg).bitclk_srate, (rate)) + +#define CG2900_MC_PCM_SAMPLE_RATE_8 1 +#define CG2900_MC_PCM_SAMPLE_RATE_16 2 +#define CG2900_MC_PCM_SAMPLE_RATE_44_1 4 +#define CG2900_MC_PCM_SAMPLE_RATE_48 6 + +struct mc_vs_port_cfg_i2s { + __u8 role_hper; + __u8 csel_srate; + __u8 wordlen; +}; +#define PORTCFG_I2S_SET_ROLE(cfg, role) \ + set_low_nibble(&(cfg).role_hper, (role)) +#define PORTCFG_I2S_SET_HALFPERIOD(cfg, hper) \ + set_high_nibble(&(cfg).role_hper, (hper)) +#define PORTCFG_I2S_SET_CHANNELS(cfg, chnl) \ + set_low_nibble(&(cfg).csel_srate, (chnl)) +#define PORTCFG_I2S_SET_SRATE(cfg, rate) \ + set_high_nibble(&(cfg).csel_srate, (rate)) +#define PORTCFG_I2S_SET_WORDLEN(cfg, len) \ + set_low_nibble(&(cfg).wordlen, len) + +#define CG2900_MC_I2S_RIGHT_CHANNEL 1 +#define CG2900_MC_I2S_LEFT_CHANNEL 2 +#define CG2900_MC_I2S_BOTH_CHANNELS 3 + +#define CG2900_MC_I2S_SAMPLE_RATE_8 0 +#define CG2900_MC_I2S_SAMPLE_RATE_16 1 +#define CG2900_MC_I2S_SAMPLE_RATE_44_1 2 +#define CG2900_MC_I2S_SAMPLE_RATE_48 4 + +#define CG2900_MC_I2S_WORD_16 1 +#define CG2900_MC_I2S_WORD_32 3 + +struct mc_vs_port_cfg_fm { + __u8 srate; /* NB: value goes in _upper_ nibble! */ +}; +#define PORTCFG_FM_SET_SRATE(cfg, rate) \ + set_high_nibble(&(cfg).srate, (rate)) + +struct mc_vs_port_cfg_sco { + __le16 acl_id; + __u8 wbs_codec; +} __packed; +#define PORTCFG_SCO_SET_CODEC(cfg, codec) \ + set_high_nibble(&(cfg).wbs_codec, (codec)) + +#define CG2900_MC_VS_CREATE_STREAM 0xFD66 +struct mc_vs_create_stream_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 inport; + __u8 outport; + __u8 order; /* NB: not used by chip */ +} __packed; + +#define CG2900_MC_VS_DELETE_STREAM 0xFD67 +struct mc_vs_delete_stream_cmd { + __le16 opcode; + __u8 plen; + __u8 stream; +} __packed; + +#define CG2900_MC_VS_STREAM_CONTROL 0xFD68 +struct mc_vs_stream_ctrl_cmd { + __le16 opcode; + __u8 plen; + __u8 command; + __u8 n_streams; + __u8 stream[]; +} __packed; + +#define CG2900_MC_STREAM_START 0x00 +#define CG2900_MC_STREAM_STOP 0x01 +#define CG2900_MC_STREAM_STOP_FLUSH 0x02 + +#define CG2900_MC_VS_SET_FM_START_MODE 0xFD69 + +/* + * FM + */ + +/* FM legacy command packet */ +struct fm_leg_cmd { + __u8 length; + __u8 opcode; + __u8 read_write; + __u8 fm_function; + union { /* Payload varies with function */ + __le16 irqmask; + struct fm_leg_fm_cmd { + __le16 head; + __le16 data[]; + } fm_cmd; + }; +} __packed; + +/* FM legacy command complete packet */ +struct fm_leg_cmd_cmpl { + __u8 param_length; + __u8 status; + __u8 opcode; + __u8 read_write; + __u8 cmd_status; + __u8 fm_function; + __le16 response_head; + __le16 data[]; +} __packed; + +/* FM legacy interrupt packet, PG2 style */ +struct fm_leg_irq_v2 { + __u8 param_length; + __u8 status; + __u8 opcode; + __u8 event_type; + __u8 event_id; + __le16 irq; +} __packed; + +/* FM legacy interrupt packet, PG1 style */ +struct fm_leg_irq_v1 { + __u8 param_length; + __u8 opcode; + __u8 event_id; + __le16 irq; +} __packed; + +union fm_leg_evt_or_irq { + __u8 param_length; + struct fm_leg_cmd_cmpl evt; + struct fm_leg_irq_v2 irq_v2; + struct fm_leg_irq_v1 irq_v1; +} __packed; + +/* FM Opcode generic*/ +#define CG2900_FM_GEN_ID_LEGACY 0xFE + +/* FM event*/ +#define CG2900_FM_EVENT_UNKNOWN 0 +#define CG2900_FM_EVENT_CMD_COMPLETE 1 +#define CG2900_FM_EVENT_INTERRUPT 2 + +/* FM do-command identifiers. */ +#define CG2900_FM_DO_AIP_FADE_START 0x0046 +#define CG2900_FM_DO_AUP_BT_FADE_START 0x01C2 +#define CG2900_FM_DO_AUP_EXT_FADE_START 0x0102 +#define CG2900_FM_DO_AUP_FADE_START 0x00A2 +#define CG2900_FM_DO_FMR_SETANTENNA 0x0663 +#define CG2900_FM_DO_FMR_SP_AFSWITCH_START 0x04A3 +#define CG2900_FM_DO_FMR_SP_AFUPDATE_START 0x0463 +#define CG2900_FM_DO_FMR_SP_BLOCKSCAN_START 0x0683 +#define CG2900_FM_DO_FMR_SP_PRESETPI_START 0x0443 +#define CG2900_FM_DO_FMR_SP_SCAN_START 0x0403 +#define CG2900_FM_DO_FMR_SP_SEARCH_START 0x03E3 +#define CG2900_FM_DO_FMR_SP_SEARCHPI_START 0x0703 +#define CG2900_FM_DO_FMR_SP_TUNE_SETCHANNEL 0x03C3 +#define CG2900_FM_DO_FMR_SP_TUNE_STEPCHANNEL 0x04C3 +#define CG2900_FM_DO_FMT_PA_SETCTRL 0x01A4 +#define CG2900_FM_DO_FMT_PA_SETMODE 0x01E4 +#define CG2900_FM_DO_FMT_SP_TUNE_SETCHANNEL 0x0064 +#define CG2900_FM_DO_GEN_ANTENNACHECK_START 0x02A1 +#define CG2900_FM_DO_GEN_GOTOMODE 0x0041 +#define CG2900_FM_DO_GEN_POWERSUPPLY_SETMODE 0x0221 +#define CG2900_FM_DO_GEN_SELECTREFERENCECLOCK 0x0201 +#define CG2900_FM_DO_GEN_SETPROCESSINGCLOCK 0x0241 +#define CG2900_FM_DO_GEN_SETREFERENCECLOCKPLL 0x01A1 +#define CG2900_FM_DO_TST_TX_RAMP_START 0x0147 +#define CG2900_FM_CMD_NONE 0xFFFF +#define CG2900_FM_CMD_ID_GEN_GOTO_POWER_DOWN 0x0081 +#define CG2900_FM_CMD_ID_GEN_GOTO_STANDBY 0x0061 + +/* FM Command IDs */ +#define CG2900_FM_CMD_ID_AUP_EXT_SET_MODE 0x0162 +#define CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL 0x0182 +#define CG2900_FM_CMD_ID_AIP_SET_MODE 0x01C6 +#define CG2900_FM_CMD_ID_AIP_BT_SET_CTRL 0x01A6 +#define CG2900_FM_CMD_ID_AIP_BT_SET_MODE 0x01E6 + +/* FM Command Parameters. */ +#define CG2900_FM_CMD_PARAM_ENABLE 0x00 +#define CG2900_FM_CMD_PARAM_DISABLE 0x01 +#define CG2900_FM_CMD_PARAM_RESET 0x02 +#define CG2900_FM_CMD_PARAM_WRITECOMMAND 0x10 +#define CG2900_FM_CMD_PARAM_SET_INT_MASK_ALL 0x20 +#define CG2900_FM_CMD_PARAM_GET_INT_MASK_ALL 0x21 +#define CG2900_FM_CMD_PARAM_SET_INT_MASK 0x22 +#define CG2900_FM_CMD_PARAM_GET_INT_MASK 0x23 +#define CG2900_FM_CMD_PARAM_FM_FW_DOWNLOAD 0x30 +#define CG2900_FM_CMD_PARAM_NONE 0xFF + +/* FM Legacy Command Parameters */ +#define CG2900_FM_CMD_LEG_PARAM_WRITE 0x00 +#define CG2900_FM_CMD_LEG_PARAM_IRQ 0x01 + +/* FM Command Status. */ +#define CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED 0x00 +#define CG2900_FM_CMD_STATUS_HW_FAILURE 0x03 +#define CG2900_FM_CMD_STATUS_INVALID_PARAMS 0x12 +#define CG2900_FM_CMD_STATUS_UNINITILIZED 0x15 +#define CG2900_FM_CMD_STATUS_UNSPECIFIED_ERROR 0x1F +#define CG2900_FM_CMD_STATUS_COMMAND_DISALLOWED 0x0C +#define CG2900_FM_CMD_STATUS_FW_WRONG_SEQUENCE_NR 0xF1 +#define CG2900_FM_CMD_STATUS_FW_UNKNOWN_FILE 0xF2 +#define CG2900_FM_CMD_STATUS_FW_FILE_VER_MISMATCH 0xF3 + +/* FM Interrupts. */ +#define CG2900_FM_IRPT_FIQ 0x0000 +#define CG2900_FM_IRPT_OPERATION_SUCCEEDED 0x0001 +#define CG2900_FM_IRPT_OPERATION_FAILED 0x0002 +#define CG2900_FM_IRPT_BUFFER_FULL 0x0008 +#define CG2900_FM_IRPT_BUFFER_EMPTY 0x0008 +#define CG2900_FM_IRPT_SIGNAL_QUALITY_LOW 0x0010 +#define CG2900_FM_IRPT_MUTE_STATUS_CHANGED 0x0010 +#define CG2900_FM_IRPT_MONO_STEREO_TRANSITION 0x0020 +#define CG2900_FM_IRPT_OVER_MODULATION 0x0020 +#define CG2900_FM_IRPT_RDS_SYNC_FOUND 0x0040 +#define CG2900_FM_IRPT_INPUT_OVERDRIVE 0x0040 +#define CG2900_FM_IRPT_RDS_SYNC_LOST 0x0080 +#define CG2900_FM_IRPT_PI_CODE_CHANGED 0x0100 +#define CG2900_FM_IRPT_REQUEST_BLOCK_AVALIBLE 0x0200 +#define CG2900_FM_IRPT_BUFFER_CLEARED 0x2000 +#define CG2900_FM_IRPT_WARM_BOOT_READY 0x4000 +#define CG2900_FM_IRPT_COLD_BOOT_READY 0x8000 + +/* FM Legacy Function Command Parameters */ + +/* AUP_EXT_SetMode Output enum */ +#define CG2900_FM_CMD_AUP_EXT_SET_MODE_DISABLED 0x0000 +#define CG2900_FM_CMD_AUP_EXT_SET_MODE_I2S 0x0001 +#define CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL 0x0002 + +/* SetControl Conversion enum */ +#define CG2900_FM_CMD_SET_CTRL_CONV_UP 0x0000 +#define CG2900_FM_CMD_SET_CTRL_CONV_DOWN 0x0001 + +/* AIP_SetMode Input enum */ +#define CG2900_FM_CMD_AIP_SET_MODE_INPUT_ANA 0x0000 +#define CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG 0x0001 + +/* AIP_BT_SetMode Input enum */ +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_RESERVED 0x0000 +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_I2S 0x0001 +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR 0x0002 +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_FIFO 0x0003 + +/* FM Parameter Lengths = FM command length - length field (1 byte) */ +#define CG2900_FM_CMD_PARAM_LEN(len) (len - 1) + +/* + * FM Command ID mapped per byte and shifted 3 bits left + * Also adds number of parameters at first 3 bits of LSB. + */ +static inline __u16 cg2900_get_fm_cmd_id(__u16 opcode) +{ + return opcode >> 3; +} + +static inline __u16 cg2900_make_fm_cmd_id(__u16 id, __u8 num_params) +{ + return (id << 3) | num_params; +} + +/* + * GNSS + */ + +struct gnss_hci_hdr { + __u8 op_code; + __le16 plen; +} __packed; + +#endif /* _CG2900_CHIP_H_ */ diff --git a/drivers/staging/cg2900/mfd/cg2900_core.c b/drivers/staging/cg2900/mfd/cg2900_core.c new file mode 100644 index 00000000000..6ac27748e44 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_core.c @@ -0,0 +1,715 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ +#define NAME "cg2900_core" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <asm/byteorder.h> +#include <linux/firmware.h> +#include <linux/fs.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/stat.h> +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/mfd/core.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> + +#include "cg2900.h" +#include "cg2900_core.h" + +/* Device names */ +#define CG2900_CDEV_NAME "cg2900_core_test" +#define CG2900_CLASS_NAME "cg2900_class" +#define CG2900_DEVICE_NAME "cg2900_driver" +#define CORE_WQ_NAME "cg2900_core_wq" + +#define LOGGER_DIRECTION_TX 0 +#define LOGGER_DIRECTION_RX 1 + +/* + * Timeout values + */ +#define CHIP_READY_TIMEOUT (100) /* ms */ +#define REVISION_READOUT_TIMEOUT (500) /* ms */ +#define SLEEP_TIMEOUT_MS (10000) /* ms */ + +/** + * enum boot_state - BOOT-state for CG2900 Core. + * @BOOT_RESET: HCI Reset has been sent. + * @BOOT_READ_LOCAL_VERSION_INFORMATION: ReadLocalVersionInformation + * command has been sent. + * @BOOT_READY: CG2900 Core boot is ready. + * @BOOT_FAILED: CG2900 Core boot failed. + */ +enum boot_state { + BOOT_RESET, + BOOT_READ_LOCAL_VERSION_INFORMATION, + BOOT_READY, + BOOT_FAILED +}; + +/** + * struct chip_handler_item - Structure to store chip handler cb. + * @list: list_head struct. + * @cb: Chip handler callback struct. + */ +struct chip_handler_item { + struct list_head list; + struct cg2900_id_callbacks cb; +}; + +/** + * struct core_info - Main info structure for CG2900 Core. + * @boot_state: Current BOOT-state of CG2900 Core. + * @wq: CG2900 Core workqueue. + * @chip_dev: Device structure for chip driver. + * @work: Work structure. + */ +struct core_info { + enum boot_state boot_state; + struct workqueue_struct *wq; + struct cg2900_chip_dev *chip_dev; + struct work_struct work; +}; + +/** + * struct main_info - Main info structure for CG2900 Core. + * @dev: Device structure for STE Connectivity driver. + * @man_mutex: Management mutex. + * @chip_handlers: List of the register handlers for different chips. + * @wq: Wait queue. + */ +struct main_info { + struct device *dev; + struct mutex man_mutex; + struct list_head chip_handlers; + wait_queue_head_t wq; +}; + +/* core_info - Main information object for CG2900 Core. */ +static struct main_info *main_info; + +/* Module parameters */ +u8 bd_address[] = {0x00, 0xBE, 0xAD, 0xDE, 0x80, 0x00}; +EXPORT_SYMBOL_GPL(bd_address); +int bd_addr_count = BT_BDADDR_SIZE; + +static int sleep_timeout_ms = SLEEP_TIMEOUT_MS; + +/** + * send_bt_cmd() - Copy and send sk_buffer with no assigned user. + * @dev: Current chip to transmit to. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The send_bt_cmd() function allocate sk_buffer, copy supplied + * data to it, and send the sk_buffer to controller. + */ +void send_bt_cmd(struct cg2900_chip_dev *dev, void *data, int length) +{ + struct sk_buff *skb; + int err; + + skb = alloc_skb(length + HCI_H4_SIZE, GFP_ATOMIC); + if (!skb) { + dev_err(dev->dev, "send_bt_cmd: Couldn't alloc sk_buff with " + "length %d\n", length); + return; + } + + skb_reserve(skb, HCI_H4_SIZE); + memcpy(skb_put(skb, length), data, length); + skb_push(skb, HCI_H4_SIZE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + err = dev->t_cb.write(dev, skb); + if (err) { + dev_err(dev->dev, "send_bt_cmd: Transport write failed (%d)\n", + err); + kfree_skb(skb); + } +} + +/** + * handle_reset_cmd_complete_evt() - Handle a received HCI Command Complete event for a Reset command. + * @dev: Current device. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_reset_cmd_complete_evt(struct cg2900_chip_dev *dev, u8 *data) +{ + bool pkt_handled = false; + u8 status = data[0]; + struct hci_command_hdr cmd; + struct core_info *info = dev->prv_data; + + dev_dbg(dev->dev, "Received Reset complete event with status 0x%X\n", + status); + + if (info->boot_state == BOOT_RESET) { + /* Transmit HCI Read Local Version Information command */ + dev_dbg(dev->dev, "New boot_state: " + "BOOT_READ_LOCAL_VERSION_INFORMATION\n"); + info->boot_state = BOOT_READ_LOCAL_VERSION_INFORMATION; + cmd.opcode = cpu_to_le16(HCI_OP_READ_LOCAL_VERSION); + cmd.plen = 0; /* No parameters for HCI reset */ + send_bt_cmd(dev, &cmd, sizeof(cmd)); + + pkt_handled = true; + } + + return pkt_handled; +} + +/** + * handle_read_local_version_info_cmd_complete_evt() - Handle a received HCI Command Complete event for a ReadLocalVersionInformation command. + * @dev: Current device. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool +handle_read_local_version_info_cmd_complete_evt(struct cg2900_chip_dev *dev, + u8 *data) +{ + struct hci_rp_read_local_version *evt; + struct core_info *info = dev->prv_data; + + /* Check we're in the right state */ + if (info->boot_state != BOOT_READ_LOCAL_VERSION_INFORMATION) + return false; + + /* We got an answer for our HCI command. Extract data */ + evt = (struct hci_rp_read_local_version *)data; + + /* We will handle the packet */ + if (HCI_BT_ERROR_NO_ERROR != evt->status) { + dev_err(dev->dev, "Received Read Local Version Information " + "with status 0x%X\n", evt->status); + dev_dbg(dev->dev, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + wake_up_all(&main_info->wq); + return true; + } + + /* The command worked. Store the data */ + dev->chip.hci_version = evt->hci_ver; + dev->chip.hci_revision = le16_to_cpu(evt->hci_rev); + dev->chip.lmp_pal_version = evt->lmp_ver; + dev->chip.manufacturer = le16_to_cpu(evt->manufacturer); + dev->chip.hci_sub_version = le16_to_cpu(evt->lmp_subver); + dev_info(dev->dev, "Received Read Local Version Information with:\n" + "\thci_version: 0x%02X\n" + "\thci_revision: 0x%04X\n" + "\tlmp_pal_version: 0x%02X\n" + "\tmanufacturer: 0x%04X\n" + "\thci_sub_version: 0x%04X\n", + dev->chip.hci_version, dev->chip.hci_revision, + dev->chip.lmp_pal_version, dev->chip.manufacturer, + dev->chip.hci_sub_version); + + dev_dbg(dev->dev, "New boot_state: BOOT_READY\n"); + info->boot_state = BOOT_READY; + wake_up_all(&main_info->wq); + + return true; +} + +/** + * handle_rx_data_bt_evt() - Check if data should be handled in CG2900 Core. + * @dev: Current chip + * @skb: Data packet + * + * The handle_rx_data_bt_evt() function checks if received data should be + * handled in CG2900 Core. If so handle it correctly. + * Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_rx_data_bt_evt(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + bool pkt_handled = false; + u8 *data = &skb->data[CG2900_SKB_RESERVE]; + struct hci_event_hdr *evt; + struct hci_ev_cmd_complete *cmd_complete; + u16 op_code; + + evt = (struct hci_event_hdr *)data; + + /* First check the event code */ + if (HCI_EV_CMD_COMPLETE != evt->evt) + return false; + + data += sizeof(*evt); + cmd_complete = (struct hci_ev_cmd_complete *)data; + + op_code = le16_to_cpu(cmd_complete->opcode); + + dev_dbg(dev->dev, "Received Command Complete: op_code = 0x%04X\n", + op_code); + data += sizeof(*cmd_complete); /* Move to first byte after OCF */ + + if (op_code == HCI_OP_RESET) + pkt_handled = handle_reset_cmd_complete_evt(dev, data); + else if (op_code == HCI_OP_READ_LOCAL_VERSION) + pkt_handled = handle_read_local_version_info_cmd_complete_evt + (dev, data); + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +static void cg2900_data_from_chip(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + u8 h4_channel; + + dev_dbg(dev->dev, "cg2900_data_from_chip\n"); + + if (!skb) { + dev_err(dev->dev, "No data supplied\n"); + return; + } + + h4_channel = skb->data[0]; + + /* + * First check if this is the response for something + * we have sent internally. + */ + if (HCI_BT_EVT_H4_CHANNEL == h4_channel && + handle_rx_data_bt_evt(dev, skb)) { + dev_dbg(dev->dev, "Received packet handled internally\n"); + } else { + dev_err(dev->dev, + "cg2900_data_from_chip: Received unexpected packet\n"); + kfree_skb(skb); + } +} + +/** + * work_hw_registered() - Called when the interface to HW has been established. + * @work: Reference to work data. + * + * Since there now is a transport identify the connected chip and decide which + * chip handler to use. + */ +static void work_hw_registered(struct work_struct *work) +{ + struct hci_command_hdr cmd; + struct cg2900_chip_dev *dev; + struct core_info *info; + bool chip_handled = false; + struct list_head *cursor; + struct chip_handler_item *tmp; + + dev_dbg(main_info->dev, "work_hw_registered\n"); + + if (!work) { + dev_err(main_info->dev, "work_hw_registered: work == NULL\n"); + return; + } + + info = container_of(work, struct core_info, work); + dev = info->chip_dev; + + /* + * This might look strange, but we need to read out + * the revision info in order to be able to shutdown the chip properly. + */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait 100ms before continuing to be sure that the chip is ready */ + schedule_timeout_killable(msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + /* Set our function to receive data from chip */ + dev->c_cb.data_from_chip = cg2900_data_from_chip; + + /* + * Transmit HCI reset command to ensure the chip is using + * the correct transport + */ + dev_dbg(dev->dev, "New boot_state: BOOT_RESET\n"); + info->boot_state = BOOT_RESET; + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + send_bt_cmd(dev, &cmd, sizeof(cmd)); + + dev_dbg(dev->dev, + "Wait up to 500 milliseconds for revision to be read\n"); + wait_event_timeout(main_info->wq, + (BOOT_READY == info->boot_state || + BOOT_FAILED == info->boot_state), + msecs_to_jiffies(REVISION_READOUT_TIMEOUT)); + + if (BOOT_READY != info->boot_state) { + dev_err(dev->dev, + "Could not read out revision from the chip\n"); + info->boot_state = BOOT_FAILED; + dev->t_cb.set_chip_power(dev, false); + return; + } + + dev->c_cb.data_from_chip = NULL; + + mutex_lock(&main_info->man_mutex); + list_for_each(cursor, &main_info->chip_handlers) { + tmp = list_entry(cursor, struct chip_handler_item, list); + chip_handled = tmp->cb.check_chip_support(dev); + if (chip_handled) { + dev_info(dev->dev, "Chip handler found\n"); + break; + } + } + mutex_unlock(&main_info->man_mutex); + + if (!chip_handled) + dev_info(dev->dev, "No chip handler found\n"); +} + +/** + * cg2900_register_chip_driver() - Register a chip handler. + * @cb: Callbacks to call when chip is connected. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + */ +int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb) +{ + struct chip_handler_item *item; + + dev_dbg(main_info->dev, "cg2900_register_chip_driver\n"); + + if (!cb) { + dev_err(main_info->dev, "NULL supplied as cb\n"); + return -EINVAL; + } + + item = kzalloc(sizeof(*item), GFP_KERNEL); + if (!item) { + dev_err(main_info->dev, + "cg2900_register_chip_driver: " + "Failed to alloc memory\n"); + return -ENOMEM; + } + + memcpy(&item->cb, cb, sizeof(cb)); + mutex_lock(&main_info->man_mutex); + list_add_tail(&item->list, &main_info->chip_handlers); + mutex_unlock(&main_info->man_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(cg2900_register_chip_driver); + +/** + * cg2900_deregister_chip_driver() - Deregister a chip handler. + * @cb: Callbacks to call when chip is connected. + */ +void cg2900_deregister_chip_driver(struct cg2900_id_callbacks *cb) +{ + struct chip_handler_item *tmp; + struct list_head *cursor, *next; + + dev_dbg(main_info->dev, "cg2900_deregister_chip_driver\n"); + + if (!cb) { + dev_err(main_info->dev, "NULL supplied as cb\n"); + return; + } + mutex_lock(&main_info->man_mutex); + list_for_each_safe(cursor, next, &main_info->chip_handlers) { + tmp = list_entry(cursor, struct chip_handler_item, list); + if (tmp->cb.check_chip_support == cb->check_chip_support) { + list_del(cursor); + kfree(tmp); + break; + } + } + mutex_unlock(&main_info->man_mutex); +} +EXPORT_SYMBOL_GPL(cg2900_deregister_chip_driver); + +/** + * cg2900_register_trans_driver() - Register a transport driver. + * @dev: Transport device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + * -EACCES if work can't be queued. + */ +int cg2900_register_trans_driver(struct cg2900_chip_dev *dev) +{ + int err; + struct cg2900_platform_data *pf_data; + struct core_info *info; + + BUG_ON(!main_info); + + if (!dev || !dev->dev) { + dev_err(main_info->dev, "cg2900_register_trans_driver: " + "Received NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(dev->dev, "cg2900_register_trans_driver\n"); + + if (!dev->t_cb.write) { + dev_err(dev->dev, "cg2900_register_trans_driver: Write function" + " missing\n"); + return -EINVAL; + } + + pf_data = dev_get_platdata(dev->dev); + if (!pf_data) { + dev_err(dev->dev, "cg2900_register_trans_driver: Missing " + "platform data\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Couldn't allocate info\n"); + return -ENOMEM; + } + + if (pf_data->init) { + err = pf_data->init(dev); + if (err) { + dev_err(dev->dev, "Platform init failed (%d)\n", err); + goto error_handling; + } + } + + info->chip_dev = dev; + dev->prv_data = info; + + info->wq = create_singlethread_workqueue(CORE_WQ_NAME); + if (!info->wq) { + dev_err(dev->dev, "Could not create workqueue\n"); + err = -ENOMEM; + goto error_handling_exit; + } + + dev_info(dev->dev, "Transport connected\n"); + + INIT_WORK(&info->work, work_hw_registered); + if (!queue_work(info->wq, &info->work)) { + dev_err(dev->dev, "Failed to queue work_hw_registered because " + "it's already in the queue\n"); + err = -EACCES; + goto error_handling_wq; + } + + return 0; + +error_handling_wq: + destroy_workqueue(info->wq); +error_handling_exit: + if (pf_data->exit) + pf_data->exit(dev); +error_handling: + kfree(info); + return err; +} +EXPORT_SYMBOL_GPL(cg2900_register_trans_driver); + +/** + * cg2900_deregister_trans_driver() - Deregister a transport driver. + * @dev: Transport device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + */ +int cg2900_deregister_trans_driver(struct cg2900_chip_dev *dev) +{ + struct cg2900_platform_data *pf_data; + struct core_info *info = dev->prv_data; + + BUG_ON(!main_info); + + dev_dbg(dev->dev, "cg2900_deregister_trans_driver\n"); + + if (dev->c_cb.chip_removed) + dev->c_cb.chip_removed(dev); + + destroy_workqueue(info->wq); + + dev->prv_data = NULL; + kfree(info); + + dev_info(dev->dev, "Transport disconnected\n"); + + pf_data = dev_get_platdata(dev->dev); + if (!pf_data) { + dev_err(dev->dev, "Missing platform data\n"); + return -EINVAL; + } + + if (pf_data->exit) + pf_data->exit(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(cg2900_deregister_trans_driver); + +/** + * cg2900_get_sleep_timeout() - Return sleep timeout in jiffies. + * + * Returns: + * Sleep timeout in jiffies. 0 means that sleep timeout shall not be used. + */ +unsigned long cg2900_get_sleep_timeout(void) +{ + if (!sleep_timeout_ms) + return 0; + + return msecs_to_jiffies(sleep_timeout_ms); +} +EXPORT_SYMBOL_GPL(cg2900_get_sleep_timeout); + +/** + * cg2900_probe() - Initialize module. + * + * @pdev: Platform device. + * + * This function initialize the transport and CG2900 Core, then + * register to the transport framework. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + */ +static int __devinit cg2900_probe(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "cg2900_probe\n"); + + main_info = kzalloc(sizeof(*main_info), GFP_KERNEL); + if (!main_info) { + dev_err(&pdev->dev, "Couldn't allocate main_info\n"); + return -ENOMEM; + } + + main_info->dev = &pdev->dev; + mutex_init(&main_info->man_mutex); + INIT_LIST_HEAD(&main_info->chip_handlers); + init_waitqueue_head(&main_info->wq); + + dev_info(&pdev->dev, "CG2900 Core driver started\n"); + + return 0; +} + +/** + * cg2900_remove() - Remove module. + * + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * -ENOMEM if core_info does not exist. + * -EINVAL if platform data does not exist in the device. + */ +static int __devexit cg2900_remove(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "cg2900_remove\n"); + + kfree(main_info); + main_info = NULL; + + dev_info(&pdev->dev, "CG2900 Core driver removed\n"); + + return 0; +} + +static struct platform_driver cg2900_driver = { + .driver = { + .name = "cg2900", + .owner = THIS_MODULE, + }, + .probe = cg2900_probe, + .remove = __devexit_p(cg2900_remove), +}; + +/** + * cg2900_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_init(void) +{ + pr_debug("cg2900_init"); + return platform_driver_register(&cg2900_driver); +} + +/** + * cg2900_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_exit(void) +{ + pr_debug("cg2900_exit"); + platform_driver_unregister(&cg2900_driver); +} + +module_init(cg2900_init); +module_exit(cg2900_exit); + +module_param(sleep_timeout_ms, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(sleep_timeout_ms, + "Sleep timeout for data transmissions:\n" + "\tDefault 10000 ms\n" + "\t0 = disable\n" + "\t>0 = sleep timeout in milliseconds"); + +module_param_array(bd_address, byte, &bd_addr_count, + S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(bd_address, + "Bluetooth Device address. " + "Default 0x00 0x80 0xDE 0xAD 0xBE 0xEF. " + "Enter as comma separated value."); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 CG2900 Connectivity Device Driver"); diff --git a/drivers/staging/cg2900/mfd/cg2900_core.h b/drivers/staging/cg2900/mfd/cg2900_core.h new file mode 100644 index 00000000000..bdd951a501d --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_core.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#ifndef _CG2900_CORE_H_ +#define _CG2900_CORE_H_ + +#include <linux/device.h> +#include <linux/skbuff.h> + +/* Reserve 1 byte for the HCI H:4 header */ +#define HCI_H4_SIZE 1 +#define CG2900_SKB_RESERVE HCI_H4_SIZE + +/* Number of bytes to reserve at start of sk_buffer when receiving packet */ +#define RX_SKB_RESERVE 8 + +#define BT_BDADDR_SIZE 6 + +/* Standardized Bluetooth H:4 channels */ +#define HCI_BT_CMD_H4_CHANNEL 0x01 +#define HCI_BT_ACL_H4_CHANNEL 0x02 +#define HCI_BT_SCO_H4_CHANNEL 0x03 +#define HCI_BT_EVT_H4_CHANNEL 0x04 + +/* Default H4 channels which may change depending on connected controller */ +#define HCI_FM_RADIO_H4_CHANNEL 0x08 +#define HCI_GNSS_H4_CHANNEL 0x09 + +/* Bluetooth error codes */ +#define HCI_BT_ERROR_NO_ERROR 0x00 + +/* Bluetooth lengths */ +#define HCI_BT_SEND_FILE_MAX_CHUNK_SIZE 254 + +#define LOGGER_DIRECTION_TX 0 +#define LOGGER_DIRECTION_RX 1 + +/* module_param declared in cg2900_core.c */ +extern u8 bd_address[BT_BDADDR_SIZE]; + +#endif /* _CG2900_CORE_H_ */ diff --git a/drivers/staging/cg2900/mfd/cg2900_lib.c b/drivers/staging/cg2900/mfd/cg2900_lib.c new file mode 100644 index 00000000000..84f7eb5eb62 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_lib.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ +#define NAME "cg2900_lib" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/skbuff.h> +#include <linux/types.h> + +#include "cg2900.h" +#include "cg2900_chip.h" +#include "cg2900_core.h" +#include "cg2900_lib.h" + +/* + * Max length in bytes for line buffer used to parse settings and patch file. + * Must be max length of name plus characters used to define chip version. + */ +#define LINE_BUFFER_LENGTH (NAME_MAX + 30) +#define LOGGER_HEADER_SIZE 1 +/** + * cg2900_tx_to_chip() - Transmit buffer to the transport. + * @user: User data for BT command channel. + * @logger: User data for logger channel. + * @skb: Data packet. + * + * The transmit_skb_to_chip() function transmit buffer to the transport. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +void cg2900_tx_to_chip(struct cg2900_user_data *user, + struct cg2900_user_data *logger, struct sk_buff *skb) +{ + int err; + struct cg2900_chip_dev *chip_dev; + + dev_dbg(user->dev, "cg2900_tx_to_chip %d bytes.\n", skb->len); + + if (logger) + cg2900_send_to_hci_logger(logger, skb, LOGGER_DIRECTION_TX); + + chip_dev = cg2900_get_prv(user); + err = chip_dev->t_cb.write(chip_dev, skb); + if (err) { + dev_err(user->dev, "cg2900_tx_to_chip: Transport write failed " + "(%d)\n", err); + kfree_skb(skb); + } +} +EXPORT_SYMBOL_GPL(cg2900_tx_to_chip); + +/** + * cg2900_tx_no_user() - Transmit buffer to the transport. + * @dev: Current chip to transmit to. + * @skb: Data packet. + * + * This function transmits buffer to the transport when no user exist (system + * startup for example). + */ +void cg2900_tx_no_user(struct cg2900_chip_dev *dev, struct sk_buff *skb) +{ + int err; + + dev_dbg(dev->dev, "cg2900_tx_no_user %d bytes.\n", skb->len); + + err = dev->t_cb.write(dev, skb); + if (err) { + dev_err(dev->dev, "cg2900_tx_no_user: Transport write failed " + "(%d)\n", err); + kfree_skb(skb); + } +} +EXPORT_SYMBOL_GPL(cg2900_tx_no_user); + +/** + * create_and_send_bt_cmd() - Copy and send sk_buffer. + * @user: User data for current channel. + * @logger: User data for logger channel. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The create_and_send_bt_cmd() function allocate sk_buffer, copy supplied data + * to it, and send the sk_buffer to controller. + */ +void cg2900_send_bt_cmd(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + void *data, int length) +{ + struct sk_buff *skb; + + skb = user->alloc_skb(length, GFP_ATOMIC); + if (!skb) { + dev_err(user->dev, "cg2900_send_bt_cmd: Couldn't alloc " + "sk_buff with length %d\n", length); + return; + } + + memcpy(skb_put(skb, length), data, length); + skb_push(skb, HCI_H4_SIZE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + cg2900_tx_to_chip(user, logger, skb); +} +EXPORT_SYMBOL_GPL(cg2900_send_bt_cmd); + +/** + * cg2900_send_bt_cmd_no_user() - Copy and send sk_buffer with no assigned user. + * @dev: Current chip to transmit to. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The cg2900_send_bt_cmd_no_user() function allocate sk_buffer, copy supplied + * data to it, and send the sk_buffer to controller. + */ +void cg2900_send_bt_cmd_no_user(struct cg2900_chip_dev *dev, void *data, + int length) +{ + struct sk_buff *skb; + + skb = alloc_skb(length + HCI_H4_SIZE, GFP_KERNEL); + if (!skb) { + dev_err(dev->dev, "cg2900_send_bt_cmd_no_user: Couldn't alloc " + "sk_buff with length %d\n", length); + return; + } + + skb_reserve(skb, HCI_H4_SIZE); + memcpy(skb_put(skb, length), data, length); + skb_push(skb, HCI_H4_SIZE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + cg2900_tx_no_user(dev, skb); +} +EXPORT_SYMBOL_GPL(cg2900_send_bt_cmd_no_user); + +/** + * create_work_item() - Create work item and add it to the work queue. + * @wq: Work queue. + * @work_func: Work function. + * @user_data: Arbitrary data set by user. + * + * The create_work_item() function creates work item and add it to + * the work queue. + * Note that work is allocated by kmalloc and work must be freed when work + * function is started. + */ +void cg2900_create_work_item(struct workqueue_struct *wq, work_func_t work_func, + void *user_data) +{ + struct cg2900_work *new_work; + int err; + + new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC); + if (!new_work) { + pr_err("Failed to alloc memory for new_work"); + return; + } + + INIT_WORK(&new_work->work, work_func); + new_work->user_data = user_data; + + err = queue_work(wq, &new_work->work); + if (!err) { + pr_err("Failed to queue work_struct because it's already " + "in the queue"); + kfree(new_work); + } +} +EXPORT_SYMBOL_GPL(cg2900_create_work_item); + +/** + * read_and_send_file_part() - Transmit a part of the supplied file. + * @user: User data for current channel. + * @logger: User data for logger channel. + * @info: File information. + * + * The cg2900_read_and_send_file_part() function transmit a part of the supplied + * file to the controller. + * + * Returns: + * 0 if there is no more data in the file. + * >0 for number of bytes sent. + * -ENOMEM if skb allocation failed. + */ +int cg2900_read_and_send_file_part(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + struct cg2900_file_info *info) +{ + int bytes_to_copy; + struct sk_buff *skb; + struct bt_vs_write_file_block_cmd *cmd; + int plen; + + /* + * Calculate number of bytes to copy; + * either max bytes for HCI packet or number of bytes left in file + */ + bytes_to_copy = min((int)HCI_BT_SEND_FILE_MAX_CHUNK_SIZE, + (int)(info->fw_file->size - info->file_offset)); + + if (bytes_to_copy <= 0) { + /* Nothing more to read in file. */ + dev_dbg(user->dev, "File download finished\n"); + info->chunk_id = 0; + info->file_offset = 0; + return 0; + } + + /* There is more data to send */ + plen = sizeof(*cmd) + bytes_to_copy; + skb = user->alloc_skb(plen, GFP_KERNEL); + if (!skb) { + dev_err(user->dev, "Couldn't allocate sk_buffer\n"); + return -ENOMEM; + } + + skb_put(skb, plen); + + cmd = (struct bt_vs_write_file_block_cmd *)skb->data; + cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_WRITE_FILE_BLOCK); + cmd->plen = BT_PARAM_LEN(plen); + cmd->id = info->chunk_id; + info->chunk_id++; + + /* Copy the data from offset position */ + memcpy(cmd->data, + &(info->fw_file->data[info->file_offset]), + bytes_to_copy); + + /* Increase offset with number of bytes copied */ + info->file_offset += bytes_to_copy; + + skb_push(skb, CG2900_SKB_RESERVE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + cg2900_tx_to_chip(user, logger, skb); + + return bytes_to_copy; +} +EXPORT_SYMBOL_GPL(cg2900_read_and_send_file_part); + +void cg2900_send_to_hci_logger(struct cg2900_user_data *logger, + struct sk_buff *skb, + u8 direction) +{ + struct sk_buff *skb_log; + u8 *p; + + /* + * Alloc a new sk_buff and copy the data into it. Then send it to + * the HCI logger. + */ + skb_log = alloc_skb(skb->len + LOGGER_HEADER_SIZE, GFP_NOWAIT); + if (!skb_log) { + pr_err("cg2900_send_to_hci_logger:\ + Couldn't allocate skb_log\n"); + return; + } + /* Reserve 1 byte for direction.*/ + skb_reserve(skb_log, LOGGER_HEADER_SIZE); + + memcpy(skb_put(skb_log, skb->len), skb->data, skb->len); + p = skb_push(skb_log, LOGGER_HEADER_SIZE); + *p = (u8) direction; + + if (logger->read_cb) + logger->read_cb(logger, skb_log); + else + kfree_skb(skb_log); + + return; +} +EXPORT_SYMBOL_GPL(cg2900_send_to_hci_logger); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux CG2900 Library functions"); diff --git a/drivers/staging/cg2900/mfd/cg2900_lib.h b/drivers/staging/cg2900/mfd/cg2900_lib.h new file mode 100644 index 00000000000..99d5ce6cfdb --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_lib.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#ifndef _CG2900_LIB_H_ +#define _CG2900_LIB_H_ + +#include <linux/firmware.h> +#include <linux/skbuff.h> +#include <linux/workqueue.h> + +#include "cg2900.h" + +/** + * struct cg2900_work - Generic work structure. + * @work: Work structure. + * @user_data: Arbitrary data set by user. + */ +struct cg2900_work { + struct work_struct work; + void *user_data; +}; + +/** + * struct cg2900_file_info - Info structure for file to download. + * @fw_file: Stores firmware file. + * @file_offset: Current read offset in firmware file. + * @chunk_id: Stores current chunk ID of write file + * operations. + */ +struct cg2900_file_info { + const struct firmware *fw_file; + int file_offset; + u8 chunk_id; +}; + +extern void cg2900_tx_to_chip(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + struct sk_buff *skb); +extern void cg2900_tx_no_user(struct cg2900_chip_dev *dev, struct sk_buff *skb); +extern void cg2900_send_bt_cmd(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + void *data, int length); +extern void cg2900_send_bt_cmd_no_user(struct cg2900_chip_dev *dev, void *data, + int length); +extern void cg2900_create_work_item(struct workqueue_struct *wq, + work_func_t work_func, + void *user_data); +extern int cg2900_read_and_send_file_part(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + struct cg2900_file_info *info); +extern void cg2900_send_to_hci_logger(struct cg2900_user_data *logger, + struct sk_buff *skb, + u8 direction); + +#endif /* _CG2900_LIB_H_ */ diff --git a/drivers/staging/cg2900/mfd/cg2900_test.c b/drivers/staging/cg2900/mfd/cg2900_test.c new file mode 100644 index 00000000000..58ac6166af6 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_test.c @@ -0,0 +1,402 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Driver for ST-Ericsson CG2900 test character device. + */ +#define NAME "cg2900_test" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <asm/byteorder.h> +#include <linux/firmware.h> +#include <linux/fs.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/wait.h> + +#include "cg2900.h" +#include "cg2900_core.h" + +#define MISC_DEV (info->misc_dev.this_device) + +/* Device names */ +#define CG2900_CDEV_NAME "cg2900_core_test" + +/** + * struct test_info - Main info structure for CG2900 test char device. + * @misc_dev: Registered Misc Device. + * @rx_queue: RX data queue. + * @dev: Device structure for STE Connectivity driver. + * @pdev: Platform device structure for STE Connectivity driver. + */ +struct test_info { + struct miscdevice misc_dev; + struct sk_buff_head rx_queue; + struct device *dev; + struct platform_device *pdev; +}; + +static struct test_info *test_info; + +/* + * main_wait_queue - Char device Wait Queue in CG2900 Core. + */ +static DECLARE_WAIT_QUEUE_HEAD(char_wait_queue); + +/** + * tx_to_char_dev() - Handle data received from CG2900 Core. + * @dev: Current chip device information. + * @skb: Buffer with data coming form device. + */ +static int tx_to_char_dev(struct cg2900_chip_dev *dev, struct sk_buff *skb) +{ + struct test_info *info = dev->t_data; + skb_queue_tail(&info->rx_queue, skb); + wake_up_interruptible_all(&char_wait_queue); + return 0; +} + +/** + * cg2900_test_open() - User space char device has been opened. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + * -EACCES if transport already exists. + * -ENOMEM if allocation fails. + * Errors from create_work_item. + */ +static int cg2900_test_open(struct inode *inode, struct file *filp) +{ + struct test_info *info = test_info; + struct cg2900_chip_dev *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + dev_err(MISC_DEV, "Cannot allocate test_dev\n"); + return -ENOMEM; + } + dev->dev = info->dev; + dev->pdev = info->pdev; + dev->t_data = info; + dev->t_cb.write = tx_to_char_dev; + filp->private_data = dev; + + dev_info(MISC_DEV, "CG2900 test char dev opened\n"); + return cg2900_register_trans_driver(dev); +} + +/** + * cg2900_test_release() - User space char device has been closed. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + */ +static int cg2900_test_release(struct inode *inode, struct file *filp) +{ + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + + dev_info(MISC_DEV, "CG2900 test char dev closed\n"); + skb_queue_purge(&info->rx_queue); + cg2900_deregister_trans_driver(dev); + kfree(dev); + + return 0; +} + +/** + * cg2900_test_read() - Queue and copy buffer to user space char device. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Count of received data in bytes. + * @f_pos: Position in buffer. + * + * Returns: + * >= 0 is number of bytes read. + * -EFAULT if copy_to_user fails. + */ +static ssize_t cg2900_test_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + int bytes_to_copy; + int err; + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + struct sk_buff_head *rx_queue = &info->rx_queue; + + dev_dbg(MISC_DEV, "cg2900_test_read count %d\n", count); + + if (skb_queue_empty(rx_queue)) + wait_event_interruptible(char_wait_queue, + !(skb_queue_empty(rx_queue))); + + skb = skb_dequeue(rx_queue); + if (!skb) { + dev_dbg(MISC_DEV, + "skb queue is empty - return with zero bytes\n"); + bytes_to_copy = 0; + goto finished; + } + + bytes_to_copy = min(count, skb->len); + err = copy_to_user(buf, skb->data, bytes_to_copy); + if (err) { + skb_queue_head(rx_queue, skb); + return -EFAULT; + } + + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) + skb_queue_head(rx_queue, skb); + else + kfree_skb(skb); + +finished: + return bytes_to_copy; +} + +/** + * cg2900_test_write() - Copy buffer from user and write to CG2900 Core. + * @filp: Pointer to the file struct. + * @buf: Read buffer. + * @count: Size of the buffer write. + * @f_pos: Position in buffer. + * + * Returns: + * >= 0 is number of bytes written. + * -EFAULT if copy_from_user fails. + */ +static ssize_t cg2900_test_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + + dev_dbg(MISC_DEV, "cg2900_test_write count %d\n", count); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(count + RX_SKB_RESERVE, GFP_KERNEL); + if (!skb) { + dev_err(MISC_DEV, "cg2900_test_write: Failed to alloc skb\n"); + return -ENOMEM; + } + skb_reserve(skb, RX_SKB_RESERVE); + + if (copy_from_user(skb_put(skb, count), buf, count)) { + kfree_skb(skb); + return -EFAULT; + } + + dev->c_cb.data_from_chip(dev, skb); + + return count; +} + +/** + * cg2900_test_poll() - Handle POLL call to the interface. + * @filp: Pointer to the file struct. + * @wait: Poll table supplied to caller. + * + * Returns: + * Mask of current set POLL values (0 or (POLLIN | POLLRDNORM)) + */ +static unsigned int cg2900_test_poll(struct file *filp, poll_table *wait) +{ + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + unsigned int mask = 0; + + poll_wait(filp, &char_wait_queue, wait); + + if (!(skb_queue_empty(&info->rx_queue))) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static const struct file_operations test_char_dev_fops = { + .open = cg2900_test_open, + .release = cg2900_test_release, + .read = cg2900_test_read, + .write = cg2900_test_write, + .poll = cg2900_test_poll +}; + +/** + * test_char_dev_create() - Create a char device for testing. + * @info: Test device info. + * + * Creates a separate char device that will interact directly with userspace + * test application. + * + * Returns: + * 0 if there is no error. + * Error codes from misc_register. + */ +static int test_char_dev_create(struct test_info *info) +{ + int err; + + /* Initialize the RX queue */ + skb_queue_head_init(&info->rx_queue); + + /* Prepare miscdevice struct before registering the device */ + info->misc_dev.minor = MISC_DYNAMIC_MINOR; + info->misc_dev.name = CG2900_CDEV_NAME; + info->misc_dev.fops = &test_char_dev_fops; + info->misc_dev.parent = info->dev; + info->misc_dev.mode = S_IRUGO | S_IWUGO; + + err = misc_register(&info->misc_dev); + if (err) { + dev_err(info->dev, "Error %d registering misc dev", err); + return err; + } + + return 0; +} + +/** + * test_char_dev_destroy() - Clean up after test_char_dev_create(). + * @info: Test device info. + */ +static void test_char_dev_destroy(struct test_info *info) +{ + int err; + + err = misc_deregister(&info->misc_dev); + if (err) + dev_err(info->dev, "Error %d deregistering misc dev\n", err); + + /* Clean the message queue */ + skb_queue_purge(&info->rx_queue); +} + +/** + * cg2900_test_probe() - Initialize module. + * + * @pdev: Platform device. + * + * This function initializes and registers the test misc char device. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * -EEXIST if device already exists. + * Error codes generated by test_char_dev_create. + */ +static int __devinit cg2900_test_probe(struct platform_device *pdev) +{ + int err; + + dev_dbg(&pdev->dev, "cg2900_test_probe\n"); + + if (test_info) { + dev_err(&pdev->dev, "test_info exists\n"); + return -EEXIST; + } + + test_info = kzalloc(sizeof(*test_info), GFP_KERNEL); + if (!test_info) { + dev_err(&pdev->dev, "Couldn't allocate test_info\n"); + return -ENOMEM; + } + + test_info->dev = &pdev->dev; + test_info->pdev = pdev; + + /* Create and add test char device. */ + err = test_char_dev_create(test_info); + if (err) { + kfree(test_info); + test_info = NULL; + return err; + } + + dev_set_drvdata(&pdev->dev, test_info); + + dev_info(&pdev->dev, "CG2900 test char device driver started\n"); + + return 0; +} + +/** + * cg2900_test_remove() - Remove module. + * + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * -ENOMEM if core_info does not exist. + * -EINVAL if platform data does not exist in the device. + */ +static int __devexit cg2900_test_remove(struct platform_device *pdev) +{ + struct test_info *test_info; + + dev_dbg(&pdev->dev, "cg2900_test_remove\n"); + test_info = dev_get_drvdata(&pdev->dev); + test_char_dev_destroy(test_info); + dev_set_drvdata(&pdev->dev, NULL); + kfree(test_info); + test_info = NULL; + dev_info(&pdev->dev, "CG2900 Test char device driver removed\n"); + return 0; +} + +static struct platform_driver cg2900_test_driver = { + .driver = { + .name = "cg2900-test", + .owner = THIS_MODULE, + }, + .probe = cg2900_test_probe, + .remove = __devexit_p(cg2900_test_remove), +}; + +/** + * cg2900_test_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_test_init(void) +{ + pr_debug("cg2900_test_init"); + return platform_driver_register(&cg2900_test_driver); +} + +/** + * cg2900_test_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_test_exit(void) +{ + pr_debug("cg2900_test_exit"); + platform_driver_unregister(&cg2900_test_driver); +} + +module_init(cg2900_test_init); +module_exit(cg2900_test_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux CG2900 Test Char Device Driver"); diff --git a/drivers/staging/cg2900/mfd/stlc2690_chip.c b/drivers/staging/cg2900/mfd/stlc2690_chip.c new file mode 100644 index 00000000000..84de0d7a976 --- /dev/null +++ b/drivers/staging/cg2900/mfd/stlc2690_chip.c @@ -0,0 +1,1671 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller. + */ +#define NAME "stlc2690_chip" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <asm/byteorder.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/stat.h> +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/mfd/core.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> + +#include "cg2900.h" +#include "cg2900_core.h" +#include "cg2900_lib.h" +#include "stlc2690_chip.h" + +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + +#define MAIN_DEV (main_info->dev) +#define BOOT_DEV (info->user_in_charge->dev) + +#define WQ_NAME "stlc2690_chip_wq" + +#define LINE_TOGGLE_DETECT_TIMEOUT 50 /* ms */ +#define CHIP_READY_TIMEOUT 100 /* ms */ +#define CHIP_STARTUP_TIMEOUT 15000 /* ms */ +#define CHIP_SHUTDOWN_TIMEOUT 15000 /* ms */ + +/** CHANNEL_BT_CMD - Bluetooth HCI H:4 channel + * for Bluetooth commands in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_CMD 0x01 + +/** CHANNEL_BT_ACL - Bluetooth HCI H:4 channel + * for Bluetooth ACL data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_ACL 0x02 + +/** CHANNEL_BT_EVT - Bluetooth HCI H:4 channel + * for Bluetooth events in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_EVT 0x04 + +/** CHANNEL_HCI_LOGGER - Bluetooth HCI H:4 channel + * for logging all transmitted H4 packets (on all channels). + */ +#define CHANNEL_HCI_LOGGER 0xFA + +/** CHANNEL_CORE - Bluetooth HCI H:4 channel + * for user space control of the ST-Ericsson connectivity controller. + */ +#define CHANNEL_CORE 0xFD + +/* + * For the char dev names we keep the same names in order to be able to reuse + * the users and to keep a consistent interface. + */ + +/** STLC2690_BT_CMD - Bluetooth HCI H4 channel for Bluetooth commands. + */ +#define STLC2690_BT_CMD "cg2900_bt_cmd" + +/** STLC2690_BT_ACL - Bluetooth HCI H4 channel for Bluetooth ACL data. + */ +#define STLC2690_BT_ACL "cg2900_bt_acl" + +/** STLC2690_BT_EVT - Bluetooth HCI H4 channel for Bluetooth events. + */ +#define STLC2690_BT_EVT "cg2900_bt_evt" + +/** STLC2690_HCI_LOGGER - BT channel for logging all transmitted H4 packets. + * Data read is copy of all data transferred on the other channels. + * Only write allowed is configuration of the HCI Logger. + */ +#define STLC2690_HCI_LOGGER "cg2900_hci_logger" + +/** STLC2690_CORE- Channel for keeping ST-Ericsson STLC2690 enabled. + * Opening this channel forces the chip to stay powered. + * No data can be written to or read from this channel. + */ +#define STLC2690_CORE "cg2900_core" + +/** + * enum main_state - Main-state for STLC2690 driver. + * @STLC2690_INIT: STLC2690 initializing. + * @STLC2690_IDLE: No user registered to STLC2690 driver. + * @STLC2690_BOOTING: STLC2690 booting after first user is registered. + * @STLC2690_CLOSING: STLC2690 closing after last user has deregistered. + * @STLC2690_RESETING: STLC2690 reset requested. + * @STLC2690_ACTIVE: STLC2690 up and running with at least one user. + */ +enum main_state { + STLC2690_INIT, + STLC2690_IDLE, + STLC2690_BOOTING, + STLC2690_CLOSING, + STLC2690_RESETING, + STLC2690_ACTIVE +}; + +/** + * enum boot_state - BOOT-state for STLC2690 chip driver. + * @BOOT_RESET: HCI Reset has been sent. + * @BOOT_SEND_BD_ADDRESS: VS Store In FS command with BD address + * has been sent. + * @BOOT_GET_FILES_TO_LOAD: STLC2690 chip driver is retrieving file + * to load. + * @BOOT_DOWNLOAD_PATCH: STLC2690 chip driver is downloading + * patches. + * @BOOT_ACTIVATE_PATCHES_AND_SETTINGS: STLC2690 chip driver is activating + * patches and settings. + * @BOOT_READY: STLC2690 chip driver boot is ready. + * @BOOT_FAILED: STLC2690 chip driver boot failed. + */ +enum boot_state { + BOOT_RESET, + BOOT_SEND_BD_ADDRESS, + BOOT_GET_FILES_TO_LOAD, + BOOT_DOWNLOAD_PATCH, + BOOT_ACTIVATE_PATCHES_AND_SETTINGS, + BOOT_READY, + BOOT_FAILED +}; + +/** + * enum file_load_state - BOOT_FILE_LOAD-state for STLC2690 chip driver. + * @FILE_LOAD_GET_PATCH: Loading patches. + * @FILE_LOAD_GET_STATIC_SETTINGS: Loading static settings. + * @FILE_LOAD_NO_MORE_FILES: No more files to load. + * @FILE_LOAD_FAILED: File loading failed. + */ +enum file_load_state { + FILE_LOAD_GET_PATCH, + FILE_LOAD_GET_STATIC_SETTINGS, + FILE_LOAD_NO_MORE_FILES, + FILE_LOAD_FAILED +}; + +/** + * enum download_state - BOOT_DOWNLOAD state. + * @DOWNLOAD_PENDING: Download in progress. + * @DOWNLOAD_SUCCESS: Download successfully finished. + * @DOWNLOAD_FAILED: Downloading failed. + */ +enum download_state { + DOWNLOAD_PENDING, + DOWNLOAD_SUCCESS, + DOWNLOAD_FAILED +}; + + +/** + * struct stlc2690_channel_item - List object for channel. + * @list: list_head struct. + * @user: User for this channel. + */ +struct stlc2690_channel_item { + struct list_head list; + struct cg2900_user_data *user; +}; + +/** + * struct stlc2690_skb_data - Structure for storing private data in an sk_buffer. + * @dev: STLC2690 device for this sk_buffer. + */ +struct stlc2690_skb_data { + struct cg2900_user_data *user; +}; +#define stlc2690_skb_data(__skb) ((struct stlc2690_skb_data *)((__skb)->cb)) + +/** + * struct stlc2690_chip_info - Main info structure for STLC2690 chip driver. + * @patch_file_name: Stores patch file name. + * @settings_file_name: Stores settings file name. + * @file_info: Firmware file info (patch or settings). + * @main_state: Current MAIN-state of STLC2690 chip driver. + * @boot_state: Current BOOT-state of STLC2690 chip driver. + * @file_load_state: Current BOOT_FILE_LOAD-state of STLC2690 chip + * driver. + * @download_state: Current BOOT_DOWNLOAD-state of STLC2690 chip + * driver. + * @wq: STLC2690 chip driver workqueue. + * @chip_dev: Chip handler info. + * @user_in_charge: User currently operating. Normally used at + * channel open and close. + * @last_user: Last user of this chip. + * @logger: Logger user of this chip. + * @startup: True if system is starting up. + * @mfd_size: Number of MFD cells. + * @mfd_char_size: Number of MFD char device cells. + */ +struct stlc2690_chip_info { + char *patch_file_name; + char *settings_file_name; + struct cg2900_file_info file_info; + enum main_state main_state; + enum boot_state boot_state; + enum file_load_state file_load_state; + enum download_state download_state; + struct workqueue_struct *wq; + struct cg2900_chip_dev *chip_dev; + spinlock_t rw_lock; + struct list_head open_channels; + struct cg2900_user_data *user_in_charge; + struct cg2900_user_data *last_user; + struct cg2900_user_data *logger; + bool startup; + int mfd_size; + int mfd_char_size; +}; + +/** + * struct main_info - Main info structure for STLC2690 chip driver. + * @dev: Device structure. + * @cell_base_id: Base ID for MFD cells. + * @man_mutex: Management mutex. + */ +struct main_info { + struct device *dev; + int cell_base_id; + struct mutex man_mutex; +}; + +static struct main_info *main_info; + +/* + * main_wait_queue - Main Wait Queue in STLC2690 driver. + */ +static DECLARE_WAIT_QUEUE_HEAD(main_wait_queue); + +static struct mfd_cell stlc2690_devs[]; +static struct mfd_cell stlc2690_char_devs[]; + +static void chip_startup_finished(struct stlc2690_chip_info *info, int err); + +/** + * send_bd_address() - Send HCI VS command with BD address to the chip. + */ +static void send_bd_address(struct stlc2690_chip_info *info) +{ + struct bt_vs_store_in_fs_cmd *cmd; + u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE; + + cmd = kmalloc(plen, GFP_KERNEL); + if (!cmd) + return; + + cmd->opcode = cpu_to_le16(STLC2690_BT_OP_VS_STORE_IN_FS); + cmd->plen = BT_PARAM_LEN(plen); + cmd->user_id = STLC2690_VS_STORE_IN_FS_USR_ID_BD_ADDR; + cmd->len = BT_BDADDR_SIZE; + /* Now copy the BD address received from user space control app. */ + memcpy(cmd->data, bd_address, BT_BDADDR_SIZE); + + dev_dbg(BOOT_DEV, "New boot_state: BOOT_SEND_BD_ADDRESS\n"); + info->boot_state = BOOT_SEND_BD_ADDRESS; + + cg2900_send_bt_cmd(info->user_in_charge, info->logger, cmd, plen); + + kfree(cmd); +} + +/** + * send_settings_file() - Transmit settings file. + * + * The send_settings_file() function transmit settings file. + * The file is read in parts to fit in HCI packets. When finished, + * close the settings file and send HCI reset to activate settings and patches. + */ +static void send_settings_file(struct stlc2690_chip_info *info) +{ + int bytes_sent; + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_settings_file: Error %d occurred\n", + bytes_sent); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, bytes_sent); + return; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + /* Settings file finished. Release used resources */ + dev_dbg(BOOT_DEV, "Settings file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_NO_MORE_FILES\n"); + info->file_load_state = FILE_LOAD_NO_MORE_FILES; + + /* Create and send HCI VS Store In FS command with bd address. */ + send_bd_address(info); +} + +/** + * send_patch_file - Transmit patch file. + * + * The send_patch_file() function transmit patch file. + * The file is read in parts to fit in HCI packets. When the complete file is + * transmitted, the file is closed. + * When finished, continue with settings file. + */ +static void send_patch_file(struct cg2900_chip_dev *dev) +{ + int err; + int bytes_sent; + struct stlc2690_chip_info *info = dev->c_data; + int file_name_size = strlen("STLC2690_XXXX_XXXX_settings.fw"); + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_patch_file: Error %d occurred\n", + bytes_sent); + err = bytes_sent; + goto error_handling; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + dev_dbg(BOOT_DEV, "Patch file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + /* + * Create the settings file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->settings_file_name, file_name_size + 1, + "STLC2690_%04X_%04X_settings.fw", + dev->chip.hci_revision, dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading settings file %s\n", + info->settings_file_name); + } else { + dev_err(BOOT_DEV, "Settings file name failed! err=%d\n", err); + goto error_handling; + } + + /* Retrieve the settings file */ + err = request_firmware(&info->file_info.fw_file, + info->settings_file_name, + info->chip_dev->dev); + if (err) { + dev_err(BOOT_DEV, "Couldn't get settings file (%d)\n", err); + goto error_handling; + } + /* Now send the settings file */ + dev_dbg(BOOT_DEV, + "New file_load_state: FILE_LOAD_GET_STATIC_SETTINGS\n"); + info->file_load_state = FILE_LOAD_GET_STATIC_SETTINGS; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + send_settings_file(info); + return; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, err); +} + +/** + * work_reset_after_error() - Handle reset. + * @work: Reference to work data. + * + * Handle a reset after received Command Complete event. + */ +static void work_reset_after_error(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_reset_after_error: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + chip_startup_finished(info, -EIO); + + kfree(my_work); +} + +/** + * work_load_patch_and_settings() - Start loading patches and settings. + * @work: Reference to work data. + */ +static void work_load_patch_and_settings(struct work_struct *work) +{ + int err = 0; + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + int file_name_size = strlen("STLC2690_XXXX_XXXX_patch.fw"); + + if (!work) { + dev_err(MAIN_DEV, + "work_load_patch_and_settings: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Check that we are in the right state */ + if (info->boot_state != BOOT_GET_FILES_TO_LOAD) + goto finished; + + /* + * Create the patch file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->patch_file_name, file_name_size + 1, + "STLC2690_%04X_%04X_patch.fw", dev->chip.hci_revision, + dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading patch file %s\n", + info->patch_file_name); + } else { + dev_err(BOOT_DEV, "Patch file name failed! err=%d\n", err); + goto error_handling; + } + + /* We now all info needed */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DOWNLOAD_PATCH\n"); + info->boot_state = BOOT_DOWNLOAD_PATCH; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_GET_PATCH\n"); + info->file_load_state = FILE_LOAD_GET_PATCH; + info->file_info.chunk_id = 0; + info->file_info.file_offset = 0; + info->file_info.fw_file = NULL; + + /* OK. Now it is time to download the patches */ + err = request_firmware(&(info->file_info.fw_file), + info->patch_file_name, + dev->dev); + if (err < 0) { + dev_err(BOOT_DEV, "Couldn't get patch file (%d)\n", err); + goto error_handling; + } + send_patch_file(dev); + + goto finished; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); +finished: + kfree(my_work); +} + +/** + * work_cont_file_download() - A file block has been written. + * @work: Reference to work data. + * + * Handle a received HCI VS Write File Block Complete event. + * Normally this means continue to send files to the controller. + */ +static void work_cont_file_download(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_cont_file_download: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Continue to send patches or settings to the controller */ + if (info->file_load_state == FILE_LOAD_GET_PATCH) + send_patch_file(dev); + else if (info->file_load_state == FILE_LOAD_GET_STATIC_SETTINGS) + send_settings_file(info); + else + dev_dbg(BOOT_DEV, "No more files to load\n"); + + kfree(my_work); +} + +/** + * handle_reset_cmd_complete() - Handles HCI Reset Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_reset_cmd_complete(struct cg2900_chip_dev *dev, u8 *data) +{ + u8 status = data[0]; + struct stlc2690_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, "Received Reset complete event with status 0x%X\n", + status); + + if (BOOT_RESET != info->boot_state && + BOOT_ACTIVATE_PATCHES_AND_SETTINGS != info->boot_state) + return false; + + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, "Command complete for HciReset received with " + "error 0x%X\n", status); + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + return true; + } + + if (BOOT_RESET == info->boot_state) { + info->boot_state = BOOT_GET_FILES_TO_LOAD; + cg2900_create_work_item(info->wq, work_load_patch_and_settings, + dev); + } else { + /* + * The boot sequence is now finished successfully. + * Set states and signal to waiting thread. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_READY\n"); + info->boot_state = BOOT_READY; + chip_startup_finished(info, 0); + } + + return true; +} + + +/** + * handle_vs_store_in_fs_cmd_complete() - Handles HCI VS StoreInFS Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_store_in_fs_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct stlc2690_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, + "Received Store_in_FS complete event with status 0x%X\n", + status); + + if (info->boot_state != BOOT_SEND_BD_ADDRESS) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) { + struct hci_command_hdr cmd; + + /* Send HCI Reset command to activate patches */ + dev_dbg(BOOT_DEV, + "New boot_state: BOOT_ACTIVATE_PATCHES_AND_SETTINGS\n"); + info->boot_state = BOOT_ACTIVATE_PATCHES_AND_SETTINGS; + + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for Reset */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); + } else { + dev_err(BOOT_DEV, + "Command complete for StoreInFS received with error " + "0x%X\n", status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_complete() - Handles HCI VS WriteFileBlock Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct stlc2690_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) + cg2900_create_work_item(info->wq, work_cont_file_download, dev); + else { + dev_err(BOOT_DEV, + "Command complete for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_status() - Handles HCI VS WriteFileBlock Command Status event. + * @status: Returned status of WriteFileBlock command. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_status(struct cg2900_chip_dev *dev, + u8 status) +{ + struct stlc2690_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + /* + * Only do something if there is an error. Otherwise we will wait for + * CmdComplete. + */ + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, + "Command status for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_rx_data_bt_evt() - Check if received data should be handled in STLC2690 chip driver. + * @skb: Data packet + * + * The handle_rx_data_bt_evt() function checks if received data should be + * handled in STLC2690 chip driver. If so handle it correctly. + * Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_rx_data_bt_evt(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + bool pkt_handled = false; + /* skb cannot be NULL here so it is safe to de-reference */ + u8 *data = skb->data; + struct hci_event_hdr *evt; + u16 op_code; + + evt = (struct hci_event_hdr *)data; + data += sizeof(*evt); + + /* First check the event code. */ + if (HCI_EV_CMD_COMPLETE == evt->evt) { + struct hci_ev_cmd_complete *cmd_complete; + + cmd_complete = (struct hci_ev_cmd_complete *)data; + op_code = le16_to_cpu(cmd_complete->opcode); + dev_dbg(dev->dev, + "Received Command Complete: op_code = 0x%04X\n", + op_code); + /* Move to first byte after OCF */ + data += sizeof(*cmd_complete); + + if (op_code == HCI_OP_RESET) + pkt_handled = handle_reset_cmd_complete(dev, data); + else if (op_code == STLC2690_BT_OP_VS_STORE_IN_FS) + pkt_handled = handle_vs_store_in_fs_cmd_complete(dev, + data); + else if (op_code == STLC2690_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = + handle_vs_write_file_block_cmd_complete(dev, + data); + } else if (HCI_EV_CMD_STATUS == evt->evt) { + struct hci_ev_cmd_status *cmd_status; + + cmd_status = (struct hci_ev_cmd_status *)data; + + op_code = le16_to_cpu(cmd_status->opcode); + + dev_dbg(dev->dev, "Received Command Status: op_code = 0x%04X\n", + op_code); + + if (op_code == STLC2690_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = handle_vs_write_file_block_cmd_status + (dev, cmd_status->status); + } else if (HCI_EV_HW_ERROR == evt->evt) { + struct hci_ev_hw_error *hw_error; + + hw_error = (struct hci_ev_hw_error *)data; + /* + * Only do a printout. There might be a receiving stack that can + * handle this event + */ + dev_err(dev->dev, "HW Error event received with error 0x%02X\n", + hw_error->hw_code); + return false; + } else + return false; + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +/** + * data_from_chip() - Called when data is received from the chip. + * @dev: Chip info. + * @skb: Packet received. + * + * The data_from_chip() function checks if packet is a response for a packet it + * itself has transmitted. If not it finds the correct user and sends the packet + * to the user. + */ +static void data_from_chip(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + int h4_channel; + struct list_head *cursor; + struct stlc2690_channel_item *tmp; + struct stlc2690_chip_info *info = dev->c_data; + struct cg2900_user_data *user = NULL; + + h4_channel = skb->data[0]; + skb_pull(skb, HCI_H4_SIZE); + + /* Then check if this is a response to data we have sent */ + if (h4_channel == CHANNEL_BT_EVT && handle_rx_data_bt_evt(dev, skb)) + return; + + spin_lock_bh(&info->rw_lock); + + /* Let's see if this packet has the same user as the last one */ + if (info->last_user && info->last_user->h4_channel == h4_channel) { + user = info->last_user; + goto user_found; + } + + /* Search through the list of all open channels to find the user */ + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + if (tmp->user->h4_channel == h4_channel) { + user = tmp->user; + goto user_found; + } + } + +user_found: + info->last_user = user; + spin_unlock_bh(&info->rw_lock); + + if (user) + user->read_cb(user, skb); + else { + dev_err(dev->dev, + "Could not find corresponding user to h4_channel %d\n", + h4_channel); + kfree_skb(skb); + } +} + +static void chip_removed(struct cg2900_chip_dev *dev) +{ + struct stlc2690_chip_info *info = dev->c_data; + + mfd_remove_devices(dev->dev); + kfree(info->settings_file_name); + kfree(info->patch_file_name); + destroy_workqueue(info->wq); + kfree(info); + dev->c_data = NULL; + dev->c_cb.chip_removed = NULL; + dev->c_cb.data_from_chip = NULL; +} + +/** + * chip_shutdown() - Reset and power the chip off. + */ +static void chip_shutdown(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct stlc2690_chip_info *info = dev->c_data; + + dev_dbg(user->dev, "chip_shutdown\n"); + + /* Close the transport, which will power off the chip */ + if (dev->t_cb.close) + dev->t_cb.close(dev); + + /* Chip shut-down finished, set correct state and wake up the chip. */ + dev_dbg(dev->dev, "New main_state: STLC2690_IDLE\n"); + info->main_state = STLC2690_IDLE; + wake_up_all(&main_wait_queue); +} + +static void chip_startup_finished(struct stlc2690_chip_info *info, int err) +{ + dev_dbg(BOOT_DEV, "chip_startup_finished (%d)\n", err); + + if (err) + /* Shutdown the chip */ + chip_shutdown(info->user_in_charge); + else { + dev_dbg(BOOT_DEV, "New main_state: CORE_ACTIVE\n"); + info->main_state = STLC2690_ACTIVE; + } + + wake_up_all(&main_wait_queue); + + if (err) + return; + + if (!info->chip_dev->t_cb.chip_startup_finished) + dev_err(BOOT_DEV, "chip_startup_finished callback not found\n"); + else + info->chip_dev->t_cb.chip_startup_finished(info->chip_dev); +} + +static int stlc2690_open(struct cg2900_user_data *user) +{ + int err; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + struct list_head *cursor; + struct stlc2690_channel_item *tmp; + struct hci_command_hdr cmd; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "stlc2690_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "stlc2690_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + mutex_lock(&main_info->man_mutex); + + /* Add a minor wait in order to avoid CPU blocking, looping openings */ + err = wait_event_timeout(main_wait_queue, + (STLC2690_IDLE == info->main_state || + STLC2690_ACTIVE == info->main_state), + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + if (err <= 0) { + if (STLC2690_INIT == info->main_state) + dev_err(user->dev, "Transport not opened\n"); + else + dev_err(user->dev, "stlc2690_open currently busy " + "(0x%X). Try again\n", info->main_state); + err = -EBUSY; + goto err_free_mutex; + } + + err = 0; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + if (tmp->user->h4_channel == user->h4_channel) { + dev_err(user->dev, "Channel %d is already opened\n", + user->h4_channel); + err = -EACCES; + goto err_free_mutex; + } + } + + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) { + dev_err(user->dev, "Could not allocate tmp\n"); + err = -ENOMEM; + goto err_free_mutex; + } + tmp->user = user; + + if (STLC2690_ACTIVE != info->main_state && + !user->chip_independent) { + /* Open transport and start-up the chip */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait to be sure that the chip is ready */ + schedule_timeout_killable( + msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + if (dev->t_cb.open) + err = dev->t_cb.open(dev); + if (err) { + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, false); + goto err_free_list_item; + } + + /* Start the boot sequence */ + info->user_in_charge = user; + info->last_user = user; + dev_dbg(user->dev, "New boot_state: BOOT_RESET\n"); + info->boot_state = BOOT_RESET; + dev_dbg(user->dev, "New main_state: STLC2690_BOOTING\n"); + info->main_state = STLC2690_BOOTING; + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + cg2900_send_bt_cmd(user, info->logger, &cmd, sizeof(cmd)); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to start\n"); + wait_event_timeout(main_wait_queue, + (STLC2690_ACTIVE == info->main_state || + STLC2690_IDLE == info->main_state), + msecs_to_jiffies(CHIP_STARTUP_TIMEOUT)); + if (STLC2690_ACTIVE != info->main_state) { + dev_err(user->dev, "STLC2690 driver failed to start\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CORE_IDLE\n"); + info->main_state = STLC2690_IDLE; + err = -EIO; + goto err_free_list_item; + } + } + + list_add_tail(&tmp->list, &info->open_channels); + + user->opened = true; + + dev_dbg(user->dev, "H:4 channel opened\n"); + + mutex_unlock(&main_info->man_mutex); + return 0; +err_free_list_item: + kfree(tmp); +err_free_mutex: + mutex_unlock(&main_info->man_mutex); + return err; +} + +static int stlc2690_hci_log_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_hci_log_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "stlc2690_hci_log_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->logger) { + dev_err(user->dev, "HCI Logger already stored\n"); + return -EACCES; + } + + info->logger = user; + err = stlc2690_open(user); + if (err) + info->logger = NULL; + return err; +} + +static void stlc2690_close(struct cg2900_user_data *user) +{ + bool keep_powered = false; + struct list_head *cursor, *next; + struct stlc2690_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "stlc2690_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + mutex_lock(&main_info->man_mutex); + + /* + * Go through each open channel. Remove our channel and check if there + * is any other channel that want to keep the chip running + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + if (tmp->user == user) { + list_del(cursor); + kfree(tmp); + } else if (!tmp->user->chip_independent) + keep_powered = true; + } + + if (keep_powered) + /* This was not the last user, we're done. */ + goto finished; + + if (STLC2690_IDLE == info->main_state) + /* Chip has already been shut down. */ + goto finished; + + dev_dbg(user->dev, "New main_state: CORE_CLOSING\n"); + info->main_state = STLC2690_CLOSING; + chip_shutdown(user); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to shut-down\n"); + wait_event_timeout(main_wait_queue, + STLC2690_IDLE == info->main_state, + msecs_to_jiffies(CHIP_SHUTDOWN_TIMEOUT)); + + /* Force shutdown if we timed out */ + if (STLC2690_IDLE != info->main_state) { + dev_err(user->dev, + "ST-Ericsson STLC2690 Core Driver was shut-down with " + "problems\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CORE_IDLE\n"); + info->main_state = STLC2690_IDLE; + } + +finished: + mutex_unlock(&main_info->man_mutex); + user->opened = false; + dev_dbg(user->dev, "H:4 channel closed\n"); +} + +static void stlc2690_hci_log_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_hci_log_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "stlc2690_hci_log_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + info->logger = NULL; + stlc2690_close(user); +} + +static int stlc2690_reset(struct cg2900_user_data *user) +{ + struct list_head *cursor, *next; + struct stlc2690_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_reset: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_info(user->dev, "stlc2690_reset\n"); + + BUG_ON(!main_info); + + mutex_lock(&main_info->man_mutex); + + dev_dbg(user->dev, "New main_state: CORE_RESETING\n"); + info->main_state = STLC2690_RESETING; + + chip_shutdown(user); + + /* + * Inform all opened channels about the reset and free the user devices + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + list_del(cursor); + tmp->user->opened = false; + tmp->user->reset_cb(tmp->user); + kfree(tmp); + } + + /* Reset finished. We are now idle until first channel is opened */ + dev_dbg(user->dev, "New main_state: STLC2690_IDLE\n"); + info->main_state = STLC2690_IDLE; + + mutex_unlock(&main_info->man_mutex); + + /* + * Send wake-up since this might have been called from a failed boot. + * No harm done if it is a STLC2690 chip user who called. + */ + wake_up_all(&main_wait_queue); + + return 0; +} + +static struct sk_buff *stlc2690_alloc_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + dev_dbg(MAIN_DEV, "stlc2690_alloc_skb size %d bytes\n", size); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + CG2900_SKB_RESERVE, priority); + if (skb) + skb_reserve(skb, CG2900_SKB_RESERVE); + + return skb; +} + +static int stlc2690_write(struct cg2900_user_data *user, struct sk_buff *skb) +{ + int err = 0; + u8 *h4_header; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_write: Calling with NULL pointer\n"); + return -EINVAL; + } + + if (!skb) { + dev_err(user->dev, "stlc2690_write with no sk_buffer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_dbg(user->dev, "stlc2690_write length %d bytes\n", skb->len); + + if (!user->opened) { + dev_err(user->dev, + "Trying to transmit data on a closed channel\n"); + return -EACCES; + } + + /* + * Move the data pointer to the H:4 header position and + * store the H4 header. + */ + h4_header = skb_push(skb, CG2900_SKB_RESERVE); + *h4_header = (u8)user->h4_channel; + cg2900_tx_to_chip(user, info->logger, skb); + + return err; +} + +static int stlc2690_no_write(struct cg2900_user_data *user, + struct sk_buff *skb) +{ + dev_err(user->dev, "Not allowed to send on this channel\n"); + return -EPERM; +} + +static bool stlc2690_get_local_revision(struct cg2900_user_data *user, + struct cg2900_rev_data *rev_data) +{ + struct cg2900_chip_dev *dev; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "stlc2690_get_local_revision: Calling with " + "NULL pointer\n"); + return false; + } + + if (!rev_data) { + dev_err(user->dev, "Calling with rev_data NULL\n"); + return false; + } + + dev = cg2900_get_prv(user); + + rev_data->revision = dev->chip.hci_revision; + rev_data->sub_version = dev->chip.hci_sub_version; + + return true; +} + +static struct cg2900_user_data btcmd_data = { + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data btacl_data = { + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data btevt_data = { + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data hci_logger_data = { + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = stlc2690_no_write, + .open = stlc2690_hci_log_open, + .close = stlc2690_hci_log_close, +}; +static struct cg2900_user_data core_data = { + .h4_channel = CHANNEL_CORE, + .write = stlc2690_no_write, +}; + +static struct mfd_cell stlc2690_devs[] = { + { + .name = "cg2900-btcmd", + .platform_data = &btcmd_data, + .pdata_size = sizeof(btcmd_data), + }, + { + .name = "cg2900-btacl", + .platform_data = &btacl_data, + .pdata_size = sizeof(btacl_data), + }, + { + .name = "cg2900-btevt", + .platform_data = &btevt_data, + .pdata_size = sizeof(btevt_data), + }, + { + .name = "cg2900-hcilogger", + .platform_data = &hci_logger_data, + .pdata_size = sizeof(hci_logger_data), + }, + { + .name = "cg2900-core", + .platform_data = &core_data, + .pdata_size = sizeof(core_data), + }, +}; + +static struct cg2900_user_data char_btcmd_data = { + .channel_data = { + .char_dev_name = STLC2690_BT_CMD, + }, + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data char_btacl_data = { + .channel_data = { + .char_dev_name = STLC2690_BT_ACL, + }, + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data char_btevt_data = { + .channel_data = { + .char_dev_name = STLC2690_BT_EVT, + }, + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data char_hci_logger_data = { + .channel_data = { + .char_dev_name = STLC2690_HCI_LOGGER, + }, + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = stlc2690_no_write, + .open = stlc2690_hci_log_open, + .close = stlc2690_hci_log_close, +}; +static struct cg2900_user_data char_core_data = { + .channel_data = { + .char_dev_name = STLC2690_CORE, + }, + .h4_channel = CHANNEL_CORE, + .write = stlc2690_no_write, +}; + +static struct mfd_cell stlc2690_char_devs[] = { + { + .name = "cg2900-chardev", + .id = 0, + .platform_data = &char_btcmd_data, + .pdata_size = sizeof(char_btcmd_data), + }, + { + .name = "cg2900-chardev", + .id = 1, + .platform_data = &char_btacl_data, + .pdata_size = sizeof(char_btacl_data), + }, + { + .name = "cg2900-chardev", + .id = 2, + .platform_data = &char_btevt_data, + .pdata_size = sizeof(char_btevt_data), + }, + { + .name = "cg2900-chardev", + .id = 7, + .platform_data = &char_hci_logger_data, + .pdata_size = sizeof(char_hci_logger_data), + }, + { + .name = "cg2900-chardev", + .id = 8, + .platform_data = &char_core_data, + .pdata_size = sizeof(char_core_data), + }, +}; + +/** + * set_plat_data() - Initializes data for an MFD cell. + * @cell: MFD cell. + * @dev: Current chip. + * + * Sets each callback to default function unless already set. + */ +static void set_plat_data(struct mfd_cell *cell, struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *user = cell->platform_data; + + if (!user->open) + user->open = stlc2690_open; + if (!user->close) + user->close = stlc2690_close; + if (!user->reset) + user->reset = stlc2690_reset; + if (!user->alloc_skb) + user->alloc_skb = stlc2690_alloc_skb; + if (!user->write) + user->write = stlc2690_write; + if (!user->get_local_revision) + user->get_local_revision = stlc2690_get_local_revision; + + cg2900_set_prv(user, dev); +} + +/** + * check_chip_support() - Checks if connected chip is handled by this driver. + * @dev: Chip info structure. + * + * If supported return true and fill in @callbacks. + * + * Returns: + * true if chip is handled by this driver. + * false otherwise. + */ +static bool check_chip_support(struct cg2900_chip_dev *dev) +{ + struct cg2900_platform_data *pf_data; + struct stlc2690_chip_info *info; + int i; + int err; + + dev_dbg(dev->dev, "check_chip_support\n"); + + /* + * Check if this is a STLC2690 revision. + * We do not care about the sub-version at the moment. Change this if + * necessary. + */ + if (dev->chip.manufacturer != STLC2690_SUPP_MANUFACTURER || + dev->chip.hci_revision < STLC2690_SUPP_REVISION_MIN || + dev->chip.hci_revision > STLC2690_SUPP_REVISION_MAX) { + dev_dbg(dev->dev, "Chip not supported by STLC2690 driver\n" + "\tMan: 0x%02X\n" + "\tRev: 0x%04X\n" + "\tSub: 0x%04X\n", + dev->chip.manufacturer, dev->chip.hci_revision, + dev->chip.hci_sub_version); + return false; + } + + /* Store needed data */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Couldn't allocate info struct\n"); + return false; + } + + /* Initialize all variables */ + INIT_LIST_HEAD(&info->open_channels); + spin_lock_init(&info->rw_lock); + info->chip_dev = dev; + + info->wq = create_singlethread_workqueue(WQ_NAME); + if (!info->wq) { + dev_err(dev->dev, "Could not create workqueue\n"); + goto err_handling_free_info; + } + + info->patch_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->patch_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffer for patch file\n"); + goto err_handling_destroy_wq; + } + + info->settings_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->settings_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffers settings file\n"); + goto err_handling_free_patch_name; + } + + dev->c_data = info; + /* Set the callbacks */ + dev->c_cb.data_from_chip = data_from_chip; + dev->c_cb.chip_removed = chip_removed, + info->chip_dev = dev; + + mutex_lock(&main_info->man_mutex); + + pf_data = dev_get_platdata(dev->dev); + btcmd_data.channel_data.bt_bus = pf_data->bus; + btacl_data.channel_data.bt_bus = pf_data->bus; + btevt_data.channel_data.bt_bus = pf_data->bus; + + for (i = 0; i < ARRAY_SIZE(stlc2690_devs); i++) + set_plat_data(&stlc2690_devs[i], dev); + for (i = 0; i < ARRAY_SIZE(stlc2690_char_devs); i++) + set_plat_data(&stlc2690_char_devs[i], dev); + mutex_unlock(&main_info->man_mutex); + + dev_info(dev->dev, "Chip supported by the STLC2690 chip driver\n"); + + /* Close the transport, which will power off the chip */ + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(dev->dev, "New main_state: STLC2690_IDLE\n"); + info->main_state = STLC2690_IDLE; + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, stlc2690_devs, + ARRAY_SIZE(stlc2690_devs), NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add stlc2690_devs (%d)\n", err); + goto err_handling_free_settings_name; + } + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, + stlc2690_char_devs, + ARRAY_SIZE(stlc2690_char_devs), NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add stlc2690_char_devs (%d)\n", + err); + goto err_handling_remove_devs; + } + + /* + * Increase base ID so next connected transport will not get the + * same device IDs. + */ + main_info->cell_base_id += MAX(ARRAY_SIZE(stlc2690_devs), + ARRAY_SIZE(stlc2690_char_devs)); + + return true; + +err_handling_remove_devs: + mfd_remove_devices(dev->dev); +err_handling_free_settings_name: + kfree(info->settings_file_name); +err_handling_free_patch_name: + kfree(info->patch_file_name); +err_handling_destroy_wq: + destroy_workqueue(info->wq); +err_handling_free_info: + kfree(info); + return false; +} + +static struct cg2900_id_callbacks chip_support_callbacks = { + .check_chip_support = check_chip_support, +}; + +/** + * stlc2690_chip_probe() - Initialize STLC2690 chip handler resources. + * @pdev: Platform device. + * + * This function initializes the STLC2690 driver, then registers to + * the CG2900 Core. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * Error codes generated by cg2900_register_chip_driver. + */ +static int __devinit stlc2690_chip_probe(struct platform_device *pdev) +{ + int err; + + dev_dbg(&pdev->dev, "stlc2690_chip_probe\n"); + + main_info = kzalloc(sizeof(*main_info), GFP_ATOMIC); + if (!main_info) { + dev_err(&pdev->dev, "Couldn't allocate main_info\n"); + return -ENOMEM; + } + + main_info->dev = &pdev->dev; + mutex_init(&main_info->man_mutex); + + err = cg2900_register_chip_driver(&chip_support_callbacks); + if (err) { + dev_err(&pdev->dev, + "Couldn't register chip driver (%d)\n", err); + goto error_handling; + } + + dev_info(&pdev->dev, "STLC2690 chip driver started\n"); + + return 0; + +error_handling: + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return err; +} + +/** + * stlc2690_chip_remove() - Release STLC2690 chip handler resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success (always success). + */ +static int __devexit stlc2690_chip_remove(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "STLC2690 chip driver removed\n"); + + cg2900_deregister_chip_driver(&chip_support_callbacks); + + if (!main_info) + return 0; + + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return 0; +} + +static struct platform_driver stlc2690_chip_driver = { + .driver = { + .name = "stlc2690-chip", + .owner = THIS_MODULE, + }, + .probe = stlc2690_chip_probe, + .remove = __devexit_p(stlc2690_chip_remove), +}; + +/** + * stlc2690_chip_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init stlc2690_chip_init(void) +{ + pr_debug("stlc2690_chip_init"); + return platform_driver_register(&stlc2690_chip_driver); +} + +/** + * stlc2690_chip_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit stlc2690_chip_exit(void) +{ + pr_debug("stlc2690_chip_exit"); + platform_driver_unregister(&stlc2690_chip_driver); +} + +module_init(stlc2690_chip_init); +module_exit(stlc2690_chip_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux STLC2690 Connectivity Device Driver"); diff --git a/drivers/staging/cg2900/mfd/stlc2690_chip.h b/drivers/staging/cg2900/mfd/stlc2690_chip.h new file mode 100644 index 00000000000..d14e7737636 --- /dev/null +++ b/drivers/staging/cg2900/mfd/stlc2690_chip.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller. + */ + +#ifndef _STLC2690_CHIP_H_ +#define _STLC2690_CHIP_H_ + +/* Supported chips */ +#define STLC2690_SUPP_MANUFACTURER 0x30 +#define STLC2690_SUPP_REVISION_MIN 0x0500 +#define STLC2690_SUPP_REVISION_MAX 0x06FF + +#define BT_SIZE_OF_HDR (sizeof(__le16) + sizeof(__u8)) +#define BT_PARAM_LEN(__pkt_len) (__pkt_len - BT_SIZE_OF_HDR) + +/* BT VS Store In FS command */ +#define STLC2690_BT_OP_VS_STORE_IN_FS 0xFC22 +struct bt_vs_store_in_fs_cmd { + __le16 opcode; + __u8 plen; + __u8 user_id; + __u8 len; + __u8 data[]; +} __packed; + +/* BT VS Write File Block command */ +#define STLC2690_BT_OP_VS_WRITE_FILE_BLOCK 0xFC2E +struct bt_vs_write_file_block_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 data[]; +} __packed; + +/* User ID for storing BD address in chip using Store_In_FS command */ +#define STLC2690_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE + +#endif /* _STLC2690_CHIP_H_ */ diff --git a/drivers/staging/cw1200/.gitignore b/drivers/staging/cw1200/.gitignore new file mode 100644 index 00000000000..6ad0d1ec58e --- /dev/null +++ b/drivers/staging/cw1200/.gitignore @@ -0,0 +1,10 @@ +*.o +*.ko +*.ko.cmd +.tmp_versions +modules.order +Module.symvers +Module.markers +*.o.cmd +*.mod.c +*.swp diff --git a/drivers/staging/cw1200/Kconfig b/drivers/staging/cw1200/Kconfig new file mode 100644 index 00000000000..4be840c1576 --- /dev/null +++ b/drivers/staging/cw1200/Kconfig @@ -0,0 +1,105 @@ +config CW1200 + tristate "CW1200 WLAN support" + select MAC80211 + select CFG80211 + help + + This is an experimental driver for the cw1200 chip-set. + Enabling this option enables the generic driver without + any platform support. + + Please select the appropriate platform below. + +if CW1200 + +config CW1200_NON_POWER_OF_TWO_BLOCKSIZES + bool "Platform supports non-power-of-two SDIO transfer" + depends on CW1200 + help + Say N here only if you are running the driver on a platform + which does not have support for non-power-of-two SDIO transfer. + If unsure, say Y. + +config CW1200_USE_GPIO_IRQ + bool "Use GPIO interrupt" + depends on CW1200 + help + Say Y here if you want to include GPIO IRQ support instead of SDIO IRQ. + If unsure, say N. + +config CW1200_5GHZ_SUPPORT + bool "5GHz band support" + depends on CW1200 + help + Say Y if your device supports 5GHz band. Should be disabled for + CW1100 silicon. + If unsure, say N. + +config CW1200_WAPI_SUPPORT + bool "WAPI support" + depends on CW1200 + help + Say Y if your compat-wireless support WAPI. + If unsure, say N. + +config CW1200_USE_STE_EXTENSIONS + bool "STE extensions" + depends on CW1200 + help + Say Y if you want to include STE extensions. + If unsure, say N. + +config CW1200_DISABLE_BEACON_HINTS + bool "Disable 11d beacon hints" + depends on CW1200 + help + Say Y if you want to disable 11d beacon hints. + If unsure, say N. + +config CW1200_U5500_SUPPORT + bool "Enable U5500 support" + depends on CW1200 + help + Say Y if you want to enable wlan on u5500 platform support. + If unsure, say N. + +menu "Driver debug features" + depends on CW1200 + +config CW1200_DEBUGFS + bool "Expose driver internals to DebugFS (DEVELOPMENT)" + +config CW1200_BH_DEBUG + bool "Enable low-level device communication logs (DEVELOPMENT)" + +config CW1200_WSM_DEBUG + bool "Enable WSM API debug messages (DEVELOPMENT)" + +config CW1200_WSM_DUMPS + bool "Verbose WSM API logging (DEVELOPMENT)" + +config CW1200_WSM_DUMPS_SHORT + bool "Dump only first x bytes (default 20) (DEVELOPMENT)" + +config CW1200_TXRX_DEBUG + bool "Enable TX/RX debug messages (DEVELOPMENT)" + +config CW1200_TX_POLICY_DEBUG + bool "Enable TX policy debug (DEVELOPMENT)" + +config CW1200_STA_DEBUG + bool "Enable STA/AP debug (DEVELOPMENT)" + +config CW1200_DUMP_ON_ERROR + bool "Dump kernel in case of critical error (DEVELOPMENT)" + +endmenu + +config CW1200_ITP + bool "Enable ITP DebugFS" + depends on CW1200 + help + Say Y if you want to include ITP code. + If unsure, say N. + +endif diff --git a/drivers/staging/cw1200/Makefile b/drivers/staging/cw1200/Makefile new file mode 100644 index 00000000000..67d7867c1b5 --- /dev/null +++ b/drivers/staging/cw1200/Makefile @@ -0,0 +1,20 @@ +cw1200_core-y := \ + fwio.o \ + txrx.o \ + main.o \ + queue.o \ + hwio.o \ + bh.o \ + wsm.o \ + sta.o \ + ap.o \ + scan.o +cw1200_core-$(CONFIG_CW1200_DEBUGFS) += debug.o +cw1200_core-$(CONFIG_CW1200_ITP) += itp.o +cw1200_core-$(CONFIG_PM) += pm.o + +cw1200_wlan-y := cw1200_sdio.o + +obj-$(CONFIG_CW1200) += cw1200_core.o +obj-$(CONFIG_CW1200) += cw1200_wlan.o + diff --git a/drivers/staging/cw1200/TODO b/drivers/staging/cw1200/TODO new file mode 100644 index 00000000000..0d2be40e1f4 --- /dev/null +++ b/drivers/staging/cw1200/TODO @@ -0,0 +1,10 @@ +TODO: + - IBSS: Not implemented (3-10 m*d). + - 11n: Almost done. WSM API upgrade is required fo finish implementation. (2-3 m*d). + - 11n: verification (??? m*d Resources? WLAN RF lab? 11n sniffers + availability? Bring up of the test equipment?). + - memory leakage verification and proper cleanup: not done (1-3 m*d). + - AP (hot-spot) mode: Implemented, some problems with WEP104/WPA/WPA2 security. + FW bug? To be investigated. + - U-APSD configuration (0.5-1 m*d). + - Cleanup of debug printouts (1 m*d). diff --git a/drivers/staging/cw1200/ap.c b/drivers/staging/cw1200/ap.c new file mode 100644 index 00000000000..55096cf0e2c --- /dev/null +++ b/drivers/staging/cw1200/ap.c @@ -0,0 +1,1149 @@ +/* + * mac80211 STA and AP API for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "cw1200.h" +#include "sta.h" +#include "ap.h" +#include "bh.h" + +#if defined(CONFIG_CW1200_STA_DEBUG) +#define ap_printk(...) printk(__VA_ARGS__) +#else +#define ap_printk(...) +#endif + +#define CW1200_LINK_ID_GC_TIMEOUT ((unsigned long)(10 * HZ)) + +#ifndef ERP_INFO_BYTE_OFFSET +#define ERP_INFO_BYTE_OFFSET 2 +#endif + +static int cw1200_upload_beacon(struct cw1200_common *priv); +static int cw1200_upload_pspoll(struct cw1200_common *priv); +static int cw1200_upload_null(struct cw1200_common *priv); +static int cw1200_start_ap(struct cw1200_common *priv); +static int cw1200_update_beaconing(struct cw1200_common *priv); +static int cw1200_enable_beaconing(struct cw1200_common *priv, + bool enable); +static void __cw1200_sta_notify(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + enum sta_notify_cmd notify_cmd, + int link_id); + +/* ******************************************************************** */ +/* AP API */ + +int cw1200_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct cw1200_common *priv = hw->priv; + struct cw1200_sta_priv *sta_priv = + (struct cw1200_sta_priv *)&sta->drv_priv; + struct cw1200_link_entry *entry; + struct sk_buff *skb; + + if (priv->mode != NL80211_IFTYPE_AP) + return 0; + + sta_priv->link_id = cw1200_find_link_id(priv, sta->addr); + if (WARN_ON(!sta_priv->link_id)) { + /* Impossible error */ + wiphy_info(priv->hw->wiphy, + "[AP] No more link IDs available.\n"); + return -ENOENT; + } + + entry = &priv->link_id_db[sta_priv->link_id - 1]; + spin_lock_bh(&priv->ps_state_lock); + if ((sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) == + IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) + priv->sta_asleep_mask |= BIT(sta_priv->link_id); + entry->status = CW1200_LINK_HARD; + while ((skb = skb_dequeue(&entry->rx_queue))) + ieee80211_rx_irqsafe(priv->hw, skb); + spin_unlock_bh(&priv->ps_state_lock); + return 0; +} + +int cw1200_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct cw1200_common *priv = hw->priv; + struct cw1200_sta_priv *sta_priv = + (struct cw1200_sta_priv *)&sta->drv_priv; + struct cw1200_link_entry *entry; + + if (priv->mode != NL80211_IFTYPE_AP || !sta_priv->link_id) + return 0; + + entry = &priv->link_id_db[sta_priv->link_id - 1]; + spin_lock_bh(&priv->ps_state_lock); + entry->status = CW1200_LINK_RESERVE; + entry->timestamp = jiffies; + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, &priv->link_id_work) <= 0) + wsm_unlock_tx(priv); + spin_unlock_bh(&priv->ps_state_lock); + flush_workqueue(priv->workqueue); + return 0; +} + +static void __cw1200_sta_notify(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + enum sta_notify_cmd notify_cmd, + int link_id) +{ + struct cw1200_common *priv = dev->priv; + u32 bit, prev; + + /* Zero link id means "for all link IDs" */ + if (link_id) + bit = BIT(link_id); + else if (WARN_ON_ONCE(notify_cmd != STA_NOTIFY_AWAKE)) + bit = 0; + else + bit = priv->link_id_map; + prev = priv->sta_asleep_mask & bit; + + switch (notify_cmd) { + case STA_NOTIFY_SLEEP: + if (!prev) { + if (priv->buffered_multicasts && + !priv->sta_asleep_mask) + queue_work(priv->workqueue, + &priv->multicast_start_work); + priv->sta_asleep_mask |= bit; + } + break; + case STA_NOTIFY_AWAKE: + if (prev) { + priv->sta_asleep_mask &= ~bit; + priv->pspoll_mask &= ~bit; + if (priv->tx_multicast && link_id && + !priv->sta_asleep_mask) + queue_work(priv->workqueue, + &priv->multicast_stop_work); + cw1200_bh_wakeup(priv); + } + break; + } +} + +void cw1200_sta_notify(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + enum sta_notify_cmd notify_cmd, + struct ieee80211_sta *sta) +{ + struct cw1200_common *priv = dev->priv; + struct cw1200_sta_priv *sta_priv = + (struct cw1200_sta_priv *)&sta->drv_priv; + + spin_lock_bh(&priv->ps_state_lock); + __cw1200_sta_notify(dev, vif, notify_cmd, sta_priv->link_id); + spin_unlock_bh(&priv->ps_state_lock); +} + +static void cw1200_ps_notify(struct cw1200_common *priv, + int link_id, bool ps) +{ + if (link_id > CW1200_MAX_STA_IN_AP_MODE) + return; + + txrx_printk(KERN_DEBUG "%s for LinkId: %d. STAs asleep: %.8X\n", + ps ? "Stop" : "Start", + link_id, priv->sta_asleep_mask); + + __cw1200_sta_notify(priv->hw, priv->vif, + ps ? STA_NOTIFY_SLEEP : STA_NOTIFY_AWAKE, link_id); +} + +static int cw1200_set_tim_impl(struct cw1200_common *priv, bool aid0_bit_set) +{ + struct sk_buff *skb; + struct wsm_update_ie update_ie = { + .what = WSM_UPDATE_IE_BEACON, + .count = 1, + }; + u16 tim_offset, tim_length; + + ap_printk(KERN_DEBUG "[AP] %s mcast: %s.\n", + __func__, aid0_bit_set ? "ena" : "dis"); + + skb = ieee80211_beacon_get_tim(priv->hw, priv->vif, + &tim_offset, &tim_length); + if (!skb) { + if (!__cw1200_flush(priv, true)) + wsm_unlock_tx(priv); + return -ENOENT; + } + + if (tim_offset && tim_length >= 6) { + /* Ignore DTIM count from mac80211: + * firmware handles DTIM internally. */ + skb->data[tim_offset + 2] = 0; + + /* Set/reset aid0 bit */ + if (aid0_bit_set) + skb->data[tim_offset + 4] |= 1; + else + skb->data[tim_offset + 4] &= ~1; + } + + update_ie.ies = &skb->data[tim_offset]; + update_ie.length = tim_length; + WARN_ON(wsm_update_ie(priv, &update_ie)); + + dev_kfree_skb(skb); + + return 0; +} + +void cw1200_set_tim_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, set_tim_work); + (void)cw1200_set_tim_impl(priv, priv->aid0_bit_set); +} + +int cw1200_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta, + bool set) +{ + struct cw1200_common *priv = dev->priv; + queue_work(priv->workqueue, &priv->set_tim_work); + return 0; +} + +void cw1200_set_cts_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, set_cts_work.work); + + u8 erp_ie[3] = {WLAN_EID_ERP_INFO, 0x1, 0}; + struct wsm_update_ie update_ie = { + .what = WSM_UPDATE_IE_BEACON, + .count = 1, + .ies = erp_ie, + .length = 3, + }; + u32 erp_info; + __le32 use_cts_prot; + mutex_lock(&priv->conf_mutex); + erp_info = priv->erp_info; + mutex_unlock(&priv->conf_mutex); + use_cts_prot = + erp_info & WLAN_ERP_USE_PROTECTION ? + __cpu_to_le32(1) : 0; + + erp_ie[ERP_INFO_BYTE_OFFSET] = erp_info; + + ap_printk(KERN_DEBUG "[STA] ERP information 0x%x\n", erp_info); + + WARN_ON(wsm_write_mib(priv, WSM_MIB_ID_NON_ERP_PROTECTION, + &use_cts_prot, sizeof(use_cts_prot))); + WARN_ON(wsm_update_ie(priv, &update_ie)); + + return; +} + +static int cw1200_set_btcoexinfo(struct cw1200_common *priv) +{ + struct wsm_override_internal_txrate arg; + int ret = 0; + + if (priv->mode == NL80211_IFTYPE_STATION) { + /* Plumb PSPOLL and NULL template */ + WARN_ON(cw1200_upload_pspoll(priv)); + WARN_ON(cw1200_upload_null(priv)); + } else { + return 0; + } + + memset(&arg, 0, sizeof(struct wsm_override_internal_txrate)); + + if (!priv->vif->p2p) { + /* STATION mode */ + if (priv->bss_params.operationalRateSet & ~0xF) { + ap_printk(KERN_DEBUG "[STA] STA has ERP rates\n"); + /* G or BG mode */ + arg.internalTxRate = (__ffs( + priv->bss_params.operationalRateSet & ~0xF)); + } else { + ap_printk(KERN_DEBUG "[STA] STA has non ERP rates\n"); + /* B only mode */ + arg.internalTxRate = (__ffs( + priv->association_mode.basicRateSet)); + } + arg.nonErpInternalTxRate = (__ffs( + priv->association_mode.basicRateSet)); + } else { + /* P2P mode */ + arg.internalTxRate = (__ffs( + priv->bss_params.operationalRateSet & ~0xF)); + arg.nonErpInternalTxRate = (__ffs( + priv->bss_params.operationalRateSet & ~0xF)); + } + + ap_printk(KERN_DEBUG "[STA] BTCOEX_INFO" + "MODE %d, internalTxRate : %x, nonErpInternalTxRate: %x\n", + priv->mode, + arg.internalTxRate, + arg.nonErpInternalTxRate); + + ret = WARN_ON(wsm_write_mib(priv, WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE, + &arg, sizeof(arg))); + + return ret; +} + +void cw1200_bss_info_changed(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + struct cw1200_common *priv = dev->priv; + + mutex_lock(&priv->conf_mutex); + if (changed & BSS_CHANGED_BSSID) { + memcpy(priv->bssid, info->bssid, ETH_ALEN); + cw1200_setup_mac(priv); + } + + /* TODO: BSS_CHANGED_IBSS */ + + if (changed & BSS_CHANGED_ARP_FILTER) { + struct wsm_arp_ipv4_filter filter = {0}; + int i; + + ap_printk(KERN_DEBUG "[STA] BSS_CHANGED_ARP_FILTER " + "enabled: %d, cnt: %d\n", + info->arp_filter_enabled, + info->arp_addr_cnt); + + if (info->arp_filter_enabled) + filter.enable = __cpu_to_le32(1); + + /* Currently only one IP address is supported by firmware. + * In case of more IPs arp filtering will be disabled. */ + if (info->arp_addr_cnt > 0 && + info->arp_addr_cnt <= WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES) { + for (i = 0; i < info->arp_addr_cnt; i++) { + filter.ipv4Address[i] = info->arp_addr_list[i]; + ap_printk(KERN_DEBUG "[STA] addr[%d]: 0x%X\n", + i, filter.ipv4Address[i]); + } + } else + filter.enable = 0; + + ap_printk(KERN_DEBUG "[STA] arp ip filter enable: %d\n", + __le32_to_cpu(filter.enable)); + + if (wsm_set_arp_ipv4_filter(priv, &filter)) + WARN_ON(1); + } + + + if (changed & BSS_CHANGED_BEACON) { + ap_printk(KERN_DEBUG "BSS_CHANGED_BEACON\n"); + WARN_ON(cw1200_update_beaconing(priv)); + WARN_ON(cw1200_upload_beacon(priv)); + } + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + ap_printk(KERN_DEBUG "BSS_CHANGED_BEACON_ENABLED\n"); + + if (priv->enable_beacon != info->enable_beacon) { + WARN_ON(cw1200_enable_beaconing(priv, + info->enable_beacon)); + priv->enable_beacon = info->enable_beacon; + } + } + + if (changed & BSS_CHANGED_BEACON_INT) { + ap_printk(KERN_DEBUG "CHANGED_BEACON_INT\n"); + /* Restart AP only when connected */ + if (priv->join_status == CW1200_JOIN_STATUS_AP) + WARN_ON(cw1200_update_beaconing(priv)); + } + + + if (changed & BSS_CHANGED_ASSOC) { + wsm_lock_tx(priv); + priv->wep_default_key_id = -1; + wsm_unlock_tx(priv); + + if (!info->assoc /* && !info->ibss_joined */) { + priv->cqm_link_loss_count = 60; + priv->cqm_beacon_loss_count = 20; + priv->cqm_tx_failure_thold = 0; + } + priv->cqm_tx_failure_count = 0; + } + + if (changed & + (BSS_CHANGED_ASSOC | + BSS_CHANGED_BASIC_RATES | + BSS_CHANGED_ERP_PREAMBLE | + BSS_CHANGED_HT | + BSS_CHANGED_ERP_SLOT)) { + ap_printk(KERN_DEBUG "BSS_CHANGED_ASSOC.\n"); + if (info->assoc) { /* TODO: ibss_joined */ + struct ieee80211_sta *sta = NULL; + priv->join_dtim_period = info->dtim_period; + priv->beacon_int = info->beacon_int; + + /* Associated: kill join timeout */ + cancel_delayed_work_sync(&priv->join_timeout); + + rcu_read_lock(); + if (info->bssid) + sta = ieee80211_find_sta(vif, info->bssid); + if (sta) { + BUG_ON(!priv->channel); + priv->ht_info.ht_cap = sta->ht_cap; + priv->bss_params.operationalRateSet = + __cpu_to_le32( + cw1200_rate_mask_to_wsm(priv, + sta->supp_rates[priv->channel->band])); + priv->ht_info.channel_type = + info->channel_type; + priv->ht_info.operation_mode = + info->ht_operation_mode; + } else { + memset(&priv->ht_info, 0, + sizeof(priv->ht_info)); + priv->bss_params.operationalRateSet = -1; + } + rcu_read_unlock(); + + if (sta) { + __le32 val = 0; + if (priv->ht_info.operation_mode & + IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT) { + ap_printk(KERN_DEBUG"[STA]" + " Non-GF STA present\n"); + /* Non Green field capable STA */ + val = __cpu_to_le32(BIT(1)); + } + WARN_ON(wsm_write_mib(priv, + WSM_MID_ID_SET_HT_PROTECTION, + &val, sizeof(val))); + } + + priv->association_mode.greenfieldMode = + cw1200_ht_greenfield(&priv->ht_info); + priv->association_mode.flags = + WSM_ASSOCIATION_MODE_SNOOP_ASSOC_FRAMES | + WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE | + WSM_ASSOCIATION_MODE_USE_HT_MODE | + WSM_ASSOCIATION_MODE_USE_BASIC_RATE_SET | + WSM_ASSOCIATION_MODE_USE_MPDU_START_SPACING; + priv->association_mode.preambleType = + info->use_short_preamble ? + WSM_JOIN_PREAMBLE_SHORT : + WSM_JOIN_PREAMBLE_LONG; + priv->association_mode.basicRateSet = __cpu_to_le32( + cw1200_rate_mask_to_wsm(priv, + info->basic_rates)); + priv->association_mode.mpduStartSpacing = + cw1200_ht_ampdu_density(&priv->ht_info); + +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + priv->cqm_beacon_loss_count = + info->cqm_beacon_miss_thold; + priv->cqm_tx_failure_thold = + info->cqm_tx_fail_thold; + priv->cqm_tx_failure_count = 0; + cancel_delayed_work_sync(&priv->bss_loss_work); + cancel_delayed_work_sync(&priv->connection_loss_work); +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ + + priv->bss_params.beaconLostCount = + priv->cqm_beacon_loss_count ? + priv->cqm_beacon_loss_count : + priv->cqm_link_loss_count; + + priv->bss_params.aid = info->aid; + + if (priv->join_dtim_period < 1) + priv->join_dtim_period = 1; + + ap_printk(KERN_DEBUG "[STA] DTIM %d, interval: %d\n", + priv->join_dtim_period, priv->beacon_int); + ap_printk(KERN_DEBUG "[STA] Preamble: %d, " \ + "Greenfield: %d, Aid: %d, " \ + "Rates: 0x%.8X, Basic: 0x%.8X\n", + priv->association_mode.preambleType, + priv->association_mode.greenfieldMode, + priv->bss_params.aid, + priv->bss_params.operationalRateSet, + priv->association_mode.basicRateSet); + WARN_ON(wsm_set_association_mode(priv, + &priv->association_mode)); + WARN_ON(wsm_keep_alive_period(priv, 30 /* sec */)); + WARN_ON(wsm_set_bss_params(priv, &priv->bss_params)); + priv->setbssparams_done = true; + WARN_ON(wsm_set_beacon_wakeup_period(priv, + priv->beacon_int * priv->join_dtim_period > + MAX_BEACON_SKIP_TIME_MS ? 1 : + priv->join_dtim_period, 0)); + + cw1200_set_pm(priv, &priv->powersave_mode); + if (priv->vif->p2p) { + ap_printk(KERN_DEBUG + "[STA] Setting p2p powersave " + "configuration.\n"); + WARN_ON(wsm_set_p2p_ps_modeinfo(priv, + &priv->p2p_ps_modeinfo)); + } + + if (priv->is_BT_Present) + WARN_ON(cw1200_set_btcoexinfo(priv)); +#if 0 + /* It's better to override internal TX rete; otherwise + * device sends RTS at too high rate. However device + * can't receive CTS at 1 and 2 Mbps. Well, 5.5 is a + * good choice for RTS/CTS, but that means PS poll + * will be sent at the same rate - impact on link + * budget. Not sure what is better.. */ + + /* Update: internal rate selection algorythm is not + * bad: if device is not receiving CTS at high rate, + * it drops RTS rate. + * So, conclusion: if-0 the code. Keep code just for + * information: + * Do not touch WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE! */ + + /* ~3 is a bug in device: RTS/CTS is not working at + * low rates */ + + __le32 internal_tx_rate = __cpu_to_le32(__ffs( + priv->association_mode.basicRateSet & ~3)); + WARN_ON(wsm_write_mib(priv, + WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE, + &internal_tx_rate, + sizeof(internal_tx_rate))); +#endif + } else { + memset(&priv->association_mode, 0, + sizeof(priv->association_mode)); + memset(&priv->bss_params, 0, sizeof(priv->bss_params)); + } + } + if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_ERP_CTS_PROT)) { + u32 prev_erp_info = priv->erp_info; + + if (info->use_cts_prot) + priv->erp_info |= WLAN_ERP_USE_PROTECTION; + else if (!(prev_erp_info & WLAN_ERP_NON_ERP_PRESENT)) + priv->erp_info &= ~WLAN_ERP_USE_PROTECTION; + + if (prev_erp_info != priv->erp_info) + queue_delayed_work(priv->workqueue, + &priv->set_cts_work, 0*HZ); + } + + if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_ERP_SLOT)) { + __le32 slot_time = info->use_short_slot ? + __cpu_to_le32(9) : __cpu_to_le32(20); + ap_printk(KERN_DEBUG "[STA] Slot time :%d us.\n", + __le32_to_cpu(slot_time)); + WARN_ON(wsm_write_mib(priv, WSM_MIB_ID_DOT11_SLOT_TIME, + &slot_time, sizeof(slot_time))); + } + if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_CQM)) { + struct wsm_rcpi_rssi_threshold threshold = { + .rollingAverageCount = 8, + }; + +#if 0 + /* For verification purposes */ + info->cqm_rssi_thold = -50; + info->cqm_rssi_hyst = 4; +#endif /* 0 */ + + ap_printk(KERN_DEBUG "[CQM] RSSI threshold " + "subscribe: %d +- %d\n", + info->cqm_rssi_thold, info->cqm_rssi_hyst); +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + ap_printk(KERN_DEBUG "[CQM] Beacon loss subscribe: %d\n", + info->cqm_beacon_miss_thold); + ap_printk(KERN_DEBUG "[CQM] TX failure subscribe: %d\n", + info->cqm_tx_fail_thold); + priv->cqm_rssi_thold = info->cqm_rssi_thold; + priv->cqm_rssi_hyst = info->cqm_rssi_hyst; +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ + if (info->cqm_rssi_thold || info->cqm_rssi_hyst) { + /* RSSI subscription enabled */ + /* TODO: It's not a correct way of setting threshold. + * Upper and lower must be set equal here and adjusted + * in callback. However current implementation is much + * more relaible and stable. */ + threshold.upperThreshold = + info->cqm_rssi_thold + info->cqm_rssi_hyst; + threshold.lowerThreshold = + info->cqm_rssi_thold; + threshold.rssiRcpiMode |= + WSM_RCPI_RSSI_THRESHOLD_ENABLE; + } else { + /* There is a bug in FW, see sta.c. We have to enable + * dummy subscription to get correct RSSI values. */ + threshold.rssiRcpiMode |= + WSM_RCPI_RSSI_THRESHOLD_ENABLE | + WSM_RCPI_RSSI_DONT_USE_UPPER | + WSM_RCPI_RSSI_DONT_USE_LOWER; + } + WARN_ON(wsm_set_rcpi_rssi_threshold(priv, &threshold)); + +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + priv->cqm_tx_failure_thold = info->cqm_tx_fail_thold; + priv->cqm_tx_failure_count = 0; + + if (priv->cqm_beacon_loss_count != + info->cqm_beacon_miss_thold) { + priv->cqm_beacon_loss_count = + info->cqm_beacon_miss_thold; + priv->bss_params.beaconLostCount = + priv->cqm_beacon_loss_count ? + priv->cqm_beacon_loss_count : + priv->cqm_link_loss_count; + /* Make sure we are associated before sending + * set_bss_params to firmware */ + if (priv->bss_params.aid) { + WARN_ON(wsm_set_bss_params(priv, + &priv->bss_params)); + priv->setbssparams_done = true; + } + } +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ + } + mutex_unlock(&priv->conf_mutex); +} + +void cw1200_multicast_start_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, multicast_start_work); + long tmo = priv->join_dtim_period * + (priv->beacon_int + 20) * HZ / 1024; + + cancel_work_sync(&priv->multicast_stop_work); + + if (!priv->aid0_bit_set) { + wsm_lock_tx(priv); + cw1200_set_tim_impl(priv, true); + priv->aid0_bit_set = true; + mod_timer(&priv->mcast_timeout, jiffies + tmo); + wsm_unlock_tx(priv); + } +} + +void cw1200_multicast_stop_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, multicast_stop_work); + + if (priv->aid0_bit_set) { + del_timer_sync(&priv->mcast_timeout); + wsm_lock_tx(priv); + priv->aid0_bit_set = false; + cw1200_set_tim_impl(priv, false); + wsm_unlock_tx(priv); + } +} + +void cw1200_mcast_timeout(unsigned long arg) +{ + struct cw1200_common *priv = + (struct cw1200_common *)arg; + + wiphy_warn(priv->hw->wiphy, + "Multicast delivery timeout.\n"); + spin_lock_bh(&priv->ps_state_lock); + priv->tx_multicast = priv->aid0_bit_set && + priv->buffered_multicasts; + if (priv->tx_multicast) + cw1200_bh_wakeup(priv); + spin_unlock_bh(&priv->ps_state_lock); +} + +int cw1200_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size) +{ + /* Aggregation is implemented fully in firmware, + * including block ack negotiation. Do not allow + * mac80211 stack to do anything: it interferes with + * the firmware. */ + return -ENOTSUPP; +} + +/* ******************************************************************** */ +/* WSM callback */ +void cw1200_suspend_resume(struct cw1200_common *priv, + struct wsm_suspend_resume *arg) +{ + ap_printk(KERN_DEBUG "[AP] %s: %s\n", + arg->stop ? "stop" : "start", + arg->multicast ? "broadcast" : "unicast"); + + if (arg->multicast) { + bool cancel_tmo = false; + spin_lock_bh(&priv->ps_state_lock); + if (arg->stop) { + priv->tx_multicast = false; + } else { + /* Firmware sends this indication every DTIM if there + * is a STA in powersave connected. There is no reason + * to suspend, following wakeup will consume much more + * power than it could be saved. */ + cw1200_pm_stay_awake(&priv->pm_state, + priv->join_dtim_period * + (priv->beacon_int + 20) * HZ / 1024); + priv->tx_multicast = priv->aid0_bit_set && + priv->buffered_multicasts; + if (priv->tx_multicast) { + cancel_tmo = true; + cw1200_bh_wakeup(priv); + } + } + spin_unlock_bh(&priv->ps_state_lock); + if (cancel_tmo) + del_timer_sync(&priv->mcast_timeout); + } else { + spin_lock_bh(&priv->ps_state_lock); + cw1200_ps_notify(priv, arg->link_id, arg->stop); + spin_unlock_bh(&priv->ps_state_lock); + if (!arg->stop) + cw1200_bh_wakeup(priv); + } + return; +} + +/* ******************************************************************** */ +/* AP privates */ + +static int cw1200_upload_beacon(struct cw1200_common *priv) +{ + int ret = 0; + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_BEACON, + }; + struct ieee80211_mgmt *mgmt; + u8 *erp_inf, *ies; + u32 ies_len; + + if (priv->vif->p2p) + frame.rate = WSM_TRANSMIT_RATE_6; + + frame.skb = ieee80211_beacon_get(priv->hw, priv->vif); + if (WARN_ON(!frame.skb)) + return -ENOMEM; + + mgmt = (void *)frame.skb->data; + ies = mgmt->u.beacon.variable; + ies_len = frame.skb->len - (u32)(ies - (u8 *)mgmt); + erp_inf = (u8 *)cfg80211_find_ie(WLAN_EID_ERP_INFO, ies, ies_len); + if (erp_inf) { + if (erp_inf[ERP_INFO_BYTE_OFFSET] + & WLAN_ERP_BARKER_PREAMBLE) + priv->erp_info |= WLAN_ERP_BARKER_PREAMBLE; + else + priv->erp_info &= ~WLAN_ERP_BARKER_PREAMBLE; + + if (erp_inf[ERP_INFO_BYTE_OFFSET] + & WLAN_ERP_NON_ERP_PRESENT) { + priv->erp_info |= WLAN_ERP_USE_PROTECTION; + priv->erp_info |= WLAN_ERP_NON_ERP_PRESENT; + } else { + priv->erp_info &= ~WLAN_ERP_USE_PROTECTION; + priv->erp_info &= ~WLAN_ERP_NON_ERP_PRESENT; + } + } + + ret = wsm_set_template_frame(priv, &frame); + if (!ret) { + /* TODO: Distille probe resp; remove TIM + * and other beacon-specific IEs */ + *(__le16 *)frame.skb->data = + __cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_PROBE_RESP); + frame.frame_type = WSM_FRAME_TYPE_PROBE_RESPONSE; + if (priv->vif->p2p) + frame.disable = true; + ret = wsm_set_template_frame(priv, &frame); + } + dev_kfree_skb(frame.skb); + + return ret; +} + +static int cw1200_upload_pspoll(struct cw1200_common *priv) +{ + int ret = 0; + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_PS_POLL, + .rate = 0xFF, + }; + + + frame.skb = ieee80211_pspoll_get(priv->hw, priv->vif); + if (WARN_ON(!frame.skb)) + return -ENOMEM; + + ret = wsm_set_template_frame(priv, &frame); + + dev_kfree_skb(frame.skb); + + return ret; +} + +static int cw1200_upload_null(struct cw1200_common *priv) +{ + int ret = 0; + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_NULL, + .rate = 0xFF, + }; + + + frame.skb = ieee80211_nullfunc_get(priv->hw, priv->vif); + if (WARN_ON(!frame.skb)) + return -ENOMEM; + + ret = wsm_set_template_frame(priv, &frame); + + dev_kfree_skb(frame.skb); + + return ret; +} + +static int cw1200_enable_beaconing(struct cw1200_common *priv, + bool enable) +{ + struct wsm_beacon_transmit transmit = { + .enableBeaconing = enable, + }; + + return wsm_beacon_transmit(priv, &transmit); +} + +static int cw1200_start_ap(struct cw1200_common *priv) +{ + int ret; + const u8 *ssidie; + struct sk_buff *skb; + int offset; + struct ieee80211_bss_conf *conf = &priv->vif->bss_conf; + struct wsm_start start = { + .mode = priv->vif->p2p ? + WSM_START_MODE_P2P_GO : WSM_START_MODE_AP, + .band = (priv->channel->band == IEEE80211_BAND_5GHZ) ? + WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G, + .channelNumber = priv->channel->hw_value, + .beaconInterval = conf->beacon_int, + .DTIMPeriod = conf->dtim_period, + .preambleType = conf->use_short_preamble ? + WSM_JOIN_PREAMBLE_SHORT : + WSM_JOIN_PREAMBLE_LONG, + .probeDelay = 100, + .basicRateSet = cw1200_rate_mask_to_wsm(priv, + conf->basic_rates), + }; + struct wsm_operational_mode mode = { + .power_mode = wsm_power_mode_quiescent, + .disableMoreFlagUsage = true, + }; + + /* Get SSID */ + skb = ieee80211_beacon_get(priv->hw, priv->vif); + if (WARN_ON(!skb)) + return -ENOMEM; + + offset = offsetof(struct ieee80211_mgmt, u.beacon.variable); + ssidie = cfg80211_find_ie(WLAN_EID_SSID, skb->data + offset, + skb->len - offset); + + memset(priv->ssid, 0, sizeof(priv->ssid)); + if (ssidie) { + priv->ssid_length = ssidie[1]; + if (WARN_ON(priv->ssid_length > sizeof(priv->ssid))) + priv->ssid_length = sizeof(priv->ssid); + memcpy(priv->ssid, &ssidie[2], priv->ssid_length); + } else { + priv->ssid_length = 0; + } + dev_kfree_skb(skb); + + priv->beacon_int = conf->beacon_int; + priv->join_dtim_period = conf->dtim_period; + + start.ssidLength = priv->ssid_length; + memcpy(&start.ssid[0], priv->ssid, start.ssidLength); + + memset(&priv->link_id_db, 0, sizeof(priv->link_id_db)); + + ap_printk(KERN_DEBUG "[AP] ch: %d(%d), bcn: %d(%d), " + "brt: 0x%.8X, ssid: %.*s.\n", + start.channelNumber, start.band, + start.beaconInterval, start.DTIMPeriod, + start.basicRateSet, + start.ssidLength, start.ssid); + ret = WARN_ON(wsm_start(priv, &start)); + if (!ret) + ret = WARN_ON(cw1200_upload_keys(priv)); + if (!ret && priv->vif->p2p) { + ap_printk(KERN_DEBUG + "[AP] Setting p2p powersave " + "configuration.\n"); + WARN_ON(wsm_set_p2p_ps_modeinfo(priv, + &priv->p2p_ps_modeinfo)); + } + if (!ret) { + WARN_ON(wsm_set_block_ack_policy(priv, + 0, 0)); + priv->join_status = CW1200_JOIN_STATUS_AP; + cw1200_update_filtering(priv); + } + WARN_ON(wsm_set_operational_mode(priv, &mode)); + return ret; +} + +static int cw1200_update_beaconing(struct cw1200_common *priv) +{ + struct ieee80211_bss_conf *conf = &priv->vif->bss_conf; + struct wsm_reset reset = { + .link_id = 0, + .reset_statistics = true, + }; + + if (priv->mode == NL80211_IFTYPE_AP) { + /* TODO: check if changed channel, band */ + if (priv->join_status != CW1200_JOIN_STATUS_AP || + priv->beacon_int != conf->beacon_int) { + ap_printk(KERN_DEBUG "ap restarting\n"); + wsm_lock_tx(priv); + if (priv->join_status != CW1200_JOIN_STATUS_PASSIVE) + WARN_ON(wsm_reset(priv, &reset)); + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + WARN_ON(cw1200_start_ap(priv)); + wsm_unlock_tx(priv); + } else + ap_printk(KERN_DEBUG "ap started join_status: %d\n", + priv->join_status); + } + return 0; +} + +int cw1200_find_link_id(struct cw1200_common *priv, const u8 *mac) +{ + int i, ret = 0; + spin_lock_bh(&priv->ps_state_lock); + for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) { + if (!memcmp(mac, priv->link_id_db[i].mac, ETH_ALEN) && + priv->link_id_db[i].status) { + priv->link_id_db[i].timestamp = jiffies; + ret = i + 1; + break; + } + } + spin_unlock_bh(&priv->ps_state_lock); + return ret; +} + +int cw1200_alloc_link_id(struct cw1200_common *priv, const u8 *mac) +{ + int i, ret = 0; + unsigned long max_inactivity = 0; + unsigned long now = jiffies; + + spin_lock_bh(&priv->ps_state_lock); + for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) { + if (!priv->link_id_db[i].status) { + ret = i + 1; + break; + } else if (priv->link_id_db[i].status != CW1200_LINK_HARD && + !priv->tx_queue_stats.link_map_cache[i + 1]) { + + unsigned long inactivity = + now - priv->link_id_db[i].timestamp; + if (inactivity < max_inactivity) + continue; + max_inactivity = inactivity; + ret = i + 1; + } + } + if (ret) { + struct cw1200_link_entry *entry = &priv->link_id_db[ret - 1]; + ap_printk(KERN_DEBUG "[AP] STA added, link_id: %d\n", + ret); + entry->status = CW1200_LINK_RESERVE; + memcpy(&entry->mac, mac, ETH_ALEN); + memset(&entry->buffered, 0, CW1200_MAX_TID); + skb_queue_head_init(&entry->rx_queue); + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, &priv->link_id_work) <= 0) + wsm_unlock_tx(priv); + } else { + wiphy_info(priv->hw->wiphy, + "[AP] Early: no more link IDs available.\n"); + } + + spin_unlock_bh(&priv->ps_state_lock); + return ret; +} + +void cw1200_link_id_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, link_id_work); + wsm_flush_tx(priv); + cw1200_link_id_gc_work(&priv->link_id_gc_work.work); + wsm_unlock_tx(priv); +} + +void cw1200_link_id_gc_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, link_id_gc_work.work); + struct wsm_reset reset = { + .reset_statistics = false, + }; + struct wsm_map_link map_link = { + .link_id = 0, + }; + unsigned long now = jiffies; + unsigned long next_gc = -1; + long ttl; + bool need_reset; + u32 mask; + int i; + + if (priv->join_status != CW1200_JOIN_STATUS_AP) + return; + + wsm_lock_tx(priv); + spin_lock_bh(&priv->ps_state_lock); + for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) { + need_reset = false; + mask = BIT(i + 1); + if (priv->link_id_db[i].status == CW1200_LINK_RESERVE || + (priv->link_id_db[i].status == CW1200_LINK_HARD && + !(priv->link_id_map & mask))) { + if (priv->link_id_map & mask) { + priv->sta_asleep_mask &= ~mask; + priv->pspoll_mask &= ~mask; + need_reset = true; + } + priv->link_id_map |= mask; + if (priv->link_id_db[i].status != CW1200_LINK_HARD) + priv->link_id_db[i].status = CW1200_LINK_SOFT; + memcpy(map_link.mac_addr, priv->link_id_db[i].mac, + ETH_ALEN); + spin_unlock_bh(&priv->ps_state_lock); + if (need_reset) { + reset.link_id = i + 1; + WARN_ON(wsm_reset(priv, &reset)); + } + map_link.link_id = i + 1; + WARN_ON(wsm_map_link(priv, &map_link)); + next_gc = min(next_gc, CW1200_LINK_ID_GC_TIMEOUT); + spin_lock_bh(&priv->ps_state_lock); + } else if (priv->link_id_db[i].status == CW1200_LINK_SOFT) { + ttl = priv->link_id_db[i].timestamp - now + + CW1200_LINK_ID_GC_TIMEOUT; + if (ttl <= 0) { + need_reset = true; + priv->link_id_db[i].status = CW1200_LINK_OFF; + priv->link_id_map &= ~mask; + priv->sta_asleep_mask &= ~mask; + priv->pspoll_mask &= ~mask; + memset(map_link.mac_addr, 0, ETH_ALEN); + spin_unlock_bh(&priv->ps_state_lock); + reset.link_id = i + 1; + WARN_ON(wsm_reset(priv, &reset)); + spin_lock_bh(&priv->ps_state_lock); + } else { + next_gc = min_t(unsigned long, next_gc, ttl); + } +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + } else if (priv->link_id_db[i].status == CW1200_LINK_RESET || + priv->link_id_db[i].status == + CW1200_LINK_RESET_REMAP) { + int status = priv->link_id_db[i].status; + priv->link_id_db[i].status = + priv->link_id_db[i].prev_status; + priv->link_id_db[i].timestamp = now; + reset.link_id = i + 1; + spin_unlock_bh(&priv->ps_state_lock); + WARN_ON(wsm_reset(priv, &reset)); + if (status == CW1200_LINK_RESET_REMAP) { + memcpy(map_link.mac_addr, + priv->link_id_db[i].mac, + ETH_ALEN); + map_link.link_id = i + 1; + WARN_ON(wsm_map_link(priv, &map_link)); + next_gc = min(next_gc, + CW1200_LINK_ID_GC_TIMEOUT); + } + spin_lock_bh(&priv->ps_state_lock); +#endif + } + if (need_reset) { + skb_queue_purge(&priv->link_id_db[i].rx_queue); + ap_printk(KERN_DEBUG "[AP] STA removed, link_id: %d\n", + reset.link_id); + } + } + spin_unlock_bh(&priv->ps_state_lock); + if (next_gc != -1) + queue_delayed_work(priv->workqueue, + &priv->link_id_gc_work, next_gc); + wsm_unlock_tx(priv); +} + +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) +void cw1200_notify_noa(struct cw1200_common *priv, int delay) +{ + struct cfg80211_p2p_ps p2p_ps = {0}; + struct wsm_p2p_ps_modeinfo *modeinfo; + modeinfo = &priv->p2p_ps_modeinfo; + + ap_printk(KERN_DEBUG "[AP]: %s called\n", __func__); + + if (priv->join_status != CW1200_JOIN_STATUS_AP) + return; + + if (delay) + msleep(delay); + + if (!WARN_ON(wsm_get_p2p_ps_modeinfo(priv, modeinfo))) { +#if defined(CONFIG_CW1200_STA_DEBUG) + print_hex_dump_bytes("[AP] p2p_get_ps_modeinfo: ", + DUMP_PREFIX_NONE, + (u8 *)modeinfo, + sizeof(*modeinfo)); +#endif /* CONFIG_CW1200_STA_DEBUG */ + p2p_ps.opp_ps = !!(modeinfo->oppPsCTWindow & BIT(7)); + p2p_ps.ctwindow = modeinfo->oppPsCTWindow & (~BIT(7)); + p2p_ps.count = modeinfo->count; + p2p_ps.start = __le32_to_cpu(modeinfo->startTime); + p2p_ps.duration = __le32_to_cpu(modeinfo->duration); + p2p_ps.interval = __le32_to_cpu(modeinfo->interval); + p2p_ps.index = modeinfo->reserved; + + ieee80211_p2p_noa_notify(priv->vif, + &p2p_ps, + GFP_KERNEL); + } +} +#endif diff --git a/drivers/staging/cw1200/ap.h b/drivers/staging/cw1200/ap.h new file mode 100644 index 00000000000..c10e4ef16d2 --- /dev/null +++ b/drivers/staging/cw1200/ap.h @@ -0,0 +1,49 @@ +/* + * mac80211 STA and AP API for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef AP_H_INCLUDED +#define AP_H_INCLUDED + +int cw1200_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta, + bool set); +int cw1200_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +int cw1200_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +void cw1200_sta_notify(struct ieee80211_hw *dev, struct ieee80211_vif *vif, + enum sta_notify_cmd notify_cmd, + struct ieee80211_sta *sta); +void cw1200_bss_info_changed(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed); +int cw1200_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size); + +void cw1200_suspend_resume(struct cw1200_common *priv, + struct wsm_suspend_resume *arg); +void cw1200_set_tim_work(struct work_struct *work); +void cw1200_set_cts_work(struct work_struct *work); +void cw1200_multicast_start_work(struct work_struct *work); +void cw1200_multicast_stop_work(struct work_struct *work); +void cw1200_mcast_timeout(unsigned long arg); +int cw1200_find_link_id(struct cw1200_common *priv, const u8 *mac); +int cw1200_alloc_link_id(struct cw1200_common *priv, const u8 *mac); +void cw1200_link_id_work(struct work_struct *work); +void cw1200_link_id_gc_work(struct work_struct *work); +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) +void cw1200_notify_noa(struct cw1200_common *priv, int delay); +#endif + +#endif diff --git a/drivers/staging/cw1200/bh.c b/drivers/staging/cw1200/bh.c new file mode 100644 index 00000000000..427f3e2ba52 --- /dev/null +++ b/drivers/staging/cw1200/bh.c @@ -0,0 +1,622 @@ +/* + * Device handling thread implementation for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * Based on: + * ST-Ericsson UMAC CW1200 driver, which is + * Copyright (c) 2010, ST-Ericsson + * Author: Ajitpal Singh <ajitpal.singh@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <net/mac80211.h> +#include <linux/kthread.h> + +#include "cw1200.h" +#include "bh.h" +#include "hwio.h" +#include "wsm.h" +#include "sbus.h" +#include "debug.h" + +#if defined(CONFIG_CW1200_BH_DEBUG) +#define bh_printk(...) printk(__VA_ARGS__) +#else +#define bh_printk(...) +#endif + +static int cw1200_bh(void *arg); + +/* TODO: Verify these numbers with WSM specification. */ +#define DOWNLOAD_BLOCK_SIZE_WR (0x1000 - 4) +/* an SPI message cannot be bigger than (2"12-1)*2 bytes + * "*2" to cvt to bytes */ +#define MAX_SZ_RD_WR_BUFFERS (DOWNLOAD_BLOCK_SIZE_WR*2) +#define PIGGYBACK_CTRL_REG (2) +#define EFFECTIVE_BUF_SIZE (MAX_SZ_RD_WR_BUFFERS - PIGGYBACK_CTRL_REG) + +/* Suspend state privates */ +enum cw1200_bh_pm_state { + CW1200_BH_RESUMED = 0, + CW1200_BH_SUSPEND, + CW1200_BH_SUSPENDED, + CW1200_BH_RESUME, +}; + +typedef int (*cw1200_wsm_handler)(struct cw1200_common *priv, + u8 *data, size_t size); + + +int cw1200_register_bh(struct cw1200_common *priv) +{ + int err = 0; + struct sched_param param = { .sched_priority = 1 }; + bh_printk(KERN_DEBUG "[BH] register.\n"); + BUG_ON(priv->bh_thread); + atomic_set(&priv->bh_rx, 0); + atomic_set(&priv->bh_tx, 0); + atomic_set(&priv->bh_term, 0); + atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED); + priv->buf_id_tx = 0; + priv->buf_id_rx = 0; + init_waitqueue_head(&priv->bh_wq); + init_waitqueue_head(&priv->bh_evt_wq); + priv->bh_thread = kthread_create(&cw1200_bh, priv, "cw1200_bh"); + if (IS_ERR(priv->bh_thread)) { + err = PTR_ERR(priv->bh_thread); + priv->bh_thread = NULL; + } else { + WARN_ON(sched_setscheduler(priv->bh_thread, + SCHED_FIFO, ¶m)); +#ifdef HAS_PUT_TASK_STRUCT + get_task_struct(priv->bh_thread); +#endif + wake_up_process(priv->bh_thread); + } + return err; +} + +void cw1200_unregister_bh(struct cw1200_common *priv) +{ + struct task_struct *thread = priv->bh_thread; + if (WARN_ON(!thread)) + return; + + priv->bh_thread = NULL; + bh_printk(KERN_DEBUG "[BH] unregister.\n"); + atomic_add(1, &priv->bh_term); + wake_up(&priv->bh_wq); + kthread_stop(thread); +#ifdef HAS_PUT_TASK_STRUCT + put_task_struct(thread); +#endif +} + +void cw1200_irq_handler(struct cw1200_common *priv) +{ + bh_printk(KERN_DEBUG "[BH] irq.\n"); + if (/* WARN_ON */(priv->bh_error)) + return; + + if (atomic_add_return(1, &priv->bh_rx) == 1) + wake_up(&priv->bh_wq); +} + +void cw1200_bh_wakeup(struct cw1200_common *priv) +{ + bh_printk(KERN_DEBUG "[BH] wakeup.\n"); + if (WARN_ON(priv->bh_error)) + return; + + if (atomic_add_return(1, &priv->bh_tx) == 1) + wake_up(&priv->bh_wq); +} + +int cw1200_bh_suspend(struct cw1200_common *priv) +{ + bh_printk(KERN_DEBUG "[BH] suspend.\n"); + if (WARN_ON(priv->bh_error)) + return 0; + + atomic_set(&priv->bh_suspend, CW1200_BH_SUSPEND); + wake_up(&priv->bh_wq); + return wait_event_timeout(priv->bh_evt_wq, priv->bh_error || + (CW1200_BH_SUSPENDED == atomic_read(&priv->bh_suspend)), + 1 * HZ) ? 0 : -ETIMEDOUT; +} + +int cw1200_bh_resume(struct cw1200_common *priv) +{ + bh_printk(KERN_DEBUG "[BH] resume.\n"); + if (WARN_ON(priv->bh_error)) + return 0; + + atomic_set(&priv->bh_suspend, CW1200_BH_RESUME); + wake_up(&priv->bh_wq); + return wait_event_timeout(priv->bh_evt_wq, priv->bh_error || + (CW1200_BH_RESUMED == atomic_read(&priv->bh_suspend)), + 1 * HZ) ? 0 : -ETIMEDOUT; +} + +static inline void wsm_alloc_tx_buffer(struct cw1200_common *priv) +{ + ++priv->hw_bufs_used; +} + +int wsm_release_tx_buffer(struct cw1200_common *priv, int count) +{ + int ret = 0; + int hw_bufs_used = priv->hw_bufs_used; + + priv->hw_bufs_used -= count; + if (WARN_ON(priv->hw_bufs_used < 0)) + ret = -1; + else if (hw_bufs_used >= priv->wsm_caps.numInpChBufs) + ret = 1; + if (!priv->hw_bufs_used) + wake_up(&priv->bh_evt_wq); + return ret; +} + +static struct sk_buff *cw1200_get_skb(struct cw1200_common *priv, size_t len) +{ + struct sk_buff *skb; + size_t alloc_len = (len > SDIO_BLOCK_SIZE) ? len : SDIO_BLOCK_SIZE; + + if (len > SDIO_BLOCK_SIZE || !priv->skb_cache) { + skb = dev_alloc_skb(alloc_len + + WSM_TX_EXTRA_HEADROOM + + 8 /* TKIP IV */ + + 12 /* TKIP ICV + MIC */ + - 2 /* Piggyback */); + /* In AP mode RXed SKB can be looped back as a broadcast. + * Here we reserve enough space for headers. */ + skb_reserve(skb, WSM_TX_EXTRA_HEADROOM + + 8 /* TKIP IV */ + - WSM_RX_EXTRA_HEADROOM); + } else { + skb = priv->skb_cache; + priv->skb_cache = NULL; + } + return skb; +} + +static void cw1200_put_skb(struct cw1200_common *priv, struct sk_buff *skb) +{ + if (priv->skb_cache) + dev_kfree_skb(skb); + else + priv->skb_cache = skb; +} + +static int cw1200_bh_read_ctrl_reg(struct cw1200_common *priv, + u16 *ctrl_reg) +{ + int ret; + + ret = cw1200_reg_read_16(priv, + ST90TDS_CONTROL_REG_ID, ctrl_reg); + if (ret) { + ret = cw1200_reg_read_16(priv, + ST90TDS_CONTROL_REG_ID, ctrl_reg); + if (ret) + printk(KERN_ERR + "[BH] Failed to read control register.\n"); + } + + return ret; +} + +static int cw1200_device_wakeup(struct cw1200_common *priv) +{ + u16 ctrl_reg; + int ret; + + bh_printk(KERN_DEBUG "[BH] Device wakeup.\n"); + + /* To force the device to be always-on, the host sets WLAN_UP to 1 */ + ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, + ST90TDS_CONT_WUP_BIT); + if (WARN_ON(ret)) + return ret; + + ret = cw1200_bh_read_ctrl_reg(priv, &ctrl_reg); + if (WARN_ON(ret)) + return ret; + + /* If the device returns WLAN_RDY as 1, the device is active and will + * remain active. */ + if (ctrl_reg & ST90TDS_CONT_RDY_BIT) { + bh_printk(KERN_DEBUG "[BH] Device awake.\n"); + return 1; + } + + return 0; +} + +/* Must be called from BH thraed. */ +void cw1200_enable_powersave(struct cw1200_common *priv, + bool enable) +{ + bh_printk(KERN_DEBUG "[BH] Powerave is %s.\n", + enable ? "enabled" : "disabled"); + priv->powersave_enabled = enable; +} + +static int cw1200_bh(void *arg) +{ + struct cw1200_common *priv = arg; + struct sk_buff *skb_rx = NULL; + size_t read_len = 0; + int rx, tx, term, suspend; + struct wsm_hdr *wsm; + size_t wsm_len; + int wsm_id; + u8 wsm_seq; + int rx_resync = 1; + u16 ctrl_reg = 0; + int tx_allowed; + int pending_tx = 0; + int tx_burst; + int rx_burst = 0; + long status; +#if defined(CONFIG_CW1200_WSM_DUMPS) + size_t wsm_dump_max = -1; +#endif + u32 dummy; + + for (;;) { + if (!priv->hw_bufs_used + && priv->powersave_enabled + && !priv->device_can_sleep) + status = 1 * HZ; + else if (priv->hw_bufs_used) + /* Interrupt loss detection */ + status = 1 * HZ; + else + status = MAX_SCHEDULE_TIMEOUT; + + /* Dummy Read for SDIO retry mechanism*/ + if (((atomic_read(&priv->bh_rx) == 0) && + (atomic_read(&priv->bh_tx) == 0))) + cw1200_reg_read(priv, ST90TDS_CONFIG_REG_ID, + &dummy, sizeof(dummy)); +#if defined(CONFIG_CW1200_WSM_DUMPS_SHORT) + wsm_dump_max = priv->wsm_dump_max_size; +#endif /* CONFIG_CW1200_WSM_DUMPS_SHORT */ + + status = wait_event_interruptible_timeout(priv->bh_wq, ({ + rx = atomic_xchg(&priv->bh_rx, 0); + tx = atomic_xchg(&priv->bh_tx, 0); + term = atomic_xchg(&priv->bh_term, 0); + suspend = pending_tx ? + 0 : atomic_read(&priv->bh_suspend); + (rx || tx || term || suspend || priv->bh_error); + }), status); + + if (status < 0 || term || priv->bh_error) + break; + + if (!status && priv->hw_bufs_used) { + unsigned long timestamp = jiffies; + long timeout; + bool pending = false; + int i; + + wiphy_warn(priv->hw->wiphy, "Missed interrupt?\n"); + rx = 1; + + /* Get a timestamp of "oldest" frame */ + for (i = 0; i < 4; ++i) + pending |= cw1200_queue_get_xmit_timestamp( + &priv->tx_queue[i], + ×tamp); + + /* Check if frame transmission is timed out. + * Add an extra second with respect to possible + * interrupt loss. */ + timeout = timestamp + + WSM_CMD_LAST_CHANCE_TIMEOUT + + 1 * HZ - + jiffies; + + /* And terminate BH tread if the frame is "stuck" */ + if (pending && timeout < 0) { + wiphy_warn(priv->hw->wiphy, + "Timeout waiting for TX confirm.\n"); + break; + } + +#if defined(CONFIG_CW1200_DUMP_ON_ERROR) + BUG_ON(1); +#endif /* CONFIG_CW1200_DUMP_ON_ERROR */ + } else if (!status) { + bh_printk(KERN_DEBUG "[BH] Device wakedown.\n"); + WARN_ON(cw1200_reg_write_16(priv, + ST90TDS_CONTROL_REG_ID, 0)); + priv->device_can_sleep = true; + continue; + } else if (suspend) { + bh_printk(KERN_DEBUG "[BH] Device suspend.\n"); + if (priv->powersave_enabled) { + WARN_ON(cw1200_reg_write_16(priv, + ST90TDS_CONTROL_REG_ID, 0)); + priv->device_can_sleep = true; + } + + atomic_set(&priv->bh_suspend, CW1200_BH_SUSPENDED); + wake_up(&priv->bh_evt_wq); + status = wait_event_interruptible(priv->bh_wq, + CW1200_BH_RESUME == atomic_read( + &priv->bh_suspend)); + if (status < 0) { + wiphy_err(priv->hw->wiphy, + "%s: Failed to wait for resume: %ld.\n", + __func__, status); + break; + } + bh_printk(KERN_DEBUG "[BH] Device resume.\n"); + atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED); + wake_up(&priv->bh_evt_wq); + atomic_add(1, &priv->bh_rx); + continue; + } + + tx += pending_tx; + pending_tx = 0; + + if (rx) { + size_t alloc_len; + u8 *data; + + if (WARN_ON(cw1200_bh_read_ctrl_reg( + priv, &ctrl_reg))) + break; +rx: + read_len = (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2; + if (!read_len) { + rx_burst = 0; + goto tx; + } + + if (WARN_ON((read_len < sizeof(struct wsm_hdr)) || + (read_len > EFFECTIVE_BUF_SIZE))) { + printk(KERN_DEBUG "Invalid read len: %d", + read_len); + break; + } + + /* Add SIZE of PIGGYBACK reg (CONTROL Reg) + * to the NEXT Message length + 2 Bytes for SKB */ + read_len = read_len + 2; + +#if defined(CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES) + alloc_len = priv->sbus_ops->align_size( + priv->sbus_priv, read_len); +#else /* CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES */ + /* Platform's SDIO workaround */ + alloc_len = read_len & ~(SDIO_BLOCK_SIZE - 1); + if (read_len & (SDIO_BLOCK_SIZE - 1)) + alloc_len += SDIO_BLOCK_SIZE; +#endif /* CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES */ + + /* Check if not exceeding CW1200 capabilities */ + if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) { + printk(KERN_DEBUG "Read aligned len: %d\n", + alloc_len); + } + + skb_rx = cw1200_get_skb(priv, alloc_len); + if (WARN_ON(!skb_rx)) + break; + + skb_trim(skb_rx, 0); + skb_put(skb_rx, read_len); + data = skb_rx->data; + if (WARN_ON(!data)) + break; + + if (WARN_ON(cw1200_data_read(priv, data, alloc_len))) + break; + + /* Piggyback */ + ctrl_reg = __le16_to_cpu( + ((__le16 *)data)[alloc_len / 2 - 1]); + + wsm = (struct wsm_hdr *)data; + wsm_len = __le32_to_cpu(wsm->len); + if (WARN_ON(wsm_len > read_len)) + break; + +#if defined(CONFIG_CW1200_WSM_DUMPS) + if (unlikely(priv->wsm_enable_wsm_dumps)) + print_hex_dump_bytes("<-- ", + DUMP_PREFIX_NONE, + data, min(wsm_len, wsm_dump_max)); +#endif /* CONFIG_CW1200_WSM_DUMPS */ + + wsm_id = __le32_to_cpu(wsm->id) & 0xFFF; + wsm_seq = (__le32_to_cpu(wsm->id) >> 13) & 7; + + skb_trim(skb_rx, wsm_len); + + if (unlikely(wsm_id == 0x0800)) { + wsm_handle_exception(priv, + &data[sizeof(*wsm)], + wsm_len - sizeof(*wsm)); + break; + } else if (unlikely(!rx_resync)) { + if (WARN_ON(wsm_seq != priv->wsm_rx_seq)) { +#if defined(CONFIG_CW1200_DUMP_ON_ERROR) + BUG_ON(1); +#endif /* CONFIG_CW1200_DUMP_ON_ERROR */ + break; + } + } + priv->wsm_rx_seq = (wsm_seq + 1) & 7; + rx_resync = 0; + + if (wsm_id & 0x0400) { + int rc = wsm_release_tx_buffer(priv, 1); + if (WARN_ON(rc < 0)) + break; + else if (rc > 0) + tx = 1; + } + + /* cw1200_wsm_rx takes care on SKB livetime */ + if (WARN_ON(wsm_handle_rx(priv, wsm_id, wsm, &skb_rx))) + break; + + if (skb_rx) { + cw1200_put_skb(priv, skb_rx); + skb_rx = NULL; + } + + read_len = 0; + + if (rx_burst) { + cw1200_debug_rx_burst(priv); + --rx_burst; + goto rx; + } + } + +tx: + BUG_ON(priv->hw_bufs_used > priv->wsm_caps.numInpChBufs); + tx_burst = priv->wsm_caps.numInpChBufs - priv->hw_bufs_used; + tx_allowed = tx_burst > 0; + + if (tx && tx_allowed) { + size_t tx_len; + u8 *data; + int ret; + + if (priv->device_can_sleep) { + ret = cw1200_device_wakeup(priv); + if (WARN_ON(ret < 0)) + break; + else if (ret) + priv->device_can_sleep = false; + else { + /* Wait for "awake" interrupt */ + pending_tx = tx; + continue; + } + } + + wsm_alloc_tx_buffer(priv); + ret = wsm_get_tx(priv, &data, &tx_len, &tx_burst); + if (ret <= 0) { + wsm_release_tx_buffer(priv, 1); + if (WARN_ON(ret < 0)) + break; + } else { + wsm = (struct wsm_hdr *)data; + BUG_ON(tx_len < sizeof(*wsm)); + BUG_ON(__le32_to_cpu(wsm->len) != tx_len); + +#if 0 /* count is not implemented */ + if (ret > 1) + atomic_add(1, &priv->bh_tx); +#else + atomic_add(1, &priv->bh_tx); +#endif + + +#if defined(CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES) + tx_len = priv->sbus_ops->align_size( + priv->sbus_priv, tx_len); +#else /* CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES */ + /* HACK!!! Platform limitation. + * It is also supported by upper layer: + * there is always enough space at the + * end of the buffer. */ + if (tx_len & (SDIO_BLOCK_SIZE - 1)) { + tx_len &= ~(SDIO_BLOCK_SIZE - 1); + tx_len += SDIO_BLOCK_SIZE; + } +#endif /* CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES */ + + /* Check if not exceeding CW1200 + capabilities */ + if (WARN_ON_ONCE( + tx_len > EFFECTIVE_BUF_SIZE)) { + printk(KERN_DEBUG "Write aligned len:" + " %d\n", tx_len); + } + + wsm->id &= __cpu_to_le32( + ~WSM_TX_SEQ(WSM_TX_SEQ_MAX)); + wsm->id |= cpu_to_le32( + WSM_TX_SEQ(priv->wsm_tx_seq)); + + if (WARN_ON(cw1200_data_write(priv, + data, tx_len))) { + wsm_release_tx_buffer(priv, 1); + break; + } + +#if defined(CONFIG_CW1200_WSM_DUMPS) + if (unlikely(priv->wsm_enable_wsm_dumps)) + print_hex_dump_bytes("--> ", + DUMP_PREFIX_NONE, + data, + min(__le32_to_cpu(wsm->len), + wsm_dump_max)); +#endif /* CONFIG_CW1200_WSM_DUMPS */ + + wsm_txed(priv, data); + priv->wsm_tx_seq = (priv->wsm_tx_seq + 1) & + WSM_TX_SEQ_MAX; + + if (tx_burst > 1) { + cw1200_debug_tx_burst(priv); + ++rx_burst; + goto tx; + } + } + } + + if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) + goto rx; + } + + if (skb_rx) { + cw1200_put_skb(priv, skb_rx); + skb_rx = NULL; + } + + + if (!term) { + cw1200_dbg(CW1200_DBG_ERROR, "[BH] Fatal error, exitting.\n"); +#if defined(CONFIG_CW1200_DUMP_ON_ERROR) + BUG_ON(1); +#endif /* CONFIG_CW1200_DUMP_ON_ERROR */ + priv->bh_error = 1; +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + ieee80211_driver_hang_notify(priv->vif, GFP_KERNEL); + cw1200_pm_stay_awake(&priv->pm_state, 3*HZ); +#endif + /* TODO: schedule_work(recovery) */ +#ifndef HAS_PUT_TASK_STRUCT + /* The only reason of having this stupid code here is + * that __put_task_struct is not exported by kernel. */ + for (;;) { + int status = wait_event_interruptible(priv->bh_wq, ({ + term = atomic_xchg(&priv->bh_term, 0); + (term); + })); + + if (status || term) + break; + } +#endif + } + return 0; +} diff --git a/drivers/staging/cw1200/bh.h b/drivers/staging/cw1200/bh.h new file mode 100644 index 00000000000..ea4598afc43 --- /dev/null +++ b/drivers/staging/cw1200/bh.h @@ -0,0 +1,30 @@ +/* + * Device handling thread interface for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef CW1200_BH_H +#define CW1200_BH_H + +/* extern */ struct cw1200_common; + +#define SDIO_BLOCK_SIZE (512) + +int cw1200_register_bh(struct cw1200_common *priv); +void cw1200_unregister_bh(struct cw1200_common *priv); +void cw1200_irq_handler(struct cw1200_common *priv); +void cw1200_bh_wakeup(struct cw1200_common *priv); +int cw1200_bh_suspend(struct cw1200_common *priv); +int cw1200_bh_resume(struct cw1200_common *priv); +/* Must be called from BH thread. */ +void cw1200_enable_powersave(struct cw1200_common *priv, + bool enable); +int wsm_release_tx_buffer(struct cw1200_common *priv, int count); + +#endif /* CW1200_BH_H */ diff --git a/drivers/staging/cw1200/cw1200.h b/drivers/staging/cw1200/cw1200.h new file mode 100644 index 00000000000..5869c917fe6 --- /dev/null +++ b/drivers/staging/cw1200/cw1200.h @@ -0,0 +1,313 @@ +/* + * Common private data for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * Based on the mac80211 Prism54 code, which is + * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net> + * + * Based on the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef CW1200_H +#define CW1200_H + +#include <linux/wait.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/atomic.h> +#include <net/mac80211.h> + +#include "queue.h" +#include "wsm.h" +#include "scan.h" +#include "txrx.h" +#include "ht.h" +#include "pm.h" + +/* extern */ struct sbus_ops; +/* extern */ struct task_struct; +/* extern */ struct cw1200_debug_priv; +/* extern */ struct firmware; + +#if defined(CONFIG_CW1200_TXRX_DEBUG) +#define txrx_printk(...) printk(__VA_ARGS__) +#else +#define txrx_printk(...) +#endif + +#define CW1200_MAX_CTRL_FRAME_LEN (0x1000) + +#define CW1200_MAX_STA_IN_AP_MODE (5) +#define CW1200_LINK_ID_AFTER_DTIM (CW1200_MAX_STA_IN_AP_MODE + 1) +#define CW1200_LINK_ID_UAPSD (CW1200_MAX_STA_IN_AP_MODE + 2) +#define CW1200_LINK_ID_MAX (CW1200_MAX_STA_IN_AP_MODE + 3) +#define CW1200_MAX_REQUEUE_ATTEMPTS (5) + +#define CW1200_MAX_TID (8) + +#define CW1200_BLOCK_ACK_CNT (30) +#define CW1200_BLOCK_ACK_THLD (800) +#define CW1200_BLOCK_ACK_HIST (3) +#define CW1200_BLOCK_ACK_INTERVAL (1 * HZ / CW1200_BLOCK_ACK_HIST) + +/* Please keep order */ +enum cw1200_join_status { + CW1200_JOIN_STATUS_PASSIVE = 0, + CW1200_JOIN_STATUS_MONITOR, + CW1200_JOIN_STATUS_STA, + CW1200_JOIN_STATUS_AP, +}; + +enum cw1200_link_status { + CW1200_LINK_OFF, + CW1200_LINK_RESERVE, + CW1200_LINK_SOFT, + CW1200_LINK_HARD, +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + CW1200_LINK_RESET, + CW1200_LINK_RESET_REMAP, +#endif +}; + +enum cw1200_bss_loss_status { + CW1200_BSS_LOSS_NONE, + CW1200_BSS_LOSS_CHECKING, + CW1200_BSS_LOSS_CONFIRMING, + CW1200_BSS_LOSS_CONFIRMED, +}; + +struct cw1200_link_entry { + unsigned long timestamp; + enum cw1200_link_status status; +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + enum cw1200_link_status prev_status; +#endif + u8 mac[ETH_ALEN]; + u8 buffered[CW1200_MAX_TID]; + struct sk_buff_head rx_queue; +}; + +struct cw1200_common { + struct cw1200_queue tx_queue[4]; + struct cw1200_queue_stats tx_queue_stats; + int tx_burst_idx; + struct cw1200_debug_priv *debug; + + struct ieee80211_hw *hw; + struct ieee80211_vif *vif; + struct device *pdev; + struct workqueue_struct *workqueue; + + struct mutex conf_mutex; + + const struct sbus_ops *sbus_ops; + struct sbus_priv *sbus_priv; + + /* HW type (HIF_...) */ + int hw_type; + int hw_revision; + + /* firmware/hardware info */ + unsigned int tx_hdr_len; + + /* Radio data */ + int output_power; + int noise; + + /* calibration, output power limit and rssi<->dBm conversation data */ + + /* BBP/MAC state */ + const struct firmware *sdd; + struct ieee80211_rate *rates; + struct ieee80211_rate *mcs_rates; + u8 mac_addr[ETH_ALEN]; + struct ieee80211_channel *channel; + u8 bssid[ETH_ALEN]; + struct wsm_edca_params edca; + struct wsm_tx_queue_params tx_queue_params; + struct wsm_association_mode association_mode; + struct wsm_set_bss_params bss_params; + struct cw1200_ht_info ht_info; + struct wsm_set_pm powersave_mode; + struct wsm_set_pm firmware_ps_mode; + int cqm_rssi_thold; + unsigned cqm_rssi_hyst; + unsigned cqm_tx_failure_thold; + unsigned cqm_tx_failure_count; + bool cqm_use_rssi; + int cqm_link_loss_count; + int cqm_beacon_loss_count; + int channel_switch_in_progress; + wait_queue_head_t channel_switch_done; + u8 long_frame_max_tx_count; + u8 short_frame_max_tx_count; + int mode; + bool enable_beacon; + int beacon_int; + size_t ssid_length; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + bool listening; + struct wsm_rx_filter rx_filter; + struct wsm_beacon_filter_table bf_table; + struct wsm_beacon_filter_control bf_control; + struct wsm_multicast_filter multicast_filter; + bool has_multicast_subscription; + bool disable_beacon_filter; + struct work_struct update_filtering_work; + u8 ba_tid_mask; + int ba_acc; + int ba_cnt; + int ba_hist; + struct timer_list ba_timer; + spinlock_t ba_lock; + bool ba_ena; + struct work_struct ba_work; + struct cw1200_pm_state pm_state; + struct wsm_p2p_ps_modeinfo p2p_ps_modeinfo; + struct wsm_uapsd_info uapsd_info; + bool setbssparams_done; + bool is_BT_Present; + u8 conf_listen_interval; + u32 listen_interval; + u32 erp_info; + + /* BH */ + atomic_t bh_rx; + atomic_t bh_tx; + atomic_t bh_term; + atomic_t bh_suspend; + struct task_struct *bh_thread; + int bh_error; + wait_queue_head_t bh_wq; + wait_queue_head_t bh_evt_wq; + int buf_id_tx; /* byte */ + int buf_id_rx; /* byte */ + int wsm_rx_seq; /* byte */ + int wsm_tx_seq; /* byte */ + int hw_bufs_used; + struct sk_buff *skb_cache; + bool powersave_enabled; + bool device_can_sleep; + + /* WSM */ + struct wsm_caps wsm_caps; + struct mutex wsm_cmd_mux; + struct wsm_buf wsm_cmd_buf; + struct wsm_cmd wsm_cmd; + wait_queue_head_t wsm_cmd_wq; + wait_queue_head_t wsm_startup_done; + struct wsm_cbc wsm_cbc; + atomic_t tx_lock; + + /* WSM debug */ + int wsm_enable_wsm_dumps; + u32 wsm_dump_max_size; + + /* Scan status */ + struct cw1200_scan scan; + + /* WSM Join */ + enum cw1200_join_status join_status; + u8 join_bssid[ETH_ALEN]; + u32 pending_frame_id; + struct work_struct join_work; + struct delayed_work join_timeout; + struct work_struct unjoin_work; + struct work_struct offchannel_work; + int join_dtim_period; + bool delayed_unjoin; + + /* TX/RX and security */ + s8 wep_default_key_id; + struct work_struct wep_key_work; + u32 key_map; + struct wsm_add_key keys[WSM_KEY_MAX_INDEX + 1]; + unsigned long rx_timestamp; + + /* AP powersave */ + u32 link_id_map; + struct cw1200_link_entry link_id_db[CW1200_MAX_STA_IN_AP_MODE]; + struct work_struct link_id_work; + struct delayed_work link_id_gc_work; + u32 sta_asleep_mask; + u32 pspoll_mask; + bool aid0_bit_set; + spinlock_t ps_state_lock; + bool buffered_multicasts; + bool tx_multicast; + struct work_struct set_tim_work; + struct delayed_work set_cts_work; + struct work_struct multicast_start_work; + struct work_struct multicast_stop_work; + struct timer_list mcast_timeout; + + + /* WSM events and CQM implementation */ + spinlock_t event_queue_lock; + struct list_head event_queue; + struct work_struct event_handler; + struct delayed_work bss_loss_work; + struct delayed_work connection_loss_work; + struct work_struct tx_failure_work; + int delayed_link_loss; + spinlock_t bss_loss_lock; + int bss_loss_status; + int bss_loss_confirm_id; + + /* TX rate policy cache */ + struct tx_policy_cache tx_policy_cache; + struct work_struct tx_policy_upload_work; + + /* cryptographic engine information */ + + /* bit field of glowing LEDs */ + u16 softled_state; + + /* statistics */ + struct ieee80211_low_level_stats stats; +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + /* Workaround for WFD testcase 6.1.10*/ + struct work_struct linkid_reset_work; + u8 action_frame_sa[ETH_ALEN]; + u8 action_linkid; +#endif +}; + +struct cw1200_sta_priv { + int link_id; +}; + +/* interfaces for the drivers */ +int cw1200_core_probe(const struct sbus_ops *sbus_ops, + struct sbus_priv *sbus, + struct device *pdev, + struct cw1200_common **pself); +void cw1200_core_release(struct cw1200_common *self); + +#define CW1200_DBG_MSG 0x00000001 +#define CW1200_DBG_NIY 0x00000002 +#define CW1200_DBG_SBUS 0x00000004 +#define CW1200_DBG_INIT 0x00000008 +#define CW1200_DBG_ERROR 0x00000010 +#define CW1200_DBG_LEVEL 0xFFFFFFFF + +#define cw1200_dbg(level, ...) \ + do { \ + if ((level) & CW1200_DBG_LEVEL) \ + printk(KERN_DEBUG __VA_ARGS__); \ + } while (0) + +#define STUB() \ + do { \ + cw1200_dbg(CW1200_DBG_NIY, "%s: STUB at line %d.\n", \ + __func__, __LINE__); \ + } while (0) + +#endif /* CW1200_H */ diff --git a/drivers/staging/cw1200/cw1200_plat.h b/drivers/staging/cw1200/cw1200_plat.h new file mode 100644 index 00000000000..3a73183c9f8 --- /dev/null +++ b/drivers/staging/cw1200/cw1200_plat.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef CW1200_PLAT_H_INCLUDED +#define CW1200_PLAT_H_INCLUDED + +#include <linux/ioport.h> + +struct cw1200_platform_data { + const char *mmc_id; + const struct resource *irq; + const struct resource *reset; + int (*power_ctrl)(const struct cw1200_platform_data *pdata, + bool enable); + int (*clk_ctrl)(const struct cw1200_platform_data *pdata, + bool enable); + int (*prcmu_ctrl)(const struct cw1200_platform_data *pdata, + bool enable); +}; + +/* Declaration only. Should be implemented in arch/xxx/mach-yyy */ +const struct cw1200_platform_data *cw1200_get_platform_data(void); + +#endif /* CW1200_PLAT_H_INCLUDED */ diff --git a/drivers/staging/cw1200/cw1200_sdio.c b/drivers/staging/cw1200/cw1200_sdio.c new file mode 100644 index 00000000000..4b9d622689f --- /dev/null +++ b/drivers/staging/cw1200/cw1200_sdio.c @@ -0,0 +1,469 @@ +/* + * Mac80211 SDIO driver for ST-Ericsson CW1200 device + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/mmc/host.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio.h> +#include <linux/spinlock.h> +#include <asm/mach-types.h> +#include <net/mac80211.h> + +#include "cw1200.h" +#include "sbus.h" +#include "cw1200_plat.h" + +MODULE_AUTHOR("Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com>"); +MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SDIO driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("cw1200_wlan"); + +struct sbus_priv { + struct sdio_func *func; + struct cw1200_common *core; + const struct cw1200_platform_data *pdata; + spinlock_t lock; + sbus_irq_handler irq_handler; + void *irq_priv; +}; + +static const struct sdio_device_id cw1200_sdio_ids[] = { + { SDIO_DEVICE(SDIO_ANY_ID, SDIO_ANY_ID) }, + { /* end: all zeroes */ }, +}; + +/* sbus_ops implemetation */ + +static int cw1200_sdio_memcpy_fromio(struct sbus_priv *self, + unsigned int addr, + void *dst, int count) +{ + return sdio_memcpy_fromio(self->func, dst, addr, count); +} + +static int cw1200_sdio_memcpy_toio(struct sbus_priv *self, + unsigned int addr, + const void *src, int count) +{ + return sdio_memcpy_toio(self->func, addr, (void *)src, count); +} + +static void cw1200_sdio_lock(struct sbus_priv *self) +{ + sdio_claim_host(self->func); +} + +static void cw1200_sdio_unlock(struct sbus_priv *self) +{ + sdio_release_host(self->func); +} + +#ifndef CONFIG_CW1200_USE_GPIO_IRQ +static void cw1200_sdio_irq_handler(struct sdio_func *func) +{ + struct sbus_priv *self = sdio_get_drvdata(func); + unsigned long flags; + + BUG_ON(!self); + spin_lock_irqsave(&self->lock, flags); + if (self->irq_handler) + self->irq_handler(self->irq_priv); + spin_unlock_irqrestore(&self->lock, flags); +} +#else /* CONFIG_CW1200_USE_GPIO_IRQ */ +static irqreturn_t cw1200_gpio_irq_handler(int irq, void *dev_id) +{ + struct sbus_priv *self = dev_id; + + BUG_ON(!self); + if (self->irq_handler) + self->irq_handler(self->irq_priv); + return IRQ_HANDLED; +} + +static int cw1200_request_irq(struct sbus_priv *self, + irq_handler_t handler) +{ + int ret; + int func_num; + const struct resource *irq = self->pdata->irq; + u8 cccr; + + ret = request_any_context_irq(irq->start, handler, + IRQF_TRIGGER_RISING, irq->name, self); + if (WARN_ON(ret < 0)) + goto exit; + + /* Hack to access Fuction-0 */ + func_num = self->func->num; + self->func->num = 0; + + cccr = sdio_readb(self->func, SDIO_CCCR_IENx, &ret); + if (WARN_ON(ret)) + goto set_func; + + /* Master interrupt enable ... */ + cccr |= BIT(0); + + /* ... for our function */ + cccr |= BIT(func_num); + + sdio_writeb(self->func, cccr, SDIO_CCCR_IENx, &ret); + if (WARN_ON(ret)) + goto set_func; + + /* Restore the WLAN function number */ + self->func->num = func_num; + return 0; + +set_func: + self->func->num = func_num; + free_irq(irq->start, self); +exit: + return ret; +} +#endif /* CONFIG_CW1200_USE_GPIO_IRQ */ + +static int cw1200_sdio_irq_subscribe(struct sbus_priv *self, + sbus_irq_handler handler, + void *priv) +{ + int ret; + unsigned long flags; + + if (!handler) + return -EINVAL; + + spin_lock_irqsave(&self->lock, flags); + self->irq_priv = priv; + self->irq_handler = handler; + spin_unlock_irqrestore(&self->lock, flags); + + printk(KERN_DEBUG "SW IRQ subscribe\n"); + sdio_claim_host(self->func); +#ifndef CONFIG_CW1200_USE_GPIO_IRQ + ret = sdio_claim_irq(self->func, cw1200_sdio_irq_handler); +#else + ret = cw1200_request_irq(self, cw1200_gpio_irq_handler); +#endif + sdio_release_host(self->func); + return ret; +} + +static int cw1200_sdio_irq_unsubscribe(struct sbus_priv *self) +{ + int ret = 0; + unsigned long flags; +#ifdef CONFIG_CW1200_USE_GPIO_IRQ + const struct resource *irq = self->pdata->irq; +#endif + + WARN_ON(!self->irq_handler); + if (!self->irq_handler) + return 0; + + printk(KERN_DEBUG "SW IRQ unsubscribe\n"); +#ifndef CONFIG_CW1200_USE_GPIO_IRQ + sdio_claim_host(self->func); + ret = sdio_release_irq(self->func); + sdio_release_host(self->func); +#else + free_irq(irq->start, self); +#endif + + spin_lock_irqsave(&self->lock, flags); + self->irq_priv = NULL; + self->irq_handler = NULL; + spin_unlock_irqrestore(&self->lock, flags); + + return ret; +} + +static int cw1200_detect_card(const struct cw1200_platform_data *pdata) +{ + /* HACK!!! + * Rely on mmc->class_dev.class set in mmc_alloc_host + * Tricky part: a new mmc hook is being (temporary) created + * to discover mmc_host class. + * Do you know more elegant way how to enumerate mmc_hosts? + */ + + struct mmc_host *mmc = NULL; + struct class_dev_iter iter; + struct device *dev; + + mmc = mmc_alloc_host(0, NULL); + if (!mmc) + return -ENOMEM; + + BUG_ON(!mmc->class_dev.class); + class_dev_iter_init(&iter, mmc->class_dev.class, NULL, NULL); + for (;;) { + dev = class_dev_iter_next(&iter); + if (!dev) { + printk(KERN_ERR "cw1200: %s is not found.\n", + pdata->mmc_id); + break; + } else { + struct mmc_host *host = container_of(dev, + struct mmc_host, class_dev); + + if (dev_name(&host->class_dev) && + strcmp(dev_name(&host->class_dev), + pdata->mmc_id)) + continue; + + mmc_detect_change(host, 10); + break; + } + } + mmc_free_host(mmc); + return 0; +} + +static int cw1200_sdio_off(const struct cw1200_platform_data *pdata) +{ + int ret = 0; +#ifndef CONFIG_CW1200_U5500_SUPPORT + const struct resource *reset = pdata->reset; + gpio_set_value(reset->start, 0); + gpio_free(reset->start); +#else + if (pdata->prcmu_ctrl) + ret = pdata->prcmu_ctrl(pdata, false); + msleep(50); +#endif + cw1200_detect_card(pdata); + return ret; +} + +static int cw1200_sdio_on(const struct cw1200_platform_data *pdata) +{ + int ret = 0; +#ifndef CONFIG_CW1200_U5500_SUPPORT + const struct resource *reset = pdata->reset; + gpio_request(reset->start, reset->name); + gpio_direction_output(reset->start, 1); + /* It is not stated in the datasheet, but at least some of devices + * have problems with reset if this stage is omited. */ + msleep(50); + gpio_direction_output(reset->start, 0); + /* A valid reset shall be obtained by maintaining WRESETN + * active (low) for at least two cycles of LP_CLK after VDDIO + * is stable within it operating range. */ + usleep_range(1000, 20000); + gpio_set_value(reset->start, 1); + /* The host should wait 32 ms after the WRESETN release + * for the on-chip LDO to stabilize */ + msleep(32); +#else + if (pdata->prcmu_ctrl) + ret = pdata->prcmu_ctrl(pdata, true); + msleep(50); +#endif + cw1200_detect_card(pdata); + return ret; +} + +static int cw1200_sdio_reset(struct sbus_priv *self) +{ + cw1200_sdio_off(self->pdata); + msleep(1000); + cw1200_sdio_on(self->pdata); + return 0; +} + +static size_t cw1200_sdio_align_size(struct sbus_priv *self, size_t size) +{ + size_t aligned = sdio_align_size(self->func, size); + return aligned; +} + +int cw1200_sdio_set_block_size(struct sbus_priv *self, size_t size) +{ + return sdio_set_block_size(self->func, size); +} + +static int cw1200_sdio_pm(struct sbus_priv *self, bool suspend) +{ + int ret = 0; + const struct resource *irq = self->pdata->irq; + + if (irq) + ret = irq_set_irq_wake(irq->start, suspend); + + return ret; +} + +static struct sbus_ops cw1200_sdio_sbus_ops = { + .sbus_memcpy_fromio = cw1200_sdio_memcpy_fromio, + .sbus_memcpy_toio = cw1200_sdio_memcpy_toio, + .lock = cw1200_sdio_lock, + .unlock = cw1200_sdio_unlock, + .irq_subscribe = cw1200_sdio_irq_subscribe, + .irq_unsubscribe = cw1200_sdio_irq_unsubscribe, + .reset = cw1200_sdio_reset, + .align_size = cw1200_sdio_align_size, + .power_mgmt = cw1200_sdio_pm, + .set_block_size = cw1200_sdio_set_block_size, +}; + +/* Probe Function to be called by SDIO stack when device is discovered */ +static int cw1200_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct sbus_priv *self; + int status; + + cw1200_dbg(CW1200_DBG_INIT, "Probe called\n"); + + self = kzalloc(sizeof(*self), GFP_KERNEL); + if (!self) { + cw1200_dbg(CW1200_DBG_ERROR, "Can't allocate SDIO sbus_priv."); + return -ENOMEM; + } + + spin_lock_init(&self->lock); + self->pdata = cw1200_get_platform_data(); + self->func = func; + sdio_set_drvdata(func, self); + sdio_claim_host(func); + sdio_enable_func(func); + sdio_release_host(func); + + status = cw1200_core_probe(&cw1200_sdio_sbus_ops, + self, &func->dev, &self->core); + if (status) { + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + sdio_set_drvdata(func, NULL); + kfree(self); + } + + return status; +} + +/* Disconnect Function to be called by SDIO stack when + * device is disconnected */ +static void cw1200_sdio_disconnect(struct sdio_func *func) +{ + struct sbus_priv *self = sdio_get_drvdata(func); + + if (self) { + if (self->core) { + cw1200_core_release(self->core); + self->core = NULL; + } + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + sdio_set_drvdata(func, NULL); + kfree(self); + } +} + +static int cw1200_suspend(struct device *dev) +{ + int ret; + struct sdio_func *func = dev_to_sdio_func(dev); + + /* Notify SDIO that CW1200 will remain powered during suspend */ + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + if (ret) + cw1200_dbg(CW1200_DBG_ERROR, + "Error setting SDIO pm flags: %i\n", ret); + + return ret; +} + +static int cw1200_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops cw1200_pm_ops = { + .suspend = cw1200_suspend, + .resume = cw1200_resume, +}; + +static struct sdio_driver sdio_driver = { + .name = "cw1200_wlan", + .id_table = cw1200_sdio_ids, + .probe = cw1200_sdio_probe, + .remove = cw1200_sdio_disconnect, + .drv = { + .pm = &cw1200_pm_ops, + } +}; + +/* Init Module function -> Called by insmod */ +static int __init cw1200_sdio_init(void) +{ + const struct cw1200_platform_data *pdata; + int ret; + + pdata = cw1200_get_platform_data(); + + ret = sdio_register_driver(&sdio_driver); + if (ret) + goto err_reg; + + if (pdata->clk_ctrl) { + ret = pdata->clk_ctrl(pdata, true); + if (ret) + goto err_clk; + } + + if (pdata->power_ctrl) { + ret = pdata->power_ctrl(pdata, true); + if (ret) + goto err_power; + } + + ret = cw1200_sdio_on(pdata); + if (ret) + goto err_on; + + return 0; + +err_on: + if (pdata->power_ctrl) + pdata->power_ctrl(pdata, false); +err_power: + if (pdata->clk_ctrl) + pdata->clk_ctrl(pdata, false); +err_clk: + sdio_unregister_driver(&sdio_driver); +err_reg: + return ret; +} + +/* Called at Driver Unloading */ +static void __exit cw1200_sdio_exit(void) +{ + const struct cw1200_platform_data *pdata; + pdata = cw1200_get_platform_data(); + sdio_unregister_driver(&sdio_driver); + cw1200_sdio_off(pdata); + if (pdata->power_ctrl) + pdata->power_ctrl(pdata, false); + if (pdata->clk_ctrl) + pdata->clk_ctrl(pdata, false); +} + + +module_init(cw1200_sdio_init); +module_exit(cw1200_sdio_exit); diff --git a/drivers/staging/cw1200/debug.c b/drivers/staging/cw1200/debug.c new file mode 100644 index 00000000000..3da603ac87d --- /dev/null +++ b/drivers/staging/cw1200/debug.c @@ -0,0 +1,611 @@ +/* + * mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers + * DebugFS code + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include "cw1200.h" +#include "debug.h" + +/* join_status */ +static const char * const cw1200_debug_join_status[] = { + "passive", + "monitor", + "station", + "access point", +}; + +/* WSM_JOIN_PREAMBLE_... */ +static const char * const cw1200_debug_preamble[] = { + "long", + "short", + "long on 1 and 2 Mbps", +}; + +static const char * const cw1200_debug_fw_types[] = { + "ETF", + "WFM", + "WSM", + "HI test", + "Platform test", +}; + +static const char * const cw1200_debug_link_id[] = { + "OFF", + "REQ", + "SOFT", + "HARD", +}; + +static const char *cw1200_debug_mode(int mode) +{ + switch (mode) { + case NL80211_IFTYPE_UNSPECIFIED: + return "unspecified"; + case NL80211_IFTYPE_MONITOR: + return "monitor"; + case NL80211_IFTYPE_STATION: + return "station"; + case NL80211_IFTYPE_ADHOC: + return "ad-hok"; + case NL80211_IFTYPE_MESH_POINT: + return "mesh point"; + case NL80211_IFTYPE_AP: + return "access point"; + case NL80211_IFTYPE_P2P_CLIENT: + return "p2p client"; + case NL80211_IFTYPE_P2P_GO: + return "p2p go"; + default: + return "unsupported"; + } +} + +static void cw1200_queue_status_show(struct seq_file *seq, + struct cw1200_queue *q) +{ + int i; + seq_printf(seq, "Queue %d:\n", q->queue_id); + seq_printf(seq, " capacity: %d\n", q->capacity); + seq_printf(seq, " queued: %d\n", q->num_queued); + seq_printf(seq, " pending: %d\n", q->num_pending); + seq_printf(seq, " sent: %d\n", q->num_sent); + seq_printf(seq, " locked: %s\n", q->tx_locked_cnt ? "yes" : "no"); + seq_printf(seq, " overfull: %s\n", q->overfull ? "yes" : "no"); + seq_puts(seq, " link map: 0-> "); + for (i = 0; i < q->stats->map_capacity; ++i) + seq_printf(seq, "%.2d ", q->link_map_cache[i]); + seq_printf(seq, "<-%d\n", q->stats->map_capacity); +} + +static void cw1200_debug_print_map(struct seq_file *seq, + struct cw1200_common *priv, + const char *label, + u32 map) +{ + int i; + seq_printf(seq, "%s0-> ", label); + for (i = 0; i < priv->tx_queue_stats.map_capacity; ++i) + seq_printf(seq, "%s ", (map & BIT(i)) ? "**" : ".."); + seq_printf(seq, "<-%d\n", priv->tx_queue_stats.map_capacity - 1); +} + +static int cw1200_status_show(struct seq_file *seq, void *v) +{ + int i; + struct list_head *item; + struct cw1200_common *priv = seq->private; + struct cw1200_debug_priv *d = priv->debug; + int ba_cnt, ba_acc, ba_avg = 0; + bool ba_ena; + + spin_lock_bh(&priv->ba_lock); + ba_cnt = priv->debug->ba_cnt; + ba_acc = priv->debug->ba_acc; + ba_ena = priv->ba_ena; + if (ba_cnt) + ba_avg = ba_acc / ba_cnt; + spin_unlock_bh(&priv->ba_lock); + + seq_puts(seq, "CW1200 Wireless LAN driver status\n"); + seq_printf(seq, "Hardware: %d.%d\n", + priv->wsm_caps.hardwareId, + priv->wsm_caps.hardwareSubId); + seq_printf(seq, "Firmware: %s %d.%d\n", + cw1200_debug_fw_types[priv->wsm_caps.firmwareType], + priv->wsm_caps.firmwareVersion, + priv->wsm_caps.firmwareBuildNumber); + seq_printf(seq, "FW API: %d\n", + priv->wsm_caps.firmwareApiVer); + seq_printf(seq, "FW caps: 0x%.4X\n", + priv->wsm_caps.firmwareCap); + seq_printf(seq, "Mode: %s%s\n", + cw1200_debug_mode(priv->mode), + priv->listening ? " (listening)" : ""); + seq_printf(seq, "Assoc: %s\n", + cw1200_debug_join_status[priv->join_status]); + if (priv->channel) + seq_printf(seq, "Channel: %d%s\n", + priv->channel->hw_value, + priv->channel_switch_in_progress ? + " (switching)" : ""); + if (priv->rx_filter.promiscuous) + seq_puts(seq, "Filter: promisc\n"); + else if (priv->rx_filter.fcs) + seq_puts(seq, "Filter: fcs\n"); + if (priv->rx_filter.bssid) + seq_puts(seq, "Filter: bssid\n"); + if (priv->bf_control.bcn_count) + seq_puts(seq, "Filter: beacons\n"); + + if (priv->enable_beacon || + priv->mode == NL80211_IFTYPE_AP || + priv->mode == NL80211_IFTYPE_ADHOC || + priv->mode == NL80211_IFTYPE_MESH_POINT || + priv->mode == NL80211_IFTYPE_P2P_GO) + seq_printf(seq, "Beaconing: %s\n", + priv->enable_beacon ? + "enabled" : "disabled"); + if (priv->ssid_length || + priv->mode == NL80211_IFTYPE_AP || + priv->mode == NL80211_IFTYPE_ADHOC || + priv->mode == NL80211_IFTYPE_MESH_POINT || + priv->mode == NL80211_IFTYPE_P2P_GO) + seq_printf(seq, "SSID: %.*s\n", + priv->ssid_length, priv->ssid); + + for (i = 0; i < 4; ++i) { + seq_printf(seq, "EDCA(%d): %d, %d, %d, %d, %d\n", i, + priv->edca.params[i].cwMin, + priv->edca.params[i].cwMax, + priv->edca.params[i].aifns, + priv->edca.params[i].txOpLimit, + priv->edca.params[i].maxReceiveLifetime); + } + if (priv->join_status == CW1200_JOIN_STATUS_STA) { + static const char *pmMode = "unknown"; + switch (priv->powersave_mode.pmMode) { + case WSM_PSM_ACTIVE: + pmMode = "off"; + break; + case WSM_PSM_PS: + pmMode = "on"; + break; + case WSM_PSM_FAST_PS: + pmMode = "dynamic"; + break; + } + seq_printf(seq, "Preamble: %s\n", + cw1200_debug_preamble[ + priv->association_mode.preambleType]); + seq_printf(seq, "AMPDU spcn: %d\n", + priv->association_mode.mpduStartSpacing); + seq_printf(seq, "Basic rate: 0x%.8X\n", + le32_to_cpu(priv->association_mode.basicRateSet)); + seq_printf(seq, "Bss lost: %d beacons\n", + priv->bss_params.beaconLostCount); + seq_printf(seq, "AID: %d\n", + priv->bss_params.aid); + seq_printf(seq, "Rates: 0x%.8X\n", + priv->bss_params.operationalRateSet); + seq_printf(seq, "Powersave: %s\n", pmMode); + } + seq_printf(seq, "HT: %s\n", + cw1200_is_ht(&priv->ht_info) ? "on" : "off"); + if (cw1200_is_ht(&priv->ht_info)) { + seq_printf(seq, "Greenfield: %s\n", + cw1200_ht_greenfield(&priv->ht_info) ? "yes" : "no"); + seq_printf(seq, "AMPDU dens: %d\n", + cw1200_ht_ampdu_density(&priv->ht_info)); + } + seq_printf(seq, "RSSI thold: %d\n", + priv->cqm_rssi_thold); + seq_printf(seq, "RSSI hyst: %d\n", + priv->cqm_rssi_hyst); + seq_printf(seq, "TXFL thold: %d\n", + priv->cqm_tx_failure_thold); + seq_printf(seq, "Linkloss: %d\n", + priv->cqm_link_loss_count); + seq_printf(seq, "Bcnloss: %d\n", + priv->cqm_beacon_loss_count); + seq_printf(seq, "Long retr: %d\n", + priv->long_frame_max_tx_count); + seq_printf(seq, "Short retr: %d\n", + priv->short_frame_max_tx_count); + spin_lock_bh(&priv->tx_policy_cache.lock); + i = 0; + list_for_each(item, &priv->tx_policy_cache.used) + ++i; + spin_unlock_bh(&priv->tx_policy_cache.lock); + seq_printf(seq, "RC in use: %d\n", i); + seq_printf(seq, "BA stat: %d, %d (%d)\n", + ba_cnt, ba_acc, ba_avg); + seq_printf(seq, "Block ACK: %s\n", ba_ena ? "on" : "off"); + + seq_puts(seq, "\n"); + for (i = 0; i < 4; ++i) { + cw1200_queue_status_show(seq, &priv->tx_queue[i]); + seq_puts(seq, "\n"); + } + + cw1200_debug_print_map(seq, priv, "Link map: ", + priv->link_id_map); + cw1200_debug_print_map(seq, priv, "Asleep map: ", + priv->sta_asleep_mask); + cw1200_debug_print_map(seq, priv, "PSPOLL map: ", + priv->pspoll_mask); + + seq_puts(seq, "\n"); + + for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) { + if (priv->link_id_db[i].status) { + seq_printf(seq, "Link %d: %s, %pM\n", + i + 1, cw1200_debug_link_id[ + priv->link_id_db[i].status], + priv->link_id_db[i].mac); + } + } + + seq_puts(seq, "\n"); + + seq_printf(seq, "BH status: %s\n", + atomic_read(&priv->bh_term) ? "terminated" : "alive"); + seq_printf(seq, "Pending RX: %d\n", + atomic_read(&priv->bh_rx)); + seq_printf(seq, "Pending TX: %d\n", + atomic_read(&priv->bh_tx)); + if (priv->bh_error) + seq_printf(seq, "BH errcode: %d\n", + priv->bh_error); + seq_printf(seq, "TX bufs: %d x %d bytes\n", + priv->wsm_caps.numInpChBufs, + priv->wsm_caps.sizeInpChBuf); + seq_printf(seq, "Used bufs: %d\n", + priv->hw_bufs_used); + seq_printf(seq, "Powermgmt: %s\n", + priv->powersave_enabled ? "on" : "off"); + seq_printf(seq, "Device: %s\n", + priv->device_can_sleep ? "alseep" : "awake"); + + spin_lock(&priv->wsm_cmd.lock); + seq_printf(seq, "WSM status: %s\n", + priv->wsm_cmd.done ? "idle" : "active"); + seq_printf(seq, "WSM cmd: 0x%.4X (%d bytes)\n", + priv->wsm_cmd.cmd, priv->wsm_cmd.len); + seq_printf(seq, "WSM retval: %d\n", + priv->wsm_cmd.ret); + spin_unlock(&priv->wsm_cmd.lock); + + seq_printf(seq, "Datapath: %s\n", + atomic_read(&priv->tx_lock) ? "locked" : "unlocked"); + if (atomic_read(&priv->tx_lock)) + seq_printf(seq, "TXlock cnt: %d\n", + atomic_read(&priv->tx_lock)); + + seq_printf(seq, "TXed: %d\n", + d->tx); + seq_printf(seq, "AGG TXed: %d\n", + d->tx_agg); + seq_printf(seq, "MULTI TXed: %d (%d)\n", + d->tx_multi, d->tx_multi_frames); + seq_printf(seq, "RXed: %d\n", + d->rx); + seq_printf(seq, "AGG RXed: %d\n", + d->rx_agg); + seq_printf(seq, "TX miss: %d\n", + d->tx_cache_miss); + seq_printf(seq, "TX align: %d\n", + d->tx_align); + seq_printf(seq, "TX burst: %d\n", + d->tx_burst); + seq_printf(seq, "RX burst: %d\n", + d->rx_burst); + seq_printf(seq, "TX TTL: %d\n", + d->tx_ttl); + seq_printf(seq, "Scan: %s\n", + atomic_read(&priv->scan.in_progress) ? "active" : "idle"); + seq_printf(seq, "Led state: 0x%.2X\n", + priv->softled_state); + + return 0; +} + +static int cw1200_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, &cw1200_status_show, + inode->i_private); +} + +static const struct file_operations fops_status = { + .open = cw1200_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int cw1200_counters_show(struct seq_file *seq, void *v) +{ + int ret; + struct cw1200_common *priv = seq->private; + struct wsm_counters_table counters; + + ret = wsm_get_counters_table(priv, &counters); + if (ret) + return ret; + +#define CAT_STR(x, y) x ## y +#define PUT_COUNTER(tab, name) \ + seq_printf(seq, "%s:" tab "%d\n", #name, \ + __le32_to_cpu(counters.CAT_STR(count, name))) + + PUT_COUNTER("\t\t", PlcpErrors); + PUT_COUNTER("\t\t", FcsErrors); + PUT_COUNTER("\t\t", TxPackets); + PUT_COUNTER("\t\t", RxPackets); + PUT_COUNTER("\t\t", RxPacketErrors); + PUT_COUNTER("\t", RxDecryptionFailures); + PUT_COUNTER("\t\t", RxMicFailures); + PUT_COUNTER("\t", RxNoKeyFailures); + PUT_COUNTER("\t", TxMulticastFrames); + PUT_COUNTER("\t", TxFramesSuccess); + PUT_COUNTER("\t", TxFrameFailures); + PUT_COUNTER("\t", TxFramesRetried); + PUT_COUNTER("\t", TxFramesMultiRetried); + PUT_COUNTER("\t", RxFrameDuplicates); + PUT_COUNTER("\t\t", RtsSuccess); + PUT_COUNTER("\t\t", RtsFailures); + PUT_COUNTER("\t\t", AckFailures); + PUT_COUNTER("\t", RxMulticastFrames); + PUT_COUNTER("\t", RxFramesSuccess); + PUT_COUNTER("\t", RxCMACICVErrors); + PUT_COUNTER("\t\t", RxCMACReplays); + PUT_COUNTER("\t", RxMgmtCCMPReplays); + +#undef PUT_COUNTER +#undef CAT_STR + + return 0; +} + +static int cw1200_counters_open(struct inode *inode, struct file *file) +{ + return single_open(file, &cw1200_counters_show, + inode->i_private); +} + +static const struct file_operations fops_counters = { + .open = cw1200_counters_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int cw1200_generic_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t cw1200_11n_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cw1200_common *priv = file->private_data; + struct ieee80211_supported_band *band = + priv->hw->wiphy->bands[IEEE80211_BAND_2GHZ]; + return simple_read_from_buffer(user_buf, count, ppos, + band->ht_cap.ht_supported ? "1\n" : "0\n", 2); +} + +static ssize_t cw1200_11n_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cw1200_common *priv = file->private_data; + struct ieee80211_supported_band *band[2] = { + priv->hw->wiphy->bands[IEEE80211_BAND_2GHZ], + priv->hw->wiphy->bands[IEEE80211_BAND_5GHZ], + }; + char buf[1]; + int ena = 0; + + if (!count) + return -EINVAL; + if (copy_from_user(buf, user_buf, 1)) + return -EFAULT; + if (buf[0] == 1) + ena = 1; + + band[0]->ht_cap.ht_supported = ena; +#ifdef CONFIG_CW1200_5GHZ_SUPPORT + band[1]->ht_cap.ht_supported = ena; +#endif /* CONFIG_CW1200_5GHZ_SUPPORT */ + + return count; +} + +static const struct file_operations fops_11n = { + .open = cw1200_generic_open, + .read = cw1200_11n_read, + .write = cw1200_11n_write, + .llseek = default_llseek, +}; + +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) +static ssize_t cw1200_hang_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cw1200_common *priv = file->private_data; + char buf[1]; + + if (!count) + return -EINVAL; + if (copy_from_user(buf, user_buf, 1)) + return -EFAULT; + + if (priv->vif) { + cw1200_pm_stay_awake(&priv->pm_state, 3*HZ); + ieee80211_driver_hang_notify(priv->vif, GFP_KERNEL); + } else + return -ENODEV; + + return count; +} + +static const struct file_operations fops_hang = { + .open = cw1200_generic_open, + .write = cw1200_hang_write, + .llseek = default_llseek, +}; +#endif + +static ssize_t cw1200_wsm_dumps(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cw1200_common *priv = file->private_data; + char buf[1]; + + if (!count) + return -EINVAL; + if (copy_from_user(buf, user_buf, 1)) + return -EFAULT; + + if (buf[0] == '1') + priv->wsm_enable_wsm_dumps = 1; + else + priv->wsm_enable_wsm_dumps = 0; + + return count; +} + +static const struct file_operations fops_wsm_dumps = { + .open = cw1200_generic_open, + .write = cw1200_wsm_dumps, + .llseek = default_llseek, +}; + +#if defined(CONFIG_CW1200_WSM_DUMPS_SHORT) +static ssize_t cw1200_short_dump_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cw1200_common *priv = file->private_data; + char buf[20]; + size_t size = 0; + + sprintf(buf, "Size: %u\n", priv->wsm_dump_max_size); + size = strlen(buf); + + return simple_read_from_buffer(user_buf, count, ppos, + buf, size); +} + +static ssize_t cw1200_short_dump_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cw1200_common *priv = file->private_data; + char buf[20]; + unsigned long dump_size = 0; + + if (!count || count > 20) + return -EINVAL; + if (copy_from_user(buf, user_buf, count)) + return -EFAULT; + + if (kstrtoul(buf, 10, &dump_size)) + return -EINVAL; + printk(KERN_ERR "%s get %lu\n", __func__, dump_size); + + priv->wsm_dump_max_size = dump_size; + + return count; +} + +static const struct file_operations fops_short_dump = { + .open = cw1200_generic_open, + .write = cw1200_short_dump_write, + .read = cw1200_short_dump_read, + .llseek = default_llseek, +}; +#endif /* CONFIG_CW1200_WSM_DUMPS_SHORT */ + +int cw1200_debug_init(struct cw1200_common *priv) +{ + int ret = -ENOMEM; + struct cw1200_debug_priv *d = kzalloc(sizeof(struct cw1200_debug_priv), + GFP_KERNEL); + priv->debug = d; + if (!d) + return ret; + + d->debugfs_phy = debugfs_create_dir("cw1200", + priv->hw->wiphy->debugfsdir); + if (!d->debugfs_phy) + goto err; + + if (!debugfs_create_file("status", S_IRUSR, d->debugfs_phy, + priv, &fops_status)) + goto err; + + if (!debugfs_create_file("counters", S_IRUSR, d->debugfs_phy, + priv, &fops_counters)) + goto err; + + if (!debugfs_create_file("11n", S_IRUSR | S_IWUSR, + d->debugfs_phy, priv, &fops_11n)) + goto err; + +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + if (!debugfs_create_file("hang", S_IWUSR, d->debugfs_phy, + priv, &fops_hang)) + goto err; +#endif + + if (!debugfs_create_file("wsm_dumps", S_IWUSR, d->debugfs_phy, + priv, &fops_wsm_dumps)) + goto err; + +#if defined(CONFIG_CW1200_WSM_DUMPS_SHORT) + if (!debugfs_create_file("wsm_dump_size", S_IRUSR | S_IWUSR, + d->debugfs_phy, priv, &fops_short_dump)) + goto err; +#endif /* CONFIG_CW1200_WSM_DUMPS_SHORT */ + + ret = cw1200_itp_init(priv); + if (ret) + goto err; + + return 0; + +err: + priv->debug = NULL; + debugfs_remove_recursive(d->debugfs_phy); + kfree(d); + return ret; +} + +void cw1200_debug_release(struct cw1200_common *priv) +{ + struct cw1200_debug_priv *d = priv->debug; + if (d) { + cw1200_itp_release(priv); + priv->debug = NULL; + kfree(d); + } +} + +int cw1200_print_fw_version(struct cw1200_common *priv, u8 *buf, size_t len) +{ + return snprintf(buf, len, "%s %d.%d", + cw1200_debug_fw_types[priv->wsm_caps.firmwareType], + priv->wsm_caps.firmwareVersion, + priv->wsm_caps.firmwareBuildNumber); +} diff --git a/drivers/staging/cw1200/debug.h b/drivers/staging/cw1200/debug.h new file mode 100644 index 00000000000..72b827f296b --- /dev/null +++ b/drivers/staging/cw1200/debug.h @@ -0,0 +1,168 @@ +/* + * DebugFS code for ST-Ericsson CW1200 mac80211 driver + * + * Copyright (c) 2011, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef CW1200_DEBUG_H_INCLUDED +#define CW1200_DEBUG_H_INCLUDED + +#include "itp.h" + +struct cw200_common; + +#ifdef CONFIG_CW1200_DEBUGFS + +struct cw1200_debug_priv { + struct dentry *debugfs_phy; + int tx; + int tx_agg; + int rx; + int rx_agg; + int tx_multi; + int tx_multi_frames; + int tx_cache_miss; + int tx_align; + int tx_ttl; + int tx_burst; + int rx_burst; + int ba_cnt; + int ba_acc; +#ifdef CONFIG_CW1200_ITP + struct cw1200_itp itp; +#endif /* CONFIG_CW1200_ITP */ +}; + +int cw1200_debug_init(struct cw1200_common *priv); +void cw1200_debug_release(struct cw1200_common *priv); + +static inline void cw1200_debug_txed(struct cw1200_common *priv) +{ + ++priv->debug->tx; +} + +static inline void cw1200_debug_txed_agg(struct cw1200_common *priv) +{ + ++priv->debug->tx_agg; +} + +static inline void cw1200_debug_txed_multi(struct cw1200_common *priv, + int count) +{ + ++priv->debug->tx_multi; + priv->debug->tx_multi_frames += count; +} + +static inline void cw1200_debug_rxed(struct cw1200_common *priv) +{ + ++priv->debug->rx; +} + +static inline void cw1200_debug_rxed_agg(struct cw1200_common *priv) +{ + ++priv->debug->rx_agg; +} + +static inline void cw1200_debug_tx_cache_miss(struct cw1200_common *priv) +{ + ++priv->debug->tx_cache_miss; +} + +static inline void cw1200_debug_tx_align(struct cw1200_common *priv) +{ + ++priv->debug->tx_align; +} + +static inline void cw1200_debug_tx_ttl(struct cw1200_common *priv) +{ + ++priv->debug->tx_ttl; +} + +static inline void cw1200_debug_tx_burst(struct cw1200_common *priv) +{ + ++priv->debug->tx_burst; +} + +static inline void cw1200_debug_rx_burst(struct cw1200_common *priv) +{ + ++priv->debug->rx_burst; +} + +static inline void cw1200_debug_ba(struct cw1200_common *priv, + int ba_cnt, int ba_acc) +{ + priv->debug->ba_cnt = ba_cnt; + priv->debug->ba_acc = ba_acc; +} + +int cw1200_print_fw_version(struct cw1200_common *priv, u8 *buf, size_t len); + +#else /* CONFIG_CW1200_DEBUGFS */ + +static inline int cw1200_debug_init(struct cw1200_common *priv) +{ + return 0; +} + +static inline void cw1200_debug_release(struct cw1200_common *priv) +{ +} + +static inline void cw1200_debug_txed(struct cw1200_common *priv) +{ +} + +static inline void cw1200_debug_txed_agg(struct cw1200_common *priv) +{ +} + +static inline void cw1200_debug_txed_multi(struct cw1200_common *priv, + int count) +{ +} + +static inline void cw1200_debug_rxed(struct cw1200_common *priv) +{ +} + +static inline void cw1200_debug_rxed_agg(struct cw1200_common *priv) +{ +} + +static inline void cw1200_debug_tx_cache_miss(struct cw1200_common *priv) +{ +} + +static inline void cw1200_debug_tx_align(struct cw1200_common *priv) +{ +} + +static inline void cw1200_debug_tx_ttl(struct cw1200_common *priv) +{ +} + +static inline void cw1200_debug_tx_burst(struct cw1200_common *priv) +{ +} + +static inline void cw1200_debug_rx_burst(struct cw1200_common *priv) +{ +} + +static inline void cw1200_debug_ba(struct cw1200_common *priv, + int ba_cnt, int ba_acc) +{ +} + +int cw1200_print_fw_version(struct cw1200_common *priv, u8 *buf, size_t len) +{ +} + +#endif /* CONFIG_CW1200_DEBUGFS */ + +#endif /* CW1200_DEBUG_H_INCLUDED */ diff --git a/drivers/staging/cw1200/fwio.c b/drivers/staging/cw1200/fwio.c new file mode 100644 index 00000000000..72b77bc8bb0 --- /dev/null +++ b/drivers/staging/cw1200/fwio.c @@ -0,0 +1,594 @@ +/* + * Firmware I/O code for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * Based on: + * ST-Ericsson UMAC CW1200 driver which is + * Copyright (c) 2010, ST-Ericsson + * Author: Ajitpal Singh <ajitpal.singh@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/sched.h> +#include <linux/firmware.h> + +#include "cw1200.h" +#include "fwio.h" +#include "hwio.h" +#include "sbus.h" +#include "bh.h" + +static int cw1200_get_hw_type(u32 config_reg_val, int *major_revision) +{ + int hw_type = -1; + u32 silicon_type = (config_reg_val >> 24) & 0x3; + u32 silicon_vers = (config_reg_val >> 31) & 0x1; + + /* Check if we have CW1200 or STLC9000 */ + if ((silicon_type == 0x1) || (silicon_type == 0x2)) { + *major_revision = silicon_type; + if (silicon_vers) + hw_type = HIF_8601_VERSATILE; + else + hw_type = HIF_8601_SILICON; + } else { + *major_revision = 1; + hw_type = HIF_9000_SILICON_VERSTAILE; + } + + return hw_type; +} + +static int config_reg_read_stlc9000(struct cw1200_common *priv, + u16 reg, u32 *val) +{ + u16 val16; + int ret = cw1200_reg_read_16(priv, reg, &val16); + if (ret < 0) + return ret; + *val = val16; + return 0; +} + +static int config_reg_write_stlc9000(struct cw1200_common *priv, + u16 reg, u32 val) +{ + return cw1200_reg_write_16(priv, reg, (u16)val); +} + +static int cw1200_load_firmware_cw1200(struct cw1200_common *priv) +{ + int ret, block, num_blocks; + unsigned i; + u32 val32; + u32 put = 0, get = 0; + u8 *buf = NULL; + const char *fw_path; + const struct firmware *firmware = NULL; + + /* Macroses are local. */ +#define APB_WRITE(reg, val) \ + do { \ + ret = cw1200_apb_write_32(priv, CW12000_APB(reg), (val)); \ + if (ret < 0) { \ + cw1200_dbg(CW1200_DBG_ERROR, \ + "%s: can't write %s at line %d.\n", \ + __func__, #reg, __LINE__); \ + goto error; \ + } \ + } while (0) +#define APB_READ(reg, val) \ + do { \ + ret = cw1200_apb_read_32(priv, CW12000_APB(reg), &(val)); \ + if (ret < 0) { \ + cw1200_dbg(CW1200_DBG_ERROR, \ + "%s: can't read %s at line %d.\n", \ + __func__, #reg, __LINE__); \ + goto error; \ + } \ + } while (0) +#define REG_WRITE(reg, val) \ + do { \ + ret = cw1200_reg_write_32(priv, (reg), (val)); \ + if (ret < 0) { \ + cw1200_dbg(CW1200_DBG_ERROR, \ + "%s: can't write %s at line %d.\n", \ + __func__, #reg, __LINE__); \ + goto error; \ + } \ + } while (0) +#define REG_READ(reg, val) \ + do { \ + ret = cw1200_reg_read_32(priv, (reg), &(val)); \ + if (ret < 0) { \ + cw1200_dbg(CW1200_DBG_ERROR, \ + "%s: can't read %s at line %d.\n", \ + __func__, #reg, __LINE__); \ + goto error; \ + } \ + } while (0) + + switch (priv->hw_revision) { + case CW1200_HW_REV_CUT10: + fw_path = FIRMWARE_CUT10; + break; + case CW1200_HW_REV_CUT11: + fw_path = FIRMWARE_CUT11; + break; + case CW1200_HW_REV_CUT20: + fw_path = FIRMWARE_CUT20; + break; + case CW1200_HW_REV_CUT22: + fw_path = FIRMWARE_CUT22; + break; + default: + cw1200_dbg(CW1200_DBG_ERROR, + "%s: invalid silicon revision %d.\n", + __func__, priv->hw_revision); + return -EINVAL; + } + + /* Initialize common registers */ + APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, DOWNLOAD_ARE_YOU_HERE); + APB_WRITE(DOWNLOAD_PUT_REG, 0); + APB_WRITE(DOWNLOAD_GET_REG, 0); + APB_WRITE(DOWNLOAD_STATUS_REG, DOWNLOAD_PENDING); + APB_WRITE(DOWNLOAD_FLAGS_REG, 0); + + /* Write the NOP Instruction */ + REG_WRITE(ST90TDS_SRAM_BASE_ADDR_REG_ID, 0xFFF20000); + REG_WRITE(ST90TDS_AHB_DPORT_REG_ID, 0xEAFFFFFE); + + /* Release CPU from RESET */ + REG_READ(ST90TDS_CONFIG_REG_ID, val32); + val32 &= ~ST90TDS_CONFIG_CPU_RESET_BIT; + REG_WRITE(ST90TDS_CONFIG_REG_ID, val32); + + /* Enable Clock */ + val32 &= ~ST90TDS_CONFIG_CPU_CLK_DIS_BIT; + REG_WRITE(ST90TDS_CONFIG_REG_ID, val32); + + /* Load a firmware file */ + ret = request_firmware(&firmware, fw_path, priv->pdev); + if (ret) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: can't load firmware file %s.\n", + __func__, fw_path); + goto error; + } + BUG_ON(!firmware->data); + + buf = kmalloc(DOWNLOAD_BLOCK_SIZE, GFP_KERNEL | GFP_DMA); + if (!buf) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: can't allocate firmware buffer.\n", __func__); + ret = -ENOMEM; + goto error; + } + + /* Check if the bootloader is ready */ + for (i = 0; i < 100; i += 1 + i / 2) { + APB_READ(DOWNLOAD_IMAGE_SIZE_REG, val32); + if (val32 == DOWNLOAD_I_AM_HERE) + break; + mdelay(i); + } /* End of for loop */ + + if (val32 != DOWNLOAD_I_AM_HERE) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: bootloader is not ready.\n", __func__); + ret = -ETIMEDOUT; + goto error; + } + + /* Calculcate number of download blocks */ + num_blocks = (firmware->size - 1) / DOWNLOAD_BLOCK_SIZE + 1; + + /* Updating the length in Download Ctrl Area */ + val32 = firmware->size; /* Explicit cast from size_t to u32 */ + APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, val32); + + /* Firmware downloading loop */ + for (block = 0; block < num_blocks ; block++) { + size_t tx_size; + size_t block_size; + + /* check the download status */ + APB_READ(DOWNLOAD_STATUS_REG, val32); + if (val32 != DOWNLOAD_PENDING) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: bootloader reported error %d.\n", + __func__, val32); + ret = -EIO; + goto error; + } + + /* loop until put - get <= 24K */ + for (i = 0; i < 100; i++) { + APB_READ(DOWNLOAD_GET_REG, get); + if ((put - get) <= + (DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE)) + break; + mdelay(i); + } + + if ((put - get) > (DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE)) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Timeout waiting for FIFO.\n", + __func__); + return -ETIMEDOUT; + } + + /* calculate the block size */ + tx_size = block_size = min((size_t)(firmware->size - put), + (size_t)DOWNLOAD_BLOCK_SIZE); + + memcpy(buf, &firmware->data[put], block_size); + if (block_size < DOWNLOAD_BLOCK_SIZE) { + memset(&buf[block_size], + 0, DOWNLOAD_BLOCK_SIZE - block_size); + tx_size = DOWNLOAD_BLOCK_SIZE; + } + + /* send the block to sram */ + ret = cw1200_apb_write(priv, + CW12000_APB(DOWNLOAD_FIFO_OFFSET + + (put & (DOWNLOAD_FIFO_SIZE - 1))), + buf, tx_size); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: can't write block at line %d.\n", + __func__, __LINE__); + goto error; + } + + /* update the put register */ + put += block_size; + APB_WRITE(DOWNLOAD_PUT_REG, put); + } /* End of firmware download loop */ + + /* Wait for the download completion */ + for (i = 0; i < 300; i += 1 + i / 2) { + APB_READ(DOWNLOAD_STATUS_REG, val32); + if (val32 != DOWNLOAD_PENDING) + break; + mdelay(i); + } + if (val32 != DOWNLOAD_SUCCESS) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: wait for download completion failed. " \ + "Read: 0x%.8X\n", __func__, val32); + ret = -ETIMEDOUT; + goto error; + } else { + cw1200_dbg(CW1200_DBG_MSG, + "Firmware download completed.\n"); + ret = 0; + } + +error: + kfree(buf); + if (firmware) + release_firmware(firmware); + return ret; + +#undef APB_WRITE +#undef APB_READ +#undef REG_WRITE +#undef REG_READ +} + +int cw1200_load_firmware(struct cw1200_common *priv) +{ + int ret; + int i; + u32 val32; + u16 val16; + u32 dpll = 0; + int major_revision; + int (*config_reg_read)(struct cw1200_common *priv, u16 reg, u32 *val); + int (*config_reg_write)(struct cw1200_common *priv, u16 reg, u32 val); + + BUG_ON(!priv); + + /* Read CONFIG Register Value - We will read 32 bits */ + ret = cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: can't read config register.\n", __func__); + goto out; + } + + priv->hw_type = cw1200_get_hw_type(val32, &major_revision); + if (priv->hw_type < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: can't deduct hardware type.\n", __func__); + ret = -ENOTSUPP; + goto out; + } + + switch (priv->hw_type) { + case HIF_8601_VERSATILE: + case HIF_8601_SILICON: + dpll = DPLL_INIT_VAL_CW1200; + config_reg_read = cw1200_reg_read_32; + config_reg_write = cw1200_reg_write_32; + break; + case HIF_9000_SILICON_VERSTAILE: + dpll = DPLL_INIT_VAL_9000; + config_reg_read = config_reg_read_stlc9000; + config_reg_write = config_reg_write_stlc9000; + break; + default: + BUG_ON(1); + } + + ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID, dpll); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: can't write DPLL register.\n", __func__); + goto out; + } + + msleep(20); + + /* Read DPLL Reg value and compare with value written */ + ret = cw1200_reg_read_32(priv, + ST90TDS_TSET_GEN_R_W_REG_ID, &val32); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: can't read DPLL register.\n", __func__); + goto out; + } + + if (val32 != dpll) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: unable to initialise " \ + "DPLL register. Wrote 0x%.8X, read 0x%.8X.\n", + __func__, dpll, val32); + ret = -EIO; + goto out; + } + + /* Set wakeup bit in device */ + ret = cw1200_reg_read_16(priv, ST90TDS_CONTROL_REG_ID, &val16); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: set_wakeup: can't read " \ + "control register.\n", __func__); + goto out; + } + + ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, + val16 | ST90TDS_CONT_WUP_BIT); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: set_wakeup: can't write " \ + "control register.\n", __func__); + goto out; + } + + /* Wait for wakeup */ + for (i = 0 ; i < 300 ; i += 1 + i / 2) { + ret = cw1200_reg_read_16(priv, + ST90TDS_CONTROL_REG_ID, &val16); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: wait_for_wakeup: can't read " \ + "control register.\n", __func__); + goto out; + } + + if (val16 & ST90TDS_CONT_RDY_BIT) { + cw1200_dbg(CW1200_DBG_MSG, + "WLAN device is ready.\n"); + break; + } + msleep(i); + } + + if ((val16 & ST90TDS_CONT_RDY_BIT) == 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: wait_for_wakeup: device is not responding.\n", + __func__); + ret = -ETIMEDOUT; + goto out; + } + + if (major_revision == 1) { + /* CW1200 Hardware detection logic : Check for CUT1.1 */ + ret = cw1200_ahb_read_32(priv, CW1200_CUT_ID_ADDR, &val32); + if (ret) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: HW detection: can't read CUT ID.\n", + __func__); + goto out; + } + + switch (val32) { + case CW1200_CUT_11_ID_STR: + cw1200_dbg(CW1200_DBG_MSG, + "Cut 1.1 silicon is detected.\n"); + priv->hw_revision = CW1200_HW_REV_CUT11; + break; + default: + cw1200_dbg(CW1200_DBG_MSG, + "Cut 1.0 silicon is detected.\n"); + priv->hw_revision = CW1200_HW_REV_CUT10; + break; + } + } else if (major_revision == 2) { + u32 ar1, ar2, ar3; + cw1200_dbg(CW1200_DBG_MSG, "Cut 2.x silicon is detected.\n"); + + ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR, &ar1); + if (ret) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: (1) HW detection: can't read CUT ID.\n", + __func__); + goto out; + } + ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR + 4, &ar2); + if (ret) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: (2) HW detection: can't read CUT ID.\n", + __func__); + goto out; + } + + ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR + 8, &ar3); + if (ret) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: (3) HW detection: can't read CUT ID.\n", + __func__); + goto out; + } + + if (ar1 == CW1200_CUT_22_ID_STR1 && + ar2 == CW1200_CUT_22_ID_STR2 && + ar3 == CW1200_CUT_22_ID_STR3) { + cw1200_dbg(CW1200_DBG_MSG, "Cut 2.2 detected.\n"); + priv->hw_revision = CW1200_HW_REV_CUT22; + } else { + cw1200_dbg(CW1200_DBG_MSG, "Cut 2.0 detected.\n"); + priv->hw_revision = CW1200_HW_REV_CUT20; + } + } else { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: unsupported silicon major revision %d.\n", + __func__, major_revision); + ret = -ENOTSUPP; + goto out; + } + + /* Checking for access mode */ + ret = config_reg_read(priv, ST90TDS_CONFIG_REG_ID, &val32); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: check_access_mode: can't read " \ + "config register.\n", __func__); + goto out; + } + + if (val32 & ST90TDS_CONFIG_ACCESS_MODE_BIT) { + switch (priv->hw_type) { + case HIF_8601_SILICON: + cw1200_dbg(CW1200_DBG_MSG, + "%s: CW1200 detected.\n", __func__); + ret = cw1200_load_firmware_cw1200(priv); + break; + case HIF_8601_VERSATILE: + /* TODO: Not implemented yet! + ret = cw1200_load_firmware_cw1100(priv); + */ + ret = -ENOTSUPP; + goto out; + case HIF_9000_SILICON_VERSTAILE: + /* TODO: Not implemented yet! + ret = cw1200_load_firmware_stlc9000(priv); + */ + ret = -ENOTSUPP; + goto out; + default: + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Unknown hardware: %d.\n", + __func__, priv->hw_type); + } + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: can't download firmware.\n", __func__); + goto out; + } + } else { + cw1200_dbg(CW1200_DBG_MSG, + "%s: check_access_mode: device is already " \ + "in QUEUE mode.\n", __func__); + /* TODO: verify this branch. Do we need something to do? */ + } + + /* Register Interrupt Handler */ + ret = priv->sbus_ops->irq_subscribe(priv->sbus_priv, + (sbus_irq_handler)cw1200_irq_handler, priv); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: can't register IRQ handler.\n", __func__); + goto out; + } + + if (HIF_8601_SILICON == priv->hw_type) { + /* If device is CW1200 the IRQ enable/disable bits + * are in CONFIG register */ + ret = config_reg_read(priv, ST90TDS_CONFIG_REG_ID, &val32); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: enable_irq: can't read " \ + "config register.\n", __func__); + goto unsubscribe; + } + ret = config_reg_write(priv, ST90TDS_CONFIG_REG_ID, + val32 | ST90TDS_CONF_IRQ_RDY_ENABLE); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: enable_irq: can't write " \ + "config register.\n", __func__); + goto unsubscribe; + } + } else { + /* If device is STLC9000 the IRQ enable/disable bits + * are in CONTROL register */ + /* Enable device interrupts - Both DATA_RDY and WLAN_RDY */ + ret = cw1200_reg_read_16(priv, ST90TDS_CONFIG_REG_ID, &val16); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: enable_irq: can't read " \ + "control register.\n", __func__); + goto unsubscribe; + } + ret = cw1200_reg_write_16(priv, ST90TDS_CONFIG_REG_ID, + val16 | ST90TDS_CONT_IRQ_RDY_ENABLE); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: enable_irq: can't write " \ + "control register.\n", __func__); + goto unsubscribe; + } + + } + + /* Configure device for MESSSAGE MODE */ + ret = config_reg_read(priv, ST90TDS_CONFIG_REG_ID, &val32); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: set_mode: can't read config register.\n", + __func__); + goto unsubscribe; + } + ret = config_reg_write(priv, ST90TDS_CONFIG_REG_ID, + val32 & ~ST90TDS_CONFIG_ACCESS_MODE_BIT); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: set_mode: can't write config register.\n", + __func__); + goto unsubscribe; + } + + /* Unless we read the CONFIG Register we are + * not able to get an interrupt */ + mdelay(10); + config_reg_read(priv, ST90TDS_CONFIG_REG_ID, &val32); + +out: + return ret; + +unsubscribe: + priv->sbus_ops->irq_unsubscribe(priv->sbus_priv); + return ret; +} + diff --git a/drivers/staging/cw1200/fwio.h b/drivers/staging/cw1200/fwio.h new file mode 100644 index 00000000000..cb91b8dc481 --- /dev/null +++ b/drivers/staging/cw1200/fwio.h @@ -0,0 +1,36 @@ +/* + * Firmware API for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * Based on: + * ST-Ericsson UMAC CW1200 driver which is + * Copyright (c) 2010, ST-Ericsson + * Author: Ajitpal Singh <ajitpal.singh@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef FWIO_H_INCLUDED +#define FWIO_H_INCLUDED + +#define FIRMWARE_CUT22 ("cw1200/wsm_22.bin") +#define FIRMWARE_CUT20 ("cw1200/wsm_20.bin") +#define FIRMWARE_CUT11 ("cw1200/wsm_11.bin") +#define FIRMWARE_CUT10 ("cw1200/wsm_10.bin") +#define SDD_FILE_22 ("cw1200/sdd_22.bin") +#define SDD_FILE_20 ("cw1200/sdd_20.bin") +#define SDD_FILE_11 ("cw1200/sdd_11.bin") +#define SDD_FILE_10 ("cw1200/sdd_10.bin") + +#define CW1200_HW_REV_CUT10 (10) +#define CW1200_HW_REV_CUT11 (11) +#define CW1200_HW_REV_CUT20 (20) +#define CW1200_HW_REV_CUT22 (22) + +int cw1200_load_firmware(struct cw1200_common *priv); + +#endif diff --git a/drivers/staging/cw1200/ht.h b/drivers/staging/cw1200/ht.h new file mode 100644 index 00000000000..5c486a634c7 --- /dev/null +++ b/drivers/staging/cw1200/ht.h @@ -0,0 +1,43 @@ +/* + * HT-related code for ST-Ericsson CW1200 driver + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef CW1200_HT_H_INCLUDED +#define CW1200_HT_H_INCLUDED + +#include <net/mac80211.h> + +struct cw1200_ht_info { + struct ieee80211_sta_ht_cap ht_cap; + enum nl80211_channel_type channel_type; + u16 operation_mode; +}; + +static inline int cw1200_is_ht(const struct cw1200_ht_info *ht_info) +{ + return ht_info->channel_type != NL80211_CHAN_NO_HT; +} + +static inline int cw1200_ht_greenfield(const struct cw1200_ht_info *ht_info) +{ + return cw1200_is_ht(ht_info) && + (ht_info->ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD) && + !(ht_info->operation_mode & + IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); +} + +static inline int cw1200_ht_ampdu_density(const struct cw1200_ht_info *ht_info) +{ + if (!cw1200_is_ht(ht_info)) + return 0; + return ht_info->ht_cap.ampdu_density; +} + +#endif /* CW1200_HT_H_INCLUDED */ diff --git a/drivers/staging/cw1200/hwio.c b/drivers/staging/cw1200/hwio.c new file mode 100644 index 00000000000..b544a0a4a80 --- /dev/null +++ b/drivers/staging/cw1200/hwio.c @@ -0,0 +1,287 @@ +/* + * Low-level device IO routines for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * Based on: + * ST-Ericsson UMAC CW1200 driver, which is + * Copyright (c) 2010, ST-Ericsson + * Author: Ajitpal Singh <ajitpal.singh@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/types.h> + +#include "cw1200.h" +#include "hwio.h" +#include "sbus.h" + + /* Sdio addr is 4*spi_addr */ +#define SPI_REG_ADDR_TO_SDIO(spi_reg_addr) ((spi_reg_addr) << 2) +#define SDIO_ADDR17BIT(buf_id, mpf, rfu, reg_id_ofs) \ + ((((buf_id) & 0x1F) << 7) \ + | (((mpf) & 1) << 6) \ + | (((rfu) & 1) << 5) \ + | (((reg_id_ofs) & 0x1F) << 0)) +#define MAX_RETRY 3 + + +static int __cw1200_reg_read(struct cw1200_common *priv, u16 addr, + void *buf, size_t buf_len, int buf_id) +{ + u16 addr_sdio; + u32 sdio_reg_addr_17bit ; + + /* Check if buffer is aligned to 4 byte boundary */ + if (WARN_ON(((unsigned long)buf & 3) && (buf_len > 4))) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: buffer is not aligned.\n", __func__); + return -EINVAL; + } + + /* Convert to SDIO Register Address */ + addr_sdio = SPI_REG_ADDR_TO_SDIO(addr); + sdio_reg_addr_17bit = SDIO_ADDR17BIT(buf_id, 0, 0, addr_sdio); + + BUG_ON(!priv->sbus_ops); + return priv->sbus_ops->sbus_memcpy_fromio(priv->sbus_priv, + sdio_reg_addr_17bit, + buf, buf_len); +} + +static int __cw1200_reg_write(struct cw1200_common *priv, u16 addr, + const void *buf, size_t buf_len, int buf_id) +{ + u16 addr_sdio; + u32 sdio_reg_addr_17bit ; + +#if 0 + /* Check if buffer is aligned to 4 byte boundary */ + if (WARN_ON(((unsigned long)buf & 3) && (buf_len > 4))) { + cw1200_dbg(CW1200_DBG_ERROR, "%s: buffer is not aligned.\n", + __func__); + return -EINVAL; + } +#endif + + /* Convert to SDIO Register Address */ + addr_sdio = SPI_REG_ADDR_TO_SDIO(addr); + sdio_reg_addr_17bit = SDIO_ADDR17BIT(buf_id, 0, 0, addr_sdio); + + BUG_ON(!priv->sbus_ops); + return priv->sbus_ops->sbus_memcpy_toio(priv->sbus_priv, + sdio_reg_addr_17bit, + buf, buf_len); +} + +static inline int __cw1200_reg_read_32(struct cw1200_common *priv, + u16 addr, u32 *val) +{ + return __cw1200_reg_read(priv, addr, val, sizeof(val), 0); +} + +static inline int __cw1200_reg_write_32(struct cw1200_common *priv, + u16 addr, u32 val) +{ + return __cw1200_reg_write(priv, addr, &val, sizeof(val), 0); +} + +int cw1200_reg_read(struct cw1200_common *priv, u16 addr, void *buf, + size_t buf_len) +{ + int ret; + BUG_ON(!priv->sbus_ops); + priv->sbus_ops->lock(priv->sbus_priv); + ret = __cw1200_reg_read(priv, addr, buf, buf_len, 0); + priv->sbus_ops->unlock(priv->sbus_priv); + return ret; +} + +int cw1200_reg_write(struct cw1200_common *priv, u16 addr, const void *buf, + size_t buf_len) +{ + int ret; + BUG_ON(!priv->sbus_ops); + priv->sbus_ops->lock(priv->sbus_priv); + ret = __cw1200_reg_write(priv, addr, buf, buf_len, 0); + priv->sbus_ops->unlock(priv->sbus_priv); + return ret; +} + +int cw1200_data_read(struct cw1200_common *priv, void *buf, size_t buf_len) +{ + int ret, retry = 1; + BUG_ON(!priv->sbus_ops); + priv->sbus_ops->lock(priv->sbus_priv); + { + int buf_id_rx = priv->buf_id_rx; + while (retry <= MAX_RETRY) { + ret = __cw1200_reg_read(priv, + ST90TDS_IN_OUT_QUEUE_REG_ID, buf, + buf_len, buf_id_rx + 1); + if (!ret) { + buf_id_rx = (buf_id_rx + 1) & 3; + priv->buf_id_rx = buf_id_rx; + break; + } else { + retry++; + mdelay(1); + cw1200_dbg(CW1200_DBG_ERROR, "%s,error :[%d]\n", + __func__, ret); + } + } + } + priv->sbus_ops->unlock(priv->sbus_priv); + return ret; +} + +int cw1200_data_write(struct cw1200_common *priv, const void *buf, + size_t buf_len) +{ + int ret, retry = 1; + BUG_ON(!priv->sbus_ops); + priv->sbus_ops->lock(priv->sbus_priv); + { + int buf_id_tx = priv->buf_id_tx; + while (retry <= MAX_RETRY) { + ret = __cw1200_reg_write(priv, + ST90TDS_IN_OUT_QUEUE_REG_ID, buf, + buf_len, buf_id_tx); + if (!ret) { + buf_id_tx = (buf_id_tx + 1) & 31; + priv->buf_id_tx = buf_id_tx; + break; + } else { + retry++; + mdelay(1); + cw1200_dbg(CW1200_DBG_ERROR, "%s,error :[%d]\n", + __func__, ret); + } + } + } + priv->sbus_ops->unlock(priv->sbus_priv); + return ret; +} + +int cw1200_indirect_read(struct cw1200_common *priv, u32 addr, void *buf, + size_t buf_len, u32 prefetch, u16 port_addr) +{ + u32 val32 = 0; + int i, ret; + + if ((buf_len / 2) >= 0x1000) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Can't read more than 0xfff words.\n", + __func__); + WARN_ON(1); + return -EINVAL; + goto out; + } + + priv->sbus_ops->lock(priv->sbus_priv); + /* Write address */ + ret = __cw1200_reg_write_32(priv, ST90TDS_SRAM_BASE_ADDR_REG_ID, addr); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Can't write address register.\n", + __func__); + goto out; + } + + /* Read CONFIG Register Value - We will read 32 bits */ + ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Can't read config register.\n", + __func__); + goto out; + } + + /* Set PREFETCH bit */ + ret = __cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID, + val32 | prefetch); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Can't write prefetch bit.\n", + __func__); + goto out; + } + + /* Check for PRE-FETCH bit to be cleared */ + for (i = 0; i < 20; i++) { + ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Can't check prefetch bit.\n", + __func__); + goto out; + } + if (!(val32 & prefetch)) + break; + + mdelay(i); + } + + if (val32 & prefetch) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Prefetch bit is not cleared.\n", + __func__); + goto out; + } + + /* Read data port */ + ret = __cw1200_reg_read(priv, port_addr, buf, buf_len, 0); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Can't read data port.\n", + __func__); + goto out; + } + +out: + priv->sbus_ops->unlock(priv->sbus_priv); + return ret; +} + +int cw1200_apb_write(struct cw1200_common *priv, u32 addr, const void *buf, + size_t buf_len) +{ + int ret; + + if ((buf_len / 2) >= 0x1000) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Can't wrire more than 0xfff words.\n", + __func__); + WARN_ON(1); + return -EINVAL; + } + + priv->sbus_ops->lock(priv->sbus_priv); + + /* Write address */ + ret = __cw1200_reg_write_32(priv, ST90TDS_SRAM_BASE_ADDR_REG_ID, addr); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: Can't write address register.\n", + __func__); + goto out; + } + + /* Write data port */ + ret = __cw1200_reg_write(priv, ST90TDS_SRAM_DPORT_REG_ID, + buf, buf_len, 0); + if (ret < 0) { + cw1200_dbg(CW1200_DBG_ERROR, "%s: Can't write data port.\n", + __func__); + goto out; + } + +out: + priv->sbus_ops->unlock(priv->sbus_priv); + return ret; +} + diff --git a/drivers/staging/cw1200/hwio.h b/drivers/staging/cw1200/hwio.h new file mode 100644 index 00000000000..25c8f6b4c28 --- /dev/null +++ b/drivers/staging/cw1200/hwio.h @@ -0,0 +1,243 @@ +/* + * Low-level API for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * Based on: + * ST-Ericsson UMAC CW1200 driver which is + * Copyright (c) 2010, ST-Ericsson + * Author: Ajitpal Singh <ajitpal.singh@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef CW1200_HWIO_H_INCLUDED +#define CW1200_HWIO_H_INCLUDED + +/* extern */ struct cw1200_common; + +/* DPLL initial values */ +#define DPLL_INIT_VAL_9000 (0x00000191) +#define DPLL_INIT_VAL_CW1200 (0x0EC4F121) + +/* Hardware Type Definitions */ +#define HIF_8601_VERSATILE (0) +#define HIF_8601_SILICON (1) +#define HIF_9000_SILICON_VERSTAILE (2) + +#define CW1200_CUT_11_ID_STR (0x302E3830) +#define CW1200_CUT_22_ID_STR1 (0x302e3132) +#define CW1200_CUT_22_ID_STR2 (0x32302e30) +#define CW1200_CUT_22_ID_STR3 (0x3335) +#define CW1200_CUT_ID_ADDR (0xFFF17F90) +#define CW1200_CUT2_ID_ADDR (0xFFF1FF90) + +/* Download control area */ +/* boot loader start address in SRAM */ +#define DOWNLOAD_BOOT_LOADER_OFFSET (0x00000000) +/* 32K, 0x4000 to 0xDFFF */ +#define DOWNLOAD_FIFO_OFFSET (0x00004000) +/* 32K */ +#define DOWNLOAD_FIFO_SIZE (0x00008000) +/* 128 bytes, 0xFF80 to 0xFFFF */ +#define DOWNLOAD_CTRL_OFFSET (0x0000FF80) +#define DOWNLOAD_CTRL_DATA_DWORDS (32-6) + +struct download_cntl_t { + /* size of whole firmware file (including Cheksum), host init */ + u32 ImageSize; + /* downloading flags */ + u32 Flags; + /* No. of bytes put into the download, init & updated by host */ + u32 Put; + /* last traced program counter, last ARM reg_pc */ + u32 TracePc; + /* No. of bytes read from the download, host init, device updates */ + u32 Get; + /* r0, boot losader status, host init to pending, device updates */ + u32 Status; + /* Extra debug info, r1 to r14 if status=r0=DOWNLOAD_EXCEPTION */ + u32 DebugData[DOWNLOAD_CTRL_DATA_DWORDS]; +}; + +#define DOWNLOAD_IMAGE_SIZE_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, ImageSize)) +#define DOWNLOAD_FLAGS_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, Flags)) +#define DOWNLOAD_PUT_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, Put)) +#define DOWNLOAD_TRACE_PC_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, TracePc)) +#define DOWNLOAD_GET_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, Get)) +#define DOWNLOAD_STATUS_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, Status)) +#define DOWNLOAD_DEBUG_DATA_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, DebugData)) +#define DOWNLOAD_DEBUG_DATA_LEN (108) + +#define DOWNLOAD_BLOCK_SIZE (1024) + +/* For boot loader detection */ +#define DOWNLOAD_ARE_YOU_HERE (0x87654321) +#define DOWNLOAD_I_AM_HERE (0x12345678) + +/* Download error code */ +#define DOWNLOAD_PENDING (0xFFFFFFFF) +#define DOWNLOAD_SUCCESS (0) +#define DOWNLOAD_EXCEPTION (1) +#define DOWNLOAD_ERR_MEM_1 (2) +#define DOWNLOAD_ERR_MEM_2 (3) +#define DOWNLOAD_ERR_SOFTWARE (4) +#define DOWNLOAD_ERR_FILE_SIZE (5) +#define DOWNLOAD_ERR_CHECKSUM (6) +#define DOWNLOAD_ERR_OVERFLOW (7) +#define DOWNLOAD_ERR_IMAGE (8) +#define DOWNLOAD_ERR_HOST (9) +#define DOWNLOAD_ERR_ABORT (10) + + +#define SYS_BASE_ADDR_SILICON (0) +#define PAC_BASE_ADDRESS_SILICON (SYS_BASE_ADDR_SILICON + 0x09000000) +#define PAC_SHARED_MEMORY_SILICON (PAC_BASE_ADDRESS_SILICON) + +#define CW12000_APB(addr) (PAC_SHARED_MEMORY_SILICON + (addr)) + +/* *************************************************************** +*Device register definitions +*************************************************************** */ +/* WBF - SPI Register Addresses */ +#define ST90TDS_ADDR_ID_BASE (0x0000) +/* 16/32 bits */ +#define ST90TDS_CONFIG_REG_ID (0x0000) +/* 16/32 bits */ +#define ST90TDS_CONTROL_REG_ID (0x0001) +/* 16 bits, Q mode W/R */ +#define ST90TDS_IN_OUT_QUEUE_REG_ID (0x0002) +/* 32 bits, AHB bus R/W */ +#define ST90TDS_AHB_DPORT_REG_ID (0x0003) +/* 16/32 bits */ +#define ST90TDS_SRAM_BASE_ADDR_REG_ID (0x0004) +/* 32 bits, APB bus R/W */ +#define ST90TDS_SRAM_DPORT_REG_ID (0x0005) +/* 32 bits, t_settle/general */ +#define ST90TDS_TSET_GEN_R_W_REG_ID (0x0006) +/* 16 bits, Q mode read, no length */ +#define ST90TDS_FRAME_OUT_REG_ID (0x0007) +#define ST90TDS_ADDR_ID_MAX (ST90TDS_FRAME_OUT_REG_ID) + +/* WBF - Control register bit set */ +/* next o/p length, bit 11 to 0 */ +#define ST90TDS_CONT_NEXT_LEN_MASK (0x0FFF) +#define ST90TDS_CONT_WUP_BIT (BIT(12)) +#define ST90TDS_CONT_RDY_BIT (BIT(13)) +#define ST90TDS_CONT_IRQ_ENABLE (BIT(14)) +#define ST90TDS_CONT_RDY_ENABLE (BIT(15)) +#define ST90TDS_CONT_IRQ_RDY_ENABLE (BIT(14)|BIT(15)) + +/* SPI Config register bit set */ +#define ST90TDS_CONFIG_FRAME_BIT (BIT(2)) +#define ST90TDS_CONFIG_WORD_MODE_BITS (BIT(3)|BIT(4)) +#define ST90TDS_CONFIG_WORD_MODE_1 (BIT(3)) +#define ST90TDS_CONFIG_WORD_MODE_2 (BIT(4)) +#define ST90TDS_CONFIG_ERROR_0_BIT (BIT(5)) +#define ST90TDS_CONFIG_ERROR_1_BIT (BIT(6)) +#define ST90TDS_CONFIG_ERROR_2_BIT (BIT(7)) +/* TBD: Sure??? */ +#define ST90TDS_CONFIG_CSN_FRAME_BIT (BIT(7)) +#define ST90TDS_CONFIG_ERROR_3_BIT (BIT(8)) +#define ST90TDS_CONFIG_ERROR_4_BIT (BIT(9)) +/* QueueM */ +#define ST90TDS_CONFIG_ACCESS_MODE_BIT (BIT(10)) +/* AHB bus */ +#define ST90TDS_CONFIG_AHB_PFETCH_BIT (BIT(11)) +#define ST90TDS_CONFIG_CPU_CLK_DIS_BIT (BIT(12)) +/* APB bus */ +#define ST90TDS_CONFIG_PFETCH_BIT (BIT(13)) +/* cpu reset */ +#define ST90TDS_CONFIG_CPU_RESET_BIT (BIT(14)) +#define ST90TDS_CONFIG_CLEAR_INT_BIT (BIT(15)) + +/* For CW1200 the IRQ Enable and Ready Bits are in CONFIG register */ +#define ST90TDS_CONF_IRQ_RDY_ENABLE (BIT(16)|BIT(17)) + +int cw1200_data_read(struct cw1200_common *priv, + void *buf, size_t buf_len); +int cw1200_data_write(struct cw1200_common *priv, + const void *buf, size_t buf_len); + +int cw1200_reg_read(struct cw1200_common *priv, u16 addr, + void *buf, size_t buf_len); +int cw1200_reg_write(struct cw1200_common *priv, u16 addr, + const void *buf, size_t buf_len); + +static inline int cw1200_reg_read_16(struct cw1200_common *priv, + u16 addr, u16 *val) +{ + u32 bigVal; + int ret; + ret = cw1200_reg_read(priv, addr, &bigVal, sizeof(bigVal)); + *val = (u16)bigVal; + return ret; +} + +static inline int cw1200_reg_write_16(struct cw1200_common *priv, + u16 addr, u16 val) +{ + u32 bigVal = (u32)val; + return cw1200_reg_write(priv, addr, &bigVal, sizeof(bigVal)); +} + +static inline int cw1200_reg_read_32(struct cw1200_common *priv, + u16 addr, u32 *val) +{ + return cw1200_reg_read(priv, addr, val, sizeof(val)); +} + +static inline int cw1200_reg_write_32(struct cw1200_common *priv, + u16 addr, u32 val) +{ + return cw1200_reg_write(priv, addr, &val, sizeof(val)); +} + +int cw1200_indirect_read(struct cw1200_common *priv, u32 addr, void *buf, + size_t buf_len, u32 prefetch, u16 port_addr); +int cw1200_apb_write(struct cw1200_common *priv, u32 addr, const void *buf, + size_t buf_len); + +static inline int cw1200_apb_read(struct cw1200_common *priv, u32 addr, + void *buf, size_t buf_len) +{ + return cw1200_indirect_read(priv, addr, buf, buf_len, + ST90TDS_CONFIG_PFETCH_BIT, ST90TDS_SRAM_DPORT_REG_ID); +} + +static inline int cw1200_ahb_read(struct cw1200_common *priv, u32 addr, + void *buf, size_t buf_len) +{ + return cw1200_indirect_read(priv, addr, buf, buf_len, + ST90TDS_CONFIG_AHB_PFETCH_BIT, ST90TDS_AHB_DPORT_REG_ID); +} + +static inline int cw1200_apb_read_32(struct cw1200_common *priv, + u32 addr, u32 *val) +{ + return cw1200_apb_read(priv, addr, val, sizeof(val)); +} + +static inline int cw1200_apb_write_32(struct cw1200_common *priv, + u32 addr, u32 val) +{ + return cw1200_apb_write(priv, addr, &val, sizeof(val)); +} + +static inline int cw1200_ahb_read_32(struct cw1200_common *priv, + u32 addr, u32 *val) +{ + return cw1200_ahb_read(priv, addr, val, sizeof(val)); +} + +#endif /* CW1200_HWIO_H_INCLUDED */ diff --git a/drivers/staging/cw1200/itp.c b/drivers/staging/cw1200/itp.c new file mode 100644 index 00000000000..eb7e53bf096 --- /dev/null +++ b/drivers/staging/cw1200/itp.c @@ -0,0 +1,739 @@ +/* + * mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers + * ITP code + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/poll.h> +#include <linux/time.h> +#include <linux/kallsyms.h> +#include <net/mac80211.h> +#include "cw1200.h" +#include "debug.h" +#include "itp.h" +#include "sta.h" + +static int __cw1200_itp_open(struct cw1200_common *priv); +static int __cw1200_itp_close(struct cw1200_common *priv); +static void cw1200_itp_rx_start(struct cw1200_common *priv); +static void cw1200_itp_rx_stop(struct cw1200_common *priv); +static void cw1200_itp_rx_stats(struct cw1200_common *priv); +static void cw1200_itp_rx_reset(struct cw1200_common *priv); +static void cw1200_itp_tx_stop(struct cw1200_common *priv); +static void cw1200_itp_handle(struct cw1200_common *priv, + struct sk_buff *skb); +static void cw1200_itp_err(struct cw1200_common *priv, + int err, + int arg); +static void __cw1200_itp_tx_stop(struct cw1200_common *priv); + +static ssize_t cw1200_itp_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cw1200_common *priv = file->private_data; + struct cw1200_itp *itp = &priv->debug->itp; + struct sk_buff *skb; + int ret; + + if (skb_queue_empty(&itp->log_queue)) + return 0; + + skb = skb_dequeue(&itp->log_queue); + ret = copy_to_user(user_buf, skb->data, skb->len); + *ppos += skb->len; + skb->data[skb->len] = 0; + itp_printk(KERN_DEBUG "[ITP] >>> %s", skb->data); + consume_skb(skb); + + return skb->len - ret; +} + +static ssize_t cw1200_itp_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cw1200_common *priv = file->private_data; + struct sk_buff *skb; + + if (!count || count > 1024) + return -EINVAL; + skb = dev_alloc_skb(count + 1); + if (!skb) + return -ENOMEM; + skb_trim(skb, 0); + skb_put(skb, count + 1); + if (copy_from_user(skb->data, user_buf, count)) { + kfree_skb(skb); + return -EFAULT; + } + skb->data[count] = 0; + + cw1200_itp_handle(priv, skb); + consume_skb(skb); + return count; +} + +static unsigned int cw1200_itp_poll(struct file *file, poll_table *wait) +{ + struct cw1200_common *priv = file->private_data; + struct cw1200_itp *itp = &priv->debug->itp; + unsigned int mask = 0; + + poll_wait(file, &itp->read_wait, wait); + + if (!skb_queue_empty(&itp->log_queue)) + mask |= POLLIN | POLLRDNORM; + + mask |= POLLOUT | POLLWRNORM; + + return mask; +} + +static int cw1200_itp_open(struct inode *inode, struct file *file) +{ + struct cw1200_common *priv = inode->i_private; + struct cw1200_itp *itp = &priv->debug->itp; + int ret = 0; + + file->private_data = priv; + if (atomic_inc_return(&itp->open_count) == 1) { + ret = __cw1200_itp_open(priv); + if (ret && !atomic_dec_return(&itp->open_count)) + __cw1200_itp_close(priv); + } else { + atomic_dec(&itp->open_count); + ret = -EBUSY; + } + + return ret; +} + +static int cw1200_itp_close(struct inode *inode, struct file *file) +{ + struct cw1200_common *priv = file->private_data; + struct cw1200_itp *itp = &priv->debug->itp; + if (!atomic_dec_return(&itp->open_count)) { + __cw1200_itp_close(priv); + wake_up(&itp->close_wait); + } + return 0; +} + +static const struct file_operations fops_itp = { + .open = cw1200_itp_open, + .read = cw1200_itp_read, + .write = cw1200_itp_write, + .poll = cw1200_itp_poll, + .release = cw1200_itp_close, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static void cw1200_itp_fill_pattern(u8 *data, int size, + enum cw1200_itp_data_modes mode) +{ + u8 *p = data; + + if (size <= 0) + return; + + switch (mode) { + default: + case ITP_DATA_ZEROS: + memset(data, 0x0, size); + break; + case ITP_DATA_ONES: + memset(data, 0xff, size); + break; + case ITP_DATA_ZERONES: + memset(data, 0x55, size); + break; + case ITP_DATA_RANDOM: + while (p < data+size-sizeof(u32)) { + (*(u32 *)p) = random32(); + p += sizeof(u32); + } + while (p < data+size) { + (*p) = random32() & 0xFF; + p++; + } + break; + } + return; +} + +static void cw1200_itp_tx_work(struct work_struct *work) +{ + struct cw1200_itp *itp = container_of(work, struct cw1200_itp, + tx_work.work); + struct cw1200_common *priv = itp->priv; + atomic_set(&priv->bh_tx, 1); + wake_up(&priv->bh_wq); +} + +static void cw1200_itp_tx_finish(struct work_struct *work) +{ + struct cw1200_itp *itp = container_of(work, struct cw1200_itp, + tx_finish.work); + __cw1200_itp_tx_stop(itp->priv); +} + +int cw1200_itp_init(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + + itp->priv = priv; + atomic_set(&itp->open_count, 0); + atomic_set(&itp->stop_tx, 0); + atomic_set(&itp->awaiting_confirm, 0); + skb_queue_head_init(&itp->log_queue); + spin_lock_init(&itp->tx_lock); + init_waitqueue_head(&itp->read_wait); + init_waitqueue_head(&itp->write_wait); + init_waitqueue_head(&itp->close_wait); + INIT_DELAYED_WORK(&itp->tx_work, cw1200_itp_tx_work); + INIT_DELAYED_WORK(&itp->tx_finish, cw1200_itp_tx_finish); + itp->data = NULL; + itp->hdr_len = WSM_TX_EXTRA_HEADROOM + + sizeof(struct ieee80211_hdr_3addr); + + if (!debugfs_create_file("itp", S_IRUSR | S_IWUSR, + priv->debug->debugfs_phy, priv, &fops_itp)) + return -ENOMEM; + + return 0; +} + +void cw1200_itp_release(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + + wait_event_interruptible(itp->close_wait, + !atomic_read(&itp->open_count)); + + WARN_ON(atomic_read(&itp->open_count)); + + skb_queue_purge(&itp->log_queue); + cw1200_itp_tx_stop(priv); +} + +static int __cw1200_itp_open(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + + if (!priv->vif) + return -EINVAL; + if (priv->join_status) + return -EINVAL; + itp->saved_channel = priv->channel; + if (!priv->channel) + priv->channel = &priv->hw-> + wiphy->bands[IEEE80211_BAND_2GHZ]->channels[0]; + wsm_set_bssid_filtering(priv, false); + cw1200_itp_rx_reset(priv); + return 0; +} + +static int __cw1200_itp_close(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + if (atomic_read(&itp->test_mode) == TEST_MODE_RX_TEST) + cw1200_itp_rx_stop(priv); + cw1200_itp_tx_stop(priv); + cw1200_disable_listening(priv); + cw1200_update_filtering(priv); + priv->channel = itp->saved_channel; + return 0; +} + +bool cw1200_is_itp(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + return atomic_read(&itp->open_count) != 0; +} + +static void cw1200_itp_rx_reset(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + itp->rx_cnt = 0; + itp->rx_rssi = 0; + itp->rx_rssi_max = -1000; + itp->rx_rssi_min = 1000; +} + +static void cw1200_itp_rx_start(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + + itp_printk(KERN_DEBUG "[ITP] RX start, band = %d, ch = %d\n", + itp->band, itp->ch); + atomic_set(&itp->test_mode, TEST_MODE_RX_TEST); + cw1200_update_listening(priv, false); + priv->channel = &priv->hw-> + wiphy->bands[itp->band]->channels[itp->ch]; + cw1200_update_listening(priv, true); + wsm_set_bssid_filtering(priv, false); +} + +static void cw1200_itp_rx_stop(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + itp_printk(KERN_DEBUG "[ITP] RX stop\n"); + atomic_set(&itp->test_mode, TEST_MODE_NO_TEST); + cw1200_itp_rx_reset(priv); +} + +static void cw1200_itp_rx_stats(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + struct sk_buff *skb; + char buf[128]; + int len, ret; + struct wsm_counters_table counters; + + ret = wsm_get_counters_table(priv, &counters); + + if (ret) + cw1200_itp_err(priv, -EBUSY, 20); + + if (!itp->rx_cnt) + len = snprintf(buf, sizeof(buf), "1,0,0,0,0,%d\n", + counters.countRxPacketErrors); + else + len = snprintf(buf, sizeof(buf), "1,%d,%ld,%d,%d,%d\n", + itp->rx_cnt, + itp->rx_cnt ? itp->rx_rssi / itp->rx_cnt : 0, + itp->rx_rssi_min, itp->rx_rssi_max, + counters.countRxPacketErrors); + + if (len <= 0) { + cw1200_itp_err(priv, -EBUSY, 21); + return; + } + + skb = dev_alloc_skb(len); + if (!skb) { + cw1200_itp_err(priv, -ENOMEM, 22); + return; + } + + itp->rx_cnt = 0; + itp->rx_rssi = 0; + itp->rx_rssi_max = -1000; + itp->rx_rssi_min = 1000; + + skb_trim(skb, 0); + skb_put(skb, len); + + memcpy(skb->data, buf, len); + skb_queue_tail(&itp->log_queue, skb); + wake_up(&itp->read_wait); +} + +static void cw1200_itp_tx_start(struct cw1200_common *priv) +{ + struct wsm_tx *tx; + struct ieee80211_hdr_3addr *hdr; + struct cw1200_itp *itp = &priv->debug->itp; + struct wsm_association_mode assoc_mode = { + .flags = WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE, + .preambleType = itp->preamble, + }; + int len; + u8 da_addr[6] = ITP_DEFAULT_DA_ADDR; + + /* Rates index 4 and 5 are not supported */ + if (itp->rate > 3) + itp->rate += 2; + + itp_printk(KERN_DEBUG "[ITP] TX start: band = %d, ch = %d, rate = %d," + " preamble = %d, number = %d, data_mode = %d," + " interval = %d, power = %d, data_len = %d\n", + itp->band, itp->ch, itp->rate, itp->preamble, + itp->number, itp->data_mode, itp->interval_us, + itp->power, itp->data_len); + + len = itp->hdr_len + itp->data_len; + + itp->data = kmalloc(len, GFP_KERNEL); + tx = (struct wsm_tx *)itp->data; + tx->hdr.len = itp->data_len + itp->hdr_len; + tx->hdr.id = __cpu_to_le16(0x0004 | 1 << 6); + tx->maxTxRate = itp->rate; + tx->queueId = 3; + tx->more = 0; + tx->flags = 0xc; + tx->packetID = 0x55ff55; + tx->reserved = 0; + tx->expireTime = 1; + + if (itp->preamble == ITP_PREAMBLE_GREENFIELD) + tx->htTxParameters = WSM_HT_TX_GREENFIELD; + else if (itp->preamble == ITP_PREAMBLE_MIXED) + tx->htTxParameters = WSM_HT_TX_MIXED; + + hdr = (struct ieee80211_hdr_3addr *)&itp->data[sizeof(struct wsm_tx)]; + memset(hdr, 0, sizeof(*hdr)); + hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_FCTL_TODS); + memcpy(hdr->addr1, da_addr, ETH_ALEN); + memcpy(hdr->addr2, priv->vif->addr, ETH_ALEN); + memcpy(hdr->addr3, da_addr, ETH_ALEN); + + cw1200_itp_fill_pattern(&itp->data[itp->hdr_len], + itp->data_len, itp->data_mode); + + cw1200_update_listening(priv, false); + priv->channel = &priv->hw-> + wiphy->bands[itp->band]->channels[itp->ch]; + WARN_ON(wsm_set_output_power(priv, itp->power)); + if (itp->preamble == ITP_PREAMBLE_SHORT || + itp->preamble == ITP_PREAMBLE_LONG) + WARN_ON(wsm_set_association_mode(priv, + &assoc_mode)); + wsm_set_bssid_filtering(priv, false); + cw1200_update_listening(priv, true); + + spin_lock_bh(&itp->tx_lock); + atomic_set(&itp->test_mode, TEST_MODE_TX_TEST); + atomic_set(&itp->awaiting_confirm, 0); + atomic_set(&itp->stop_tx, 0); + atomic_set(&priv->bh_tx, 1); + ktime_get_ts(&itp->last_sent); + wake_up(&priv->bh_wq); + spin_unlock_bh(&itp->tx_lock); +} + +void __cw1200_itp_tx_stop(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + spin_lock_bh(&itp->tx_lock); + kfree(itp->data); + itp->data = NULL; + atomic_set(&itp->test_mode, TEST_MODE_NO_TEST); + spin_unlock_bh(&itp->tx_lock); +} + +static void cw1200_itp_tx_stop(struct cw1200_common *priv) +{ + struct cw1200_itp *itp = &priv->debug->itp; + itp_printk(KERN_DEBUG "[ITP] TX stop\n"); + atomic_set(&itp->stop_tx, 1); + flush_workqueue(priv->workqueue); + + /* time for FW to confirm all tx requests */ + msleep(500); + + __cw1200_itp_tx_stop(priv); +} + +static void cw1200_itp_get_version(struct cw1200_common *priv, + enum cw1200_itp_version_type type) +{ + struct cw1200_itp *itp = &priv->debug->itp; + struct sk_buff *skb; + char buf[ITP_BUF_SIZE]; + size_t size = 0; + int len; + itp_printk(KERN_DEBUG "[ITP] print %s version\n", type == ITP_CHIP_ID ? + "chip" : "firmware"); + + len = snprintf(buf, ITP_BUF_SIZE, "2,"); + if (len <= 0) { + cw1200_itp_err(priv, -EINVAL, 40); + return; + } + size += len; + + switch (type) { + case ITP_CHIP_ID: + len = cw1200_print_fw_version(priv, buf+size, + ITP_BUF_SIZE - size); + + if (len <= 0) { + cw1200_itp_err(priv, -EINVAL, 41); + return; + } + size += len; + break; + case ITP_FW_VER: + len = snprintf(buf+size, ITP_BUF_SIZE - size, + "%d.%d", priv->wsm_caps.hardwareId, + priv->wsm_caps.hardwareSubId); + if (len <= 0) { + cw1200_itp_err(priv, -EINVAL, 42); + return; + } + size += len; + break; + default: + cw1200_itp_err(priv, -EINVAL, 43); + break; + } + + len = snprintf(buf+size, ITP_BUF_SIZE-size, "\n"); + if (len <= 0) { + cw1200_itp_err(priv, -EINVAL, 44); + return; + } + size += len; + + skb = dev_alloc_skb(size); + if (!skb) { + cw1200_itp_err(priv, -ENOMEM, 45); + return; + } + + skb_trim(skb, 0); + skb_put(skb, size); + + memcpy(skb->data, buf, size); + skb_queue_tail(&itp->log_queue, skb); + wake_up(&itp->read_wait); +} + +int cw1200_itp_get_tx(struct cw1200_common *priv, u8 **data, + size_t *tx_len, int *burst) +{ + struct cw1200_itp *itp; + struct timespec now; + int time_left_us; + + if (!priv->debug) + return 0; + + itp = &priv->debug->itp; + + if (!itp) + return 0; + + spin_lock_bh(&itp->tx_lock); + if (atomic_read(&itp->test_mode) != TEST_MODE_TX_TEST) + goto out; + + if (atomic_read(&itp->stop_tx)) + goto out; + + if (itp->number == 0) { + atomic_set(&itp->stop_tx, 1); + queue_delayed_work(priv->workqueue, &itp->tx_finish, + HZ/10); + goto out; + } + + if (!itp->data) + goto out; + + if (priv->hw_bufs_used >= 2) { + if (!atomic_read(&priv->bh_rx)) + atomic_set(&priv->bh_rx, 1); + atomic_set(&priv->bh_tx, 1); + goto out; + } + + ktime_get_ts(&now); + time_left_us = (itp->last_sent.tv_sec - + now.tv_sec)*1000000 + + (itp->last_sent.tv_nsec - now.tv_nsec)/1000 + + itp->interval_us; + + if (time_left_us > ITP_TIME_THRES_US) { + queue_delayed_work(priv->workqueue, &itp->tx_work, + ITP_US_TO_MS(time_left_us)*HZ/1000); + goto out; + } + + if (time_left_us > 50) + udelay(time_left_us); + + if (itp->number > 0) + itp->number--; + + *data = itp->data; + *tx_len = itp->data_len + itp->hdr_len; + + if (itp->data_mode == ITP_DATA_RANDOM) + cw1200_itp_fill_pattern(&itp->data[itp->hdr_len], + itp->data_len, itp->data_mode); + *burst = 2; + atomic_set(&priv->bh_tx, 1); + ktime_get_ts(&itp->last_sent); + atomic_add(1, &itp->awaiting_confirm); + spin_unlock_bh(&itp->tx_lock); + return 1; + +out: + spin_unlock_bh(&itp->tx_lock); + return 0; +} + +bool cw1200_itp_rxed(struct cw1200_common *priv, struct sk_buff *skb) +{ + struct cw1200_itp *itp = &priv->debug->itp; + struct ieee80211_rx_status *rx = IEEE80211_SKB_RXCB(skb); + int signal; + + if (atomic_read(&itp->test_mode) != TEST_MODE_RX_TEST) + return cw1200_is_itp(priv); + if (rx->freq != priv->channel->center_freq) + return true; + + signal = rx->signal; + itp->rx_cnt++; + itp->rx_rssi += signal; + if (itp->rx_rssi_min > rx->signal) + itp->rx_rssi_min = rx->signal; + if (itp->rx_rssi_max < rx->signal) + itp->rx_rssi_max = rx->signal; + + return true; +} + +void cw1200_itp_wake_up_tx(struct cw1200_common *priv) +{ + wake_up(&priv->debug->itp.write_wait); +} + +bool cw1200_itp_tx_running(struct cw1200_common *priv) +{ + if (atomic_read(&priv->debug->itp.awaiting_confirm) || + atomic_read(&priv->debug->itp.test_mode) == + TEST_MODE_TX_TEST) { + atomic_sub(1, &priv->debug->itp.awaiting_confirm); + return true; + } + return false; +} + +static void cw1200_itp_handle(struct cw1200_common *priv, + struct sk_buff *skb) +{ + struct cw1200_itp *itp = &priv->debug->itp; + const struct wiphy *wiphy = priv->hw->wiphy; + int cmd; + int ret; + + itp_printk(KERN_DEBUG "[ITP] <<< %s", skb->data); + if (sscanf(skb->data, "%d", &cmd) != 1) { + cw1200_itp_err(priv, -EINVAL, 1); + return; + } + + switch (cmd) { + case 1: /* RX test */ + if (atomic_read(&itp->test_mode)) { + cw1200_itp_err(priv, -EBUSY, 0); + return; + } + ret = sscanf(skb->data, "%d,%d,%d", + &cmd, &itp->band, &itp->ch); + if (ret != 3) { + cw1200_itp_err(priv, -EINVAL, ret + 1); + return; + } + if (itp->band >= 2) + cw1200_itp_err(priv, -EINVAL, 2); + else if (!wiphy->bands[itp->band]) + cw1200_itp_err(priv, -EINVAL, 2); + else if (itp->ch >= + wiphy->bands[itp->band]->n_channels) + cw1200_itp_err(priv, -EINVAL, 3); + else { + cw1200_itp_rx_stats(priv); + cw1200_itp_rx_start(priv); + } + break; + case 2: /* RX stat */ + cw1200_itp_rx_stats(priv); + break; + case 3: /* RX/TX stop */ + if (atomic_read(&itp->test_mode) == TEST_MODE_RX_TEST) { + cw1200_itp_rx_stats(priv); + cw1200_itp_rx_stop(priv); + } else if (atomic_read(&itp->test_mode) == TEST_MODE_TX_TEST) { + cw1200_itp_tx_stop(priv); + } else + cw1200_itp_err(priv, -EBUSY, 0); + break; + case 4: /* TX start */ + if (atomic_read(&itp->test_mode) != TEST_MODE_NO_TEST) { + cw1200_itp_err(priv, -EBUSY, 0); + return; + } + ret = sscanf(skb->data, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + &cmd, &itp->band, &itp->ch, &itp->rate, + &itp->preamble, &itp->number, &itp->data_mode, + &itp->interval_us, &itp->power, &itp->data_len); + if (ret != 10) { + cw1200_itp_err(priv, -EINVAL, ret + 1); + return; + } + if (itp->band >= 2) + cw1200_itp_err(priv, -EINVAL, 2); + else if (!wiphy->bands[itp->band]) + cw1200_itp_err(priv, -EINVAL, 2); + else if (itp->ch >= + wiphy->bands[itp->band]->n_channels) + cw1200_itp_err(priv, -EINVAL, 3); + else if (itp->rate >= 20) + cw1200_itp_err(priv, -EINVAL, 4); + else if (itp->preamble >= ITP_PREAMBLE_MAX) + cw1200_itp_err(priv, -EINVAL, 5); + else if (itp->data_mode >= ITP_DATA_MAX_MODE) + cw1200_itp_err(priv, -EINVAL, 7); + else if (itp->data_len < ITP_MIN_DATA_SIZE || + itp->data_len > priv->wsm_caps.sizeInpChBuf - + itp->hdr_len) + cw1200_itp_err(priv, -EINVAL, 8); + else { + cw1200_itp_tx_start(priv); + } + break; + case 5: + cw1200_itp_get_version(priv, ITP_CHIP_ID); + break; + case 6: + cw1200_itp_get_version(priv, ITP_FW_VER); + break; + + } +} + +static void cw1200_itp_err(struct cw1200_common *priv, + int err, int arg) +{ + struct cw1200_itp *itp = &priv->debug->itp; + struct sk_buff *skb; + static char buf[255]; + int len; + + len = snprintf(buf, sizeof(buf), "%d,%d\n", + err, arg); + if (len <= 0) + return; + + skb = dev_alloc_skb(len); + if (!skb) + return; + + skb_trim(skb, 0); + skb_put(skb, len); + + memcpy(skb->data, buf, len); + skb_queue_tail(&itp->log_queue, skb); + wake_up(&itp->read_wait); + + len = sprint_symbol(buf, + (unsigned long)__builtin_return_address(0)); + if (len <= 0) + return; + itp_printk(KERN_DEBUG "[ITP] error %d,%d from %s\n", + err, arg, buf); +} diff --git a/drivers/staging/cw1200/itp.h b/drivers/staging/cw1200/itp.h new file mode 100644 index 00000000000..635e7f85ff9 --- /dev/null +++ b/drivers/staging/cw1200/itp.h @@ -0,0 +1,151 @@ +/* + * ITP code for ST-Ericsson CW1200 mac80211 driver + * + * Copyright (c) 2011, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef CW1200_ITP_H_INCLUDED +#define CW1200_ITP_H_INCLUDED + +struct cw200_common; +struct wsm_tx_confirm; +struct dentry; + +#ifdef CONFIG_CW1200_ITP + +/*extern*/ struct ieee80211_channel; + +#define TEST_MODE_NO_TEST (0) +#define TEST_MODE_RX_TEST (1) +#define TEST_MODE_TX_TEST (2) + +#define itp_printk(...) printk(__VA_ARGS__) +#define ITP_DEFAULT_DA_ADDR {0xff, 0xff, 0xff, 0xff, 0xff, 0xff} +#define ITP_MIN_DATA_SIZE 6 +#define ITP_MAX_DATA_SIZE 1600 +#define ITP_TIME_THRES_US 10000 +#define ITP_US_TO_MS(x) ((x)/1000) +#define ITP_MS_TO_US(x) ((x)*1000) +#if ((ITP_US_TO_MS(ITP_TIME_THRES_US))*HZ/1000) < 1 +#warning not able to achieve non-busywaiting ITP_TIME_THRES_US\ +precision with current HZ value ! +#endif +#define ITP_BUF_SIZE 255 + + +enum cw1200_itp_data_modes { + ITP_DATA_ZEROS, + ITP_DATA_ONES, + ITP_DATA_ZERONES, + ITP_DATA_RANDOM, + ITP_DATA_MAX_MODE, +}; + +enum cw1200_itp_version_type { + ITP_CHIP_ID, + ITP_FW_VER, +}; + +enum cw1200_itp_preamble_type { + ITP_PREAMBLE_LONG, + ITP_PREAMBLE_SHORT, + ITP_PREAMBLE_OFDM, + ITP_PREAMBLE_MIXED, + ITP_PREAMBLE_GREENFIELD, + ITP_PREAMBLE_MAX, +}; + + +struct cw1200_itp { + struct cw1200_common *priv; + atomic_t open_count; + atomic_t awaiting_confirm; + struct sk_buff_head log_queue; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + wait_queue_head_t close_wait; + struct ieee80211_channel *saved_channel; + atomic_t stop_tx; + struct delayed_work tx_work; + struct delayed_work tx_finish; + spinlock_t tx_lock; + struct timespec last_sent; + atomic_t test_mode; + int rx_cnt; + long rx_rssi; + int rx_rssi_max; + int rx_rssi_min; + unsigned band; + unsigned ch; + unsigned rate; + unsigned preamble; + unsigned int number; + unsigned data_mode; + int interval_us; + int power; + u8 *data; + int hdr_len; + int data_len; +}; + +int cw1200_itp_init(struct cw1200_common *priv); +void cw1200_itp_release(struct cw1200_common *priv); + +bool cw1200_is_itp(struct cw1200_common *priv); +bool cw1200_itp_rxed(struct cw1200_common *priv, struct sk_buff *skb); +void cw1200_itp_wake_up_tx(struct cw1200_common *priv); +int cw1200_itp_get_tx(struct cw1200_common *priv, u8 **data, + size_t *tx_len, int *burst); +bool cw1200_itp_tx_running(struct cw1200_common *priv); + +#else /* CONFIG_CW1200_ITP */ + +static inline int +cw1200_itp_init(struct cw1200_common *priv) +{ + return 0; +} + +static inline void cw1200_itp_release(struct cw1200_common *priv) +{ +} + +static inline bool cw1200_is_itp(struct cw1200_common *priv) +{ + return false; +} + +static inline bool cw1200_itp_rxed(struct cw1200_common *priv, + struct sk_buff *skb) +{ + return false; +} + + +static inline void cw1200_itp_consume_txed(struct cw1200_common *priv) +{ +} + +static inline void cw1200_itp_wake_up_tx(struct cw1200_common *priv) +{ +} + +static inline int cw1200_itp_get_tx(struct cw1200_common *priv, u8 **data, + size_t *tx_len, int *burst) +{ + return 0; +} + +static inline bool cw1200_itp_tx_running(struct cw1200_common *priv) +{ + return false; +} + +#endif /* CONFIG_CW1200_ITP */ + +#endif /* CW1200_ITP_H_INCLUDED */ diff --git a/drivers/staging/cw1200/main.c b/drivers/staging/cw1200/main.c new file mode 100644 index 00000000000..06434629dcd --- /dev/null +++ b/drivers/staging/cw1200/main.c @@ -0,0 +1,567 @@ +/* + * mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * Based on: + * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net> + * Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de> + * Copyright 2008, Johannes Berg <johannes@sipsolutions.net> + * + * Based on: + * - the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al. + * - stlc45xx driver + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/firmware.h> +#include <linux/etherdevice.h> +#include <linux/vmalloc.h> +#include <linux/random.h> +#include <linux/sched.h> +#include <net/mac80211.h> + +#include "cw1200.h" +#include "txrx.h" +#include "sbus.h" +#include "fwio.h" +#include "hwio.h" +#include "bh.h" +#include "sta.h" +#include "ap.h" +#include "scan.h" +#include "debug.h" +#include "pm.h" + +MODULE_AUTHOR("Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com>"); +MODULE_DESCRIPTION("Softmac ST-Ericsson CW1200 common code"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("cw1200_core"); + +/* Accept MAC address of the form macaddr=0x00,0x80,0xE1,0x30,0x40,0x50 */ +static u8 cw1200_mac_template[ETH_ALEN] = {0x00, 0x80, 0xe1, 0x00, 0x00, 0x00}; +module_param_array_named(macaddr, cw1200_mac_template, byte, NULL, S_IRUGO); +MODULE_PARM_DESC(macaddr, "MAC address"); + +/* TODO: use rates and channels from the device */ +#define RATETAB_ENT(_rate, _rateid, _flags) \ + { \ + .bitrate = (_rate), \ + .hw_value = (_rateid), \ + .flags = (_flags), \ + } + +static struct ieee80211_rate cw1200_rates[] = { + RATETAB_ENT(10, 0, 0), + RATETAB_ENT(20, 1, 0), + RATETAB_ENT(55, 2, 0), + RATETAB_ENT(110, 3, 0), + RATETAB_ENT(60, 6, 0), + RATETAB_ENT(90, 7, 0), + RATETAB_ENT(120, 8, 0), + RATETAB_ENT(180, 9, 0), + RATETAB_ENT(240, 10, 0), + RATETAB_ENT(360, 11, 0), + RATETAB_ENT(480, 12, 0), + RATETAB_ENT(540, 13, 0), +}; + +static struct ieee80211_rate cw1200_mcs_rates[] = { + RATETAB_ENT(65, 14, IEEE80211_TX_RC_MCS), + RATETAB_ENT(130, 15, IEEE80211_TX_RC_MCS), + RATETAB_ENT(195, 16, IEEE80211_TX_RC_MCS), + RATETAB_ENT(260, 17, IEEE80211_TX_RC_MCS), + RATETAB_ENT(390, 18, IEEE80211_TX_RC_MCS), + RATETAB_ENT(520, 19, IEEE80211_TX_RC_MCS), + RATETAB_ENT(585, 20, IEEE80211_TX_RC_MCS), + RATETAB_ENT(650, 21, IEEE80211_TX_RC_MCS), +}; + +#define cw1200_a_rates (cw1200_rates + 4) +#define cw1200_a_rates_size (ARRAY_SIZE(cw1200_rates) - 4) +#define cw1200_g_rates (cw1200_rates + 0) +#define cw1200_g_rates_size (ARRAY_SIZE(cw1200_rates)) +#define cw1200_n_rates (cw1200_mcs_rates) +#define cw1200_n_rates_size (ARRAY_SIZE(cw1200_mcs_rates)) + + +#define CHAN2G(_channel, _freq, _flags) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define CHAN5G(_channel, _flags) { \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = 5000 + (5 * (_channel)), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +static struct ieee80211_channel cw1200_2ghz_chantable[] = { + CHAN2G(1, 2412, 0), + CHAN2G(2, 2417, 0), + CHAN2G(3, 2422, 0), + CHAN2G(4, 2427, 0), + CHAN2G(5, 2432, 0), + CHAN2G(6, 2437, 0), + CHAN2G(7, 2442, 0), + CHAN2G(8, 2447, 0), + CHAN2G(9, 2452, 0), + CHAN2G(10, 2457, 0), + CHAN2G(11, 2462, 0), + CHAN2G(12, 2467, 0), + CHAN2G(13, 2472, 0), + CHAN2G(14, 2484, 0), +}; + +#ifdef CONFIG_CW1200_5GHZ_SUPPORT +static struct ieee80211_channel cw1200_5ghz_chantable[] = { + CHAN5G(34, 0), CHAN5G(36, 0), + CHAN5G(38, 0), CHAN5G(40, 0), + CHAN5G(42, 0), CHAN5G(44, 0), + CHAN5G(46, 0), CHAN5G(48, 0), + CHAN5G(52, 0), CHAN5G(56, 0), + CHAN5G(60, 0), CHAN5G(64, 0), + CHAN5G(100, 0), CHAN5G(104, 0), + CHAN5G(108, 0), CHAN5G(112, 0), + CHAN5G(116, 0), CHAN5G(120, 0), + CHAN5G(124, 0), CHAN5G(128, 0), + CHAN5G(132, 0), CHAN5G(136, 0), + CHAN5G(140, 0), CHAN5G(149, 0), + CHAN5G(153, 0), CHAN5G(157, 0), + CHAN5G(161, 0), CHAN5G(165, 0), + CHAN5G(184, 0), CHAN5G(188, 0), + CHAN5G(192, 0), CHAN5G(196, 0), + CHAN5G(200, 0), CHAN5G(204, 0), + CHAN5G(208, 0), CHAN5G(212, 0), + CHAN5G(216, 0), +}; +#endif /* CONFIG_CW1200_5GHZ_SUPPORT */ + +static struct ieee80211_supported_band cw1200_band_2ghz = { + .channels = cw1200_2ghz_chantable, + .n_channels = ARRAY_SIZE(cw1200_2ghz_chantable), + .bitrates = cw1200_g_rates, + .n_bitrates = cw1200_g_rates_size, + .ht_cap = { + .cap = IEEE80211_HT_CAP_GRN_FLD | + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) | + IEEE80211_HT_CAP_MAX_AMSDU, + .ht_supported = 1, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE, + .mcs = { + .rx_mask[0] = 0xFF, + .rx_highest = __cpu_to_le16(0x41), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, + }, + }, +}; + +#ifdef CONFIG_CW1200_5GHZ_SUPPORT +static struct ieee80211_supported_band cw1200_band_5ghz = { + .channels = cw1200_5ghz_chantable, + .n_channels = ARRAY_SIZE(cw1200_5ghz_chantable), + .bitrates = cw1200_a_rates, + .n_bitrates = cw1200_a_rates_size, + .ht_cap = { + .cap = IEEE80211_HT_CAP_GRN_FLD | + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) | + IEEE80211_HT_CAP_MAX_AMSDU, + .ht_supported = 1, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE, + .mcs = { + .rx_mask[0] = 0xFF, + .rx_highest = __cpu_to_le16(0x41), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, + }, + }, +}; +#endif /* CONFIG_CW1200_5GHZ_SUPPORT */ + +static const unsigned long cw1200_ttl[] = { + 1 * HZ, /* VO */ + 2 * HZ, /* VI */ + 5 * HZ, /* BE */ + 10 * HZ /* BK */ +}; + +static const struct ieee80211_ops cw1200_ops = { + .start = cw1200_start, + .stop = cw1200_stop, + .add_interface = cw1200_add_interface, + .remove_interface = cw1200_remove_interface, + .tx = cw1200_tx, + .hw_scan = cw1200_hw_scan, + .set_tim = cw1200_set_tim, + .sta_notify = cw1200_sta_notify, + .sta_add = cw1200_sta_add, + .sta_remove = cw1200_sta_remove, + .set_key = cw1200_set_key, + .set_rts_threshold = cw1200_set_rts_threshold, + .config = cw1200_config, + .bss_info_changed = cw1200_bss_info_changed, + .prepare_multicast = cw1200_prepare_multicast, + .configure_filter = cw1200_configure_filter, + .conf_tx = cw1200_conf_tx, + .get_stats = cw1200_get_stats, + .ampdu_action = cw1200_ampdu_action, + .flush = cw1200_flush, +#ifdef CONFIG_PM + .suspend = cw1200_wow_suspend, + .resume = cw1200_wow_resume, +#endif /* CONFIG_PM */ + /* Intentionally not offloaded: */ + /*.channel_switch = cw1200_channel_switch, */ + /*.remain_on_channel = cw1200_remain_on_channel, */ + /*.cancel_remain_on_channel = cw1200_cancel_remain_on_channel, */ +}; + +struct ieee80211_hw *cw1200_init_common(size_t priv_data_len) +{ + int i; + struct ieee80211_hw *hw; + struct cw1200_common *priv; + + hw = ieee80211_alloc_hw(priv_data_len, &cw1200_ops); + if (!hw) + return NULL; + + priv = hw->priv; + priv->hw = hw; + priv->mode = NL80211_IFTYPE_UNSPECIFIED; + priv->rates = cw1200_rates; /* TODO: fetch from FW */ + priv->mcs_rates = cw1200_n_rates; + /* Enable block ACK for every TID but voice. */ + priv->ba_tid_mask = 0x3F; + + hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_SUPPORTS_DYNAMIC_PS | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_SUPPORTS_UAPSD | + IEEE80211_HW_CONNECTION_MONITOR | + IEEE80211_HW_SUPPORTS_CQM_RSSI | + IEEE80211_HW_NEED_DTIM_PERIOD | + /* Aggregation is fully controlled by firmware. + * Do not need any support from the mac80211 stack */ + /* IEEE80211_HW_AMPDU_AGGREGATION | */ +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + IEEE80211_HW_SUPPORTS_P2P_PS | + IEEE80211_HW_SUPPORTS_CQM_BEACON_MISS | + IEEE80211_HW_SUPPORTS_CQM_TX_FAIL | +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ + IEEE80211_HW_BEACON_FILTER; + + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MESH_POINT) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO); + + /* Support only for limited wowlan functionalities */ + hw->wiphy->wowlan.flags = WIPHY_WOWLAN_ANY | + WIPHY_WOWLAN_DISCONNECT; + hw->wiphy->wowlan.n_patterns = 0; + +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ + +#if defined(CONFIG_CW1200_DISABLE_BEACON_HINTS) + hw->wiphy->flags |= WIPHY_FLAG_DISABLE_BEACON_HINTS; +#endif + + hw->channel_change_time = 1000; /* TODO: find actual value */ + /* priv->beacon_req_id = cpu_to_le32(0); */ + hw->queues = 4; + priv->noise = -94; + + hw->max_rates = 8; + hw->max_rate_tries = 15; + hw->extra_tx_headroom = WSM_TX_EXTRA_HEADROOM + + 8 /* TKIP IV */ + + 12 /* TKIP ICV and MIC */; + + hw->sta_data_size = sizeof(struct cw1200_sta_priv); + + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &cw1200_band_2ghz; +#ifdef CONFIG_CW1200_5GHZ_SUPPORT + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &cw1200_band_5ghz; +#endif /* CONFIG_CW1200_5GHZ_SUPPORT */ + + hw->wiphy->max_scan_ssids = 2; + hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; + + SET_IEEE80211_PERM_ADDR(hw, cw1200_mac_template); + + if (hw->wiphy->perm_addr[3] == 0 && + hw->wiphy->perm_addr[4] == 0 && + hw->wiphy->perm_addr[5] == 0) { + get_random_bytes(&hw->wiphy->perm_addr[3], 3); + } + + mutex_init(&priv->wsm_cmd_mux); + mutex_init(&priv->conf_mutex); + priv->workqueue = create_singlethread_workqueue("cw1200_wq"); + sema_init(&priv->scan.lock, 1); + INIT_WORK(&priv->scan.work, cw1200_scan_work); + INIT_DELAYED_WORK(&priv->scan.probe_work, cw1200_probe_work); + INIT_DELAYED_WORK(&priv->scan.timeout, cw1200_scan_timeout); + INIT_WORK(&priv->join_work, cw1200_join_work); + INIT_DELAYED_WORK(&priv->join_timeout, cw1200_join_timeout); + INIT_WORK(&priv->unjoin_work, cw1200_unjoin_work); + INIT_WORK(&priv->offchannel_work, cw1200_offchannel_work); + INIT_WORK(&priv->wep_key_work, cw1200_wep_key_work); + INIT_WORK(&priv->tx_policy_upload_work, tx_policy_upload_work); + spin_lock_init(&priv->event_queue_lock); + INIT_LIST_HEAD(&priv->event_queue); + INIT_WORK(&priv->event_handler, cw1200_event_handler); + INIT_DELAYED_WORK(&priv->bss_loss_work, cw1200_bss_loss_work); + INIT_DELAYED_WORK(&priv->connection_loss_work, + cw1200_connection_loss_work); + spin_lock_init(&priv->bss_loss_lock); + INIT_WORK(&priv->tx_failure_work, cw1200_tx_failure_work); + spin_lock_init(&priv->ps_state_lock); + INIT_DELAYED_WORK(&priv->set_cts_work, cw1200_set_cts_work); + INIT_WORK(&priv->set_tim_work, cw1200_set_tim_work); + INIT_WORK(&priv->multicast_start_work, cw1200_multicast_start_work); + INIT_WORK(&priv->multicast_stop_work, cw1200_multicast_stop_work); + INIT_WORK(&priv->link_id_work, cw1200_link_id_work); + INIT_DELAYED_WORK(&priv->link_id_gc_work, cw1200_link_id_gc_work); +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + INIT_WORK(&priv->linkid_reset_work, cw1200_link_id_reset); +#endif + INIT_WORK(&priv->update_filtering_work, cw1200_update_filtering_work); + INIT_WORK(&priv->ba_work, cw1200_ba_work); + init_timer(&priv->mcast_timeout); + priv->mcast_timeout.data = (unsigned long)priv; + priv->mcast_timeout.function = cw1200_mcast_timeout; + spin_lock_init(&priv->ba_lock); + init_timer(&priv->ba_timer); + priv->ba_timer.data = (unsigned long)priv; + priv->ba_timer.function = cw1200_ba_timer; + + if (unlikely(cw1200_queue_stats_init(&priv->tx_queue_stats, + CW1200_LINK_ID_MAX, + cw1200_skb_dtor, + priv))) { + ieee80211_free_hw(hw); + return NULL; + } + + for (i = 0; i < 4; ++i) { + if (unlikely(cw1200_queue_init(&priv->tx_queue[i], + &priv->tx_queue_stats, i, 16, + cw1200_ttl[i]))) { + for (; i > 0; i--) + cw1200_queue_deinit(&priv->tx_queue[i - 1]); + cw1200_queue_stats_deinit(&priv->tx_queue_stats); + ieee80211_free_hw(hw); + return NULL; + } + } + + init_waitqueue_head(&priv->channel_switch_done); + init_waitqueue_head(&priv->wsm_cmd_wq); + init_waitqueue_head(&priv->wsm_startup_done); + wsm_buf_init(&priv->wsm_cmd_buf); + spin_lock_init(&priv->wsm_cmd.lock); + tx_policy_init(priv); +#if defined(CONFIG_CW1200_WSM_DUMPS_SHORT) + priv->wsm_dump_max_size = 20; +#endif /* CONFIG_CW1200_WSM_DUMPS_SHORT */ + + return hw; +} +EXPORT_SYMBOL_GPL(cw1200_init_common); + +int cw1200_register_common(struct ieee80211_hw *dev) +{ + struct cw1200_common *priv = dev->priv; + int err; + + err = cw1200_pm_init(&priv->pm_state, priv); + if (err) { + cw1200_dbg(CW1200_DBG_ERROR, "Cannot init PM. (%d).\n", + err); + return err; + } + + err = ieee80211_register_hw(dev); + if (err) { + cw1200_dbg(CW1200_DBG_ERROR, "Cannot register device (%d).\n", + err); + cw1200_pm_deinit(&priv->pm_state); + return err; + } + +#ifdef CONFIG_CW1200_LEDS + err = cw1200_init_leds(priv); + if (err) { + cw1200_pm_deinit(&priv->pm_state); + ieee80211_unregister_hw(dev); + return err; + } +#endif /* CONFIG_CW1200_LEDS */ + + cw1200_debug_init(priv); + + cw1200_dbg(CW1200_DBG_MSG, "is registered as '%s'\n", + wiphy_name(dev->wiphy)); + return 0; +} +EXPORT_SYMBOL_GPL(cw1200_register_common); + +void cw1200_free_common(struct ieee80211_hw *dev) +{ + /* struct cw1200_common *priv = dev->priv; */ + /* unsigned int i; */ + + ieee80211_free_hw(dev); +} +EXPORT_SYMBOL_GPL(cw1200_free_common); + +void cw1200_unregister_common(struct ieee80211_hw *dev) +{ + struct cw1200_common *priv = dev->priv; + int i; + + ieee80211_unregister_hw(dev); + + del_timer_sync(&priv->mcast_timeout); + del_timer_sync(&priv->ba_timer); + + priv->sbus_ops->irq_unsubscribe(priv->sbus_priv); + cw1200_unregister_bh(priv); + + cw1200_debug_release(priv); + +#ifdef CONFIG_CW1200_LEDS + cw1200_unregister_leds(priv); +#endif /* CONFIG_CW1200_LEDS */ + + mutex_destroy(&priv->conf_mutex); + + wsm_buf_deinit(&priv->wsm_cmd_buf); + + destroy_workqueue(priv->workqueue); + priv->workqueue = NULL; + + if (priv->skb_cache) { + dev_kfree_skb(priv->skb_cache); + priv->skb_cache = NULL; + } + + if (priv->sdd) { + release_firmware(priv->sdd); + priv->sdd = NULL; + } + + for (i = 0; i < 4; ++i) + cw1200_queue_deinit(&priv->tx_queue[i]); + cw1200_queue_stats_deinit(&priv->tx_queue_stats); + cw1200_pm_deinit(&priv->pm_state); +} +EXPORT_SYMBOL_GPL(cw1200_unregister_common); + +int cw1200_core_probe(const struct sbus_ops *sbus_ops, + struct sbus_priv *sbus, + struct device *pdev, + struct cw1200_common **pself) +{ + int err = -ENOMEM; + struct ieee80211_hw *dev; + struct cw1200_common *priv; + struct wsm_operational_mode mode = { + .power_mode = wsm_power_mode_quiescent, + .disableMoreFlagUsage = true, + }; + + dev = cw1200_init_common(sizeof(struct cw1200_common)); + if (!dev) + goto err; + + priv = dev->priv; + + priv->sbus_ops = sbus_ops; + priv->sbus_priv = sbus; + priv->pdev = pdev; + SET_IEEE80211_DEV(priv->hw, pdev); + + /* WSM callbacks. */ + priv->wsm_cbc.scan_complete = cw1200_scan_complete_cb; + priv->wsm_cbc.tx_confirm = cw1200_tx_confirm_cb; + priv->wsm_cbc.rx = cw1200_rx_cb; + priv->wsm_cbc.suspend_resume = cw1200_suspend_resume; + /* priv->wsm_cbc.set_pm_complete = cw1200_set_pm_complete_cb; */ + priv->wsm_cbc.channel_switch = cw1200_channel_switch_cb; + + err = cw1200_register_bh(priv); + if (err) + goto err1; + + err = cw1200_load_firmware(priv); + if (err) + goto err2; + priv->sbus_ops->lock(priv->sbus_priv); + WARN_ON(priv->sbus_ops->set_block_size(priv->sbus_priv, + SDIO_BLOCK_SIZE)); + priv->sbus_ops->unlock(priv->sbus_priv); + + if (wait_event_interruptible_timeout(priv->wsm_startup_done, + priv->wsm_caps.firmwareReady, 3*HZ) <= 0) { + /* TODO: Needs to find how to reset device */ + /* in QUEUE mode properly. */ + goto err3; + } + + /* Set low-power mode. */ + WARN_ON(wsm_set_operational_mode(priv, &mode)); + + /* Enable multi-TX confirmation */ + WARN_ON(wsm_use_multi_tx_conf(priv, true)); + + err = cw1200_register_common(dev); + if (err) { + priv->sbus_ops->irq_unsubscribe(priv->sbus_priv); + goto err3; + } + + *pself = dev->priv; + return err; + +err3: + sbus_ops->reset(sbus); +err2: + cw1200_unregister_bh(priv); +err1: + cw1200_free_common(dev); +err: + return err; +} +EXPORT_SYMBOL_GPL(cw1200_core_probe); + +void cw1200_core_release(struct cw1200_common *self) +{ + cw1200_unregister_common(self->hw); + cw1200_free_common(self->hw); + return; +} +EXPORT_SYMBOL_GPL(cw1200_core_release); diff --git a/drivers/staging/cw1200/pm.c b/drivers/staging/cw1200/pm.c new file mode 100644 index 00000000000..d95e5d3dc9e --- /dev/null +++ b/drivers/staging/cw1200/pm.c @@ -0,0 +1,459 @@ +/* + * Mac80211 power management API for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2011, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/platform_device.h> +#include <linux/if_ether.h> +#include "cw1200.h" +#include "pm.h" +#include "sta.h" +#include "bh.h" +#include "sbus.h" + +#define CW1200_BEACON_SKIPPING_MULTIPLIER 3 + +struct cw1200_udp_port_filter { + struct wsm_udp_port_filter_hdr hdr; + struct wsm_udp_port_filter dhcp; + struct wsm_udp_port_filter upnp; +} __packed; + +struct cw1200_ether_type_filter { + struct wsm_ether_type_filter_hdr hdr; + struct wsm_ether_type_filter ip; + struct wsm_ether_type_filter pae; + struct wsm_ether_type_filter wapi; +} __packed; + +static struct cw1200_udp_port_filter cw1200_udp_port_filter_on = { + .hdr.nrFilters = 2, + .dhcp = { + .filterAction = WSM_FILTER_ACTION_FILTER_OUT, + .portType = WSM_FILTER_PORT_TYPE_DST, + .udpPort = __cpu_to_le16(67), + }, + .upnp = { + .filterAction = WSM_FILTER_ACTION_FILTER_OUT, + .portType = WSM_FILTER_PORT_TYPE_DST, + .udpPort = __cpu_to_le16(1900), + }, + /* Please add other known ports to be filtered out here and + * update nrFilters field in the header. + * Up to 4 filters are allowed. */ +}; + +static struct wsm_udp_port_filter_hdr cw1200_udp_port_filter_off = { + .nrFilters = 0, +}; + +#ifndef ETH_P_WAPI +#define ETH_P_WAPI 0x88B4 +#endif + +static struct cw1200_ether_type_filter cw1200_ether_type_filter_on = { + .hdr.nrFilters = 3, + .ip = { + .filterAction = WSM_FILTER_ACTION_FILTER_IN, + .etherType = __cpu_to_le16(ETH_P_IP), + }, + .pae = { + .filterAction = WSM_FILTER_ACTION_FILTER_IN, + .etherType = __cpu_to_le16(ETH_P_PAE), + }, + .wapi = { + .filterAction = WSM_FILTER_ACTION_FILTER_IN, + .etherType = __cpu_to_le16(ETH_P_WAPI), + }, + /* Please add other known ether types to be filtered out here and + * update nrFilters field in the header. + * Up to 4 filters are allowed. */ +}; + +static struct wsm_ether_type_filter_hdr cw1200_ether_type_filter_off = { + .nrFilters = 0, +}; + +static int cw1200_suspend_late(struct device *dev); +static void cw1200_pm_release(struct device *dev); +static int cw1200_pm_probe(struct platform_device *pdev); + +/* private */ +struct cw1200_suspend_state { + unsigned long bss_loss_tmo; + unsigned long connection_loss_tmo; + unsigned long join_tmo; + unsigned long direct_probe; + unsigned long link_id_gc; + bool beacon_skipping; +}; + +static const struct dev_pm_ops cw1200_pm_ops = { + .suspend_noirq = cw1200_suspend_late, +}; + +static struct platform_driver cw1200_power_driver = { + .probe = cw1200_pm_probe, + .driver = { + .name = "cw1200_power", + .pm = &cw1200_pm_ops, + }, +}; + +static int cw1200_pm_init_common(struct cw1200_pm_state *pm, + struct cw1200_common *priv) +{ + int ret; + + spin_lock_init(&pm->lock); + ret = platform_driver_register(&cw1200_power_driver); + if (ret) + return ret; + pm->pm_dev = platform_device_alloc("cw1200_power", 0); + if (!pm->pm_dev) { + platform_driver_unregister(&cw1200_power_driver); + return -ENOMEM; + } + + pm->pm_dev->dev.platform_data = priv; + ret = platform_device_add(pm->pm_dev); + if (ret) { + kfree(pm->pm_dev); + pm->pm_dev = NULL; + } + + return ret; +} + +static void cw1200_pm_deinit_common(struct cw1200_pm_state *pm) +{ + platform_driver_unregister(&cw1200_power_driver); + if (pm->pm_dev) { + pm->pm_dev->dev.platform_data = NULL; + platform_device_unregister(pm->pm_dev); + pm->pm_dev = NULL; + } +} + +#ifdef CONFIG_WAKELOCK + +int cw1200_pm_init(struct cw1200_pm_state *pm, + struct cw1200_common *priv) +{ + int ret = cw1200_pm_init_common(pm, priv); + if (!ret) + wake_lock_init(&pm->wakelock, + WAKE_LOCK_SUSPEND, "cw1200_wlan"); + return ret; +} + +void cw1200_pm_deinit(struct cw1200_pm_state *pm) +{ + if (wake_lock_active(&pm->wakelock)) + wake_unlock(&pm->wakelock); + wake_lock_destroy(&pm->wakelock); + cw1200_pm_deinit_common(pm); +} + +void cw1200_pm_stay_awake(struct cw1200_pm_state *pm, + unsigned long tmo) +{ + long cur_tmo; + spin_lock_bh(&pm->lock); + cur_tmo = pm->wakelock.expires - jiffies; + if (!wake_lock_active(&pm->wakelock) || + cur_tmo < (long)tmo) + wake_lock_timeout(&pm->wakelock, tmo); + spin_unlock_bh(&pm->lock); +} + +#else /* CONFIG_WAKELOCK */ + +static void cw1200_pm_stay_awake_tmo(unsigned long arg) +{ +} + +int cw1200_pm_init(struct cw1200_pm_state *pm, + struct cw1200_common *priv) +{ + int ret = cw1200_pm_init_common(pm, priv); + if (!ret) { + init_timer(&pm->stay_awake); + pm->stay_awake.data = (unsigned long)pm; + pm->stay_awake.function = cw1200_pm_stay_awake_tmo; + } + return ret; +} + +void cw1200_pm_deinit(struct cw1200_pm_state *pm) +{ + del_timer_sync(&pm->stay_awake); + cw1200_pm_deinit_common(pm); +} + +void cw1200_pm_stay_awake(struct cw1200_pm_state *pm, + unsigned long tmo) +{ + long cur_tmo; + spin_lock_bh(&pm->lock); + cur_tmo = pm->stay_awake.expires - jiffies; + if (!timer_pending(&pm->stay_awake) || + cur_tmo < (long)tmo) + mod_timer(&pm->stay_awake, jiffies + tmo); + spin_unlock_bh(&pm->lock); +} + +#endif /* CONFIG_WAKELOCK */ + +static long cw1200_suspend_work(struct delayed_work *work) +{ + int ret = cancel_delayed_work(work); + long tmo; + if (ret > 0) { + /* Timer is pending */ + tmo = work->timer.expires - jiffies; + if (tmo < 0) + tmo = 0; + } else { + tmo = -1; + } + return tmo; +} + +static int cw1200_resume_work(struct cw1200_common *priv, + struct delayed_work *work, + unsigned long tmo) +{ + if ((long)tmo < 0) + return 1; + + return queue_delayed_work(priv->workqueue, work, tmo); +} + +static int cw1200_suspend_late(struct device *dev) +{ + struct cw1200_common *priv = dev->platform_data; + if (atomic_read(&priv->bh_rx)) { + wiphy_dbg(priv->hw->wiphy, + "%s: Suspend interrupted.\n", + __func__); + return -EAGAIN; + } + return 0; +} + +static void cw1200_pm_release(struct device *dev) +{ +} + +static int cw1200_pm_probe(struct platform_device *pdev) +{ + pdev->dev.release = cw1200_pm_release; + return 0; +} + +int cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) +{ + struct cw1200_common *priv = hw->priv; + struct cw1200_pm_state *pm_state = &priv->pm_state; + struct cw1200_suspend_state *state; + int ret; + +#ifndef CONFIG_WAKELOCK + spin_lock_bh(&pm_state->lock); + ret = timer_pending(&pm_state->stay_awake); + spin_unlock_bh(&pm_state->lock); + if (ret) + return -EAGAIN; +#endif + + /* Do not suspend when datapath is not idle */ + if (priv->tx_queue_stats.num_queued) + return -EBUSY; + + /* Make sure there is no configuration requests in progress. */ + if (!mutex_trylock(&priv->conf_mutex)) + return -EBUSY; + + /* Ensure pending operations are done. + * Note also that wow_suspend must return in ~2.5sec, before + * watchdog is triggered. */ + if (priv->channel_switch_in_progress) + goto revert1; + + /* Do not suspend when join work is scheduled */ + if (work_pending(&priv->join_work)) + goto revert1; + + /* Do not suspend when scanning */ + if (down_trylock(&priv->scan.lock)) + goto revert1; + + /* Lock TX. */ + wsm_lock_tx_async(priv); + + /* Wait to avoid possible race with bh code. + * But do not wait too long... */ + if (wait_event_timeout(priv->bh_evt_wq, + !priv->hw_bufs_used, HZ / 10) <= 0) + goto revert2; + + /* Set UDP filter */ + wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_on.hdr); + + /* Set ethernet frame type filter */ + wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_on.hdr); + + /* Allocate state */ + state = kzalloc(sizeof(struct cw1200_suspend_state), GFP_KERNEL); + if (!state) + goto revert3; + + /* Store delayed work states. */ + state->bss_loss_tmo = + cw1200_suspend_work(&priv->bss_loss_work); + state->connection_loss_tmo = + cw1200_suspend_work(&priv->connection_loss_work); + state->join_tmo = + cw1200_suspend_work(&priv->join_timeout); + state->direct_probe = + cw1200_suspend_work(&priv->scan.probe_work); + state->link_id_gc = + cw1200_suspend_work(&priv->link_id_gc_work); + + /* Enable beacon skipping */ + if (priv->join_status == CW1200_JOIN_STATUS_STA + && priv->join_dtim_period + && !priv->has_multicast_subscription) { + state->beacon_skipping = true; + wsm_set_beacon_wakeup_period(priv, + priv->join_dtim_period, + CW1200_BEACON_SKIPPING_MULTIPLIER * + priv->join_dtim_period); + } + + /* Stop serving thread */ + if (cw1200_bh_suspend(priv)) + goto revert4; + + ret = timer_pending(&priv->mcast_timeout); + if (ret) + goto revert5; + + /* Cancel block ack stat timer */ + del_timer_sync(&priv->ba_timer); + + /* Store suspend state */ + pm_state->suspend_state = state; + + /* Enable IRQ wake */ + ret = priv->sbus_ops->power_mgmt(priv->sbus_priv, true); + if (ret) { + wiphy_err(priv->hw->wiphy, + "%s: PM request failed: %d. WoW is disabled.\n", + __func__, ret); + cw1200_wow_resume(hw); + return -EBUSY; + } + + /* Force resume if event is coming from the device. */ + if (atomic_read(&priv->bh_rx)) { + cw1200_wow_resume(hw); + return -EAGAIN; + } + + return 0; + +revert5: + WARN_ON(cw1200_bh_resume(priv)); +revert4: + cw1200_resume_work(priv, &priv->bss_loss_work, + state->bss_loss_tmo); + cw1200_resume_work(priv, &priv->connection_loss_work, + state->connection_loss_tmo); + cw1200_resume_work(priv, &priv->join_timeout, + state->join_tmo); + cw1200_resume_work(priv, &priv->scan.probe_work, + state->direct_probe); + cw1200_resume_work(priv, &priv->link_id_gc_work, + state->link_id_gc); + kfree(state); +revert3: + wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off); + wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off); +revert2: + wsm_unlock_tx(priv); + up(&priv->scan.lock); +revert1: + mutex_unlock(&priv->conf_mutex); + return -EBUSY; +} + +int cw1200_wow_resume(struct ieee80211_hw *hw) +{ + struct cw1200_common *priv = hw->priv; + struct cw1200_pm_state *pm_state = &priv->pm_state; + struct cw1200_suspend_state *state; + + state = pm_state->suspend_state; + pm_state->suspend_state = NULL; + + /* Disable IRQ wake */ + priv->sbus_ops->power_mgmt(priv->sbus_priv, false); + + /* Resume BH thread */ + WARN_ON(cw1200_bh_resume(priv)); + + if (state->beacon_skipping) { + wsm_set_beacon_wakeup_period(priv, priv->beacon_int * + priv->join_dtim_period > + MAX_BEACON_SKIP_TIME_MS ? 1 : + priv->join_dtim_period, 0); + state->beacon_skipping = false; + } + + /* Resume delayed work */ + cw1200_resume_work(priv, &priv->bss_loss_work, + state->bss_loss_tmo); + cw1200_resume_work(priv, &priv->connection_loss_work, + state->connection_loss_tmo); + cw1200_resume_work(priv, &priv->join_timeout, + state->join_tmo); + cw1200_resume_work(priv, &priv->scan.probe_work, + state->direct_probe); + cw1200_resume_work(priv, &priv->link_id_gc_work, + state->link_id_gc); + + /* Restart block ack stat */ + spin_lock_bh(&priv->ba_lock); + if (priv->ba_cnt) + mod_timer(&priv->ba_timer, + jiffies + CW1200_BLOCK_ACK_INTERVAL); + spin_unlock_bh(&priv->ba_lock); + + /* Remove UDP port filter */ + wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off); + + /* Remove ethernet frame type filter */ + wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off); + + /* Unlock datapath */ + wsm_unlock_tx(priv); + + /* Unlock scan */ + up(&priv->scan.lock); + + /* Unlock configuration mutex */ + mutex_unlock(&priv->conf_mutex); + + /* Free memory */ + kfree(state); + + return 0; +} diff --git a/drivers/staging/cw1200/pm.h b/drivers/staging/cw1200/pm.h new file mode 100644 index 00000000000..0515f6cfb92 --- /dev/null +++ b/drivers/staging/cw1200/pm.h @@ -0,0 +1,49 @@ +/* + * Mac80211 power management interface for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2011, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef PM_H_INCLUDED +#define PM_H_INCLUDED + +#ifdef CONFIG_WAKELOCK +#include <linux/wakelock.h> +#endif + +/* ******************************************************************** */ +/* mac80211 API */ + +#ifdef CONFIG_PM + +/* extern */ struct cw1200_common; +/* private */ struct cw1200_suspend_state; + +struct cw1200_pm_state { + struct cw1200_suspend_state *suspend_state; +#ifdef CONFIG_WAKELOCK + struct wake_lock wakelock; +#else + struct timer_list stay_awake; +#endif + struct platform_device *pm_dev; + spinlock_t lock; +}; + +int cw1200_pm_init(struct cw1200_pm_state *pm, + struct cw1200_common *priv); +void cw1200_pm_deinit(struct cw1200_pm_state *pm); +void cw1200_pm_stay_awake(struct cw1200_pm_state *pm, + unsigned long tmo); +int cw1200_wow_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan); +int cw1200_wow_resume(struct ieee80211_hw *hw); + +#endif /* CONFIG_PM */ + +#endif diff --git a/drivers/staging/cw1200/queue.c b/drivers/staging/cw1200/queue.c new file mode 100644 index 00000000000..014e6b2a8a4 --- /dev/null +++ b/drivers/staging/cw1200/queue.c @@ -0,0 +1,584 @@ +/* + * O(1) TX queue with built-in allocator for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <net/mac80211.h> +#include <linux/sched.h> +#include "queue.h" +#include "cw1200.h" +#include "debug.h" + +/* private */ struct cw1200_queue_item +{ + struct list_head head; + struct sk_buff *skb; + u32 packetID; + unsigned long queue_timestamp; + unsigned long xmit_timestamp; + struct cw1200_txpriv txpriv; + u8 generation; +}; + +static inline void __cw1200_queue_lock(struct cw1200_queue *queue) +{ + struct cw1200_queue_stats *stats = queue->stats; + if (queue->tx_locked_cnt++ == 0) { + txrx_printk(KERN_DEBUG "[TX] Queue %d is locked.\n", + queue->queue_id); + ieee80211_stop_queue(stats->priv->hw, queue->queue_id); + } +} + +static inline void __cw1200_queue_unlock(struct cw1200_queue *queue) +{ + struct cw1200_queue_stats *stats = queue->stats; + BUG_ON(!queue->tx_locked_cnt); + if (--queue->tx_locked_cnt == 0) { + txrx_printk(KERN_DEBUG "[TX] Queue %d is unlocked.\n", + queue->queue_id); + ieee80211_wake_queue(stats->priv->hw, queue->queue_id); + } +} + +static inline void cw1200_queue_parse_id(u32 packetID, u8 *queue_generation, + u8 *queue_id, + u8 *item_generation, + u8 *item_id) +{ + *item_id = (packetID >> 0) & 0xFF; + *item_generation = (packetID >> 8) & 0xFF; + *queue_id = (packetID >> 16) & 0xFF; + *queue_generation = (packetID >> 24) & 0xFF; +} + +static inline u32 cw1200_queue_make_packet_id(u8 queue_generation, u8 queue_id, + u8 item_generation, u8 item_id) +{ + return ((u32)item_id << 0) | + ((u32)item_generation << 8) | + ((u32)queue_id << 16) | + ((u32)queue_generation << 24); +} + +static void cw1200_queue_post_gc(struct cw1200_queue_stats *stats, + struct list_head *gc_list) +{ + struct cw1200_queue_item *item; + + while (!list_empty(gc_list)) { + item = list_first_entry( + gc_list, struct cw1200_queue_item, head); + list_del(&item->head); + stats->skb_dtor(stats->priv, item->skb, &item->txpriv); + kfree(item); + } +} + +static void cw1200_queue_register_post_gc(struct list_head *gc_list, + struct cw1200_queue_item *item) +{ + struct cw1200_queue_item *gc_item; + gc_item = kmalloc(sizeof(struct cw1200_queue_item), + GFP_ATOMIC); + BUG_ON(!gc_item); + memcpy(gc_item, item, sizeof(struct cw1200_queue_item)); + list_add_tail(&gc_item->head, gc_list); +} + +static void __cw1200_queue_gc(struct cw1200_queue *queue, + struct list_head *head, + bool unlock) +{ + struct cw1200_queue_stats *stats = queue->stats; + struct cw1200_queue_item *item = NULL; + bool wakeup_stats = false; + + while (!list_empty(&queue->queue)) { + item = list_first_entry( + &queue->queue, struct cw1200_queue_item, head); + if (jiffies - item->queue_timestamp < queue->ttl) + break; + --queue->num_queued; + --queue->link_map_cache[item->txpriv.link_id]; + spin_lock_bh(&stats->lock); + --stats->num_queued; + if (!--stats->link_map_cache[item->txpriv.link_id]) + wakeup_stats = true; + spin_unlock_bh(&stats->lock); + cw1200_debug_tx_ttl(stats->priv); + cw1200_queue_register_post_gc(head, item); + item->skb = NULL; + list_move_tail(&item->head, &queue->free_pool); + } + + if (wakeup_stats) + wake_up(&stats->wait_link_id_empty); + + if (queue->overfull) { + if (queue->num_queued <= (queue->capacity >> 1)) { + queue->overfull = false; + if (unlock) + __cw1200_queue_unlock(queue); + } else { + unsigned long tmo = item->queue_timestamp + queue->ttl; + mod_timer(&queue->gc, tmo); + cw1200_pm_stay_awake(&stats->priv->pm_state, + tmo - jiffies); + } + } +} + +static void cw1200_queue_gc(unsigned long arg) +{ + LIST_HEAD(list); + struct cw1200_queue *queue = + (struct cw1200_queue *)arg; + + spin_lock_bh(&queue->lock); + __cw1200_queue_gc(queue, &list, true); + spin_unlock_bh(&queue->lock); + cw1200_queue_post_gc(queue->stats, &list); +} + +int cw1200_queue_stats_init(struct cw1200_queue_stats *stats, + size_t map_capacity, + cw1200_queue_skb_dtor_t skb_dtor, + struct cw1200_common *priv) +{ + memset(stats, 0, sizeof(*stats)); + stats->map_capacity = map_capacity; + stats->skb_dtor = skb_dtor; + stats->priv = priv; + spin_lock_init(&stats->lock); + init_waitqueue_head(&stats->wait_link_id_empty); + + stats->link_map_cache = kzalloc(sizeof(int[map_capacity]), + GFP_KERNEL); + if (!stats->link_map_cache) + return -ENOMEM; + + return 0; +} + +int cw1200_queue_init(struct cw1200_queue *queue, + struct cw1200_queue_stats *stats, + u8 queue_id, + size_t capacity, + unsigned long ttl) +{ + size_t i; + + memset(queue, 0, sizeof(*queue)); + queue->stats = stats; + queue->capacity = capacity; + queue->queue_id = queue_id; + queue->ttl = ttl; + INIT_LIST_HEAD(&queue->queue); + INIT_LIST_HEAD(&queue->pending); + INIT_LIST_HEAD(&queue->free_pool); + spin_lock_init(&queue->lock); + init_timer(&queue->gc); + queue->gc.data = (unsigned long)queue; + queue->gc.function = cw1200_queue_gc; + + queue->pool = kzalloc(sizeof(struct cw1200_queue_item) * capacity, + GFP_KERNEL); + if (!queue->pool) + return -ENOMEM; + + queue->link_map_cache = kzalloc(sizeof(int[stats->map_capacity]), + GFP_KERNEL); + if (!queue->link_map_cache) { + kfree(queue->pool); + queue->pool = NULL; + return -ENOMEM; + } + + for (i = 0; i < capacity; ++i) + list_add_tail(&queue->pool[i].head, &queue->free_pool); + + return 0; +} + +int cw1200_queue_clear(struct cw1200_queue *queue) +{ + int i; + LIST_HEAD(gc_list); + struct cw1200_queue_stats *stats = queue->stats; + + spin_lock_bh(&queue->lock); + queue->generation++; + list_splice_tail_init(&queue->queue, &queue->pending); + while (!list_empty(&queue->pending)) { + struct cw1200_queue_item *item = list_first_entry( + &queue->pending, struct cw1200_queue_item, head); + WARN_ON(!item->skb); + cw1200_queue_register_post_gc(&gc_list, item); + item->skb = NULL; + list_move_tail(&item->head, &queue->free_pool); + } + queue->num_queued = 0; + queue->num_pending = 0; + + spin_lock_bh(&stats->lock); + for (i = 0; i < stats->map_capacity; ++i) { + stats->num_queued -= queue->link_map_cache[i]; + stats->link_map_cache[i] -= queue->link_map_cache[i]; + queue->link_map_cache[i] = 0; + } + spin_unlock_bh(&stats->lock); + if (unlikely(queue->overfull)) { + queue->overfull = false; + __cw1200_queue_unlock(queue); + } + spin_unlock_bh(&queue->lock); + wake_up(&stats->wait_link_id_empty); + cw1200_queue_post_gc(stats, &gc_list); + return 0; +} + +void cw1200_queue_stats_deinit(struct cw1200_queue_stats *stats) +{ + kfree(stats->link_map_cache); + stats->link_map_cache = NULL; +} + +void cw1200_queue_deinit(struct cw1200_queue *queue) +{ + cw1200_queue_clear(queue); + del_timer_sync(&queue->gc); + INIT_LIST_HEAD(&queue->free_pool); + kfree(queue->pool); + kfree(queue->link_map_cache); + queue->pool = NULL; + queue->link_map_cache = NULL; + queue->capacity = 0; +} + +size_t cw1200_queue_get_num_queued(struct cw1200_queue *queue, + u32 link_id_map) +{ + size_t ret; + int i, bit; + size_t map_capacity = queue->stats->map_capacity; + + if (!link_id_map) + return 0; + + spin_lock_bh(&queue->lock); + if (likely(link_id_map == (u32) -1)) + ret = queue->num_queued - queue->num_pending; + else { + ret = 0; + for (i = 0, bit = 1; i < map_capacity; ++i, bit <<= 1) { + if (link_id_map & bit) + ret += queue->link_map_cache[i]; + } + } + spin_unlock_bh(&queue->lock); + return ret; +} + +int cw1200_queue_put(struct cw1200_queue *queue, + struct sk_buff *skb, + struct cw1200_txpriv *txpriv) +{ + int ret = 0; + LIST_HEAD(gc_list); + struct cw1200_queue_stats *stats = queue->stats; + + if (txpriv->link_id >= queue->stats->map_capacity) + return -EINVAL; + + spin_lock_bh(&queue->lock); + if (!WARN_ON(list_empty(&queue->free_pool))) { + struct cw1200_queue_item *item = list_first_entry( + &queue->free_pool, struct cw1200_queue_item, head); + BUG_ON(item->skb); + + list_move_tail(&item->head, &queue->queue); + item->skb = skb; + item->txpriv = *txpriv; + item->generation = 0; + item->packetID = cw1200_queue_make_packet_id( + queue->generation, queue->queue_id, + item->generation, item - queue->pool); + item->queue_timestamp = jiffies; + + ++queue->num_queued; + ++queue->link_map_cache[txpriv->link_id]; + + spin_lock_bh(&stats->lock); + ++stats->num_queued; + ++stats->link_map_cache[txpriv->link_id]; + spin_unlock_bh(&stats->lock); + + /* + * TX may happen in parallel sometimes. + * Leave extra queue slots so we don't overflow. + */ + if (queue->overfull == false && + queue->num_queued >= + (queue->capacity - (num_present_cpus() - 1))) { + queue->overfull = true; + __cw1200_queue_lock(queue); + mod_timer(&queue->gc, jiffies); + } + } else { + ret = -ENOENT; + } + spin_unlock_bh(&queue->lock); + return ret; +} + +int cw1200_queue_get(struct cw1200_queue *queue, + u32 link_id_map, + struct wsm_tx **tx, + struct ieee80211_tx_info **tx_info, + const struct cw1200_txpriv **txpriv) +{ + int ret = -ENOENT; + struct cw1200_queue_item *item; + struct cw1200_queue_stats *stats = queue->stats; + bool wakeup_stats = false; + + spin_lock_bh(&queue->lock); + list_for_each_entry(item, &queue->queue, head) { + if (link_id_map & BIT(item->txpriv.link_id)) { + ret = 0; + break; + } + } + + if (!WARN_ON(ret)) { + *tx = (struct wsm_tx *)item->skb->data; + *tx_info = IEEE80211_SKB_CB(item->skb); + *txpriv = &item->txpriv; + (*tx)->packetID = __cpu_to_le32(item->packetID); + list_move_tail(&item->head, &queue->pending); + ++queue->num_pending; + --queue->link_map_cache[item->txpriv.link_id]; + item->xmit_timestamp = jiffies; + + spin_lock_bh(&stats->lock); + --stats->num_queued; + if (!--stats->link_map_cache[item->txpriv.link_id]) + wakeup_stats = true; + spin_unlock_bh(&stats->lock); + } + spin_unlock_bh(&queue->lock); + if (wakeup_stats) + wake_up(&stats->wait_link_id_empty); + return ret; +} + +int cw1200_queue_requeue(struct cw1200_queue *queue, u32 packetID) +{ + int ret = 0; + u8 queue_generation, queue_id, item_generation, item_id; + struct cw1200_queue_item *item; + struct cw1200_queue_stats *stats = queue->stats; + + cw1200_queue_parse_id(packetID, &queue_generation, &queue_id, + &item_generation, &item_id); + + item = &queue->pool[item_id]; + + spin_lock_bh(&queue->lock); + BUG_ON(queue_id != queue->queue_id); + if (unlikely(queue_generation != queue->generation)) { + ret = -ENOENT; + } else if (unlikely(item_id >= (unsigned) queue->capacity)) { + WARN_ON(1); + ret = -EINVAL; + } else if (unlikely(item->generation != item_generation)) { + WARN_ON(1); + ret = -ENOENT; + } else { + --queue->num_pending; + ++queue->link_map_cache[item->txpriv.link_id]; + + spin_lock_bh(&stats->lock); + ++stats->num_queued; + ++stats->link_map_cache[item->txpriv.link_id]; + spin_unlock_bh(&stats->lock); + + item->generation = ++item_generation; + item->packetID = cw1200_queue_make_packet_id( + queue_generation, queue_id, item_generation, item_id); + list_move(&item->head, &queue->queue); + } + spin_unlock_bh(&queue->lock); + return ret; +} + +int cw1200_queue_requeue_all(struct cw1200_queue *queue) +{ + struct cw1200_queue_stats *stats = queue->stats; + spin_lock_bh(&queue->lock); + while (!list_empty(&queue->pending)) { + struct cw1200_queue_item *item = list_entry( + queue->pending.prev, struct cw1200_queue_item, head); + + --queue->num_pending; + ++queue->link_map_cache[item->txpriv.link_id]; + + spin_lock_bh(&stats->lock); + ++stats->num_queued; + ++stats->link_map_cache[item->txpriv.link_id]; + spin_unlock_bh(&stats->lock); + + ++item->generation; + item->packetID = cw1200_queue_make_packet_id( + queue->generation, queue->queue_id, + item->generation, item - queue->pool); + list_move(&item->head, &queue->queue); + } + spin_unlock_bh(&queue->lock); + + return 0; +} + +int cw1200_queue_remove(struct cw1200_queue *queue, u32 packetID) +{ + int ret = 0; + u8 queue_generation, queue_id, item_generation, item_id; + struct cw1200_queue_item *item; + struct cw1200_queue_stats *stats = queue->stats; + struct sk_buff *gc_skb = NULL; + struct cw1200_txpriv gc_txpriv; + + cw1200_queue_parse_id(packetID, &queue_generation, &queue_id, + &item_generation, &item_id); + + item = &queue->pool[item_id]; + + spin_lock_bh(&queue->lock); + BUG_ON(queue_id != queue->queue_id); + if (unlikely(queue_generation != queue->generation)) { + ret = -ENOENT; + } else if (unlikely(item_id >= (unsigned) queue->capacity)) { + WARN_ON(1); + ret = -EINVAL; + } else if (unlikely(item->generation != item_generation)) { + WARN_ON(1); + ret = -ENOENT; + } else { + gc_txpriv = item->txpriv; + gc_skb = item->skb; + item->skb = NULL; + --queue->num_pending; + --queue->num_queued; + ++queue->num_sent; + ++item->generation; + /* Do not use list_move_tail here, but list_move: + * try to utilize cache row. + */ + list_move(&item->head, &queue->free_pool); + + if (unlikely(queue->overfull) && + (queue->num_queued <= (queue->capacity >> 1))) { + queue->overfull = false; + __cw1200_queue_unlock(queue); + } + } + spin_unlock_bh(&queue->lock); + + if (gc_skb) + stats->skb_dtor(stats->priv, gc_skb, &gc_txpriv); + + return ret; +} + +int cw1200_queue_get_skb(struct cw1200_queue *queue, u32 packetID, + struct sk_buff **skb, + const struct cw1200_txpriv **txpriv) +{ + int ret = 0; + u8 queue_generation, queue_id, item_generation, item_id; + struct cw1200_queue_item *item; + cw1200_queue_parse_id(packetID, &queue_generation, &queue_id, + &item_generation, &item_id); + + item = &queue->pool[item_id]; + + spin_lock_bh(&queue->lock); + BUG_ON(queue_id != queue->queue_id); + if (unlikely(queue_generation != queue->generation)) { + ret = -ENOENT; + } else if (unlikely(item_id >= (unsigned) queue->capacity)) { + WARN_ON(1); + ret = -EINVAL; + } else if (unlikely(item->generation != item_generation)) { + WARN_ON(1); + ret = -ENOENT; + } else { + *skb = item->skb; + *txpriv = &item->txpriv; + } + spin_unlock_bh(&queue->lock); + return ret; +} + +void cw1200_queue_lock(struct cw1200_queue *queue) +{ + spin_lock_bh(&queue->lock); + __cw1200_queue_lock(queue); + spin_unlock_bh(&queue->lock); +} + +void cw1200_queue_unlock(struct cw1200_queue *queue) +{ + spin_lock_bh(&queue->lock); + __cw1200_queue_unlock(queue); + spin_unlock_bh(&queue->lock); +} + +bool cw1200_queue_get_xmit_timestamp(struct cw1200_queue *queue, + unsigned long *timestamp) +{ + struct cw1200_queue_item *item; + bool ret; + + spin_lock_bh(&queue->lock); + ret = !list_empty(&queue->pending); + if (ret) { + list_for_each_entry(item, &queue->pending, head) { + if (time_before(item->xmit_timestamp, *timestamp)) + *timestamp = item->xmit_timestamp; + } + } + spin_unlock_bh(&queue->lock); + return ret; +} + +bool cw1200_queue_stats_is_empty(struct cw1200_queue_stats *stats, + u32 link_id_map) +{ + bool empty = true; + + spin_lock_bh(&stats->lock); + if (link_id_map == (u32)-1) + empty = stats->num_queued == 0; + else { + int i; + for (i = 0; i < stats->map_capacity; ++i) { + if (link_id_map & BIT(i)) { + if (stats->link_map_cache[i]) { + empty = false; + break; + } + } + } + } + spin_unlock_bh(&stats->lock); + + return empty; +} diff --git a/drivers/staging/cw1200/queue.h b/drivers/staging/cw1200/queue.h new file mode 100644 index 00000000000..aa9a7f9444c --- /dev/null +++ b/drivers/staging/cw1200/queue.h @@ -0,0 +1,116 @@ +/* + * O(1) TX queue with built-in allocator for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef CW1200_QUEUE_H_INCLUDED +#define CW1200_QUEUE_H_INCLUDED + +/* private */ struct cw1200_queue_item; + +/* extern */ struct sk_buff; +/* extern */ struct wsm_tx; +/* extern */ struct cw1200_common; +/* extern */ struct ieee80211_tx_queue_stats; +/* extern */ struct cw1200_txpriv; + +/* forward */ struct cw1200_queue_stats; + +typedef void (*cw1200_queue_skb_dtor_t)(struct cw1200_common *priv, + struct sk_buff *skb, + const struct cw1200_txpriv *txpriv); + +struct cw1200_queue { + struct cw1200_queue_stats *stats; + size_t capacity; + size_t num_queued; + size_t num_pending; + size_t num_sent; + struct cw1200_queue_item *pool; + struct list_head queue; + struct list_head free_pool; + struct list_head pending; + int tx_locked_cnt; + int *link_map_cache; + bool overfull; + spinlock_t lock; + u8 queue_id; + u8 generation; + struct timer_list gc; + unsigned long ttl; +}; + +struct cw1200_queue_stats { + spinlock_t lock; + int *link_map_cache; + int num_queued; + size_t map_capacity; + wait_queue_head_t wait_link_id_empty; + cw1200_queue_skb_dtor_t skb_dtor; + struct cw1200_common *priv; +}; + +struct cw1200_txpriv { + u8 link_id; + u8 raw_link_id; + u8 tid; + u8 rate_id; + u8 offset; +}; + +int cw1200_queue_stats_init(struct cw1200_queue_stats *stats, + size_t map_capacity, + cw1200_queue_skb_dtor_t skb_dtor, + struct cw1200_common *priv); +int cw1200_queue_init(struct cw1200_queue *queue, + struct cw1200_queue_stats *stats, + u8 queue_id, + size_t capacity, + unsigned long ttl); +int cw1200_queue_clear(struct cw1200_queue *queue); +void cw1200_queue_stats_deinit(struct cw1200_queue_stats *stats); +void cw1200_queue_deinit(struct cw1200_queue *queue); + +size_t cw1200_queue_get_num_queued(struct cw1200_queue *queue, + u32 link_id_map); +int cw1200_queue_put(struct cw1200_queue *queue, + struct sk_buff *skb, + struct cw1200_txpriv *txpriv); +int cw1200_queue_get(struct cw1200_queue *queue, + u32 link_id_map, + struct wsm_tx **tx, + struct ieee80211_tx_info **tx_info, + const struct cw1200_txpriv **txpriv); +int cw1200_queue_requeue(struct cw1200_queue *queue, u32 packetID); +int cw1200_queue_requeue_all(struct cw1200_queue *queue); +int cw1200_queue_remove(struct cw1200_queue *queue, + u32 packetID); +int cw1200_queue_get_skb(struct cw1200_queue *queue, u32 packetID, + struct sk_buff **skb, + const struct cw1200_txpriv **txpriv); +void cw1200_queue_lock(struct cw1200_queue *queue); +void cw1200_queue_unlock(struct cw1200_queue *queue); +bool cw1200_queue_get_xmit_timestamp(struct cw1200_queue *queue, + unsigned long *timestamp); + + +bool cw1200_queue_stats_is_empty(struct cw1200_queue_stats *stats, + u32 link_id_map); + +static inline u8 cw1200_queue_get_queue_id(u32 packetID) +{ + return (packetID >> 16) & 0xFF; +} + +static inline u8 cw1200_queue_get_generation(u32 packetID) +{ + return (packetID >> 8) & 0xFF; +} + +#endif /* CW1200_QUEUE_H_INCLUDED */ diff --git a/drivers/staging/cw1200/sbus.h b/drivers/staging/cw1200/sbus.h new file mode 100644 index 00000000000..49bd06d20e5 --- /dev/null +++ b/drivers/staging/cw1200/sbus.h @@ -0,0 +1,39 @@ +/* + * Common sbus abstraction layer interface for cw1200 wireless driver + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef CW1200_SBUS_H +#define CW1200_SBUS_H + +/* + * sbus priv forward definition. + * Implemented and instantiated in particular modules. + */ +struct sbus_priv; + +typedef void (*sbus_irq_handler)(void *priv); + +struct sbus_ops { + int (*sbus_memcpy_fromio)(struct sbus_priv *self, unsigned int addr, + void *dst, int count); + int (*sbus_memcpy_toio)(struct sbus_priv *self, unsigned int addr, + const void *src, int count); + void (*lock)(struct sbus_priv *self); + void (*unlock)(struct sbus_priv *self); + int (*irq_subscribe)(struct sbus_priv *self, sbus_irq_handler handler, + void *priv); + int (*irq_unsubscribe)(struct sbus_priv *self); + int (*reset)(struct sbus_priv *self); + size_t (*align_size)(struct sbus_priv *self, size_t size); + int (*power_mgmt)(struct sbus_priv *self, bool suspend); + int (*set_block_size)(struct sbus_priv *self, size_t size); +}; + +#endif /* CW1200_SBUS_H */ diff --git a/drivers/staging/cw1200/scan.c b/drivers/staging/cw1200/scan.c new file mode 100644 index 00000000000..b12af9ded62 --- /dev/null +++ b/drivers/staging/cw1200/scan.c @@ -0,0 +1,446 @@ +/* + * Scan implementation for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/sched.h> +#include "cw1200.h" +#include "scan.h" +#include "sta.h" +#include "pm.h" + +static void cw1200_scan_restart_delayed(struct cw1200_common *priv); + +static int cw1200_scan_start(struct cw1200_common *priv, struct wsm_scan *scan) +{ + int ret, i; + int tmo = 2000; + + for (i = 0; i < scan->numOfChannels; ++i) + tmo += scan->ch[i].maxChannelTime + 10; + + atomic_set(&priv->scan.in_progress, 1); + cw1200_pm_stay_awake(&priv->pm_state, tmo * HZ / 1000); + queue_delayed_work(priv->workqueue, &priv->scan.timeout, + tmo * HZ / 1000); + ret = wsm_scan(priv, scan); + if (unlikely(ret)) { + atomic_set(&priv->scan.in_progress, 0); + cancel_delayed_work_sync(&priv->scan.timeout); + cw1200_scan_restart_delayed(priv); + } + return ret; +} + +int cw1200_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct cw1200_common *priv = hw->priv; + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_PROBE_REQUEST, + }; + int i; + + if (!priv->vif) + return -EINVAL; + + /* Scan when P2P_GO corrupt firmware MiniAP mode */ + if (priv->join_status == CW1200_JOIN_STATUS_AP) + return -EOPNOTSUPP; + + if (req->n_ssids == 1 && !req->ssids[0].ssid_len) + req->n_ssids = 0; + + wiphy_dbg(hw->wiphy, "[SCAN] Scan request for %d SSIDs.\n", + req->n_ssids); + + if (req->n_ssids > WSM_SCAN_MAX_NUM_OF_SSIDS) + return -EINVAL; + + frame.skb = ieee80211_probereq_get(hw, priv->vif, NULL, 0, + req->ie, req->ie_len); + if (!frame.skb) + return -ENOMEM; + + /* will be unlocked in cw1200_scan_work() */ + down(&priv->scan.lock); + mutex_lock(&priv->conf_mutex); + if (frame.skb) { + int ret = wsm_set_template_frame(priv, &frame); + if (0 == ret) { + /* + * set empty probe response template in order + * to receive probe requests from firmware + */ + frame.frame_type = WSM_FRAME_TYPE_PROBE_RESPONSE; + frame.disable = true; + ret = wsm_set_template_frame(priv, &frame); + } + if (ret) { + mutex_unlock(&priv->conf_mutex); + up(&priv->scan.lock); + dev_kfree_skb(frame.skb); + return ret; + } + } + + wsm_lock_tx(priv); + + BUG_ON(priv->scan.req); + priv->scan.req = req; + priv->scan.n_ssids = 0; + priv->scan.status = 0; + priv->scan.begin = &req->channels[0]; + priv->scan.curr = priv->scan.begin; + priv->scan.end = &req->channels[req->n_channels]; + priv->scan.output_power = priv->output_power; + + for (i = 0; i < req->n_ssids; ++i) { + struct wsm_ssid *dst = + &priv->scan.ssids[priv->scan.n_ssids]; + BUG_ON(req->ssids[i].ssid_len > sizeof(dst->ssid)); + memcpy(&dst->ssid[0], req->ssids[i].ssid, + sizeof(dst->ssid)); + dst->length = req->ssids[i].ssid_len; + ++priv->scan.n_ssids; + } + + mutex_unlock(&priv->conf_mutex); + + if (frame.skb) + dev_kfree_skb(frame.skb); + queue_work(priv->workqueue, &priv->scan.work); + return 0; +} + +void cw1200_scan_work(struct work_struct *work) +{ + struct cw1200_common *priv = container_of(work, struct cw1200_common, + scan.work); + struct ieee80211_channel **it; + struct wsm_scan scan = { + .scanType = WSM_SCAN_TYPE_FOREGROUND, + .scanFlags = WSM_SCAN_FLAG_SPLIT_METHOD, + }; + bool first_run = priv->scan.begin == priv->scan.curr && + priv->scan.begin != priv->scan.end; + int i; + + if (first_run) { + /* Firmware gets crazy if scan request is sent + * when STA is joined but not yet associated. + * Force unjoin in this case. */ + if (cancel_delayed_work_sync(&priv->join_timeout) > 0) + cw1200_join_timeout(&priv->join_timeout.work); + } + + mutex_lock(&priv->conf_mutex); + + if (first_run) { + if (priv->join_status == CW1200_JOIN_STATUS_STA && + !(priv->powersave_mode.pmMode & WSM_PSM_PS)) { + struct wsm_set_pm pm = priv->powersave_mode; + pm.pmMode = WSM_PSM_PS; + cw1200_set_pm(priv, &pm); + } else if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) { + /* FW bug: driver has to restart p2p-dev mode + * after scan */ + cw1200_disable_listening(priv); + } + } + + if (!priv->scan.req || (priv->scan.curr == priv->scan.end)) { + if (priv->scan.output_power != priv->output_power) + WARN_ON(wsm_set_output_power(priv, + priv->output_power * 10)); + if (priv->join_status == CW1200_JOIN_STATUS_STA && + !(priv->powersave_mode.pmMode & WSM_PSM_PS)) + cw1200_set_pm(priv, &priv->powersave_mode); + + if (priv->scan.status < 0) + wiphy_dbg(priv->hw->wiphy, + "[SCAN] Scan failed (%d).\n", + priv->scan.status); + else if (priv->scan.req) + wiphy_dbg(priv->hw->wiphy, + "[SCAN] Scan completed.\n"); + else + wiphy_dbg(priv->hw->wiphy, + "[SCAN] Scan canceled.\n"); + + priv->scan.req = NULL; + cw1200_scan_restart_delayed(priv); + wsm_unlock_tx(priv); + mutex_unlock(&priv->conf_mutex); + ieee80211_scan_completed(priv->hw, priv->scan.status ? 1 : 0); + up(&priv->scan.lock); + return; + } else { + struct ieee80211_channel *first = *priv->scan.curr; + for (it = priv->scan.curr + 1, i = 1; + it != priv->scan.end && i < WSM_SCAN_MAX_NUM_OF_CHANNELS; + ++it, ++i) { + if ((*it)->band != first->band) + break; + if (((*it)->flags ^ first->flags) & + IEEE80211_CHAN_PASSIVE_SCAN) + break; + if (!(first->flags & IEEE80211_CHAN_PASSIVE_SCAN) && + (*it)->max_power != first->max_power) + break; + } + scan.band = first->band; + + if (priv->scan.req->no_cck) + scan.maxTransmitRate = WSM_TRANSMIT_RATE_6; + else + scan.maxTransmitRate = WSM_TRANSMIT_RATE_1; + /* TODO: Is it optimal? */ + scan.numOfProbeRequests = + (first->flags & IEEE80211_CHAN_PASSIVE_SCAN) ? 0 : 2; + scan.numOfSSIDs = priv->scan.n_ssids; + scan.ssids = &priv->scan.ssids[0]; + scan.numOfChannels = it - priv->scan.curr; + /* TODO: Is it optimal? */ + scan.probeDelay = 100; + /* It is not stated in WSM specification, however + * FW team says that driver may not use FG scan + * when joined. */ + if (priv->join_status == CW1200_JOIN_STATUS_STA) { + scan.scanType = WSM_SCAN_TYPE_BACKGROUND; + scan.scanFlags = WSM_SCAN_FLAG_FORCE_BACKGROUND; + } + scan.ch = kzalloc( + sizeof(struct wsm_scan_ch[it - priv->scan.curr]), + GFP_KERNEL); + if (!scan.ch) { + priv->scan.status = -ENOMEM; + goto fail; + } + for (i = 0; i < scan.numOfChannels; ++i) { + scan.ch[i].number = priv->scan.curr[i]->hw_value; + scan.ch[i].minChannelTime = 50; + scan.ch[i].maxChannelTime = 110; + } + if (!(first->flags & IEEE80211_CHAN_PASSIVE_SCAN) && + priv->scan.output_power != first->max_power) { + priv->scan.output_power = first->max_power; + WARN_ON(wsm_set_output_power(priv, + priv->scan.output_power * 10)); + } + priv->scan.status = cw1200_scan_start(priv, &scan); + kfree(scan.ch); + if (priv->scan.status) + goto fail; + priv->scan.curr = it; + } + mutex_unlock(&priv->conf_mutex); + return; + +fail: + priv->scan.curr = priv->scan.end; + mutex_unlock(&priv->conf_mutex); + queue_work(priv->workqueue, &priv->scan.work); + return; +} + +static void cw1200_scan_restart_delayed(struct cw1200_common *priv) +{ + if (priv->delayed_link_loss) { + int tmo = priv->cqm_beacon_loss_count; + + if (priv->scan.direct_probe) + tmo = 0; + + priv->delayed_link_loss = 0; + /* Restart beacon loss timer and requeue + BSS loss work. */ + wiphy_dbg(priv->hw->wiphy, + "[CQM] Requeue BSS loss in %d " + "beacons.\n", tmo); + spin_lock(&priv->bss_loss_lock); + priv->bss_loss_status = CW1200_BSS_LOSS_NONE; + spin_unlock(&priv->bss_loss_lock); + cancel_delayed_work_sync(&priv->bss_loss_work); + queue_delayed_work(priv->workqueue, + &priv->bss_loss_work, + tmo * HZ / 10); + } + + /* FW bug: driver has to restart p2p-dev mode after scan. */ + if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) { + cw1200_enable_listening(priv); + cw1200_update_filtering(priv); + } + + if (priv->delayed_unjoin) { + priv->delayed_unjoin = false; + if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); + } +} + +static void cw1200_scan_complete(struct cw1200_common *priv) +{ + if (priv->scan.direct_probe) { + wiphy_dbg(priv->hw->wiphy, "[SCAN] Direct probe complete.\n"); + cw1200_scan_restart_delayed(priv); + priv->scan.direct_probe = 0; + up(&priv->scan.lock); + wsm_unlock_tx(priv); + } else { + cw1200_scan_work(&priv->scan.work); + } +} + +void cw1200_scan_complete_cb(struct cw1200_common *priv, + struct wsm_scan_complete *arg) +{ + if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) + /* STA is stopped. */ + return; + + if (cancel_delayed_work_sync(&priv->scan.timeout) > 0) { + priv->scan.status = 1; + queue_delayed_work(priv->workqueue, + &priv->scan.timeout, 0); + } +} + +void cw1200_scan_timeout(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, scan.timeout.work); + if (likely(atomic_xchg(&priv->scan.in_progress, 0))) { + if (priv->scan.status > 0) + priv->scan.status = 0; + else if (!priv->scan.status) { + wiphy_warn(priv->hw->wiphy, + "Timeout waiting for scan " + "complete notification.\n"); + priv->scan.status = -ETIMEDOUT; + priv->scan.curr = priv->scan.end; + WARN_ON(wsm_stop_scan(priv)); + } + cw1200_scan_complete(priv); + } +} + +void cw1200_probe_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, scan.probe_work.work); + u8 queueId = cw1200_queue_get_queue_id(priv->pending_frame_id); + struct cw1200_queue *queue = &priv->tx_queue[queueId]; + const struct cw1200_txpriv *txpriv; + struct wsm_tx *wsm; + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_PROBE_REQUEST, + }; + struct wsm_ssid ssids[1] = {{ + .length = 0, + } }; + struct wsm_scan_ch ch[1] = {{ + .minChannelTime = 0, + .maxChannelTime = 10, + } }; + struct wsm_scan scan = { + .scanType = WSM_SCAN_TYPE_FOREGROUND, + .numOfProbeRequests = 1, + .probeDelay = 0, + .numOfChannels = 1, + .ssids = ssids, + .ch = ch, + }; + u8 *ies; + size_t ies_len; + int ret; + + wiphy_dbg(priv->hw->wiphy, "[SCAN] Direct probe work.\n"); + + BUG_ON(queueId >= 4); + BUG_ON(!priv->channel); + + mutex_lock(&priv->conf_mutex); + if (unlikely(down_trylock(&priv->scan.lock))) { + /* Scan is already in progress. Requeue self. */ + schedule(); + queue_delayed_work(priv->workqueue, + &priv->scan.probe_work, HZ / 10); + mutex_unlock(&priv->conf_mutex); + return; + } + + if (cw1200_queue_get_skb(queue, priv->pending_frame_id, + &frame.skb, &txpriv)) { + up(&priv->scan.lock); + mutex_unlock(&priv->conf_mutex); + wsm_unlock_tx(priv); + return; + } + wsm = (struct wsm_tx *)frame.skb->data; + scan.maxTransmitRate = wsm->maxTxRate; + scan.band = (priv->channel->band == IEEE80211_BAND_5GHZ) ? + WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G; + if (priv->join_status == CW1200_JOIN_STATUS_STA) { + scan.scanType = WSM_SCAN_TYPE_BACKGROUND; + scan.scanFlags = WSM_SCAN_FLAG_FORCE_BACKGROUND; + } + ch[0].number = priv->channel->hw_value; + + skb_pull(frame.skb, txpriv->offset); + + ies = &frame.skb->data[sizeof(struct ieee80211_hdr_3addr)]; + ies_len = frame.skb->len - sizeof(struct ieee80211_hdr_3addr); + + if (ies_len) { + u8 *ssidie = + (u8 *)cfg80211_find_ie(WLAN_EID_SSID, ies, ies_len); + if (ssidie && ssidie[1] && ssidie[1] <= sizeof(ssids[0].ssid)) { + u8 *nextie = &ssidie[2 + ssidie[1]]; + /* Remove SSID from the IE list. It has to be provided + * as a separate argument in cw1200_scan_start call */ + + /* Store SSID localy */ + ssids[0].length = ssidie[1]; + memcpy(ssids[0].ssid, &ssidie[2], ssids[0].length); + scan.numOfSSIDs = 1; + + /* Remove SSID from IE list */ + ssidie[1] = 0; + memmove(&ssidie[2], nextie, &ies[ies_len] - nextie); + skb_trim(frame.skb, frame.skb->len - ssids[0].length); + } + } + + /* FW bug: driver has to restart p2p-dev mode after scan */ + if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) + cw1200_disable_listening(priv); + ret = WARN_ON(wsm_set_template_frame(priv, &frame)); + priv->scan.direct_probe = 1; + if (!ret) { + wsm_flush_tx(priv); + ret = WARN_ON(cw1200_scan_start(priv, &scan)); + } + mutex_unlock(&priv->conf_mutex); + + skb_push(frame.skb, txpriv->offset); + if (!ret) + IEEE80211_SKB_CB(frame.skb)->flags |= IEEE80211_TX_STAT_ACK; + BUG_ON(cw1200_queue_remove(queue, priv->pending_frame_id)); + + if (ret) { + priv->scan.direct_probe = 0; + up(&priv->scan.lock); + wsm_unlock_tx(priv); + } + + return; +} diff --git a/drivers/staging/cw1200/scan.h b/drivers/staging/cw1200/scan.h new file mode 100644 index 00000000000..abffd1d796f --- /dev/null +++ b/drivers/staging/cw1200/scan.h @@ -0,0 +1,54 @@ +/* + * Scan interface for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef SCAN_H_INCLUDED +#define SCAN_H_INCLUDED + +#include <linux/semaphore.h> +#include "wsm.h" + +/* external */ struct sk_buff; +/* external */ struct cfg80211_scan_request; +/* external */ struct ieee80211_channel; +/* external */ struct ieee80211_hw; +/* external */ struct work_struct; + +struct cw1200_scan { + struct semaphore lock; + struct work_struct work; + struct delayed_work timeout; + struct cfg80211_scan_request *req; + struct ieee80211_channel **begin; + struct ieee80211_channel **curr; + struct ieee80211_channel **end; + struct wsm_ssid ssids[WSM_SCAN_MAX_NUM_OF_SSIDS]; + int output_power; + int n_ssids; + int status; + atomic_t in_progress; + /* Direct probe requests workaround */ + struct delayed_work probe_work; + int direct_probe; +}; + +int cw1200_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req); +void cw1200_scan_work(struct work_struct *work); +void cw1200_scan_timeout(struct work_struct *work); +void cw1200_scan_complete_cb(struct cw1200_common *priv, + struct wsm_scan_complete *arg); + +/* ******************************************************************** */ +/* Raw probe requests TX workaround */ +void cw1200_probe_work(struct work_struct *work); + +#endif diff --git a/drivers/staging/cw1200/sta.c b/drivers/staging/cw1200/sta.c new file mode 100644 index 00000000000..25d414c39f7 --- /dev/null +++ b/drivers/staging/cw1200/sta.c @@ -0,0 +1,1638 @@ +/* + * Mac80211 STA API for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/vmalloc.h> +#include <linux/sched.h> +#include <linux/firmware.h> + +#include "cw1200.h" +#include "sta.h" +#include "ap.h" +#include "fwio.h" +#include "bh.h" +#include "debug.h" + +#if defined(CONFIG_CW1200_STA_DEBUG) +#define sta_printk(...) printk(__VA_ARGS__) +#else +#define sta_printk(...) +#endif + +static inline void __cw1200_free_event_queue(struct list_head *list) +{ + while (!list_empty(list)) { + struct cw1200_wsm_event *event = + list_first_entry(list, struct cw1200_wsm_event, + link); + list_del(&event->link); + kfree(event); + } +} + +/* ******************************************************************** */ +/* STA API */ + +int cw1200_start(struct ieee80211_hw *dev) +{ + struct cw1200_common *priv = dev->priv; + int ret = 0; + + mutex_lock(&priv->conf_mutex); + + /* default EDCA */ + WSM_EDCA_SET(&priv->edca, 0, 0x0002, 0x0003, 0x0007, 47, 0xc8, false); + WSM_EDCA_SET(&priv->edca, 1, 0x0002, 0x0007, 0x000f, 94, 0xc8, false); + WSM_EDCA_SET(&priv->edca, 2, 0x0003, 0x000f, 0x03ff, 0, 0xc8, false); + WSM_EDCA_SET(&priv->edca, 3, 0x0007, 0x000f, 0x03ff, 0, 0xc8, false); + ret = wsm_set_edca_params(priv, &priv->edca); + if (WARN_ON(ret)) + goto out; + + ret = cw1200_set_uapsd_param(priv, &priv->edca); + if (WARN_ON(ret)) + goto out; + + priv->setbssparams_done = false; + + memset(priv->bssid, ~0, ETH_ALEN); + memcpy(priv->mac_addr, dev->wiphy->perm_addr, ETH_ALEN); + priv->mode = NL80211_IFTYPE_MONITOR; + priv->softled_state = 0; + priv->wep_default_key_id = -1; + + priv->cqm_link_loss_count = 60; + priv->cqm_beacon_loss_count = 20; + + /* Temporary configuration - beacon filter table */ + priv->bf_table.numOfIEs = __cpu_to_le32(2); + priv->bf_table.entry[0].ieId = WLAN_EID_VENDOR_SPECIFIC; + priv->bf_table.entry[0].actionFlags = WSM_BEACON_FILTER_IE_HAS_CHANGED | + WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT | + WSM_BEACON_FILTER_IE_HAS_APPEARED; + priv->bf_table.entry[0].oui[0] = 0x50; + priv->bf_table.entry[0].oui[1] = 0x6F; + priv->bf_table.entry[0].oui[2] = 0x9A; + priv->bf_table.entry[1].ieId = WLAN_EID_ERP_INFO; + priv->bf_table.entry[1].actionFlags = WSM_BEACON_FILTER_IE_HAS_CHANGED | + WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT | + WSM_BEACON_FILTER_IE_HAS_APPEARED; + + priv->bf_control.enabled = 1; + ret = cw1200_setup_mac(priv); + if (WARN_ON(ret)) + goto out; + + /* err = cw1200_set_leds(priv); */ + +out: + mutex_unlock(&priv->conf_mutex); + return ret; +} + +void cw1200_stop(struct ieee80211_hw *dev) +{ + struct cw1200_common *priv = dev->priv; + LIST_HEAD(list); + int i; + + wsm_lock_tx(priv); + + while (down_trylock(&priv->scan.lock)) { + /* Scan is in progress. Force it to stop. */ + priv->scan.req = NULL; + schedule(); + } + up(&priv->scan.lock); + + cancel_delayed_work_sync(&priv->scan.probe_work); + cancel_delayed_work_sync(&priv->scan.timeout); + cancel_delayed_work_sync(&priv->join_timeout); + cancel_delayed_work_sync(&priv->bss_loss_work); + cancel_delayed_work_sync(&priv->connection_loss_work); + cancel_delayed_work_sync(&priv->link_id_gc_work); + flush_workqueue(priv->workqueue); + del_timer_sync(&priv->mcast_timeout); + del_timer_sync(&priv->ba_timer); + + mutex_lock(&priv->conf_mutex); + priv->mode = NL80211_IFTYPE_UNSPECIFIED; + priv->listening = false; + + priv->softled_state = 0; + /* cw1200_set_leds(priv); */ + + spin_lock(&priv->event_queue_lock); + list_splice_init(&priv->event_queue, &list); + spin_unlock(&priv->event_queue_lock); + __cw1200_free_event_queue(&list); + + priv->delayed_link_loss = 0; + + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + + for (i = 0; i < 4; i++) + cw1200_queue_clear(&priv->tx_queue[i]); + + /* HACK! */ + if (atomic_xchg(&priv->tx_lock, 1) != 1) + sta_printk(KERN_DEBUG "[STA] TX is force-unlocked " + "due to stop request.\n"); + + wsm_unlock_tx(priv); + + mutex_unlock(&priv->conf_mutex); +} + +int cw1200_add_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + int ret; + struct cw1200_common *priv = dev->priv; + /* __le32 auto_calibration_mode = __cpu_to_le32(1); */ + + mutex_lock(&priv->conf_mutex); + + if (priv->mode != NL80211_IFTYPE_MONITOR) { + mutex_unlock(&priv->conf_mutex); + return -EOPNOTSUPP; + } + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + case NL80211_IFTYPE_AP: + priv->mode = vif->type; + break; + default: + mutex_unlock(&priv->conf_mutex); + return -EOPNOTSUPP; + } + + priv->vif = vif; + memcpy(priv->mac_addr, vif->addr, ETH_ALEN); + + ret = WARN_ON(cw1200_setup_mac(priv)); + /* Enable auto-calibration */ + /* Exception in subsequent channel switch; disabled. + WARN_ON(wsm_write_mib(priv, WSM_MIB_ID_SET_AUTO_CALIBRATION_MODE, + &auto_calibration_mode, sizeof(auto_calibration_mode))); + */ + + mutex_unlock(&priv->conf_mutex); + return ret; +} + +void cw1200_remove_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct cw1200_common *priv = dev->priv; + struct wsm_reset reset = { + .reset_statistics = true, + }; + int i; + + mutex_lock(&priv->conf_mutex); + wsm_lock_tx(priv); + switch (priv->join_status) { + case CW1200_JOIN_STATUS_STA: + wsm_lock_tx(priv); + if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); + break; + case CW1200_JOIN_STATUS_AP: + for (i = 0; priv->link_id_map; ++i) { + if (priv->link_id_map & BIT(i)) { + reset.link_id = i; + wsm_reset(priv, &reset); + priv->link_id_map &= ~BIT(i); + } + } + memset(priv->link_id_db, 0, + sizeof(priv->link_id_db)); + priv->sta_asleep_mask = 0; + priv->enable_beacon = false; + priv->tx_multicast = false; + priv->aid0_bit_set = false; + priv->buffered_multicasts = false; + priv->pspoll_mask = 0; + reset.link_id = 0; + wsm_reset(priv, &reset); + break; + case CW1200_JOIN_STATUS_MONITOR: + cw1200_update_listening(priv, false); + break; + default: + break; + } + priv->vif = NULL; + priv->mode = NL80211_IFTYPE_MONITOR; + memset(priv->mac_addr, 0, ETH_ALEN); + memset(priv->bssid, 0, ETH_ALEN); + cw1200_free_keys(priv); + cw1200_setup_mac(priv); + priv->listening = false; + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + if (!__cw1200_flush(priv, true)) + wsm_unlock_tx(priv); + wsm_unlock_tx(priv); + + mutex_unlock(&priv->conf_mutex); +} + +int cw1200_config(struct ieee80211_hw *dev, u32 changed) +{ + int ret = 0; + struct cw1200_common *priv = dev->priv; + struct ieee80211_conf *conf = &dev->conf; + + down(&priv->scan.lock); + mutex_lock(&priv->conf_mutex); + /* TODO: IEEE80211_CONF_CHANGE_QOS */ + if (changed & IEEE80211_CONF_CHANGE_POWER) { + priv->output_power = conf->power_level; + sta_printk(KERN_DEBUG "[STA] TX power: %d\n", + priv->output_power); + WARN_ON(wsm_set_output_power(priv, priv->output_power * 10)); + } + + if ((changed & IEEE80211_CONF_CHANGE_CHANNEL) && + (priv->channel != conf->channel)) { + struct ieee80211_channel *ch = conf->channel; + struct wsm_switch_channel channel = { + .newChannelNumber = ch->hw_value, + }; + sta_printk(KERN_DEBUG "[STA] Freq %d (wsm ch: %d).\n", + ch->center_freq, ch->hw_value); + + ret = WARN_ON(__cw1200_flush(priv, false)); + if (!ret) { + ret = WARN_ON(wsm_switch_channel(priv, &channel)); + if (!ret) { + ret = wait_event_timeout( + priv->channel_switch_done, + !priv->channel_switch_in_progress, + 3 * HZ); + /* TODO: We should check also switch channel + * complete indication + */ + if (ret) { + priv->channel = ch; + ret = 0; + } else + ret = -ETIMEDOUT; + } else + wsm_unlock_tx(priv); + } + } + + if (changed & IEEE80211_CONF_CHANGE_PS) { + if (!(conf->flags & IEEE80211_CONF_PS)) + priv->powersave_mode.pmMode = WSM_PSM_ACTIVE; + else if (conf->dynamic_ps_timeout <= 0) + priv->powersave_mode.pmMode = WSM_PSM_PS; + else + priv->powersave_mode.pmMode = WSM_PSM_FAST_PS; + + /* Firmware requires that value for this 1-byte field must + * be specified in units of 500us. Values above the 128ms + * threshold are not supported. */ + if (conf->dynamic_ps_timeout >= 0x80) + priv->powersave_mode.fastPsmIdlePeriod = 0xFF; + else + priv->powersave_mode.fastPsmIdlePeriod = + conf->dynamic_ps_timeout << 1; + + if (priv->join_status == CW1200_JOIN_STATUS_STA && + priv->bss_params.aid) + cw1200_set_pm(priv, &priv->powersave_mode); + } + +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + if (changed & IEEE80211_CONF_CHANGE_P2P_PS) { + struct wsm_p2p_ps_modeinfo *modeinfo; + modeinfo = &priv->p2p_ps_modeinfo; + sta_printk(KERN_DEBUG "[STA] IEEE80211_CONF_CHANGE_P2P_PS\n"); + sta_printk(KERN_DEBUG "[STA] Legacy PS: %d for AID %d " + "in %d mode.\n", conf->p2p_ps.legacy_ps, + priv->bss_params.aid, priv->join_status); + + if (conf->p2p_ps.legacy_ps >= 0) { + if (conf->p2p_ps.legacy_ps > 0) + priv->powersave_mode.pmMode = WSM_PSM_PS; + else + priv->powersave_mode.pmMode = WSM_PSM_ACTIVE; + + if (priv->join_status == CW1200_JOIN_STATUS_STA) + cw1200_set_pm(priv, &priv->powersave_mode); + } + + sta_printk(KERN_DEBUG "[STA] CTWindow: %d\n", + conf->p2p_ps.ctwindow); + if (conf->p2p_ps.ctwindow >= 128) + modeinfo->oppPsCTWindow = 127; + else if (conf->p2p_ps.ctwindow >= 0) + modeinfo->oppPsCTWindow = conf->p2p_ps.ctwindow; + + sta_printk(KERN_DEBUG "[STA] Opportunistic: %d\n", + conf->p2p_ps.opp_ps); + switch (conf->p2p_ps.opp_ps) { + case 0: + modeinfo->oppPsCTWindow &= ~(BIT(7)); + break; + case 1: + modeinfo->oppPsCTWindow |= BIT(7); + break; + default: + break; + } + + sta_printk(KERN_DEBUG "[STA] NOA: %d, %d, %d, %d\n", + conf->p2p_ps.count, + conf->p2p_ps.start, + conf->p2p_ps.duration, + conf->p2p_ps.interval); + /* Notice of Absence */ + modeinfo->count = conf->p2p_ps.count; + + if (conf->p2p_ps.count) { + /* In case P2P_GO we need some extra time to be sure + * we will update beacon/probe_resp IEs correctly */ +#define NOA_DELAY_START_MS 300 + if (priv->join_status == CW1200_JOIN_STATUS_AP) + modeinfo->startTime = + __cpu_to_le32(conf->p2p_ps.start + + NOA_DELAY_START_MS); + else + modeinfo->startTime = + __cpu_to_le32(conf->p2p_ps.start); + modeinfo->duration = + __cpu_to_le32(conf->p2p_ps.duration); + modeinfo->interval = + __cpu_to_le32(conf->p2p_ps.interval); + modeinfo->dtimCount = 1; + modeinfo->reserved = 0; + } else { + modeinfo->dtimCount = 0; + modeinfo->startTime = 0; + modeinfo->reserved = 0; + modeinfo->duration = 0; + modeinfo->interval = 0; + } + +#if defined(CONFIG_CW1200_STA_DEBUG) + print_hex_dump_bytes("p2p_set_ps_modeinfo: ", + DUMP_PREFIX_NONE, + (u8 *)modeinfo, + sizeof(*modeinfo)); +#endif /* CONFIG_CW1200_STA_DEBUG */ + if (priv->join_status == CW1200_JOIN_STATUS_STA || + priv->join_status == CW1200_JOIN_STATUS_AP) { + WARN_ON(wsm_set_p2p_ps_modeinfo(priv, modeinfo)); + } + + /* Temporary solution while firmware don't support NOA change + * notification yet */ + cw1200_notify_noa(priv, 10); + } +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ + + if (changed & IEEE80211_CONF_CHANGE_MONITOR) { + /* TBD: It looks like it's transparent + * there's a monitor interface present -- use this + * to determine for example whether to calculate + * timestamps for packets or not, do not use instead + * of filter flags! */ + } + + if (changed & IEEE80211_CONF_CHANGE_IDLE) { + struct wsm_operational_mode mode = { + .power_mode = wsm_power_mode_quiescent, + .disableMoreFlagUsage = true, + }; + + wsm_lock_tx(priv); + /* Disable p2p-dev mode forced by TX request */ + if ((priv->join_status == CW1200_JOIN_STATUS_MONITOR) && + (conf->flags & IEEE80211_CONF_IDLE) && + !priv->listening) { + cw1200_disable_listening(priv); + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + } + WARN_ON(wsm_set_operational_mode(priv, &mode)); + wsm_unlock_tx(priv); + } + + if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) { + sta_printk(KERN_DEBUG "[STA] Retry limits: %d (long), " \ + "%d (short).\n", + conf->long_frame_max_tx_count, + conf->short_frame_max_tx_count); + spin_lock_bh(&priv->tx_policy_cache.lock); + priv->long_frame_max_tx_count = conf->long_frame_max_tx_count; + priv->short_frame_max_tx_count = + (conf->short_frame_max_tx_count < 0x0F) ? + conf->short_frame_max_tx_count : 0x0F; + priv->hw->max_rate_tries = priv->short_frame_max_tx_count; + spin_unlock_bh(&priv->tx_policy_cache.lock); + /* TBD: I think we don't need tx_policy_force_upload(). + * Outdated policies will leave cache in a normal way. */ + /* WARN_ON(tx_policy_force_upload(priv)); */ + } + mutex_unlock(&priv->conf_mutex); + up(&priv->scan.lock); + return ret; +} + +void cw1200_update_filtering(struct cw1200_common *priv) +{ + int ret; + bool bssid_filtering = !priv->rx_filter.bssid; + static struct wsm_beacon_filter_control bf_disabled = { + .enabled = 0, + .bcn_count = 1, + }; + + if (priv->join_status == CW1200_JOIN_STATUS_PASSIVE) + return; + else if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) + bssid_filtering = false; + + /* + * When acting as p2p client being connected to p2p GO, in order to + * receive frames from a different p2p device, turn off bssid filter. + * + * WARNING: FW dependency! + * This can only be used with FW WSM371 and its successors. + * In that FW version even with bssid filter turned off, + * device will block most of the unwanted frames. + */ + if (priv->vif->p2p) + bssid_filtering = false; + + ret = wsm_set_rx_filter(priv, &priv->rx_filter); + if (!ret) + ret = wsm_set_beacon_filter_table(priv, &priv->bf_table); + if (!ret) { + if (priv->disable_beacon_filter) + ret = wsm_beacon_filter_control(priv, + &bf_disabled); + else + ret = wsm_beacon_filter_control(priv, + &priv->bf_control); + } + if (!ret) + ret = wsm_set_bssid_filtering(priv, bssid_filtering); + if (!ret) + ret = wsm_set_multicast_filter(priv, &priv->multicast_filter); + if (ret) + wiphy_err(priv->hw->wiphy, + "%s: Update filtering failed: %d.\n", + __func__, ret); + return; +} + +void cw1200_update_filtering_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, + update_filtering_work); + + cw1200_update_filtering(priv); +} + +u64 cw1200_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +{ + static u8 broadcast_ipv6[ETH_ALEN] = { + 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 + }; + static u8 broadcast_ipv4[ETH_ALEN] = { + 0x01, 0x00, 0x5e, 0x00, 0x00, 0x01 + }; + struct cw1200_common *priv = hw->priv; + struct netdev_hw_addr *ha; + int count = 0; + + /* Disable multicast filtering */ + priv->has_multicast_subscription = false; + memset(&priv->multicast_filter, 0x00, sizeof(priv->multicast_filter)); + + if (netdev_hw_addr_list_count(mc_list) > WSM_MAX_GRP_ADDRTABLE_ENTRIES) + return 0; + + /* Enable if requested */ + netdev_hw_addr_list_for_each(ha, mc_list) { + sta_printk(KERN_DEBUG "[STA] multicast: %pM\n", ha->addr); + memcpy(&priv->multicast_filter.macAddress[count], + ha->addr, ETH_ALEN); + if (memcmp(ha->addr, broadcast_ipv4, ETH_ALEN) && + memcmp(ha->addr, broadcast_ipv6, ETH_ALEN)) + priv->has_multicast_subscription = true; + count++; + } + + if (count) { + priv->multicast_filter.enable = __cpu_to_le32(1); + priv->multicast_filter.numOfAddresses = __cpu_to_le32(count); + } + + return netdev_hw_addr_list_count(mc_list); +} + +void cw1200_configure_filter(struct ieee80211_hw *dev, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct cw1200_common *priv = dev->priv; + bool listening = !!(*total_flags & + (FIF_PROMISC_IN_BSS | + FIF_OTHER_BSS | + FIF_BCN_PRBRESP_PROMISC | + FIF_PROBE_REQ)); + + *total_flags &= FIF_PROMISC_IN_BSS | + FIF_OTHER_BSS | + FIF_FCSFAIL | + FIF_BCN_PRBRESP_PROMISC | + FIF_PROBE_REQ; + + down(&priv->scan.lock); + mutex_lock(&priv->conf_mutex); + + priv->rx_filter.promiscuous = (*total_flags & FIF_PROMISC_IN_BSS) + ? 1 : 0; + priv->rx_filter.bssid = (*total_flags & (FIF_OTHER_BSS | + FIF_PROBE_REQ)) ? 1 : 0; + priv->rx_filter.fcs = (*total_flags & FIF_FCSFAIL) ? 1 : 0; + priv->bf_control.bcn_count = (*total_flags & + (FIF_BCN_PRBRESP_PROMISC | + FIF_PROMISC_IN_BSS | + FIF_PROBE_REQ)) ? 1 : 0; + if (priv->listening ^ listening) { + priv->listening = listening; + wsm_lock_tx(priv); + cw1200_update_listening(priv, listening); + wsm_unlock_tx(priv); + } + cw1200_update_filtering(priv); + mutex_unlock(&priv->conf_mutex); + up(&priv->scan.lock); +} + +int cw1200_conf_tx(struct ieee80211_hw *dev, struct ieee80211_vif *vif, + u16 queue, const struct ieee80211_tx_queue_params *params) +{ + struct cw1200_common *priv = dev->priv; + int ret = 0; + /* To prevent re-applying PM request OID again and again*/ + bool old_uapsdFlags; + + mutex_lock(&priv->conf_mutex); + + if (queue < dev->queues) { + old_uapsdFlags = priv->uapsd_info.uapsdFlags; + + WSM_TX_QUEUE_SET(&priv->tx_queue_params, queue, 0, 0, 0); + ret = wsm_set_tx_queue_params(priv, + &priv->tx_queue_params.params[queue], queue); + if (ret) { + ret = -EINVAL; + goto out; + } + + WSM_EDCA_SET(&priv->edca, queue, params->aifs, + params->cw_min, params->cw_max, params->txop, 0xc8, + params->uapsd); + ret = wsm_set_edca_params(priv, &priv->edca); + if (ret) { + ret = -EINVAL; + goto out; + } + + if (priv->mode == NL80211_IFTYPE_STATION) { + ret = cw1200_set_uapsd_param(priv, &priv->edca); + if (!ret && priv->setbssparams_done && + (priv->join_status == CW1200_JOIN_STATUS_STA) && + (old_uapsdFlags != priv->uapsd_info.uapsdFlags)) + cw1200_set_pm(priv, &priv->powersave_mode); + } + } else + ret = -EINVAL; + +out: + mutex_unlock(&priv->conf_mutex); + return ret; +} + +int cw1200_get_stats(struct ieee80211_hw *dev, + struct ieee80211_low_level_stats *stats) +{ + struct cw1200_common *priv = dev->priv; + + memcpy(stats, &priv->stats, sizeof(*stats)); + return 0; +} + +/* +int cw1200_get_tx_stats(struct ieee80211_hw *dev, + struct ieee80211_tx_queue_stats *stats) +{ + int i; + struct cw1200_common *priv = dev->priv; + + for (i = 0; i < dev->queues; ++i) + cw1200_queue_get_stats(&priv->tx_queue[i], &stats[i]); + + return 0; +} +*/ + +int cw1200_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg) +{ + struct wsm_set_pm pm = *arg; + + if (priv->uapsd_info.uapsdFlags != 0) + pm.pmMode &= ~WSM_PSM_FAST_PS_FLAG; + + if (memcmp(&pm, &priv->firmware_ps_mode, + sizeof(struct wsm_set_pm))) { + priv->firmware_ps_mode = pm; + return wsm_set_pm(priv, &pm); + } else { + return 0; + } +} + +int cw1200_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + int ret = -EOPNOTSUPP; + struct cw1200_common *priv = dev->priv; + + mutex_lock(&priv->conf_mutex); + + if (cmd == SET_KEY) { + u8 *peer_addr = NULL; + int pairwise = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? + 1 : 0; + int idx = cw1200_alloc_key(priv); + struct wsm_add_key *wsm_key = &priv->keys[idx]; + + if (idx < 0) { + ret = -EINVAL; + goto finally; + } + + BUG_ON(pairwise && !sta); + if (sta) + peer_addr = sta->addr; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (key->keylen > 16) { + cw1200_free_key(priv, idx); + ret = -EINVAL; + goto finally; + } + + if (pairwise) { + wsm_key->type = WSM_KEY_TYPE_WEP_PAIRWISE; + memcpy(wsm_key->wepPairwiseKey.peerAddress, + peer_addr, ETH_ALEN); + memcpy(wsm_key->wepPairwiseKey.keyData, + &key->key[0], key->keylen); + wsm_key->wepPairwiseKey.keyLength = key->keylen; + } else { + wsm_key->type = WSM_KEY_TYPE_WEP_DEFAULT; + memcpy(wsm_key->wepGroupKey.keyData, + &key->key[0], key->keylen); + wsm_key->wepGroupKey.keyLength = key->keylen; + wsm_key->wepGroupKey.keyId = key->keyidx; + } + break; + case WLAN_CIPHER_SUITE_TKIP: + if (pairwise) { + wsm_key->type = WSM_KEY_TYPE_TKIP_PAIRWISE; + memcpy(wsm_key->tkipPairwiseKey.peerAddress, + peer_addr, ETH_ALEN); + memcpy(wsm_key->tkipPairwiseKey.tkipKeyData, + &key->key[0], 16); + memcpy(wsm_key->tkipPairwiseKey.txMicKey, + &key->key[16], 8); + memcpy(wsm_key->tkipPairwiseKey.rxMicKey, + &key->key[24], 8); + } else { + size_t mic_offset = + (priv->mode == NL80211_IFTYPE_AP) ? + 16 : 24; + wsm_key->type = WSM_KEY_TYPE_TKIP_GROUP; + memcpy(wsm_key->tkipGroupKey.tkipKeyData, + &key->key[0], 16); + memcpy(wsm_key->tkipGroupKey.rxMicKey, + &key->key[mic_offset], 8); + + /* TODO: Where can I find TKIP SEQ? */ + memset(wsm_key->tkipGroupKey.rxSeqCounter, + 0, 8); + wsm_key->tkipGroupKey.keyId = key->keyidx; + + print_hex_dump_bytes("TKIP: ", DUMP_PREFIX_NONE, + key->key, key->keylen); + } + break; + case WLAN_CIPHER_SUITE_CCMP: + if (pairwise) { + wsm_key->type = WSM_KEY_TYPE_AES_PAIRWISE; + memcpy(wsm_key->aesPairwiseKey.peerAddress, + peer_addr, ETH_ALEN); + memcpy(wsm_key->aesPairwiseKey.aesKeyData, + &key->key[0], 16); + } else { + wsm_key->type = WSM_KEY_TYPE_AES_GROUP; + memcpy(wsm_key->aesGroupKey.aesKeyData, + &key->key[0], 16); + /* TODO: Where can I find AES SEQ? */ + memset(wsm_key->aesGroupKey.rxSeqCounter, + 0, 8); + wsm_key->aesGroupKey.keyId = key->keyidx; + } + break; +#ifdef CONFIG_CW1200_WAPI_SUPPORT + case WLAN_CIPHER_SUITE_SMS4: + if (pairwise) { + wsm_key->type = WSM_KEY_TYPE_WAPI_PAIRWISE; + memcpy(wsm_key->wapiPairwiseKey.peerAddress, + peer_addr, ETH_ALEN); + memcpy(wsm_key->wapiPairwiseKey.wapiKeyData, + &key->key[0], 16); + memcpy(wsm_key->wapiPairwiseKey.micKeyData, + &key->key[16], 16); + wsm_key->wapiPairwiseKey.keyId = key->keyidx; + } else { + wsm_key->type = WSM_KEY_TYPE_WAPI_GROUP; + memcpy(wsm_key->wapiGroupKey.wapiKeyData, + &key->key[0], 16); + memcpy(wsm_key->wapiGroupKey.micKeyData, + &key->key[16], 16); + wsm_key->wapiGroupKey.keyId = key->keyidx; + } + break; +#endif /* CONFIG_CW1200_WAPI_SUPPORT */ + default: + WARN_ON(1); + cw1200_free_key(priv, idx); + ret = -EOPNOTSUPP; + goto finally; + } + ret = WARN_ON(wsm_add_key(priv, wsm_key)); + if (!ret) + key->hw_key_idx = idx; + else + cw1200_free_key(priv, idx); + } else if (cmd == DISABLE_KEY) { + struct wsm_remove_key wsm_key = { + .entryIndex = key->hw_key_idx, + }; + + if (wsm_key.entryIndex > WSM_KEY_MAX_INDEX) { + ret = -EINVAL; + goto finally; + } + + cw1200_free_key(priv, wsm_key.entryIndex); + ret = wsm_remove_key(priv, &wsm_key); + } else { + BUG_ON("Unsupported command"); + } + +finally: + mutex_unlock(&priv->conf_mutex); + return ret; +} + +void cw1200_wep_key_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, wep_key_work); + u8 queueId = cw1200_queue_get_queue_id(priv->pending_frame_id); + struct cw1200_queue *queue = &priv->tx_queue[queueId]; + __le32 wep_default_key_id = __cpu_to_le32( + priv->wep_default_key_id); + + BUG_ON(queueId >= 4); + + sta_printk(KERN_DEBUG "[STA] Setting default WEP key: %d\n", + priv->wep_default_key_id); + wsm_flush_tx(priv); + WARN_ON(wsm_write_mib(priv, WSM_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID, + &wep_default_key_id, sizeof(wep_default_key_id))); + cw1200_queue_requeue(queue, priv->pending_frame_id); + wsm_unlock_tx(priv); +} + +int cw1200_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + int ret; + __le32 val32; + + if (value != (u32) -1) + val32 = __cpu_to_le32(value); + else + val32 = 0; /* disabled */ + + /* mutex_lock(&priv->conf_mutex); */ + ret = WARN_ON(wsm_write_mib(hw->priv, WSM_MIB_ID_DOT11_RTS_THRESHOLD, + &val32, sizeof(val32))); + /* mutex_unlock(&priv->conf_mutex); */ + return ret; +} + +int __cw1200_flush(struct cw1200_common *priv, bool drop) +{ + int i, ret; + + for (;;) { + /* TODO: correct flush handling is required when dev_stop. + * Temporary workaround: 2s + */ + if (drop) { + for (i = 0; i < 4; ++i) + cw1200_queue_clear(&priv->tx_queue[i]); + } else { + ret = wait_event_timeout( + priv->tx_queue_stats.wait_link_id_empty, + cw1200_queue_stats_is_empty( + &priv->tx_queue_stats, -1), + 2 * HZ); + } + + if (!drop && unlikely(ret <= 0)) { + ret = -ETIMEDOUT; + break; + } else { + ret = 0; + } + + wsm_lock_tx(priv); + if (unlikely(!cw1200_queue_stats_is_empty( + &priv->tx_queue_stats, -1))) { + /* Highly unlekely: WSM requeued frames. */ + wsm_unlock_tx(priv); + continue; + } + break; + } + return ret; +} + +void cw1200_flush(struct ieee80211_hw *hw, bool drop) +{ + struct cw1200_common *priv = hw->priv; + + switch (priv->mode) { + case NL80211_IFTYPE_MONITOR: + drop = true; + break; + case NL80211_IFTYPE_AP: + if (!priv->enable_beacon) + drop = true; + break; + } + + if (!WARN_ON(__cw1200_flush(priv, drop))) + wsm_unlock_tx(priv); + + return; +} + +/* ******************************************************************** */ +/* WSM callbacks */ + +void cw1200_channel_switch_cb(struct cw1200_common *priv) +{ + wsm_unlock_tx(priv); +} + +void cw1200_free_event_queue(struct cw1200_common *priv) +{ + LIST_HEAD(list); + + spin_lock(&priv->event_queue_lock); + list_splice_init(&priv->event_queue, &list); + spin_unlock(&priv->event_queue_lock); + + __cw1200_free_event_queue(&list); +} + +void cw1200_event_handler(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, event_handler); + struct cw1200_wsm_event *event; + LIST_HEAD(list); + + spin_lock(&priv->event_queue_lock); + list_splice_init(&priv->event_queue, &list); + spin_unlock(&priv->event_queue_lock); + + list_for_each_entry(event, &list, link) { + switch (event->evt.eventId) { + case WSM_EVENT_ERROR: + /* I even don't know what is it about.. */ + STUB(); + break; + case WSM_EVENT_BSS_LOST: + { + spin_lock(&priv->bss_loss_lock); + if (priv->bss_loss_status > CW1200_BSS_LOSS_NONE) { + spin_unlock(&priv->bss_loss_lock); + break; + } + priv->bss_loss_status = CW1200_BSS_LOSS_CHECKING; + spin_unlock(&priv->bss_loss_lock); + + sta_printk(KERN_DEBUG "[CQM] BSS lost.\n"); + cancel_delayed_work_sync(&priv->bss_loss_work); + cancel_delayed_work_sync(&priv->connection_loss_work); + if (!down_trylock(&priv->scan.lock)) { + up(&priv->scan.lock); + priv->delayed_link_loss = 0; + queue_delayed_work(priv->workqueue, + &priv->bss_loss_work, 0); + } else { + /* Scan is in progress. Delay reporting. */ + /* Scan complete will trigger bss_loss_work */ + priv->delayed_link_loss = 1; + /* Also we're starting watchdog. */ + queue_delayed_work(priv->workqueue, + &priv->bss_loss_work, 10 * HZ); + } + break; + } + case WSM_EVENT_BSS_REGAINED: + { + sta_printk(KERN_DEBUG "[CQM] BSS regained.\n"); + priv->delayed_link_loss = 0; + spin_lock(&priv->bss_loss_lock); + priv->bss_loss_status = CW1200_BSS_LOSS_NONE; + spin_unlock(&priv->bss_loss_lock); + cancel_delayed_work_sync(&priv->bss_loss_work); + cancel_delayed_work_sync(&priv->connection_loss_work); + break; + } + case WSM_EVENT_RADAR_DETECTED: + STUB(); + break; + case WSM_EVENT_RCPI_RSSI: + { + /* RSSI: signed Q8.0, RCPI: unsigned Q7.1 + * RSSI = RCPI / 2 - 110 */ + int rcpiRssi = (int)(event->evt.eventData & 0xFF); + int cqm_evt; + if (priv->cqm_use_rssi) + rcpiRssi = (s8)rcpiRssi; + else + rcpiRssi = rcpiRssi / 2 - 110; + + cqm_evt = (rcpiRssi <= priv->cqm_rssi_thold) ? + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW : + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH; + sta_printk(KERN_DEBUG "[CQM] RSSI event: %d", rcpiRssi); + ieee80211_cqm_rssi_notify(priv->vif, cqm_evt, + GFP_KERNEL); + break; + } + case WSM_EVENT_BT_INACTIVE: + STUB(); + break; + case WSM_EVENT_BT_ACTIVE: + STUB(); + break; + } + } + __cw1200_free_event_queue(&list); +} + +void cw1200_bss_loss_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, bss_loss_work.work); + int timeout; /* in beacons */ + struct sk_buff *skb; + + timeout = priv->cqm_link_loss_count - + priv->cqm_beacon_loss_count; + + /* Skip the confimration procedure in P2P case */ + if (priv->vif->p2p) + goto report; + + spin_lock(&priv->bss_loss_lock); + if (priv->bss_loss_status == CW1200_BSS_LOSS_CHECKING) { + spin_unlock(&priv->bss_loss_lock); + skb = ieee80211_nullfunc_get(priv->hw, priv->vif); + if (!(WARN_ON(!skb))) { + cw1200_tx(priv->hw, skb); + /* Start watchdog -- if nullfunc TX doesn't fail + * in 1 sec, forward event to upper layers */ + queue_delayed_work(priv->workqueue, + &priv->bss_loss_work, 1 * HZ); + } + return; + } else if (priv->bss_loss_status == CW1200_BSS_LOSS_CONFIRMING) { + priv->bss_loss_status = CW1200_BSS_LOSS_NONE; + spin_unlock(&priv->bss_loss_lock); + return; + } + spin_unlock(&priv->bss_loss_lock); + +report: + if (priv->cqm_beacon_loss_count) { + sta_printk(KERN_DEBUG "[CQM] Beacon loss.\n"); + if (timeout <= 0) + timeout = 0; +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + ieee80211_cqm_beacon_miss_notify(priv->vif, GFP_KERNEL); +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ + } else { + timeout = 0; + } + + cancel_delayed_work_sync(&priv->connection_loss_work); + queue_delayed_work(priv->workqueue, + &priv->connection_loss_work, + timeout * HZ / 10); + + spin_lock(&priv->bss_loss_lock); + priv->bss_loss_status = CW1200_BSS_LOSS_NONE; + spin_unlock(&priv->bss_loss_lock); +} + +void cw1200_connection_loss_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, + connection_loss_work.work); + sta_printk(KERN_DEBUG "[CQM] Reporting connection loss.\n"); + ieee80211_connection_loss(priv->vif); +} + +void cw1200_tx_failure_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, tx_failure_work); + sta_printk(KERN_DEBUG "[CQM] Reporting TX failure.\n"); +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + ieee80211_cqm_tx_fail_notify(priv->vif, GFP_KERNEL); +#else /* CONFIG_CW1200_USE_STE_EXTENSIONS */ + (void)priv; +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ +} + +/* ******************************************************************** */ +/* Internal API */ + + + +/* +* This function is called to Parse the SDD file + *to extract listen_interval and PTA related information +*/ +static int cw1200_parse_SDD_file(struct cw1200_common *priv) +{ + u8 *sdd_data = (u8 *)priv->sdd->data; + struct cw1200_sdd { + u8 id ; + u8 length ; + u8 data[] ; + } *pElement; + int parsedLength = 0; + #define SDD_PTA_CFG_ELT_ID 0xEB + #define FIELD_OFFSET(type, field) ((u8 *)&((type *)0)->field - (u8 *)0) + + priv->is_BT_Present = false; + + pElement = (struct cw1200_sdd *)sdd_data; + + pElement = (struct cw1200_sdd *)((u8 *)pElement + + FIELD_OFFSET(struct cw1200_sdd, data) + pElement->length); + + parsedLength += (FIELD_OFFSET(struct cw1200_sdd, data) + + pElement->length); + + while (parsedLength <= priv->sdd->size) { + switch (pElement->id) { + case SDD_PTA_CFG_ELT_ID: + { + priv->conf_listen_interval = + (*((u16 *)pElement->data+1) >> 7) & 0x1F; + priv->is_BT_Present = true; + sta_printk(KERN_DEBUG "PTA element found.\n"); + sta_printk(KERN_DEBUG "Listen Interval %d\n", + priv->conf_listen_interval); + } + break; + + default: + break; + } + + pElement = (struct cw1200_sdd *) + ((u8 *)pElement + FIELD_OFFSET(struct cw1200_sdd, data) + + pElement->length); + parsedLength += + (FIELD_OFFSET(struct cw1200_sdd, data) + pElement->length); + } + + if (priv->is_BT_Present == false) { + sta_printk(KERN_DEBUG "PTA element NOT found.\n"); + priv->conf_listen_interval = 0; + } + return 0; + + #undef SDD_PTA_CFG_ELT_ID + #undef FIELD_OFFSET +} + + +int cw1200_setup_mac(struct cw1200_common *priv) +{ + int ret = 0; + + /* NOTE: There is a bug in FW: it reports signal + * as RSSI if RSSI subscription is enabled. + * It's not enough to set WSM_RCPI_RSSI_USE_RSSI. */ + /* NOTE2: RSSI based reports have been switched to RCPI, since + * FW has a bug and RSSI reported values are not stable, + * what can leads to signal level oscilations in user-end applications */ + struct wsm_rcpi_rssi_threshold threshold = { + .rssiRcpiMode = WSM_RCPI_RSSI_THRESHOLD_ENABLE | + WSM_RCPI_RSSI_DONT_USE_UPPER | + WSM_RCPI_RSSI_DONT_USE_LOWER, + .rollingAverageCount = 16, + }; + + /* Remember the decission here to make sure, we will handle + * the RCPI/RSSI value correctly on WSM_EVENT_RCPI_RSS */ + if (threshold.rssiRcpiMode & WSM_RCPI_RSSI_USE_RSSI) + priv->cqm_use_rssi = true; + + if (!priv->sdd) { + const char *sdd_path = NULL; + struct wsm_configuration cfg = { + .dot11StationId = &priv->mac_addr[0], + }; + + switch (priv->hw_revision) { + case CW1200_HW_REV_CUT10: + sdd_path = SDD_FILE_10; + break; + case CW1200_HW_REV_CUT11: + sdd_path = SDD_FILE_11; + break; + case CW1200_HW_REV_CUT20: + sdd_path = SDD_FILE_20; + break; + case CW1200_HW_REV_CUT22: + sdd_path = SDD_FILE_22; + break; + default: + BUG_ON(1); + } + + ret = request_firmware(&priv->sdd, + sdd_path, priv->pdev); + + if (unlikely(ret)) { + cw1200_dbg(CW1200_DBG_ERROR, + "%s: can't load sdd file %s.\n", + __func__, sdd_path); + return ret; + } + + cfg.dpdData = priv->sdd->data; + cfg.dpdData_size = priv->sdd->size; + ret = WARN_ON(wsm_configuration(priv, &cfg)); + /* Parse SDD file for PTA element */ + cw1200_parse_SDD_file(priv); + } + if (ret) + return ret; + + /* Configure RSSI/SCPI reporting as RSSI. */ + WARN_ON(wsm_set_rcpi_rssi_threshold(priv, &threshold)); + + /* TODO: */ + switch (priv->mode) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_AP: + break; + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + /* TODO: Not verified yet. */ + STUB(); + break; + } + + return 0; +} + +void cw1200_offchannel_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, offchannel_work); + u8 queueId = cw1200_queue_get_queue_id(priv->pending_frame_id); + struct cw1200_queue *queue = &priv->tx_queue[queueId]; + + BUG_ON(queueId >= 4); + BUG_ON(!priv->channel); + + mutex_lock(&priv->conf_mutex); + if (likely(!priv->join_status)) { + wsm_flush_tx(priv); + cw1200_update_listening(priv, true); + cw1200_update_filtering(priv); + } + if (unlikely(!priv->join_status)) + cw1200_queue_remove(queue, priv->pending_frame_id); + else + cw1200_queue_requeue(queue, priv->pending_frame_id); + mutex_unlock(&priv->conf_mutex); + wsm_unlock_tx(priv); +} + +void cw1200_join_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, join_work); + u8 queueId = cw1200_queue_get_queue_id(priv->pending_frame_id); + struct cw1200_queue *queue = &priv->tx_queue[queueId]; + const struct cw1200_txpriv *txpriv = NULL; + struct sk_buff *skb = NULL; + const struct wsm_tx *wsm; + const struct ieee80211_hdr *frame; + const u8 *bssid; + struct cfg80211_bss *bss; + const u8 *ssidie; + const u8 *dtimie; + const struct ieee80211_tim_ie *tim = NULL; + struct wsm_protected_mgmt_policy mgmt_policy; + + BUG_ON(queueId >= 4); + if (cw1200_queue_get_skb(queue, priv->pending_frame_id, + &skb, &txpriv)) { + wsm_unlock_tx(priv); + return; + } + wsm = (struct wsm_tx *)&skb->data[0]; + frame = (struct ieee80211_hdr *)&skb->data[txpriv->offset]; + bssid = &frame->addr1[0]; /* AP SSID in a 802.11 frame */ + + BUG_ON(!wsm); + BUG_ON(!priv->channel); + + if (unlikely(priv->join_status)) { + wsm_lock_tx(priv); + cw1200_unjoin_work(&priv->unjoin_work); + } + + cancel_delayed_work_sync(&priv->join_timeout); + + bss = cfg80211_get_bss(priv->hw->wiphy, priv->channel, + bssid, NULL, 0, 0, 0); + if (!bss) { + cw1200_queue_remove(queue, priv->pending_frame_id); + wsm_unlock_tx(priv); + return; + } + ssidie = cfg80211_find_ie(WLAN_EID_SSID, + bss->information_elements, + bss->len_information_elements); + dtimie = cfg80211_find_ie(WLAN_EID_TIM, + bss->information_elements, + bss->len_information_elements); + if (dtimie) + tim = (struct ieee80211_tim_ie *)&dtimie[2]; + + mutex_lock(&priv->conf_mutex); + { + struct wsm_join join = { + .mode = (bss->capability & WLAN_CAPABILITY_IBSS) ? + WSM_JOIN_MODE_IBSS : WSM_JOIN_MODE_BSS, + .preambleType = WSM_JOIN_PREAMBLE_SHORT, + .probeForJoin = 1, + /* dtimPeriod will be updated after association */ + .dtimPeriod = 1, + .beaconInterval = bss->beacon_interval, + /* basicRateSet will be updated after association */ + .basicRateSet = 7, + }; + + /* BT Coex related changes */ + if (priv->is_BT_Present) { + if (((priv->conf_listen_interval * 100) % + bss->beacon_interval) == 0) + priv->listen_interval = + ((priv->conf_listen_interval * 100) / + bss->beacon_interval); + else + priv->listen_interval = + ((priv->conf_listen_interval * 100) / + bss->beacon_interval + 1); + } + + if (tim && tim->dtim_period > 1) { + join.dtimPeriod = tim->dtim_period; + priv->join_dtim_period = tim->dtim_period; + } + priv->beacon_int = bss->beacon_interval; + sta_printk(KERN_DEBUG "[STA] Join DTIM: %d, interval: %d\n", + join.dtimPeriod, priv->beacon_int); + + join.channelNumber = priv->channel->hw_value; + join.band = (priv->channel->band == IEEE80211_BAND_5GHZ) ? + WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G; + + memcpy(&join.bssid[0], bssid, sizeof(join.bssid)); + memcpy(&priv->join_bssid[0], bssid, sizeof(priv->join_bssid)); + + if (ssidie) { + join.ssidLength = ssidie[1]; + if (WARN_ON(join.ssidLength > sizeof(join.ssid))) + join.ssidLength = sizeof(join.ssid); + memcpy(&join.ssid[0], &ssidie[2], join.ssidLength); + } + + if (priv->vif->p2p) { + join.flags |= WSM_JOIN_FLAGS_P2P_GO; + join.basicRateSet = + cw1200_rate_mask_to_wsm(priv, 0xFF0); + } + + wsm_flush_tx(priv); + + /* Queue unjoin if not associated in 3 sec. */ + queue_delayed_work(priv->workqueue, + &priv->join_timeout, 3 * HZ); + /*Stay Awake for Join Timeout*/ + cw1200_pm_stay_awake(&priv->pm_state, 3 * HZ); + + cw1200_update_listening(priv, false); + /* BlockACK policy will be updated when assoc is done */ + WARN_ON(wsm_set_block_ack_policy(priv, + 0, priv->ba_tid_mask)); + + spin_lock_bh(&priv->ba_lock); + priv->ba_ena = false; + priv->ba_cnt = 0; + priv->ba_acc = 0; + priv->ba_hist = 0; + spin_unlock_bh(&priv->ba_lock); + + mgmt_policy.protectedMgmtEnable = 0; + mgmt_policy.unprotectedMgmtFramesAllowed = 1; + mgmt_policy.encryptionForAuthFrame = 1; + wsm_set_protected_mgmt_policy(priv, &mgmt_policy); + + if (wsm_join(priv, &join)) { + memset(&priv->join_bssid[0], + 0, sizeof(priv->join_bssid)); + cw1200_queue_remove(queue, priv->pending_frame_id); + cancel_delayed_work_sync(&priv->join_timeout); + cw1200_update_listening(priv, priv->listening); + } else { + /* Upload keys */ + WARN_ON(cw1200_upload_keys(priv)); + cw1200_queue_requeue(queue, priv->pending_frame_id); + priv->join_status = CW1200_JOIN_STATUS_STA; + + /* Due to beacon filtering it is possible that the + * AP's beacon is not known for the mac80211 stack. + * Disable filtering temporary to make sure the stack + * receives at least one */ + priv->disable_beacon_filter = true; + + } + cw1200_update_filtering(priv); + } + mutex_unlock(&priv->conf_mutex); + cfg80211_put_bss(bss); + wsm_unlock_tx(priv); +} + +void cw1200_join_timeout(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, join_timeout.work); + sta_printk(KERN_DEBUG "[WSM] Issue unjoin command (TMO).\n"); + wsm_lock_tx(priv); + cw1200_unjoin_work(&priv->unjoin_work); +} + +void cw1200_unjoin_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, unjoin_work); + + struct wsm_reset reset = { + .reset_statistics = true, + }; + + del_timer_sync(&priv->ba_timer); + mutex_lock(&priv->conf_mutex); + if (unlikely(atomic_read(&priv->scan.in_progress))) { + if (priv->delayed_unjoin) { + wiphy_dbg(priv->hw->wiphy, + "%s: Delayed unjoin " + "is already scheduled.\n", + __func__); + wsm_unlock_tx(priv); + } else { + priv->delayed_unjoin = true; + } + mutex_unlock(&priv->conf_mutex); + return; + } + + if (priv->join_status && + priv->join_status > CW1200_JOIN_STATUS_STA) { + wiphy_err(priv->hw->wiphy, + "%s: Unexpected: join status: %d\n", + __func__, priv->join_status); + BUG_ON(1); + } + if (priv->join_status) { + cancel_work_sync(&priv->update_filtering_work); + memset(&priv->join_bssid[0], 0, sizeof(priv->join_bssid)); + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + + /* Unjoin is a reset. */ + wsm_flush_tx(priv); + WARN_ON(wsm_keep_alive_period(priv, 0)); + WARN_ON(wsm_reset(priv, &reset)); + priv->join_dtim_period = 0; + WARN_ON(cw1200_setup_mac(priv)); + cw1200_free_event_queue(priv); + cancel_work_sync(&priv->event_handler); + cancel_delayed_work_sync(&priv->connection_loss_work); + cw1200_update_listening(priv, priv->listening); + WARN_ON(wsm_set_block_ack_policy(priv, + 0, priv->ba_tid_mask)); + priv->disable_beacon_filter = false; + cw1200_update_filtering(priv); + priv->setbssparams_done = false; + memset(&priv->association_mode, 0, + sizeof(priv->association_mode)); + memset(&priv->bss_params, 0, sizeof(priv->bss_params)); + memset(&priv->firmware_ps_mode, 0, + sizeof(priv->firmware_ps_mode)); + sta_printk(KERN_DEBUG "[STA] Unjoin.\n"); + } + mutex_unlock(&priv->conf_mutex); + wsm_unlock_tx(priv); +} + +int cw1200_enable_listening(struct cw1200_common *priv) +{ + struct wsm_start start = { + .mode = WSM_START_MODE_P2P_DEV, + .band = (priv->channel->band == IEEE80211_BAND_5GHZ) ? + WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G, + .channelNumber = priv->channel->hw_value, + .beaconInterval = 100, + .DTIMPeriod = 1, + .probeDelay = 0, + .basicRateSet = 0x0F, + }; + return wsm_start(priv, &start); +} + +int cw1200_disable_listening(struct cw1200_common *priv) +{ + int ret; + struct wsm_reset reset = { + .reset_statistics = true, + }; + ret = wsm_reset(priv, &reset); + return ret; +} + +void cw1200_update_listening(struct cw1200_common *priv, bool enabled) +{ + if (enabled) { + switch (priv->join_status) { + case CW1200_JOIN_STATUS_PASSIVE: + if (!WARN_ON(cw1200_enable_listening(priv))) + priv->join_status = CW1200_JOIN_STATUS_MONITOR; + break; + default: + break; + } + } else { + switch (priv->join_status) { + case CW1200_JOIN_STATUS_MONITOR: + if (!WARN_ON(cw1200_disable_listening(priv))) + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + default: + break; + } + } +} + +int cw1200_set_uapsd_param(struct cw1200_common *priv, + const struct wsm_edca_params *arg) +{ + int ret; + u16 uapsdFlags = 0; + + /* Here's the mapping AC [queue, bit] + VO [0,3], VI [1, 2], BE [2, 1], BK [3, 0]*/ + + if (arg->params[0].uapsdEnable) + uapsdFlags |= 1 << 3; + + if (arg->params[1].uapsdEnable) + uapsdFlags |= 1 << 2; + + if (arg->params[2].uapsdEnable) + uapsdFlags |= 1 << 1; + + if (arg->params[3].uapsdEnable) + uapsdFlags |= 1; + + /* Currently pseudo U-APSD operation is not supported, so setting + * MinAutoTriggerInterval, MaxAutoTriggerInterval and + * AutoTriggerStep to 0 */ + + priv->uapsd_info.uapsdFlags = cpu_to_le16(uapsdFlags); + priv->uapsd_info.minAutoTriggerInterval = 0; + priv->uapsd_info.maxAutoTriggerInterval = 0; + priv->uapsd_info.autoTriggerStep = 0; + + ret = wsm_set_uapsd_info(priv, &priv->uapsd_info); + return ret; +} + +void cw1200_ba_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, ba_work); + u8 tx_ba_tid_mask; + + if (priv->join_status != CW1200_JOIN_STATUS_STA) + return; + if (!priv->setbssparams_done) + return; + + spin_lock_bh(&priv->ba_lock); + tx_ba_tid_mask = priv->ba_ena ? priv->ba_tid_mask : 0; + spin_unlock_bh(&priv->ba_lock); + + WARN_ON(wsm_set_block_ack_policy(priv, + tx_ba_tid_mask, priv->ba_tid_mask)); +} + +void cw1200_ba_timer(unsigned long arg) +{ + bool ba_ena; + struct cw1200_common *priv = + (struct cw1200_common *)arg; + + spin_lock_bh(&priv->ba_lock); + cw1200_debug_ba(priv, priv->ba_cnt, priv->ba_acc); + + if (atomic_read(&priv->scan.in_progress)) + goto skip_statistic_update; + + ba_ena = (priv->ba_cnt >= CW1200_BLOCK_ACK_CNT && + priv->ba_acc / priv->ba_cnt >= CW1200_BLOCK_ACK_THLD); + priv->ba_cnt = 0; + priv->ba_acc = 0; + + if (ba_ena != priv->ba_ena) { + if (ba_ena || ++priv->ba_hist >= CW1200_BLOCK_ACK_HIST) { + priv->ba_ena = ba_ena; + priv->ba_hist = 0; + sta_printk(KERN_DEBUG "[STA] %s block ACK:\n", + ba_ena ? "enable" : "disable"); + queue_work(priv->workqueue, &priv->ba_work); + } + } else if (priv->ba_hist) + --priv->ba_hist; + +skip_statistic_update: + spin_unlock_bh(&priv->ba_lock); +} diff --git a/drivers/staging/cw1200/sta.h b/drivers/staging/cw1200/sta.h new file mode 100644 index 00000000000..4e4833afcf7 --- /dev/null +++ b/drivers/staging/cw1200/sta.h @@ -0,0 +1,87 @@ +/* + * Mac80211 STA interface for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef STA_H_INCLUDED +#define STA_H_INCLUDED + +/* ******************************************************************** */ +/* mac80211 API */ + +int cw1200_start(struct ieee80211_hw *dev); +void cw1200_stop(struct ieee80211_hw *dev); +int cw1200_add_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif); +void cw1200_remove_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif); +int cw1200_config(struct ieee80211_hw *dev, u32 changed); +void cw1200_configure_filter(struct ieee80211_hw *dev, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast); +int cw1200_conf_tx(struct ieee80211_hw *dev, struct ieee80211_vif *vif, + u16 queue, const struct ieee80211_tx_queue_params *params); +int cw1200_get_stats(struct ieee80211_hw *dev, + struct ieee80211_low_level_stats *stats); +/* Not more a part of interface? +int cw1200_get_tx_stats(struct ieee80211_hw *dev, + struct ieee80211_tx_queue_stats *stats); +*/ +int cw1200_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key); + +int cw1200_set_rts_threshold(struct ieee80211_hw *hw, u32 value); + +void cw1200_flush(struct ieee80211_hw *hw, bool drop); + +u64 cw1200_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list); + +int cw1200_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg); + +/* ******************************************************************** */ +/* WSM callbacks */ + +/* void cw1200_set_pm_complete_cb(struct cw1200_common *priv, + struct wsm_set_pm_complete *arg); */ +void cw1200_channel_switch_cb(struct cw1200_common *priv); + +/* ******************************************************************** */ +/* WSM events */ + +void cw1200_free_event_queue(struct cw1200_common *priv); +void cw1200_event_handler(struct work_struct *work); +void cw1200_bss_loss_work(struct work_struct *work); +void cw1200_connection_loss_work(struct work_struct *work); +void cw1200_keep_alive_work(struct work_struct *work); +void cw1200_tx_failure_work(struct work_struct *work); + +/* ******************************************************************** */ +/* Internal API */ + +int cw1200_setup_mac(struct cw1200_common *priv); +void cw1200_join_work(struct work_struct *work); +void cw1200_join_timeout(struct work_struct *work); +void cw1200_unjoin_work(struct work_struct *work); +void cw1200_offchannel_work(struct work_struct *work); +void cw1200_wep_key_work(struct work_struct *work); +void cw1200_update_listening(struct cw1200_common *priv, bool enabled); +void cw1200_update_filtering(struct cw1200_common *priv); +void cw1200_update_filtering_work(struct work_struct *work); +int __cw1200_flush(struct cw1200_common *priv, bool drop); +int cw1200_enable_listening(struct cw1200_common *priv); +int cw1200_disable_listening(struct cw1200_common *priv); +int cw1200_set_uapsd_param(struct cw1200_common *priv, + const struct wsm_edca_params *arg); +void cw1200_ba_work(struct work_struct *work); +void cw1200_ba_timer(unsigned long arg); + +#endif diff --git a/drivers/staging/cw1200/txrx.c b/drivers/staging/cw1200/txrx.c new file mode 100644 index 00000000000..7c0fa0b0f2a --- /dev/null +++ b/drivers/staging/cw1200/txrx.c @@ -0,0 +1,1372 @@ +/* + * Datapath implementation for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <net/mac80211.h> +#include <linux/etherdevice.h> + +#include "cw1200.h" +#include "wsm.h" +#include "bh.h" +#include "ap.h" +#include "debug.h" + +#if defined(CONFIG_CW1200_TX_POLICY_DEBUG) +#define tx_policy_printk(...) printk(__VA_ARGS__) +#else +#define tx_policy_printk(...) +#endif + +#define CW1200_INVALID_RATE_ID (0xFF) + +static int cw1200_handle_action_rx(struct cw1200_common *priv, + struct sk_buff *skb); +static const struct ieee80211_rate * +cw1200_get_tx_rate(const struct cw1200_common *priv, + const struct ieee80211_tx_rate *rate); + +/* ******************************************************************** */ +/* TX queue lock / unlock */ + +static inline void cw1200_tx_queues_lock(struct cw1200_common *priv) +{ + int i; + for (i = 0; i < 4; ++i) + cw1200_queue_lock(&priv->tx_queue[i]); +} + +static inline void cw1200_tx_queues_unlock(struct cw1200_common *priv) +{ + int i; + for (i = 0; i < 4; ++i) + cw1200_queue_unlock(&priv->tx_queue[i]); +} + +/* ******************************************************************** */ +/* TX policy cache implementation */ + +static void tx_policy_dump(struct tx_policy *policy) +{ + tx_policy_printk(KERN_DEBUG "[TX policy] " + "%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X" + "%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X" + "%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X: %d\n", + policy->raw[0] & 0x0F, policy->raw[0] >> 4, + policy->raw[1] & 0x0F, policy->raw[1] >> 4, + policy->raw[2] & 0x0F, policy->raw[2] >> 4, + policy->raw[3] & 0x0F, policy->raw[3] >> 4, + policy->raw[4] & 0x0F, policy->raw[4] >> 4, + policy->raw[5] & 0x0F, policy->raw[5] >> 4, + policy->raw[6] & 0x0F, policy->raw[6] >> 4, + policy->raw[7] & 0x0F, policy->raw[7] >> 4, + policy->raw[8] & 0x0F, policy->raw[8] >> 4, + policy->raw[9] & 0x0F, policy->raw[9] >> 4, + policy->raw[10] & 0x0F, policy->raw[10] >> 4, + policy->raw[11] & 0x0F, policy->raw[11] >> 4, + policy->defined); +} + +static void tx_policy_build(const struct cw1200_common *priv, + /* [out] */ struct tx_policy *policy, + struct ieee80211_tx_rate *rates, size_t count) +{ + int i, j; + unsigned limit = priv->short_frame_max_tx_count; + unsigned total = 0; + BUG_ON(rates[0].idx < 0); + memset(policy, 0, sizeof(*policy)); + + /* minstrel is buggy a little bit, so distille + * incoming rates first. */ + + /* Sort rates in descending order. */ + for (i = 1; i < count; ++i) { + if (rates[i].idx < 0) { + count = i; + break; + } + if (rates[i].idx > rates[i - 1].idx) { + struct ieee80211_tx_rate tmp = rates[i - 1]; + rates[i - 1] = rates[i]; + rates[i] = tmp; + } + } + + /* Eliminate duplicates. */ + total = rates[0].count; + for (i = 0, j = 1; j < count; ++j) { + if (rates[j].idx == rates[i].idx) { + rates[i].count += rates[j].count; + } else if (rates[j].idx > rates[i].idx) { + break; + } else { + ++i; + if (i != j) + rates[i] = rates[j]; + } + total += rates[j].count; + } + count = i + 1; + + /* Re-fill policy trying to keep every requested rate and with + * respect to the global max tx retransmission count. */ + if (limit < count) + limit = count; + if (total > limit) { + for (i = 0; i < count; ++i) { + int left = count - i - 1; + if (rates[i].count > limit - left) + rates[i].count = limit - left; + limit -= rates[i].count; + } + } + + /* HACK!!! Device has problems (at least) switching from + * 54Mbps CTS to 1Mbps. This switch takes enormous amount + * of time (100-200 ms), leading to valuable throughput drop. + * As a workaround, additional g-rates are injected to the + * policy. + */ + if (count == 2 && !(rates[0].flags & IEEE80211_TX_RC_MCS) && + rates[0].idx > 4 && rates[0].count > 2 && + rates[1].idx < 2) { + /* ">> 1" is an equivalent of "/ 2", but faster */ + int mid_rate = (rates[0].idx + 4) >> 1; + + /* Decrease number of retries for the initial rate */ + rates[0].count -= 2; + + if (mid_rate != 4) { + /* Keep fallback rate at 1Mbps. */ + rates[3] = rates[1]; + + /* Inject 1 transmission on lowest g-rate */ + rates[2].idx = 4; + rates[2].count = 1; + rates[2].flags = rates[1].flags; + + /* Inject 1 transmission on mid-rate */ + rates[1].idx = mid_rate; + rates[1].count = 1; + + /* Fallback to 1 Mbps is a really bad thing, + * so let's try to increase probability of + * successful transmission on the lowest g rate + * even more */ + if (rates[0].count >= 3) { + --rates[0].count; + ++rates[2].count; + } + + /* Adjust amount of rates defined */ + count += 2; + } else { + /* Keep fallback rate at 1Mbps. */ + rates[2] = rates[1]; + + /* Inject 2 transmissions on lowest g-rate */ + rates[1].idx = 4; + rates[1].count = 2; + + /* Adjust amount of rates defined */ + count += 1; + } + } + + policy->defined = cw1200_get_tx_rate(priv, &rates[0])->hw_value + 1; + + for (i = 0; i < count; ++i) { + register unsigned rateid, off, shift, retries; + + rateid = cw1200_get_tx_rate(priv, &rates[i])->hw_value; + off = rateid >> 3; /* eq. rateid / 8 */ + shift = (rateid & 0x07) << 2; /* eq. (rateid % 8) * 4 */ + + retries = rates[i].count; + if (unlikely(retries > 0x0F)) + rates[i].count = retries = 0x0F; + policy->tbl[off] |= __cpu_to_le32(retries << shift); + policy->retry_count += retries; + } + + tx_policy_printk(KERN_DEBUG "[TX policy] Policy (%d): " \ + "%d:%d, %d:%d, %d:%d, %d:%d, %d:%d\n", + count, + rates[0].idx, rates[0].count, + rates[1].idx, rates[1].count, + rates[2].idx, rates[2].count, + rates[3].idx, rates[3].count, + rates[4].idx, rates[4].count); +} + +static inline bool tx_policy_is_equal(const struct tx_policy *wanted, + const struct tx_policy *cached) +{ + size_t count = wanted->defined >> 1; + if (wanted->defined > cached->defined) + return false; + if (count) { + if (memcmp(wanted->raw, cached->raw, count)) + return false; + } + if (wanted->defined & 1) { + if ((wanted->raw[count] & 0x0F) != (cached->raw[count] & 0x0F)) + return false; + } + return true; +} + +static int tx_policy_find(struct tx_policy_cache *cache, + const struct tx_policy *wanted) +{ + /* O(n) complexity. Not so good, but there's only 8 entries in + * the cache. + * Also lru helps to reduce search time. */ + struct tx_policy_cache_entry *it; + /* First search for policy in "used" list */ + list_for_each_entry(it, &cache->used, link) { + if (tx_policy_is_equal(wanted, &it->policy)) + return it - cache->cache; + } + /* Then - in "free list" */ + list_for_each_entry(it, &cache->free, link) { + if (tx_policy_is_equal(wanted, &it->policy)) + return it - cache->cache; + } + return -1; +} + +static inline void tx_policy_use(struct tx_policy_cache *cache, + struct tx_policy_cache_entry *entry) +{ + ++entry->policy.usage_count; + list_move(&entry->link, &cache->used); +} + +static inline int tx_policy_release(struct tx_policy_cache *cache, + struct tx_policy_cache_entry *entry) +{ + int ret = --entry->policy.usage_count; + if (!ret) + list_move(&entry->link, &cache->free); + return ret; +} + +/* ******************************************************************** */ +/* External TX policy cache API */ + +void tx_policy_init(struct cw1200_common *priv) +{ + struct tx_policy_cache *cache = &priv->tx_policy_cache; + int i; + + memset(cache, 0, sizeof(*cache)); + + spin_lock_init(&cache->lock); + INIT_LIST_HEAD(&cache->used); + INIT_LIST_HEAD(&cache->free); + + for (i = 0; i < TX_POLICY_CACHE_SIZE; ++i) + list_add(&cache->cache[i].link, &cache->free); +} + +static int tx_policy_get(struct cw1200_common *priv, + struct ieee80211_tx_rate *rates, + size_t count, bool *renew) +{ + int idx; + struct tx_policy_cache *cache = &priv->tx_policy_cache; + struct tx_policy wanted; + + tx_policy_build(priv, &wanted, rates, count); + + spin_lock_bh(&cache->lock); + if (WARN_ON_ONCE(list_empty(&cache->free))) { + spin_unlock_bh(&cache->lock); + return CW1200_INVALID_RATE_ID; + } + idx = tx_policy_find(cache, &wanted); + if (idx >= 0) { + tx_policy_printk(KERN_DEBUG "[TX policy] Used TX policy: %d\n", + idx); + *renew = false; + } else { + struct tx_policy_cache_entry *entry; + *renew = true; + /* If policy is not found create a new one + * using the oldest entry in "free" list */ + entry = list_entry(cache->free.prev, + struct tx_policy_cache_entry, link); + entry->policy = wanted; + idx = entry - cache->cache; + tx_policy_printk(KERN_DEBUG "[TX policy] New TX policy: %d\n", + idx); + tx_policy_dump(&entry->policy); + } + tx_policy_use(cache, &cache->cache[idx]); + if (unlikely(list_empty(&cache->free))) { + /* Lock TX queues. */ + cw1200_tx_queues_lock(priv); + } + spin_unlock_bh(&cache->lock); + return idx; +} + +static void tx_policy_put(struct cw1200_common *priv, int idx) +{ + int usage, locked; + struct tx_policy_cache *cache = &priv->tx_policy_cache; + + spin_lock_bh(&cache->lock); + locked = list_empty(&cache->free); + usage = tx_policy_release(cache, &cache->cache[idx]); + if (unlikely(locked) && !usage) { + /* Unlock TX queues. */ + cw1200_tx_queues_unlock(priv); + } + spin_unlock_bh(&cache->lock); +} + +/* +bool tx_policy_cache_full(struct cw1200_common *priv) +{ + bool ret; + struct tx_policy_cache *cache = &priv->tx_policy_cache; + spin_lock_bh(&cache->lock); + ret = list_empty(&cache->free); + spin_unlock_bh(&cache->lock); + return ret; +} +*/ + +static int tx_policy_upload(struct cw1200_common *priv) +{ + struct tx_policy_cache *cache = &priv->tx_policy_cache; + int i; + struct wsm_set_tx_rate_retry_policy arg = { + .hdr = { + .numTxRatePolicies = 0, + } + }; + spin_lock_bh(&cache->lock); + + /* Upload only modified entries. */ + for (i = 0; i < TX_POLICY_CACHE_SIZE; ++i) { + struct tx_policy *src = &cache->cache[i].policy; + if (src->retry_count && !src->uploaded) { + struct wsm_set_tx_rate_retry_policy_policy *dst = + &arg.tbl[arg.hdr.numTxRatePolicies]; + dst->policyIndex = i; + dst->shortRetryCount = priv->short_frame_max_tx_count; + dst->longRetryCount = priv->long_frame_max_tx_count; + + /* BIT(2) - Terminate retries when Tx rate retry policy + * finishes. + * BIT(3) - Count initial frame transmission as part of + * rate retry counting but not as a retry + * attempt */ + dst->policyFlags = BIT(2) | BIT(3); + + memcpy(dst->rateCountIndices, src->tbl, + sizeof(dst->rateCountIndices)); + src->uploaded = 1; + ++arg.hdr.numTxRatePolicies; + } + } + spin_unlock_bh(&cache->lock); + cw1200_debug_tx_cache_miss(priv); + tx_policy_printk(KERN_DEBUG "[TX policy] Upload %d policies\n", + arg.hdr.numTxRatePolicies); + return wsm_set_tx_rate_retry_policy(priv, &arg); +} + +void tx_policy_upload_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, tx_policy_upload_work); + + tx_policy_printk(KERN_DEBUG "[TX] TX policy upload.\n"); + WARN_ON(tx_policy_upload(priv)); + + wsm_unlock_tx(priv); + cw1200_tx_queues_unlock(priv); +} + +/* ******************************************************************** */ +/* cw1200 TX implementation */ + +struct cw1200_txinfo { + struct sk_buff *skb; + unsigned queue; + struct ieee80211_tx_info *tx_info; + const struct ieee80211_rate *rate; + struct ieee80211_hdr *hdr; + size_t hdrlen; + const u8 *da; + struct cw1200_sta_priv *sta_priv; + struct cw1200_txpriv txpriv; +}; + +u32 cw1200_rate_mask_to_wsm(struct cw1200_common *priv, u32 rates) +{ + u32 ret = 0; + int i; + for (i = 0; i < 32; ++i) { + if (rates & BIT(i)) + ret |= BIT(priv->rates[i].hw_value); + } + return ret; +} + +static const struct ieee80211_rate * +cw1200_get_tx_rate(const struct cw1200_common *priv, + const struct ieee80211_tx_rate *rate) +{ + if (rate->idx < 0) + return NULL; + if (rate->flags & IEEE80211_TX_RC_MCS) + return &priv->mcs_rates[rate->idx]; + return &priv->hw->wiphy->bands[priv->channel->band]-> + bitrates[rate->idx]; +} + +static int +cw1200_tx_h_calc_link_ids(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + + if (likely(t->tx_info->control.sta && t->sta_priv->link_id)) + t->txpriv.raw_link_id = + t->txpriv.link_id = + t->sta_priv->link_id; + else if (priv->mode != NL80211_IFTYPE_AP) + t->txpriv.raw_link_id = + t->txpriv.link_id = 0; + else if (is_multicast_ether_addr(t->da)) { + if (priv->enable_beacon) { + t->txpriv.raw_link_id = 0; + t->txpriv.link_id = CW1200_LINK_ID_AFTER_DTIM; + } else { + t->txpriv.raw_link_id = 0; + t->txpriv.link_id = 0; + } + } else { + t->txpriv.link_id = cw1200_find_link_id(priv, t->da); + if (!t->txpriv.link_id) + t->txpriv.link_id = cw1200_alloc_link_id(priv, t->da); + if (!t->txpriv.link_id) { + wiphy_err(priv->hw->wiphy, + "%s: No more link IDs available.\n", + __func__); + return -ENOENT; + } + t->txpriv.raw_link_id = t->txpriv.link_id; + } + if (t->txpriv.raw_link_id) + priv->link_id_db[t->txpriv.raw_link_id - 1].timestamp = + jiffies; + +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + if (t->tx_info->control.sta && + (t->tx_info->control.sta->uapsd_queues & BIT(t->queue))) + t->txpriv.link_id = CW1200_LINK_ID_UAPSD; +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ + return 0; +} + +static void +cw1200_tx_h_pm(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + if (unlikely(ieee80211_is_auth(t->hdr->frame_control))) { + u32 mask = ~BIT(t->txpriv.raw_link_id); + spin_lock_bh(&priv->ps_state_lock); + priv->sta_asleep_mask &= mask; + priv->pspoll_mask &= mask; + spin_unlock_bh(&priv->ps_state_lock); + } +} + +static void +cw1200_tx_h_calc_tid(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + if (ieee80211_is_data_qos(t->hdr->frame_control)) { + u8 *qos = ieee80211_get_qos_ctl(t->hdr); + t->txpriv.tid = qos[0] & IEEE80211_QOS_CTL_TID_MASK; + } else if (ieee80211_is_data(t->hdr->frame_control)) { + t->txpriv.tid = 0; + } +} + +/* IV/ICV injection. */ +/* TODO: Quite unoptimal. It's better co modify mac80211 + * to reserve space for IV */ +static int +cw1200_tx_h_crypt(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + size_t iv_len; + size_t icv_len; + u8 *icv; + u8 *newhdr; + + if (!t->tx_info->control.hw_key || + !(t->hdr->frame_control & + __cpu_to_le32(IEEE80211_FCTL_PROTECTED))) + return 0; + + iv_len = t->tx_info->control.hw_key->iv_len; + icv_len = t->tx_info->control.hw_key->icv_len; + + if (t->tx_info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) + icv_len += 8; /* MIC */ + + if ((skb_headroom(t->skb) + skb_tailroom(t->skb) < + iv_len + icv_len + WSM_TX_EXTRA_HEADROOM) || + (skb_headroom(t->skb) < + iv_len + WSM_TX_EXTRA_HEADROOM)) { + wiphy_err(priv->hw->wiphy, + "Bug: no space allocated for crypto headers.\n" + "headroom: %d, tailroom: %d, " + "req_headroom: %d, req_tailroom: %d\n" + "Please fix it in cw1200_get_skb().\n", + skb_headroom(t->skb), skb_tailroom(t->skb), + iv_len + WSM_TX_EXTRA_HEADROOM, icv_len); + return -ENOMEM; + } else if (skb_tailroom(t->skb) < icv_len) { + size_t offset = icv_len - skb_tailroom(t->skb); + u8 *p; + wiphy_warn(priv->hw->wiphy, + "Slowpath: tailroom is not big enough. " + "Req: %d, got: %d.\n", + icv_len, skb_tailroom(t->skb)); + + p = skb_push(t->skb, offset); + memmove(p, &p[offset], t->skb->len - offset); + skb_trim(t->skb, t->skb->len - offset); + } + + newhdr = skb_push(t->skb, iv_len); + memmove(newhdr, newhdr + iv_len, t->hdrlen); + t->hdr = (struct ieee80211_hdr *) newhdr; + t->hdrlen += iv_len; + icv = skb_put(t->skb, icv_len); + + return 0; +} + +static int +cw1200_tx_h_align(struct cw1200_common *priv, + struct cw1200_txinfo *t, + u8 *flags) +{ + size_t offset = (size_t)t->skb->data & 3; + + if (!offset) + return 0; + + if (offset & 1) { + wiphy_err(priv->hw->wiphy, + "Bug: attempt to transmit a frame " + "with wrong alignment: %d\n", + offset); + return -EINVAL; + } + + if (skb_headroom(t->skb) < offset) { + wiphy_err(priv->hw->wiphy, + "Bug: no space allocated " + "for DMA alignment.\n" + "headroom: %d\n", + skb_headroom(t->skb)); + return -ENOMEM; + } + skb_push(t->skb, offset); + t->hdrlen += offset; + t->txpriv.offset += offset; + *flags |= WSM_TX_2BYTES_SHIFT; + cw1200_debug_tx_align(priv); + return 0; +} + +static int +cw1200_tx_h_action(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + struct ieee80211_mgmt *mgmt = + (struct ieee80211_mgmt *)t->hdr; + if (ieee80211_is_action(t->hdr->frame_control) && + mgmt->u.action.category == WLAN_CATEGORY_BACK) + return 1; + else + return 0; +} + +/* Add WSM header */ +static struct wsm_tx * +cw1200_tx_h_wsm(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + struct wsm_tx *wsm; + + if (skb_headroom(t->skb) < sizeof(struct wsm_tx)) { + wiphy_err(priv->hw->wiphy, + "Bug: no space allocated " + "for WSM header.\n" + "headroom: %d\n", + skb_headroom(t->skb)); + return NULL; + } + + wsm = (struct wsm_tx *)skb_push(t->skb, sizeof(struct wsm_tx)); + t->txpriv.offset += sizeof(struct wsm_tx); + memset(wsm, 0, sizeof(*wsm)); + wsm->hdr.len = __cpu_to_le16(t->skb->len); + wsm->hdr.id = __cpu_to_le16(0x0004); + wsm->queueId = wsm_queue_id_to_wsm(t->queue); + return wsm; +} + +/* BT Coex specific handling */ +static void +cw1200_tx_h_bt(struct cw1200_common *priv, + struct cw1200_txinfo *t, + struct wsm_tx *wsm) +{ + u8 priority = 0; + + if (!priv->is_BT_Present) + return; + + if (unlikely(ieee80211_is_nullfunc(t->hdr->frame_control))) + priority = WSM_EPTA_PRIORITY_MGT; + else if (ieee80211_is_data(t->hdr->frame_control)) { + /* Skip LLC SNAP header (+6) */ + u8 *payload = &t->skb->data[t->hdrlen]; + u16 *ethertype = (u16 *) &payload[6]; + if (unlikely(*ethertype == __be16_to_cpu(ETH_P_PAE))) + priority = WSM_EPTA_PRIORITY_EAPOL; + } else if (unlikely(ieee80211_is_assoc_req(t->hdr->frame_control) || + ieee80211_is_reassoc_req(t->hdr->frame_control))) { + struct ieee80211_mgmt *mgt_frame = + (struct ieee80211_mgmt *)t->hdr; + + if (mgt_frame->u.assoc_req.listen_interval < + priv->listen_interval) { + txrx_printk(KERN_DEBUG + "Modified Listen Interval to %d from %d\n", + priv->listen_interval, + mgt_frame->u.assoc_req.listen_interval); + /* Replace listen interval derieved from + * the one read from SDD */ + mgt_frame->u.assoc_req.listen_interval = + priv->listen_interval; + } + } + + if (likely(!priority)) { + if (ieee80211_is_action(t->hdr->frame_control)) + priority = WSM_EPTA_PRIORITY_ACTION; + else if (ieee80211_is_mgmt(t->hdr->frame_control)) + priority = WSM_EPTA_PRIORITY_MGT; + else if ((wsm->queueId == WSM_QUEUE_VOICE)) + priority = WSM_EPTA_PRIORITY_VOICE; + else if ((wsm->queueId == WSM_QUEUE_VIDEO)) + priority = WSM_EPTA_PRIORITY_VIDEO; + else + priority = WSM_EPTA_PRIORITY_DATA; + } + + txrx_printk(KERN_DEBUG "[TX] EPTA priority %d.\n", + priority); + + wsm->flags |= priority << 1; +} + +static int +cw1200_tx_h_rate_policy(struct cw1200_common *priv, + struct cw1200_txinfo *t, + struct wsm_tx *wsm) +{ + bool tx_policy_renew = false; + + t->txpriv.rate_id = tx_policy_get(priv, + t->tx_info->control.rates, IEEE80211_TX_MAX_RATES, + &tx_policy_renew); + if (t->txpriv.rate_id == CW1200_INVALID_RATE_ID) + return -EFAULT; + + wsm->flags |= t->txpriv.rate_id << 4; + + t->rate = cw1200_get_tx_rate(priv, + &t->tx_info->control.rates[0]), + wsm->maxTxRate = t->rate->hw_value; + if (t->rate->flags & IEEE80211_TX_RC_MCS) { + if (cw1200_ht_greenfield(&priv->ht_info)) + wsm->htTxParameters |= + __cpu_to_le32(WSM_HT_TX_GREENFIELD); + else + wsm->htTxParameters |= + __cpu_to_le32(WSM_HT_TX_MIXED); + } + + if (tx_policy_renew) { + tx_policy_printk(KERN_DEBUG "[TX] TX policy renew.\n"); + /* It's not so optimal to stop TX queues every now and then. + * Maybe it's better to reimplement task scheduling with + * a counter. */ + /* cw1200_tx_queues_lock(priv); */ + /* Definetly better. TODO. */ + wsm_lock_tx_async(priv); + cw1200_tx_queues_lock(priv); + if (queue_work(priv->workqueue, + &priv->tx_policy_upload_work) <= 0) { + cw1200_tx_queues_unlock(priv); + wsm_unlock_tx(priv); + } + } + return 0; +} + +static bool +cw1200_tx_h_pm_state(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + int was_buffered = 1; + + if (t->txpriv.link_id == CW1200_LINK_ID_AFTER_DTIM && + !priv->buffered_multicasts) { + priv->buffered_multicasts = true; + if (priv->sta_asleep_mask) + queue_work(priv->workqueue, + &priv->multicast_start_work); + } + + if (t->txpriv.raw_link_id && t->txpriv.tid < CW1200_MAX_TID) + was_buffered = priv->link_id_db[t->txpriv.raw_link_id - 1] + .buffered[t->txpriv.tid]++; + + return !was_buffered; +} + +static void +cw1200_tx_h_ba_stat(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + if (priv->join_status != CW1200_JOIN_STATUS_STA) + return; + if (!cw1200_is_ht(&priv->ht_info)) + return; + if (!priv->setbssparams_done) + return; + if (!ieee80211_is_data(t->hdr->frame_control)) + return; + + spin_lock_bh(&priv->ba_lock); + priv->ba_acc += t->skb->len - t->hdrlen; + if (!priv->ba_cnt++) { + mod_timer(&priv->ba_timer, + jiffies + CW1200_BLOCK_ACK_INTERVAL); + } + spin_unlock_bh(&priv->ba_lock); +} + +/* ******************************************************************** */ + +void cw1200_tx(struct ieee80211_hw *dev, struct sk_buff *skb) +{ + struct cw1200_common *priv = dev->priv; + struct cw1200_txinfo t = { + .skb = skb, + .queue = skb_get_queue_mapping(skb), + .tx_info = IEEE80211_SKB_CB(skb), + .hdr = (struct ieee80211_hdr *)skb->data, + .txpriv.tid = CW1200_MAX_TID, + .txpriv.rate_id = CW1200_INVALID_RATE_ID, + }; + struct ieee80211_sta *sta; + struct wsm_tx *wsm; + bool tid_update = 0; + u8 flags = 0; + int ret; + + t.hdrlen = ieee80211_hdrlen(t.hdr->frame_control); + t.da = ieee80211_get_DA(t.hdr); + t.sta_priv = + (struct cw1200_sta_priv *)&t.tx_info->control.sta->drv_priv; + + if (WARN_ON(t.queue >= 4)) + goto drop; + + ret = cw1200_tx_h_calc_link_ids(priv, &t); + if (ret) + goto drop; + + txrx_printk(KERN_DEBUG "[TX] TX %d bytes " + "(queue: %d, link_id: %d (%d)).\n", + skb->len, t.queue, t.txpriv.link_id, + t.txpriv.raw_link_id); + + cw1200_tx_h_pm(priv, &t); + cw1200_tx_h_calc_tid(priv, &t); + ret = cw1200_tx_h_crypt(priv, &t); + if (ret) + goto drop; + ret = cw1200_tx_h_align(priv, &t, &flags); + if (ret) + goto drop; + ret = cw1200_tx_h_action(priv, &t); + if (ret) + goto drop; + wsm = cw1200_tx_h_wsm(priv, &t); + if (!wsm) { + ret = -ENOMEM; + goto drop; + } + wsm->flags |= flags; + cw1200_tx_h_bt(priv, &t, wsm); + ret = cw1200_tx_h_rate_policy(priv, &t, wsm); + if (ret) + goto drop; + + rcu_read_lock(); + sta = rcu_dereference(t.tx_info->control.sta); + + cw1200_tx_h_ba_stat(priv, &t); + spin_lock_bh(&priv->ps_state_lock); + { + tid_update = cw1200_tx_h_pm_state(priv, &t); + BUG_ON(cw1200_queue_put(&priv->tx_queue[t.queue], + t.skb, &t.txpriv)); + } + spin_unlock_bh(&priv->ps_state_lock); + +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + if (tid_update && sta) + ieee80211_sta_set_buffered(sta, + t.txpriv.tid, true); +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ + + rcu_read_unlock(); + + cw1200_bh_wakeup(priv); + + return; + +drop: + cw1200_skb_dtor(priv, skb, &t.txpriv); + return; +} + +/* ******************************************************************** */ + +static int cw1200_handle_action_rx(struct cw1200_common *priv, + struct sk_buff *skb) +{ + struct ieee80211_mgmt *mgmt = (void *)skb->data; + + /* Filter block ACK negotiation: fully controlled by firmware */ + if (mgmt->u.action.category == WLAN_CATEGORY_BACK) + return 1; + + return 0; +} + +static int cw1200_handle_pspoll(struct cw1200_common *priv, + struct sk_buff *skb) +{ + struct ieee80211_sta *sta; + struct ieee80211_pspoll *pspoll = + (struct ieee80211_pspoll *) skb->data; + int link_id = 0; + u32 pspoll_mask = 0; + int drop = 1; + int i; + + if (priv->join_status != CW1200_JOIN_STATUS_AP) + goto done; + if (memcmp(priv->vif->addr, pspoll->bssid, ETH_ALEN)) + goto done; + + rcu_read_lock(); + sta = ieee80211_find_sta(priv->vif, pspoll->ta); + if (sta) { + struct cw1200_sta_priv *sta_priv; + sta_priv = (struct cw1200_sta_priv *)&sta->drv_priv; + link_id = sta_priv->link_id; + pspoll_mask = BIT(sta_priv->link_id); + } + rcu_read_unlock(); + if (!link_id) + goto done; + + priv->pspoll_mask |= pspoll_mask; + drop = 0; + + /* Do not report pspols if data for given link id is + * queued already. */ + for (i = 0; i < 4; ++i) { + if (cw1200_queue_get_num_queued( + &priv->tx_queue[i], + pspoll_mask)) { + cw1200_bh_wakeup(priv); + drop = 1; + break; + } + } + txrx_printk(KERN_DEBUG "[RX] PSPOLL: %s\n", drop ? "local" : "fwd"); +done: + return drop; +} + +/* ******************************************************************** */ + +void cw1200_tx_confirm_cb(struct cw1200_common *priv, + struct wsm_tx_confirm *arg) +{ + u8 queue_id = cw1200_queue_get_queue_id(arg->packetID); + struct cw1200_queue *queue = &priv->tx_queue[queue_id]; + struct sk_buff *skb; + const struct cw1200_txpriv *txpriv; + + txrx_printk(KERN_DEBUG "[TX] TX confirm: %d, %d.\n", + arg->status, arg->ackFailures); + + if (unlikely(cw1200_itp_tx_running(priv))) + return; + + if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) { + /* STA is stopped. */ + return; + } + + if (WARN_ON(queue_id >= 4)) + return; + + if (arg->status) + txrx_printk(KERN_DEBUG "TX failed: %d.\n", + arg->status); + + if ((arg->status == WSM_REQUEUE) && + (arg->flags & WSM_TX_STATUS_REQUEUE)) { + /* "Requeue" means "implicit suspend" */ + struct wsm_suspend_resume suspend = { + .link_id = arg->link_id, + .stop = 1, + .multicast = !arg->link_id, + }; + cw1200_suspend_resume(priv, &suspend); + wiphy_warn(priv->hw->wiphy, "Requeue for link_id %d (try %d)." + " STAs asleep: 0x%.8X\n", + arg->link_id, + cw1200_queue_get_generation(arg->packetID) + 1, + priv->sta_asleep_mask); + WARN_ON(cw1200_queue_requeue(queue, + arg->packetID)); + spin_lock_bh(&priv->ps_state_lock); + if (!arg->link_id) { + priv->buffered_multicasts = true; + if (priv->sta_asleep_mask) { + queue_work(priv->workqueue, + &priv->multicast_start_work); + } + } + spin_unlock_bh(&priv->ps_state_lock); + } else if (!WARN_ON(cw1200_queue_get_skb( + queue, arg->packetID, &skb, &txpriv))) { + struct ieee80211_tx_info *tx = IEEE80211_SKB_CB(skb); + int tx_count = arg->ackFailures; + u8 ht_flags = 0; + int i; + + if (cw1200_ht_greenfield(&priv->ht_info)) + ht_flags |= IEEE80211_TX_RC_GREEN_FIELD; + + if (likely(!arg->status)) { + tx->flags |= IEEE80211_TX_STAT_ACK; + priv->cqm_tx_failure_count = 0; + ++tx_count; + cw1200_debug_txed(priv); + if (arg->flags & WSM_TX_STATUS_AGGREGATION) { + /* Do not report aggregation to mac80211: + * it confuses minstrel a lot. */ + /* tx->flags |= IEEE80211_TX_STAT_AMPDU; */ + cw1200_debug_txed_agg(priv); + } + } else { + spin_lock(&priv->bss_loss_lock); + if (priv->bss_loss_status == + CW1200_BSS_LOSS_CONFIRMING && + priv->bss_loss_confirm_id == + arg->packetID) { + priv->bss_loss_status = + CW1200_BSS_LOSS_CONFIRMED; + spin_unlock(&priv->bss_loss_lock); + cancel_delayed_work(&priv->bss_loss_work); + queue_delayed_work(priv->workqueue, + &priv->bss_loss_work, 0); + } else + spin_unlock(&priv->bss_loss_lock); + + /* TODO: Update TX failure counters */ + if (unlikely(priv->cqm_tx_failure_thold && + (++priv->cqm_tx_failure_count > + priv->cqm_tx_failure_thold))) { + priv->cqm_tx_failure_thold = 0; + queue_work(priv->workqueue, + &priv->tx_failure_work); + } + if (tx_count) + ++tx_count; + } + + for (i = 0; i < IEEE80211_TX_MAX_RATES; ++i) { + if (tx->status.rates[i].count >= tx_count) { + tx->status.rates[i].count = tx_count; + break; + } + tx_count -= tx->status.rates[i].count; + if (tx->status.rates[i].flags & IEEE80211_TX_RC_MCS) + tx->status.rates[i].flags |= ht_flags; + } + + for (++i; i < IEEE80211_TX_MAX_RATES; ++i) { + tx->status.rates[i].count = 0; + tx->status.rates[i].idx = -1; + } + + + cw1200_queue_remove(queue, arg->packetID); + } +} + +static void cw1200_notify_buffered_tx(struct cw1200_common *priv, + struct sk_buff *skb, int link_id, int tid) +{ +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + struct ieee80211_sta *sta; + struct ieee80211_hdr *hdr; + u8 *buffered; + u8 still_buffered = 0; + + if (link_id && tid < CW1200_MAX_TID) { + buffered = priv->link_id_db + [link_id - 1].buffered; + + spin_lock_bh(&priv->ps_state_lock); + if (!WARN_ON(!buffered[tid])) + still_buffered = --buffered[tid]; + spin_unlock_bh(&priv->ps_state_lock); + + if (!still_buffered && tid < CW1200_MAX_TID) { + hdr = (struct ieee80211_hdr *) skb->data; + rcu_read_lock(); + sta = ieee80211_find_sta(priv->vif, hdr->addr1); + if (sta) + ieee80211_sta_set_buffered(sta, tid, false); + rcu_read_unlock(); + } + } +#endif /* CONFIG_CW1200_USE_STE_EXTENSIONS */ +} + +void cw1200_skb_dtor(struct cw1200_common *priv, + struct sk_buff *skb, + const struct cw1200_txpriv *txpriv) +{ + skb_pull(skb, txpriv->offset); + if (txpriv->rate_id != CW1200_INVALID_RATE_ID) { + cw1200_notify_buffered_tx(priv, skb, + txpriv->raw_link_id, txpriv->tid); + tx_policy_put(priv, txpriv->rate_id); + } + if (likely(!cw1200_is_itp(priv))) + ieee80211_tx_status(priv->hw, skb); +} + +void cw1200_rx_cb(struct cw1200_common *priv, + struct wsm_rx *arg, + struct sk_buff **skb_p) +{ + struct sk_buff *skb = *skb_p; + struct ieee80211_rx_status *hdr = IEEE80211_SKB_RXCB(skb); + struct ieee80211_hdr *frame = (struct ieee80211_hdr *)skb->data; +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; +#endif + struct cw1200_link_entry *entry = NULL; + unsigned long grace_period; + bool early_data = false; + hdr->flag = 0; + + if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) { + /* STA is stopped. */ + goto drop; + } + + if (arg->link_id && arg->link_id <= CW1200_MAX_STA_IN_AP_MODE) { + entry = &priv->link_id_db[arg->link_id - 1]; + if (entry->status == CW1200_LINK_SOFT && + ieee80211_is_data(frame->frame_control)) + early_data = true; + entry->timestamp = jiffies; + } +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + else if ((priv->vif->p2p == WSM_START_MODE_P2P_GO) + && ieee80211_is_action(frame->frame_control) + && (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC)) { + txrx_printk(KERN_DEBUG "[RX] Going to MAP&RESET link ID\n"); + + if (work_pending(&priv->linkid_reset_work)) + WARN_ON(1); + + memcpy(&priv->action_frame_sa[0], + ieee80211_get_SA(frame), ETH_ALEN); + priv->action_linkid = 0; + schedule_work(&priv->linkid_reset_work); + } + + if (arg->link_id && (priv->vif->p2p == WSM_START_MODE_P2P_GO) + && ieee80211_is_action(frame->frame_control) + && (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC)) { + /* Link ID already exists for the ACTION frame. + * Reset and Remap */ + if (work_pending(&priv->linkid_reset_work)) + WARN_ON(1); + memcpy(&priv->action_frame_sa[0], + ieee80211_get_SA(frame), ETH_ALEN); + priv->action_linkid = arg->link_id; + schedule_work(&priv->linkid_reset_work); + } +#endif + if (unlikely(arg->status)) { + if (arg->status == WSM_STATUS_MICFAILURE) { + txrx_printk(KERN_DEBUG "[RX] MIC failure.\n"); + hdr->flag |= RX_FLAG_MMIC_ERROR; + } else if (arg->status == WSM_STATUS_NO_KEY_FOUND) { + txrx_printk(KERN_DEBUG "[RX] No key found.\n"); + goto drop; + } else { + txrx_printk(KERN_DEBUG "[RX] Receive failure: %d.\n", + arg->status); + goto drop; + } + } + + if (skb->len < sizeof(struct ieee80211_pspoll)) { + wiphy_warn(priv->hw->wiphy, "Mailformed SDU rx'ed. " + "Size is lesser than IEEE header.\n"); + goto drop; + } + + if (unlikely(ieee80211_is_pspoll(frame->frame_control))) + if (cw1200_handle_pspoll(priv, skb)) + goto drop; + + hdr->mactime = 0; /* Not supported by WSM */ + hdr->band = (arg->channelNumber > 14) ? + IEEE80211_BAND_5GHZ : IEEE80211_BAND_2GHZ; + hdr->freq = ieee80211_channel_to_frequency( + arg->channelNumber, + hdr->band); + + if (arg->rxedRate >= 14) { + hdr->flag |= RX_FLAG_HT; + hdr->rate_idx = arg->rxedRate - 14; + } else if (arg->rxedRate >= 4) { + hdr->rate_idx = arg->rxedRate - 2; + } else { + hdr->rate_idx = arg->rxedRate; + } + + hdr->signal = (s8)arg->rcpiRssi; + hdr->antenna = 0; + + if (WSM_RX_STATUS_ENCRYPTION(arg->flags)) { + size_t iv_len = 0, icv_len = 0; + size_t hdrlen = ieee80211_hdrlen(frame->frame_control); + + hdr->flag |= RX_FLAG_DECRYPTED | RX_FLAG_IV_STRIPPED; + + /* Oops... There is no fast way to ask mac80211 about + * IV/ICV lengths. Even defineas are not exposed.*/ + switch (WSM_RX_STATUS_ENCRYPTION(arg->flags)) { + case WSM_RX_STATUS_WEP: + iv_len = 4 /* WEP_IV_LEN */; + icv_len = 4 /* WEP_ICV_LEN */; + break; + case WSM_RX_STATUS_TKIP: + iv_len = 8 /* TKIP_IV_LEN */; + icv_len = 4 /* TKIP_ICV_LEN */ + + 8 /*MICHAEL_MIC_LEN*/; + hdr->flag |= RX_FLAG_MMIC_STRIPPED; + break; + case WSM_RX_STATUS_AES: + iv_len = 8 /* CCMP_HDR_LEN */; + icv_len = 8 /* CCMP_MIC_LEN */; + break; + case WSM_RX_STATUS_WAPI: + iv_len = 18 /* WAPI_HDR_LEN */; + icv_len = 16 /* WAPI_MIC_LEN */; + break; + default: + WARN_ON("Unknown encryption type"); + goto drop; + } + + /* Firmware strips ICV in case of MIC failure. */ + if (arg->status == WSM_STATUS_MICFAILURE) + icv_len = 0; + + if (skb->len < hdrlen + iv_len + icv_len) { + wiphy_warn(priv->hw->wiphy, "Mailformed SDU rx'ed. " + "Size is lesser than crypto headers.\n"); + goto drop; + } + + /* Remove IV, ICV and MIC */ + skb_trim(skb, skb->len - icv_len); + memmove(skb->data + iv_len, skb->data, hdrlen); + skb_pull(skb, iv_len); + } + + cw1200_debug_rxed(priv); + if (arg->flags & WSM_RX_STATUS_AGGREGATE) + cw1200_debug_rxed_agg(priv); + + if (ieee80211_is_action(frame->frame_control) && + (arg->flags & WSM_RX_STATUS_ADDRESS1)) { + if (cw1200_handle_action_rx(priv, skb)) + return; + } else if (unlikely(priv->disable_beacon_filter) && + !arg->status && + ieee80211_is_beacon(frame->frame_control) && + !memcmp(ieee80211_get_SA(frame), priv->join_bssid, + ETH_ALEN)) { + priv->disable_beacon_filter = false; + queue_work(priv->workqueue, &priv->update_filtering_work); + } + + /* Stay awake for 1sec. after frame is received to give + * userspace chance to react and acquire appropriate + * wakelock. */ + if (ieee80211_is_auth(frame->frame_control)) + grace_period = 5 * HZ; + else if (ieee80211_is_deauth(frame->frame_control)) + grace_period = 5 * HZ; + else + grace_period = 1 * HZ; + cw1200_pm_stay_awake(&priv->pm_state, grace_period); + + if (unlikely(cw1200_itp_rxed(priv, skb))) + consume_skb(skb); + else if (unlikely(early_data)) { + spin_lock_bh(&priv->ps_state_lock); + /* Double-check status with lock held */ + if (entry->status == CW1200_LINK_SOFT) + skb_queue_tail(&entry->rx_queue, skb); + else + ieee80211_rx_irqsafe(priv->hw, skb); + spin_unlock_bh(&priv->ps_state_lock); + } else { + ieee80211_rx_irqsafe(priv->hw, skb); + } + *skb_p = NULL; + + return; + +drop: + /* TODO: update failure counters */ + return; +} + +/* ******************************************************************** */ +/* Security */ + +int cw1200_alloc_key(struct cw1200_common *priv) +{ + int idx; + + idx = ffs(~priv->key_map) - 1; + if (idx < 0 || idx > WSM_KEY_MAX_INDEX) + return -1; + + priv->key_map |= BIT(idx); + priv->keys[idx].entryIndex = idx; + return idx; +} + +void cw1200_free_key(struct cw1200_common *priv, int idx) +{ + BUG_ON(!(priv->key_map & BIT(idx))); + memset(&priv->keys[idx], 0, sizeof(priv->keys[idx])); + priv->key_map &= ~BIT(idx); +} + +void cw1200_free_keys(struct cw1200_common *priv) +{ + memset(&priv->keys, 0, sizeof(priv->keys)); + priv->key_map = 0; +} + +int cw1200_upload_keys(struct cw1200_common *priv) +{ + int idx, ret = 0; + for (idx = 0; idx <= WSM_KEY_MAX_INDEX; ++idx) + if (priv->key_map & BIT(idx)) { + ret = wsm_add_key(priv, &priv->keys[idx]); + if (ret < 0) + break; + } + return ret; +} +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) +/* Workaround for WFD test case 6.1.10 */ +void cw1200_link_id_reset(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, linkid_reset_work); + int temp_linkid; + + if (!priv->action_linkid) { + /* In GO mode we can receive ACTION frames without a linkID */ + temp_linkid = cw1200_alloc_link_id(priv, + &priv->action_frame_sa[0]); + WARN_ON(!temp_linkid); + if (temp_linkid) { + /* Make sure we execute the WQ */ + flush_workqueue(priv->workqueue); + /* Release the link ID */ + spin_lock_bh(&priv->ps_state_lock); + priv->link_id_db[temp_linkid - 1].prev_status = + priv->link_id_db[temp_linkid - 1].status; + priv->link_id_db[temp_linkid - 1].status = + CW1200_LINK_RESET; + spin_unlock_bh(&priv->ps_state_lock); + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, + &priv->link_id_work) <= 0) + wsm_unlock_tx(priv); + } + } else { + spin_lock_bh(&priv->ps_state_lock); + priv->link_id_db[priv->action_linkid - 1].prev_status = + priv->link_id_db[priv->action_linkid - 1].status; + priv->link_id_db[priv->action_linkid - 1].status = + CW1200_LINK_RESET_REMAP; + spin_unlock_bh(&priv->ps_state_lock); + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, &priv->link_id_work) <= 0) + wsm_unlock_tx(priv); + flush_workqueue(priv->workqueue); + } +} +#endif diff --git a/drivers/staging/cw1200/txrx.h b/drivers/staging/cw1200/txrx.h new file mode 100644 index 00000000000..f3b2023ff2b --- /dev/null +++ b/drivers/staging/cw1200/txrx.h @@ -0,0 +1,95 @@ +/* + * Datapath interface for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef CW1200_TXRX_H +#define CW1200_TXRX_H + +#include <linux/list.h> + +/* extern */ struct ieee80211_hw; +/* extern */ struct sk_buff; +/* extern */ struct wsm_tx; +/* extern */ struct wsm_rx; +/* extern */ struct wsm_tx_confirm; +/* extern */ struct cw1200_txpriv; + +struct tx_policy { + union { + __le32 tbl[3]; + u8 raw[12]; + }; + u8 defined; /* TODO: u32 or u8, profile and select best */ + u8 usage_count; /* --// -- */ + u8 retry_count; /* --// -- */ + u8 uploaded; +}; + +struct tx_policy_cache_entry { + struct tx_policy policy; + struct list_head link; +}; + +#define TX_POLICY_CACHE_SIZE (8) +struct tx_policy_cache { + struct tx_policy_cache_entry cache[TX_POLICY_CACHE_SIZE]; + struct list_head used; + struct list_head free; + spinlock_t lock; +}; + +/* ******************************************************************** */ +/* TX policy cache */ +/* Intention of TX policy cache is an overcomplicated WSM API. + * Device does not accept per-PDU tx retry sequence. + * It uses "tx retry policy id" instead, so driver code has to sync + * linux tx retry sequences with a retry policy table in the device. + */ +void tx_policy_init(struct cw1200_common *priv); +void tx_policy_upload_work(struct work_struct *work); + +/* ******************************************************************** */ +/* TX implementation */ + +u32 cw1200_rate_mask_to_wsm(struct cw1200_common *priv, + u32 rates); +void cw1200_tx(struct ieee80211_hw *dev, struct sk_buff *skb); +void cw1200_skb_dtor(struct cw1200_common *priv, + struct sk_buff *skb, + const struct cw1200_txpriv *txpriv); + +/* ******************************************************************** */ +/* WSM callbacks */ + +void cw1200_tx_confirm_cb(struct cw1200_common *priv, + struct wsm_tx_confirm *arg); +void cw1200_rx_cb(struct cw1200_common *priv, + struct wsm_rx *arg, + struct sk_buff **skb_p); + +/* ******************************************************************** */ +/* Timeout */ + +void cw1200_tx_timeout(struct work_struct *work); + +/* ******************************************************************** */ +/* Security */ +int cw1200_alloc_key(struct cw1200_common *priv); +void cw1200_free_key(struct cw1200_common *priv, int idx); +void cw1200_free_keys(struct cw1200_common *priv); +int cw1200_upload_keys(struct cw1200_common *priv); + +/* ******************************************************************** */ +/* Workaround for WFD test case 6.1.10 */ +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) +void cw1200_link_id_reset(struct work_struct *work); +#endif + +#endif /* CW1200_TXRX_H */ diff --git a/drivers/staging/cw1200/wsm.c b/drivers/staging/cw1200/wsm.c new file mode 100644 index 00000000000..3cf53704f07 --- /dev/null +++ b/drivers/staging/cw1200/wsm.c @@ -0,0 +1,1836 @@ +/* + * WSM host interface (HI) implementation for + * ST-Ericsson CW1200 mac80211 drivers. + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/skbuff.h> +#include <linux/wait.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/random.h> + +#include "cw1200.h" +#include "wsm.h" +#include "bh.h" +#include "debug.h" +#include "itp.h" + +#if defined(CONFIG_CW1200_WSM_DEBUG) +#define wsm_printk(...) printk(__VA_ARGS__) +#else +#define wsm_printk(...) +#endif + +#define WSM_CMD_TIMEOUT (2 * HZ) /* With respect to interrupt loss */ +#define WSM_CMD_JOIN_TIMEOUT (7 * HZ) /* Join timeout is 5 sec. in FW */ +#define WSM_CMD_START_TIMEOUT (7 * HZ) +#define WSM_CMD_RESET_TIMEOUT (3 * HZ) /* 2 sec. timeout was observed. */ + +#define WSM_SKIP(buf, size) \ + do { \ + if (unlikely((buf)->data + size > (buf)->end)) \ + goto underflow; \ + (buf)->data += size; \ + } while (0) + +#define WSM_GET(buf, ptr, size) \ + do { \ + if (unlikely((buf)->data + size > (buf)->end)) \ + goto underflow; \ + memcpy(ptr, (buf)->data, size); \ + (buf)->data += size; \ + } while (0) + +#define __WSM_GET(buf, type, cvt) \ + ({ \ + type val; \ + if (unlikely((buf)->data + sizeof(type) > (buf)->end)) \ + goto underflow; \ + val = cvt(*(type *)(buf)->data); \ + (buf)->data += sizeof(type); \ + val; \ + }) + +#define WSM_GET8(buf) __WSM_GET(buf, u8, (u8)) +#define WSM_GET16(buf) __WSM_GET(buf, u16, __le16_to_cpu) +#define WSM_GET32(buf) __WSM_GET(buf, u32, __le32_to_cpu) + +#define WSM_PUT(buf, ptr, size) \ + do { \ + if (unlikely((buf)->data + size > (buf)->end)) \ + if (unlikely(wsm_buf_reserve((buf), size))) \ + goto nomem; \ + memcpy((buf)->data, ptr, size); \ + (buf)->data += size; \ + } while (0) + +#define __WSM_PUT(buf, val, type, cvt) \ + do { \ + if (unlikely((buf)->data + sizeof(type) > (buf)->end)) \ + if (unlikely(wsm_buf_reserve((buf), sizeof(type)))) \ + goto nomem; \ + *(type *)(buf)->data = cvt(val); \ + (buf)->data += sizeof(type); \ + } while (0) + +#define WSM_PUT8(buf, val) __WSM_PUT(buf, val, u8, (u8)) +#define WSM_PUT16(buf, val) __WSM_PUT(buf, val, u16, __cpu_to_le16) +#define WSM_PUT32(buf, val) __WSM_PUT(buf, val, u32, __cpu_to_le32) + +static void wsm_buf_reset(struct wsm_buf *buf); +static int wsm_buf_reserve(struct wsm_buf *buf, size_t extra_size); + +static int wsm_cmd_send(struct cw1200_common *priv, + struct wsm_buf *buf, + void *arg, u16 cmd, long tmo); + +static inline void wsm_cmd_lock(struct cw1200_common *priv) +{ + mutex_lock(&priv->wsm_cmd_mux); +} + +static inline void wsm_cmd_unlock(struct cw1200_common *priv) +{ + mutex_unlock(&priv->wsm_cmd_mux); +} + +/* ******************************************************************** */ +/* WSM API implementation */ + +static int wsm_generic_confirm(struct cw1200_common *priv, + void *arg, + struct wsm_buf *buf) +{ + u32 status = WSM_GET32(buf); + if (status != WSM_STATUS_SUCCESS) + return -EINVAL; + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +int wsm_configuration(struct cw1200_common *priv, struct wsm_configuration *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT32(buf, arg->dot11MaxTransmitMsduLifeTime); + WSM_PUT32(buf, arg->dot11MaxReceiveLifeTime); + WSM_PUT32(buf, arg->dot11RtsThreshold); + + /* DPD block. */ + WSM_PUT16(buf, arg->dpdData_size + 12); + WSM_PUT16(buf, 1); /* DPD version */ + WSM_PUT(buf, arg->dot11StationId, ETH_ALEN); + WSM_PUT16(buf, 5); /* DPD flags */ + WSM_PUT(buf, arg->dpdData, arg->dpdData_size); + + ret = wsm_cmd_send(priv, buf, arg, 0x0009, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +static int wsm_configuration_confirm(struct cw1200_common *priv, + struct wsm_configuration *arg, + struct wsm_buf *buf) +{ + int i; + int status; + + status = WSM_GET32(buf); + if (WARN_ON(status != WSM_STATUS_SUCCESS)) + return -EINVAL; + + WSM_GET(buf, arg->dot11StationId, ETH_ALEN); + arg->dot11FrequencyBandsSupported = WSM_GET8(buf); + WSM_SKIP(buf, 1); + arg->supportedRateMask = WSM_GET32(buf); + for (i = 0; i < 2; ++i) { + arg->txPowerRange[i].min_power_level = WSM_GET32(buf); + arg->txPowerRange[i].max_power_level = WSM_GET32(buf); + arg->txPowerRange[i].stepping = WSM_GET32(buf); + } + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +/* ******************************************************************** */ + +int wsm_reset(struct cw1200_common *priv, const struct wsm_reset *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + u16 cmd = 0x000A | WSM_TX_LINK_ID(arg->link_id); + + wsm_cmd_lock(priv); + + WSM_PUT32(buf, arg->reset_statistics ? 0 : 1); + ret = wsm_cmd_send(priv, buf, NULL, cmd, WSM_CMD_RESET_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +struct wsm_mib { + u16 mibId; + void *buf; + size_t buf_size; +}; + +int wsm_read_mib(struct cw1200_common *priv, u16 mibId, void *_buf, + size_t buf_size) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + struct wsm_mib mib_buf = { + .mibId = mibId, + .buf = _buf, + .buf_size = buf_size, + }; + wsm_cmd_lock(priv); + + WSM_PUT16(buf, mibId); + WSM_PUT16(buf, 0); + + ret = wsm_cmd_send(priv, buf, &mib_buf, 0x0005, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +static int wsm_read_mib_confirm(struct cw1200_common *priv, + struct wsm_mib *arg, + struct wsm_buf *buf) +{ + u16 size; + if (WARN_ON(WSM_GET32(buf) != WSM_STATUS_SUCCESS)) + return -EINVAL; + + if (WARN_ON(WSM_GET16(buf) != arg->mibId)) + return -EINVAL; + + size = WSM_GET16(buf); + if (size > arg->buf_size) + size = arg->buf_size; + + WSM_GET(buf, arg->buf, size); + arg->buf_size = size; + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +/* ******************************************************************** */ + +int wsm_write_mib(struct cw1200_common *priv, u16 mibId, void *_buf, + size_t buf_size) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + struct wsm_mib mib_buf = { + .mibId = mibId, + .buf = _buf, + .buf_size = buf_size, + }; + + wsm_cmd_lock(priv); + + WSM_PUT16(buf, mibId); + WSM_PUT16(buf, buf_size); + WSM_PUT(buf, _buf, buf_size); + + ret = wsm_cmd_send(priv, buf, &mib_buf, 0x0006, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +static int wsm_write_mib_confirm(struct cw1200_common *priv, + struct wsm_mib *arg, + struct wsm_buf *buf) +{ + int ret; + + ret = wsm_generic_confirm(priv, arg, buf); + if (ret) + return ret; + + if (arg->mibId == 0x1006) { + /* OperationalMode: update PM status. */ + const char *p = arg->buf; + cw1200_enable_powersave(priv, + (p[0] & 0x0F) ? true : false); + } + return 0; +} + +/* ******************************************************************** */ + +int wsm_scan(struct cw1200_common *priv, const struct wsm_scan *arg) +{ + int i; + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + if (unlikely(arg->numOfChannels > 48)) + return -EINVAL; + + if (unlikely(arg->numOfSSIDs > 2)) + return -EINVAL; + + if (unlikely(arg->band > 1)) + return -EINVAL; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->band); + WSM_PUT8(buf, arg->scanType); + WSM_PUT8(buf, arg->scanFlags); + WSM_PUT8(buf, arg->maxTransmitRate); + WSM_PUT32(buf, arg->autoScanInterval); + WSM_PUT8(buf, arg->numOfProbeRequests); + WSM_PUT8(buf, arg->numOfChannels); + WSM_PUT8(buf, arg->numOfSSIDs); + WSM_PUT8(buf, arg->probeDelay); + + for (i = 0; i < arg->numOfChannels; ++i) { + WSM_PUT16(buf, arg->ch[i].number); + WSM_PUT16(buf, 0); + WSM_PUT32(buf, arg->ch[i].minChannelTime); + WSM_PUT32(buf, arg->ch[i].maxChannelTime); + WSM_PUT32(buf, 0); + } + + for (i = 0; i < arg->numOfSSIDs; ++i) { + WSM_PUT32(buf, arg->ssids[i].length); + WSM_PUT(buf, &arg->ssids[i].ssid[0], + sizeof(arg->ssids[i].ssid)); + } + + ret = wsm_cmd_send(priv, buf, NULL, 0x0007, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_stop_scan(struct cw1200_common *priv) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + wsm_cmd_lock(priv); + ret = wsm_cmd_send(priv, buf, NULL, 0x0008, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; +} + + +static int wsm_tx_confirm(struct cw1200_common *priv, + struct wsm_buf *buf, + int link_id) +{ + struct wsm_tx_confirm tx_confirm; + + tx_confirm.packetID = WSM_GET32(buf); + tx_confirm.status = WSM_GET32(buf); + tx_confirm.txedRate = WSM_GET8(buf); + tx_confirm.ackFailures = WSM_GET8(buf); + tx_confirm.flags = WSM_GET16(buf); + tx_confirm.mediaDelay = WSM_GET32(buf); + tx_confirm.txQueueDelay = WSM_GET32(buf); + tx_confirm.link_id = link_id; + + if (priv->wsm_cbc.tx_confirm) + priv->wsm_cbc.tx_confirm(priv, &tx_confirm); + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +static int wsm_multi_tx_confirm(struct cw1200_common *priv, + struct wsm_buf *buf, int link_id) +{ + int ret; + int count; + int i; + + count = WSM_GET32(buf); + if (WARN_ON(count <= 0)) + return -EINVAL; + else if (count > 1) { + ret = wsm_release_tx_buffer(priv, count - 1); + if (ret < 0) + return ret; + else if (ret > 0) + cw1200_bh_wakeup(priv); + } + + cw1200_debug_txed_multi(priv, count); + for (i = 0; i < count; ++i) { + ret = wsm_tx_confirm(priv, buf, link_id); + if (ret) + return ret; + } + return ret; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +/* ******************************************************************** */ + +static int wsm_join_confirm(struct cw1200_common *priv, + struct wsm_join *arg, + struct wsm_buf *buf) +{ + if (WARN_ON(WSM_GET32(buf) != WSM_STATUS_SUCCESS)) + return -EINVAL; + + arg->minPowerLevel = WSM_GET32(buf); + arg->maxPowerLevel = WSM_GET32(buf); + + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +int wsm_join(struct cw1200_common *priv, struct wsm_join *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->mode); + WSM_PUT8(buf, arg->band); + WSM_PUT16(buf, arg->channelNumber); + WSM_PUT(buf, &arg->bssid[0], sizeof(arg->bssid)); + WSM_PUT16(buf, arg->atimWindow); + WSM_PUT8(buf, arg->preambleType); + WSM_PUT8(buf, arg->probeForJoin); + WSM_PUT8(buf, arg->dtimPeriod); + WSM_PUT8(buf, arg->flags); + WSM_PUT32(buf, arg->ssidLength); + WSM_PUT(buf, &arg->ssid[0], sizeof(arg->ssid)); + WSM_PUT32(buf, arg->beaconInterval); + WSM_PUT32(buf, arg->basicRateSet); + + priv->tx_burst_idx = -1; + ret = wsm_cmd_send(priv, buf, arg, 0x000B, WSM_CMD_JOIN_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_set_bss_params(struct cw1200_common *priv, + const struct wsm_set_bss_params *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, 0); + WSM_PUT8(buf, arg->beaconLostCount); + WSM_PUT16(buf, arg->aid); + WSM_PUT32(buf, arg->operationalRateSet); + + ret = wsm_cmd_send(priv, buf, NULL, 0x0011, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_add_key(struct cw1200_common *priv, const struct wsm_add_key *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT(buf, arg, sizeof(*arg)); + + ret = wsm_cmd_send(priv, buf, NULL, 0x000C, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_remove_key(struct cw1200_common *priv, const struct wsm_remove_key *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->entryIndex); + WSM_PUT8(buf, 0); + WSM_PUT16(buf, 0); + + ret = wsm_cmd_send(priv, buf, NULL, 0x000D, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_set_tx_queue_params(struct cw1200_common *priv, + const struct wsm_set_tx_queue_params *arg, u8 id) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + u8 queue_id_to_wmm_aci[] = {3, 2, 0, 1}; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, queue_id_to_wmm_aci[id]); + WSM_PUT8(buf, 0); + WSM_PUT8(buf, arg->ackPolicy); + WSM_PUT8(buf, 0); + WSM_PUT32(buf, arg->maxTransmitLifetime); + WSM_PUT16(buf, arg->allowedMediumTime); + WSM_PUT16(buf, 0); + + ret = wsm_cmd_send(priv, buf, NULL, 0x0012, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_set_edca_params(struct cw1200_common *priv, + const struct wsm_edca_params *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + /* Implemented according to specification. */ + + WSM_PUT16(buf, arg->params[3].cwMin); + WSM_PUT16(buf, arg->params[2].cwMin); + WSM_PUT16(buf, arg->params[1].cwMin); + WSM_PUT16(buf, arg->params[0].cwMin); + + WSM_PUT16(buf, arg->params[3].cwMax); + WSM_PUT16(buf, arg->params[2].cwMax); + WSM_PUT16(buf, arg->params[1].cwMax); + WSM_PUT16(buf, arg->params[0].cwMax); + + WSM_PUT8(buf, arg->params[3].aifns); + WSM_PUT8(buf, arg->params[2].aifns); + WSM_PUT8(buf, arg->params[1].aifns); + WSM_PUT8(buf, arg->params[0].aifns); + + WSM_PUT16(buf, arg->params[3].txOpLimit); + WSM_PUT16(buf, arg->params[2].txOpLimit); + WSM_PUT16(buf, arg->params[1].txOpLimit); + WSM_PUT16(buf, arg->params[0].txOpLimit); + + WSM_PUT32(buf, arg->params[3].maxReceiveLifetime); + WSM_PUT32(buf, arg->params[2].maxReceiveLifetime); + WSM_PUT32(buf, arg->params[1].maxReceiveLifetime); + WSM_PUT32(buf, arg->params[0].maxReceiveLifetime); + + ret = wsm_cmd_send(priv, buf, NULL, 0x0013, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_switch_channel(struct cw1200_common *priv, + const struct wsm_switch_channel *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_lock_tx(priv); + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->channelMode); + WSM_PUT8(buf, arg->channelSwitchCount); + WSM_PUT16(buf, arg->newChannelNumber); + + priv->channel_switch_in_progress = 1; + + ret = wsm_cmd_send(priv, buf, NULL, 0x0016, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + if (ret) { + wsm_unlock_tx(priv); + priv->channel_switch_in_progress = 0; + } + return ret; + +nomem: + wsm_cmd_unlock(priv); + wsm_unlock_tx(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->pmMode); + WSM_PUT8(buf, arg->fastPsmIdlePeriod); + WSM_PUT8(buf, arg->apPsmChangePeriod); + WSM_PUT8(buf, arg->minAutoPsPollPeriod); + + ret = wsm_cmd_send(priv, buf, NULL, 0x0010, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_start(struct cw1200_common *priv, const struct wsm_start *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->mode); + WSM_PUT8(buf, arg->band); + WSM_PUT16(buf, arg->channelNumber); + WSM_PUT32(buf, arg->CTWindow); + WSM_PUT32(buf, arg->beaconInterval); + WSM_PUT8(buf, arg->DTIMPeriod); + WSM_PUT8(buf, arg->preambleType); + WSM_PUT8(buf, arg->probeDelay); + WSM_PUT8(buf, arg->ssidLength); + WSM_PUT(buf, arg->ssid, sizeof(arg->ssid)); + WSM_PUT32(buf, arg->basicRateSet); + + priv->tx_burst_idx = -1; + ret = wsm_cmd_send(priv, buf, NULL, 0x0017, WSM_CMD_START_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_beacon_transmit(struct cw1200_common *priv, + const struct wsm_beacon_transmit *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT32(buf, arg->enableBeaconing ? 1 : 0); + + ret = wsm_cmd_send(priv, buf, NULL, 0x0018, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_start_find(struct cw1200_common *priv) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + ret = wsm_cmd_send(priv, buf, NULL, 0x0019, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; +} + +/* ******************************************************************** */ + +int wsm_stop_find(struct cw1200_common *priv) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + ret = wsm_cmd_send(priv, buf, NULL, 0x001A, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; +} + +/* ******************************************************************** */ + +int wsm_map_link(struct cw1200_common *priv, const struct wsm_map_link *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + u16 cmd = 0x001C | WSM_TX_LINK_ID(arg->link_id); + + wsm_cmd_lock(priv); + + WSM_PUT(buf, &arg->mac_addr[0], sizeof(arg->mac_addr)); + WSM_PUT16(buf, 0); + + ret = wsm_cmd_send(priv, buf, NULL, cmd, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_update_ie(struct cw1200_common *priv, + const struct wsm_update_ie *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT16(buf, arg->what); + WSM_PUT16(buf, arg->count); + WSM_PUT(buf, arg->ies, arg->length); + + ret = wsm_cmd_send(priv, buf, NULL, 0x001B, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; + +} + +/* ******************************************************************** */ +/* WSM indication events implementation */ + +static int wsm_startup_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + u16 status; + char fw_label[129]; + static const char * const fw_types[] = { + "ETF", + "WFM", + "WSM", + "HI test", + "Platform test" + }; + + priv->wsm_caps.numInpChBufs = WSM_GET16(buf); + priv->wsm_caps.sizeInpChBuf = WSM_GET16(buf); + priv->wsm_caps.hardwareId = WSM_GET16(buf); + priv->wsm_caps.hardwareSubId = WSM_GET16(buf); + status = WSM_GET16(buf); + priv->wsm_caps.firmwareCap = WSM_GET16(buf); + priv->wsm_caps.firmwareType = WSM_GET16(buf); + priv->wsm_caps.firmwareApiVer = WSM_GET16(buf); + priv->wsm_caps.firmwareBuildNumber = WSM_GET16(buf); + priv->wsm_caps.firmwareVersion = WSM_GET16(buf); + WSM_GET(buf, &fw_label[0], sizeof(fw_label) - 1); + fw_label[sizeof(fw_label) - 1] = 0; /* Do not trust FW too much. */ + + if (WARN_ON(status)) + return -EINVAL; + + if (WARN_ON(priv->wsm_caps.firmwareType > 4)) + return -EINVAL; + + printk(KERN_INFO "CW1200 WSM init done.\n" + " Input buffers: %d x %d bytes\n" + " Hardware: %d.%d\n" + " %s firmware [%s], ver: %d, build: %d," + " api: %d, cap: 0x%.4X\n", + priv->wsm_caps.numInpChBufs, priv->wsm_caps.sizeInpChBuf, + priv->wsm_caps.hardwareId, priv->wsm_caps.hardwareSubId, + fw_types[priv->wsm_caps.firmwareType], + &fw_label[0], priv->wsm_caps.firmwareVersion, + priv->wsm_caps.firmwareBuildNumber, + priv->wsm_caps.firmwareApiVer, priv->wsm_caps.firmwareCap); + + priv->wsm_caps.firmwareReady = 1; + + wake_up(&priv->wsm_startup_done); + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +static int wsm_receive_indication(struct cw1200_common *priv, + int link_id, + struct wsm_buf *buf, + struct sk_buff **skb_p) +{ + priv->rx_timestamp = jiffies; + if (priv->wsm_cbc.rx) { + struct wsm_rx rx; + struct ieee80211_hdr *hdr; + size_t hdr_len; + __le16 fctl; + + rx.status = WSM_GET32(buf); + rx.channelNumber = WSM_GET16(buf); + rx.rxedRate = WSM_GET8(buf); + rx.rcpiRssi = WSM_GET8(buf); + rx.flags = WSM_GET32(buf); + + /* FW Workaround: Drop probe resp or + beacon when RSSI is 0 */ + hdr = (struct ieee80211_hdr *) (*skb_p)->data; + + if (!rx.rcpiRssi && + (ieee80211_is_probe_resp(hdr->frame_control) || + ieee80211_is_beacon(hdr->frame_control))) + return 0; + + /* If no RSSI subscription has been made, + * convert RCPI to RSSI here */ + if (!priv->cqm_use_rssi) + rx.rcpiRssi = rx.rcpiRssi / 2 - 110; + + rx.link_id = link_id; + fctl = *(__le16 *)buf->data; + hdr_len = buf->data - buf->begin; + skb_pull(*skb_p, hdr_len); + if (!rx.status && unlikely(ieee80211_is_deauth(fctl))) { + if (priv->join_status == CW1200_JOIN_STATUS_STA) { + /* Shedule unjoin work */ + wsm_printk(KERN_DEBUG \ + "[WSM] Issue unjoin command (RX).\n"); + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, + &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); + } + } + priv->wsm_cbc.rx(priv, &rx, skb_p); + if (*skb_p) + skb_push(*skb_p, hdr_len); + } + return 0; + +underflow: + return -EINVAL; +} + +static int wsm_event_indication(struct cw1200_common *priv, struct wsm_buf *buf) +{ + int first; + struct cw1200_wsm_event *event; + + if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) { + /* STA is stopped. */ + return 0; + } + + event = kzalloc(sizeof(struct cw1200_wsm_event), GFP_KERNEL); + + event->evt.eventId = __le32_to_cpu(WSM_GET32(buf)); + event->evt.eventData = __le32_to_cpu(WSM_GET32(buf)); + + wsm_printk(KERN_DEBUG "[WSM] Event: %d(%d)\n", + event->evt.eventId, event->evt.eventData); + + spin_lock(&priv->event_queue_lock); + first = list_empty(&priv->event_queue); + list_add_tail(&event->link, &priv->event_queue); + spin_unlock(&priv->event_queue_lock); + + if (first) + queue_work(priv->workqueue, &priv->event_handler); + + return 0; + +underflow: + kfree(event); + return -EINVAL; +} + +static int wsm_channel_switch_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + wsm_unlock_tx(priv); /* Re-enable datapath */ + WARN_ON(WSM_GET32(buf)); + + priv->channel_switch_in_progress = 0; + wake_up(&priv->channel_switch_done); + + if (priv->wsm_cbc.channel_switch) + priv->wsm_cbc.channel_switch(priv); + return 0; + +underflow: + return -EINVAL; +} + +static int wsm_set_pm_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + return 0; +} + +static int wsm_scan_complete_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + if (priv->wsm_cbc.scan_complete) { + struct wsm_scan_complete arg; + arg.status = WSM_GET32(buf); + arg.psm = WSM_GET8(buf); + arg.numChannels = WSM_GET8(buf); + priv->wsm_cbc.scan_complete(priv, &arg); + } + return 0; + +underflow: + return -EINVAL; +} + +static int wsm_find_complete_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + /* TODO: Implement me. */ + STUB(); + return 0; +} + +static int wsm_suspend_resume_indication(struct cw1200_common *priv, + int link_id, struct wsm_buf *buf) +{ + if (priv->wsm_cbc.suspend_resume) { + u32 flags; + struct wsm_suspend_resume arg; + + flags = WSM_GET32(buf); + arg.link_id = link_id; + arg.stop = !(flags & 1); + arg.multicast = !!(flags & 8); + arg.queue = (flags >> 1) & 3; + + priv->wsm_cbc.suspend_resume(priv, &arg); + } + return 0; + +underflow: + return -EINVAL; +} + + +/* ******************************************************************** */ +/* WSM TX */ + +int wsm_cmd_send(struct cw1200_common *priv, + struct wsm_buf *buf, + void *arg, u16 cmd, long tmo) +{ + size_t buf_len = buf->data - buf->begin; + int ret; + + if (cmd == 0x0006) /* Write MIB */ + wsm_printk(KERN_DEBUG "[WSM] >>> 0x%.4X [MIB: 0x%.4X] (%d)\n", + cmd, __le16_to_cpu(((__le16 *)buf->begin)[2]), + buf_len); + else + wsm_printk(KERN_DEBUG "[WSM] >>> 0x%.4X (%d)\n", cmd, buf_len); + + /* Fill HI message header */ + /* BH will add sequence number */ + ((__le16 *)buf->begin)[0] = __cpu_to_le16(buf_len); + ((__le16 *)buf->begin)[1] = __cpu_to_le16(cmd); + + spin_lock(&priv->wsm_cmd.lock); + BUG_ON(priv->wsm_cmd.ptr); + priv->wsm_cmd.done = 0; + priv->wsm_cmd.ptr = buf->begin; + priv->wsm_cmd.len = buf_len; + priv->wsm_cmd.arg = arg; + priv->wsm_cmd.cmd = cmd; + spin_unlock(&priv->wsm_cmd.lock); + + cw1200_bh_wakeup(priv); + + if (unlikely(priv->bh_error)) { + /* Do not wait for timeout if BH is dead. Exit immediately. */ + ret = 0; + } else { + long rx_timestamp; + /* Firmware prioritizes data traffic over control confirm. + * Loop below checks if data was RXed and increases timeout + * accordingly. */ + do { + /* It's safe to use unprotected access to + * wsm_cmd.done here */ + ret = wait_event_timeout( + priv->wsm_cmd_wq, + priv->wsm_cmd.done, tmo); + rx_timestamp = jiffies - priv->rx_timestamp; + if (unlikely(rx_timestamp < 0)) + rx_timestamp = tmo + 1; + } while (!ret && rx_timestamp <= tmo); + } + + if (unlikely(ret == 0)) { + u16 raceCheck; + + spin_lock(&priv->wsm_cmd.lock); + raceCheck = priv->wsm_cmd.cmd; + priv->wsm_cmd.arg = NULL; + priv->wsm_cmd.ptr = NULL; + spin_unlock(&priv->wsm_cmd.lock); + + /* Race condition check to make sure _confirm is not called + * after exit of _send */ + if (raceCheck == 0xFFFF) { + /* If wsm_handle_rx got stuck in _confirm we will hang + * system there. It's better than silently currupt + * stack or heap, isn't it? */ + BUG_ON(wait_event_timeout( + priv->wsm_cmd_wq, priv->wsm_cmd.done, + WSM_CMD_LAST_CHANCE_TIMEOUT) <= 0); + } + + /* Kill BH thread to report the error to the top layer. */ + priv->bh_error = 1; + wake_up(&priv->bh_wq); + ret = -ETIMEDOUT; + } else { + spin_lock(&priv->wsm_cmd.lock); + BUG_ON(!priv->wsm_cmd.done); + ret = priv->wsm_cmd.ret; + spin_unlock(&priv->wsm_cmd.lock); + } + wsm_buf_reset(buf); + return ret; +} + +/* ******************************************************************** */ +/* WSM TX port control */ + +void wsm_lock_tx(struct cw1200_common *priv) +{ + wsm_cmd_lock(priv); + if (atomic_add_return(1, &priv->tx_lock) == 1) { + if (wsm_flush_tx(priv)) + wsm_printk(KERN_DEBUG "[WSM] TX is locked.\n"); + } + wsm_cmd_unlock(priv); +} + +void wsm_lock_tx_async(struct cw1200_common *priv) +{ + if (atomic_add_return(1, &priv->tx_lock) == 1) + wsm_printk(KERN_DEBUG "[WSM] TX is locked (async).\n"); +} + +bool wsm_flush_tx(struct cw1200_common *priv) +{ + unsigned long timestamp = jiffies; + bool pending = false; + long timeout; + int i; + + /* Flush must be called with TX lock held. */ + BUG_ON(!atomic_read(&priv->tx_lock)); + + /* First check if we really need to do something. + * It is safe to use unprotected access, as hw_bufs_used + * can only decrements. */ + if (!priv->hw_bufs_used) + return true; + + if (priv->bh_error) { + /* In case of failure do not wait for magic. */ + wsm_printk(KERN_ERR "[WSM] Fatal error occured, " + "will not flush TX.\n"); + return false; + } else { + /* Get a timestamp of "oldest" frame */ + for (i = 0; i < 4; ++i) + pending |= cw1200_queue_get_xmit_timestamp( + &priv->tx_queue[i], + ×tamp); + /* It is allowed to lock TX with only a command in the pipe. */ + if (!pending) + return true; + + timeout = timestamp + WSM_CMD_LAST_CHANCE_TIMEOUT - jiffies; + if (timeout < 0 || wait_event_timeout(priv->bh_evt_wq, + !priv->hw_bufs_used, + timeout) <= 0) { + /* Hmmm... Not good. Frame had stuck in firmware. */ + priv->bh_error = 1; + wake_up(&priv->bh_wq); + return false; + } + + /* Ok, everything is flushed. */ + return true; + } +} + +void wsm_unlock_tx(struct cw1200_common *priv) +{ + int tx_lock; + if (priv->bh_error) + wsm_printk(KERN_ERR "fatal error occured, unlock is unsafe\n"); + else { + tx_lock = atomic_sub_return(1, &priv->tx_lock); + if (tx_lock < 0) { + BUG_ON(1); + } else if (tx_lock == 0) { + cw1200_bh_wakeup(priv); + wsm_printk(KERN_DEBUG "[WSM] TX is unlocked.\n"); + } + } +} + +/* ******************************************************************** */ +/* WSM RX */ + +int wsm_handle_exception(struct cw1200_common *priv, u8 *data, size_t len) +{ + struct wsm_buf buf; + u32 reason; + u32 reg[18]; + char fname[48]; + size_t i; + + static const char * const reason_str[] = { + "undefined instruction", + "prefetch abort", + "data abort", + "unknown error", + }; + +#if defined(CONFIG_CW1200_USE_STE_EXTENSIONS) + /* Send the event upwards on the FW exception */ + cw1200_pm_stay_awake(&priv->pm_state, 3*HZ); + ieee80211_driver_hang_notify(priv->vif, GFP_KERNEL); +#endif + + buf.begin = buf.data = data; + buf.end = &buf.begin[len]; + + reason = WSM_GET32(&buf); + for (i = 0; i < ARRAY_SIZE(reg); ++i) + reg[i] = WSM_GET32(&buf); + WSM_GET(&buf, fname, sizeof(fname)); + + if (reason < 4) + wiphy_err(priv->hw->wiphy, + "Firmware exception: %s.\n", + reason_str[reason]); + else + wiphy_err(priv->hw->wiphy, + "Firmware assert at %.*s, line %d\n", + sizeof(fname), fname, reg[1]); + + for (i = 0; i < 12; i += 4) + wiphy_err(priv->hw->wiphy, + "R%d: 0x%.8X, R%d: 0x%.8X, R%d: 0x%.8X, R%d: 0x%.8X,\n", + i + 0, reg[i + 0], i + 1, reg[i + 1], + i + 2, reg[i + 2], i + 3, reg[i + 3]); + wiphy_err(priv->hw->wiphy, + "R12: 0x%.8X, SP: 0x%.8X, LR: 0x%.8X, PC: 0x%.8X,\n", + reg[i + 0], reg[i + 1], reg[i + 2], reg[i + 3]); + i += 4; + wiphy_err(priv->hw->wiphy, + "CPSR: 0x%.8X, SPSR: 0x%.8X\n", + reg[i + 0], reg[i + 1]); + + print_hex_dump_bytes("R1: ", DUMP_PREFIX_NONE, + fname, sizeof(fname)); + return 0; + +underflow: + wiphy_err(priv->hw->wiphy, + "Firmware exception.\n"); + print_hex_dump_bytes("Exception: ", DUMP_PREFIX_NONE, + data, len); + return -EINVAL; +} + +int wsm_handle_rx(struct cw1200_common *priv, int id, + struct wsm_hdr *wsm, struct sk_buff **skb_p) +{ + int ret = 0; + struct wsm_buf wsm_buf; + int link_id = (id >> 6) & 0x0F; + + /* Strip link id. */ + id &= ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX); + + wsm_buf.begin = (u8 *)&wsm[0]; + wsm_buf.data = (u8 *)&wsm[1]; + wsm_buf.end = &wsm_buf.begin[__le32_to_cpu(wsm->len)]; + + wsm_printk(KERN_DEBUG "[WSM] <<< 0x%.4X (%d)\n", id, + wsm_buf.end - wsm_buf.begin); + + if (id == 0x404) { + ret = wsm_tx_confirm(priv, &wsm_buf, link_id); + } else if (id == 0x41E) { + ret = wsm_multi_tx_confirm(priv, &wsm_buf, link_id); + } else if (id & 0x0400) { + void *wsm_arg; + u16 wsm_cmd; + + /* Do not trust FW too much. Protection against repeated + * response and race condition removal (see above). */ + spin_lock(&priv->wsm_cmd.lock); + wsm_arg = priv->wsm_cmd.arg; + wsm_cmd = priv->wsm_cmd.cmd & + ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX); + priv->wsm_cmd.cmd = 0xFFFF; + spin_unlock(&priv->wsm_cmd.lock); + + if (WARN_ON((id & ~0x0400) != wsm_cmd)) { + /* Note that any non-zero is a fatal retcode. */ + ret = -EINVAL; + goto out; + } + + switch (id) { + case 0x0409: + /* Note that wsm_arg can be NULL in case of timeout in + * wsm_cmd_send(). */ + if (likely(wsm_arg)) + ret = wsm_configuration_confirm(priv, wsm_arg, + &wsm_buf); + break; + case 0x0405: + if (likely(wsm_arg)) + ret = wsm_read_mib_confirm(priv, wsm_arg, + &wsm_buf); + break; + case 0x0406: + if (likely(wsm_arg)) + ret = wsm_write_mib_confirm(priv, wsm_arg, + &wsm_buf); + break; + case 0x040B: + if (likely(wsm_arg)) + ret = wsm_join_confirm(priv, wsm_arg, &wsm_buf); + break; + case 0x0407: /* start-scan */ + case 0x0408: /* stop-scan */ + case 0x040A: /* wsm_reset */ + case 0x040C: /* add_key */ + case 0x040D: /* remove_key */ + case 0x0410: /* wsm_set_pm */ + case 0x0411: /* set_bss_params */ + case 0x0412: /* set_tx_queue_params */ + case 0x0413: /* set_edca_params */ + case 0x0416: /* switch_channel */ + case 0x0417: /* start */ + case 0x0418: /* beacon_transmit */ + case 0x0419: /* start_find */ + case 0x041A: /* stop_find */ + case 0x041B: /* update_ie */ + case 0x041C: /* map_link */ + WARN_ON(wsm_arg != NULL); + ret = wsm_generic_confirm(priv, wsm_arg, &wsm_buf); + if (ret) + wiphy_warn(priv->hw->wiphy, + "wsm_generic_confirm " + "failed for request 0x%.4X.\n", + id & ~0x0400); + break; + default: + BUG_ON(1); + } + + spin_lock(&priv->wsm_cmd.lock); + priv->wsm_cmd.ret = ret; + priv->wsm_cmd.done = 1; + spin_unlock(&priv->wsm_cmd.lock); + ret = 0; /* Error response from device should ne stop BH. */ + + wake_up(&priv->wsm_cmd_wq); + } else if (id & 0x0800) { + switch (id) { + case 0x0801: + ret = wsm_startup_indication(priv, &wsm_buf); + break; + case 0x0804: + ret = wsm_receive_indication(priv, link_id, + &wsm_buf, skb_p); + break; + case 0x0805: + ret = wsm_event_indication(priv, &wsm_buf); + break; + case 0x080A: + ret = wsm_channel_switch_indication(priv, &wsm_buf); + break; + case 0x0809: + ret = wsm_set_pm_indication(priv, &wsm_buf); + break; + case 0x0806: + ret = wsm_scan_complete_indication(priv, &wsm_buf); + break; + case 0x080B: + ret = wsm_find_complete_indication(priv, &wsm_buf); + break; + case 0x080C: + ret = wsm_suspend_resume_indication(priv, + link_id, &wsm_buf); + break; + default: + STUB(); + } + } else { + WARN_ON(1); + ret = -EINVAL; + } +out: + return ret; +} + +static bool wsm_handle_tx_data(struct cw1200_common *priv, + const struct wsm_tx *wsm, + const struct ieee80211_tx_info *tx_info, + const struct cw1200_txpriv *txpriv, + struct cw1200_queue *queue) +{ + bool handled = false; + const struct ieee80211_hdr *frame = + (struct ieee80211_hdr *) &((u8 *)wsm)[txpriv->offset]; + __le16 fctl = frame->frame_control; + enum { + doProbe, + doDrop, + doJoin, + doOffchannel, + doWep, + doTx, + } action = doTx; + + switch (priv->mode) { + case NL80211_IFTYPE_STATION: + if (unlikely((priv->join_status == CW1200_JOIN_STATUS_STA) && + ieee80211_is_nullfunc(fctl))) { + spin_lock(&priv->bss_loss_lock); + if (priv->bss_loss_status == CW1200_BSS_LOSS_CHECKING) { + priv->bss_loss_status = + CW1200_BSS_LOSS_CONFIRMING; + priv->bss_loss_confirm_id = wsm->packetID; + } + spin_unlock(&priv->bss_loss_lock); + } else if (unlikely( + (priv->join_status <= CW1200_JOIN_STATUS_MONITOR) || + memcmp(frame->addr1, priv->join_bssid, + sizeof(priv->join_bssid)))) { + if (ieee80211_is_auth(fctl)) + action = doJoin; + else if (ieee80211_is_probe_req(fctl)) + action = doTx; + else if (priv->join_status >= + CW1200_JOIN_STATUS_MONITOR) + action = doTx; + else + action = doOffchannel; + } + break; + case NL80211_IFTYPE_AP: + if (unlikely(!priv->join_status)) + action = doDrop; + else if (unlikely(!(BIT(txpriv->raw_link_id) & + (BIT(0) | priv->link_id_map)))) { + wiphy_warn(priv->hw->wiphy, + "A frame with expired link id " + "is dropped.\n"); + action = doDrop; + } + if (cw1200_queue_get_generation(wsm->packetID) > + CW1200_MAX_REQUEUE_ATTEMPTS) { + /* HACK!!! WSM324 firmware has tendency to requeue + * multicast frames in a loop, causing performance + * drop and high power consumption of the driver. + * In this situation it is better just to drop + * the problematic frame. */ + wiphy_warn(priv->hw->wiphy, + "Too many attempts " + "to requeue a frame. " + "Frame is dropped.\n"); + action = doDrop; + } + break; + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + STUB(); + case NL80211_IFTYPE_MONITOR: + default: + action = doDrop; + break; + } + + if (action == doTx) { + if (unlikely(ieee80211_is_probe_req(fctl))) + action = doProbe; + else if ((fctl & __cpu_to_le32(IEEE80211_FCTL_PROTECTED)) && + tx_info->control.hw_key && + unlikely(tx_info->control.hw_key->keyidx != + priv->wep_default_key_id) && + (tx_info->control.hw_key->cipher == + WLAN_CIPHER_SUITE_WEP40 || + tx_info->control.hw_key->cipher == + WLAN_CIPHER_SUITE_WEP104)) + action = doWep; + } + + switch (action) { + case doProbe: + { + /* An interesting FW "feature". Device filters + * probe responses. + * The easiest way to get it back is to convert + * probe request into WSM start_scan command. */ + wsm_printk(KERN_DEBUG \ + "[WSM] Convert probe request to scan.\n"); + wsm_lock_tx_async(priv); + priv->pending_frame_id = __le32_to_cpu(wsm->packetID); + queue_delayed_work(priv->workqueue, + &priv->scan.probe_work, 0); + handled = true; + } + break; + case doDrop: + { + /* See detailed description of "join" below. + * We are dropping everything except AUTH in non-joined mode. */ + wsm_printk(KERN_DEBUG "[WSM] Drop frame (0x%.4X).\n", fctl); + BUG_ON(cw1200_queue_remove(queue, + __le32_to_cpu(wsm->packetID))); + handled = true; + } + break; + case doJoin: + { + /* There is one more interesting "feature" + * in FW: it can't do RX/TX before "join". + * "Join" here is not an association, + * but just a syncronization between AP and STA. + * priv->join_status is used only in bh thread and does + * not require protection */ + wsm_printk(KERN_DEBUG "[WSM] Issue join command.\n"); + wsm_lock_tx_async(priv); + priv->pending_frame_id = __le32_to_cpu(wsm->packetID); + if (queue_work(priv->workqueue, &priv->join_work) <= 0) + wsm_unlock_tx(priv); + handled = true; + } + break; + case doOffchannel: + { + wsm_printk(KERN_DEBUG "[WSM] Offchannel TX request.\n"); + wsm_lock_tx_async(priv); + priv->pending_frame_id = __le32_to_cpu(wsm->packetID); + if (queue_work(priv->workqueue, &priv->offchannel_work) <= 0) + wsm_unlock_tx(priv); + handled = true; + } + break; + case doWep: + { + wsm_printk(KERN_DEBUG "[WSM] Issue set_default_wep_key.\n"); + wsm_lock_tx_async(priv); + priv->wep_default_key_id = tx_info->control.hw_key->keyidx; + priv->pending_frame_id = __le32_to_cpu(wsm->packetID); + if (queue_work(priv->workqueue, &priv->wep_key_work) <= 0) + wsm_unlock_tx(priv); + handled = true; + } + break; + case doTx: + { +#if 0 + /* Kept for history. If you want to implement wsm->more, + * make sure you are able to send a frame after that. */ + wsm->more = (count > 1) ? 1 : 0; + if (wsm->more) { + /* HACK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * It's undocumented in WSM spec, but CW1200 hangs + * if 'more' is set and no TX is performed due to TX + * buffers limitation. */ + if (priv->hw_bufs_used + 1 == + priv->wsm_caps.numInpChBufs) + wsm->more = 0; + } + + /* BUG!!! FIXME: we can't use 'more' at all: we don't know + * future. It could be a request from upper layer with TX lock + * requirements (scan, for example). If "more" is set device + * will not send data and wsm_tx_lock() will fail... + * It's not obvious how to fix this deadlock. Any ideas? + * As a workaround more is set to 0. */ + wsm->more = 0; +#endif /* 0 */ + + if (ieee80211_is_deauth(fctl) && + priv->mode != NL80211_IFTYPE_AP) { + /* Shedule unjoin work */ + wsm_printk(KERN_DEBUG "[WSM] Issue unjoin command" + " (TX).\n"); +#if 0 + wsm->more = 0; +#endif /* 0 */ + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, + &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); + } + } + break; + } + return handled; +} + +static int cw1200_get_prio_queue(struct cw1200_common *priv, + u32 link_id_map, int *total) +{ + static const int urgent = BIT(CW1200_LINK_ID_AFTER_DTIM) | + BIT(CW1200_LINK_ID_UAPSD); + struct wsm_edca_queue_params *edca; + unsigned score, best = -1; + int winner = -1; + int queued; + int i; + + /* search for a winner using edca params */ + for (i = 0; i < 4; ++i) { + queued = cw1200_queue_get_num_queued(&priv->tx_queue[i], + link_id_map); + if (!queued) + continue; + *total += queued; + edca = &priv->edca.params[i]; + score = ((edca->aifns + edca->cwMin) << 16) + + (edca->cwMax - edca->cwMin) * + (random32() & 0xFFFF); + if (score < best && (winner < 0 || i != 3)) { + best = score; + winner = i; + } + } + + /* override winner if bursting */ + if (winner >= 0 && priv->tx_burst_idx >= 0 && + winner != priv->tx_burst_idx && + !cw1200_queue_get_num_queued( + &priv->tx_queue[winner], + link_id_map & urgent) && + cw1200_queue_get_num_queued( + &priv->tx_queue[priv->tx_burst_idx], + link_id_map)) + winner = priv->tx_burst_idx; + + return winner; +} + +static int wsm_get_tx_queue_and_mask(struct cw1200_common *priv, + struct cw1200_queue **queue_p, + u32 *tx_allowed_mask_p, + bool *more) +{ + int idx; + u32 tx_allowed_mask; + int total = 0; + + /* Search for a queue with multicast frames buffered */ + if (priv->tx_multicast) { + tx_allowed_mask = BIT(CW1200_LINK_ID_AFTER_DTIM); + idx = cw1200_get_prio_queue(priv, + tx_allowed_mask, &total); + if (idx >= 0) { + *more = total > 1; + goto found; + } + } + + /* Search for unicast traffic */ + tx_allowed_mask = ~priv->sta_asleep_mask; + tx_allowed_mask |= BIT(CW1200_LINK_ID_UAPSD); + if (priv->sta_asleep_mask) { + tx_allowed_mask |= priv->pspoll_mask; + tx_allowed_mask &= ~BIT(CW1200_LINK_ID_AFTER_DTIM); + } else { + tx_allowed_mask |= BIT(CW1200_LINK_ID_AFTER_DTIM); + } + idx = cw1200_get_prio_queue(priv, + tx_allowed_mask, &total); + if (idx < 0) + return -ENOENT; + +found: + *queue_p = &priv->tx_queue[idx]; + *tx_allowed_mask_p = tx_allowed_mask; + return 0; +} + +int wsm_get_tx(struct cw1200_common *priv, u8 **data, + size_t *tx_len, int *burst) +{ + struct wsm_tx *wsm = NULL; + struct ieee80211_tx_info *tx_info; + struct cw1200_queue *queue = NULL; + int queue_num; + u32 tx_allowed_mask = 0; + const struct cw1200_txpriv *txpriv = NULL; + /* + * Count was intended as an input for wsm->more flag. + * During implementation it was found that wsm->more + * is not usable, see details above. It is kept just + * in case you would like to try to implement it again. + */ + int count = 0; + + /* More is used only for broadcasts. */ + bool more = false; + + count = cw1200_itp_get_tx(priv, data, tx_len, burst); + if (count) + return count; + + if (priv->wsm_cmd.ptr) { + ++count; + spin_lock(&priv->wsm_cmd.lock); + BUG_ON(!priv->wsm_cmd.ptr); + *data = priv->wsm_cmd.ptr; + *tx_len = priv->wsm_cmd.len; + *burst = 1; + spin_unlock(&priv->wsm_cmd.lock); + } else { + for (;;) { + int ret; + + if (atomic_add_return(0, &priv->tx_lock)) + break; + + spin_lock_bh(&priv->ps_state_lock); + + ret = wsm_get_tx_queue_and_mask(priv, &queue, + &tx_allowed_mask, &more); + queue_num = queue - priv->tx_queue; + + if (priv->buffered_multicasts && + (ret || !more) && + (priv->tx_multicast || + !priv->sta_asleep_mask)) { + priv->buffered_multicasts = false; + if (priv->tx_multicast) { + priv->tx_multicast = false; + queue_work(priv->workqueue, + &priv->multicast_stop_work); + } + } + + spin_unlock_bh(&priv->ps_state_lock); + + if (ret) + break; + + if (cw1200_queue_get(queue, + tx_allowed_mask, + &wsm, &tx_info, &txpriv)) + continue; + + if (wsm_handle_tx_data(priv, wsm, + tx_info, txpriv, queue)) + continue; /* Handled by WSM */ + + wsm->hdr.id &= __cpu_to_le16( + ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX)); + wsm->hdr.id |= cpu_to_le16( + WSM_TX_LINK_ID(txpriv->raw_link_id)); + priv->pspoll_mask &= ~BIT(txpriv->raw_link_id); + + *data = (u8 *)wsm; + *tx_len = __le16_to_cpu(wsm->hdr.len); + + /* allow bursting if txop is set */ + if (priv->edca.params[queue_num].txOpLimit) + *burst = min(*burst, + (int)cw1200_queue_get_num_queued( + queue, tx_allowed_mask) + 1); + else + *burst = 1; + + /* store index of bursting queue */ + if (*burst > 1) + priv->tx_burst_idx = queue_num; + else + priv->tx_burst_idx = -1; + + if (more) { + struct ieee80211_hdr *hdr = + (struct ieee80211_hdr *) + &((u8 *)wsm)[txpriv->offset]; + /* more buffered multicast/broadcast frames + * ==> set MoreData flag in IEEE 802.11 header + * to inform PS STAs */ + hdr->frame_control |= + cpu_to_le16(IEEE80211_FCTL_MOREDATA); + } + + wsm_printk(KERN_DEBUG "[WSM] >>> 0x%.4X (%d) %p %c\n", + 0x0004, *tx_len, *data, + wsm->more ? 'M' : ' '); + ++count; + break; + } + } + + return count; +} + +void wsm_txed(struct cw1200_common *priv, u8 *data) +{ + if (data == priv->wsm_cmd.ptr) { + spin_lock(&priv->wsm_cmd.lock); + priv->wsm_cmd.ptr = NULL; + spin_unlock(&priv->wsm_cmd.lock); + } +} + +/* ******************************************************************** */ +/* WSM buffer */ + +void wsm_buf_init(struct wsm_buf *buf) +{ + BUG_ON(buf->begin); + buf->begin = kmalloc(SDIO_BLOCK_SIZE, GFP_KERNEL | GFP_DMA); + buf->end = buf->begin ? &buf->begin[SDIO_BLOCK_SIZE] : buf->begin; + wsm_buf_reset(buf); +} + +void wsm_buf_deinit(struct wsm_buf *buf) +{ + kfree(buf->begin); + buf->begin = buf->data = buf->end = NULL; +} + +static void wsm_buf_reset(struct wsm_buf *buf) +{ + if (buf->begin) { + buf->data = &buf->begin[4]; + *(u32 *)buf->begin = 0; + } else + buf->data = buf->begin; +} + +static int wsm_buf_reserve(struct wsm_buf *buf, size_t extra_size) +{ + size_t pos = buf->data - buf->begin; + size_t size = pos + extra_size; + + + if (size & (SDIO_BLOCK_SIZE - 1)) { + size &= SDIO_BLOCK_SIZE; + size += SDIO_BLOCK_SIZE; + } + + buf->begin = krealloc(buf->begin, size, GFP_KERNEL | GFP_DMA); + if (buf->begin) { + buf->data = &buf->begin[pos]; + buf->end = &buf->begin[size]; + return 0; + } else { + buf->end = buf->data = buf->begin; + return -ENOMEM; + } +} + + diff --git a/drivers/staging/cw1200/wsm.h b/drivers/staging/cw1200/wsm.h new file mode 100644 index 00000000000..c3bd002d432 --- /dev/null +++ b/drivers/staging/cw1200/wsm.h @@ -0,0 +1,1833 @@ +/* + * WSM host interface (HI) interface for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com> + * + * Based on CW1200 UMAC WSM API, which is + * Copyright (C) ST-Ericsson SA 2010 + * Author: Stewart Mathers <stewart.mathers@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef CW1200_WSM_H_INCLUDED +#define CW1200_WSM_H_INCLUDED + +#include <linux/spinlock.h> + +struct cw1200_common; + +/* Bands */ +/* Radio band 2.412 -2.484 GHz. */ +#define WSM_PHY_BAND_2_4G (0) + +/* Radio band 4.9375-5.8250 GHz. */ +#define WSM_PHY_BAND_5G (1) + +/* Transmit rates */ +/* 1 Mbps ERP-DSSS */ +#define WSM_TRANSMIT_RATE_1 (0) + +/* 2 Mbps ERP-DSSS */ +#define WSM_TRANSMIT_RATE_2 (1) + +/* 5.5 Mbps ERP-CCK, ERP-PBCC (Not supported) */ +/* #define WSM_TRANSMIT_RATE_5 (2) */ + +/* 11 Mbps ERP-CCK, ERP-PBCC (Not supported) */ +/* #define WSM_TRANSMIT_RATE_11 (3) */ + +/* 22 Mbps ERP-PBCC (Not supported) */ +/* #define WSM_TRANSMIT_RATE_22 (4) */ + +/* 33 Mbps ERP-PBCC (Not supported) */ +/* #define WSM_TRANSMIT_RATE_33 (5) */ + +/* 6 Mbps (3 Mbps) ERP-OFDM, BPSK coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_6 (6) + +/* 9 Mbps (4.5 Mbps) ERP-OFDM, BPSK coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_9 (7) + +/* 12 Mbps (6 Mbps) ERP-OFDM, QPSK coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_12 (8) + +/* 18 Mbps (9 Mbps) ERP-OFDM, QPSK coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_18 (9) + +/* 24 Mbps (12 Mbps) ERP-OFDM, 16QAM coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_24 (10) + +/* 36 Mbps (18 Mbps) ERP-OFDM, 16QAM coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_36 (11) + +/* 48 Mbps (24 Mbps) ERP-OFDM, 64QAM coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_48 (12) + +/* 54 Mbps (27 Mbps) ERP-OFDM, 64QAM coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_54 (13) + +/* 6.5 Mbps HT-OFDM, BPSK coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_HT_6 (14) + +/* 13 Mbps HT-OFDM, QPSK coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_HT_13 (15) + +/* 19.5 Mbps HT-OFDM, QPSK coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_HT_19 (16) + +/* 26 Mbps HT-OFDM, 16QAM coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_HT_26 (17) + +/* 39 Mbps HT-OFDM, 16QAM coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_HT_39 (18) + +/* 52 Mbps HT-OFDM, 64QAM coding rate 2/3 */ +#define WSM_TRANSMIT_RATE_HT_52 (19) + +/* 58.5 Mbps HT-OFDM, 64QAM coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_HT_58 (20) + +/* 65 Mbps HT-OFDM, 64QAM coding rate 5/6 */ +#define WSM_TRANSMIT_RATE_HT_65 (21) + +/* Scan types */ +/* Foreground scan */ +#define WSM_SCAN_TYPE_FOREGROUND (0) + +/* Background scan */ +#define WSM_SCAN_TYPE_BACKGROUND (1) + +/* Auto scan */ +#define WSM_SCAN_TYPE_AUTO (2) + +/* Scan flags */ +/* Forced background scan means if the station cannot */ +/* enter the power-save mode, it shall force to perform a */ +/* background scan. Only valid when ScanType is */ +/* background scan. */ +#define WSM_SCAN_FLAG_FORCE_BACKGROUND (BIT(0)) + +/* The WLAN device scans one channel at a time so */ +/* that disturbance to the data traffic is minimized. */ +#define WSM_SCAN_FLAG_SPLIT_METHOD (BIT(1)) + +/* Preamble Type. Long if not set. */ +#define WSM_SCAN_FLAG_SHORT_PREAMBLE (BIT(2)) + +/* 11n Tx Mode. Mixed if not set. */ +#define WSM_SCAN_FLAG_11N_GREENFIELD (BIT(3)) + +/* Scan constraints */ +/* Maximum number of channels to be scanned. */ +#define WSM_SCAN_MAX_NUM_OF_CHANNELS (48) + +/* The maximum number of SSIDs that the device can scan for. */ +#define WSM_SCAN_MAX_NUM_OF_SSIDS (2) + +/* Power management modes */ +/* 802.11 Active mode */ +#define WSM_PSM_ACTIVE (0) + +/* 802.11 PS mode */ +#define WSM_PSM_PS BIT(0) + +/* Fast Power Save bit */ +#define WSM_PSM_FAST_PS_FLAG BIT(7) + +/* Dynamic aka Fast power save */ +#define WSM_PSM_FAST_PS (BIT(0) | BIT(7)) + +/* Undetermined */ +/* Note : Undetermined status is reported when the */ +/* NULL data frame used to advertise the PM mode to */ +/* the AP at Pre or Post Background Scan is not Acknowledged */ +#define WSM_PSM_UNKNOWN BIT(1) + +/* Queue IDs */ +/* best effort/legacy */ +#define WSM_QUEUE_BEST_EFFORT (0) + +/* background */ +#define WSM_QUEUE_BACKGROUND (1) + +/* video */ +#define WSM_QUEUE_VIDEO (2) + +/* voice */ +#define WSM_QUEUE_VOICE (3) + +/* HT TX parameters */ +/* Non-HT */ +#define WSM_HT_TX_NON_HT (0) + +/* Mixed format */ +#define WSM_HT_TX_MIXED (1) + +/* Greenfield format */ +#define WSM_HT_TX_GREENFIELD (2) + +/* STBC allowed */ +#define WSM_HT_TX_STBC (BIT(7)) + +/* EPTA prioirty flags for BT Coex */ +/* default epta priority */ +#define WSM_EPTA_PRIORITY_DEFAULT 4 +/* use for normal data */ +#define WSM_EPTA_PRIORITY_DATA 4 +/* use for connect/disconnect/roaming*/ +#define WSM_EPTA_PRIORITY_MGT 5 +/* use for action frames */ +#define WSM_EPTA_PRIORITY_ACTION 5 +/* use for AC_VI data */ +#define WSM_EPTA_PRIORITY_VIDEO 5 +/* use for AC_VO data */ +#define WSM_EPTA_PRIORITY_VOICE 6 +/* use for EAPOL exchange */ +#define WSM_EPTA_PRIORITY_EAPOL 7 + +/* TX status */ +/* Frame was sent aggregated */ +/* Only valid for WSM_SUCCESS status. */ +#define WSM_TX_STATUS_AGGREGATION (BIT(0)) + +/* Host should requeue this frame later. */ +/* Valid only when status is WSM_REQUEUE. */ +#define WSM_TX_STATUS_REQUEUE (BIT(1)) + +/* Normal Ack */ +#define WSM_TX_STATUS_NORMAL_ACK (0<<2) + +/* No Ack */ +#define WSM_TX_STATUS_NO_ACK (1<<2) + +/* No explicit acknowledgement */ +#define WSM_TX_STATUS_NO_EXPLICIT_ACK (2<<2) + +/* Block Ack */ +/* Only valid for WSM_SUCCESS status. */ +#define WSM_TX_STATUS_BLOCK_ACK (3<<2) + +/* RX status */ +/* Unencrypted */ +#define WSM_RX_STATUS_UNENCRYPTED (0<<0) + +/* WEP */ +#define WSM_RX_STATUS_WEP (1<<0) + +/* TKIP */ +#define WSM_RX_STATUS_TKIP (2<<0) + +/* AES */ +#define WSM_RX_STATUS_AES (3<<0) + +/* WAPI */ +#define WSM_RX_STATUS_WAPI (4<<0) + +/* Macro to fetch encryption subfield. */ +#define WSM_RX_STATUS_ENCRYPTION(status) ((status) & 0x07) + +/* Frame was part of an aggregation */ +#define WSM_RX_STATUS_AGGREGATE (BIT(3)) + +/* Frame was first in the aggregation */ +#define WSM_RX_STATUS_AGGREGATE_FIRST (BIT(4)) + +/* Frame was last in the aggregation */ +#define WSM_RX_STATUS_AGGREGATE_LAST (BIT(5)) + +/* Indicates a defragmented frame */ +#define WSM_RX_STATUS_DEFRAGMENTED (BIT(6)) + +/* Indicates a Beacon frame */ +#define WSM_RX_STATUS_BEACON (BIT(7)) + +/* Indicates STA bit beacon TIM field */ +#define WSM_RX_STATUS_TIM (BIT(8)) + +/* Indicates Beacon frame's virtual bitmap contains multicast bit */ +#define WSM_RX_STATUS_MULTICAST (BIT(9)) + +/* Indicates frame contains a matching SSID */ +#define WSM_RX_STATUS_MATCHING_SSID (BIT(10)) + +/* Indicates frame contains a matching BSSI */ +#define WSM_RX_STATUS_MATCHING_BSSI (BIT(11)) + +/* Indicates More bit set in Framectl field */ +#define WSM_RX_STATUS_MORE_DATA (BIT(12)) + +/* Indicates frame received during a measurement process */ +#define WSM_RX_STATUS_MEASUREMENT (BIT(13)) + +/* Indicates frame received as an HT packet */ +#define WSM_RX_STATUS_HT (BIT(14)) + +/* Indicates frame received with STBC */ +#define WSM_RX_STATUS_STBC (BIT(15)) + +/* Indicates Address 1 field matches dot11StationId */ +#define WSM_RX_STATUS_ADDRESS1 (BIT(16)) + +/* Indicates Group address present in the Address 1 field */ +#define WSM_RX_STATUS_GROUP (BIT(17)) + +/* Indicates Broadcast address present in the Address 1 field */ +#define WSM_RX_STATUS_BROADCAST (BIT(18)) + +/* Indicates group key used with encrypted frames */ +#define WSM_RX_STATUS_GROUP_KEY (BIT(19)) + +/* Macro to fetch encryption key index. */ +#define WSM_RX_STATUS_KEY_IDX(status) (((status >> 20)) & 0x0F) + +/* Frame Control field starts at Frame offset + 2 */ +#define WSM_TX_2BYTES_SHIFT (BIT(7)) + +/* Join mode */ +/* IBSS */ +#define WSM_JOIN_MODE_IBSS (0) + +/* BSS */ +#define WSM_JOIN_MODE_BSS (1) + +/* PLCP preamble type */ +/* For long preamble */ +#define WSM_JOIN_PREAMBLE_LONG (0) + +/* For short preamble (Long for 1Mbps) */ +#define WSM_JOIN_PREAMBLE_SHORT (1) + +/* For short preamble (Long for 1 and 2Mbps) */ +#define WSM_JOIN_PREAMBLE_SHORT_2 (2) + +/* Join flags */ +/* Unsynchronized */ +#define WSM_JOIN_FLAGS_UNSYNCRONIZED BIT(0) +/* The BSS owner is a P2P GO */ +#define WSM_JOIN_FLAGS_P2P_GO BIT(1) +/* Force to join BSS with the BSSID and the + * SSID specified without waiting for beacons. The + * ProbeForJoin parameter is ignored. */ +#define WSM_JOIN_FLAGS_FORCE BIT(2) +/* Give probe request/response higher + * priority over the BT traffic */ +#define WSM_JOIN_FLAGS_PRIO BIT(3) + +/* Key types */ +#define WSM_KEY_TYPE_WEP_DEFAULT (0) +#define WSM_KEY_TYPE_WEP_PAIRWISE (1) +#define WSM_KEY_TYPE_TKIP_GROUP (2) +#define WSM_KEY_TYPE_TKIP_PAIRWISE (3) +#define WSM_KEY_TYPE_AES_GROUP (4) +#define WSM_KEY_TYPE_AES_PAIRWISE (5) +#define WSM_KEY_TYPE_WAPI_GROUP (6) +#define WSM_KEY_TYPE_WAPI_PAIRWISE (7) + +/* Key indexes */ +#define WSM_KEY_MAX_INDEX (10) + +/* ACK policy */ +#define WSM_ACK_POLICY_NORMAL (0) +#define WSM_ACK_POLICY_NO_ACK (1) + +/* Start modes */ +#define WSM_START_MODE_AP (0) /* Mini AP */ +#define WSM_START_MODE_P2P_GO (1) /* P2P GO */ +#define WSM_START_MODE_P2P_DEV (2) /* P2P device */ + +/* SetAssociationMode MIB flags */ +#define WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE (BIT(0)) +#define WSM_ASSOCIATION_MODE_USE_HT_MODE (BIT(1)) +#define WSM_ASSOCIATION_MODE_USE_BASIC_RATE_SET (BIT(2)) +#define WSM_ASSOCIATION_MODE_USE_MPDU_START_SPACING (BIT(3)) +#define WSM_ASSOCIATION_MODE_SNOOP_ASSOC_FRAMES (BIT(4)) + +/* RcpiRssiThreshold MIB flags */ +#define WSM_RCPI_RSSI_THRESHOLD_ENABLE (BIT(0)) +#define WSM_RCPI_RSSI_USE_RSSI (BIT(1)) +#define WSM_RCPI_RSSI_DONT_USE_UPPER (BIT(2)) +#define WSM_RCPI_RSSI_DONT_USE_LOWER (BIT(3)) + +/* Update-ie constants */ +#define WSM_UPDATE_IE_BEACON (BIT(0)) +#define WSM_UPDATE_IE_PROBE_RESP (BIT(1)) +#define WSM_UPDATE_IE_PROBE_REQ (BIT(2)) + +/* WSM events */ +/* Error */ +#define WSM_EVENT_ERROR (0) + +/* BSS lost */ +#define WSM_EVENT_BSS_LOST (1) + +/* BSS regained */ +#define WSM_EVENT_BSS_REGAINED (2) + +/* Radar detected */ +#define WSM_EVENT_RADAR_DETECTED (3) + +/* RCPI or RSSI threshold triggered */ +#define WSM_EVENT_RCPI_RSSI (4) + +/* BT inactive */ +#define WSM_EVENT_BT_INACTIVE (5) + +/* BT active */ +#define WSM_EVENT_BT_ACTIVE (6) + +/* MIB IDs */ +/* 4.1 dot11StationId */ +#define WSM_MIB_ID_DOT11_STATION_ID 0x0000 + +/* 4.2 dot11MaxtransmitMsduLifeTime */ +#define WSM_MIB_ID_DOT11_MAX_TRANSMIT_LIFTIME 0x0001 + +/* 4.3 dot11MaxReceiveLifeTime */ +#define WSM_MIB_ID_DOT11_MAX_RECEIVE_LIFETIME 0x0002 + +/* 4.4 dot11SlotTime */ +#define WSM_MIB_ID_DOT11_SLOT_TIME 0x0003 + +/* 4.5 dot11GroupAddressesTable */ +#define WSM_MIB_ID_DOT11_GROUP_ADDRESSES_TABLE 0x0004 +#define WSM_MAX_GRP_ADDRTABLE_ENTRIES 8 + +/* 4.6 dot11WepDefaultKeyId */ +#define WSM_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID 0x0005 + +/* 4.7 dot11CurrentTxPowerLevel */ +#define WSM_MIB_ID_DOT11_CURRENT_TX_POWER_LEVEL 0x0006 + +/* 4.8 dot11RTSThreshold */ +#define WSM_MIB_ID_DOT11_RTS_THRESHOLD 0x0007 + +/* 4.9 NonErpProtection */ +#define WSM_MIB_ID_NON_ERP_PROTECTION 0x1000 + +/* 4.10 ArpIpAddressesTable */ +#define WSM_MIB_ID_ARP_IP_ADDRESSES_TABLE 0x1001 +#define WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES 1 + +/* 4.11 TemplateFrame */ +#define WSM_MIB_ID_TEMPLATE_FRAME 0x1002 + +/* 4.12 RxFilter */ +#define WSM_MIB_ID_RX_FILTER 0x1003 + +/* 4.13 BeaconFilterTable */ +#define WSM_MIB_ID_BEACON_FILTER_TABLE 0x1004 + +/* 4.14 BeaconFilterEnable */ +#define WSM_MIB_ID_BEACON_FILTER_ENABLE 0x1005 + +/* 4.15 OperationalPowerMode */ +#define WSM_MIB_ID_OPERATIONAL_POWER_MODE 0x1006 + +/* 4.16 BeaconWakeUpPeriod */ +#define WSM_MIB_ID_BEACON_WAKEUP_PERIOD 0x1007 + +/* 4.17 RcpiRssiThreshold */ +#define WSM_MIB_ID_RCPI_RSSI_THRESHOLD 0x1009 + +/* 4.18 StatisticsTable */ +#define WSM_MIB_ID_STATISTICS_TABLE 0x100A + +/* 4.19 IbssPsConfig */ +#define WSM_MIB_ID_IBSS_PS_CONFIG 0x100B + +/* 4.20 CountersTable */ +#define WSM_MIB_ID_COUNTERS_TABLE 0x100C + +/* 4.21 BlockAckPolicy */ +#define WSM_MIB_ID_BLOCK_ACK_POLICY 0x100E + +/* 4.22 OverrideInternalTxRate */ +#define WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE 0x100F + +/* 4.23 SetAssociationMode */ +#define WSM_MIB_ID_SET_ASSOCIATION_MODE 0x1010 + +/* 4.24 UpdateEptaConfigData */ +#define WSM_MIB_ID_UPDATE_EPTA_CONFIG_DATA 0x1011 + +/* 4.25 SelectCcaMethod */ +#define WSM_MIB_ID_SELECT_CCA_METHOD 0x1012 + +/* 4.26 SetUpasdInformation */ +#define WSM_MIB_ID_SET_UAPSD_INFORMATION 0x1013 + +/* 4.27 SetAutoCalibrationMode WBF00004073 */ +#define WSM_MIB_ID_SET_AUTO_CALIBRATION_MODE 0x1015 + +/* 4.28 SetTxRateRetryPolicy */ +#define WSM_MIB_ID_SET_TX_RATE_RETRY_POLICY 0x1016 + +/* 4.29 SetHostMessageTypeFilter */ +#define WSM_MIB_ID_SET_HOST_MSG_TYPE_FILTER 0x1017 + +/* 4.30 P2PFindInfo */ +#define WSM_MIB_ID_P2P_FIND_INFO 0x1018 + +/* 4.31 P2PPsModeInfo */ +#define WSM_MIB_ID_P2P_PS_MODE_INFO 0x1019 + +/* 4.32 SetEtherTypeDataFrameFilter */ +#define WSM_MIB_ID_SET_ETHERTYPE_DATAFRAME_FILTER 0x101A + +/* 4.33 SetUDPPortDataFrameFilter */ +#define WSM_MIB_ID_SET_UDPPORT_DATAFRAME_FILTER 0x101B + +/* 4.34 SetMagicDataFrameFilter */ +#define WSM_MIB_ID_SET_MAGIC_DATAFRAME_FILTER 0x101C + +/* This is the end of specification. */ + +/* 4.35 P2PDeviceInfo */ +#define WSM_MIB_ID_P2P_DEVICE_INFO 0x101D + +/* 4.36 SetWCDMABand */ +#define WSM_MIB_ID_SET_WCDMA_BAND 0x101E + +/* 4.37 GroupTxSequenceCounter */ +#define WSM_MIB_ID_GRP_SEQ_COUNTER 0x101F + +/* 4.38 ProtectedMgmtPolicy */ +#define WSM_MIB_ID_PROTECTED_MGMT_POLICY 0x1020 + +/* 4.39 SetHtProtection */ +#define WSM_MID_ID_SET_HT_PROTECTION 0x1021 + +/* 4.40 GPIO Command */ +#define WSM_MIB_ID_GPIO_COMMAND 0x1022 + +/* 4.41 TSF Counter Value */ +#define WSM_MIB_ID_TSF_COUNTER 0x1023 + +/* Test Purposes Only */ +#define WSM_MIB_ID_BLOCK_ACK_INFO 0x100D + +/* 4.42 UseMultiTxConfMessage */ +#define WSM_MIB_USE_MULTI_TX_CONF 0x1024 + +/* 4.43 Keep-alive period */ +#define WSM_MIB_ID_KEEP_ALIVE_PERIOD 0x1025 + +/* 4.44 Disable BSSID filter */ +#define WSM_MIB_ID_DISABLE_BSSID_FILTER 0x1026 + +/* Frame template types */ +#define WSM_FRAME_TYPE_PROBE_REQUEST (0) +#define WSM_FRAME_TYPE_BEACON (1) +#define WSM_FRAME_TYPE_NULL (2) +#define WSM_FRAME_TYPE_QOS_NULL (3) +#define WSM_FRAME_TYPE_PS_POLL (4) +#define WSM_FRAME_TYPE_PROBE_RESPONSE (5) + +#define WSM_FRAME_GREENFIELD (0x80) /* See 4.11 */ + +/* Status */ +/* The WSM firmware has completed a request */ +/* successfully. */ +#define WSM_STATUS_SUCCESS (0) + +/* This is a generic failure code if other error codes do */ +/* not apply. */ +#define WSM_STATUS_FAILURE (1) + +/* A request contains one or more invalid parameters. */ +#define WSM_INVALID_PARAMETER (2) + +/* The request cannot perform because the device is in */ +/* an inappropriate mode. */ +#define WSM_ACCESS_DENIED (3) + +/* The frame received includes a decryption error. */ +#define WSM_STATUS_DECRYPTFAILURE (4) + +/* A MIC failure is detected in the received packets. */ +#define WSM_STATUS_MICFAILURE (5) + +/* The transmit request failed due to retry limit being */ +/* exceeded. */ +#define WSM_STATUS_RETRY_EXCEEDED (6) + +/* The transmit request failed due to MSDU life time */ +/* being exceeded. */ +#define WSM_STATUS_TX_LIFETIME_EXCEEDED (7) + +/* The link to the AP is lost. */ +#define WSM_STATUS_LINK_LOST (8) + +/* No key was found for the encrypted frame */ +#define WSM_STATUS_NO_KEY_FOUND (9) + +/* Jammer was detected when transmitting this frame */ +#define WSM_STATUS_JAMMER_DETECTED (10) + +/* The message should be requeued later. */ +/* This is applicable only to Transmit */ +#define WSM_REQUEUE (11) + +/* Advanced filtering options */ +#define WSM_MAX_FILTER_ELEMENTS (4) + +#define WSM_FILTER_ACTION_IGNORE (0) +#define WSM_FILTER_ACTION_FILTER_IN (1) +#define WSM_FILTER_ACTION_FILTER_OUT (2) + +#define WSM_FILTER_PORT_TYPE_DST (0) +#define WSM_FILTER_PORT_TYPE_SRC (1) + + + +struct wsm_hdr { + __le16 len; + __le16 id; +}; + +#define WSM_TX_SEQ_MAX (7) +#define WSM_TX_SEQ(seq) \ + ((seq & WSM_TX_SEQ_MAX) << 13) +#define WSM_TX_LINK_ID_MAX (0x0F) +#define WSM_TX_LINK_ID(link_id) \ + ((link_id & WSM_TX_LINK_ID_MAX) << 6) + +#define MAX_BEACON_SKIP_TIME_MS 1000 + +#define WSM_CMD_LAST_CHANCE_TIMEOUT (HZ * 3 / 2) + +/* ******************************************************************** */ +/* WSM capcbility */ + +struct wsm_caps { + u16 numInpChBufs; + u16 sizeInpChBuf; + u16 hardwareId; + u16 hardwareSubId; + u16 firmwareCap; + u16 firmwareType; + u16 firmwareApiVer; + u16 firmwareBuildNumber; + u16 firmwareVersion; + int firmwareReady; +}; + +/* ******************************************************************** */ +/* WSM commands */ + +struct wsm_tx_power_range { + int min_power_level; + int max_power_level; + u32 stepping; +}; + +/* 3.1 */ +struct wsm_configuration { + /* [in] */ u32 dot11MaxTransmitMsduLifeTime; + /* [in] */ u32 dot11MaxReceiveLifeTime; + /* [in] */ u32 dot11RtsThreshold; + /* [in, out] */ u8 *dot11StationId; + /* [in] */ const void *dpdData; + /* [in] */ size_t dpdData_size; + /* [out] */ u8 dot11FrequencyBandsSupported; + /* [out] */ u32 supportedRateMask; + /* [out] */ struct wsm_tx_power_range txPowerRange[2]; +}; + +int wsm_configuration(struct cw1200_common *priv, + struct wsm_configuration *arg); + +/* 3.3 */ +struct wsm_reset { + /* [in] */ int link_id; + /* [in] */ bool reset_statistics; +}; + +int wsm_reset(struct cw1200_common *priv, const struct wsm_reset *arg); + +/* 3.5 */ +int wsm_read_mib(struct cw1200_common *priv, u16 mibId, void *buf, + size_t buf_size); + +/* 3.7 */ +int wsm_write_mib(struct cw1200_common *priv, u16 mibId, void *buf, + size_t buf_size); + +/* 3.9 */ +struct wsm_ssid { + u8 ssid[32]; + u32 length; +}; + +struct wsm_scan_ch { + u16 number; + u32 minChannelTime; + u32 maxChannelTime; + u32 txPowerLevel; +}; + +/* 3.13 */ +struct wsm_scan_complete { + /* WSM_STATUS_... */ + u32 status; + + /* WSM_PSM_... */ + u8 psm; + + /* Number of channels that the scan operation completed. */ + u8 numChannels; +}; + +typedef void (*wsm_scan_complete_cb) (struct cw1200_common *priv, + struct wsm_scan_complete *arg); + +/* 3.9 */ +struct wsm_scan { + /* WSM_PHY_BAND_... */ + /* [in] */ u8 band; + + /* WSM_SCAN_TYPE_... */ + /* [in] */ u8 scanType; + + /* WSM_SCAN_FLAG_... */ + /* [in] */ u8 scanFlags; + + /* WSM_TRANSMIT_RATE_... */ + /* [in] */ u8 maxTransmitRate; + + /* Interval period in TUs that the device shall the re- */ + /* execute the requested scan. Max value supported by the device */ + /* is 256s. */ + /* [in] */ u32 autoScanInterval; + + /* Number of probe requests (per SSID) sent to one (1) */ + /* channel. Zero (0) means that none is send, which */ + /* means that a passive scan is to be done. Value */ + /* greater than zero (0) means that an active scan is to */ + /* be done. */ + /* [in] */ u32 numOfProbeRequests; + + /* Number of channels to be scanned. */ + /* Maximum value is WSM_SCAN_MAX_NUM_OF_CHANNELS. */ + /* [in] */ u8 numOfChannels; + + /* Number of SSID provided in the scan command (this */ + /* is zero (0) in broadcast scan) */ + /* The maximum number of SSIDs is WSM_SCAN_MAX_NUM_OF_SSIDS. */ + /* [in] */ u8 numOfSSIDs; + + /* The delay time (in microseconds) period */ + /* before sending a probe-request. */ + /* [in] */ u8 probeDelay; + + /* SSIDs to be scanned [numOfSSIDs]; */ + /* [in] */ struct wsm_ssid *ssids; + + /* Channels to be scanned [numOfChannels]; */ + /* [in] */ struct wsm_scan_ch *ch; +}; + +int wsm_scan(struct cw1200_common *priv, const struct wsm_scan *arg); + +/* 3.11 */ +int wsm_stop_scan(struct cw1200_common *priv); + +/* 3.14 */ +struct wsm_tx_confirm { + /* Packet identifier used in wsm_tx. */ + /* [out] */ u32 packetID; + + /* WSM_STATUS_... */ + /* [out] */ u32 status; + + /* WSM_TRANSMIT_RATE_... */ + /* [out] */ u8 txedRate; + + /* The number of times the frame was transmitted */ + /* without receiving an acknowledgement. */ + /* [out] */ u8 ackFailures; + + /* WSM_TX_STATUS_... */ + /* [out] */ u16 flags; + + /* The total time in microseconds that the frame spent in */ + /* the WLAN device before transmission as completed. */ + /* [out] */ u32 mediaDelay; + + /* The total time in microseconds that the frame spent in */ + /* the WLAN device before transmission was started. */ + /* [out] */ u32 txQueueDelay; + + /* [out]*/ u32 link_id; +}; + +/* 3.15 */ +typedef void (*wsm_tx_confirm_cb) (struct cw1200_common *priv, + struct wsm_tx_confirm *arg); + +/* Note that ideology of wsm_tx struct is different against the rest of + * WSM API. wsm_hdr is /not/ a caller-adapted struct to be used as an input + * argument for WSM call, but a prepared bytestream to be sent to firmware. + * It is filled partly in cw1200_tx, partly in low-level WSM code. + * Please pay attention once again: ideology is different. + * + * Legend: + * - [in]: cw1200_tx must fill this field. + * - [wsm]: the field is filled by low-level WSM. + */ +struct wsm_tx { + /* common WSM header */ + /* [in/wsm] */ struct wsm_hdr hdr; + + /* Packet identifier that meant to be used in completion. */ + /* [in] */ __le32 packetID; + + /* WSM_TRANSMIT_RATE_... */ + /* [in] */ u8 maxTxRate; + + /* WSM_QUEUE_... */ + /* [in] */ u8 queueId; + + /* True: another packet is pending on the host for transmission. */ + /* [wsm] */ u8 more; + + /* Bit 0 = 0 - Start expiry time from first Tx attempt (default) */ + /* Bit 0 = 1 - Start expiry time from receipt of Tx Request */ + /* Bits 3:1 - PTA Priority */ + /* Bits 6:4 - Tx Rate Retry Policy */ + /* Bit 7 - Reserved */ + /* [in] */ u8 flags; + + /* Should be 0. */ + /* [in] */ __le32 reserved; + + /* The elapsed time in TUs, after the initial transmission */ + /* of an MSDU, after which further attempts to transmit */ + /* the MSDU shall be terminated. Overrides the global */ + /* dot11MaxTransmitMsduLifeTime setting [optional] */ + /* Device will set the default value if this is 0. */ + /* [wsm] */ __le32 expireTime; + + /* WSM_HT_TX_... */ + /* [in] */ __le32 htTxParameters; +}; + +/* = sizeof(generic hi hdr) + sizeof(wsm hdr) + sizeof(alignment) */ +#define WSM_TX_EXTRA_HEADROOM (28) + +/* 3.16 */ +struct wsm_rx { + /* WSM_STATUS_... */ + /* [out] */ u32 status; + + /* Specifies the channel of the received packet. */ + /* [out] */ u16 channelNumber; + + /* WSM_TRANSMIT_RATE_... */ + /* [out] */ u8 rxedRate; + + /* This value is expressed in signed Q8.0 format for */ + /* RSSI and unsigned Q7.1 format for RCPI. */ + /* [out] */ u8 rcpiRssi; + + /* WSM_RX_STATUS_... */ + /* [out] */ u32 flags; + + /* An 802.11 frame. */ + /* [out] */ void *frame; + + /* Size of the frame */ + /* [out] */ size_t frame_size; + + /* Link ID */ + /* [out] */ int link_id; +}; + +/* = sizeof(generic hi hdr) + sizeof(wsm hdr) */ +#define WSM_RX_EXTRA_HEADROOM (16) + +typedef void (*wsm_rx_cb) (struct cw1200_common *priv, struct wsm_rx *arg, + struct sk_buff **skb_p); + +/* 3.17 */ +struct wsm_event { + /* WSM_STATUS_... */ + /* [out] */ u32 eventId; + + /* Indication parameters. */ + /* For error indication, this shall be a 32-bit WSM status. */ + /* For RCPI or RSSI indication, this should be an 8-bit */ + /* RCPI or RSSI value. */ + /* [out] */ u32 eventData; +}; + +struct cw1200_wsm_event { + struct list_head link; + struct wsm_event evt; +}; + +/* 3.18 - 3.22 */ +/* Measurement. Skipped for now. Irrelevent. */ + +typedef void (*wsm_event_cb) (struct cw1200_common *priv, + struct wsm_event *arg); + +/* 3.23 */ +struct wsm_join { + /* WSM_JOIN_MODE_... */ + /* [in] */ u8 mode; + + /* WSM_PHY_BAND_... */ + /* [in] */ u8 band; + + /* Specifies the channel number to join. The channel */ + /* number will be mapped to an actual frequency */ + /* according to the band */ + /* [in] */ u16 channelNumber; + + /* Specifies the BSSID of the BSS or IBSS to be joined */ + /* or the IBSS to be started. */ + /* [in] */ u8 bssid[6]; + + /* ATIM window of IBSS */ + /* When ATIM window is zero the initiated IBSS does */ + /* not support power saving. */ + /* [in] */ u16 atimWindow; + + /* WSM_JOIN_PREAMBLE_... */ + /* [in] */ u8 preambleType; + + /* Specifies if a probe request should be send with the */ + /* specified SSID when joining to the network. */ + /* [in] */ u8 probeForJoin; + + /* DTIM Period (In multiples of beacon interval) */ + /* [in] */ u8 dtimPeriod; + + /* WSM_JOIN_FLAGS_... */ + /* [in] */ u8 flags; + + /* Length of the SSID */ + /* [in] */ u32 ssidLength; + + /* Specifies the SSID of the IBSS to join or start */ + /* [in] */ u8 ssid[32]; + + /* Specifies the time between TBTTs in TUs */ + /* [in] */ u32 beaconInterval; + + /* A bit mask that defines the BSS basic rate set. */ + /* [in] */ u32 basicRateSet; + + /* Minimum transmission power level in units of 0.1dBm */ + /* [out] */ int minPowerLevel; + + /* Maximum transmission power level in units of 0.1dBm */ + /* [out] */ int maxPowerLevel; +}; + +int wsm_join(struct cw1200_common *priv, struct wsm_join *arg); + +/* 3.25 */ +struct wsm_set_pm { + /* WSM_PSM_... */ + /* [in] */ u8 pmMode; + + /* in unit of 500us; 0 to use default */ + /* [in] */ u8 fastPsmIdlePeriod; + + /* in unit of 500us; 0 to use default */ + /* [in] */ u8 apPsmChangePeriod; + + /* in unit of 500us; 0 to disable auto-pspoll */ + /* [in] */ u8 minAutoPsPollPeriod; +}; + +int wsm_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg); + +/* 3.27 */ +struct wsm_set_pm_complete { + u8 psm; /* WSM_PSM_... */ +}; + +typedef void (*wsm_set_pm_complete_cb) (struct cw1200_common *priv, + struct wsm_set_pm_complete *arg); + +/* 3.28 */ +struct wsm_set_bss_params { + /* The number of lost consecutive beacons after which */ + /* the WLAN device should indicate the BSS-Lost event */ + /* to the WLAN host driver. */ + u8 beaconLostCount; + + /* The AID received during the association process. */ + u16 aid; + + /* The operational rate set mask */ + u32 operationalRateSet; +}; + +int wsm_set_bss_params(struct cw1200_common *priv, + const struct wsm_set_bss_params *arg); + +/* 3.30 */ +struct wsm_add_key { + u8 type; /* WSM_KEY_TYPE_... */ + u8 entryIndex; /* Key entry index: 0 -- WSM_KEY_MAX_INDEX */ + u16 reserved; + union { + struct { + u8 peerAddress[6]; /* MAC address of the + * peer station */ + u8 reserved; + u8 keyLength; /* Key length in bytes */ + u8 keyData[16]; /* Key data */ + } __packed wepPairwiseKey; + struct { + u8 keyId; /* Unique per key identifier + * (0..3) */ + u8 keyLength; /* Key length in bytes */ + u16 reserved; + u8 keyData[16]; /* Key data */ + } __packed wepGroupKey; + struct { + u8 peerAddress[6]; /* MAC address of the + * peer station */ + u8 reserved[2]; + u8 tkipKeyData[16]; /* TKIP key data */ + u8 rxMicKey[8]; /* Rx MIC key */ + u8 txMicKey[8]; /* Tx MIC key */ + } __packed tkipPairwiseKey; + struct { + u8 tkipKeyData[16]; /* TKIP key data */ + u8 rxMicKey[8]; /* Rx MIC key */ + u8 keyId; /* Key ID */ + u8 reserved[3]; + u8 rxSeqCounter[8]; /* Receive Sequence Counter */ + } __packed tkipGroupKey; + struct { + u8 peerAddress[6]; /* MAC address of the + * peer station */ + u16 reserved; + u8 aesKeyData[16]; /* AES key data */ + } __packed aesPairwiseKey; + struct { + u8 aesKeyData[16]; /* AES key data */ + u8 keyId; /* Key ID */ + u8 reserved[3]; + u8 rxSeqCounter[8]; /* Receive Sequence Counter */ + } __packed aesGroupKey; + struct { + u8 peerAddress[6]; /* MAC address of the + * peer station */ + u8 keyId; /* Key ID */ + u8 reserved; + u8 wapiKeyData[16]; /* WAPI key data */ + u8 micKeyData[16]; /* MIC key data */ + } __packed wapiPairwiseKey; + struct { + u8 wapiKeyData[16]; /* WAPI key data */ + u8 micKeyData[16]; /* MIC key data */ + u8 keyId; /* Key ID */ + u8 reserved[3]; + } __packed wapiGroupKey; + } __packed; +} __packed; + +int wsm_add_key(struct cw1200_common *priv, const struct wsm_add_key *arg); + +/* 3.32 */ +struct wsm_remove_key { + /* Key entry index : 0-10 */ + u8 entryIndex; +}; + +int wsm_remove_key(struct cw1200_common *priv, + const struct wsm_remove_key *arg); + +/* 3.34 */ +struct wsm_set_tx_queue_params { + /* WSM_ACK_POLICY_... */ + u8 ackPolicy; + + /* Medium Time of TSPEC (in 32us units) allowed per */ + /* One Second Averaging Period for this queue. */ + u16 allowedMediumTime; + + /* dot11MaxTransmitMsduLifetime to be used for the */ + /* specified queue. */ + u32 maxTransmitLifetime; +}; + +struct wsm_tx_queue_params { + /* NOTE: index is a linux queue id. */ + struct wsm_set_tx_queue_params params[4]; +}; + + +#define WSM_TX_QUEUE_SET(queue_params, queue, ack_policy, allowed_time,\ + max_life_time) \ +do { \ + struct wsm_set_tx_queue_params *p = &(queue_params)->params[queue]; \ + p->ackPolicy = (ack_policy); \ + p->allowedMediumTime = (allowed_time); \ + p->maxTransmitLifetime = (max_life_time); \ +} while (0) + +int wsm_set_tx_queue_params(struct cw1200_common *priv, + const struct wsm_set_tx_queue_params *arg, u8 id); + +/* 3.36 */ +struct wsm_edca_queue_params { + /* CWmin (in slots) for the access class. */ + /* [in] */ u16 cwMin; + + /* CWmax (in slots) for the access class. */ + /* [in] */ u16 cwMax; + + /* AIFS (in slots) for the access class. */ + /* [in] */ u8 aifns; + + /* TX OP Limit (in microseconds) for the access class. */ + /* [in] */ u16 txOpLimit; + + /* dot11MaxReceiveLifetime to be used for the specified */ + /* the access class. Overrides the global */ + /* dot11MaxReceiveLifetime value */ + /* [in] */ u32 maxReceiveLifetime; + + /* UAPSD trigger support for the access class. */ + /* [in] */ bool uapsdEnable; +}; + +struct wsm_edca_params { + /* NOTE: index is a linux queue id. */ + struct wsm_edca_queue_params params[4]; +}; + +#define TXOP_UNIT 32 +#define WSM_EDCA_SET(edca, queue, aifs, cw_min, cw_max, txop, life_time,\ + uapsd) \ + do { \ + struct wsm_edca_queue_params *p = &(edca)->params[queue]; \ + p->cwMin = (cw_min); \ + p->cwMax = (cw_max); \ + p->aifns = (aifs); \ + p->txOpLimit = ((txop) * TXOP_UNIT); \ + p->maxReceiveLifetime = (life_time); \ + p->uapsdEnable = (uapsd); \ + } while (0) + +int wsm_set_edca_params(struct cw1200_common *priv, + const struct wsm_edca_params *arg); + +int wsm_set_uapsd_param(struct cw1200_common *priv, + const struct wsm_edca_params *arg); + +/* 3.38 */ +/* Set-System info. Skipped for now. Irrelevent. */ + +/* 3.40 */ +struct wsm_switch_channel { + /* 1 - means the STA shall not transmit any further */ + /* frames until the channel switch has completed */ + /* [in] */ u8 channelMode; + + /* Number of TBTTs until channel switch occurs. */ + /* 0 - indicates switch shall occur at any time */ + /* 1 - occurs immediately before the next TBTT */ + /* [in] */ u8 channelSwitchCount; + + /* The new channel number to switch to. */ + /* Note this is defined as per section 2.7. */ + /* [in] */ u16 newChannelNumber; +}; + +int wsm_switch_channel(struct cw1200_common *priv, + const struct wsm_switch_channel *arg); + +typedef void (*wsm_channel_switch_cb) (struct cw1200_common *priv); + +struct wsm_start { + /* WSM_START_MODE_... */ + /* [in] */ u8 mode; + + /* WSM_PHY_BAND_... */ + /* [in] */ u8 band; + + /* Channel number */ + /* [in] */ u16 channelNumber; + + /* Client Traffic window in units of TU */ + /* Valid only when mode == ..._P2P */ + /* [in] */ u32 CTWindow; + + /* Interval between two consecutive */ + /* beacon transmissions in TU. */ + /* [in] */ u32 beaconInterval; + + /* DTIM period in terms of beacon intervals */ + /* [in] */ u8 DTIMPeriod; + + /* WSM_JOIN_PREAMBLE_... */ + /* [in] */ u8 preambleType; + + /* The delay time (in microseconds) period */ + /* before sending a probe-request. */ + /* [in] */ u8 probeDelay; + + /* Length of the SSID */ + /* [in] */ u8 ssidLength; + + /* SSID of the BSS or P2P_GO to be started now. */ + /* [in] */ u8 ssid[32]; + + /* The basic supported rates for the MiniAP. */ + /* [in] */ u32 basicRateSet; +}; + +int wsm_start(struct cw1200_common *priv, const struct wsm_start *arg); + +struct wsm_beacon_transmit { + /* 1: enable; 0: disable */ + /* [in] */ u8 enableBeaconing; +}; + +int wsm_beacon_transmit(struct cw1200_common *priv, + const struct wsm_beacon_transmit *arg); + +int wsm_start_find(struct cw1200_common *priv); + +int wsm_stop_find(struct cw1200_common *priv); + +typedef void (*wsm_find_complete_cb) (struct cw1200_common *priv, u32 status); + +struct wsm_suspend_resume { + /* See 3.52 */ + /* Link ID */ + /* [out] */ int link_id; + /* Stop sending further Tx requests down to device for this link */ + /* [out] */ bool stop; + /* Transmit multicast Frames */ + /* [out] */ bool multicast; + /* The AC on which Tx to be suspended /resumed. */ + /* This is applicable only for U-APSD */ + /* WSM_QUEUE_... */ + /* [out] */ int queue; +}; + +typedef void (*wsm_suspend_resume_cb) (struct cw1200_common *priv, + struct wsm_suspend_resume *arg); + +/* 3.54 Update-IE request. */ +struct wsm_update_ie { + /* WSM_UPDATE_IE_... */ + /* [in] */ u16 what; + /* [in] */ u16 count; + /* [in] */ u8 *ies; + /* [in] */ size_t length; +}; + +int wsm_update_ie(struct cw1200_common *priv, + const struct wsm_update_ie *arg); + +/* 3.56 */ +struct wsm_map_link { + /* MAC address of the remote device */ + /* [in] */ u8 mac_addr[6]; + /* [in] */ u8 link_id; +}; + +int wsm_map_link(struct cw1200_common *priv, const struct wsm_map_link *arg); + +struct wsm_cbc { + wsm_scan_complete_cb scan_complete; + wsm_tx_confirm_cb tx_confirm; + wsm_rx_cb rx; + wsm_event_cb event; + wsm_set_pm_complete_cb set_pm_complete; + wsm_channel_switch_cb channel_switch; + wsm_find_complete_cb find_complete; + wsm_suspend_resume_cb suspend_resume; +}; + +/* ******************************************************************** */ +/* MIB shortcats */ + +static inline int wsm_set_output_power(struct cw1200_common *priv, + int power_level) +{ + __le32 val = __cpu_to_le32(power_level); + return wsm_write_mib(priv, WSM_MIB_ID_DOT11_CURRENT_TX_POWER_LEVEL, + &val, sizeof(val)); +} + +static inline int wsm_set_beacon_wakeup_period(struct cw1200_common *priv, + unsigned dtim_interval, + unsigned listen_interval) +{ + struct { + u8 numBeaconPeriods; + u8 reserved; + __le16 listenInterval; + } val = { + dtim_interval, 0, __cpu_to_le16(listen_interval)}; + if (dtim_interval > 0xFF || listen_interval > 0xFFFF) + return -EINVAL; + else + return wsm_write_mib(priv, WSM_MIB_ID_BEACON_WAKEUP_PERIOD, + &val, sizeof(val)); +} + +struct wsm_rcpi_rssi_threshold { + u8 rssiRcpiMode; /* WSM_RCPI_RSSI_... */ + u8 lowerThreshold; + u8 upperThreshold; + u8 rollingAverageCount; +}; + +static inline int wsm_set_rcpi_rssi_threshold(struct cw1200_common *priv, + struct wsm_rcpi_rssi_threshold *arg) +{ + return wsm_write_mib(priv, WSM_MIB_ID_RCPI_RSSI_THRESHOLD, arg, + sizeof(*arg)); +} + +struct wsm_counters_table { + __le32 countPlcpErrors; + __le32 countFcsErrors; + __le32 countTxPackets; + __le32 countRxPackets; + __le32 countRxPacketErrors; + __le32 countRxDecryptionFailures; + __le32 countRxMicFailures; + __le32 countRxNoKeyFailures; + __le32 countTxMulticastFrames; + __le32 countTxFramesSuccess; + __le32 countTxFrameFailures; + __le32 countTxFramesRetried; + __le32 countTxFramesMultiRetried; + __le32 countRxFrameDuplicates; + __le32 countRtsSuccess; + __le32 countRtsFailures; + __le32 countAckFailures; + __le32 countRxMulticastFrames; + __le32 countRxFramesSuccess; + __le32 countRxCMACICVErrors; + __le32 countRxCMACReplays; + __le32 countRxMgmtCCMPReplays; +}; + +static inline int wsm_get_counters_table(struct cw1200_common *priv, + struct wsm_counters_table *arg) +{ + return wsm_read_mib(priv, WSM_MIB_ID_COUNTERS_TABLE, + arg, sizeof(*arg)); +} + +static inline int wsm_get_station_id(struct cw1200_common *priv, u8 *mac) +{ + return wsm_read_mib(priv, WSM_MIB_ID_DOT11_STATION_ID, mac, ETH_ALEN); +} + +struct wsm_rx_filter { + bool promiscuous; + bool bssid; + bool fcs; +}; + +static inline int wsm_set_rx_filter(struct cw1200_common *priv, + const struct wsm_rx_filter *arg) +{ + __le32 val = 0; + if (arg->promiscuous) + val |= __cpu_to_le32(BIT(0)); + if (arg->bssid) + val |= __cpu_to_le32(BIT(1)); + if (arg->fcs) + val |= __cpu_to_le32(BIT(2)); + return wsm_write_mib(priv, WSM_MIB_ID_RX_FILTER, &val, sizeof(val)); +} + +#define WSM_BEACON_FILTER_IE_HAS_CHANGED BIT(0) +#define WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT BIT(1) +#define WSM_BEACON_FILTER_IE_HAS_APPEARED BIT(2) + +struct wsm_beacon_filter_table_entry { + u8 ieId; + u8 actionFlags; + u8 oui[3]; + u8 matchData[3]; +} __packed; + +struct wsm_beacon_filter_table { + __le32 numOfIEs; + struct wsm_beacon_filter_table_entry entry[10]; +} __packed; + +static inline int wsm_set_beacon_filter_table(struct cw1200_common *priv, + struct wsm_beacon_filter_table *ft) +{ + size_t size = __le32_to_cpu(ft->numOfIEs) * + sizeof(struct wsm_beacon_filter_table_entry) + + sizeof(__le32); + + return wsm_write_mib(priv, WSM_MIB_ID_BEACON_FILTER_TABLE, ft, size); +} + +struct wsm_beacon_filter_control { + int enabled; + int bcn_count; +}; + +static inline int wsm_beacon_filter_control(struct cw1200_common *priv, + struct wsm_beacon_filter_control *arg) +{ + struct { + __le32 enabled; + __le32 bcn_count; + } val; + val.enabled = __cpu_to_le32(arg->enabled); + val.bcn_count = __cpu_to_le32(arg->bcn_count); + return wsm_write_mib(priv, WSM_MIB_ID_BEACON_FILTER_ENABLE, &val, + sizeof(val)); +} + +enum wsm_power_mode { + wsm_power_mode_active = 0, + wsm_power_mode_doze = 1, + wsm_power_mode_quiescent = 2, +}; + +struct wsm_operational_mode { + enum wsm_power_mode power_mode; + int disableMoreFlagUsage; + int performAntDiversity; +}; + +static inline int wsm_set_operational_mode(struct cw1200_common *priv, + const struct wsm_operational_mode *arg) +{ + u8 val = arg->power_mode; + if (arg->disableMoreFlagUsage) + val |= BIT(4); + if (arg->performAntDiversity) + val |= BIT(5); + return wsm_write_mib(priv, WSM_MIB_ID_OPERATIONAL_POWER_MODE, &val, + sizeof(val)); +} + +struct wsm_template_frame { + u8 frame_type; + u8 rate; + bool disable; + struct sk_buff *skb; +}; + +static inline int wsm_set_template_frame(struct cw1200_common *priv, + struct wsm_template_frame *arg) +{ + int ret; + u8 *p = skb_push(arg->skb, 4); + p[0] = arg->frame_type; + p[1] = arg->rate; + if (arg->disable) + ((u16 *) p)[1] = 0; + else + ((u16 *) p)[1] = __cpu_to_le16(arg->skb->len - 4); + ret = wsm_write_mib(priv, WSM_MIB_ID_TEMPLATE_FRAME, p, arg->skb->len); + skb_pull(arg->skb, 4); + return ret; +} + + +struct wsm_protected_mgmt_policy { + bool protectedMgmtEnable; + bool unprotectedMgmtFramesAllowed; + bool encryptionForAuthFrame; +}; + +static inline int wsm_set_protected_mgmt_policy(struct cw1200_common *priv, + struct wsm_protected_mgmt_policy *arg) +{ + __le32 val = 0; + int ret; + if (arg->protectedMgmtEnable) + val |= __cpu_to_le32(BIT(0)); + if (arg->unprotectedMgmtFramesAllowed) + val |= __cpu_to_le32(BIT(1)); + if (arg->encryptionForAuthFrame) + val |= __cpu_to_le32(BIT(2)); + ret = wsm_write_mib(priv, WSM_MIB_ID_PROTECTED_MGMT_POLICY, + &val, sizeof(val)); + return ret; +} + +static inline int wsm_set_block_ack_policy(struct cw1200_common *priv, + u8 blockAckTxTidPolicy, + u8 blockAckRxTidPolicy) +{ + struct { + u8 blockAckTxTidPolicy; + u8 reserved1; + u8 blockAckRxTidPolicy; + u8 reserved2; + } val = { + .blockAckTxTidPolicy = blockAckTxTidPolicy, + .blockAckRxTidPolicy = blockAckRxTidPolicy, + }; + return wsm_write_mib(priv, WSM_MIB_ID_BLOCK_ACK_POLICY, &val, + sizeof(val)); +} + +struct wsm_association_mode { + u8 flags; /* WSM_ASSOCIATION_MODE_... */ + u8 preambleType; /* WSM_JOIN_PREAMBLE_... */ + u8 greenfieldMode; /* 1 for greenfield */ + u8 mpduStartSpacing; + __le32 basicRateSet; +}; + +static inline int wsm_set_association_mode(struct cw1200_common *priv, + struct wsm_association_mode *arg) +{ + return wsm_write_mib(priv, WSM_MIB_ID_SET_ASSOCIATION_MODE, arg, + sizeof(*arg)); +} + +struct wsm_set_tx_rate_retry_policy_header { + u8 numTxRatePolicies; + u8 reserved[3]; +} __packed; + +struct wsm_set_tx_rate_retry_policy_policy { + u8 policyIndex; + u8 shortRetryCount; + u8 longRetryCount; + u8 policyFlags; + u8 rateRecoveryCount; + u8 reserved[3]; + __le32 rateCountIndices[3]; +} __packed; + +struct wsm_set_tx_rate_retry_policy { + struct wsm_set_tx_rate_retry_policy_header hdr; + struct wsm_set_tx_rate_retry_policy_policy tbl[8]; +} __packed; + +static inline int wsm_set_tx_rate_retry_policy(struct cw1200_common *priv, + struct wsm_set_tx_rate_retry_policy *arg) +{ + size_t size = sizeof(struct wsm_set_tx_rate_retry_policy_header) + + arg->hdr.numTxRatePolicies * + sizeof(struct wsm_set_tx_rate_retry_policy_policy); + return wsm_write_mib(priv, WSM_MIB_ID_SET_TX_RATE_RETRY_POLICY, arg, + size); +} + +/* 4.32 SetEtherTypeDataFrameFilter */ +struct wsm_ether_type_filter_hdr { + u8 nrFilters; /* Up to WSM_MAX_FILTER_ELEMENTS */ + u8 reserved[3]; +} __packed; + +struct wsm_ether_type_filter { + u8 filterAction; /* WSM_FILTER_ACTION_XXX */ + u8 reserved; + __le16 etherType; /* Type of ethernet frame */ +} __packed; + +static inline int wsm_set_ether_type_filter(struct cw1200_common *priv, + struct wsm_ether_type_filter_hdr *arg) +{ + size_t size = sizeof(struct wsm_ether_type_filter_hdr) + + arg->nrFilters * sizeof(struct wsm_ether_type_filter); + return wsm_write_mib(priv, WSM_MIB_ID_SET_ETHERTYPE_DATAFRAME_FILTER, + arg, size); +} + + +/* 4.33 SetUDPPortDataFrameFilter */ +struct wsm_udp_port_filter_hdr { + u8 nrFilters; /* Up to WSM_MAX_FILTER_ELEMENTS */ + u8 reserved[3]; +} __packed; + +struct wsm_udp_port_filter { + u8 filterAction; /* WSM_FILTER_ACTION_XXX */ + u8 portType; /* WSM_FILTER_PORT_TYPE_XXX */ + __le16 udpPort; /* Port number */ +} __packed; + +static inline int wsm_set_udp_port_filter(struct cw1200_common *priv, + struct wsm_udp_port_filter_hdr *arg) +{ + size_t size = sizeof(struct wsm_udp_port_filter_hdr) + + arg->nrFilters * sizeof(struct wsm_udp_port_filter); + return wsm_write_mib(priv, WSM_MIB_ID_SET_UDPPORT_DATAFRAME_FILTER, + arg, size); +} + +/* Undocumented MIBs: */ +/* 4.35 P2PDeviceInfo */ +#define D11_MAX_SSID_LEN (32) + +struct wsm_p2p_device_type { + __le16 categoryId; + u8 oui[4]; + __le16 subCategoryId; +} __packed; + +struct wsm_p2p_device_info { + struct wsm_p2p_device_type primaryDevice; + u8 reserved1[3]; + u8 devNameSize; + u8 localDevName[D11_MAX_SSID_LEN]; + u8 reserved2[3]; + u8 numSecDevSupported; + struct wsm_p2p_device_type secondaryDevices[0]; +} __packed; + +/* 4.36 SetWCDMABand - WO */ +struct wsm_cdma_band { + u8 WCDMA_Band; + u8 reserved[3]; +} __packed; + +/* 4.37 GroupTxSequenceCounter - RO */ +struct wsm_group_tx_seq { + __le32 bits_47_16; + __le16 bits_15_00; + __le16 reserved; +} __packed; + +/* 4.39 SetHtProtection - WO */ +#define WSM_DUAL_CTS_PROT_ENB (1 << 0) +#define WSM_NON_GREENFIELD_STA PRESENT(1 << 1) +#define WSM_HT_PROT_MODE__NO_PROT (0 << 2) +#define WSM_HT_PROT_MODE__NON_MEMBER (1 << 2) +#define WSM_HT_PROT_MODE__20_MHZ (2 << 2) +#define WSM_HT_PROT_MODE__NON_HT_MIXED (3 << 2) +#define WSM_LSIG_TXOP_PROT_FULL (1 << 4) +#define WSM_LARGE_L_LENGTH_PROT (1 << 5) + +struct wsm_ht_protection { + __le32 flags; +} __packed; + +/* 4.40 GPIO Command - R/W */ +#define WSM_GPIO_COMMAND_SETUP 0 +#define WSM_GPIO_COMMAND_READ 1 +#define WSM_GPIO_COMMAND_WRITE 2 +#define WSM_GPIO_COMMAND_RESET 3 +#define WSM_GPIO_ALL_PINS 0xFF + +struct wsm_gpio_command { + u8 GPIO_Command; + u8 pin; + __le16 config; +} __packed; + +/* 4.41 TSFCounter - RO */ +struct wsm_tsf_counter { + __le64 TSF_Counter; +} __packed; + +/* 4.43 Keep alive period */ +struct wsm_keep_alive_period { + __le16 keepAlivePeriod; + u8 reserved[2]; +} __packed; + +static inline int wsm_keep_alive_period(struct cw1200_common *priv, + int period) +{ + struct wsm_keep_alive_period arg = { + .keepAlivePeriod = __cpu_to_le16(period), + }; + return wsm_write_mib(priv, WSM_MIB_ID_KEEP_ALIVE_PERIOD, + &arg, sizeof(arg)); +}; + +/* BSSID filtering */ +struct wsm_set_bssid_filtering { + u8 filter; + u8 reserved[3]; +} __packed; + +static inline int wsm_set_bssid_filtering(struct cw1200_common *priv, + bool enabled) +{ + struct wsm_set_bssid_filtering arg = { + .filter = !enabled, + }; + return wsm_write_mib(priv, WSM_MIB_ID_DISABLE_BSSID_FILTER, + &arg, sizeof(arg)); +} + +/* Multicat filtering - 4.5 */ +struct wsm_multicast_filter { + __le32 enable; + __le32 numOfAddresses; + u8 macAddress[WSM_MAX_GRP_ADDRTABLE_ENTRIES][ETH_ALEN]; +} __packed; + +static inline int wsm_set_multicast_filter(struct cw1200_common *priv, + struct wsm_multicast_filter *fp) +{ + return wsm_write_mib(priv, WSM_MIB_ID_DOT11_GROUP_ADDRESSES_TABLE, + fp, sizeof(*fp)); +} + +/* ARP IPv4 filtering - 4.10 */ +struct wsm_arp_ipv4_filter { + __le32 enable; + __be32 ipv4Address[WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES]; +} __packed; + +static inline int wsm_set_arp_ipv4_filter(struct cw1200_common *priv, + struct wsm_arp_ipv4_filter *fp) +{ + return wsm_write_mib(priv, WSM_MIB_ID_ARP_IP_ADDRESSES_TABLE, + fp, sizeof(*fp)); +} + +/* P2P Power Save Mode Info - 4.31 */ +struct wsm_p2p_ps_modeinfo { + u8 oppPsCTWindow; + u8 count; + u8 reserved; + u8 dtimCount; + __le32 duration; + __le32 interval; + __le32 startTime; +} __packed; + +static inline int wsm_set_p2p_ps_modeinfo(struct cw1200_common *priv, + struct wsm_p2p_ps_modeinfo *mi) +{ + return wsm_write_mib(priv, WSM_MIB_ID_P2P_PS_MODE_INFO, + mi, sizeof(*mi)); +} + +static inline int wsm_get_p2p_ps_modeinfo(struct cw1200_common *priv, + struct wsm_p2p_ps_modeinfo *mi) +{ + return wsm_read_mib(priv, WSM_MIB_ID_P2P_PS_MODE_INFO, + mi, sizeof(*mi)); +} + +/* UseMultiTxConfMessage */ + +static inline int wsm_use_multi_tx_conf(struct cw1200_common *priv, + bool enabled) +{ + __le32 arg = enabled ? __cpu_to_le32(1) : 0; + + return wsm_write_mib(priv, WSM_MIB_USE_MULTI_TX_CONF, + &arg, sizeof(arg)); +} + + +/* 4.26 SetUpasdInformation */ +struct wsm_uapsd_info { + __le16 uapsdFlags; + __le16 minAutoTriggerInterval; + __le16 maxAutoTriggerInterval; + __le16 autoTriggerStep; +}; + +static inline int wsm_set_uapsd_info(struct cw1200_common *priv, + struct wsm_uapsd_info *arg) +{ + return wsm_write_mib(priv, WSM_MIB_ID_SET_UAPSD_INFORMATION, + arg, sizeof(*arg)); +} + +/* 4.22 OverrideInternalTxRate */ +struct wsm_override_internal_txrate { + u8 internalTxRate; + u8 nonErpInternalTxRate; + u8 reserved[2]; +} __packed; + +static inline int wsm_set_override_internal_txrate(struct cw1200_common *priv, + struct wsm_override_internal_txrate *arg) +{ + return wsm_write_mib(priv, WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE, + arg, sizeof(*arg)); +} + +/* ******************************************************************** */ +/* WSM TX port control */ + +void wsm_lock_tx(struct cw1200_common *priv); +void wsm_lock_tx_async(struct cw1200_common *priv); +bool wsm_flush_tx(struct cw1200_common *priv); +void wsm_unlock_tx(struct cw1200_common *priv); + +/* ******************************************************************** */ +/* WSM / BH API */ + +int wsm_handle_exception(struct cw1200_common *priv, u8 *data, size_t len); +int wsm_handle_rx(struct cw1200_common *priv, int id, struct wsm_hdr *wsm, + struct sk_buff **skb_p); + +/* ******************************************************************** */ +/* wsm_buf API */ + +struct wsm_buf { + u8 *begin; + u8 *data; + u8 *end; +}; + +void wsm_buf_init(struct wsm_buf *buf); +void wsm_buf_deinit(struct wsm_buf *buf); + +/* ******************************************************************** */ +/* wsm_cmd API */ + +struct wsm_cmd { + spinlock_t lock; + int done; + u8 *ptr; + size_t len; + void *arg; + int ret; + u16 cmd; +}; + +/* ******************************************************************** */ +/* WSM TX buffer access */ + +int wsm_get_tx(struct cw1200_common *priv, u8 **data, + size_t *tx_len, int *burst); +void wsm_txed(struct cw1200_common *priv, u8 *data); + +/* ******************************************************************** */ +/* Queue mapping: WSM <---> linux */ +/* Linux: VO VI BE BK */ +/* WSM: BE BK VI VO */ + +static inline u8 wsm_queue_id_to_linux(u8 queueId) +{ + static const u8 queue_mapping[] = { + 2, 3, 1, 0 + }; + return queue_mapping[queueId]; +} + +static inline u8 wsm_queue_id_to_wsm(u8 queueId) +{ + static const u8 queue_mapping[] = { + 3, 2, 0, 1 + }; + return queue_mapping[queueId]; +} + +#endif /* CW1200_HWIO_H_INCLUDED */ diff --git a/drivers/staging/mmio/Kconfig b/drivers/staging/mmio/Kconfig new file mode 100644 index 00000000000..d6a5a9ad918 --- /dev/null +++ b/drivers/staging/mmio/Kconfig @@ -0,0 +1,11 @@ + +config U8500_MMIO + bool "ST-Ericsson MMIO (Camera) Driver" + depends on ARCH_U8500 + help + Enables the ST-Ericsson MMIO (Camera) Driver + +config U5500_MMIO + bool "ST-Ericsson U5500 MMIO (Camera) Driver" + depends on UX500_SOC_DB5500 + diff --git a/drivers/staging/mmio/Makefile b/drivers/staging/mmio/Makefile new file mode 100644 index 00000000000..bec2a6efe63 --- /dev/null +++ b/drivers/staging/mmio/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_U8500_MMIO) := st_mmio.o diff --git a/drivers/staging/mmio/mmio.h b/drivers/staging/mmio/mmio.h new file mode 100644 index 00000000000..1c6f68e3556 --- /dev/null +++ b/drivers/staging/mmio/mmio.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Joakim Axelsson <joakim.axelsson@stericsson.com> for ST-Ericsson + * Author: Rajat Verma <rajat.verma@stericsson.com> for ST-Ericsson + * License Terms: GNU General Public License v2 + */ + +#ifndef MMIO_H +#define MMIO_H + +#include <linux/ioctl.h> + +#define MMIO_NAME "mmio_camera" +#define SRA_SUPPORT 1 + +#ifdef SRA_SUPPORT +#define SREG_16_BIT (0x1) +#define SREG_32_BIT (0x2) +#endif +/* Kernel side interface for MMIO */ +/* Which camera is currently active */ +enum camera_slot_t { + PRIMARY_CAMERA = 0, + SECONDARY_CAMERA, + CAMERA_SLOT_END +}; +struct mmio_gpio { + int gpio; /* Set to zero if not in use */ + int active_high;/* Set if pin is active high */ + int udelay; /* Time to wait when activating the pin, in usec */ +}; +enum mmio_select_i2c_t { + MMIO_ACTIVATE_IPI2C2 = 0, + MMIO_ACTIVATE_I2C_HOST, + MMIO_DEACTIVATE_I2C +}; + +enum mmio_select_xshutdown_t { + MMIO_ENABLE_XSHUTDOWN_FW = 0, + MMIO_ENABLE_XSHUTDOWN_HOST, + MMIO_DISABLE_XSHUTDOWN +}; +struct mmio_platform_data { + struct device *dev; + enum camera_slot_t camera_slot; /* Which camera is currently used, + * Primary/Secondary */ + void *extra; /* Board's private data structure + * placeholder */ + int reset_ipgpio[CAMERA_SLOT_END]; /* Contains logical IP GPIO for + * reset pin */ + int sia_base; + int cr_base; + int (*platform_init)(struct mmio_platform_data *pdata); + void (*platform_exit)(struct mmio_platform_data *pdata); + int (*power_enable)(struct mmio_platform_data *pdata); + void (*power_disable)(struct mmio_platform_data *pdata); + int (*config_xshutdown_pins)(struct mmio_platform_data *pdata, + enum mmio_select_xshutdown_t select, int is_active_high); + int (*config_i2c_pins)(struct mmio_platform_data *pdata, + enum mmio_select_i2c_t select); + int (*clock_enable)(struct mmio_platform_data *pdata); + void (*clock_disable)(struct mmio_platform_data *pdata); + void (*set_xshutdown)(struct mmio_platform_data *pdata); +}; + +#define USER_SIDE_INTERFACE 1 +/* User side is only allowed to access code in USER_SIDE_INTERFACE block */ +#ifdef USER_SIDE_INTERFACE +enum mmio_bool_t { + MMIO_FALSE = 0, + MMIO_TRUE = !MMIO_FALSE, + MMIO_BOOL_MAX = 0x7FFFFFFF +}; + +struct xshutdown_info_t { + int ip_gpio; + int camera_function; +}; + +struct xp70_fw_t { + void __iomem *addr_sdram_ext; + void __iomem *addr_esram_ext; + void __iomem *addr_split; + void __iomem *addr_data; + unsigned int size_sdram_ext; + unsigned int size_esram_ext; + unsigned int size_split; + unsigned int size_data; +}; + +struct isp_write_t { + unsigned long t1_dest; + unsigned long *data; + unsigned long count; +}; + +struct trace_buf_t { + void *address; + unsigned int size; +}; + +#ifdef SRA_SUPPORT +struct s_reg { + unsigned int addr; + unsigned int value; + unsigned int mask; +}; + +struct s_reg_list { + unsigned int access_mode; + unsigned int entries; + struct s_reg *s_regs_p; +}; +#endif +struct mmio_input_output_t { + union { + enum mmio_bool_t power_on; + struct xp70_fw_t xp70_fw; + struct isp_write_t isp_write; + unsigned int addr_to_map; + struct xshutdown_info_t xshutdown_info; + enum camera_slot_t camera_slot; + struct trace_buf_t trace_buf; +#ifdef SRA_SUPPORT + struct s_reg_list s_reg_list; +#endif + } mmio_arg; +}; + +#define MMIO_TRUE (1) +#define MMIO_FALSE (0) +#define MMIO_INVALID (~0) + +/*Xshutdown from host takes two arguments*/ +#define MMIO_XSHUTDOWN_ENABLE (0x1) +#define MMIO_XSHUTDOWN_ACTIVE_HIGH (0x2) + +#define MMIO_MAGIC_NUMBER 0x15 + +#define MMIO_CAM_INITBOARD _IOW(MMIO_MAGIC_NUMBER, 1,\ +struct mmio_input_output_t*) +#define MMIO_CAM_PWR_SENSOR _IOW(MMIO_MAGIC_NUMBER, 2,\ +struct mmio_input_output_t*) +#define MMIO_CAM_SET_EXT_CLK _IOW(MMIO_MAGIC_NUMBER, 3,\ +struct mmio_input_output_t*) +#define MMIO_CAM_SET_PRI_HWIF _IO(MMIO_MAGIC_NUMBER, 4) +#define MMIO_CAM_SET_SEC_HWIF _IO(MMIO_MAGIC_NUMBER, 5) +#define MMIO_CAM_INITMMDSPTIMER _IO(MMIO_MAGIC_NUMBER, 6) +#define MMIO_CAM_LOAD_XP70_FW _IOW(MMIO_MAGIC_NUMBER, 7,\ +struct mmio_input_output_t*) +#define MMIO_CAM_MAP_STATS_AREA _IOWR(MMIO_MAGIC_NUMBER, 8,\ +struct mmio_input_output_t*) +#define MMIO_ACTIVATE_I2C2 _IOW(MMIO_MAGIC_NUMBER, 9, int*) +#define MMIO_ENABLE_XSHUTDOWN_FROM_HOST _IOW(MMIO_MAGIC_NUMBER, 10, int*) +#define MMIO_CAM_ISP_WRITE _IOW(MMIO_MAGIC_NUMBER, 11,\ +struct mmio_input_output_t*) +#define MMIO_CAM_GET_IP_GPIO _IOWR(MMIO_MAGIC_NUMBER, 12,\ +struct mmio_input_output_t*) +#define MMIO_CAM_DESINITBOARD _IO(MMIO_MAGIC_NUMBER, 13) +#define MMIO_CAM_SET_TRACE_BUFFER _IOW(MMIO_MAGIC_NUMBER, 14,\ +struct mmio_input_output_t*) + +#ifdef SRA_SUPPORT +#define MMIO_CAM_READ_REGS _IOWR(MMIO_MAGIC_NUMBER, 15,\ +struct mmio_input_output_t*) +#define MMIO_CAM_MODIFY_REGS _IOWR(MMIO_MAGIC_NUMBER, 16,\ +struct mmio_input_output_t*) +#define MMIO_CAM_WRITE_REGS _IOWR(MMIO_MAGIC_NUMBER, 17,\ +struct mmio_input_output_t*) +#endif + +#endif /* USER_SIDE_INTERFACE */ + +#endif +/* MMIO_H */ diff --git a/drivers/staging/mmio/st_mmio.c b/drivers/staging/mmio/st_mmio.c new file mode 100644 index 00000000000..a006c55c544 --- /dev/null +++ b/drivers/staging/mmio/st_mmio.c @@ -0,0 +1,1173 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pankaj Chauhan <pankaj.chauhan@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ +#include <linux/delay.h> +#include <linux/init.h> /* Initiliasation support */ +#include <linux/module.h> /* Module support */ +#include <linux/kernel.h> /* Kernel support */ +#include <linux/version.h> /* Kernel version */ +#include <linux/fs.h> /* File operations (fops) defines */ +#include <linux/errno.h> /* Defines standard err codes */ +#include <linux/io.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/vmalloc.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/ratelimit.h> +#include "mmio.h" + +#define ISP_REGION_IO (0xE0000000) +#define SIA_ISP_REG_ADDR (0x521E4) +#define SIA_BASE_ADDR (0x54000) +#define SIA_ISP_MEM (0x56000) +#define SIA_TIMER_ITC (0x5BC00) +#define SIA_ISP_MCU_SYS_SIZE (0x100000) +#define SIA_ISP_MEM_PAGE_REG (0x54070) +#define SIA_ISP_MCU_SYS_ADDR0_OFFSET (SIA_BASE_ADDR + 0x40) +#define SIA_ISP_MCU_SYS_SIZE0_OFFSET (SIA_BASE_ADDR + 0x42) +#define SIA_ISP_MCU_SYS_ADDR1_OFFSET (SIA_ISP_MCU_SYS_ADDR0_OFFSET + 0x04) +#define SIA_ISP_MCU_SYS_SIZE1_OFFSET (SIA_ISP_MCU_SYS_SIZE0_OFFSET + 0x04) +#define SIA_ISP_MCU_IO_ADDR0_HI (SIA_BASE_ADDR + 0x60) + +/* HTimer enable in CR register */ +#define CR_REG0_HTIMEN (1 << 26) +#define PICTOR_IN_XP70_L2_MEM_BASE_ADDR (0x40000) +#define PICTOR_IN_XP70_TCDM_MEM_BASE_ADDR (0x60000) +#define L2_PSRAM_MEM_SIZE (0x10000) + +#define FW_TO_HOST_ADDR_MASK (0x00001FFF) +#define FW_TO_HOST_ADDR_SHIFT (0xD) +#define FW_TO_HOST_CLR_MASK (0x3F) +#define PHY_TO_ISP_MCU_IO_ADDR0_HI(x) (((x) >> 24) << 8) +#define XP70_ADDR_MASK (0x00FFFFFF) + +#define CLOCK_ENABLE_DELAY (0x2) + +#define MAX_PRCMU_QOS_APP (0x64) + +#define ISP_WRITE_DATA_SIZE (0x4) + +#define clrbits32(_addr, _clear) \ + writel(readl(_addr) & ~(u32)(_clear), _addr) +#define setbits32(_addr, _set) \ + writel(readl(_addr) | (u32)(_set), _addr) + +#define XP70_BLOCK_SIZE 124 +#define XP70_NB_BLOCK 50 +/* + * For 30 fps video, there is 33 msec delay between every two frames + * MMIO driver reads traces from trace buffer every XP70_TIMEOUT_MSEC. + * If traces are not read in time from trace buffer, camera firmware + * will start overwiting the traces as size of trace buffer is limited. + */ +#define XP70_TIMEOUT_MSEC 30 +#define XP70_DEFAULT_MSG_ID (0xCDCDCDCD) +#define XP70_MAX_BLOCK_ID (0xFFFFFFFF) + +#define upper_16_bits(n) ((u16)((u32)(n) >> 16)) + +struct trace_block { + u32 msg_id; + char data[XP70_BLOCK_SIZE]; +}; + +struct mmio_trace { + u32 nb_block; + u32 block_size; + u32 block_id; + u32 overwrite_count; + struct trace_block block[XP70_NB_BLOCK]; +}; + +struct trace_buffer_status { + u32 prev_overwrite_count; + u32 prev_block_id; +}; + +struct mmio_info { + struct mmio_platform_data *pdata; /* Config from board */ + struct device *dev; /* My device */ + /* Runtime variables */ + struct miscdevice misc_dev; + void __iomem *siabase; + void __iomem *crbase; + /* States */ + int xshutdown_enabled; + int xshutdown_is_active_high; + /* tracing */ + struct trace_buffer_status trace_status; + struct mmio_trace *trace_buffer; + struct delayed_work trace_work; + int trace_allowed; + struct mutex lock; +}; + +/* + * The one and only private data holder. Default inited to NULL. + * Declare it here so no code above can use it directly. + */ +static struct mmio_info *info; + +/* + * This function converts a given logical memory region size + * to appropriate ISP_MCU_SYS_SIZEx register value. + */ +static int get_mcu_sys_size(u32 size, u32 *val) +{ + int ret = 0; + + if (size > 0 && size <= SZ_4K) + *val = 4; + else if (size > SZ_4K && size <= SZ_8K) + *val = 5; + else if (size > SZ_8K && size <= SZ_16K) + *val = 6; + else if (size > SZ_16K && size <= SZ_32K) + *val = 7; + else if (size > SZ_32K && size <= SZ_64K) + *val = 0; + else if (size > SZ_64K && size <= SZ_1M) + *val = 1; + else if (size > SZ_1M && size <= SZ_16M) + *val = 2; + else if (size > SZ_16M && size <= SZ_256M) + *val = 3; + else + ret = -EINVAL; + + return ret; +} + +static int mmio_cam_pwr_sensor(struct mmio_info *info, int on) +{ + int err = 0; + + if (on) { + err = info->pdata->power_enable(info->pdata); + + if (err) + dev_err(info->dev, + "power_enable failed. err = %d\n", err); + + /* + * When switching from secondary YUV camera + * to primary Raw Bayer Camera, a hang is observed without the + * below delay. I2C access failure are observed while + * communicating with primary camera sensor indicating camera + * sensor was not powered up correctly. + */ + mdelay(CLOCK_ENABLE_DELAY); + } else { + info->pdata->power_disable(info->pdata); + } + + return err; +} + +static int mmio_cam_control_clocks(struct mmio_info *info, + enum mmio_bool_t power_on) +{ + int err = 0; + + if (power_on) { + err = info->pdata->clock_enable(info->pdata); + + if (err) + dev_err(info->dev, + "clock_enable failed, err = %d\n", + err); + } else { + info->pdata->clock_disable(info->pdata); + } + + return err; +} + +static int mmio_cam_set_pri_hwif(struct mmio_info *info) +{ + if (info->xshutdown_enabled) + info->pdata->set_xshutdown(info->pdata); + + return 0; +} + +static int mmio_cam_set_sec_hwif(struct mmio_info *info) +{ + if (info->xshutdown_enabled) + info->pdata->set_xshutdown(info->pdata); + + return 0; +} + +static int mmio_cam_init_mmdsp_timer(struct mmio_info *info) +{ + /* Disabling Accelerators timers */ + clrbits32(info->crbase, CR_REG0_HTIMEN); + /* Write MMDSPTimer */ + writel(0, info->siabase + SIA_TIMER_ITC); + /* Enabling Accelerators timers */ + setbits32(info->crbase, CR_REG0_HTIMEN); + return 0; +} + +static u32 t1_to_arm(u32 t1_addr, void __iomem *smia_base_address, + u16 *p_mem_page) +{ + u16 mem_page_update = 0; + mem_page_update = (t1_addr >> FW_TO_HOST_ADDR_SHIFT) & + FW_TO_HOST_CLR_MASK; + + if (mem_page_update != *p_mem_page) { + /* Update sia_mem_page register */ + dev_dbg(info->dev, "mem_page_update=0x%x, mem_page=0x%x\n", + mem_page_update, *p_mem_page); + writew(mem_page_update, smia_base_address + + SIA_ISP_MEM_PAGE_REG); + *p_mem_page = mem_page_update; + } + + return SIA_ISP_MEM + (t1_addr & FW_TO_HOST_ADDR_MASK); +} + +static int copy_user_buffer(void __iomem **dest_buf, + void __iomem *src_buf, u32 size) +{ + int err = 0; + + if (!src_buf) + return -EINVAL; + + *dest_buf = kmalloc(size, GFP_KERNEL); + + if (!*dest_buf) { + err = -ENOMEM; + goto nomem; + } + + if (copy_from_user(*dest_buf, src_buf, size)) { + err = -EFAULT; + goto cp_failed; + } + + return err; +cp_failed: + kfree(*dest_buf); +nomem: + return err; +} +static int mmio_load_xp70_fw(struct mmio_info *info, + struct xp70_fw_t *xp70_fw) +{ + u32 i = 0; + u32 offset = 0; + u32 itval = 0; + u16 mem_page = 0; + void __iomem *addr_split = NULL; + void __iomem *addr_data = NULL; + int err = 0; + + if (xp70_fw->size_split != 0) { + err = copy_user_buffer(&addr_split, xp70_fw->addr_split, + xp70_fw->size_split); + + if (err) + goto err_exit; + + writel(0x0, info->siabase + SIA_ISP_REG_ADDR); + + /* Put the low 64k IRP firmware in ISP MCU L2 PSRAM */ + for (i = PICTOR_IN_XP70_L2_MEM_BASE_ADDR; + i < (PICTOR_IN_XP70_L2_MEM_BASE_ADDR + + L2_PSRAM_MEM_SIZE); i = i + 2) { + itval = t1_to_arm(i, info->siabase, &mem_page); + itval = ((u32) info->siabase) + itval; + /* Copy fw in L2 */ + writew((*((u16 *) addr_split + offset++)), itval); + } + + kfree(addr_split); + } + + if (xp70_fw->size_data != 0) { + mem_page = 0; + offset = 0; + err = copy_user_buffer(&addr_data, xp70_fw->addr_data, + xp70_fw->size_data); + + if (err) + goto err_exit; + + writel(0x0, info->siabase + SIA_ISP_REG_ADDR); + + for (i = PICTOR_IN_XP70_TCDM_MEM_BASE_ADDR; + i < (PICTOR_IN_XP70_TCDM_MEM_BASE_ADDR + + (xp70_fw->size_data)); i = i + 2) { + itval = t1_to_arm(i, info->siabase, &mem_page); + itval = ((u32) info->siabase) + itval; + /* Copy fw data in TCDM */ + writew((*((u16 *) addr_data + offset++)), itval); + } + + kfree(addr_data); + } + + if (xp70_fw->size_esram_ext != 0) { + /* + * ISP_MCU_SYS_ADDRx XP70 register (@ of ESRAM where the + * external code has been loaded + */ + writew(upper_16_bits(xp70_fw->addr_esram_ext), + info->siabase + SIA_ISP_MCU_SYS_ADDR0_OFFSET); + /* ISP_MCU_SYS_SIZEx XP70 register (size of the code =64KB) */ + writew(0x0, info->siabase + SIA_ISP_MCU_SYS_SIZE0_OFFSET); + } + + if (xp70_fw->size_sdram_ext != 0) { + /* + * ISP_MCU_SYS_ADDRx XP70 register (@ of SDRAM where the + * external code has been loaded + */ + writew(upper_16_bits(xp70_fw->addr_sdram_ext), + info->siabase + SIA_ISP_MCU_SYS_ADDR1_OFFSET); + /* ISP_MCU_SYS_SIZEx XP70 register */ + err = get_mcu_sys_size(xp70_fw->size_sdram_ext, &itval); + + if (err) + goto err_exit; + + writew(itval, info->siabase + SIA_ISP_MCU_SYS_SIZE1_OFFSET); + } + + return 0; +err_exit: + dev_err(info->dev, "Loading XP70 fw failed\n"); + return -EFAULT; +} + +static int mmio_map_statistics_mem_area(struct mmio_info *info, + void __iomem *addr_to_map) +{ + u16 value; + BUG_ON(addr_to_map == NULL); + /* 16 Mbyte aligned page */ + value = PHY_TO_ISP_MCU_IO_ADDR0_HI(*((u32 *)addr_to_map)); + writew(value, info->siabase + SIA_ISP_MCU_IO_ADDR0_HI); + /* Return the address in the XP70 address space */ + *((u32 *)addr_to_map) = (*((u32 *)addr_to_map) & XP70_ADDR_MASK) | + ISP_REGION_IO; + return 0; +} + +static int mmio_activate_i2c2(struct mmio_info *info, unsigned long enable) +{ + int err = 0; + + switch (enable) { + case MMIO_ACTIVATE_I2C_HOST: + /* Select I2C-2 */ + err = info->pdata->config_i2c_pins(info->pdata, + MMIO_ACTIVATE_I2C_HOST); + + if (err) { + dev_err(info->dev, "Failed to Enable I2C-2, err %d\n", + err); + goto out; + } + + break; + case MMIO_ACTIVATE_IPI2C2: + /* Select IPI2C */ + err = info->pdata->config_i2c_pins(info->pdata, + MMIO_ACTIVATE_IPI2C2); + + if (err) { + dev_err(info->dev, "Failed to Enable IPI2C, err %d\n", + err); + goto out; + } + + break; + case MMIO_DEACTIVATE_I2C: { + info->pdata->config_i2c_pins(info->pdata, MMIO_DEACTIVATE_I2C); + } + break; + default: + dev_warn(info->dev, "Invalid I2C2 config\n"); + err = -EINVAL; + break; + } + +out: + return err; +} + +static int mmio_enable_xshutdown_from_host(struct mmio_info *info, + unsigned long enable) +{ + int err = 0; + info->xshutdown_is_active_high = enable & MMIO_XSHUTDOWN_ACTIVE_HIGH; + + if (enable & MMIO_XSHUTDOWN_ENABLE) { + err = info->pdata->config_xshutdown_pins(info->pdata, + MMIO_ENABLE_XSHUTDOWN_HOST, enable & + MMIO_XSHUTDOWN_ACTIVE_HIGH); + } else { + info->pdata->config_xshutdown_pins(info->pdata, + MMIO_ENABLE_XSHUTDOWN_FW, -1); + /* + * XShutdown is controlled by firmware, initial output value is + * provided by firmware + */ + } + + info->xshutdown_enabled = enable & MMIO_XSHUTDOWN_ENABLE; + return 0; +} + +static int mmio_cam_initboard(struct mmio_info *info) +{ + int err = 0; + err = prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, MMIO_NAME, + MAX_PRCMU_QOS_APP); + + if (err) { + dev_err(info->dev, "Error adding PRCMU QoS requirement %d\n", + err); + goto out; + } + + /* Configure xshutdown to be disabled by default */ + err = mmio_enable_xshutdown_from_host(info, 0); + + if (err) + goto out; + + /* Enable IPI2C */ + err = mmio_activate_i2c2(info, MMIO_ACTIVATE_IPI2C2); +out: + return err; +} + +static int mmio_cam_desinitboard(struct mmio_info *info) +{ + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, MMIO_NAME); + return 0; +} + +static int mmio_isp_write(struct mmio_info *info, + struct isp_write_t *isp_write_p) +{ + int err = 0, i; + void __iomem *data = NULL; + void __iomem *addr = NULL; + u16 mem_page = 0; + + if (!isp_write_p->count) { + dev_warn(info->dev, "no data to write to isp\n"); + return -EINVAL; + } + + err = copy_user_buffer(&data, isp_write_p->data, + isp_write_p->count * ISP_WRITE_DATA_SIZE); + + if (err) + goto out; + + for (i = 0; i < isp_write_p->count; i++) { + addr = (void *)(info->siabase + t1_to_arm(isp_write_p->t1_dest + + ISP_WRITE_DATA_SIZE * i, + info->siabase, &mem_page)); + *((u32 *)addr) = *((u32 *)data + i); + } + + kfree(data); +out: + return err; +} + +static int mmio_set_trace_buffer(struct mmio_info *info, + struct trace_buf_t *buf) +{ + u32 i; + int ret = 0; + + if (info->trace_allowed != 1) { + dev_warn(info->dev, "trace disabled in kernel\n"); + ret = -EPERM; + goto out; + } + + if (!buf->size || !buf->address + || buf->size < sizeof(struct mmio_trace)) { + dev_err(info->dev, "invalid xp70 trace buffer\n"); + ret = -EINVAL; + goto out; + } + + mutex_lock(&info->lock); + if (info->trace_buffer) { + dev_info(info->dev, "unmap old buffer"); + iounmap(info->trace_buffer); + info->trace_buffer = NULL; + } + + info->trace_buffer = ioremap((u32)buf->address, buf->size); + + if (!info->trace_buffer) { + dev_err(info->dev, "failed to map trace buffer\n"); + ret = -ENOMEM; + goto out_unlock; + } + + dev_info(info->dev, "xp70 overwrite_cnt=%d (0x%x) blk_id=%d (0x%x)", + info->trace_buffer->overwrite_count, + info->trace_buffer->overwrite_count, + info->trace_buffer->block_id, info->trace_buffer->block_id); +#ifndef CAM_SHARED_MEM_DEBUG + + /* Reset the allocated buffer contents */ + for (i = 0; i < XP70_NB_BLOCK; i++) + info->trace_buffer->block[i].msg_id = XP70_DEFAULT_MSG_ID; + +#endif /* CAM_SHARED_MEMORY_DEBUG */ + dev_info(info->dev, "xp70 overwrite_cnt=%d (0x%x) blk_id=%d (0x%x)\n", + info->trace_buffer->overwrite_count, + info->trace_buffer->overwrite_count, + info->trace_buffer->block_id, info->trace_buffer->block_id); + info->trace_status.prev_overwrite_count = 0; + info->trace_status.prev_block_id = 0; + + /* schedule work */ + if (!schedule_delayed_work(&info->trace_work, + msecs_to_jiffies(XP70_TIMEOUT_MSEC))) + dev_err(info->dev, "failed to schedule work\n"); + +out_unlock: + mutex_unlock(&info->lock); +out: + return ret; +} + +static long mmio_ioctl(struct file *filp, u32 cmd, + unsigned long arg) +{ + struct mmio_input_output_t data; + int no_of_bytes; + int enable; + int ret = 0; + struct mmio_info *info = (struct mmio_info *)filp->private_data; + BUG_ON(info == NULL); + + switch (cmd) { + case MMIO_CAM_INITBOARD: + no_of_bytes = sizeof(struct mmio_input_output_t); + memset(&data, 0, sizeof(struct mmio_input_output_t)); + + if (copy_from_user(&data, (struct mmio_input_output_t *)arg, + no_of_bytes)) { + dev_err(info->dev, + "Copy from userspace failed\n"); + ret = -EFAULT; + break; + } + + info->pdata->camera_slot = data.mmio_arg.camera_slot; + ret = mmio_cam_initboard(info); + break; + case MMIO_CAM_DESINITBOARD: + ret = mmio_cam_desinitboard(info); + break; + case MMIO_CAM_PWR_SENSOR: + no_of_bytes = sizeof(struct mmio_input_output_t); + memset(&data, 0, sizeof(struct mmio_input_output_t)); + + if (copy_from_user + (&data, (struct mmio_input_output_t *)arg, + no_of_bytes)) { + dev_err(info->dev, + "Copy from userspace failed\n"); + ret = -EFAULT; + break; + } + + ret = mmio_cam_pwr_sensor(info, data.mmio_arg.power_on); + break; + case MMIO_CAM_SET_EXT_CLK: + no_of_bytes = sizeof(struct mmio_input_output_t); + memset(&data, 0, sizeof(struct mmio_input_output_t)); + + if (copy_from_user + (&data, (struct mmio_input_output_t *)arg, + no_of_bytes)) { + dev_err(info->dev, + "Copy from userspace failed\n"); + ret = -EFAULT; + break; + } + + ret = mmio_cam_control_clocks(info, data.mmio_arg.power_on); + break; + case MMIO_CAM_LOAD_XP70_FW: + no_of_bytes = sizeof(struct mmio_input_output_t); + memset(&data, 0, sizeof(struct mmio_input_output_t)); + + if (copy_from_user + (&data, (struct mmio_input_output_t *)arg, + no_of_bytes)) { + dev_err(info->dev, + "Copy from userspace failed\n"); + ret = -EFAULT; + break; + } + + ret = mmio_load_xp70_fw(info, &data.mmio_arg.xp70_fw); + break; + case MMIO_CAM_MAP_STATS_AREA: + no_of_bytes = sizeof(struct mmio_input_output_t); + memset(&data, 0, sizeof(struct mmio_input_output_t)); + + if (copy_from_user + (&data, (struct mmio_input_output_t *)arg, + no_of_bytes)) { + dev_err(info->dev, + "Copy from userspace failed\n"); + ret = -EFAULT; + break; + } + + ret = mmio_map_statistics_mem_area(info, + &data.mmio_arg.addr_to_map); + + if (0 != ret) { + dev_err(info->dev, + "Unable to map Statistics Mem area\n"); + break; + } + + if (copy_to_user((struct mmio_input_output_t *)arg, + &data, sizeof(no_of_bytes))) { + dev_err(info->dev, + "Copy to userspace failed\n"); + ret = -EFAULT; + break; + } + + break; + case MMIO_CAM_SET_PRI_HWIF: + ret = mmio_cam_set_pri_hwif(info); + break; + case MMIO_CAM_SET_SEC_HWIF: + ret = mmio_cam_set_sec_hwif(info); + break; + case MMIO_CAM_INITMMDSPTIMER: + ret = mmio_cam_init_mmdsp_timer(info); + break; + case MMIO_CAM_ISP_WRITE: + no_of_bytes = sizeof(struct mmio_input_output_t); + memset(&data, 0, sizeof(struct mmio_input_output_t)); + + if (copy_from_user + (&data, (struct mmio_input_output_t *)arg, + no_of_bytes)) { + dev_err(info->dev, + "Copy from userspace failed\n"); + ret = -EFAULT; + break; + } + + ret = mmio_isp_write(info, &data.mmio_arg.isp_write); + break; + case MMIO_ACTIVATE_I2C2: + no_of_bytes = sizeof(struct mmio_input_output_t); + memset(&data, 0, sizeof(struct mmio_input_output_t)); + + if (copy_from_user + (&enable, (int *)arg, sizeof(enable))) { + dev_err(info->dev, + "Copy from userspace failed\n"); + ret = -EFAULT; + break; + } + + ret = mmio_activate_i2c2(info, enable); + break; + case MMIO_ENABLE_XSHUTDOWN_FROM_HOST: + no_of_bytes = sizeof(struct mmio_input_output_t); + memset(&data, 0, sizeof(struct mmio_input_output_t)); + + if (copy_from_user + (&enable, (int *)arg, sizeof(enable))) { + dev_err(info->dev, + "Copy from userspace failed\n"); + ret = -EFAULT; + break; + } + + ret = mmio_enable_xshutdown_from_host(info, enable); + break; + case MMIO_CAM_GET_IP_GPIO: + no_of_bytes = sizeof(struct mmio_input_output_t); + memset(&data, 0, sizeof(struct mmio_input_output_t)); + + if (copy_from_user + (&data, (struct mmio_input_output_t *)arg, + no_of_bytes)) { + dev_err(info->dev, + "Copy from userspace failed\n"); + ret = -EFAULT; + break; + } + + data.mmio_arg.xshutdown_info.ip_gpio = + info->pdata->reset_ipgpio + [data.mmio_arg.xshutdown_info.camera_function]; + + if (copy_to_user((struct mmio_input_output_t *)arg, + &data, sizeof(no_of_bytes))) { + dev_err(info->dev, + "Copy to userspace failed\n"); + ret = -EFAULT; + break; + } + + break; + case MMIO_CAM_SET_TRACE_BUFFER: + no_of_bytes = sizeof(struct mmio_input_output_t); + memset(&data, 0, sizeof(struct mmio_input_output_t)); + + if (copy_from_user + (&data, (struct mmio_input_output_t *) arg, + no_of_bytes)) { + dev_err(info->dev, + "Copy from userspace failed\n"); + ret = -EFAULT; + break; + } + + ret = mmio_set_trace_buffer(info, &data.mmio_arg.trace_buf); + break; + default: + dev_err(info->dev, "Not an ioctl for this module\n"); + ret = -EINVAL; + break; + } + + return ret; +} + +static int mmio_release(struct inode *node, struct file *filp) +{ + struct mmio_info *info = filp->private_data; + BUG_ON(info == NULL); + mmio_activate_i2c2(info, MMIO_DEACTIVATE_I2C); + info->pdata->config_xshutdown_pins(info->pdata, MMIO_DISABLE_XSHUTDOWN, + -1); + + mutex_lock(&info->lock); + if (info->trace_buffer) { + flush_delayed_work_sync(&info->trace_work); + iounmap(info->trace_buffer); + info->trace_buffer = NULL; + } + mutex_unlock(&info->lock); + return 0; +} + +static int mmio_open(struct inode *node, struct file *filp) +{ + filp->private_data = info; /* Hook our mmio info */ + return 0; +} + +static const struct file_operations mmio_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = mmio_ioctl, + .open = mmio_open, + .release = mmio_release, +}; + + +static ssize_t xp70_data_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + int i; + int len; + int size = 0; + int count = 0; + int first_index; + mutex_lock(&info->lock); + first_index = info->trace_status.prev_block_id + 1; + + if (!info->trace_buffer || info->trace_buffer->block_id == + XP70_MAX_BLOCK_ID) + goto out_unlock; + + if (info->trace_allowed != 1) { + dev_warn(info->dev, "xp70 trace disabled in kernel\n"); + size = sprintf(buf, "xp70 trace disabled in kernel, " + "use sysfs to enable\n"); + goto out_unlock; + } + + count = info->trace_buffer->block_id - info->trace_status.prev_block_id; + + if ((info->trace_buffer->overwrite_count - + info->trace_status.prev_overwrite_count) * XP70_NB_BLOCK + + (info->trace_buffer->block_id - + info->trace_status.prev_block_id) + >= XP70_NB_BLOCK) { + /* overflow case */ + info->trace_status.prev_block_id = + info->trace_buffer->block_id - XP70_NB_BLOCK; + first_index = info->trace_buffer->block_id + 1; + count = XP70_NB_BLOCK; + len = sprintf(buf, "XP70 trace overflow\n"); + size += len; + buf += len; + } + + for (i = first_index; count; count--) { + int msg_len; + + if (i < 0 || i >= XP70_NB_BLOCK || count > XP70_NB_BLOCK) { + dev_err(info->dev, "trace index out-of-bounds\n"); + goto out_unlock; + } + + msg_len = strnlen(info->trace_buffer->block[i].data, + XP70_BLOCK_SIZE); + + if (msg_len > 0) { + /* zero terminate full length message */ + if (msg_len == XP70_BLOCK_SIZE) + info->trace_buffer->block[i].data[ + XP70_BLOCK_SIZE - 1] = '\0'; + + len = snprintf(buf, PAGE_SIZE - size, "%d %s\n", + info->trace_buffer->block[i].msg_id, + info->trace_buffer->block[i].data); + + if (len > PAGE_SIZE - size) { + dev_err(info->dev, "sysfs buffer overflow\n"); + size = PAGE_SIZE; + goto out_unlock; + } + + size += len; + buf += len; + } + + i = (i + 1) % XP70_NB_BLOCK; + } + +out_unlock: + mutex_unlock(&info->lock); + return size; +} + +static ssize_t xp70_trace_allowed_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + int len; + len = sprintf(buf, "%d\n", info->trace_allowed); + return len; +} + +static ssize_t xp70_trace_allowed_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + if (count <= 0) { + dev_err(info->dev, "empty buffer to store\n"); + return 0; + } + + if (buf[0] == '1') + info->trace_allowed = 1; + else if (buf[0] == '0') + info->trace_allowed = 0; + else + dev_err(info->dev, "illegal trace_allowed val %c\n", + buf[0]); + + return count; +} + +static struct device_attribute xp70_device_attrs[] = { + __ATTR_RO(xp70_data), + __ATTR(trace_allowed, S_IRUGO | S_IWUSR, xp70_trace_allowed_show, + xp70_trace_allowed_store), + __ATTR_NULL +}; + +static void xp70_buffer_wqtask(struct work_struct *data) +{ + int i; + int first_index = info->trace_status.prev_block_id + 1; + int count; + mutex_lock(&info->lock); + + if (!info->trace_buffer) + goto out_err; + + dev_dbg(info->dev, "xp70 overwrite_cnt=%d (0x%x) blk_id=%d (0x%x)", + info->trace_buffer->overwrite_count, + info->trace_buffer->overwrite_count, + info->trace_buffer->block_id, info->trace_buffer->block_id); + + /* check if trace already started */ + if (info->trace_buffer->block_id == XP70_MAX_BLOCK_ID || + info->trace_buffer->block_id == XP70_DEFAULT_MSG_ID || + info->trace_buffer->overwrite_count == XP70_DEFAULT_MSG_ID) + goto out; + + if ((info->trace_buffer->overwrite_count - + info->trace_status.prev_overwrite_count) * XP70_NB_BLOCK + + (info->trace_buffer->block_id - + info->trace_status.prev_block_id) + >= XP70_NB_BLOCK) { + /* overflow case */ + info->trace_status.prev_block_id = + info->trace_buffer->block_id - XP70_NB_BLOCK; + first_index = info->trace_buffer->block_id + 1; + count = XP70_NB_BLOCK; + + pr_info_ratelimited("XP70 trace overflow\n"); + } else if (info->trace_buffer->block_id + >= info->trace_status.prev_block_id) { + count = info->trace_buffer->block_id - + info->trace_status.prev_block_id; + } else { + u32 block_id, prev_block_id, diff; + block_id = (u32)(info->trace_buffer->block_id); + prev_block_id = (u32)(info->trace_status.prev_block_id); + diff = (block_id + XP70_NB_BLOCK) - prev_block_id; + count = (u32)diff; + } + + for (i = first_index; count; count--) { + if (i < 0 || i >= XP70_NB_BLOCK || count > XP70_NB_BLOCK) { + pr_info_ratelimited("trace index out-of-bounds" + "i=%d count=%d XP70_NB_BLOCK=%d\n", + i, count, XP70_NB_BLOCK); + + break; + } + + if (info->trace_buffer->block[i].msg_id != + XP70_DEFAULT_MSG_ID) { + int msg_len = strnlen( + info->trace_buffer->block[i].data, + XP70_BLOCK_SIZE); + + /* zero terminate full length message */ + if (msg_len > 0) { + if (msg_len == XP70_BLOCK_SIZE) + info->trace_buffer->block[i].data[ + XP70_BLOCK_SIZE - 1] = '\0'; + + dev_info(info->dev, "%d %s\n", + info->trace_buffer->block[i].msg_id, + info->trace_buffer->block[i].data); + } + } + + i = (i + 1) % XP70_NB_BLOCK; + } + + info->trace_status.prev_overwrite_count = + info->trace_buffer->overwrite_count; + info->trace_status.prev_block_id = info->trace_buffer->block_id; +out: + /* Schedule work */ + if (!schedule_delayed_work(&info->trace_work, + msecs_to_jiffies(XP70_TIMEOUT_MSEC))) + dev_info(info->dev, "failed to schedule work\n"); + +out_err: + mutex_unlock(&info->lock); + return; +} + +/** +* mmio_probe() - Initialize MMIO Camera resources. +* @pdev: Platform device. +* +* Initialize the module and register misc device. +* +* Returns: +* 0 if there is no err. +* -ENOMEM if allocation fails. +* -EEXIST if device has already been started. +* Error codes from misc_register. +*/ +static int __devinit mmio_probe(struct platform_device *pdev) +{ + int err; + int i; + int ret; + printk(KERN_INFO "%s\n", __func__); + /* Initialize private data. */ + info = kzalloc(sizeof(struct mmio_info), GFP_KERNEL); + + if (!info) { + dev_err(&pdev->dev, "Could not alloc info struct\n"); + err = -ENOMEM; + goto err_alloc; + } + + /* Fill in private data */ + info->pdata = pdev->dev.platform_data; + info->dev = &pdev->dev; + info->pdata->dev = &pdev->dev; + info->misc_dev.minor = MISC_DYNAMIC_MINOR; + info->misc_dev.name = MMIO_NAME; + info->misc_dev.fops = &mmio_fops; + info->misc_dev.parent = pdev->dev.parent; + mutex_init(&info->lock); + info->xshutdown_enabled = 0; + info->xshutdown_is_active_high = 0; + info->trace_allowed = 0; + /* Register Misc character device */ + err = misc_register(&(info->misc_dev)); + + if (err) { + dev_err(&pdev->dev, "Error %d registering misc dev!", err); + goto err_miscreg; + } + + /* Memory mapping */ + info->siabase = ioremap(info->pdata->sia_base, SIA_ISP_MCU_SYS_SIZE); + + if (!info->siabase) { + dev_err(info->dev, "Could not ioremap SIA_BASE\n"); + err = -ENOMEM; + goto err_ioremap_sia_base; + } + + info->crbase = ioremap(info->pdata->cr_base, PAGE_SIZE); + + if (!info->crbase) { + dev_err(info->dev, "Could not ioremap CR_BASE\n"); + err = -ENOMEM; + goto err_ioremap_cr_base; + } + + /* Initialize platform specific data */ + err = info->pdata->platform_init(info->pdata); + + if (err) + goto err_platform_init; + + /* create sysfs entries */ + for (i = 0; attr_name(xp70_device_attrs[i]); i++) { + ret = device_create_file(info->misc_dev.this_device, + &xp70_device_attrs[i]); + + if (ret) { + dev_err(info->dev, "Error creating SYSFS entry" + " %s (%d)\n", xp70_device_attrs[i].attr.name, + ret); + } + } + + INIT_DELAYED_WORK(&info->trace_work, xp70_buffer_wqtask); + dev_info(&pdev->dev, "MMIO driver initialized with minor=%d\n", + info->misc_dev.minor); + return 0; +err_platform_init: + iounmap(info->crbase); +err_ioremap_cr_base: + iounmap(info->siabase); +err_ioremap_sia_base: + misc_deregister(&info->misc_dev); +err_miscreg: + kfree(info); + info = NULL; +err_alloc: + return err; +} + +/** +* mmio_remove() - Release MMIO Camera resources. +* @pdev: Platform device. +* +* Remove misc device and free resources. +* +* Returns: +* 0 if success. +* Error codes from misc_deregister. +*/ +static int __devexit mmio_remove(struct platform_device *pdev) +{ + int err; + int i; + + if (!info) + return 0; + + flush_scheduled_work(); + + /* sysfs parameters */ + for (i = 0; attr_name(xp70_device_attrs[i]); i++) + device_remove_file(info->misc_dev.this_device, + &xp70_device_attrs[i]); + + err = misc_deregister(&info->misc_dev); + + if (err) + dev_err(&pdev->dev, "Error %d deregistering misc dev", err); + + info->pdata->platform_exit(info->pdata); + iounmap(info->siabase); + iounmap(info->crbase); + mutex_destroy(&info->lock); + kfree(info); + info = NULL; + return 0; +} +static struct platform_driver mmio_driver = { + .driver = { + .name = MMIO_NAME, + .owner = THIS_MODULE, + }, + .probe = mmio_probe, + .remove = __devexit_p(mmio_remove) +}; + +/** +* mmio_init() - Initialize module. +* +* Registers platform driver. +*/ +static int __init mmio_init(void) +{ + printk(KERN_INFO "%s\n", __func__); + return platform_driver_register(&mmio_driver); +} + +/** +* mmio_exit() - Remove module. +* +* Unregisters platform driver. +*/ +static void __exit mmio_exit(void) +{ + printk(KERN_INFO "%s\n", __func__); + platform_driver_unregister(&mmio_driver); +} + +module_init(mmio_init); +module_exit(mmio_exit); + +MODULE_AUTHOR("Joakim Axelsson ST-Ericsson"); +MODULE_AUTHOR("Pankaj Chauhan ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MMIO Camera driver"); diff --git a/drivers/staging/nmf-cm/Kconfig b/drivers/staging/nmf-cm/Kconfig new file mode 100644 index 00000000000..9545fd5acd1 --- /dev/null +++ b/drivers/staging/nmf-cm/Kconfig @@ -0,0 +1,12 @@ + +config U8500_CM + tristate "U8500 Component Manager driver" + depends on UX500_SOC_DB8500 + help + This is the Component Manager driver. It is part of the + Nomadik Multiprocessing Framework. + + Note: This option allows the kernel developers to build + the driver in kernel to ease there life. By default, this driver + must be built outside this kernel source tree. + diff --git a/drivers/staging/nmf-cm/Make.config b/drivers/staging/nmf-cm/Make.config new file mode 100644 index 00000000000..ccbc150158b --- /dev/null +++ b/drivers/staging/nmf-cm/Make.config @@ -0,0 +1,8 @@ +# Copyright (C) ST-Ericsson SA 2011. All rights reserved. +# This code is ST-Ericsson proprietary and confidential. +# Any use of the code for whatever purpose is subject to +# specific written permission of ST-Ericsson SA. + +#CM driver file to copy but not to compile +CMENGINESRC_COPY_NO_BUILD = cm/engine/elf/src/elfxx.c + diff --git a/drivers/staging/nmf-cm/Makefile b/drivers/staging/nmf-cm/Makefile new file mode 100644 index 00000000000..b1a5b9afe1f --- /dev/null +++ b/drivers/staging/nmf-cm/Makefile @@ -0,0 +1,99 @@ +# +# Copyright (C) ST-Ericsson SA 2010 +# Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. +# License terms: GNU General Public License (GPL), version 2. +# + +# +# Rules to build kernel modules +# +ifneq ($(findstring KERNELRELEASE,$(.VARIABLES)),) + + # $(src): current relative dir; $(kbuild-dir): cur absolute dir + ifdef kbuild-dir + SRCDIR = $(realpath $(kbuild-dir)) + else + SRCDIR = $(realpath $(src)) + endif + include $(SRCDIR)/Make.config + ifndef FIXED_CPPFLAGS + # In Android env, we can not depend on files that are out of kernel tree. + # and thus we can't include $(SRCDIR)/../../../../mmenv/SharedARMFlags.mk + # where FIXED_CPPFLAGS is defined. + # So, define FIXED_CPPFLAGS here + FIXED_CPPFLAGS=-D__STN_8500=30 -DLINUX -D__ARM_LINUX + endif + EXTRA_CFLAGS := -I$(SRCDIR) $(FIXED_CPPFLAGS) + EXTRA_CFLAGS += -Wall -Werror + #EXTRA_CFLAGS += -DCM_DEBUG_ALLOC + + # + # CM object files to compile with + # + GENERIC_CM_FILES:=$(shell cd $(SRCDIR); find cm -name "*.c") + GENERIC_CM_FILES := $(filter-out $(CMENGINESRC_COPY_NO_BUILD), $(GENERIC_CM_FILES)) + + CM_OBJS := $(GENERIC_CM_FILES:.c=.o) + CM_OBJS += cmld.o cm_syscall.o osal-kernel.o cm_service.o cm_debug.o configuration.o + CM_OBJS += cm_dma.o + + obj-$(CONFIG_U8500_CM) := cm.o + + #Note: build system prepends the $(PWD) directory to these objects paths + cm-objs := $(CM_OBJS) + +else + + # CM module is built in kernel in android env + # or as module otherwise (OSI env, ...) + export CONFIG_U8500_CM ?= m + + ifeq ($(findstring install,$(MAKECMDGOALS)),) + # If not only performing install then include needed files for build + include $(MM_MAKEFILES_DIR)/SharedARMFlags.mk + export FIXED_CPPFLAGS + -include $(MM_MAKEFILES_DIR)/KernelConfig.mk + + ifeq ($(findstring clean,$(MAKECMDGOALS)),) + ifndef KERNEL_BUILD_DIR + $(error KERNEL_BUILD_DIR not defined) + endif + endif + endif + + include $(MM_MAKEFILES_DIR)/SharedConfig.mk + + module: + $(MAKE) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_BUILD_DIR) \ + M=$(PWD) INSTALL_HEADER_DIR=$(INSTALL_HEADER_DIR) \ + modules + + all: module + $(MAKE) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_BUILD_DIR) \ + M=$(PWD) INSTALL_HEADER_DIR=$(INSTALL_HEADER_DIR) \ + INSTALL_MOD_PATH=$(PWD)/lib/$(PLATFORM) \ + modules_install + rm -f $(PWD)/lib/$(PLATFORM)/lib/modules/*/modules.* + + # + # Rules to clean and install + # + clean: + @rm -rf $(PLATFORM) $(CM_OBJS) .built-in.o.cmd .cm*o.cmd Module.symvers \ + .tmp_versions modules.order cm.ko cm.o cm.mod.* lib \ + $(foreach f,$(CM_OBJS), $(dir $f).$(notdir $f).cmd) + + realclean: clean + $(foreach platform, \ + $(shell grep property ../../component/component.xml | cut -d\" -f 4), \ + rm -rf $(platform);) + @rm -rf *~ + + install: + $(GEN_LN) -d lib/$(PLATFORM)/lib $(INSTALL_LIB_DIR)/lib + + uninstall: + $(GEN_LN) -r -d lib/$(PLATFORM)/lib $(INSTALL_LIB_DIR)/lib + +endif #ifdef KERNELRELEASE + diff --git a/drivers/staging/nmf-cm/cm/engine/api/channel_engine.h b/drivers/staging/nmf-cm/cm/engine/api/channel_engine.h new file mode 100644 index 00000000000..19353ee7328 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/channel_engine.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief Communication Component Manager internal API type. + */ + +#ifndef CHANNEL_ENGINE_H +#define CHANNEL_ENGINE_H + +#include <nmf/inc/channel_type.h> +#include <nmf/inc/service_type.h> +#include <cm/engine/communication/inc/communication_type.h> + +/*! + * \brief Internal channel identification. + * + * Same as t_nmf_channel meaning but this the channel used internaly by + * OS Integration part + * + * \ingroup CM_OS_API + */ +typedef t_uint32 t_os_channel; + +/*! + * \brief Invalid value for os_channel + * + * Invalid value for os channel. + * + * \ingroup CM_OS_API + */ +#define NMF_OS_CHANNEL_INVALID_HANDLE 0xffffffff + +/*! + * \brief Structure used for storing required parameters for Interface Callback + * messages. + * + * This struture is used internally by CM_GetMessage() and CM_ExecuteMessage() as + * the message content in the given buffer. + * + * \ingroup CM_ENGINE_API + */ +typedef struct { + t_nmf_mpc2host_handle THIS; //!< Context of interface implementation + t_uint32 methodIndex; //!< Method index in interface + char params[1]; //!< Is of variable length concretely +} t_interface_data; + +/*! + * \brief Structure used for storing required parameters for Service Callback + * messages. + * + * This struture is used internally by CM_GetMessage() and CM_ExecuteMessage() as + * the message content in the given buffer. + * + * \ingroup CM_ENGINE_API + */ +typedef struct { + t_nmf_service_type type; //!< Type of the service message + t_nmf_service_data data; +} t_service_data; + +typedef enum { + MSG_INTERFACE, + MSG_SERVICE +} t_message_type; + +/*! + * \brief Structure used for storing required parameters for the internal NMF + * messages. + * + * This struture is used internally by CM_GetMessage() and CM_ExecuteMessage() as + * the message content in the given buffer. + * + * \ingroup CM_ENGINE_API + */ +typedef struct { + t_message_type type; //!< Type of the nmf message + union { + t_interface_data itf; + t_service_data srv; + } data; +} t_os_message; + +/*! + * \brief Structure used for storing required parameters for the internal NMF + * messages. + * + * This struture is used internally by CM_GetMessage() and CM_ExecuteMessage() as + * the message content in the given buffer. + * + * \ingroup CM_ENGINE_API + */ +typedef struct { + t_nmf_channel channel; //!< Channel (required to handle service message) + t_os_message osMsg; +} t_nmf_message; + +#endif /* CHANNEL_ENGINE_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/api/cm_engine.h b/drivers/staging/nmf-cm/cm/engine/api/cm_engine.h new file mode 100644 index 00000000000..0f4c1e4219e --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/cm_engine.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief CM Engine API. + * + * This file contains the Component Manager Engine API. + */ + +/*! + * \defgroup CM_ENGINE_MODULE CM Engine + */ +/*! + * \defgroup CM_ENGINE_API CM Engine API + * + * \note This API is not for user developers, this API is only an internal API. + * + * \warning All parameters in out from this API means that the parameter is a reference to a data that is complete by the call. + * + * This API is provided by CM Engine and shall be required by driver kernel part. + * \ingroup CM_ENGINE_MODULE + */ + +#ifndef CM_ENGINE_H_ +#define CM_ENGINE_H_ + +#include <cm/engine/api/configuration_engine.h> + +#include <cm/engine/api/component_engine.h> + +#include <cm/engine/api/memory_engine.h> + +#include <cm/engine/api/communication_engine.h> + +#include <cm/engine/api/perfmeter_engine.h> + +#include <cm/engine/api/executive_engine_mgt_engine.h> + +#include <cm/engine/api/repository_mgt_engine.h> + +#include <cm/engine/api/domain_engine.h> + +#include <cm/engine/api/migration_engine.h> + +#endif /*CM_ENGINE_H_*/ + diff --git a/drivers/staging/nmf-cm/cm/engine/api/communication_engine.h b/drivers/staging/nmf-cm/cm/engine/api/communication_engine.h new file mode 100644 index 00000000000..477a66a4002 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/communication_engine.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief Communication User Engine API. + * + * This file contains the Communication Engine API for manipulating components. + * + */ +#ifndef COMMUNICATION_ENGINE_H_ +#define COMMUNICATION_ENGINE_H_ + +#include <cm/engine/communication/inc/communication_type.h> + +/*! + * \brief Allocate Event buffer where parameters will be marshalled. + * + * In order to optimize call, this method don't need to be exported to user space, + * but must be used by CM driver. + * + * See \ref HOST2MPC "Host->MPC binding" for seeing an integration example. + * + * \note This method is not called from user space!!! + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_event_params_handle CM_ENGINE_AllocEvent(t_cm_bf_host2mpc_handle host2mpcId); + +/*! + * \brief Push a event in Fifo. + * + * In order to optimize call, this method don't need to be exported to user space, + * but must be used by CM driver. + * + * See \ref HOST2MPC "Host->MPC binding" for seeing an integration example. + * + * \note This method is not called from user space!!! + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_PushEvent(t_cm_bf_host2mpc_handle host2mpcId, t_event_params_handle h, t_uint32 methodIndex); + +/*! + * \brief Push a event in Fifo. + * + * In order to optimize call, this method need to be exported to user space + * and must be implemented by CM driver. + * + * See \ref HOST2MPC "Host->MPC binding" for seeing an integration example. + * + * \note No implementation of this method is provided in kernel CM engine!!! + * + * \ingroup CM_ENGINE_API + */ +PUBLIC t_cm_error CM_ENGINE_PushEventWithSize(t_cm_bf_host2mpc_handle host2mpcId, t_event_params_handle h, t_uint32 size, t_uint32 methodIndex); + +/*! + * \brief Aknowledge a Fifo that the received event has been demarshalled. + * + * In order to optimize call, this method don't need to be exported to user space, + * but must be used by CM driver. + * + * See \ref MPC2HOST "MPC->Host binding" for seeing an integration example. + * + * \note This method is not called from user space!!! + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED void CM_ENGINE_AcknowledgeEvent(t_cm_bf_mpc2host_handle mpc2hostId); + +#endif /*COMMUNICATION_ENGINE_H_*/ diff --git a/drivers/staging/nmf-cm/cm/engine/api/component_engine.h b/drivers/staging/nmf-cm/cm/engine/api/component_engine.h new file mode 100644 index 00000000000..cbd61769597 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/component_engine.h @@ -0,0 +1,403 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief Components Component Manager User Engine API. + * + * This file contains the Component Manager Engine API for manipulating components. + * + */ + +#ifndef COMPONENT_ENGINE_H_ +#define COMPONENT_ENGINE_H_ + +#include <cm/engine/memory/inc/domain_type.h> +#include <cm/engine/component/inc/component_type.h> +#include <cm/engine/communication/inc/communication_type.h> +#include <inc/nmf-limits.h> + +/*! + * \brief Instantiate a new component. + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_InstantiateComponent( + const char templateName[MAX_TEMPLATE_NAME_LENGTH], //!< [in] Null terminated string (Max size=\ref MAX_TEMPLATE_NAME_LENGTH) + t_cm_domain_id domainId, //!< [in] Domain + t_nmf_client_id clientId, //!< [in] Client ID (aka PID) + t_nmf_ee_priority priority, //!< [in] Component priority + const char localName[MAX_COMPONENT_NAME_LENGTH], //!< [in] Null terminated string (Max size=\ref MAX_COMPONENT_NAME_LENGTH) + const char *dataFile, //!< [in] Optional reference on file where component is stored + t_cm_instance_handle *component //!< [out] component + ); + +/*! + * \brief Start a component. + * + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_StartComponent( + t_cm_instance_handle component, + t_nmf_client_id clientId); + +/*! + * \brief Stop a component. + * + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_StopComponent( + t_cm_instance_handle component, + t_nmf_client_id clientId); + +/*! + * \brief Destroy a component. + * + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_DestroyComponent( + t_cm_instance_handle component, + t_nmf_client_id clientId); + +/*! + * \brief Stop and destroy all components belonging to the given client. + * + * \param[in] client + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_FlushComponents( + t_nmf_client_id client); + +/*! + * \brief Bind two components together. + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_BindComponent( + const t_cm_instance_handle client, //!< + const char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH], //!< Null terminated string (Max size=\ref MAX_INTERFACE_NAME_LENGTH). + const t_cm_instance_handle server, //!< + const char providedItfServerName[MAX_INTERFACE_NAME_LENGTH], //!< Null terminated string (Max size=\ref MAX_INTERFACE_NAME_LENGTH). + t_bool traced, //!< FALSE for synchronous binding, TRUE for traced one + t_nmf_client_id clientId, //!< Client ID + const char *dataFileTrace //!< Component file data in case on traced (Note: could be null if file already in cache) + ); + +/*! + * \brief Unbind a component. + * + * \param[in] client + * \param[in] requiredItfClientName Null terminated string (Max size=\ref MAX_INTERFACE_NAME_LENGTH). + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_UnbindComponent( + const t_cm_instance_handle client, + const char * requiredItfClientName, + t_nmf_client_id clientId); + +/*! + * \brief Bind a component to void (silently ignore a call). + * + * \param[in] client + * \param[in] requiredItfClientName Null terminated string (Max size=\ref MAX_INTERFACE_NAME_LENGTH). + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_BindComponentToVoid( + const t_cm_instance_handle client, + const char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH], + t_nmf_client_id clientId); + +/*! + * \brief Bind two components together in an asynchronous way + * (the components can be on the same MPC or on two different MPC) + * + * \param[in] client + * \param[in] requiredItfClientName Null terminated string (Max size=\ref MAX_INTERFACE_NAME_LENGTH). + * \param[in] server + * \param[in] providedItfServerName Null terminated string (Max size=\ref MAX_INTERFACE_NAME_LENGTH). + * \param[in] fifosize + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_BindComponentAsynchronous( + const t_cm_instance_handle client, + const char * requiredItfClientName, + const t_cm_instance_handle server, + const char * providedItfServerName, + t_uint32 fifosize, + t_cm_mpc_memory_type eventMemType, + t_nmf_client_id clientId, + const char *dataFileSkeletonOrEvent, + const char *dataFileStub); + +/*! + * \brief Unbind a component previously binded asynchronously + * + * \param[in] client + * \param[in] requiredItfClientName Null terminated string (Max size=\ref MAX_INTERFACE_NAME_LENGTH). + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_UnbindComponentAsynchronous( + const t_cm_instance_handle client, + const char * requiredItfClientName, + t_nmf_client_id clientId); + +/*! + * \brief Bind the Host to a component. + * + * \param[in] server + * \param[in] providedItfServerName Null terminated string (Max size=\ref MAX_INTERFACE_NAME_LENGTH). + * \param[in] fifosize + * \param[out] host2mpcId + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_BindComponentFromCMCore( + const t_cm_instance_handle server, + const char * providedItfServerName, + t_uint32 fifosize, + t_cm_mpc_memory_type eventMemType, + t_cm_bf_host2mpc_handle *host2mpcId, + t_nmf_client_id clientId, + const char *dataFileSkeleton); + +/*! + * \brief Unbind a component from the Host. + * + * \param[in] host2mpcId + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_UnbindComponentFromCMCore( + t_cm_bf_host2mpc_handle host2mpcId); + +/*! + * \brief Bind a component to the Host, see \ref CM_ENGINE_BindComponentToCMCore. + * + * See \ref MPC2HOST "MPC->Host binding" for seeing an integration example. + * + * \note This method is not called from CM Proxy, its only there for wrapping purpose!!! + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_BindComponentToCMCore( + const t_cm_instance_handle client, + const char *requiredItfClientName, + t_uint32 fifosize, + t_nmf_mpc2host_handle upLayerThis, + const char *dataFileStub, + t_cm_bf_mpc2host_handle *mpc2hostId, + t_nmf_client_id clientId); + +/*! + * \brief Unbind a component to the Host, see \ref CM_ENGINE_UnbindComponentToCMCore. + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_UnbindComponentToCMCore( + const t_cm_instance_handle client, + const char *requiredItfClientName, + t_nmf_mpc2host_handle *upLayerThis, + t_nmf_client_id clientId); + +/*! + * \brief Read a value on an attribute exported by a component instance. + * + * \param[in] component + * \param[in] attrName Null terminated string (Max size=\ref MAX_ATTRIBUTE_NAME_LENGTH). + * \param[out] value + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_ReadComponentAttribute( + const t_cm_instance_handle component, + const char* attrName, + t_uint24 *value); + +/*! + * \brief Get the older component. + * + * \param[in] client + * \param[out] headerComponent + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentListHeader( + const t_nmf_client_id client, + t_cm_instance_handle *headerComponent); + +/*! + * \brief Get the next component. + * + * \param[in] client + * \param[in] prevComponent + * \param[out] nextComponent + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentListNext( + const t_nmf_client_id client, + const t_cm_instance_handle prevComponent, + t_cm_instance_handle *nextComponent); + +/*! + * \brief Get a component description + * + * \param[in] component + * \param[in] templateNameLength + * \param[in] localNameLength + * \param[out] templateName Null terminated string (Size=templateNameLength, Max size=\ref MAX_TEMPLATE_NAME_LENGTH). + * \param[out] coreId + * \param[out] localName Null terminated string (Size=localNameLength, Max size=\ref MAX_COMPONENT_NAME_LENGTH). + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentDescription( + const t_cm_instance_handle component, + char *templateName, + t_uint32 templateNameLength, + t_nmf_core_id *coreId, + char *localName, + t_uint32 localNameLength, + t_nmf_ee_priority *priority); + +/*! + * \brief Get number of interface required by a component. + * + * \param[in] component + * \param[out] numberRequiredInterfaces + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentRequiredInterfaceNumber( + const t_cm_instance_handle component, + t_uint8 *numberRequiredInterfaces); + +/*! + * \brief Return information about required interface. + * + * \param[in] component + * \param[in] index + * \param[in] itfNameLength + * \param[in] itfTypeLength + * \param[out] itfName Null terminated string (Size=itfNameLength, Max size=\ref MAX_INTERFACE_NAME_LENGTH). + * \param[out] itfType Null terminated string (Size=itfTypeLength, Max size=\ref MAX_INTERFACE_TYPE_NAME_LENGTH). + * \param[out] collectionSize + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentRequiredInterface( + const t_cm_instance_handle component, + const t_uint8 index, + char *itfName, + t_uint32 itfNameLength, + char *itfType, + t_uint32 itfTypeLength, + t_cm_require_state *requireState, + t_sint16 *collectionSize); + +/*! + * \brief Get the component binded to a required interface. + * + * \param[in] component + * \param[in] itfName Null terminated string (Max size=\ref MAX_INTERFACE_NAME_LENGTH). + * \param[in] serverItfNameLength + * \param[out] server + * \param[out] serverItfName Null terminated string (Size=serverItfNameLength, Max size=\ref MAX_INTERFACE_NAME_LENGTH). + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentRequiredInterfaceBinding( + const t_cm_instance_handle component, + const char *itfName, + t_cm_instance_handle *server, + char *serverItfName, + t_uint32 serverItfNameLength); + +/*! + * \brief Get number of interface provided by a component. + * + * \param[in] component + * \param[out] numberProvidedInterfaces + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentProvidedInterfaceNumber( + const t_cm_instance_handle component, + t_uint8 *numberProvidedInterfaces); + +/*! + * \brief Return information about provided interface. + * + * \param[in] component + * \param[in] index + * \param[in] itfNameLength + * \param[in] itfTypeLength + * \param[out] itfName Null terminated string (Size=itfNameLength, Max size=\ref MAX_INTERFACE_NAME_LENGTH). + * \param[out] itfType Null terminated string (Size=itfTypeLength, Max size=\ref MAX_INTERFACE_TYPE_NAME_LENGTH). + * \param[out] collectionSize + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentProvidedInterface( + const t_cm_instance_handle component, + const t_uint8 index, + char *itfName, + t_uint32 itfNameLength, + char *itfType, + t_uint32 itfTypeLength, + t_sint16 *collectionSize); + +/*! + * \brief Get number of properties of a component. + * + * \param[in] component + * \param[out] numberProperties + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentPropertyNumber( + const t_cm_instance_handle component, + t_uint8 *numberProperties); + +/*! + * \brief Return the name of a property. + * + * \param[in] component + * \param[in] index + * \param[in] propertyNameLength + * \param[out] propertyName Null terminated string (Size=propertyNameLength, Max size=\ref MAX_PROPERTY_NAME_LENGTH). + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentPropertyName( + const t_cm_instance_handle component, + const t_uint8 index, + char *propertyName, + t_uint32 propertyNameLength); + +/*! + * \brief Get property value of a component. + * + * \param[in] component + * \param[in] propertyName + * \param[in] propertyValueLength + * \param[out] propertyValue Null terminated string (Size=propertyValueLength, Max size=\ref MAX_PROPERTY_VALUE_LENGTH). + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetComponentPropertyValue( + const t_cm_instance_handle component, + const char *propertyName, + char *propertyValue, + t_uint32 propertyValueLength); + +#endif /*COMPONENT_ENGINE_H_*/ diff --git a/drivers/staging/nmf-cm/cm/engine/api/configuration_engine.h b/drivers/staging/nmf-cm/cm/engine/api/configuration_engine.h new file mode 100644 index 00000000000..0336f62265e --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/configuration_engine.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief Configuration Component Manager User Engine API. + * + * This file contains the Configuration CM Engine API for manipulating CM. + * + */ + +#ifndef CONFIGURATION_ENGINE_H +#define CONFIGURATION_ENGINE_H + +#include <cm/engine/configuration/inc/configuration_type.h> + +/*! + * \brief Dynamically set some debug parameters of the CM + * + * \param[in] aCmdID The command for the parameter to update + * \param[in] aParam The actual value to set for the given command + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_SetMode(t_cm_cmd_id aCmdID, t_sint32 aParam); + +#endif /* CONFIGURATION_ENGINE_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/api/control/configuration_engine.h b/drivers/staging/nmf-cm/cm/engine/api/control/configuration_engine.h new file mode 100644 index 00000000000..a9543a2af39 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/control/configuration_engine.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief Configuration Component Manager User Engine API. + * + * This file contains the Configuration CM Engine API for manipulating CM. + */ + +#ifndef CONTROL_CONFIGURATION_ENGINE_H +#define CONTROL_CONFIGURATION_ENGINE_H + +#include <cm/engine/memory/inc/domain_type.h> +#include <cm/engine/memory/inc/memory_type.h> +#include <cm/engine/communication/inc/communication_type.h> + +/*****************************************************************************************/ +/* Component Manager dedicated (for Configuration purpose) structured types definition */ +/*****************************************************************************************/ + +/*! + * \brief Description of the Nomadik HW mapping configuration + * + * Describe the Nomadik mapping that is to say: + * - the ESRAM memory managed by the CM (The ESRAM address space SHALL BE declared as non cacheable, non bufferable inside host MMU table) + * - the mapping of the System HW Semaphore IP + * \ingroup CM_ENGINE_CONTROL_API + */ +typedef struct { + t_nmf_memory_segment esramDesc; //!< Description of the ESRAM memory mapping into Nomadik SOC + t_cm_system_address hwSemaphoresMappingBaseAddr; //!< Description of the System HW Semaphores IP mapping into Nomadik SOC +} t_nmf_hw_mapping_desc; + +/*! + * @defgroup t_nmf_nomadik_version t_nmf_nomadik_version + * \brief Description of the various supported Nomadik SOC version + * @{ + * \ingroup CM_ENGINE_CONTROL_API + */ +typedef t_uint8 t_nmf_nomadik_version; //!< Fake enumeration type +#define NOMADIK_8810 ((t_nmf_nomadik_version)0) //!< STn8810 chip (any cut) +#define NOMADIK_8815A0 ((t_nmf_nomadik_version)1) //!< STn8815 chip (cut A0) +#define NOMADIK_8815 ((t_nmf_nomadik_version)2) //!< STn8815 chip (other cuts) +#define NOMADIK_8820 ((t_nmf_nomadik_version)3) //!< STn8820 chip +#define NOMADIK_8500 ((t_nmf_nomadik_version)4) //!< STn8500 chip +/* @} */ + +/*! + * \brief Description of the configuration parameters of the Component Manager + * \ingroup CM_ENGINE_CONTROL_API + */ +typedef struct { + t_nmf_coms_location comsLocation; //!< Configure where CM Communications objects are put (see \ref t_nmf_coms_location) +} t_nmf_config_desc; + +/*! + * @defgroup t_nmf_power_ctx t_nmf_power_ctx + * \brief Definition of the CM-engine context + * + * OS integrator uses this value to known the context where the associated OSAL routine is called + * + * @{ + * \ingroup CM_ENGINE_CONTROL_API + */ + +typedef t_uint32 t_nmf_power_ctx; //!< Fake enumeration type +#define PWR_FLUSH_REQ_INTERRUPT_CTX ((t_nmf_power_ctx)0x00) //!< Interrupt context - called by \ref CM_ProcessMpcEvent +#define PWR_FLUSH_REQ_NORMAL_CTX ((t_nmf_power_ctx)0x01) //!< Normal context (CM user call) + +/* @} */ + + +/****************************************************************************************************************/ +/* Component Manager dedicated (for Media Processors Cores Configuration purpose) structured types definition */ +/****************************************************************************************************************/ +/*! + * @defgroup t_nmf_executive_engine_id t_nmf_executive_engine_id + * \brief Identification of the Media Processor Executive Engine to deploy + * @{ + * \ingroup CM_ENGINE_CONTROL_API + */ +typedef t_uint8 t_nmf_executive_engine_id; //!< Fake enumeration type +#define SYNCHRONOUS_EXECUTIVE_ENGINE ((t_nmf_executive_engine_id)0) //!< MPC Synchronous executive engine +#define HYBRID_EXECUTIVE_ENGINE ((t_nmf_executive_engine_id)1) //!< MPC Hybrid synchronous executive engine +/* @} */ + +/*! + * @defgroup t_nmf_semaphore_type_id t_nmf_semaphore_type_id + * \brief Definition of which type semaphore shall be used for the given Media Processor communication mechanism + * @{ + * \ingroup CM_ENGINE_CONTROL_API + */ +typedef t_uint8 t_nmf_semaphore_type_id; //!< Fake enumeration type +#define LOCAL_SEMAPHORES ((t_nmf_semaphore_type_id)0) //!< Embedded MMDSP macrocell semaphore, so CM_ProcessMpcEvent(<coreId>) shall be called under ISR connected to local MMDSP IRQ0 +#define SYSTEM_SEMAPHORES ((t_nmf_semaphore_type_id)1) //!< Shared system HW Semaphores, so CM_ProcessMpcEvent(ARM_CORE_ID) shall be called under ISR connected to shared HW Sem Host IRQ +/* @} */ + + +/*! + * \brief Opaque type for allocator, returned at CM configuration. + */ +typedef t_uint32 t_cfg_allocator_id; + +/********************************************************************************/ +/* Configuration Component Manager API prototypes */ +/********************************************************************************/ + +/*! + * \brief Initialisation part + * + * This routine initialize and configure the Component Manager. + * + * \param[in] pNmfHwMappingDesc hardware mapping description + * \param[in] pNmfConfigDesc NMF (mainly CM) Configuration description + * + * \exception TBD + * \return exception number. + * + * \warning The ESRAM address space SHALL BE declared as non cacheable, non bufferable inside host MMU table + * + * \ingroup CM_ENGINE_CONTROL_API + */ +PUBLIC t_cm_error CM_ENGINE_Init( + const t_nmf_hw_mapping_desc *pNmfHwMappingDesc, + const t_nmf_config_desc *pNmfConfigDesc + ); + + +/*! + * \brief Media Processor core initialisation part + * + * This routine configures a given Media Processor core + * + * \param[in] coreId Media Processor identifier + * \param[in] executiveEngineId Media Processor Executive Engine identifier + * \param[in] semaphoreTypeId Media Processor semaphores (to be used by communication mechanism) identifier + * \param[in] nbYramBanks is the number of tcm ram banks to reserved for y memory + * \param[in] mediaProcessorMappingBaseAddr Media Processor mapping into host CPU addressable space + * \param[in] commDomain Domain for allocating communication FIFOs + * \param[in] eeDomain Domain for EE instantiation + * \param[in] sdramCodeAllocId Allocator Id for the SDRAM Code segment + * \param[in] sdramDataAllocId Allocator Id for the SDRAM Data segment + * + * \exception TBD + * \return exception number. + * + * \warning The Media Processor mapping address space SHALL BE declared as non cacheable, non bufferable inside host MMU table + * + * \ingroup CM_ENGINE_CONTROL_API + */ +PUBLIC t_cm_error CM_ENGINE_ConfigureMediaProcessorCore( + t_nmf_core_id coreId, + t_nmf_executive_engine_id executiveEngineId, + t_nmf_semaphore_type_id semaphoreTypeId, + t_uint8 nbYramBanks, + const t_cm_system_address *mediaProcessorMappingBaseAddr, + const t_cm_domain_id eeDomain, + const t_cfg_allocator_id sdramCodeAllocId, + const t_cfg_allocator_id sdramDataAllocId + ); + +/*! + * \brief Configure a memory segment for later + * + * \exception TBD + * \return TBD + * + * \warning + * + * \ingroup CM_ENGINE_CONTROL_API + */ +PUBLIC t_cm_error CM_ENGINE_AddMpcSdramSegment( + const t_nmf_memory_segment *pDesc, //!< [in] Memory segment description. + t_cfg_allocator_id *allocId, //!< [out] Identifier of the created allocator. + const char *memoryname //!< [in] Memory purpose name + ); + +/********************************************************************************/ +/* Destruction Component Manager API prototypes */ +/********************************************************************************/ +/*! + * \brief Destruction part + * + * This routine destroyes and releases all resources used by the Component Manager. + * + * \ingroup CM_ENGINE_CONTROL_API + */ +PUBLIC void CM_ENGINE_Destroy(void); + + +#endif /* CONTROL_CONFIGURATION_ENGINE_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/api/control/control_engine.h b/drivers/staging/nmf-cm/cm/engine/api/control/control_engine.h new file mode 100644 index 00000000000..1d823b27fc1 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/control/control_engine.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief CM Engine API. + * + * This file contains the Component Manager Engine API. + */ +/*! + * \defgroup CM_ENGINE_CONTROL_API CM Engine Control API + * \note This API is not for OS integrator, it's only for low level system integration. + * \ingroup CM_ENGINE_MODULE + */ + +#ifndef CM_CONTROL_H_ +#define CM_CONTROL_H_ + +#include <cm/engine/api/control/configuration_engine.h> + +#include <cm/engine/api/control/irq_engine.h> + +#include <cm/engine/api/control/power_engine.h> + +#endif /*CM_CONTROL_H_*/ diff --git a/drivers/staging/nmf-cm/cm/engine/api/control/irq_engine.h b/drivers/staging/nmf-cm/cm/engine/api/control/irq_engine.h new file mode 100644 index 00000000000..e3974764e91 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/control/irq_engine.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief NMF API for interrupt handler. + * + * This file contains the Component Manager API for interrupt handler. + */ +#ifndef CONTROL_IRQ_ENGINE_H +#define CONTROL_IRQ_ENGINE_H + +#include <share/inc/nmf.h> +#include <cm/inc/cm_type.h> +#include <nmf/inc/service_type.h> +#include <ee/api/trace.idt> + +/*! + * \brief MPCs -> HOST communication handler + * + * This routine shall be integrated as interrupt handler into the OS + * + * If the given Media Processor Core has been configured (through CM_ConfigureMediaProcessorCore()) as using \ref LOCAL_SEMAPHORES, then + * the NMF communication mechanism will use the embedded MMDSP macrocell semaphore, + * so CM_ProcessMpcEvent(<\e coreId>) shall be called under ISR connected to local MMDSP IRQ0, with the related \e coreId as parameter. + * + * If the given Media Processor Core has been configured (through CM_ConfigureMediaProcessorCore()) as using \ref SYSTEM_SEMAPHORES, then + * the NMF communication mechanism will use the shared system HW Semaphores, + * so CM_ProcessMpcEvent(\ref ARM_CORE_ID) shall be called under ISR connected to shared HW Sem Host IRQ, with \ref ARM_CORE_ID as parameter. + * + * NB: A Media Processor Core belonging to the distribution pool shall be configured with \ref SYSTEM_SEMAPHORES + * + * \see t_nmf_semaphore_type_id description + * + * \param[in] coreId identification of the source of the interrupt + * + * \ingroup CM_ENGINE_CONTROL_API + */ +PUBLIC IMPORT_SHARED void CM_ProcessMpcEvent(t_nmf_core_id coreId); + +/*! + * \brief Service type + * + * \note We used an enumeration in structure since this description remain inside the kernel + * and we assume that everything in the kernel is compile with same compiler and option. + * + * \ingroup CM_ENGINE_CONTROL_API + */ +typedef enum { // Allowed since i + CM_MPC_SERVICE_NONE = 0, //!< No service found + CM_MPC_SERVICE_PANIC = 1, //!< Panic service found + CM_MPC_SERVICE_PRINT = 2, //!< Print service found + CM_MPC_SERVICE_TRACE = 3 //!< Trace service found +} t_cm_service_type; + //!< Service description type +/*! + * \brief Service description data + * + * + * \ingroup CM_ENGINE_CONTROL_API + */ +typedef struct { + union { + t_nmf_panic_data panic; //!< Panic description + struct { + t_uint32 dspAddress; + t_uint32 value1; + t_uint32 value2; + } print; //!< Printf like description + } u; //!< Union of service description +} t_cm_service_description; + +/*! + * \brief MPC Panic handler + * + * This routine shall be called as interrupt handler into the OS. + * + * So CM_getPanicDescription shall be called under ISR connected to local MMDSP IRQ1, with the related \e coreId as parameter. + * + * \param[in] coreId identification of the source of the interrupt + * \param[out] srcType Pointer on service type + * \param[out] srcDescr Pointer on service description + * + * \ingroup CM_ENGINE_CONTROL_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_getServiceDescription( + t_nmf_core_id coreId, + t_cm_service_type *srcType, + t_cm_service_description *srcDescr); + +/*! + * \brief Read a null terminated string inside an MPC + * + * This routine could be used to read the MPC string give as parameter during an CM_NMF_SERVICE_PRINT + * + * \param[in] coreId Identification of the code where read string + * \param[in] dspAddress Address of the string in the MPC + * \param[out] buffer Buffer pointer where returning null terminated string + * \param[in] bufferSize Buffer size + * + * \ingroup CM_ENGINE_CONTROL_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ReadMPCString( + t_nmf_core_id coreId, + t_uint32 dspAddress, + char * buffer, + t_uint32 bufferSize); + +typedef enum { + CM_MPC_TRACE_NONE = 0, + CM_MPC_TRACE_READ = 1, + CM_MPC_TRACE_READ_OVERRUN = 2 +} t_cm_trace_type; + +PUBLIC IMPORT_SHARED t_cm_trace_type CM_ENGINE_GetNextTrace( + t_nmf_core_id coreId, + struct t_nmf_trace *trace); + +#endif /* CONTROL_IRQ_ENGINE_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/api/domain_engine.h b/drivers/staging/nmf-cm/cm/engine/api/domain_engine.h new file mode 100644 index 00000000000..7cc6f33ed90 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/domain_engine.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief Public Component Manager Memory User SYSCALL API. + * + * This file contains the Component Manager SYSCALL API for manipulating domains. + * + */ + +#ifndef __INC_DOMAIN_ENGINE_H +#define __INC_DOMAIN_ENGINE_H + +#include <cm/engine/memory/inc/domain_type.h> + +/*! + * \brief Create a domain. + * + * Create a memory domain for use in the CM for component instantiation and memory allocation. + * + * \param[in] client Id of the client. + * \param[in] domain Description of domain memories. + * \param[out] handle Idetifier of the created domain + * + * \exception CM_INVALID_DOMAIN_DEFINITION + * \exception CM_INTERNAL_DOMAIN_OVERFLOW + * \exception CM_OK + * + * \return Error code. + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_CreateMemoryDomain( + const t_nmf_client_id client, + const t_cm_domain_memory *domain, + t_cm_domain_id *handle + ); + +/*! + * \brief Create a scratch domain. + * + * Create a scratch memory domain. Scratch domains + * are used to perform overlapping allocations. + * + * \param[in] client Id of the client. + * \param[in] parentId Identifier of the parent domain. + * \param[in] domain Description of domain memories. + * \param[out] handle Idetifier of the created domain + * + * \exception CM_INVALID_DOMAIN_DEFINITION + * \exception CM_INTERNAL_DOMAIN_OVERFLOW + * \exception CM_NO_MORE_MEMORY + * \exception CM_OK + * + * \return Error code. + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_CreateMemoryDomainScratch( + const t_nmf_client_id client, + const t_cm_domain_id parentId, + const t_cm_domain_memory *domain, + t_cm_domain_id *handle + ); + +/*! + * \brief Destroy a memory domain. + + * \param[in] handle Domain identifier to destroy. + * + * \exception CM_INVALID_DOMAIN_HANDLE + * \exception CM_OK + * + * \return Error code. + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_DestroyMemoryDomain( + t_cm_domain_id handle); + +/*! + * \brief Destroy all domains belonging to a given client. + * + * \param[in] client + * + * \return Error code. + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_FlushMemoryDomains( + t_nmf_client_id client); + +/*! + * \brief Retrieve the coreId for a given domain. Utility. + + * \param[in] domainId Domain identifier. + * \param[out] coreId Core identifier. + * + * \exception CM_INVALID_DOMAIN_HANDLE Invalid domain handle + * \exception CM_OK + * + * \return Error code. + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetDomainCoreId(const t_cm_domain_id domainId, t_nmf_core_id *coreId); + +#endif /* __INC_DOMAIN_ENGINE_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/api/executive_engine_mgt_engine.h b/drivers/staging/nmf-cm/cm/engine/api/executive_engine_mgt_engine.h new file mode 100644 index 00000000000..9cb8bc1481b --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/executive_engine_mgt_engine.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief CM executive engine management Engine API. + * + * This file contains the Component Manager executive engine management Engine API. + */ +#ifndef CM_EXECUTIVE_ENGINE_MANAGEMENT_ENGINE_H_ +#define CM_EXECUTIVE_ENGINE_MANAGEMENT_ENGINE_H_ + +#include <cm/inc/cm_type.h> + +/*! + * \brief Return executive engine handle for given core + * + * \param[in] coreId The core for which we want executive engine handle. + * \param[out] executiveEngineHandle executive engine instance (null if the executive engine is not loaded) + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetExecutiveEngineHandle( + t_cm_domain_id domainId, + t_cm_instance_handle *executiveEngineHandle); + +#endif /*CM_EXECUTIVE_ENGINE_MANAGEMENT_ENGINE_H_*/ diff --git a/drivers/staging/nmf-cm/cm/engine/api/memory_engine.h b/drivers/staging/nmf-cm/cm/engine/api/memory_engine.h new file mode 100644 index 00000000000..9f5e25b3ebf --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/memory_engine.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief Public Component Manager Memory User Engine API. + * + * This file contains the Component Manager Engine API for manipulating memory. + * + */ + +#ifndef CM_MEMORY_ENGINE_H_ +#define CM_MEMORY_ENGINE_H_ + +#include <cm/engine/memory/inc/domain_type.h> +#include <cm/engine/memory/inc/memory_type.h> + +/*! + * \brief Allocate memory in a Media Processor Core memory + * + * \param[in] domainId + * \param[in] memType + * \param[in] size + * \param[in] memAlignment + * \param[out] pHandle + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_AllocMpcMemory( + t_cm_domain_id domainId, + t_nmf_client_id clientId, //!< [in] Client ID (aka PID) + t_cm_mpc_memory_type memType, + t_cm_size size, + t_cm_mpc_memory_alignment memAlignment, + t_cm_memory_handle *pHandle + ); + + +/*! + * \brief Free a MPC memory block. + * + * \param[in] handle + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_FreeMpcMemory(t_cm_memory_handle handle); + +/*! + * \brief Get the start address of the MPC memory block seen by the host CPU (physical and logical) + * + * The logical system address returned by this method is valid only in kernel space and the physical + * address is accessible only from kernel space too. + * + * \see OSMem "OS Memory management" for seeing an integration example. + * + * \param[in] handle + * \param[out] pSystemAddress + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetMpcMemorySystemAddress( + t_cm_memory_handle handle, + t_cm_system_address *pSystemAddress); + +/*! + * \brief Get the start address of the memory block seen by the Media Processor Core + * + * \param[in] handle + * \param[out] pMpcAddress + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetMpcMemoryMpcAddress( + t_cm_memory_handle handle, + t_uint32 *pMpcAddress); + +/*! + * \brief Get the memory status for given memory type of a given Media Processor Core + * + * \param[in] domainId + * \param[in] memType + * \param[out] pStatus + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetMpcMemoryStatus( + t_cm_domain_id domainId, + t_cm_mpc_memory_type memType, + t_cm_allocator_status *pStatus); + +#endif /* CM_MEMORY_ENGINE_H_ */ + diff --git a/drivers/staging/nmf-cm/cm/engine/api/migration_engine.h b/drivers/staging/nmf-cm/cm/engine/api/migration_engine.h new file mode 100644 index 00000000000..77a266d4459 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/migration_engine.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#ifndef CM_MIGRATION_ENGINE_H +#define CM_MIGRATION_ENGINE_H + +#include <cm/inc/cm_type.h> +#include <cm/engine/memory/inc/domain_type.h> + +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_Migrate(const t_cm_domain_id srcShared, const t_cm_domain_id src, const t_cm_domain_id dst); + +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_Unmigrate(void); + +#endif /* CM_MIGRATION_ENGINE_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/api/perfmeter_engine.h b/drivers/staging/nmf-cm/cm/engine/api/perfmeter_engine.h new file mode 100644 index 00000000000..bead49dc81e --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/perfmeter_engine.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief CM Performance Meter Engine API. + * + * This file contains the Component Manager Performance Meter Engine API. + */ +#ifndef CM_ENGINE_PERFMETER_ENGINE_H_ +#define CM_ENGINE_PERFMETER_ENGINE_H_ + +#include <cm/engine/perfmeter/inc/perfmeter_type.h> + +/*! + * \brief MPC cpu load + * + * \param[in] coreId identification of mpc from which we want cpu load + * \param[out] mpcLoadCounter will contain mpc cpu load counters value if success + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_getMpcLoadCounter( + t_nmf_core_id coreId, + t_cm_mpc_load_counter *mpcLoadCounter); + +/*! + * \brief MPC cpu load + * Same as \ref CM_ENGINE_getMpcLoadCounter() without lock + * + * \param[in] coreId identification of mpc from which we want cpu load + * \param[out] mpcLoadCounter will contain mpc cpu load counters value if success + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_GetMpcLoadCounter( + t_nmf_core_id coreId, + t_cm_mpc_load_counter *mpcLoadCounter); + +#endif /*CM_ENGINE_PERFMETER_ENGINE_H_*/ diff --git a/drivers/staging/nmf-cm/cm/engine/api/repository_mgt_engine.h b/drivers/staging/nmf-cm/cm/engine/api/repository_mgt_engine.h new file mode 100644 index 00000000000..b63c60d85eb --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/api/repository_mgt_engine.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief Repository Component Manager User Engine API. + * + * This file contains the Component Manager Engine API for manipulating the components files. + */ + +#ifndef REPOSITORY_MGT_ENGINE_H_ +#define REPOSITORY_MGT_ENGINE_H_ + +#include <inc/nmf-limits.h> +#include <cm/engine/repository_mgt/inc/repository_type.h> + +/*! + * \brief Get the name(s) of the component(s) to load. + * + * \param[in] client Handle of the client component (optional) + * \param[in] requiredItfClientName Null terminated string (Max size=\ref MAX_INTERFACE_NAME_LENGTH) (optional). + * \param[in] server Handle of the server component (optional) + * \param[in] providedItfServerName Null terminated string (Max size==\ref MAX_INTERFACE_NAME_LENGTH) (optional). + * \param[out] fileList List of required component(s). + * \param[in,out] listSize Initial size of the list as input. Updated with the number of entries really used. + * \param[out] type Interface type of the client required or server provided interface. Null terminated string (Max size=\ref MAX_INTERFACE_TYPE_NAME_LENGTH) (optional) . + * \param[out] methodNumber Number of method in the interface type of the client required interface. (only used when called from CM_BindComponentToUser) (optional) + * + * \note It returns the component(s) name(s) to load, depending on the first four parameters. + * + * - If all 4 are NULL, it returns the name of the Executive Engine components to load + * - If 'client' is NULL, it returns the name of the required components for a Bind From CMCore. + * - If 'server' is NULL, it returns the name of the required components for a Bind To CMCore. + * - If none is NULL, it returns the name of the required components for an asynchronous binding + * + * The names are returned in fileList, whose initial size is specified in listSize. + * (sizeList must be the number of provided entries of \ref MAX_INTERFACE_TYPE_NAME_LENGTH length + * If not enough space is provided, CM_NO_MORE_MEMORY is returned + * + * sizeList is updated with the number entries really filled. + * + * This method is also used to retrieve the interface type when called from CM_BindComponentToUser and CM_BindComponentFromUser + * and the number of methods when called from CM_BindComponentToUser. + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_GetRequiredComponentFiles( + // IN + t_action_to_do action, + const t_cm_instance_handle client, + const char *requiredItfClientName, + const t_cm_instance_handle server, + const char *providedItfServerName, + // OUT component to be pushed + char fileList[][MAX_INTERFACE_TYPE_NAME_LENGTH], + // IN max component allowed to be pushed + t_uint32 listSize, + // OUT interface information + char type[MAX_INTERFACE_TYPE_NAME_LENGTH], + t_uint32 *methodNumber); + +/*! + * \brief Push a component into the CM Component Cache. + * + * \param[in] name Component name, null terminated string (Max size=\ref MAX_INTERFACE_TYPE_NAME_LENGTH) + * \param[in] data Pointer to _user_ data of the component. + * \param[in] size Size of the data. + * + * \note Push a component in the Component Cache + * The 'data' must be provided such a way that they can be freed by a call to OSAL_Free() + * The caller doesn't need and must NOT free the data, even in case of failure. + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_PushComponent(const char *name, const void *data, t_cm_size size); + +/*! + * \brief Remove a component from the CM Component Cache. + * + * \param[in] name Component name, null terminated string (Max size=\ref MAX_INTERFACE_TYPE_NAME_LENGTH) + * + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_cm_error CM_ENGINE_ReleaseComponent (const char *name); + +/*! + * \brief Check if the CM Component Cache is empty. + * + * \return a boolean value TRUE or FALSE. + * \ingroup CM_ENGINE_API + */ +PUBLIC IMPORT_SHARED t_bool CM_ENGINE_IsComponentCacheEmpty(void); +#endif /*REPOSITORY_MGT_ENGINE_H_*/ diff --git a/drivers/staging/nmf-cm/cm/engine/communication/fifo/inc/nmf_fifo_arm.h b/drivers/staging/nmf-cm/cm/engine/communication/fifo/inc/nmf_fifo_arm.h new file mode 100644 index 00000000000..0463d6a71a5 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/communication/fifo/inc/nmf_fifo_arm.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + */ +#ifndef __INC_NMF_FIFO_ARM +#define __INC_NMF_FIFO_ARM + +#include <cm/inc/cm_type.h> +#include <share/communication/inc/nmf_fifo_desc.h> +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/component/inc/instance.h> +#include <cm/engine/memory/inc/domain.h> + +/* + * ARM Fifo descriptor (encapsulate the share one) + */ +typedef struct +{ + t_uint32 magic; + t_memory_handle chunkHandle; + t_nmf_core_id pusherCoreId; + t_nmf_core_id poperCoreId; + t_shared_addr dspAdress; + t_dsp_address_info dspAddressInfo; + t_nmf_fifo_desc *fifoDesc; //used for all fifo operations and systematically updated by the migrated offset (see cm_AllocEvent) + t_nmf_fifo_desc *fifoDescShadow; //shadow desc, is used to restore state after migration and perform the update of the real desc + + // ExtendedField + t_memory_handle extendedFieldHandle; + t_shared_field *extendedField; +} t_nmf_fifo_arm_desc; + +PUBLIC t_uint32 fifo_isFifoIdValid(t_nmf_fifo_arm_desc *pArmFifo); +PUBLIC t_nmf_fifo_arm_desc* fifo_alloc( + t_nmf_core_id pusherCoreId, t_nmf_core_id poperCoreId, + t_uint16 size_in_16bit, t_uint16 nbElem, t_uint16 nbExtendedSharedFields, + t_dsp_memory_type_id memType, t_dsp_memory_type_id memExtendedFieldType, t_cm_domain_id domainId); +PUBLIC void fifo_free(t_nmf_fifo_arm_desc *pArmFifo); +PUBLIC t_uint16 fifo_normalizeDepth(t_uint16 requestedDepth); + +PUBLIC t_shared_addr fifo_getAndAckNextElemToWritePointer(t_nmf_fifo_arm_desc *pArmFifo); +PUBLIC t_shared_addr fifo_getAndAckNextElemToReadPointer(t_nmf_fifo_arm_desc *pArmFifo); +PUBLIC t_shared_addr fifo_getNextElemToWritePointer(t_nmf_fifo_arm_desc *pArmFifo); +PUBLIC t_shared_addr fifo_getNextElemToReadPointer(t_nmf_fifo_arm_desc *pArmFifo); +PUBLIC void fifo_acknowledgeRead(t_nmf_fifo_arm_desc *pArmFifo); +PUBLIC void fifo_acknowledgeWrite(t_nmf_fifo_arm_desc *pArmFifo); +PUBLIC void fifo_coms_acknowledgeWriteAndInterruptGeneration(t_nmf_fifo_arm_desc *pArmFifo); + +PUBLIC t_cm_error fifo_params_setSharedField(t_nmf_fifo_arm_desc *pArmFifo, t_uint32 sharedFieldIndex, t_shared_field value); + +#endif /* __INC_NMF_FIFO_ARM */ diff --git a/drivers/staging/nmf-cm/cm/engine/communication/fifo/src/nmf_fifo_arm.c b/drivers/staging/nmf-cm/cm/engine/communication/fifo/src/nmf_fifo_arm.c new file mode 100644 index 00000000000..48d7f9e9f03 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/communication/fifo/src/nmf_fifo_arm.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + */ +#include <share/communication/inc/nmf_fifo_desc.h> +#include <cm/engine/semaphores/inc/semaphores.h> +#include <cm/engine/component/inc/instance.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> +#include "../inc/nmf_fifo_arm.h" + +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/trace/inc/trace.h> + +/* define value of fifo magic number */ +#define NMF_FIFO_MAGIC_NB 0xF1F0BEEF + +PRIVATE t_uint16 fifo_getCount( + t_uint16 writeIndex, + t_uint16 readIndex, + t_uint16 fifoSize +) +{ + if (writeIndex >= readIndex) {return writeIndex - readIndex;} + else {return fifoSize - readIndex + writeIndex;} +} + +PRIVATE t_uint16 fifo_incrementIndex( + t_uint16 index, + t_uint16 wrappingValue +) +{ + if (++index == wrappingValue) {index = 0;} + + return index; +} + +PUBLIC t_uint16 fifo_normalizeDepth(t_uint16 requestedDepth) +{ + /* with new implementation we don't align on power of two */ + return requestedDepth; +} + +PUBLIC t_nmf_fifo_arm_desc* fifo_alloc( + t_nmf_core_id pusherCoreId, t_nmf_core_id poperCoreId, + t_uint16 size_in_16bit, t_uint16 nbElem, t_uint16 nbExtendedSharedFields, + t_dsp_memory_type_id memType, t_dsp_memory_type_id memExtendedFieldType, t_cm_domain_id domainId) +{ + t_uint16 realNbElem = nbElem + 1;/* we need one more elem in new implementation */ + t_uint16 sizeToAlloc = sizeof(t_nmf_fifo_desc) + ((size_in_16bit<<1)*realNbElem); + t_nmf_fifo_arm_desc *pArmFifoDesc; + + pArmFifoDesc = (t_nmf_fifo_arm_desc*)OSAL_Alloc(sizeof (t_nmf_fifo_arm_desc)); + if (pArmFifoDesc == NULL) + goto errorde; + + pArmFifoDesc->chunkHandle = cm_DM_Alloc(domainId, memType, + (sizeToAlloc/2), CM_MM_ALIGN_2WORDS, TRUE); /* size in 16-bit since we use EXT16 memory */ + if (pArmFifoDesc->chunkHandle == INVALID_MEMORY_HANDLE) + goto errorsh; + + pArmFifoDesc->magic = NMF_FIFO_MAGIC_NB; + pArmFifoDesc->pusherCoreId = pusherCoreId; + pArmFifoDesc->poperCoreId = poperCoreId; + + pArmFifoDesc->fifoDesc = (t_nmf_fifo_desc *)cm_DSP_GetHostLogicalAddress(pArmFifoDesc->chunkHandle); + cm_DSP_GetDspAddress(pArmFifoDesc->chunkHandle, &pArmFifoDesc->dspAdress); + + pArmFifoDesc->fifoDescShadow = pArmFifoDesc->fifoDesc; + cm_DSP_GetDspDataAddressInfo(cm_DM_GetDomainCoreId(domainId), pArmFifoDesc->dspAdress, &pArmFifoDesc->dspAddressInfo); + + pArmFifoDesc->extendedFieldHandle = INVALID_MEMORY_HANDLE; + pArmFifoDesc->extendedField = NULL; + + pArmFifoDesc->fifoDesc->elemSize = size_in_16bit; + pArmFifoDesc->fifoDesc->fifoFullValue = nbElem; + pArmFifoDesc->fifoDesc->wrappingValue = realNbElem; + + pArmFifoDesc->fifoDesc->semId = cm_SEM_Alloc(pusherCoreId, poperCoreId); + pArmFifoDesc->fifoDesc->readIndex = 0; + pArmFifoDesc->fifoDesc->writeIndex = 0; + + LOG_INTERNAL(2, "\n##### Fifo alloc 0x%x (0x%x)\n\n", pArmFifoDesc, pArmFifoDesc->fifoDesc, 0, 0, 0, 0); + + if (nbExtendedSharedFields >= 1) + { + if(poperCoreId == ARM_CORE_ID) + { + /* Optimization: Don't put extended Field in DSP memory since use only by ARM if popper */ + pArmFifoDesc->extendedField = (t_shared_field*)OSAL_Alloc(nbExtendedSharedFields * sizeof(t_shared_field)); + if (pArmFifoDesc->extendedField == NULL) + goto errorex; + + pArmFifoDesc->fifoDesc->extendedField = (t_uint32)pArmFifoDesc->extendedField; + } + else + { + pArmFifoDesc->extendedFieldHandle = cm_DM_Alloc(domainId, memExtendedFieldType, + nbExtendedSharedFields * sizeof(t_shared_field) / 4, CM_MM_ALIGN_WORD, TRUE); + if (pArmFifoDesc->extendedFieldHandle == INVALID_MEMORY_HANDLE) + goto errorex; + + pArmFifoDesc->extendedField = (t_shared_field*)cm_DSP_GetHostLogicalAddress(pArmFifoDesc->extendedFieldHandle); + cm_DSP_GetDspAddress(pArmFifoDesc->extendedFieldHandle, (t_uint32*)&pArmFifoDesc->fifoDesc->extendedField); + } + + pArmFifoDesc->extendedField[EXTENDED_FIELD_BCTHIS_OR_TOP] = (t_shared_field)0; + } + + return pArmFifoDesc; + +errorex: + (void)cm_DM_Free(pArmFifoDesc->chunkHandle, TRUE); +errorsh: + OSAL_Free(pArmFifoDesc); +errorde: + return NULL; +} + +PUBLIC t_uint32 fifo_isFifoIdValid(t_nmf_fifo_arm_desc *pArmFifo) +{ + if (((t_uint32)pArmFifo & CM_MM_ALIGN_WORD) != 0) {return FALSE;} + if (pArmFifo->magic == NMF_FIFO_MAGIC_NB) {return TRUE;} + else {return FALSE;} +} + +PUBLIC void fifo_free(t_nmf_fifo_arm_desc *pArmFifo) +{ + CM_ASSERT(pArmFifo->pusherCoreId != ARM_CORE_ID || pArmFifo->poperCoreId != ARM_CORE_ID); + + pArmFifo->magic = ~NMF_FIFO_MAGIC_NB; + + if(pArmFifo->extendedFieldHandle != INVALID_MEMORY_HANDLE) + (void)cm_DM_Free(pArmFifo->extendedFieldHandle, TRUE); + else if(pArmFifo->extendedField != NULL) + OSAL_Free(pArmFifo->extendedField); + + (void)cm_DM_Free(pArmFifo->chunkHandle, TRUE); + OSAL_Free(pArmFifo); +} + +PUBLIC t_shared_addr fifo_getAndAckNextElemToWritePointer(t_nmf_fifo_arm_desc *pArmFifo) +{ + t_shared_addr retValue; + + retValue = fifo_getNextElemToWritePointer(pArmFifo); + if (retValue != 0) + { + fifo_acknowledgeWrite(pArmFifo); + } + + return retValue; +} + +PUBLIC t_shared_addr fifo_getAndAckNextElemToReadPointer(t_nmf_fifo_arm_desc *pArmFifo) +{ + t_shared_addr retValue; + + retValue = fifo_getNextElemToReadPointer(pArmFifo); + if (retValue != 0) + { + fifo_acknowledgeRead(pArmFifo); + } + + return retValue; +} + +PUBLIC t_shared_addr fifo_getNextElemToWritePointer(t_nmf_fifo_arm_desc *pArmFifo) +{ + t_shared_addr retValue = 0; + t_nmf_fifo_desc *pDesc; + t_uint16 count; + + if ((NULL == pArmFifo) || (NULL == (pDesc = pArmFifo->fifoDesc))) + return 0; + + count = fifo_getCount(pDesc->writeIndex, pDesc->readIndex,pDesc->wrappingValue); + if (count < pDesc->fifoFullValue) + { + retValue = ((t_shared_addr)pDesc + sizeof(t_nmf_fifo_desc) + (pDesc->writeIndex*(pDesc->elemSize<<1))); + } + + return retValue; +} + +PUBLIC t_shared_addr fifo_getNextElemToReadPointer(t_nmf_fifo_arm_desc *pArmFifo) +{ + t_shared_addr retValue = 0; + t_nmf_fifo_desc *pDesc; + t_uint16 count; + + if ((NULL == pArmFifo) || (NULL == (pDesc = pArmFifo->fifoDesc))) + return 0; + + count = fifo_getCount(pDesc->writeIndex, pDesc->readIndex,pDesc->wrappingValue); + if (count != 0) + { + retValue = ((t_shared_addr)pDesc+ sizeof(t_nmf_fifo_desc) + (pDesc->readIndex*(pDesc->elemSize<<1))); + } + + return retValue; +} + +PUBLIC void fifo_acknowledgeRead(t_nmf_fifo_arm_desc *pArmFifo) +{ + t_nmf_fifo_desc *pDesc = pArmFifo->fifoDesc; + + pDesc->readIndex = fifo_incrementIndex(pDesc->readIndex, pDesc->wrappingValue); +} + +PUBLIC void fifo_acknowledgeWrite(t_nmf_fifo_arm_desc *pArmFifo) +{ + t_nmf_fifo_desc *pDesc = pArmFifo->fifoDesc; + + pDesc->writeIndex = fifo_incrementIndex(pDesc->writeIndex, pDesc->wrappingValue); +} + +PUBLIC void fifo_coms_acknowledgeWriteAndInterruptGeneration(t_nmf_fifo_arm_desc *pArmFifo) +{ + t_nmf_fifo_desc *pDesc = pArmFifo->fifoDesc; + + fifo_acknowledgeWrite(pArmFifo); + //Be sure before generate irq that fifo has been updated + OSAL_mb(); + cm_SEM_GenerateIrq[pArmFifo->poperCoreId](pArmFifo->poperCoreId, pDesc->semId); + //cm_SEM_Take[pArmFifo->poperCoreId](pArmFifo->poperCoreId, pDesc->semId); + //cm_SEM_GiveWithInterruptGeneration[pArmFifo->poperCoreId](pArmFifo->poperCoreId, pDesc->semId); +} + +PUBLIC t_cm_error fifo_params_setSharedField(t_nmf_fifo_arm_desc *pArmFifo, t_uint32 sharedFieldIndex, t_shared_field value) +{ + pArmFifo->extendedField[sharedFieldIndex] = value; + + return CM_OK; +} + diff --git a/drivers/staging/nmf-cm/cm/engine/communication/inc/communication.h b/drivers/staging/nmf-cm/cm/engine/communication/inc/communication.h new file mode 100644 index 00000000000..53ab87b7096 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/communication/inc/communication.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Components Management internal methods - Communication part. + * + */ +#ifndef __INC_NMF_COM +#define __INC_NMF_COM + +#include <cm/inc/cm_type.h> +#include <cm/engine/communication/fifo/inc/nmf_fifo_arm.h> +#include <cm/engine/memory/inc/memory.h> + +#include <cm/engine/communication/inc/communication_type.h> + +extern t_dsp_memory_type_id comsLocation; +extern t_dsp_memory_type_id paramsLocation; +extern t_dsp_memory_type_id extendedFieldLocation; + +PUBLIC t_cm_error cm_COM_Init(t_nmf_coms_location comsLocation); +PUBLIC t_cm_error cm_COM_AllocateMpc(t_nmf_core_id coreId); +PUBLIC void cm_COM_InitMpc(t_nmf_core_id coreId); +PUBLIC void cm_COM_FreeMpc(t_nmf_core_id coreId); + +PUBLIC t_cm_error cm_PushEventTrace(t_nmf_fifo_arm_desc*, t_event_params_handle h, t_uint32 methodIndex, t_uint32 isTrace); +PUBLIC t_cm_error cm_PushEvent(t_nmf_fifo_arm_desc *pArmFifo, t_event_params_handle h, t_uint32 methodIndex); +PUBLIC void cm_AcknowledgeEvent(t_nmf_fifo_arm_desc *pArmFifo); +PUBLIC t_event_params_handle cm_AllocEvent(t_nmf_fifo_arm_desc *pArmFifo); + +/*! + * \internal + * \brief Definition of custom value for userTHIS parameter of PostDfc OSAL call + * + * This value is used as 1st parameter of a pPostDfc call to indicate that a given interrupt is linked to an internal Component Manager event + */ +#define NMF_INTERNAL_USERTHIS ((void*)MASK_ALL32) + +typedef void (*t_callback_method)(t_nmf_core_id coreId, t_event_params_handle pParam); + +#endif /* __INC_NMF_COM */ diff --git a/drivers/staging/nmf-cm/cm/engine/communication/inc/communication_type.h b/drivers/staging/nmf-cm/cm/engine/communication/inc/communication_type.h new file mode 100644 index 00000000000..53a6ff39b07 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/communication/inc/communication_type.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Communication Component Manager API type. + */ +#ifndef COMMUNICATION_TYPE_H_ +#define COMMUNICATION_TYPE_H_ + +#include <cm/inc/cm_type.h> + + +/*! + * \brief Buffer type used for (un)marshalling parameters. + * + * This buffer type is used for (un)marshalling paramaters. It can either be a + * shared memory buffer (ESRAM or SDRAM) or a pure host software memory (stack). + + * \ingroup CM_ENGINE_API + */ +typedef t_uint16 *t_event_params_handle; + +/*! + * \brief Component manager handle to Host -> MPC communication. + * + * \ingroup CM_ENGINE_API + */ +typedef t_uint32 t_cm_bf_host2mpc_handle; + +/*! + * \brief Component manager handle to MPC -> Host communication. + * + * \ingroup CM_ENGINE_API + */ +typedef t_uint32 t_cm_bf_mpc2host_handle; + +/*! + * \brief Component manager proxy handle to MPC -> Host skeleton context. + * + * \ingroup CM_ENGINE_API + */ +typedef t_uint32 t_nmf_mpc2host_handle; + +/*! + * @defgroup t_nmf_coms_location t_nmf_coms_location + * \brief Definition of the location of the internal CM communication objects + * + * @{ + * \ingroup CM_ENGINE_API + */ +typedef t_uint8 t_nmf_coms_location; //!< Fake enumeration type +#define COMS_IN_ESRAM ((t_nmf_coms_location)0) //!< All coms objects (coms and params fifos) will be in embedded RAM +#define COMS_IN_SDRAM ((t_nmf_coms_location)1) //!< All coms objects (coms and params fifos) will be in external RAM +/* @} */ + +#endif /*COMMUNICATION_TYPE_H_*/ diff --git a/drivers/staging/nmf-cm/cm/engine/communication/src/communication.c b/drivers/staging/nmf-cm/cm/engine/communication/src/communication.c new file mode 100644 index 00000000000..ead1e090d7c --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/communication/src/communication.c @@ -0,0 +1,328 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#include <cm/inc/cm_type.h> +#include "../inc/communication.h" +#include <share/communication/inc/communication_fifo.h> +#include <cm/engine/api/control/irq_engine.h> +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/communication/fifo/inc/nmf_fifo_arm.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/memory/inc/migration.h> +#include <cm/engine/semaphores/inc/semaphores.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> + +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/trace/inc/xtitrace.h> + +#include <cm/engine/component/inc/initializer.h> + +#define ARM_DSP_EVENT_FIFO_SIZE 128 + +t_dsp_memory_type_id comsLocation; +t_dsp_memory_type_id paramsLocation; +t_dsp_memory_type_id extendedFieldLocation; + +#define __DEBUG + +#ifdef __DEBUG +PRIVATE volatile t_uint32 armdspCounter = 0; +PRIVATE volatile t_uint32 armdspIrqCounter = 0; +PRIVATE volatile t_uint32 dsparmCounter = 0; +PRIVATE volatile t_uint32 dsparmIrqCounter = 0; +#endif /* __DEBUG */ + +t_nmf_fifo_arm_desc* mpc2mpcComsFifoId[NB_CORE_IDS][NB_CORE_IDS]; + +PRIVATE const t_callback_method internalHostJumptable[] = { + processAsyncAcknowledge, + processAsyncAcknowledge, + processAsyncAcknowledge, + processSyncAcknowledge, + processAsyncAcknowledge, + processAsyncAcknowledge, + processAsyncAcknowledge, + processSyncAcknowledge, + processAsyncAcknowledge, + processSyncAcknowledge, + processSyncAcknowledge, // Start sync + processSyncAcknowledge // Stop sync +}; + +PUBLIC t_cm_error cm_COM_Init(t_nmf_coms_location _comsLocation) +{ + t_nmf_core_id coreId, localCoreId; + + /* + * Configure the default location of coms and params fifo (configuration by user) */ + switch(_comsLocation) + { + case COMS_IN_SDRAM: + comsLocation = SDRAM_EXT16; + paramsLocation = SDRAM_EXT16; + extendedFieldLocation = SDRAM_EXT24; + break; + case COMS_IN_ESRAM: + comsLocation = ESRAM_EXT16; + paramsLocation = ESRAM_EXT16; + extendedFieldLocation = ESRAM_EXT24; + break; + default: CM_ASSERT(0); + } + + for (coreId = ARM_CORE_ID; coreId < NB_CORE_IDS; coreId++) + { + for (localCoreId = ARM_CORE_ID; localCoreId < NB_CORE_IDS; localCoreId++) + { + mpc2mpcComsFifoId[coreId][localCoreId] = NULL; + } + } + + return CM_OK; +} + +PUBLIC t_cm_error cm_COM_AllocateMpc(t_nmf_core_id coreId) +{ + t_nmf_core_id localCoreId; + + /* + * Allocation of the coms fifo with neighbor MPCs + * if they are already initialized (known through initializedCoresMask) + */ + for (localCoreId = ARM_CORE_ID; localCoreId < NB_CORE_IDS; localCoreId++) + { + if (localCoreId == coreId) continue; /* no coms fifo with itself ;) */ + if(cm_DSP_GetState(localCoreId)->state != MPC_STATE_BOOTED) continue; + + /* + * coms fifo from other initialized MPCs to the given one + */ + if (mpc2mpcComsFifoId[coreId][localCoreId] != NULL) continue; /* coms fifo already allocated */ + + mpc2mpcComsFifoId[coreId][localCoreId] = fifo_alloc( + coreId, localCoreId, + EVENT_ELEM_SIZE_IN_BYTE/2, ARM_DSP_EVENT_FIFO_SIZE, + 0, comsLocation, extendedFieldLocation, cm_DSP_GetState(coreId)->domainEE + ); + if (mpc2mpcComsFifoId[coreId][localCoreId] == NULL) + goto oom; + + /* + * coms fifo from the given MPC to the other initialized ones + */ + if (mpc2mpcComsFifoId[localCoreId][coreId] != NULL) continue; /* coms fifo already allocated */ + + mpc2mpcComsFifoId[localCoreId][coreId] = fifo_alloc( + localCoreId, coreId, + EVENT_ELEM_SIZE_IN_BYTE/2, ARM_DSP_EVENT_FIFO_SIZE, + 0, comsLocation, extendedFieldLocation, cm_DSP_GetState(coreId)->domainEE + ); + if (mpc2mpcComsFifoId[localCoreId][coreId] == NULL) + goto oom; + } + + return CM_OK; +oom: + cm_COM_FreeMpc(coreId); + ERROR("CM_NO_MORE_MEMORY: fifo_alloc() failed in cm_COM_AllocateMpc()\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; +} + +PUBLIC void cm_COM_InitMpc(t_nmf_core_id coreId) +{ + // Here we assume that attribute are in XRAM, thus we don't need memory type + t_uint32* toNeighborsComsFifoIdSharedVar[NB_CORE_IDS]; + t_uint32* fromNeighborsComsFifoIdSharedVar[NB_CORE_IDS]; + + t_nmf_core_id localCoreId; + + /* + * Initialization of the core identifier of a given Executive Engine + * Used into communication scheme so the init is done here, will be moved MAY BE into EE loading module!!! + */ + cm_writeAttribute(cm_EEM_getExecutiveEngine(coreId)->instance, "semaphores/myCoreId", coreId); + + /* + * Initialization of the coms fifo with the Host for the given coreId + */ + for (localCoreId = FIRST_MPC_ID/* NOT ARM*/; localCoreId <= LAST_CORE_ID; localCoreId++) + { + // Note: This loop will also include coreId in order to fill + if(cm_DSP_GetState(localCoreId)->state != MPC_STATE_BOOTED) continue;/* no coms fifo initialisation with not booted MPC */ + + toNeighborsComsFifoIdSharedVar[localCoreId] = (t_uint32*)cm_getAttributeHostAddr(cm_EEM_getExecutiveEngine(localCoreId)->instance, "comms/toNeighborsComsFifoId"); + + fromNeighborsComsFifoIdSharedVar[localCoreId] = (t_uint32*)cm_getAttributeHostAddr(cm_EEM_getExecutiveEngine(localCoreId)->instance, "comms/fromNeighborsComsFifoId"); + } + + toNeighborsComsFifoIdSharedVar[coreId][ARM_CORE_ID] = mpc2mpcComsFifoId[coreId][ARM_CORE_ID]->dspAdress; + fromNeighborsComsFifoIdSharedVar[coreId][ARM_CORE_ID] = mpc2mpcComsFifoId[ARM_CORE_ID][coreId]->dspAdress; + + for (localCoreId = FIRST_MPC_ID/* NOT ARM*/; localCoreId <= LAST_CORE_ID; localCoreId++) + { + if (localCoreId == coreId) continue; /* no coms fifo with itself ;) */ + if(cm_DSP_GetState(localCoreId)->state != MPC_STATE_BOOTED) continue;/* no coms fifo initialisation with not booted MPC */ + + toNeighborsComsFifoIdSharedVar[coreId][localCoreId] = mpc2mpcComsFifoId[coreId][localCoreId]->dspAdress; + fromNeighborsComsFifoIdSharedVar[localCoreId][coreId] = mpc2mpcComsFifoId[coreId][localCoreId]->dspAdress; + + fromNeighborsComsFifoIdSharedVar[coreId][localCoreId] = mpc2mpcComsFifoId[localCoreId][coreId]->dspAdress; + toNeighborsComsFifoIdSharedVar[localCoreId][coreId] = mpc2mpcComsFifoId[localCoreId][coreId]->dspAdress; + } +} + +PUBLIC void cm_COM_FreeMpc(t_nmf_core_id coreId) +{ + t_nmf_core_id localCoreId; + + for (localCoreId = ARM_CORE_ID; localCoreId < NB_CORE_IDS; localCoreId++) + { + /* + * Free coms fifo from other initialized MPCs to the given one + */ + if ( mpc2mpcComsFifoId[coreId][localCoreId] != NULL) + { + fifo_free(mpc2mpcComsFifoId[coreId][localCoreId]); + mpc2mpcComsFifoId[coreId][localCoreId] = NULL; + } + + /* + * Free coms fifo from the given MPC to the other initialized ones + */ + if ( mpc2mpcComsFifoId[localCoreId][coreId] != NULL) + { + fifo_free(mpc2mpcComsFifoId[localCoreId][coreId]); + mpc2mpcComsFifoId[localCoreId][coreId] = NULL; + } + } +} + +PUBLIC t_event_params_handle cm_AllocEvent(t_nmf_fifo_arm_desc *pArmFifo) + +{ + t_uint32 retValue; + + //migration impacts the ARM-side address of the fifoDesc, + //thus translate the fifo desc adress systematically. + pArmFifo->fifoDesc = (t_nmf_fifo_desc*)cm_migration_translate(pArmFifo->dspAddressInfo.segmentType, (t_shared_addr)pArmFifo->fifoDescShadow); + + retValue = fifo_getAndAckNextElemToWritePointer(pArmFifo); + + return (t_event_params_handle)retValue; +} + +PUBLIC void cm_AcknowledgeEvent(t_nmf_fifo_arm_desc *pArmFifo) +{ + fifo_acknowledgeRead(pArmFifo); +} + +PUBLIC t_cm_error cm_PushEventTrace(t_nmf_fifo_arm_desc *pArmFifo, t_event_params_handle h, t_uint32 methodIndex, t_uint32 isTrace) +{ + t_uint32 retValue; + + retValue = fifo_getNextElemToWritePointer(mpc2mpcComsFifoId[ARM_CORE_ID][pArmFifo->poperCoreId]); + + if(retValue != 0x0) { + t_shared_field *pEvent = (t_shared_field *)retValue; + +#ifdef __DEBUG + armdspCounter++; +#endif /* __DEBUG */ + + pEvent[EVENT_ELEM_METHOD_IDX] = (t_shared_addr)methodIndex; + pEvent[EVENT_ELEM_PARAM_IDX] = pArmFifo->dspAdress + (((t_cm_logical_address)h - (t_cm_logical_address)pArmFifo->fifoDesc) >> 1); //note byte to half-word conversion + pEvent[EVENT_ELEM_EXTFIELD_IDX] = pArmFifo->fifoDesc->extendedField; + + if (isTrace) + { + cm_TRC_traceCommunication( + TRACE_COMMUNICATION_COMMAND_SEND, + ARM_CORE_ID, + pArmFifo->poperCoreId); + } + fifo_coms_acknowledgeWriteAndInterruptGeneration(mpc2mpcComsFifoId[ARM_CORE_ID][pArmFifo->poperCoreId]); + + return CM_OK; + } + + ERROR("CM_MPC_NOT_RESPONDING: FIFO COM full '%s'\n", 0, 0, 0, 0, 0, 0); + return CM_MPC_NOT_RESPONDING; +} + +PUBLIC t_cm_error cm_PushEvent(t_nmf_fifo_arm_desc *pArmFifo, t_event_params_handle h, t_uint32 methodIndex) +{ + return cm_PushEventTrace(pArmFifo,h,methodIndex,1); +} + +static void cmProcessMPCFifo(t_nmf_core_id coreId) +{ + t_shared_field *pEvent; + + while((pEvent = (t_shared_field *)fifo_getNextElemToReadPointer(mpc2mpcComsFifoId[coreId][ARM_CORE_ID])) != NULL) + { + t_event_params_handle pParamsAddr; + t_shared_field *pParamsFifoESFDesc; + + pParamsAddr = (t_event_params_handle)cm_DSP_ConvertDspAddressToHostLogicalAddress( + coreId, + pEvent[EVENT_ELEM_PARAM_IDX]); + pParamsFifoESFDesc = (t_shared_field *)pEvent[EVENT_ELEM_EXTFIELD_IDX]; +#ifdef __DEBUG + dsparmCounter++; +#endif /* __DEBUG */ + + if(pParamsFifoESFDesc[EXTENDED_FIELD_BCTHIS_OR_TOP] == (t_shared_field)NMF_INTERNAL_USERTHIS) + { + internalHostJumptable[pEvent[EVENT_ELEM_METHOD_IDX]](coreId, pParamsAddr); + } + else + { + cm_TRC_traceCommunication( + TRACE_COMMUNICATION_COMMAND_RECEIVE, + ARM_CORE_ID, + coreId); + + OSAL_PostDfc( + pParamsFifoESFDesc[EXTENDED_FIELD_BCTHIS_OR_TOP], + pEvent[EVENT_ELEM_METHOD_IDX], + pParamsAddr, + pParamsFifoESFDesc[EXTENDED_FIELD_BCDESC]); + } + + // [Pwr] mpc2hostComsFifoId value is checked to support the case where + // CM_PostCleanUpAndFlush method is called under interrupt context + // -> mpc2hostComsFifoId can be released. + if (mpc2mpcComsFifoId[coreId][ARM_CORE_ID] != NULL) + fifo_acknowledgeRead(mpc2mpcComsFifoId[coreId][ARM_CORE_ID]); + else + break; + } +} + +PUBLIC EXPORT_SHARED void CM_ProcessMpcEvent(t_nmf_core_id coreId) +{ +#ifdef __DEBUG + dsparmIrqCounter++; +#endif /* __DEBUG */ + + if (coreId != ARM_CORE_ID) + { + /* Acknowledge DSP communication interrupt */ + cm_DSP_AcknowledgeDspIrq(coreId, DSP2ARM_IRQ_0); + + cmProcessMPCFifo(coreId); + } + else + { + while((coreId = cm_HSEM_GetCoreIdFromIrqSrc()) <= LAST_MPC_ID) + cmProcessMPCFifo(coreId); + } +} + diff --git a/drivers/staging/nmf-cm/cm/engine/component/inc/bind.h b/drivers/staging/nmf-cm/cm/engine/component/inc/bind.h new file mode 100644 index 00000000000..325703e3367 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/inc/bind.h @@ -0,0 +1,443 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + * \brief Binding Factories internal API. + * + * \defgroup BF_COMMON Binding factories: Common API + * \defgroup BF_PRIMITIVE Binding Factories: Primitive API + * \defgroup BF_TRACE Binding Factories: Trace API + * \defgroup BF_ASYNCHRONOUS Binding Factories: Asynchronous API + * \defgroup BF_DISTRIBUTED Binding Factories: Distributed API + */ +#ifndef __INC_CM_BIND_H +#define __INC_CM_BIND_H + +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/communication/inc/communication.h> +#include <cm/engine/utils/inc/table.h> + +/** + * \internal + * \ingroup BF_COMMON + * + * \brief Identification number of prefedined Binding Factories + */ +typedef enum { + BF_SYNCHRONOUS, //!< Intra-DSP Synchronous Binding Factory Identifier + BF_TRACE, //!< Intra-DSP trace synchronous Binding Factory Identifier + BF_ASYNCHRONOUS, //!< Intra-DSP Asynchronous Binding Factory Identifier + BF_DSP2HOST, //!< DSP to Host Binding Factory Identifier + BF_HOST2DSP, //!< Host to DSP Binding Factory Identifier + BF_DSP2DSP, //!< DSP to DSP Binding Factory Identifier +} t_bf_info_ID; + +/*! + * \internal + * \brief Description of a provided interface + * + * \ingroup COMPONENT_INTERNAL + */ +typedef struct _t_interface_reference { + const t_component_instance *instance; //!< Component instance that provide this interface + t_uint8 provideIndex; //!< Index of the interface in the provide array + t_uint8 collectionIndex;//!< Index in the collection if provided interface is a collection + t_bf_info_ID bfInfoID; //!< Identification of BF used for creating binding + void* bfInfo; //!< Storage of the binding factory info +} t_interface_reference; + +/** + * \internal + * \ingroup BF_COMMON + * + * Make some basic sanity check for a client: + * - component stopped + * - Interface really required + * + * \param[in] client The client component instance handle. + * \param[in] requiredItfClientName The client required interface name + * \param[out] requiredItf return the required interface (avoid user searching) + */ +t_cm_error cm_checkValidClient( + const t_component_instance* client, + const char* requiredItfClientName, + t_interface_require_description *itfRequire, + t_bool *bindable); +/** + * \internal + * \ingroup BF_COMMON + * + * Make some basic sanity check for a server: + * - Interface really provided + * + * \param[in] server The server component instance handle. + * \param[in] providedItfServerName The server provided interface name + * \param[out] itf return the provided interface (avoid user searching) + */ +t_cm_error cm_checkValidServer( + const t_component_instance* server, + const char* providedItfServerName, + t_interface_provide_description *itfProvide); + +/** + * \internal + * \ingroup BF_COMMON + * + * Make some basic sanity check for a binding: + * - Sanity check for a server + * - Sanity check for a client (and potentially wait initialisation) + * - Provided and required interface matches + * + * \param[in] client The client component instance handle + * \param[in] requiredItfClientName The client required interface name + * \param[in] server The server component instance handle + * \param[in] providedItfServerName The server provided interface name + * \param[out] requiredItf return the required interface (avoid user searching) + * \param[out] itf return the provided interface (avoid user searching) + */ +t_cm_error cm_checkValidBinding( + const t_component_instance* client, + const char* requiredItfClientName, + const t_component_instance* server, + const char* requiredItfServerName, + t_interface_require_description *itfRequire, + t_interface_provide_description *itfProvide, + t_bool *bindable); + +/** + * \internal + * \ingroup BF_COMMON + * + * Make some basic sanity check for each unbinding: + * - Interface really required + * - Component stopped + * + * \param[in] client The client component instance handle + * \param[in] requiredItfClientName The client required interface name + * \param[out] itfRequire return the previously binded required interface (avoid user searching) + * \param[out] itfProvide return the previously binded provided interface (avoid user searching) + * \param[out] bfInfoID return the binding factory identifiant which done the previously bind + * \param[out] bfInfo return the binding factory information which done the previously bind + */ +t_cm_error cm_checkValidUnbinding( + const t_component_instance* client, + const char* requiredItfClientName, + t_interface_require_description *itfRequire, + t_interface_provide_description *itfProvide); + +/** + * \internal + * \ingroup BF_PRIMITIVE + * + * Create a primitive binding between a client to a server interface. + * + * \param[in] itfRequire The client required interface description + * \param[in] itfProvide The server provided interface description + */ +t_cm_error cm_bindInterface( + const t_interface_require_description *itfRequire, + const t_interface_provide_description *itfProvide); + +/** + * \internal + * \ingroup BF_PRIMITIVE + * + * Unbind a previously binded client. + * + * \param[in] itfRequire The client required interafce description + */ +void cm_unbindInterface( + const t_interface_require_description *itfRequire); + +/** + * \internal + * \ingroup BF_PRIMITIVE + * + * Get a server interface previouly binded to a client + * + * \param[in] client The client component instance handle + * \param[in] requiredItfClientName The client required interface name + * \param[out] itf The server interface + */ +t_cm_error cm_lookupInterface( + const t_interface_require_description *itfRequire, + t_interface_provide_description *itfProvide); + +/** + * \internal + * \ingroup BF_PRIMITIVE + * + * Create a void binding. + * + * \param[in] client The client component instance handle + * \param[in] requiredItfClientName The client required interface name + */ +t_cm_error cm_bindInterfaceToVoid( + const t_interface_require_description *itfRequire); + +/** + * \internal + * \ingroup BF_TRACE + * + * Trace synchronous binding factory Information + */ +typedef struct { + t_component_instance *traceInstance; //!< Trace binding component instance +} t_trace_bf_info; + +/** + * \internal + * \ingroup BF_TRACE + * + * Create a traced binding between a client to a server interface. + * + * \param[in] itfRequire The client required interface description + * \param[in] itfProvide The server provided interface description + */ +t_cm_error cm_bindInterfaceTrace( + const t_interface_require_description *itfRequire, + const t_interface_provide_description *itfProvide, + t_elfdescription *elfhandleTrace); + +/** + * \internal + * \ingroup BF_TRACE + * + * Unbind a previously binded client. + * + * \param[in] itfRequire The client required interafce description + */ +void cm_unbindInterfaceTrace( + const t_interface_require_description *itfRequire, + t_trace_bf_info *bfInfo); + + +/** + * \internal + * \ingroup BF_ASYNCHRONOUS + * + * Asynchronous binding factory Information + */ +typedef struct { + t_component_instance *eventInstance; //!< Event binding component instance + t_memory_handle dspfifoHandle; //!< Memory handle of allocated event fifo (pass to the event binding component) +} t_async_bf_info; + +/** + * \internal + * \ingroup BF_ASYNCHRONOUS + * + * Create a asynchronous binding between a client to a server interface. + * \param[in] client The client component instance handle + * \param[in] requiredItfClientName The client required interface name + * \param[in] itf The server interface + * \param[in] fifosize Number of waited event in the fifo + */ +t_cm_error cm_bindInterfaceAsynchronous( + const t_interface_require_description *itfRequire, + const t_interface_provide_description *itfProvide, + t_uint32 fifosize, + t_dsp_memory_type_id dspEventMemType, + t_elfdescription *elfhandleEvent); +/** + * \internal + * \ingroup BF_ASYNCHRONOUS + * + * Destroy a asynchronous binding between a client to a server interface. + * \param[in] itfRequire the required interface + */ +void cm_unbindInterfaceAsynchronous( + const t_interface_require_description *itfRequire, + t_async_bf_info *bfInfo); + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * Stub information in distributed binding factory (client side) + */ +typedef struct { + t_component_instance *stubInstance; //!< Stub +} t_dspstub_bf_info; + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * Skeleton information in distributed binding factory (server side) + */ +typedef struct { + t_component_instance *skelInstance; //!< Skeleton binding component instance + t_memory_handle dspfifoHandle; //!< Memory handle of allocated event fifo (pass to the event binding component) +} t_dspskel_bf_info; + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * Host to DSP distributed binding factory Information + */ +typedef struct { + t_dspskel_bf_info dspskeleton; //!< Information about the DSP skeleton (server side) + t_nmf_fifo_arm_desc* fifo; //!< Handle of the fifo params + t_nmf_client_id clientId; //!< Client ID of the host client +} t_host2mpc_bf_info; + +/* + * Table of instantiated of host2mpc bindings + */ +extern t_nmf_table Host2MpcBindingTable; /**< list (table) of host2mpc bindings */ + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * Create a Host to DSP distributed binding between a host client interface to a server interface. + * (Not manage in the same way as distributed binding since the Host programming model is not component aware). + * \param[in] itfServer The server interface + * \param[in] fifosize Number of waited event in the fifo + * \param[in] dspEventMemType The type of memory to use + * \param[in] bfInfo info structure + */ +t_cm_error cm_bindComponentFromCMCore( + const t_interface_provide_description *itfProvide, + t_uint32 fifosize, + t_dsp_memory_type_id dspEventMemType, + t_elfdescription *elfhandleSkeleton, + t_host2mpc_bf_info **bfInfo); + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * Destroy a Host to DSP distributed binding between a host client interface to a server interface. + * \param[in] bfInfo The Host to DSP distributed binding factory information + */ +void cm_unbindComponentFromCMCore( + t_host2mpc_bf_info *bfInfo); + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * DSP to Host distributed binding factory Information + */ +typedef struct { + t_dspstub_bf_info dspstub; //!< Information about the DSP stub (client side) + t_nmf_fifo_arm_desc* fifo; //!< Handle of the fifo params + t_uint32 context; +} t_mpc2host_bf_info; + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * Create a DSP to Host distributed binding between a client interface to a host server interface. + * (Not manage in the same way as distributed binding since the Host programming model is not component aware). + * \param[in] client The client component instance handle + * \param[in] requiredItfClientName The client required interface name + * \param[in] itfref The host server interface to be called + * \param[in] fifosize Number of waited event in the fifo + */ +t_cm_error cm_bindComponentToCMCore( + const t_interface_require_description *itfRequire, + t_uint32 fifosize, + t_uint32 context, + t_elfdescription *elfhandleStub, + t_mpc2host_bf_info ** bfInfo); + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * Destroy a DSP to Host distributed binding between a client interface to a server interface. + * \param[in] itfRequire The required interface + * \param[out] upLayerThis The 'THIS' context of upper layer + */ +void cm_unbindComponentToCMCore( + const t_interface_require_description *itfRequire, + t_mpc2host_bf_info *bfInfo); + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * Asynchronous distributed binding factory Information + */ +typedef struct { + t_nmf_fifo_arm_desc* fifo; //!< Handle of the fifo params + t_dspstub_bf_info dspstub; //!< Information about the DSP stub (client side) + t_dspskel_bf_info dspskeleton; //!< Information about the DSP skeleton (server side) +} t_mpc2mpc_bf_info; + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * Create a asynchronous distributed binding between a client interface to a server interface. + * \param[in] client The client component instance handle + * \param[in] requiredItfClientName The client required interface name + * \param[in] itf The server interface + * \param[in] fifosize Number of waited event in the fifo + */ +t_cm_error cm_bindInterfaceDistributed( + const t_interface_require_description *itfRequire, + const t_interface_provide_description *itfProvide, + t_uint32 fifosize, + t_dsp_memory_type_id dspEventMemType, + t_elfdescription *elfhandleSkeleton, + t_elfdescription *elfhandleStub); + +/** + * \internal + * \ingroup BF_DISTRIBUTED + * + * Destroy a asynchronous distributed binding between a client interface to a server interface. + * \param[in] itfRequire The required interface + */ +void cm_unbindInterfaceDistributed( + const t_interface_require_description *itfRequire, + t_mpc2mpc_bf_info *bfInfo); + +/** + * \internal + * + * Bind a static interrupt to server provide interface name. + * \param[in] coreId The core to which component is loaded + * \param[in] interruptLine Interrupt line number to use + * \param[in] server Server instance that provide interrupt service + * \param[in] providedItfServerName Interface name hat provide interrupt service + */ +t_cm_error cm_bindInterfaceStaticInterrupt( + const t_nmf_core_id coreId, + const int interruptLine, + const t_component_instance *server, + const char* providedItfServerName); + +/** + * \internal + * + * Unbind a static interrupt. + * \param[in] coreId The core to which component is loaded + * \param[in] interruptLine Interrupt line number to use + */ +t_cm_error cm_unbindInterfaceStaticInterrupt( + const t_nmf_core_id coreId, + const int interruptLine); + +void cm_destroyRequireInterface(t_component_instance* component, t_nmf_client_id clientId); +void cm_registerSingletonBinding( + t_component_instance* component, + t_interface_require_description* itfRequire, + t_interface_provide_description* itfProvide, + t_nmf_client_id clientId); +t_bool cm_unregisterSingletonBinding( + t_component_instance* component, + t_interface_require_description* itfRequire, + t_interface_provide_description* itfProvide, + t_nmf_client_id clientId); + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/component/inc/component_type.h b/drivers/staging/nmf-cm/cm/engine/component/inc/component_type.h new file mode 100644 index 00000000000..2e769ec8b22 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/inc/component_type.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Components Component Manager API type. + * + * \defgroup COMPONENT CM Components API + * \ingroup CM_USER_API + */ + +#ifndef COMPONENT_TYPE_H_ +#define COMPONENT_TYPE_H_ + +#include <cm/inc/cm_type.h> +#include <nmf/inc/component_type.h> + +/*! + * @defgroup t_nmf_ee_priority t_nmf_ee_priority + * \brief Identification of the execution engine priority and sub priority. + * @{ + * \ingroup COMPONENT + */ +typedef t_uint32 t_nmf_ee_priority; //!< Fake enumeration type + +#define NMF_SCHED_BACKGROUND ((t_nmf_ee_priority)0) //!< Background priority +#define NMF_SCHED_NORMAL ((t_nmf_ee_priority)1) //!< Normal priority +#define NMF_SCHED_URGENT ((t_nmf_ee_priority)2) //!< Urgent priority +/* @} */ + + +/*! + * \brief Identification of host component returned during introspection + * + * \ingroup COMPONENT_INTROSPECTION + */ +#define NMF_HOST_COMPONENT ((t_cm_instance_handle)0xFFFFFFFF) + +/*! + * \brief Identification of void component returned during introspection + * + * \ingroup COMPONENT_INTROSPECTION + */ +#define NMF_VOID_COMPONENT ((t_cm_instance_handle)0xFFFFFFFE) + + +/*! + * @defgroup t_nmf_ee_priority t_nmf_ee_priority + * \brief Identification of the execution engine priority and sub priority. + * @{ + * \ingroup COMPONENT + */ +typedef t_uint8 t_cm_require_state; //!< Fake enumeration type + +#define CM_REQUIRE_STATIC ((t_cm_require_state)0) //!< Required interface is static +#define CM_REQUIRE_OPTIONAL ((t_cm_require_state)1) //!< Required interface is optional +#define CM_REQUIRE_COLLECTION ((t_cm_require_state)2) //!< Required interface is a collection + +/* @} */ + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/component/inc/description.h b/drivers/staging/nmf-cm/cm/engine/component/inc/description.h new file mode 100644 index 00000000000..882dc1ea873 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/inc/description.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#ifndef __INC_CM_COMPONENT_DESCRIPTION_H +#define __INC_CM_COMPONENT_DESCRIPTION_H + +#include <cm/engine/elf/inc/memory.h> +#include <cm/engine/utils/inc/string.h> + +#include <inc/nmf-limits.h> + +/*! + * \internal + * \brief Description of an interface + * \ingroup COMPONENT_INTERNAL + */ +typedef struct _t_interface_description { + t_dup_char type; //!< Type of the interface + t_uint16 referenceCounter; //!< Number of template referencing the interface + t_uint8 methodNumber; //!< Number of method in the interfaces + struct _t_interface_description* next; + t_dup_char methodNames[1]; //!< Array of method names +} t_interface_description; + +/*! + * \internal + * \brief Description of a variable memory on a collection index + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + t_uint32 offset; //!< Offset in the memory + const t_elfmemory *memory; //!< Memory +} t_memory_reference; + +/*! + * \internal + * \brief Description of a required interface on a collection index + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + t_uint32 numberOfClient; //!< Number of interface descriptor really connected to this interface + t_memory_reference *memories; /*!< Memory where each interface reference descriptor resides + \note memories[numberOfClient] */ +} t_interface_require_index; + +/*! + * \internal + * \brief Description of a required interface + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + t_dup_char name; //!< Name of the interface + t_interface_description *interface; //!< Description of the interface + t_uint8 requireTypes; //!< Mask of t_elf_interface_require_type + t_uint8 collectionSize; //!< Size of the collection (1 if not a collection) + t_interface_require_index *indexes; /*!< Require information for each collection index + \note indexes[collectionSize] */ +} t_interface_require; + +/*! + * \internal + * \brief Description of a provided interface method on a collection index + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + t_memory_reference memory; //!< Memory of the method +} t_interface_provide_index; + +/*! + * \internal + * \brief Description of a provided interface + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + t_dup_char name; //!< Name of the interface + t_interface_description *interface; //!< Description of the interface + t_uint8 provideTypes; //!< Mask of t_elf_interface_provide_type + t_uint8 interruptLine; //!< Interrupt line if interrupt (0 if not) + t_uint8 collectionSize; //!< Size of the collection (1 if not a collection) + t_interface_provide_index **indexes; //!< Provide information for each collection index +} t_interface_provide; + +/*! + * \internal + * \brief Description of a attribute + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + t_dup_char name; //!< Name of the attribute + t_memory_reference memory; //!< Memory where the attribute reside +} t_attribute; + +/*! + * \internal + * \brief Description of a property + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + t_dup_char name; //!< Name of this attribute + t_dup_char value; //!< String of the value +} t_property; + + + + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/component/inc/dspevent.h b/drivers/staging/nmf-cm/cm/engine/component/inc/dspevent.h new file mode 100644 index 00000000000..bb47363c0ae --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/inc/dspevent.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef __INC_DSP_EVENT +#define __INC_DSP_EVENT + +#include <cm/inc/cm_type.h> +#include <cm/engine/component/inc/instance.h> +#include <cm/engine/memory/inc/memory.h> + +/* value should be size of t_remote_event in mmdsp word */ +#define DSP_REMOTE_EVENT_SIZE_IN_DSPWORD 5 + +t_cm_error dspevent_createDspEventFifo( + const t_component_instance *pComp, + const char* nameOfTOP, + t_uint32 fifoNbElem, + t_uint32 fifoElemSizeInWord, + t_dsp_memory_type_id dspEventMemType, + t_memory_handle *pHandle); +void dspevent_destroyDspEventFifo(t_memory_handle handle); + +#endif /* __INC_DSP_EVENT */ diff --git a/drivers/staging/nmf-cm/cm/engine/component/inc/initializer.h b/drivers/staging/nmf-cm/cm/engine/component/inc/initializer.h new file mode 100644 index 00000000000..5ac9ec453b7 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/inc/initializer.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef __INC_NMF_INITIALIZER +#define __INC_NMF_INITIALIZER + +#include <cm/inc/cm_type.h> +#include <cm/engine/component/inc/instance.h> +#include <share/communication/inc/initializer.h> + +PUBLIC t_cm_error cm_COMP_INIT_Init(t_nmf_core_id coreId); +PUBLIC t_cm_error cm_COMP_CallService(int serviceIndex, t_component_instance *pComp, t_uint32 methodAddress); +PUBLIC void cm_COMP_Flush(t_nmf_core_id coreId); +PUBLIC void cm_COMP_INIT_Close(t_nmf_core_id coreId); +PUBLIC t_cm_error cm_COMP_UpdateStack(t_nmf_core_id coreId, t_uint32 stackSize); +PUBLIC t_cm_error cm_COMP_ULPForceWakeup(t_nmf_core_id coreId); +PUBLIC t_cm_error cm_COMP_ULPAllowSleep(t_nmf_core_id coreId); +PUBLIC t_cm_error cm_COMP_InstructionCacheLock(t_nmf_core_id coreId, t_uint32 mmdspAddr, t_uint32 mmdspSize); +PUBLIC t_cm_error cm_COMP_InstructionCacheUnlock(t_nmf_core_id coreId, t_uint32 mmdspAddr, t_uint32 mmdspSize); + + +PUBLIC void processAsyncAcknowledge(t_nmf_core_id coreId, t_event_params_handle pParam); +PUBLIC void processSyncAcknowledge(t_nmf_core_id coreId, t_event_params_handle pParam); + +#endif /* __INC_NMF_INITIALIZER */ diff --git a/drivers/staging/nmf-cm/cm/engine/component/inc/instance.h b/drivers/staging/nmf-cm/cm/engine/component/inc/instance.h new file mode 100644 index 00000000000..0a7d80e2e02 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/inc/instance.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Components Management internal methods - Instance API. + * + */ +#ifndef __INC_CM_INSTANCE_H +#define __INC_CM_INSTANCE_H + +#include <cm/engine/component/inc/template.h> +#include <cm/engine/repository_mgt/inc/repository_mgt.h> +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/utils/inc/table.h> +#include <cm/engine/utils/inc/string.h> + +/*---------------------------------------------------------------------------- + * Component Instance API. + *----------------------------------------------------------------------------*/ +struct _t_interface_reference; + +/*! + * \internal + * \brief Component life cycle state + * + * \ingroup COMPONENT_INTERNAL + */ +typedef enum { + STATE_NONE, + STATE_STOPPED, + STATE_RUNNABLE, + // STATE_DESTROYED identified when component remove from component list +} t_component_state; + +struct t_client_of_singleton +{ + struct t_client_of_singleton *next; + t_nmf_client_id clientId; + t_uint16 numberOfInstance; + t_uint16 numberOfStart; + t_uint16 numberOfBind; +}; + +/*! + * \internal + * \brief Description of a component instance + * + * \ingroup COMPONENT_INTERNAL + */ +typedef struct t_component_instance { + t_dup_char pathname; //!< Path Name of this component in the components architecture + + t_component_state state; //!< Component state + t_nmf_ee_priority priority; //!< Executive engine component priority + t_component_template *Template; //!< Component template + + t_uint32 thisAddress; //!< Cached value of cm_DSP_GetDspAddress(component->memories[data], &thisAddress); + + t_memory_handle memories[NUMBER_OF_MMDSP_MEMORY]; //!<Reference in different memory where datas are (YES, we fix implementation to MMDSP) + + struct _t_interface_reference **interfaceReferences; /*!< Interface references + (Share same index as template->u.p.requires) + type == targets[interface_index][collection_index] */ + + t_uint16 providedItfUsedCount; //!< Use count to reference the number of components binded to this once, ie count the number of provided interfaces in use + t_cm_instance_handle instance; //!< index of this component within the ComponentTable + t_cm_domain_id domainId; //!< Domain where the component has been installed + + struct t_client_of_singleton *clientOfSingleton; //!< Client of singleton list + t_memory_handle loadMapHandle; // handle of allocated memory for the loadMap structure and name; + void *dbgCooky; //!< pointer to OS internal data +} t_component_instance; + +t_component_template* cm_lookupTemplate(t_nmf_core_id dspId, t_dup_char str); + +/*! + * \internal + * \brief Load a component template. + * + * ... + * + * \param[in] templateName name of the template to load + * \param[in] coreId DSP where template must be loaded + * \praem[in] pRepComponent Pointer to the component entry stored in the Component Cache Repository + * \param[in, out] template reference to put the loaded template (null if first instance) + * + * \exception CM_COMPONENT_NOT_FOUND + * \exception CM_NO_MORE_MEMORY + * + * \return exception number. + * + * \warning For Component manager use only. + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_loadComponent( + t_dup_char templateName, + t_cm_domain_id domainId, + t_elfdescription* elfhandle, + t_component_template **reftemplate); + +/*! + * \internal + * \brief Unload a component template. + * + * ... + * + * \param[in] template template to be unloaded + * \praem[in] Private memories that has been created from component binary file + * + * \return exception number. + * + * \warning For Component manager use only. + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_unloadComponent( + t_component_template *reftemplate); + +/*! + * \internal + * \brief Instantiate a component. + * + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_instantiateComponent(const char* templateName, + t_cm_domain_id domainId, + t_nmf_ee_priority priority, + const char* pathName, + t_elfdescription *elfhandle, + t_component_instance** refcomponent); + +struct t_client_of_singleton* cm_getClientOfSingleton(t_component_instance* component, t_bool createdIfNotExist, t_nmf_client_id clientId); + +/*! + * \internal + * \brief Start a component. + * + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_startComponent(t_component_instance* component, t_nmf_client_id clientId); + +/*! + * \internal + * \brief Stop a component. + * + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_stopComponent(t_component_instance* component, t_nmf_client_id clientId); + +/*! + * \internal + */ +typedef enum { + DESTROY_NORMAL, + DESTROY_WITHOUT_CHECK, + DESTROY_WITHOUT_CHECK_CALL +} t_destroy_state; + +/*! + * \internal + * \brief Destroy a component instance. + * + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_destroyInstance(t_component_instance* component, t_destroy_state forceDestroy); + +/*! + * \internal + * \brief Destroy a component instance. + * + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_destroyInstanceForClient(t_component_instance* component, t_destroy_state forceDestroy, t_nmf_client_id clientId); + +/*! + * \internal + * \brief + * + * \ingroup COMPONENT_INTERNAL + */ +void cm_delayedDestroyComponent(t_component_instance *component); + +/*! + * \internal + * \brief + * + * \ingroup COMPONENT_INTERNAL + */ +t_component_instance *cm_lookupComponent(const t_cm_instance_handle hdl); + +/*! + * \internal + * \brief + * + * \ingroup COMPONENT_INTERNAL + */ +t_bool cm_isComponentOnCoreId(t_nmf_core_id coreId); + +/*! + * \internal + * \brief + * + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_COMP_Init(void); + +/*! + * \internal + * \brief + * + * \ingroup COMPONENT_INTERNAL + */ +void cm_COMP_Destroy(void); + +/* + * Table of instantiated components. + */ +extern t_nmf_table ComponentTable; /**< list (table) of components */ +#define componentEntry(i) ((t_component_instance *)ComponentTable.entries[i]) +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/component/inc/introspection.h b/drivers/staging/nmf-cm/cm/engine/component/inc/introspection.h new file mode 100644 index 00000000000..cfb55c91779 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/inc/introspection.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Components Management internal methods - Introspection. + * + */ +#ifndef __INC_CM_INTROSPECTION_H +#define __INC_CM_INTROSPECTION_H + +#include <cm/engine/component/inc/instance.h> + +/*! + * \internal + * \brief Description of a required interface reference + * + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + const t_component_instance *client; //!< Component that provide this interface + t_uint8 requireIndex; //!< Index of the interface in the require array + t_uint8 collectionIndex; //!< Index in the collection if required interface is a collection + const char* origName; //!< Name of the component interface +} t_interface_require_description; + +/*! + * \internal + * \brief Description of a provided interface + * + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + const t_component_instance *server; //!< Component that provide this interface + t_uint8 provideIndex; //!< Index of the interface in the provide array + t_uint8 collectionIndex; //!< Index in the collection if provided interface is a collection + const char* origName; //!< Name of the component interface +} t_interface_provide_description; + + +/*! + * \internal + * \brief Get property of a component. + * + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_getComponentProperty( + const t_component_instance *component, + const char *propName, + char value[MAX_PROPERTY_VALUE_LENGTH], + t_uint32 valueLength); + + +t_dsp_address cm_getAttributeMpcAddress( + const t_component_instance *component, + const char *attrName); + +t_cm_logical_address cm_getAttributeHostAddr( + const t_component_instance *component, + const char *attrName); + +t_uint32 cm_readAttributeNoError( + const t_component_instance *component, + const char *attrName); + +t_cm_error cm_readAttribute( + const t_component_instance *component, + const char *attrName, + t_uint32 *value); + +t_cm_error cm_writeAttribute( + const t_component_instance *component, + const char *attrName, + t_uint32 value); + +/*! + * \internal + * \brief Get internal component symbol + * + * \ingroup COMPONENT_INTERNAL + */ +t_dsp_address cm_getFunction( + const t_component_instance* component, + const char* interfaceName, + const char* methodName); + +/*! + * \internal + * \brief Get interface provided by a component instance. + * + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_getProvidedInterface(const t_component_instance* server, + const char* itfName, + t_interface_provide_description *itfProvide); + +/*! + * \internal + * \brief Get interface required by a component instance. + * + * \ingroup COMPONENT_INTERNAL + */ +t_cm_error cm_getRequiredInterface(const t_component_instance* server, + const char* itfName, + t_interface_require_description *itfRequire); + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/component/inc/nmfheaderabi.h b/drivers/staging/nmf-cm/cm/engine/component/inc/nmfheaderabi.h new file mode 100644 index 00000000000..9eae19b2f70 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/inc/nmfheaderabi.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief NMF component description ABI + * + * \defgroup NMF_HEADER NMF Component Description ABI + * The NMF component description ABI is stored in the nmf_segment in the ELF component file. + * The NMF component description section start by the t_elf_component_header structure. + * + * \warning <B>The format of this section is not fixed and is able to be changed without concerting.</B> + * \note You can use the nmfHeaderVersion to check if the format has changed. + * \note Each pointers in this section is relative to the beginning of the section and must be relocated before used. + * \ingroup NMF_ABI + */ +#ifndef __INC_CM_NMF_HEADERABI_H +#define __INC_CM_NMF_HEADERABI_H + +#include <cm/inc/cm_type.h> + +/*! + * \brief Description of a interface + * \ingroup NMF_HEADER + */ +typedef struct { + char *type; //!< Type of this Interface + t_uint8 methodNumber; //!< Number of method in the interfaces + t_uint8 reserved1, reserved2, reserved3; + char *methodNames[1]; //!< Array of method names [methodNumber] +} t_elf_interface_description; + +/*! + * \brief Description of required interface type (value could be combinated) + * \ingroup NMF_HEADER + */ +typedef enum { + COLLECTION_REQUIRE = 1, //!< Required interface is a collection + OPTIONAL_REQUIRE = 2, //!< Required interface if optional + STATIC_REQUIRE = 4, //!< Required interface is static + VIRTUAL_REQUIRE = 8, //!< Required interface is virtual (only for introspection purpose) + INTRINSEC_REQUIRE = 16 //!< Required interface is intrinsec (bind automatically done by runtime) +} t_elf_interface_require_type; + +/*! + * \brief Description of a required interface on a collection index + * \ingroup NMF_HEADER + */ +typedef struct { + t_uint32 numberOfClient; //!< Number of interface descriptor really connected to this interface + t_uint32 symbols[1]; /*!< Symbol of the real name of the attribute + \note Real type symbols[numberOfClient] + \note Use relocation in order to get symbol information */ +} t_elf_interface_require_index; + +/*! + * \brief Description of an interface required + * \ingroup NMF_HEADER + */ +typedef struct { + char *name; //!< name of the interface: offset in string segment + t_uint8 requireTypes; //!< Mask of t_elf_interface_require_type + t_uint8 collectionSize; //!< Size of the collection (1 if not a collection) + t_uint8 reserved1, reserved2; + t_elf_interface_description *interface; //!< Interface description + t_elf_interface_require_index indexes[1]; /*!< Require information for each collection index + \note Real type: indexes[collectionSize], + available only if not static interface */ +} t_elf_required_interface; + +/*! + * \brief Description of provided interface type (value could be combinated) + * \ingroup NMF_HEADER + */ +typedef enum { + COLLECTION_PROVIDE = 1, //!< Provided interface is a collection + VIRTUAL_PROVIDE = 2 //!< Provided interface is virtual (only for introspection purpose) +} t_elf_interface_provide_type; + +/*! + * \brief Description of an interface provided + * \ingroup NMF_HEADER + */ +typedef struct { + char* name; //!< name of the interface: offset in string segment + t_uint8 provideTypes; //!< Mask of t_elf_interface_provide_type + t_uint8 interruptLine; //!< Interrupt line if interrupt (0 if not) + t_uint8 collectionSize; //!< Size of the collection (1 if not a collection) + t_uint8 reserved1; + t_elf_interface_description *interface; //!< Interface description + t_uint32 methodSymbols[1]; /*!< Symbol of the real name of methods of the interface for each collection index + \note Real type: methodSymbols[collectionSize][methodNumber] + \note Use relocation in order to get symbol information*/ +} t_elf_provided_interface; + +/*! + * \brief Description of an attribute + * \ingroup NMF_HEADER + */ +typedef struct { + char* name; //!< Name of this attribute + t_uint32 symbols; /*!< Symbol of the real name of the attribute + \note Use relocation in order to get symbol information */ +} t_elf_attribute; + +/*! + * \brief Description of an property + * \ingroup NMF_HEADER + */ +typedef struct { + char* name; //!< Name of this attribute + char* value; //!< String of the value +} t_elf_property; + +#define MAGIC_COMPONENT 0x123 //!< Magic Number for a component \ingroup NMF_HEADER +#define MAGIC_SINGLETON 0x321 //!< Magic Number for a singleton component \ingroup NMF_HEADER +#define MAGIC_FIRMWARE 0x456 //!< Magic Number for Execution Engine Component \ingroup NMF_HEADER + +/*! + * \brief Description of a ELF component header + * + * The NMF component description section start by this structure. + * + * \ingroup NMF_HEADER + */ +typedef struct { + t_uint32 magic; //!< Magic Number + t_uint32 nmfVersion; //!< Version of the NMF Header + + char* templateName; //!< Name of the component template + + t_uint32 LCCConstruct; //!< Life cycle Constructor offset + t_uint32 LCCStart; //!< Life cycle Starter offset + t_uint32 LCCStop; //!< Life cycle Stopper offset + t_uint32 LCCDestroy; //!< Life cycle Destructer offset + + t_uint32 minStackSize; //!< Minimum stack size + + t_uint32 attributeNumber;//!< Number of attributes + t_elf_attribute *attributes; //!< Array of attributes (be careful, this reference must be relocated before use) + + t_uint32 propertyNumber; //!< Number of properties + t_elf_property *properties; //!< Array of properties (be careful, this reference must be relocated before use) + + t_uint32 provideNumber; //!< Number of interfaces provided + t_elf_provided_interface *provides; //!< Array of interfaces provided (be careful, this reference must be relocated before use) + + t_uint32 requireNumber; //!< Array of interfaces required + t_elf_required_interface *requires; //!< Array of interfaces required (be careful, this reference must be relocated before use) + +} t_elf_component_header; + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/component/inc/template.h b/drivers/staging/nmf-cm/cm/engine/component/inc/template.h new file mode 100644 index 00000000000..2718d8ae9fb --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/inc/template.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Components Management internal methods - Template API. + * + * \defgroup COMPONENT_INTERNAL Private component instances API + */ +#ifndef __INC_CM_TEMPLATE_H +#define __INC_CM_TEMPLATE_H + +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/component/inc/description.h> +#include <cm/engine/elf/inc/elfapi.h> +#include <cm/engine/utils/inc/string.h> + + +/*! + * \internal + * \brief Class of a component + * \ingroup COMPONENT_INTERNAL + */ +typedef enum { + COMPONENT, //!< Primitive component + SINGLETON, //!< Singleton component + FIRMWARE, //!< Firmware composite component +} t_component_classe; + +/*! + * \internal + * \brief Description of delayed relocation + * \ingroup COMPONENT_INTERNAL + */ +typedef struct _t_function_relocation { + t_dup_char symbol_name; + t_uint32 type; + char *reloc_addr; + struct _t_function_relocation *next; +} t_function_relocation; + +struct t_component_instance; + +/*! + * \internal + * \brief Description of a provided interface method on a collection index ; Available only when template loaded + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + t_uint32 methodAddresses; //!< Address of each method +} t_interface_provide_index_loaded; + +/*! + * \internal + * \brief Description of a provided interface ; Available only when template loaded + * \ingroup COMPONENT_INTERNAL + */ +typedef struct { + t_interface_provide_index_loaded **indexesLoaded; //!< Provide information for each collection index +} t_interface_provide_loaded; + + +/*! + * \internal + * \brief Description of a component template + * \ingroup COMPONENT_INTERNAL + */ +typedef struct _t_component_template { + t_dup_char name; //!< Template name (a.k.a component type) + + t_component_classe classe; //!< Class of the component + //TODO, juraj, remove dspId + t_nmf_core_id dspId; //!< Reference on DSP where template is loaded + + t_uint8 numberOfInstance; //!< Number of same instance (or singleton copy) create from this template + + t_uint8 propertyNumber; //!< Number of properties in this template + t_uint8 attributeNumber; //!< Number of attributes in this template + t_uint8 provideNumber; //!< Number of interface provided by this template + t_uint8 requireNumber; //!< Number of interface required by this template + + t_uint32 LCCConstructAddress; //!< Life cycle Constructor address + t_uint32 LCCStartAddress; //!< Life cycle Starter address + t_uint32 LCCStopAddress; //!< Life cycle Stopper address + t_uint32 LCCDestroyAddress; //!< Life cycle Destructer address + + t_uint32 minStackSize; //!< Minimum stack size + + t_memory_handle memories[NUMBER_OF_MMDSP_MEMORY]; //!< Reference in different memory where datas are (YES, we fix implementation to MMDSP) + const t_elfmemory *thisMemory; //!< Memory used to determine this + const t_elfmemory *codeMemory; //!< Memory used to determine code + + t_function_relocation *delayedRelocation; //!< List of reference that can't been relocatable while appropritae binding done. + + t_property *properties; //!< Array of properties in this template + t_attribute *attributes; //!< Array of attributes in this template + t_interface_provide *provides; //!< Array of interface provided by this template + t_interface_require *requires; //!< Array of interface required by this template + + t_interface_provide_loaded *providesLoaded; //!< Array of interface provided by this template ; Available when loaded + + t_bool descriptionAssociatedWithTemplate; + + struct _t_component_template *prev, *next; + struct t_component_instance *singletonIfAvaliable; +} t_component_template; + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/component/src/binder.c b/drivers/staging/nmf-cm/cm/engine/component/src/binder.c new file mode 100644 index 00000000000..5f08713833b --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/src/binder.c @@ -0,0 +1,1313 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include "../inc/bind.h" +#include "../inc/dspevent.h" +#include <cm/engine/communication/fifo/inc/nmf_fifo_arm.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> +#include <cm/engine/component/inc/introspection.h> + +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/trace/inc/xtitrace.h> + +#include <cm/engine/utils/inc/string.h> + +#define CM_IT_NAME_MAX_LENGTH 8 + +t_nmf_table Host2MpcBindingTable; /**< list (table) of host2mpc bindings */ + +static void cm_fillItName(int interruptLine, char *itName); +static t_uint16 getNumberOfBind(t_component_instance* component); + +/* + * Bind virtual interface, here we assume that: + * - client component require this interface as last one and without collection, + * - server component provide only this interface and without collection. + * Fixed in loader.c. + */ +static void cm_bindVirtualInterface( + t_component_instance* client, + const t_component_instance* server) { + t_interface_require_description itfRequire; + + if(cm_getRequiredInterface(client, "coms", &itfRequire) == CM_OK) + { + t_interface_reference* itfRef = client->interfaceReferences[itfRequire.requireIndex]; + + /* + * Memorise this reference + */ + itfRef->provideIndex = 0; + itfRef->collectionIndex = 0; + itfRef->instance = server; + itfRef->bfInfoID = (t_bf_info_ID)0; + itfRef->bfInfo = (void*)-1; // TODO + } + else + { + ERROR("Internal Error in cm_bindVirtualInterface\n", 0, 0, 0, 0, 0, 0); + } +} + +static void cm_unbindVirtualInterface( + t_component_instance* client) { + t_interface_require_description itfRequire; + + if(cm_getRequiredInterface(client, "coms", &itfRequire) == CM_OK) + { + t_interface_reference* itfRef = client->interfaceReferences[itfRequire.requireIndex]; + itfRef->instance = NULL; + } + else + { + ERROR("Internal Error in cm_unbindVirtualInterface\n", 0, 0, 0, 0, 0, 0); + } +} + +/* + * Bind component + */ +static void cm_bindLowLevelInterface( + const t_interface_require_description *itfRequire, + const t_interface_provide_description *itfLocalBC, /* On the same DSP */ + t_bf_info_ID bfInfoID, void* bfInfo) +{ + const t_component_instance* client = itfRequire->client; + t_component_instance* server = (t_component_instance*)itfLocalBC->server; + t_interface_require *require = &client->Template->requires[itfRequire->requireIndex]; + t_interface_provide* provide = &server->Template->provides[itfLocalBC->provideIndex]; + t_interface_provide_loaded* provideLoaded = &server->Template->providesLoaded[itfLocalBC->provideIndex]; + int k, j; + + if(require->indexes != NULL) + { + t_interface_require_index *requireindex = &require->indexes[itfRequire->collectionIndex]; + + for(k = 0; k < requireindex->numberOfClient; k++) { + t_uint32 *hostAddr; + + hostAddr = (t_uint32*)( + cm_DSP_GetHostLogicalAddress(client->memories[requireindex->memories[k].memory->id]) + + requireindex->memories[k].offset * requireindex->memories[k].memory->memEntSize); + + LOG_INTERNAL(2, "Fill ItfRef %s.%s mem=%s Off=%x @=%x\n", + client->pathname, require->name, + requireindex->memories[k].memory->memoryName, + requireindex->memories[k].offset, + hostAddr, 0); + + /* + * Fill the interface references. We start by This then methods in order to keep + * Unbinded panic as long as possible and not used method with wrong This. This is + * relevent only for optional since we must go in stop state before rebinding other + * required interface. + * + * Direct write to DSP memory without go through DSP abstraction since we know we are in 24bits + */ + // Write THIS reference into the Data field of the interface reference + // Write the interface methods reference + + if(((t_uint32)hostAddr & 0x7) == 0 && require->interface->methodNumber > 0) + { + // We are 64word byte aligned, combine this write with first method + *(volatile t_uint64*)hostAddr = + ((t_uint64)server->thisAddress << 0) | + ((t_uint64)provideLoaded->indexesLoaded[itfLocalBC->collectionIndex][0].methodAddresses << 32); + hostAddr += 2; + j = 1; + } + else + { + // We are not, write this which will align us + *hostAddr++ = (t_uint32)server->thisAddress; + j = 0; + } + + // Word align copy + for(; j < require->interface->methodNumber - 1; j+=2) { + *(volatile t_uint64*)hostAddr = + ((t_uint64)provideLoaded->indexesLoaded[itfLocalBC->collectionIndex][j].methodAddresses << 0) | + ((t_uint64)provideLoaded->indexesLoaded[itfLocalBC->collectionIndex][j+1].methodAddresses << 32); + hostAddr += 2; + } + + // Last word align if required + if(j < require->interface->methodNumber) + *hostAddr = provideLoaded->indexesLoaded[itfLocalBC->collectionIndex][j].methodAddresses; + } + } + else + { + t_function_relocation *reloc = client->Template->delayedRelocation; + while(reloc != NULL) { + for(j = 0; j < provide->interface->methodNumber; j++) + { + if(provide->interface->methodNames[j] == reloc->symbol_name) { + cm_ELF_performRelocation( + reloc->type, + reloc->symbol_name, + provideLoaded->indexesLoaded[itfLocalBC->collectionIndex][j].methodAddresses, + reloc->reloc_addr); + break; + } + } + + reloc = reloc -> next; + } + } + + /* + * Memorise this reference + */ + { + t_interface_reference* itfRef = &client->interfaceReferences[itfRequire->requireIndex][itfRequire->collectionIndex]; + + itfRef->provideIndex = itfLocalBC->provideIndex; + itfRef->collectionIndex = itfLocalBC->collectionIndex; + itfRef->instance = itfLocalBC->server; + itfRef->bfInfoID = bfInfoID; + itfRef->bfInfo = bfInfo; + + /* + * Do not count binding from EE (ie interrupt line), as this will prevent + * cm_destroyInstance() of server to succeed (interrupt line bindings are + * destroyed after the check in cm_destroyInstance() + */ + if (client->Template->classe != FIRMWARE) + server->providedItfUsedCount++; + } +} + +static void cm_registerLowLevelInterfaceToConst( + const t_interface_require_description *itfRequire, + const t_component_instance* targetInstance) +{ + const t_component_instance* client = itfRequire->client; + + /* + * Memorise this no reference + */ + { + t_interface_reference* itfRef = &client->interfaceReferences[itfRequire->requireIndex][itfRequire->collectionIndex]; + + // This is an unbind from a true component (not to void) + // Do not count bindings from EE (ie interrupt line) + if ((targetInstance == NULL) + && (client->Template->classe != FIRMWARE) + && (itfRef->instance != (t_component_instance *)NMF_VOID_COMPONENT) + && (itfRef->instance != NULL)) + { + ((t_component_instance*)itfRef->instance)->providedItfUsedCount--; + } + + itfRef->instance = targetInstance; + itfRef->bfInfoID = BF_SYNCHRONOUS; // Just to memorize no Binding component used and unbind ToVoid happy ;-). + } +} + +static void cm_bindLowLevelInterfaceToConst( + const t_interface_require_description *itfRequire, + const t_dsp_address functionAddress, + const t_component_instance* targetInstance) { + const t_component_instance* client = itfRequire->client; + t_interface_require *require = &client->Template->requires[itfRequire->requireIndex]; + int j, k; + + + // If DSP is off/panic/... -> write nothing + if( + require->indexes != NULL + && cm_DSP_GetState(client->Template->dspId)->state == MPC_STATE_BOOTED) + { + t_interface_require_index *requireindex = &require->indexes[itfRequire->collectionIndex]; + + for(k = 0; k < requireindex->numberOfClient; k++) { + t_uint32 *hostAddr; + + hostAddr = (t_uint32*)( + cm_DSP_GetHostLogicalAddress(client->memories[requireindex->memories[k].memory->id]) + + requireindex->memories[k].offset * requireindex->memories[k].memory->memEntSize); + + /* + * Fill the interface references. We start by Methods then This in order to swith to + * Unbinded panic as fast as possible and not used method with wrong This. This is + * relevent only for optional since we must go in stop state before rebinding other + * required interface. + * + * Direct write to DSP memory without go through DSP abstraction since we know we are in 24bits + */ + /* + * Write THIS reference into the Data field of the interface reference + * Hack for simplifying debug just to keep THIS reference with caller one + * (could be removed if __return_address MMDSP intrinsec provided by compiler). + */ + // Write the interface methods reference + + if(((t_uint32)hostAddr & 0x7) == 0 && require->interface->methodNumber > 0) + { + // We are 64word byte aligned, combine this write with first method + *(volatile t_uint64*)hostAddr = + ((t_uint64)client->thisAddress << 0) | + ((t_uint64)functionAddress << 32); + hostAddr += 2; + j = 1; + } + else + { + // We are not, write this which will align us + *hostAddr++ = (t_uint32)client->thisAddress; + j = 0; + } + + // Word align copy + for(; j < require->interface->methodNumber - 1; j+=2) { + *(volatile t_uint64*)hostAddr = + ((t_uint64)functionAddress << 0) | + ((t_uint64)functionAddress << 32); + hostAddr += 2; + } + + // Last word align if required + if(j < require->interface->methodNumber) + *hostAddr = functionAddress; + } + } + + cm_registerLowLevelInterfaceToConst(itfRequire, targetInstance); +} + +/* + * Bind User component though primitive binding factory + */ +t_cm_error cm_bindInterface( + const t_interface_require_description *itfRequire, + const t_interface_provide_description *itfProvide) { + + LOG_INTERNAL(1, "\n##### Bind Synchronous %s/%x.%s -> %s/%x.%s #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, + itfProvide->server->pathname, itfProvide->server, itfProvide->origName); + + cm_bindLowLevelInterface( + itfRequire, + itfProvide, + BF_SYNCHRONOUS, NULL); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_BIND_SYNCHRONOUS, + itfRequire->client, itfProvide->server, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + itfProvide->server->Template->provides[itfProvide->provideIndex].name); + + return CM_OK; +} + +/* + * + */ +void cm_unbindInterface( + const t_interface_require_description *itfRequire) { + + LOG_INTERNAL(1, "\n##### UnBind synchronous %s/%x.%s #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, 0, 0, 0); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_UNBIND_SYNCHRONOUS, + itfRequire->client, NULL, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + NULL); + + cm_bindLowLevelInterfaceToConst(itfRequire, + 0x0, + NULL); +} + +/* + * + */ +t_cm_error cm_bindInterfaceToVoid( + const t_interface_require_description *itfRequire) { + LOG_INTERNAL(1, "\n##### Bind %s/%x.%s -> Void #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, 0, 0, 0); + + cm_bindLowLevelInterfaceToConst(itfRequire, + cm_EEM_getExecutiveEngine(itfRequire->client->Template->dspId)->voidAddr, + (t_component_instance*)NMF_VOID_COMPONENT); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_BIND_SYNCHRONOUS, + itfRequire->client, NULL, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + NULL); + + return CM_OK; +} +/* + * Find the server and its interface inded to a given required interface for a given component + */ +t_cm_error cm_lookupInterface( + const t_interface_require_description *itfRequire, + t_interface_provide_description *itfProvide) { + const t_component_instance* client = itfRequire->client; + t_interface_reference* itfRef = &client->interfaceReferences[itfRequire->requireIndex][itfRequire->collectionIndex]; + + if(itfRef->instance != NULL) + { + itfProvide->server = itfRef->instance; + itfProvide->provideIndex = itfRef->provideIndex; + itfProvide->collectionIndex = itfRef->collectionIndex; + + return CM_OK; + } else { + itfProvide->server = NULL; + return CM_INTERFACE_NOT_BINDED; + } +} + +/* + * + */ +t_cm_error cm_bindInterfaceTrace( + const t_interface_require_description *itfRequire, + const t_interface_provide_description *itfProvide, + t_elfdescription *elfhandleTrace) +{ + t_interface_require *require = &itfRequire->client->Template->requires[itfRequire->requireIndex]; + t_interface_require_description bcitfRequire; + t_interface_provide_description bcitfProvide; + t_trace_bf_info *bfInfo; + t_cm_error error; + + LOG_INTERNAL(1, "\n##### Bind Synchronous Trace %s/%x.%s -> %s/%x.%s #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, + itfProvide->server->pathname, itfProvide->server, itfProvide->origName); + + /* Allocate aynchronous binding factory information */ + bfInfo = (t_trace_bf_info*)OSAL_Alloc(sizeof(t_trace_bf_info)); + if(bfInfo == 0) + return CM_NO_MORE_MEMORY; + + /* + * Instantiate related trace on dsp + */ + { + char traceTemplateName[4 + MAX_INTERFACE_TYPE_NAME_LENGTH + 1]; + + cm_StringCopy(traceTemplateName,"_tr.", sizeof(traceTemplateName)); + cm_StringConcatenate(traceTemplateName, require->interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + + if ((error = cm_instantiateComponent( + traceTemplateName, + itfRequire->client->domainId, + itfProvide->server->priority, + traceDup, + elfhandleTrace, + &bfInfo->traceInstance)) != CM_OK) { + OSAL_Free(bfInfo); + return (error == CM_COMPONENT_NOT_FOUND)?CM_BINDING_COMPONENT_NOT_FOUND : error; + } + } + + /* Bind event to server interface (Error must not occure) */ + CM_ASSERT(cm_getRequiredInterface(bfInfo->traceInstance, "target", &bcitfRequire) == CM_OK); + + cm_bindLowLevelInterface(&bcitfRequire, itfProvide, BF_SYNCHRONOUS, NULL); + + /* Get the event interface (Error must not occure) */ + CM_ASSERT(cm_getProvidedInterface(bfInfo->traceInstance, "target", &bcitfProvide) == CM_OK); + + /* Bind client to event (Error must not occure) */ + cm_bindLowLevelInterface(itfRequire, &bcitfProvide, BF_TRACE, bfInfo); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_BIND_SYNCHRONOUS, + itfRequire->client, itfProvide->server, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + itfProvide->server->Template->provides[itfProvide->provideIndex].name); + + return CM_OK; +} + +void cm_unbindInterfaceTrace( + const t_interface_require_description *itfRequire, + t_trace_bf_info *bfInfo) +{ + t_interface_require_description traceitfRequire; + + LOG_INTERNAL(1, "\n##### UnBind trace synchronous %s/%x.%s #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, 0, 0, 0); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_UNBIND_SYNCHRONOUS, + itfRequire->client, NULL, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + NULL); + + /* Unbind Client from Event Binding Component */ + cm_bindLowLevelInterfaceToConst(itfRequire, 0x0, NULL); + + /* Unbind explicitly Event from Server Binding Component */ + /* This is mandatory to fix the providedItfUsedCount of the server */ + CM_ASSERT(cm_getRequiredInterface(bfInfo->traceInstance, "target", &traceitfRequire) == CM_OK); + + cm_registerLowLevelInterfaceToConst(&traceitfRequire, NULL); + + /* Destroy Event Binding Component */ + cm_destroyInstance(bfInfo->traceInstance, DESTROY_WITHOUT_CHECK); + + /* Free BF info */ + OSAL_Free(bfInfo); +} + + +/* + * + */ +t_cm_error cm_bindInterfaceAsynchronous( + const t_interface_require_description *itfRequire, + const t_interface_provide_description *itfProvide, + t_uint32 fifosize, + t_dsp_memory_type_id dspEventMemType, + t_elfdescription *elfhandleEvent) { + t_interface_require *require = &itfRequire->client->Template->requires[itfRequire->requireIndex]; + t_interface_require_description eventitfRequire; + t_interface_provide_description eventitfProvide; + t_async_bf_info *bfInfo; + t_cm_error error; + + LOG_INTERNAL(1, "\n##### Bind Asynchronous %s/%x.%s -> %s/%x.%s #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, + itfProvide->server->pathname, itfProvide->server, itfProvide->origName); + + /* Allocate aynchronous binding factory information */ + bfInfo = (t_async_bf_info*)OSAL_Alloc(sizeof(t_async_bf_info)); + if(bfInfo == 0) + return CM_NO_MORE_MEMORY; + + /* + * Instantiate related event on dsp + */ + { + char eventTemplateName[4 + MAX_INTERFACE_TYPE_NAME_LENGTH + 1]; + + cm_StringCopy(eventTemplateName,"_ev.", sizeof(eventTemplateName)); + cm_StringConcatenate(eventTemplateName, require->interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + + if ((error = cm_instantiateComponent( + eventTemplateName, + itfRequire->client->domainId, + itfProvide->server->priority, + eventDup, + elfhandleEvent, + &bfInfo->eventInstance)) != CM_OK) { + OSAL_Free(bfInfo); + return (error == CM_COMPONENT_NOT_FOUND)?CM_BINDING_COMPONENT_NOT_FOUND : error; + } + } + + /* + * Initialize the event component + */ + { + unsigned int size; + + // Get fifo elem size (which was store in TOP by convention) + size = cm_readAttributeNoError(bfInfo->eventInstance, "TOP"); + LOG_INTERNAL(3, "DspEvent Fifo element size = %d\n", size, 0, 0, 0, 0, 0); + + // Allocate fifo + if ((error = dspevent_createDspEventFifo(bfInfo->eventInstance, + "TOP", + fifosize, size, + dspEventMemType, + &bfInfo->dspfifoHandle)) != CM_OK) + { + cm_destroyInstance(bfInfo->eventInstance, DESTROY_WITHOUT_CHECK); + OSAL_Free(bfInfo); + return error; + } + } + + /* Bind event to server interface (Error must not occure) */ + CM_ASSERT(cm_getRequiredInterface(bfInfo->eventInstance, "target", &eventitfRequire) == CM_OK); + + cm_bindLowLevelInterface(&eventitfRequire, itfProvide, BF_SYNCHRONOUS, NULL); + + /* Get the event interface (Error must not occure) */ + CM_ASSERT(cm_getProvidedInterface(bfInfo->eventInstance, "target", &eventitfProvide) == CM_OK); + + /* Bind client to event (Error must not occure) */ + cm_bindLowLevelInterface(itfRequire, &eventitfProvide, BF_ASYNCHRONOUS, bfInfo); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_BIND_ASYNCHRONOUS, + itfRequire->client, itfProvide->server, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + itfProvide->server->Template->provides[itfProvide->provideIndex].name); + + return CM_OK; +} + +void cm_unbindInterfaceAsynchronous( + const t_interface_require_description *itfRequire, + t_async_bf_info *bfInfo) +{ + t_interface_require_description eventitfRequire; + + LOG_INTERNAL(1, "\n##### UnBind asynchronous %s/%x.%s #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, 0, 0, 0); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_UNBIND_ASYNCHRONOUS, + itfRequire->client, NULL, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + NULL); + + /* Unbind Client from Event Binding Component */ + cm_bindLowLevelInterfaceToConst(itfRequire, 0x0, NULL); + + /* Unbind explicitly Event from Server Binding Component */ + /* This is mandatory to fix the providedItfUsedCount of the server */ + CM_ASSERT(cm_getRequiredInterface(bfInfo->eventInstance, "target", &eventitfRequire) == CM_OK); + + cm_registerLowLevelInterfaceToConst(&eventitfRequire, NULL); + + /* Destroy Event fifo */ + dspevent_destroyDspEventFifo(bfInfo->dspfifoHandle); + + /* Destroy Event Binding Component */ + cm_destroyInstance(bfInfo->eventInstance, DESTROY_WITHOUT_CHECK); + + /* Free BF info */ + OSAL_Free(bfInfo); +} + +/*! + * Create Shared FIFO and set stub and skeleton to it + */ +PRIVATE t_cm_error cm_createParamsFifo(t_component_instance *stub, + t_component_instance *skeleton, + t_cm_domain_id domainId, + t_uint32 fifosize, + t_nmf_fifo_arm_desc **fifo, + t_uint32 *fifoElemSize, + t_uint32 bcDescSize) +{ + t_nmf_core_id stubcore = (stub != NULL) ?(stub->Template->dspId): ARM_CORE_ID; + t_nmf_core_id skelcore = (skeleton != NULL) ?(skeleton->Template->dspId) : ARM_CORE_ID; + t_component_instance *bcnotnull = (stub != NULL) ? stub : skeleton; + int _fifoelemsize; + + CM_ASSERT(bcnotnull != NULL); + + /* Get fifo param elem size (which was store in FIFO by convention) */ + _fifoelemsize = cm_readAttributeNoError(bcnotnull, "FIFO"); + LOG_INTERNAL(3, "Fifo Params element size = %d\n", _fifoelemsize, 0, 0, 0, 0, 0); + if(fifoElemSize != NULL) + *fifoElemSize = _fifoelemsize; + + /* Allocation of the fifo params */ + *fifo = fifo_alloc(stubcore, skelcore, _fifoelemsize, fifosize, 1+bcDescSize, paramsLocation, extendedFieldLocation, domainId); /* 1+nbMethods fro hostBCThis_or_TOP space */ + if(*fifo == NULL) { + ERROR("CM_NO_MORE_MEMORY: fifo_alloc() failed in cm_createParamsFifo()\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + if(stub != NULL) + { + /* Set stub FIFO attribute (Error mut not occure) */ + cm_writeAttribute(stub, "FIFO", (*fifo)->dspAdress); + + LOG_INTERNAL(2, " FIFO param %x:%x\n", *fifo, (*fifo)->dspAdress, 0, 0, 0, 0); + } + + if(skeleton != NULL) + { + /* Set Skeleton FIFO attribute (Error mut not occure) */ + cm_writeAttribute(skeleton, "FIFO", (*fifo)->dspAdress); + + LOG_INTERNAL(2, " FIFO param %x:%x\n", *fifo, (*fifo)->dspAdress, 0, 0, 0, 0); + } + + return CM_OK; +} +/** + * + */ +static void cm_destroyParamsFifo(t_nmf_fifo_arm_desc *fifo) { + fifo_free(fifo); +} + +/*! + * Create DSP skeleton + */ +PRIVATE t_cm_error cm_createDSPSkeleton( + const t_interface_provide_description *itfProvide, + t_uint32 fifosize, + t_dsp_memory_type_id dspEventMemType, //INTERNAL_XRAM24 + t_elfdescription *elfhandleSkeleton, + t_dspskel_bf_info *bfInfo) +{ + t_interface_provide *provide = &itfProvide->server->Template->provides[itfProvide->provideIndex]; + t_interface_require_description skelitfRequire; + t_cm_error error; + unsigned int fifoeventsize = 0; + + /* Instantiate related stub on dsp */ + { + char stubTemplateName[4 + MAX_INTERFACE_TYPE_NAME_LENGTH + 1]; + + cm_StringCopy(stubTemplateName,"_sk.", sizeof(stubTemplateName)); + cm_StringConcatenate(stubTemplateName, provide->interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + + if ((error = cm_instantiateComponent( + stubTemplateName, + itfProvide->server->domainId, + itfProvide->server->priority, + skeletonDup, + elfhandleSkeleton, + &bfInfo->skelInstance)) != CM_OK) { + return ((error == CM_COMPONENT_NOT_FOUND)?CM_BINDING_COMPONENT_NOT_FOUND:error); + } + } + + /* Get fifo elem size (which was store in TOP by convention) */ + fifoeventsize = cm_readAttributeNoError(bfInfo->skelInstance, "TOP"); + LOG_INTERNAL(3, "DspEvent Fifo element size = %d\n", fifoeventsize, 0, 0, 0, 0, 0); + + /* Allocation of the itf event dsp fifo */ + if ((error = dspevent_createDspEventFifo( + bfInfo->skelInstance, + "TOP", + fifosize, + fifoeventsize, + dspEventMemType, + &bfInfo->dspfifoHandle)) != CM_OK) + { + cm_destroyInstance(bfInfo->skelInstance, DESTROY_WITHOUT_CHECK); + return error; + } + + /* Bind stub to server component (Error must not occure) */ + CM_ASSERT(cm_getRequiredInterface(bfInfo->skelInstance, "target", &skelitfRequire) == CM_OK); + + cm_bindLowLevelInterface(&skelitfRequire, itfProvide, BF_SYNCHRONOUS, NULL); + + return CM_OK; +} + +/** + * Destroy DSP Skeleton + */ +PRIVATE t_cm_error cm_destroyDSPSkeleton(t_dspskel_bf_info *bfInfo) { + t_interface_require_description skelitfRequire; + + /* Unbind explicitly stub from server component (Error must not occure) */ + /* This is mandatory to fix the providedItfUsedCount of the server */ + CM_ASSERT(cm_getRequiredInterface(bfInfo->skelInstance, "target", &skelitfRequire) == CM_OK); + + cm_registerLowLevelInterfaceToConst(&skelitfRequire, NULL); + + /* Destroy Event fifo */ + dspevent_destroyDspEventFifo(bfInfo->dspfifoHandle); + + /* Destroy Event Binding Component */ + return cm_destroyInstance(bfInfo->skelInstance, DESTROY_WITHOUT_CHECK); +} + +/* + * + */ +t_cm_error cm_bindComponentFromCMCore( + const t_interface_provide_description *itfProvide, + t_uint32 fifosize, + t_dsp_memory_type_id dspEventMemType, + t_elfdescription *elfhandleSkeleton, + t_host2mpc_bf_info **bfInfo) { + t_interface_provide *provide = &itfProvide->server->Template->provides[itfProvide->provideIndex]; + t_dsp_offset shareVarOffset; + t_cm_error error; + + LOG_INTERNAL(1, "\n##### Bind HOST -> %s/%x.%s #####\n", + itfProvide->server->pathname, itfProvide->server, itfProvide->origName, 0, 0, 0); + + /* Allocate host2dsp binding factory information */ + *bfInfo = (t_host2mpc_bf_info*)OSAL_Alloc(sizeof(t_host2mpc_bf_info)); + if((*bfInfo) == 0) + return CM_NO_MORE_MEMORY; + + /* Create the Skeleton */ + if ((error = cm_createDSPSkeleton(itfProvide, + fifo_normalizeDepth(fifosize), /* We SHALL create DSP Skeleton before creating the Params Fifo, but we need in advance the real depth of this fifo */ + dspEventMemType, + elfhandleSkeleton, + &(*bfInfo)->dspskeleton)) != CM_OK) + { + OSAL_Free((*bfInfo)); + return error; + } + + /* Create the FIFO Params */ + if ((error = cm_createParamsFifo(NULL, + (*bfInfo)->dspskeleton.skelInstance, + itfProvide->server->domainId, + fifosize, + &(*bfInfo)->fifo, + NULL, + provide->interface->methodNumber)) != CM_OK) + { + cm_destroyDSPSkeleton(&(*bfInfo)->dspskeleton); + OSAL_Free((*bfInfo)); + return error; + } + + /* Set Target info in FIFO param to TOP */ + shareVarOffset = cm_getAttributeMpcAddress((*bfInfo)->dspskeleton.skelInstance, "TOP"); + + /* + * Set Target info in FIFO param to armThis + * Should not return any error + */ + fifo_params_setSharedField((*bfInfo)->fifo, 0, (t_shared_field)shareVarOffset /* ArmBCThis_or_TOP */); + + /* Initialise FIFO Param bcDesc with Skeleton methods */ + { + int i; + t_component_instance *skel = (*bfInfo)->dspskeleton.skelInstance; + for (i=0; i < provide->interface->methodNumber; i++) + { + /* should not return error */ + fifo_params_setSharedField( + (*bfInfo)->fifo, + 1+i, + skel->Template->providesLoaded[0].indexesLoaded[0][i].methodAddresses + ); + } + } + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_BIND_ASYNCHRONOUS, + ARM_TRACE_COMPONENT, itfProvide->server, + NULL, + itfProvide->server->Template->provides[itfProvide->provideIndex].name); + + return CM_OK; +} + +void cm_unbindComponentFromCMCore( + t_host2mpc_bf_info* bfInfo) { + t_component_instance *skel = bfInfo->dspskeleton.skelInstance; + t_interface_reference* itfProvide = &skel->interfaceReferences[0][0]; + t_interface_provide *provide = &itfProvide->instance->Template->provides[itfProvide->provideIndex]; + + LOG_INTERNAL(1, "\n##### UnBind HOST -> %s/%x.%s #####\n", + itfProvide->instance->pathname, itfProvide->instance, provide->name, 0, 0, 0); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_UNBIND_ASYNCHRONOUS, + ARM_TRACE_COMPONENT, itfProvide->instance, + NULL, + itfProvide->instance->Template->provides[itfProvide->provideIndex].name); + + // Destroy FIFO params + cm_destroyParamsFifo(bfInfo->fifo); + + // Destory Skeleton + cm_destroyDSPSkeleton(&bfInfo->dspskeleton); + + // Free BF info (which contains bcDecr(==dspfct) and arm This) + OSAL_Free(bfInfo); +} + +/** + * Create DSP Stub + */ +PRIVATE t_cm_error cm_createDSPStub( + const t_interface_require_description *itfRequire, + const char* itfType, + t_dspstub_bf_info* bfInfo, + t_elfdescription *elfhandleStub, + t_interface_provide_description *itfstubProvide) { + t_cm_error error; + + /* + * Instantiate related skel on dsp + */ + { + char skelTemplateName[4 + MAX_INTERFACE_TYPE_NAME_LENGTH + 1]; + + cm_StringCopy(skelTemplateName, "_st.", sizeof(skelTemplateName)); + cm_StringConcatenate(skelTemplateName, itfType, MAX_INTERFACE_TYPE_NAME_LENGTH); + + if ((error = cm_instantiateComponent( + skelTemplateName, + itfRequire->client->domainId, + itfRequire->client->priority, + stubDup, + elfhandleStub, + &bfInfo->stubInstance)) != CM_OK) { + return (error == CM_COMPONENT_NOT_FOUND)?CM_BINDING_COMPONENT_NOT_FOUND : error; + } + } + + /* Get the internal component that serve this interface (Error must not occure) */ + (void)cm_getProvidedInterface(bfInfo->stubInstance, "source", itfstubProvide); + + return CM_OK; +} + +PRIVATE t_cm_error cm_destroyDSPStub( + const t_interface_require_description *itfRequire, + t_dspstub_bf_info* bfInfo) { + + /* Unbind Client from Event Binding Component */ + cm_bindLowLevelInterfaceToConst(itfRequire, + 0x0, + NULL); + + /* Destroy Event Binding Component */ + return cm_destroyInstance(bfInfo->stubInstance, DESTROY_WITHOUT_CHECK); +} +/* + * + */ +t_cm_error cm_bindComponentToCMCore( + const t_interface_require_description *itfRequire, + t_uint32 fifosize, + t_uint32 context, + t_elfdescription *elfhandleStub, + t_mpc2host_bf_info ** bfInfo) { + t_interface_require *require = &itfRequire->client->Template->requires[itfRequire->requireIndex]; + t_interface_provide_description itfstubProvide; + t_cm_error error; + t_uint32 fifoelemsize; + + LOG_INTERNAL(1, "\n##### Bind %s/%x.%s -> HOST #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, 0, 0, 0); + + /* Allocate dsp2host binding factory information */ + *bfInfo = (t_mpc2host_bf_info*)OSAL_Alloc(sizeof(t_mpc2host_bf_info)); + if(*bfInfo == 0) + return CM_NO_MORE_MEMORY; + (*bfInfo)->context = context; + + if ((error = cm_createDSPStub(itfRequire, + require->interface->type, + &(*bfInfo)->dspstub, + elfhandleStub, + &itfstubProvide)) != CM_OK) + { + OSAL_Free(*bfInfo); + return error; + } + + /* Create the FIFO Params */ + if ((error = cm_createParamsFifo( + (*bfInfo)->dspstub.stubInstance, + NULL, + itfRequire->client->domainId, + fifosize, + &(*bfInfo)->fifo, + &fifoelemsize, + 1)) != CM_OK) /* 1 => we used first field as max params size */ + { + cm_destroyDSPStub(itfRequire, &(*bfInfo)->dspstub); + OSAL_Free(*bfInfo); + return error; + } + + /* Bind client to stub component (Error must not occure) */ + cm_bindLowLevelInterface(itfRequire, &itfstubProvide, BF_DSP2HOST, *bfInfo); + + /* Bind stub component to host (virtual bind) */ + cm_bindVirtualInterface((*bfInfo)->dspstub.stubInstance, (t_component_instance*)NMF_HOST_COMPONENT); + + /* + * Set Target info in FIFO param to armThis + * Initialise FIFO Param bcDesc with Jumptable + * Should not return any error + */ + fifo_params_setSharedField((*bfInfo)->fifo, 0, (t_shared_field)context /* ArmBCThis_or_TOP */); + fifo_params_setSharedField((*bfInfo)->fifo, 1, (t_shared_field)fifoelemsize * 2/* bcDescRef */); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_BIND_ASYNCHRONOUS, + itfRequire->client, ARM_TRACE_COMPONENT, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + NULL); + + return error; +} + +void cm_unbindComponentToCMCore( + const t_interface_require_description *itfRequire, + t_mpc2host_bf_info *bfInfo) +{ + LOG_INTERNAL(1, "\n##### UnBind %s/%x.%s -> HOST #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, 0, 0, 0); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_UNBIND_ASYNCHRONOUS, + itfRequire->client, ARM_TRACE_COMPONENT, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + NULL); + + /* Unbind virtual interface coms */ + cm_unbindVirtualInterface(bfInfo->dspstub.stubInstance); + + // Destroy FIFO params + cm_destroyParamsFifo(bfInfo->fifo); + + // Destroy DSP Stub + cm_destroyDSPStub(itfRequire, &bfInfo->dspstub); + + /* Free BF info */ + OSAL_Free(bfInfo); +} + +/*! + * + */ +t_cm_error cm_bindInterfaceDistributed( + const t_interface_require_description *itfRequire, + const t_interface_provide_description *itfProvide, + t_uint32 fifosize, + t_dsp_memory_type_id dspEventMemType, + t_elfdescription *elfhandleSkeleton, + t_elfdescription *elfhandleStub) { + t_interface_require *require = &itfRequire->client->Template->requires[itfRequire->requireIndex]; + t_interface_provide_description itfstubProvide; + t_cm_error error; + t_mpc2mpc_bf_info *bfInfo; + t_dsp_offset shareVarOffset; + + LOG_INTERNAL(1, "\n##### Bind Distributed %s/%x.%s -> %s/%x.%s #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, + itfProvide->server->pathname, itfProvide->server, itfProvide->origName); + + /* Allocate aynchronous binding factory information */ + bfInfo = (t_mpc2mpc_bf_info*)OSAL_Alloc(sizeof(t_mpc2mpc_bf_info)); + if(bfInfo == 0) + return CM_NO_MORE_MEMORY; + + /* Create the Skeleton */ + if ((error = cm_createDSPSkeleton(itfProvide, + fifo_normalizeDepth(fifosize), /* We SHALL create DSP Skeleton before creating the Params Fifo, but we need in advance the real depth of this fifo */ + dspEventMemType, + elfhandleSkeleton, + &bfInfo->dspskeleton)) != CM_OK) + { + OSAL_Free(bfInfo); + return error; + } + + // Create DSP Stub + if ((error = cm_createDSPStub(itfRequire, + require->interface->type, + &bfInfo->dspstub, + elfhandleStub, + &itfstubProvide)) != CM_OK) + { + cm_destroyDSPSkeleton(&bfInfo->dspskeleton); + OSAL_Free(bfInfo); + return error; + } + + /* Bind client to stub component (Error must not occure) */ + cm_bindLowLevelInterface(itfRequire, &itfstubProvide, BF_DSP2DSP, bfInfo); + + /* Create the FIFO Params */ + if ((error = cm_createParamsFifo( + bfInfo->dspstub.stubInstance, + bfInfo->dspskeleton.skelInstance, + itfProvide->server->domainId, + fifosize, + &bfInfo->fifo, + NULL, + require->interface->methodNumber)) != CM_OK) + { + cm_destroyDSPStub(itfRequire, &bfInfo->dspstub); + cm_destroyDSPSkeleton(&bfInfo->dspskeleton); + OSAL_Free(bfInfo); + return error; + } + + /* Bind stub component to host (virtual bind) */ + cm_bindVirtualInterface(bfInfo->dspstub.stubInstance, bfInfo->dspskeleton.skelInstance); + + /* Set Target info in FIFO param to TOP */ + shareVarOffset = cm_getAttributeMpcAddress(bfInfo->dspskeleton.skelInstance, "TOP"); + + /* + * Set Target info in FIFO param to armThis + * Should not return any error + */ + fifo_params_setSharedField(bfInfo->fifo, 0, (t_shared_field)shareVarOffset /* ArmBCThis_or_TOP */); + + /* Initialise FIFO Param bcDesc with Skeleton methods */ + { + int i; + t_component_instance *skel = bfInfo->dspskeleton.skelInstance; + for (i=0; i < require->interface->methodNumber; i++) + { + /* should not return error */ + fifo_params_setSharedField( + bfInfo->fifo, + 1+i, + skel->Template->providesLoaded[0].indexesLoaded[0][i].methodAddresses + ); + } + } + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_BIND_ASYNCHRONOUS, + itfRequire->client, itfProvide->server, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + itfProvide->server->Template->provides[itfProvide->provideIndex].name); + + return CM_OK; +} + +/*! + * + */ +void cm_unbindInterfaceDistributed( + const t_interface_require_description *itfRequire, + t_mpc2mpc_bf_info *bfInfo) +{ + LOG_INTERNAL(1, "\n##### UnBind distributed %s/%x.%s #####\n", + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, 0, 0, 0); + + cm_TRC_traceBinding(TRACE_BIND_COMMAND_UNBIND_ASYNCHRONOUS, + itfRequire->client, NULL, + itfRequire->client->Template->requires[itfRequire->requireIndex].name, + NULL); + + /* Unbind virtual interface */ + cm_unbindVirtualInterface(bfInfo->dspstub.stubInstance); + + // Destroy FIFO params + cm_destroyParamsFifo(bfInfo->fifo); + + // Destroy DSP Stub + cm_destroyDSPStub(itfRequire, &bfInfo->dspstub); + + // Destory DSP Skeleton + cm_destroyDSPSkeleton(&bfInfo->dspskeleton); + + // Destroy BF Info + OSAL_Free(bfInfo); +} + +t_cm_error cm_bindInterfaceStaticInterrupt( + const t_nmf_core_id coreId, + const int interruptLine, + const t_component_instance *server, + const char* providedItfServerName +) +{ + char requiredItfClientName[CM_IT_NAME_MAX_LENGTH]; + t_component_instance *client = cm_EEM_getExecutiveEngine(coreId)->instance; + t_interface_require_description itfRequire; + t_interface_provide_description itfProvide; + t_cm_error error; + + //build it[%d] name + if (interruptLine < 0 || interruptLine > 255) {return CM_OUT_OF_LIMITS;} + cm_fillItName(interruptLine, requiredItfClientName); + + //do binding + if ((error = cm_getRequiredInterface(client,requiredItfClientName,&itfRequire)) != CM_OK) {return error;} + if ((error = cm_getProvidedInterface(server,providedItfServerName,&itfProvide)) != CM_OK) {return error;} + if((error = cm_bindInterface(&itfRequire, &itfProvide)) != CM_OK) {return error;} + + return CM_OK; +} + +t_cm_error cm_unbindInterfaceStaticInterrupt( + const t_nmf_core_id coreId, + const int interruptLine +) +{ + char requiredItfClientName[CM_IT_NAME_MAX_LENGTH]; + t_component_instance *client = cm_EEM_getExecutiveEngine(coreId)->instance; + t_interface_require_description itfRequire; + t_cm_error error; + + //build it[%d] name + if (interruptLine < 0 || interruptLine > 255) {return CM_OUT_OF_LIMITS;} + cm_fillItName(interruptLine, requiredItfClientName); + + //do unbinding + if ((error = cm_getRequiredInterface(client,requiredItfClientName,&itfRequire)) != CM_OK) {return error;} + cm_unbindInterface(&itfRequire); + + return CM_OK; +} + +void cm_destroyRequireInterface(t_component_instance* component, t_nmf_client_id clientId) +{ + int i, j; + + /* + * Special code for SINGLETON handling + */ + if(component->Template->classe == SINGLETON) + { + if(getNumberOfBind(component) > 0) + return; + } + + for(i = 0; i < component->Template->requireNumber; i++) + { + int nb = component->Template->requires[i].collectionSize; + for(j = 0; j < nb; j++) + { + if(component->interfaceReferences[i][j].instance != NULL) + { + t_interface_reference* itfRef = &component->interfaceReferences[i][j]; + t_interface_require_description itfRequire; + + itfRequire.client = component; + itfRequire.requireIndex = i; + itfRequire.collectionIndex = j; + itfRequire.origName = component->Template->requires[i].name; + + switch (itfRef->bfInfoID) { + case BF_SYNCHRONOUS: + /* Error ignored as it is always OK */ + cm_unbindInterface(&itfRequire); + break; + case BF_TRACE: + cm_unbindInterfaceTrace(&itfRequire, + (t_trace_bf_info*)itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfo); + break; + case BF_ASYNCHRONOUS: + cm_unbindInterfaceAsynchronous(&itfRequire, + (t_async_bf_info*)itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfo); + break; + case BF_DSP2HOST: + /* This 'mpc2host handle' is provided by the host at OS Integration level. + It must then be handled and released in OS specific part. + */ + cm_unbindComponentToCMCore(&itfRequire, + (t_mpc2host_bf_info*)itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfo); + break; + case BF_HOST2DSP: + /* These bindings are from CM Core to DSP, they are not listed + here and must be handles/freed by host at OS Integration level + */ + break; + case BF_DSP2DSP: + cm_unbindInterfaceDistributed(&itfRequire, + (t_mpc2mpc_bf_info*)itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfo); + break; + default: + break; + } + } + } + } +} + +void cm_registerSingletonBinding( + t_component_instance* component, + t_interface_require_description* itfRequire, + t_interface_provide_description* itfProvide, + t_nmf_client_id clientId) +{ + if(component->Template->classe == SINGLETON) + { + struct t_client_of_singleton* cl = cm_getClientOfSingleton(component, FALSE, clientId); + if(cl != NULL) + cl->numberOfBind++; + + if(itfProvide != NULL) + LOG_INTERNAL(1, " -> Singleton[%d] : Register binding %s/%x.%s -> %s/%x\n", + clientId, + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, + itfProvide->server->pathname, itfProvide->server); + else + LOG_INTERNAL(1, " -> Singleton[%d] : Register binding %s/%x.%s -> ARM/VOID\n", + clientId, + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, 0, 0); + } +} + +t_bool cm_unregisterSingletonBinding( + t_component_instance* component, + t_interface_require_description* itfRequire, + t_interface_provide_description* itfProvide, + t_nmf_client_id clientId) +{ + if(component->Template->classe == SINGLETON) + { + struct t_client_of_singleton* cl = cm_getClientOfSingleton(component, FALSE, clientId); + if(cl != NULL) + cl->numberOfBind--; + + if(itfProvide->server == (t_component_instance *)NMF_VOID_COMPONENT) + LOG_INTERNAL(1, " -> Singleton[%d] : Unregister binding %s/%x.%s -> ARM/VOID\n", + clientId, + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, 0, 0); + else if(itfProvide->server == NULL) + LOG_INTERNAL(1, " -> Singleton[%d] : Unregister binding %s/%x.%s -> ?? <already unbound>\n", + clientId, + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, 0, 0); + else + LOG_INTERNAL(1, " -> Singleton[%d] : Unregister binding %s/%x.%s -> %s/%x\n", + clientId, + itfRequire->client->pathname, itfRequire->client, itfRequire->origName, + itfProvide->server->pathname, itfProvide->server); + + if(getNumberOfBind(component) == 0) + { + LOG_INTERNAL(1, " -> Singleton[%d] : All required of %s/%x logically unbound, perform physical unbind\n", + clientId, itfRequire->client->pathname, itfRequire->client, 0, 0, 0); + + (void)cm_EEM_ForceWakeup(component->Template->dspId); + + // This is the last binding unbind all !!! + cm_destroyRequireInterface(component, clientId); + + cm_EEM_AllowSleep(component->Template->dspId); + } + else if(itfProvide->server != NULL) + { + t_interface_require* itfReq; + itfReq = &itfRequire->client->Template->requires[itfRequire->requireIndex]; + if((itfReq->requireTypes & OPTIONAL_REQUIRE) != 0x0) + return TRUE; + } + + return FALSE; + } + + return TRUE; +} + +static t_uint16 getNumberOfBind(t_component_instance* component) +{ + t_uint16 bindNumber = 0; + struct t_client_of_singleton* cur = component->clientOfSingleton; + + for( ; cur != NULL ; cur = cur->next) + { + bindNumber += cur->numberOfBind; + } + + return bindNumber; +} + +static void cm_fillItName(int interruptLine, char *itName) +{ + int divider = 10000; + + *itName++ = 'i'; + *itName++ = 't'; + *itName++ = '['; + + // Find first significant divider + while(divider > interruptLine) + divider /= 10; + + // Compute number + do + { + *itName++ = "0123456789"[interruptLine / divider]; + interruptLine %= divider; + divider /= 10; + } while(divider != 0); + + *itName++ = ']'; + *itName++ = '\0'; +} diff --git a/drivers/staging/nmf-cm/cm/engine/component/src/binder_check.c b/drivers/staging/nmf-cm/cm/engine/component/src/binder_check.c new file mode 100644 index 00000000000..373fea0cd47 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/src/binder_check.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include "../inc/bind.h" +#include <cm/engine/trace/inc/trace.h> + +#include <cm/engine/utils/inc/string.h> + +t_cm_error cm_checkValidClient( + const t_component_instance* client, + const char* requiredItfClientName, + t_interface_require_description *itfRequire, + t_bool *bindable) { + t_cm_error error; + + // Component LC state check + if (NULL == client) + return CM_INVALID_COMPONENT_HANDLE; + + // Check if the requiredItfClientName is required by client component + if ((error = cm_getRequiredInterface(client, requiredItfClientName, itfRequire)) != CM_OK) + return error; + + // Check required interface not already binded + { + t_interface_reference* itfRef = &client->interfaceReferences[itfRequire->requireIndex][itfRequire->collectionIndex]; + + if(itfRef->instance != (t_component_instance*)NULL) + { + if(client->Template->classe == SINGLETON) + { + // Singleton is immutable thus we can't rebind it, nevertheless it's not an issue + *bindable = FALSE; + return CM_OK; + } + else + { + t_interface_reference* itfRef = &client->interfaceReferences[itfRequire->requireIndex][itfRequire->collectionIndex]; + + if(itfRef->instance == (const t_component_instance*)NMF_VOID_COMPONENT) + ERROR("CM_INTERFACE_ALREADY_BINDED(): Component (%s<%s>.%s) already bound to VOID\n", + client->pathname, client->Template->name, requiredItfClientName, 0, 0, 0); + else + ERROR("CM_INTERFACE_ALREADY_BINDED(): Component (%s<%s>.%s) already bound to another server (%s<%s>.%s)\n", + client->pathname, client->Template->name, requiredItfClientName, + itfRef->instance->pathname, itfRef->instance->Template->name, itfRef->instance->Template->provides[itfRef->provideIndex].name); + return CM_INTERFACE_ALREADY_BINDED; + } + } + } + + // Delayed Component LC state check done only if not optional required interface or intrinsic one that has been solved by loader + { + t_interface_require* itfReq = &client->Template->requires[itfRequire->requireIndex]; + + if((itfReq->requireTypes & (OPTIONAL_REQUIRE | INTRINSEC_REQUIRE)) == 0) { + if(client->state == STATE_RUNNABLE) + return CM_COMPONENT_NOT_STOPPED; + } + } + + *bindable = TRUE; + + return CM_OK; +} + +t_cm_error cm_checkValidServer( + const t_component_instance* server, + const char* providedItfServerName, + t_interface_provide_description *itfProvide) { + t_cm_error error; + + // Check if the components are initialized + //if (server->state == STATE_INSTANCIATED) + // return CM_COMPONENT_NOT_INITIALIZED; + if(NULL == server) + return CM_INVALID_COMPONENT_HANDLE; + + // Check if the providedItfServerName is provided by server component + if((error = cm_getProvidedInterface(server, providedItfServerName, itfProvide)) != CM_OK) + return error; + + return CM_OK; +} + +t_cm_error cm_checkValidBinding( + const t_component_instance* client, + const char* requiredItfClientName, + const t_component_instance* server, + const char* providedItfServerName, + t_interface_require_description *itfRequire, + t_interface_provide_description *itfProvide, + t_bool *bindable) { + t_interface_require *require; + t_interface_provide *provide; + t_cm_error error; + + // Check Server + if((error = cm_checkValidServer(server, providedItfServerName, itfProvide)) != CM_OK) + return error; + + // Check Client + if((error = cm_checkValidClient(client, requiredItfClientName, itfRequire, bindable)) != CM_OK) + return error; + + // If this is a singleton which has been already bound check that next binding is at the same server + if(*bindable == FALSE + && client->Template->classe == SINGLETON) + { + t_interface_reference* itfRef = &client->interfaceReferences[itfRequire->requireIndex][itfRequire->collectionIndex]; + while( itfRef->instance != server + || itfRef->provideIndex != itfProvide->provideIndex + || itfRef->collectionIndex != itfProvide->collectionIndex ) + { + if(itfRef->instance == (const t_component_instance*)NMF_VOID_COMPONENT) + { + ERROR("CM_INTERFACE_ALREADY_BINDED(): Singleton (%s<%s>.%s) already bound to VOID\n", + client->pathname, client->Template->name, requiredItfClientName, 0, 0, 0); + return CM_INTERFACE_ALREADY_BINDED; + } + else if(itfRef->bfInfoID == BF_ASYNCHRONOUS || itfRef->bfInfoID == BF_TRACE) + { + t_interface_require_description eventitfRequire; + CM_ASSERT(cm_getRequiredInterface(itfRef->instance, "target", &eventitfRequire) == CM_OK); + itfRef = &itfRef->instance->interfaceReferences[eventitfRequire.requireIndex][eventitfRequire.collectionIndex]; + + // Go to see client of event if the same + } + else + { + ERROR("CM_INTERFACE_ALREADY_BINDED(): Singleton (%s<%s>.%s) already bound to different server (%s<%s>.%s)\n", + client->pathname, client->Template->name, requiredItfClientName, + itfRef->instance->pathname, itfRef->instance->Template->name, itfRef->instance->Template->provides[itfRef->provideIndex].name); + return CM_INTERFACE_ALREADY_BINDED; + } + } + } + + // Check if provided and required type matches + require = &client->Template->requires[itfRequire->requireIndex]; + provide = &server->Template->provides[itfProvide->provideIndex]; + if(require->interface != provide->interface) + { + ERROR("CM_ILLEGAL_BINDING(%s, %s)\n", require->interface->type, provide->interface->type, 0, 0, 0, 0); + return CM_ILLEGAL_BINDING; + } + + // Check if static required interface binded to singleton component + if((require->requireTypes & STATIC_REQUIRE) && + (server->Template->classe != SINGLETON)) + { + ERROR("CM_ILLEGAL_BINDING(): Can't bind static required interface to not singleton component\n", + 0, 0, 0, 0, 0, 0); + return CM_ILLEGAL_BINDING; + } + + return CM_OK; +} + +t_cm_error cm_checkValidUnbinding( + const t_component_instance* client, + const char* requiredItfClientName, + t_interface_require_description *itfRequire, + t_interface_provide_description *itfProvide) { + t_cm_error error; + t_interface_require* itfReq; + + // Component LC state check + if (NULL == client) + return CM_INVALID_COMPONENT_HANDLE; + + // Check if the requiredItfClientName is required by client component + if ((error = cm_getRequiredInterface(client, requiredItfClientName, itfRequire)) != CM_OK) + return error; + + itfReq = &client->Template->requires[itfRequire->requireIndex]; + + // Check if the requiredItfClientName is required by client component + if ((error = cm_lookupInterface(itfRequire, itfProvide)) != CM_OK) + { + // We allow to unbind optional required of singleton even if not binded, since it could have been unbound previously but we don't + // want to break bind singleton reference counter + if((client->Template->classe == SINGLETON) && + (itfReq->requireTypes & OPTIONAL_REQUIRE) != 0x0) + return CM_OK; + + return error; + } + + // Singleton is immutable, don't unbind it + if(client->Template->classe == SINGLETON) + return CM_OK; + + /* if interface is optionnal then allow unbinding even if not stop */ + if((itfReq->requireTypes & OPTIONAL_REQUIRE) == 0x0) + { + if(client->state == STATE_RUNNABLE) + return CM_COMPONENT_NOT_STOPPED; + } + + return CM_OK; +} + diff --git a/drivers/staging/nmf-cm/cm/engine/component/src/component_wrapper.c b/drivers/staging/nmf-cm/cm/engine/component/src/component_wrapper.c new file mode 100644 index 00000000000..88e6b4749ec --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/src/component_wrapper.c @@ -0,0 +1,1298 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/api/component_engine.h> +#include <cm/engine/api/communication_engine.h> + +#include <cm/engine/component/inc/bind.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/utils/inc/string.h> +#include <cm/engine/memory/inc/domain.h> + +#include <cm/engine/configuration/inc/configuration.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> + +/* + * Component mangement wrapping. + */ +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_InstantiateComponent( + const char* templateName, + t_cm_domain_id domainId, + t_nmf_client_id clientId, + t_nmf_ee_priority priority, + const char localName[MAX_COMPONENT_NAME_LENGTH], + const char *dataFile, + t_cm_instance_handle *instance) { + t_cm_error error; + t_nmf_core_id coreId; + t_component_instance *comp; + t_elfdescription *elfhandle = NULL; + + OSAL_LOCK_API(); + + /* + * Load Elf File + */ + if(dataFile != NULL && + (error = cm_ELF_CheckFile( + dataFile, + TRUE, + &elfhandle)) != CM_OK) + goto out; + + //only allow instantiation in non-scratch domains (ie. DOMAIN_NORMAL)! + if ((error = cm_DM_CheckDomainWithClient(domainId, DOMAIN_NORMAL, clientId)) != CM_OK) + goto out; + + coreId = cm_DM_GetDomainCoreId(domainId); + + if(coreId < FIRST_MPC_ID || coreId > LAST_CORE_ID) + { + error = CM_INVALID_PARAMETER; + goto out; + } + + if ((error = cm_CFG_CheckMpcStatus(coreId)) != CM_OK) + goto out; + + if ((error = cm_EEM_ForceWakeup(coreId)) != CM_OK) + goto out; + + error = cm_instantiateComponent( + templateName, + domainId, + priority, + localName, + elfhandle, + &comp); + if(error == CM_OK) + *instance = comp->instance; + + cm_EEM_AllowSleep(coreId); + +out: + cm_ELF_CloseFile(TRUE, elfhandle); + + OSAL_UNLOCK_API(); + + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_StartComponent( + t_cm_instance_handle instance, + t_nmf_client_id clientId) { + t_cm_error error; + t_component_instance *component; + + OSAL_LOCK_API(); + + component = cm_lookupComponent(instance); + if (NULL == component) + error = CM_INVALID_COMPONENT_HANDLE; + else + { + error = cm_startComponent(component, clientId); + } + + OSAL_UNLOCK_API(); + + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_StopComponent( + t_cm_instance_handle instance, + t_nmf_client_id clientId) { + t_cm_error error; + t_component_instance *component; + + OSAL_LOCK_API(); + + component = cm_lookupComponent(instance); + if (NULL == component) + error = CM_INVALID_COMPONENT_HANDLE; + else + { + error = cm_stopComponent(component, clientId); + } + + OSAL_UNLOCK_API(); + + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_DestroyComponent( + t_cm_instance_handle instance, + t_nmf_client_id clientId) +{ + t_cm_error error; + t_component_instance *component; + + OSAL_LOCK_API(); + + component = cm_lookupComponent(instance); + if (NULL == component) + { + error = CM_INVALID_COMPONENT_HANDLE; + } + else + { + t_nmf_core_id coreId = component->Template->dspId; + + (void)cm_EEM_ForceWakeup(coreId); + + error = cm_destroyInstanceForClient(component, DESTROY_NORMAL, clientId); + + cm_CFG_ReleaseMpc(coreId); + + cm_EEM_AllowSleep(coreId); + } + + OSAL_UNLOCK_API(); + + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_FlushComponents(t_nmf_client_id clientId) +{ + t_cm_error error = CM_OK; + t_component_instance *instance; + t_uint32 i; + + if (clientId == 0) + return CM_INVALID_PARAMETER; + + OSAL_LOCK_API(); + + // We don't know exactly where components will be, wake up everybody !! + (void)cm_EEM_ForceWakeup(SVA_CORE_ID); + (void)cm_EEM_ForceWakeup(SIA_CORE_ID); + + /* Destroy all host2mpc bindings */ + OSAL_LOCK_COM(); + for (i=0; i<Host2MpcBindingTable.idxMax; i++) + { + t_host2mpc_bf_info* bfInfo; + bfInfo = Host2MpcBindingTable.entries[i]; + if ((bfInfo != NULL) && (bfInfo->clientId == clientId)) { + cm_delEntry(&Host2MpcBindingTable, i); + OSAL_UNLOCK_COM(); + cm_unbindComponentFromCMCore(bfInfo); + OSAL_LOCK_COM(); + } + } + OSAL_UNLOCK_COM(); + + /* First, stop all remaining components for this client */ + for (i=0; i<ComponentTable.idxMax; i++) + { + if ((instance = componentEntry(i)) == NULL) + continue; + if (/* skip EE */ + (instance->Template->classe == FIRMWARE) || + /* Skip all binding components */ + (cm_StringCompare(instance->Template->name, "_ev.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_st.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_sk.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_tr.", 4) == 0)) + continue; + + /* + * Special code for SINGLETON handling + */ + if(instance->Template->classe == SINGLETON) + { + struct t_client_of_singleton* cl = cm_getClientOfSingleton(instance, FALSE, clientId); + if(cl == NULL) + continue; + + cl->numberOfStart = 1; // == 1 since it will go to 0 in cm_stopComponent + cl->numberOfInstance = 1; // == 1 since it will go to 0 in cm_destroyInstanceForClient + cl->numberOfBind = 0; // == 0 since we don't want anymore binding for this component + } + else if(domainDesc[instance->domainId].client != clientId) + /* Skip all components not belonging to our client */ + continue; + + // Stop the component + error = cm_stopComponent(instance, clientId); + if (error != CM_OK && error != CM_COMPONENT_NOT_STARTED) + LOG_INTERNAL(0, "Error stopping component %s/%x (%s, error=%d, client=%u)\n", instance->pathname, instance, instance->Template->name, error, clientId, 0); + + // Destroy dependencies + cm_destroyRequireInterface(instance, clientId); + } + + /* Destroy all remaining components for this client */ + for (i=0; i<ComponentTable.idxMax; i++) + { + if ((instance = componentEntry(i)) == NULL) + continue; + if (/* skip EE */ + (instance->Template->classe == FIRMWARE) || + /* Skip all binding components */ + (cm_StringCompare(instance->Template->name, "_ev.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_st.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_sk.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_tr.", 4) == 0)) { + continue; + } + + + /* + * Special code for SINGLETON handling + */ + if(instance->Template->classe == SINGLETON) + { + struct t_client_of_singleton* cl = cm_getClientOfSingleton(instance, FALSE, clientId); + if(cl == NULL) + continue; + } + else if(domainDesc[instance->domainId].client != clientId) + /* Skip all components not belonging to our client */ + continue; + + + // Destroy the component + error = cm_destroyInstanceForClient(instance, DESTROY_WITHOUT_CHECK, clientId); + + if (error != CM_OK) + { + /* FIXME : add component name instance in log message but need to make a copy before cm_flushComponent() + * because it's no more available after. + */ + LOG_INTERNAL(0, "Error flushing component (error=%d, client=%u)\n", error, clientId, 0, 0, 0, 0); + } + } + + cm_CFG_ReleaseMpc(SVA_CORE_ID); + cm_CFG_ReleaseMpc(SIA_CORE_ID); + + cm_EEM_AllowSleep(SVA_CORE_ID); + cm_EEM_AllowSleep(SIA_CORE_ID); + + OSAL_UNLOCK_API(); + + return error; +} + +/* + * Component binding wrapping. + */ +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_BindComponent( + const t_cm_instance_handle clientInstance, + const char* requiredItfClientName, + const t_cm_instance_handle serverInstance, + const char* providedItfServerName, + t_bool traced, + t_nmf_client_id clientId, + const char *dataFileTrace) { + t_interface_require_description itfRequire; + t_interface_provide_description itfProvide; + t_bool bindable; + t_cm_error error; + t_component_instance *client, *server; + t_elfdescription *elfhandleTrace = NULL; + + OSAL_LOCK_API(); + + /* + * Load Elf File + */ + if(dataFileTrace != NULL && + (error = cm_ELF_CheckFile( + dataFileTrace, + TRUE, + &elfhandleTrace)) != CM_OK) + goto out; + + client = cm_lookupComponent(clientInstance); + server = cm_lookupComponent(serverInstance); + // Sanity check + if((error = cm_checkValidBinding(client, requiredItfClientName, + server, providedItfServerName, + &itfRequire, &itfProvide, &bindable)) != CM_OK) + goto out; + + // Check that client and server component run on same DSP + if (itfRequire.client->Template->dspId != itfProvide.server->Template->dspId) + { + error = CM_ILLEGAL_BINDING; + goto out; + } + + // Check if we really need to bind + if(bindable) + { + if ((error = cm_EEM_ForceWakeup(itfRequire.client->Template->dspId)) != CM_OK) + goto out; + + /* + * Synchronous binding, so no binding component + */ + if(traced) + error = cm_bindInterfaceTrace(&itfRequire, &itfProvide, elfhandleTrace); + else + error = cm_bindInterface(&itfRequire, &itfProvide); + + cm_EEM_AllowSleep(itfRequire.client->Template->dspId); + } + + cm_registerSingletonBinding(client, &itfRequire, &itfProvide, clientId); + +out: + cm_ELF_CloseFile(TRUE, elfhandleTrace); + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_UnbindComponent( + const t_cm_instance_handle clientInstance, + const char* requiredItfClientName, + t_nmf_client_id clientId) { + t_interface_require_description itfRequire; + t_interface_provide_description itfProvide; + t_bf_info_ID bfInfoID; + t_cm_error error; + t_component_instance *client; + + OSAL_LOCK_API(); + + client = cm_lookupComponent(clientInstance); + // Sanity check + if((error = cm_checkValidUnbinding(client, requiredItfClientName, + &itfRequire, &itfProvide)) != CM_OK) + goto out; + + // Check if this is a Primitive binding + bfInfoID = itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfoID; + if(bfInfoID != BF_SYNCHRONOUS && bfInfoID != BF_TRACE) + { + error = CM_ILLEGAL_UNBINDING; + goto out; + } + + // Check if we really need to unbind + if(cm_unregisterSingletonBinding(client, &itfRequire, &itfProvide, clientId)) + { + (void)cm_EEM_ForceWakeup(itfRequire.client->Template->dspId); + + if(bfInfoID == BF_SYNCHRONOUS) + cm_unbindInterface(&itfRequire); + else + cm_unbindInterfaceTrace( + &itfRequire, + (t_trace_bf_info*)itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfo); + + cm_EEM_AllowSleep(itfRequire.client->Template->dspId); + + error = CM_OK; + } + +out: + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_BindComponentToVoid( + const t_cm_instance_handle clientInstance, + const char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH], + t_nmf_client_id clientId) +{ + t_interface_require_description itfRequire; + t_bool bindable; + t_cm_error error; + t_component_instance *client; + + OSAL_LOCK_API(); + + client = cm_lookupComponent(clientInstance); + // Check invalid binding + if((error = cm_checkValidClient(client, requiredItfClientName, + &itfRequire, &bindable)) != CM_OK) + goto out; + + // Check if we really need to bind + if(bindable) + { + if ((error = cm_EEM_ForceWakeup(itfRequire.client->Template->dspId)) != CM_OK) + goto out; + + error = cm_bindInterfaceToVoid(&itfRequire); + + cm_EEM_AllowSleep(itfRequire.client->Template->dspId); + } + + cm_registerSingletonBinding(client, &itfRequire, NULL, clientId); + +out: + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_BindComponentAsynchronous( + const t_cm_instance_handle clientInstance, + const char* requiredItfClientName, + const t_cm_instance_handle serverInstance, + const char* providedItfServerName, + t_uint32 fifosize, + t_cm_mpc_memory_type eventMemType, + t_nmf_client_id clientId, + const char *dataFileSkeletonOrEvent, + const char *dataFileStub) { + t_interface_require_description itfRequire; + t_interface_provide_description itfProvide; + t_dsp_memory_type_id dspEventMemType; + t_bool bindable; + t_cm_error error; + t_component_instance *client, *server; + t_elfdescription *elfhandleSkeletonOrEvent = NULL; + t_elfdescription *elfhandleStub = NULL; + + OSAL_LOCK_API(); + + /* + * Load Elf File + */ + if(dataFileSkeletonOrEvent != NULL && + (error = cm_ELF_CheckFile( + dataFileSkeletonOrEvent, + TRUE, + &elfhandleSkeletonOrEvent)) != CM_OK) + goto out; + if(dataFileStub != NULL && + (error = cm_ELF_CheckFile( + dataFileStub, + TRUE, + &elfhandleStub)) != CM_OK) + goto out; + + client = cm_lookupComponent(clientInstance); + server = cm_lookupComponent(serverInstance); + // Check invalid binding + if((error = cm_checkValidBinding(client, requiredItfClientName, + server, providedItfServerName, + &itfRequire, &itfProvide, &bindable)) != CM_OK) + goto out; + + switch(eventMemType) + { + case CM_MM_MPC_TCM24_X: + dspEventMemType = INTERNAL_XRAM24; + break; + case CM_MM_MPC_ESRAM24: + dspEventMemType = ESRAM_EXT24; + break; + case CM_MM_MPC_SDRAM24: + dspEventMemType = SDRAM_EXT24; + break; + default: + error = CM_INVALID_PARAMETER; + goto out; + } + + // Check if we really need to bind + if(bindable) + { + // Create the binding and bind it to the client (or all sub-components clients ....) + if (itfRequire.client->Template->dspId != itfProvide.server->Template->dspId) + { + if ((error = cm_EEM_ForceWakeup(itfRequire.client->Template->dspId)) != CM_OK) + goto out; + if ((error = cm_EEM_ForceWakeup(itfProvide.server->Template->dspId)) != CM_OK) + { + cm_EEM_AllowSleep(itfRequire.client->Template->dspId); + goto out; + } + + // This is a distribute communication + error = cm_bindInterfaceDistributed( + &itfRequire, + &itfProvide, + fifosize, + dspEventMemType, + elfhandleSkeletonOrEvent, + elfhandleStub); + + cm_EEM_AllowSleep(itfRequire.client->Template->dspId); + cm_EEM_AllowSleep(itfProvide.server->Template->dspId); + } + else + { + if ((error = cm_EEM_ForceWakeup(itfRequire.client->Template->dspId)) != CM_OK) + goto out; + + // This is a acynchronous communication + error = cm_bindInterfaceAsynchronous( + &itfRequire, + &itfProvide, + fifosize, + dspEventMemType, + elfhandleSkeletonOrEvent); + + cm_EEM_AllowSleep(itfRequire.client->Template->dspId); + } + } + + cm_registerSingletonBinding(client, &itfRequire, &itfProvide, clientId); + +out: + cm_ELF_CloseFile(TRUE, elfhandleSkeletonOrEvent); + cm_ELF_CloseFile(TRUE, elfhandleStub); + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_UnbindComponentAsynchronous( + const t_cm_instance_handle instance, + const char* requiredItfClientName, + t_nmf_client_id clientId) { + t_interface_require_description itfRequire; + t_interface_provide_description itfProvide; + t_bf_info_ID bfInfoID; + t_cm_error error; + t_component_instance *client; + + OSAL_LOCK_API(); + + client = cm_lookupComponent(instance); + // Sanity check + if((error = cm_checkValidUnbinding(client, requiredItfClientName, + &itfRequire, &itfProvide)) != CM_OK) + goto out; + + bfInfoID = itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfoID; + + // Check if we really need to unbind + if(cm_unregisterSingletonBinding(client, &itfRequire, &itfProvide, clientId)) + { + // Check if this is a Asynchronous binding + if(bfInfoID == BF_DSP2DSP) + { + t_nmf_core_id clientDsp = itfRequire.client->Template->dspId; + t_nmf_core_id serverDsp = itfProvide.server->Template->dspId; + + (void)cm_EEM_ForceWakeup(clientDsp); + (void)cm_EEM_ForceWakeup(serverDsp); + + cm_unbindInterfaceDistributed( + &itfRequire, + (t_mpc2mpc_bf_info*)itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfo); + + cm_EEM_AllowSleep(clientDsp); + cm_EEM_AllowSleep(serverDsp); + + error = CM_OK; + } + else if(bfInfoID == BF_ASYNCHRONOUS) + { + t_nmf_core_id clientDsp = itfRequire.client->Template->dspId; + + (void)cm_EEM_ForceWakeup(clientDsp); + + cm_unbindInterfaceAsynchronous( + &itfRequire, + (t_async_bf_info*)itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfo); + + cm_EEM_AllowSleep(clientDsp); + + error = CM_OK; + } + else + error = CM_ILLEGAL_UNBINDING; + } + + out: + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_BindComponentFromCMCore( + const t_cm_instance_handle server, + const char* providedItfServerName, + t_uint32 fifosize, + t_cm_mpc_memory_type eventMemType, + t_cm_bf_host2mpc_handle *bfHost2mpcHdl, + t_nmf_client_id clientId, + const char *dataFileSkeleton) { + t_interface_provide_description itfProvide; + t_dsp_memory_type_id dspEventMemType; + t_cm_error error; + t_component_instance* component; + t_host2mpc_bf_info *bfInfo; + t_elfdescription *elfhandleSkeleton = NULL; + + OSAL_LOCK_API(); + + /* + * Load Elf File + */ + if(dataFileSkeleton != NULL && + (error = cm_ELF_CheckFile( + dataFileSkeleton, + TRUE, + &elfhandleSkeleton)) != CM_OK) + goto out; + + component = cm_lookupComponent(server); + // Check server validity + if((error = cm_checkValidServer(component, providedItfServerName, + &itfProvide)) != CM_OK) + goto out; + + if ((error = cm_EEM_ForceWakeup(itfProvide.server->Template->dspId)) != CM_OK) + goto out; + + switch(eventMemType) + { + case CM_MM_MPC_TCM24_X: + dspEventMemType = INTERNAL_XRAM24; + break; + case CM_MM_MPC_ESRAM24: + dspEventMemType = ESRAM_EXT24; + break; + case CM_MM_MPC_SDRAM24: + dspEventMemType = SDRAM_EXT24; + break; + default: + goto out; + } + + error = cm_bindComponentFromCMCore(&itfProvide, + fifosize, + dspEventMemType, + elfhandleSkeleton, + &bfInfo); + + cm_EEM_AllowSleep(itfProvide.server->Template->dspId); + +out: + cm_ELF_CloseFile(TRUE, elfhandleSkeleton); + OSAL_UNLOCK_API(); + + if (error == CM_OK) { + bfInfo->clientId = clientId; + OSAL_LOCK_COM(); + *bfHost2mpcHdl = cm_addEntry(&Host2MpcBindingTable, bfInfo); + if (*bfHost2mpcHdl == 0) + error = CM_NO_MORE_MEMORY; + OSAL_UNLOCK_COM(); + + if (error != CM_OK) { + OSAL_LOCK_API(); + (void)cm_EEM_ForceWakeup(itfProvide.server->Template->dspId); + cm_unbindComponentFromCMCore(bfInfo); + cm_EEM_AllowSleep(itfProvide.server->Template->dspId); + OSAL_UNLOCK_API(); + } + } + + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_UnbindComponentFromCMCore( + t_cm_bf_host2mpc_handle bfHost2mpcId) { + t_host2mpc_bf_info* bfInfo; + t_nmf_core_id coreId; + + OSAL_LOCK_COM(); + bfInfo = cm_lookupEntry(&Host2MpcBindingTable, bfHost2mpcId); + if (bfInfo) + cm_delEntry(&Host2MpcBindingTable, bfHost2mpcId & INDEX_MASK); + OSAL_UNLOCK_COM(); + if (NULL == bfInfo) + return CM_INVALID_PARAMETER; + + OSAL_LOCK_API(); + + // Check if this is a DSP to Host binding + //if(bfInfo->id != BF_HOST2DSP) + // return CM_ILLEGAL_UNBINDING; + coreId = bfInfo->dspskeleton.skelInstance->Template->dspId; + + (void)cm_EEM_ForceWakeup(coreId); + + cm_unbindComponentFromCMCore(bfInfo); + + cm_EEM_AllowSleep(coreId); + + OSAL_UNLOCK_API(); + return CM_OK; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_BindComponentToCMCore( + const t_cm_instance_handle instance, + const char *requiredItfClientName, + t_uint32 fifosize, + t_nmf_mpc2host_handle upLayerThis, + const char *dataFileStub, + t_cm_bf_mpc2host_handle *mpc2hostId, + t_nmf_client_id clientId) { + t_interface_require_description itfRequire; + t_bool bindable; + t_cm_error error; + t_component_instance* client; + t_elfdescription *elfhandleStub = NULL; + + OSAL_LOCK_API(); + + /* + * Load Elf File + */ + if(dataFileStub != NULL && + (error = cm_ELF_CheckFile( + dataFileStub, + TRUE, + &elfhandleStub)) != CM_OK) + goto out; + + client = cm_lookupComponent(instance); + // Check invalid binding + if((error = cm_checkValidClient(client, requiredItfClientName, + &itfRequire, &bindable)) != CM_OK) + goto out; + + // Check if we really need to bind + if(bindable) + { + if ((error = cm_EEM_ForceWakeup(itfRequire.client->Template->dspId)) != CM_OK) + goto out; + + error = cm_bindComponentToCMCore( + &itfRequire, + fifosize, + upLayerThis, + elfhandleStub, + (t_mpc2host_bf_info**)mpc2hostId); + + cm_EEM_AllowSleep(itfRequire.client->Template->dspId); + } + + cm_registerSingletonBinding(client, &itfRequire, NULL, clientId); + +out: + cm_ELF_CloseFile(TRUE, elfhandleStub); + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_UnbindComponentToCMCore( + const t_cm_instance_handle instance, + const char *requiredItfClientName, + t_nmf_mpc2host_handle *upLayerThis, + t_nmf_client_id clientId) { + t_interface_require_description itfRequire; + t_interface_provide_description itfProvide; + t_cm_error error; + t_mpc2host_bf_info *bfInfo; + t_component_instance* client; + + OSAL_LOCK_API(); + + client = cm_lookupComponent(instance); + // Sanity check + if((error = cm_checkValidUnbinding(client, requiredItfClientName, + &itfRequire, &itfProvide)) != CM_OK) + goto out; + + // Check if this is a DSP to Host binding + if(itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfoID != BF_DSP2HOST) + { + error = CM_ILLEGAL_UNBINDING; + goto out; + } + + bfInfo = (t_mpc2host_bf_info*)itfRequire.client->interfaceReferences[itfRequire.requireIndex][itfRequire.collectionIndex].bfInfo; + + // Get client information + *upLayerThis = bfInfo->context; + + // Check if we really need to unbind + if(cm_unregisterSingletonBinding(client, &itfRequire, &itfProvide, clientId)) + { + (void)cm_EEM_ForceWakeup(itfRequire.client->Template->dspId); + + cm_unbindComponentToCMCore(&itfRequire, bfInfo); + + cm_EEM_AllowSleep(itfRequire.client->Template->dspId); + + error = CM_OK; + } +out: + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_event_params_handle CM_ENGINE_AllocEvent(t_cm_bf_host2mpc_handle host2mpcId) { + t_host2mpc_bf_info* bfInfo; + t_event_params_handle eventHandle; + + OSAL_LOCK_COM(); + bfInfo = cm_lookupEntry(&Host2MpcBindingTable, host2mpcId); + if (NULL == bfInfo) { + OSAL_UNLOCK_COM(); + return NULL; + } + + if(bfInfo->dspskeleton.skelInstance->interfaceReferences[0][0].instance->state != STATE_RUNNABLE) { + ERROR("CM_COMPONENT_NOT_STARTED: Call interface before start component %s<%s>\n", + bfInfo->dspskeleton.skelInstance->pathname, + bfInfo->dspskeleton.skelInstance->Template->name, 0, 0, 0, 0); + } + + eventHandle = cm_AllocEvent(bfInfo->fifo); + + OSAL_UNLOCK_COM(); + + return eventHandle; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_PushEvent(t_cm_bf_host2mpc_handle host2mpcId, t_event_params_handle h, t_uint32 methodIndex) { + t_host2mpc_bf_info* bfInfo; + t_cm_error error; + + OSAL_LOCK_COM(); + bfInfo = cm_lookupEntry(&Host2MpcBindingTable, host2mpcId); + if (NULL == bfInfo) { + OSAL_UNLOCK_COM(); + return CM_INVALID_PARAMETER; + } + error = cm_PushEvent(bfInfo->fifo, h, methodIndex); + OSAL_UNLOCK_COM(); + + return error; +} + +PUBLIC EXPORT_SHARED void CM_ENGINE_AcknowledgeEvent(t_cm_bf_mpc2host_handle mpc2hostId) { + t_mpc2host_bf_info* bfInfo = (t_mpc2host_bf_info*)mpc2hostId; + + //t_dsp2host_bf_info* bfInfo = (t_host2mpc_bf_info*)mpc2hostId; + OSAL_LOCK_COM(); + cm_AcknowledgeEvent(bfInfo->fifo); + OSAL_UNLOCK_COM(); +} + +/* + * Get a reference on a given attribute of a given component + */ +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_ReadComponentAttribute( + const t_cm_instance_handle instance, + const char* attrName, + t_uint24 *attrValue) +{ + t_cm_error error; + t_component_instance* component; + + OSAL_LOCK_API(); + + component = cm_lookupComponent(instance); + if (NULL == component) + error = CM_INVALID_COMPONENT_HANDLE; + else + { + if ((error = cm_EEM_ForceWakeup(component->Template->dspId)) != CM_OK) + goto out; + + // t_uint24 -> t_uint32 possible since we know it same size + error = cm_readAttribute(component, attrName, (t_uint32*)attrValue); + + cm_EEM_AllowSleep(component->Template->dspId); + } + +out: + OSAL_UNLOCK_API(); + return error; +} + +/*=============================================================================== + * Introspection API + *===============================================================================*/ +/* + * Component + */ +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentListHeader( + const t_nmf_client_id client, + t_cm_instance_handle *headerComponent) { + t_uint32 i; + + OSAL_LOCK_API(); + + *headerComponent = 0; + for (i=0; i < ComponentTable.idxMax; i++) { + if ((componentEntry(i) != NULL) && + (componentEntry(i)->Template->classe != FIRMWARE) && + (domainDesc[componentEntry(i)->domainId].client == client)) { + *headerComponent = ENTRY2HANDLE(componentEntry(i), i);; + break; + } + } + + OSAL_UNLOCK_API(); + + return CM_OK; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentListNext( + const t_nmf_client_id client, + const t_cm_instance_handle prevComponent, + t_cm_instance_handle *nextComponent){ + t_cm_error error; + t_uint32 i = prevComponent & INDEX_MASK; + + OSAL_LOCK_API(); + + // Sanity check + if ((i >= ComponentTable.idxMax) + || (((unsigned int)componentEntry(i) << INDEX_SHIFT) != (prevComponent & ~INDEX_MASK))) + error = CM_INVALID_COMPONENT_HANDLE; + else { + *nextComponent = 0; + for (i++; i < ComponentTable.idxMax; i++) { + if ((componentEntry(i) != NULL) && + (componentEntry(i)->Template->classe != FIRMWARE) && + (domainDesc[componentEntry(i)->domainId].client == client)) { + *nextComponent = ENTRY2HANDLE(componentEntry(i), i);; + break; + } + } + + error = CM_OK; + } + + OSAL_UNLOCK_API(); + + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentDescription( + const t_cm_instance_handle instance, + char *templateName, + t_uint32 templateNameLength, + t_nmf_core_id *coreId, + char *localName, + t_uint32 localNameLength, + t_nmf_ee_priority *priority) { + t_component_instance *comp; + t_cm_error error; + + OSAL_LOCK_API(); + + comp = cm_lookupComponent(instance); + // Sanity check + if (NULL == comp) { + error = CM_INVALID_COMPONENT_HANDLE; + } else { + cm_StringCopy( + templateName, + comp->Template->name, + templateNameLength); + *coreId = comp->Template->dspId; + cm_StringCopy( + localName, + comp->pathname, + localNameLength); + if (priority) + *priority = comp->priority; + error = CM_OK; + } + + OSAL_UNLOCK_API(); + + return error; +} + +/* + * Require interface + */ +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentRequiredInterfaceNumber( + const t_cm_instance_handle instance, + t_uint8 *numberRequiredInterfaces) { + t_component_instance *comp; + t_cm_error error; + + OSAL_LOCK_API(); + + comp = cm_lookupComponent(instance); + // Sanity check + if (NULL == comp) { + error = CM_INVALID_COMPONENT_HANDLE; + } else { + *numberRequiredInterfaces = comp->Template->requireNumber; + + error = CM_OK; + } + + OSAL_UNLOCK_API(); + + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentRequiredInterface( + const t_cm_instance_handle instance, + const t_uint8 index, + char *itfName, + t_uint32 itfNameLength, + char *itfType, + t_uint32 itfTypeLength, + t_cm_require_state *requireState, + t_sint16 *collectionSize) { + t_component_instance *comp; + t_cm_error error; + + OSAL_LOCK_API(); + + comp = cm_lookupComponent(instance); + // Sanity check + if (NULL == comp) { + error = CM_INVALID_COMPONENT_HANDLE; + } else if(index >= comp->Template->requireNumber) { + error = CM_NO_SUCH_REQUIRED_INTERFACE; + } else { + cm_StringCopy( + itfName, + comp->Template->requires[index].name, + itfNameLength); + cm_StringCopy( + itfType, + comp->Template->requires[index].interface->type, + itfTypeLength); + if(comp->Template->requires[index].requireTypes & COLLECTION_REQUIRE) + *collectionSize = comp->Template->requires[index].collectionSize; + else + *collectionSize = -1; + + if(requireState != NULL) { + *requireState = 0; + if(comp->Template->requires[index].requireTypes & COLLECTION_REQUIRE) + *requireState |= CM_REQUIRE_COLLECTION; + if(comp->Template->requires[index].requireTypes & OPTIONAL_REQUIRE) + *requireState |= CM_REQUIRE_OPTIONAL; + if(comp->Template->requires[index].requireTypes & STATIC_REQUIRE) + *requireState |= CM_REQUIRE_STATIC; + } + + error = CM_OK; + } + + OSAL_UNLOCK_API(); + + return error; +} +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentRequiredInterfaceBinding( + const t_cm_instance_handle instance, + const char *itfName, + t_cm_instance_handle *server, + char *serverItfName, + t_uint32 serverItfNameLength) { + t_component_instance *comp; + t_interface_require_description itfRequire; + t_interface_provide_description itfProvide; + t_cm_error error; + + OSAL_LOCK_API(); + + comp = cm_lookupComponent(instance); + // Sanity check + if(NULL == comp) { + error = CM_INVALID_COMPONENT_HANDLE; + } else if ((error = cm_getRequiredInterface(comp, itfName, &itfRequire)) != CM_OK) { + // Check if the requiredItfClientName is required by client component + } else if ((error = cm_lookupInterface(&itfRequire, &itfProvide)) != CM_OK) { + // Check if the requiredItfClientName is required by client component + } else { + if ((t_cm_instance_handle)itfProvide.server == NMF_HOST_COMPONENT + || (t_cm_instance_handle)itfProvide.server == NMF_VOID_COMPONENT) + *server = (t_cm_instance_handle)itfProvide.server; + else + *server = itfProvide.server->instance; + if(*server == NMF_HOST_COMPONENT) { + cm_StringCopy( + serverItfName, + "unknown", + serverItfNameLength); + } else if(*server == NMF_VOID_COMPONENT) { + cm_StringCopy( + serverItfName, + "void", + serverItfNameLength); + } else if(*server != 0) { + cm_StringCopy( + serverItfName, + itfProvide.server->Template->provides[itfProvide.provideIndex].name, + serverItfNameLength); + if(itfProvide.server->Template->provides[itfProvide.provideIndex].provideTypes & COLLECTION_PROVIDE) { + int len = cm_StringLength(serverItfName, serverItfNameLength); + serverItfName[len++] = '['; + if(itfProvide.collectionIndex >= 100) + serverItfName[len++] = '0' + (itfProvide.collectionIndex / 100); + if(itfProvide.collectionIndex >= 10) + serverItfName[len++] = '0' + ((itfProvide.collectionIndex % 100) / 10); + serverItfName[len++] = '0' + (itfProvide.collectionIndex % 10); + serverItfName[len++] = ']'; + serverItfName[len] = 0; + } + } + + error = CM_OK; + } + + OSAL_UNLOCK_API(); + + return error; +} + +/* + * Provide interface + */ +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentProvidedInterfaceNumber( + const t_cm_instance_handle instance, + t_uint8 *numberProvidedInterfaces) { + t_component_instance *comp; + t_cm_error error; + + OSAL_LOCK_API(); + + comp = cm_lookupComponent(instance); + // Sanity check + if (NULL == comp) { + error = CM_INVALID_COMPONENT_HANDLE; + } else { + *numberProvidedInterfaces = comp->Template->provideNumber; + + error = CM_OK; + } + + OSAL_UNLOCK_API(); + + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentProvidedInterface( + const t_cm_instance_handle instance, + const t_uint8 index, + char *itfName, + t_uint32 itfNameLength, + char *itfType, + t_uint32 itfTypeLength, + t_sint16 *collectionSize) { + t_component_instance *comp; + t_cm_error error; + + OSAL_LOCK_API(); + + comp = cm_lookupComponent(instance); + // Sanity check + if (NULL == comp) { + error = CM_INVALID_COMPONENT_HANDLE; + } else if(index >= comp->Template->provideNumber) { + error = CM_NO_SUCH_PROVIDED_INTERFACE; + } else { + cm_StringCopy( + itfName, + comp->Template->provides[index].name, + itfNameLength); + cm_StringCopy( + itfType, + comp->Template->provides[index].interface->type, + itfTypeLength); + if(comp->Template->provides[index].provideTypes & COLLECTION_PROVIDE) + *collectionSize = comp->Template->provides[index].collectionSize; + else + *collectionSize = -1; + + error = CM_OK; + } + + OSAL_UNLOCK_API(); + + return error; +} + +/* + * Component Property + */ +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentPropertyNumber( + const t_cm_instance_handle instance, + t_uint8 *numberProperties) { + t_component_instance *comp; + t_cm_error error; + + OSAL_LOCK_API(); + + comp = cm_lookupComponent(instance); + // Sanity check + if (NULL == comp) { + error = CM_INVALID_COMPONENT_HANDLE; + } else { + *numberProperties = comp->Template->propertyNumber; + + error = CM_OK; + } + + OSAL_UNLOCK_API(); + + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentPropertyName( + const t_cm_instance_handle instance, + const t_uint8 index, + char *propertyName, + t_uint32 propertyNameLength) { + t_component_instance *comp; + t_cm_error error; + + OSAL_LOCK_API(); + + comp = cm_lookupComponent(instance); + // Sanity check + if (NULL == comp) { + error = CM_INVALID_COMPONENT_HANDLE; + } else if(index >= comp->Template->propertyNumber) { + error = CM_NO_SUCH_PROPERTY; + } else { + cm_StringCopy( + propertyName, + comp->Template->properties[index].name, + propertyNameLength); + + error = CM_OK; + } + + OSAL_UNLOCK_API(); + + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetComponentPropertyValue( + const t_cm_instance_handle instance, + const char *propertyName, + char *propertyValue, + t_uint32 propertyValueLength) +{ + t_component_instance *comp; + t_cm_error error; + + OSAL_LOCK_API(); + + comp = cm_lookupComponent(instance); + if (NULL == comp) + error = CM_INVALID_COMPONENT_HANDLE; + else + { + error = cm_getComponentProperty( + comp, + propertyName, + propertyValue, + propertyValueLength); + + if(error == CM_NO_SUCH_PROPERTY) + ERROR("CM_NO_SUCH_PROPERTY(%s, %s)\n", comp->pathname, propertyName, 0, 0, 0, 0); + } + + OSAL_UNLOCK_API(); + + return error; +} diff --git a/drivers/staging/nmf-cm/cm/engine/component/src/dspevent.c b/drivers/staging/nmf-cm/cm/engine/component/src/dspevent.c new file mode 100644 index 00000000000..0d5e89e0515 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/src/dspevent.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/inc/cm_type.h> +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/communication/inc/communication.h> +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/dsp/mmdsp/inc/mmdsp_hwp.h> +#include <cm/engine/trace/inc/trace.h> +#include "../inc/dspevent.h" + + +#define DSP_REMOTE_EVENT_SIZE_IN_BYTE (4*DSP_REMOTE_EVENT_SIZE_IN_DSPWORD) +#define DSP_REMOTE_EVENT_NEXT_FIELD_OFFSET 0 +#define DSP_REMOTE_EVENT_REACTION_FIELD_OFFSET 1 +#define DSP_REMOTE_EVENT_THIS_FIELD_OFFSET 2 +#define DSP_REMOTE_EVENT_PRIORITY_FIELD_OFFSET 3 +#define DSP_REMOTE_EVENT_DATA_FIELD_OFFSET 4 + +t_cm_error dspevent_createDspEventFifo( + const t_component_instance *pComp, + const char* nameOfTOP, + t_uint32 fifoNbElem, + t_uint32 fifoElemSizeInWord, + t_dsp_memory_type_id dspEventMemType, + t_memory_handle *pHandle) +{ + t_uint32 dspElementAddr; + t_uint32 *elemAddr32; + int i; + + // Allocate fifo + *pHandle = cm_DM_Alloc(pComp->domainId, dspEventMemType, fifoNbElem*fifoElemSizeInWord, CM_MM_ALIGN_2WORDS, TRUE); + if(*pHandle == INVALID_MEMORY_HANDLE) { + ERROR("CM_NO_MORE_MEMORY: dspevent_createDspEventFifo()\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + cm_DSP_GetDspAddress(*pHandle, &dspElementAddr); + + elemAddr32 = (t_uint32*)cm_DSP_GetHostLogicalAddress(*pHandle); + + LOG_INTERNAL(2, "\n##### FIFO (dsp event): ARM=0x%x DSP=0x%x\n", elemAddr32, dspElementAddr, 0, 0, 0, 0); + + // Read attribute addr (we assume that variable in XRAM) + cm_writeAttribute(pComp, nameOfTOP, dspElementAddr); + + // Initialise the linked list (next...) + for (i = 0; i < fifoNbElem - 1; i++) + { + dspElementAddr += fifoElemSizeInWord; + + /* Write next field */ + *elemAddr32 = dspElementAddr; + /* Write THIS field & priority field */ + *(volatile t_uint64*)&elemAddr32[DSP_REMOTE_EVENT_THIS_FIELD_OFFSET] = + ((t_uint64)pComp->thisAddress | (((t_uint64)pComp->priority) << 32)); + + elemAddr32 += fifoElemSizeInWord; + } + + /* Last element: Write next field */ + *elemAddr32 = 0x0 /* NULL */; + /* Last element: Write THIS field & priority field */ + *(volatile t_uint64*)&elemAddr32[DSP_REMOTE_EVENT_THIS_FIELD_OFFSET] = + ((t_uint64)pComp->thisAddress | (((t_uint64)pComp->priority) << 32)); + + return CM_OK; +} + + + +void dspevent_destroyDspEventFifo(t_memory_handle handle) +{ + (void)cm_DM_Free(handle, TRUE); +} diff --git a/drivers/staging/nmf-cm/cm/engine/component/src/initializer.c b/drivers/staging/nmf-cm/cm/engine/component/src/initializer.c new file mode 100644 index 00000000000..7f99b710401 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/src/initializer.c @@ -0,0 +1,383 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/inc/cm_type.h> +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> +#include <cm/engine/communication/inc/communication.h> +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/dsp/mmdsp/inc/mmdsp_hwp.h> + +#include <cm/engine/power_mgt/inc/power.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> + +#include <cm/engine/trace/inc/trace.h> + +#include "../inc/dspevent.h" +#include "../inc/initializer.h" + +// Since now due to semaphore use call is synchrone so we only need a fifo size of three +// (due to updateStack + (InstructionCacheLock or InstructionCacheUnlock)) +#define DEFAULT_INITIALIZER_FIFO_SIZE 3 + +/* private prototype */ +PRIVATE t_cm_error cm_COMP_generic(t_nmf_core_id coreId, t_event_params_handle paramArray, t_uint32 paramNumber, t_uint32 serviceIndex); +PRIVATE void cm_COMP_generatePanic(t_nmf_core_id coreId); + +/* + * This module is tightly coupled with cm_DSP_components one (communication/initializer) + */ +static struct { + t_nmf_fifo_arm_desc* downlinkFifo; + t_nmf_fifo_arm_desc* uplinkFifo; + t_memory_handle dspfifoHandle; + t_nmf_osal_sem_handle fifoSemHandle; + t_uint32 servicePending; // TODO : Use sem counter instead of defining such variable (need to create new OSAL) +} initializerDesc[NB_CORE_IDS]; + +PUBLIC t_cm_error cm_COMP_INIT_Init(t_nmf_core_id coreId) +{ + t_uint32 i; + t_cm_error error; + t_component_instance *ee; + t_dsp_offset sharedVarOffset; + t_interface_provide_description itfProvide; + t_interface_provide* provide; + t_interface_provide_loaded* provideLoaded; + + ee = cm_EEM_getExecutiveEngine(coreId)->instance; + + // Get interface description + if((error = cm_getProvidedInterface(ee, "service", &itfProvide)) != CM_OK) + return error; + provide = &ee->Template->provides[itfProvide.provideIndex]; + provideLoaded = &ee->Template->providesLoaded[itfProvide.provideIndex]; + + + if ((error = dspevent_createDspEventFifo( + ee, "comms/TOP", + DEFAULT_INITIALIZER_FIFO_SIZE, + DSP_REMOTE_EVENT_SIZE_IN_DSPWORD, + INTERNAL_XRAM24, + &initializerDesc[coreId].dspfifoHandle)) != CM_OK) + return error; + + /* create fifo semaphore */ + initializerDesc[coreId].servicePending = 0; + initializerDesc[coreId].fifoSemHandle = OSAL_CreateSemaphore(DEFAULT_INITIALIZER_FIFO_SIZE); + if (initializerDesc[coreId].fifoSemHandle == 0) { + dspevent_destroyDspEventFifo(initializerDesc[coreId].dspfifoHandle); + return CM_NO_MORE_MEMORY; + } + + /* static armTHis initialisation */ + /* + * In the two next fifo_alloc call (1+n) means that we want to manage the hostThis_or_TOP and one method for each params fifos */ + initializerDesc[coreId].downlinkFifo = + fifo_alloc(ARM_CORE_ID, coreId, + INIT_COMPONENT_CMD_SIZE, DEFAULT_INITIALIZER_FIFO_SIZE, + (1+provide->interface->methodNumber), paramsLocation, extendedFieldLocation, cm_DSP_GetState(coreId)->domainEE); + if (initializerDesc[coreId].downlinkFifo == NULL) + { + OSAL_DestroySemaphore(initializerDesc[coreId].fifoSemHandle); + dspevent_destroyDspEventFifo(initializerDesc[coreId].dspfifoHandle); + ERROR("CM_NO_MORE_MEMORY: fifo_alloc() failed in cm_COMP_INIT_Init()\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + initializerDesc[coreId].uplinkFifo = + fifo_alloc(coreId, ARM_CORE_ID, + INIT_COMPONENT_ACK_SIZE, DEFAULT_INITIALIZER_FIFO_SIZE, + (1), paramsLocation, extendedFieldLocation, cm_DSP_GetState(coreId)->domainEE); /* 1 is mandatory to compute internally the indexMask */ + /* this statement is acceptable only written by skilled man ;) */ + /* We don't used bcDescRef, since we assume that we don't need params size */ + if (initializerDesc[coreId].uplinkFifo == NULL) + { + OSAL_DestroySemaphore(initializerDesc[coreId].fifoSemHandle); + fifo_free(initializerDesc[coreId].downlinkFifo); + dspevent_destroyDspEventFifo(initializerDesc[coreId].dspfifoHandle); + ERROR("CM_NO_MORE_MEMORY: fifo_alloc() failed in cm_COMP_INIT_Init()\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + cm_writeAttribute(ee, "comms/FIFOcmd", initializerDesc[coreId].downlinkFifo->dspAdress); + + cm_writeAttribute(ee, "comms/FIFOack", initializerDesc[coreId].uplinkFifo->dspAdress); + + sharedVarOffset = cm_getAttributeMpcAddress(ee, "comms/TOP"); + + /* HOST->DSP ParamsFifo extended fields initialisation */ + fifo_params_setSharedField( + initializerDesc[coreId].downlinkFifo, + 0, + (t_shared_field)sharedVarOffset /* TOP DSP Address */ + ); + for(i=0; i<provide->interface->methodNumber; i++) + { + fifo_params_setSharedField( + initializerDesc[coreId].downlinkFifo, + i + 1, + provideLoaded->indexesLoaded[itfProvide.collectionIndex][i].methodAddresses); + } + + /* DSP->HOST ParamsFifo extended fields initialisation */ + fifo_params_setSharedField( + initializerDesc[coreId].uplinkFifo, + 0, + (t_shared_field)NMF_INTERNAL_USERTHIS + ); + + return CM_OK; +} + + +PUBLIC t_cm_error cm_COMP_CallService( + int serviceIndex, + t_component_instance *pComp, + t_uint32 methodAddress) { + t_cm_error error; + t_uint16 params[INIT_COMPONENT_CMD_SIZE]; + t_bool isSynchronous = (serviceIndex == NMF_CONSTRUCT_SYNC_INDEX || + serviceIndex == NMF_START_SYNC_INDEX || + serviceIndex == NMF_STOP_SYNC_INDEX || + serviceIndex == NMF_DESTROY_INDEX)?TRUE:FALSE; + + params[INIT_COMPONENT_CMD_HANDLE_INDEX] = (t_uint16)((unsigned int)pComp & 0xFFFF); + params[INIT_COMPONENT_CMD_HANDLE_INDEX+1] = (t_uint16)((unsigned int)pComp >> 16); + params[INIT_COMPONENT_CMD_THIS_INDEX] = (t_uint16)(pComp->thisAddress & 0xFFFF); + params[INIT_COMPONENT_CMD_THIS_INDEX+1] = (t_uint16)(pComp->thisAddress >> 16); + params[INIT_COMPONENT_CMD_METHOD_INDEX] = (t_uint16)(methodAddress & 0xFFFF); + params[INIT_COMPONENT_CMD_METHOD_INDEX+1] = (t_uint16)(methodAddress >> 16); + + error = cm_COMP_generic(pComp->Template->dspId, params, sizeof(params) / sizeof(t_uint16), serviceIndex); + + if (isSynchronous == TRUE && error == CM_OK) { + if (OSAL_SEMAPHORE_WAIT_TIMEOUT(semHandle) != SYNC_OK) { + cm_COMP_generatePanic(pComp->Template->dspId); + error = CM_MPC_NOT_RESPONDING; + } + } + + return error; +} + +PUBLIC void cm_COMP_Flush(t_nmf_core_id coreId) { + + if(initializerDesc[coreId].servicePending > 0) + { + t_uint16 params[INIT_COMPONENT_CMD_SIZE]; + t_uint32 methodAddress = cm_EEM_getExecutiveEngine(coreId)->voidAddr; + + // If service still pending on MMDSP side, send a flush command (today, we reuse Destroy to not create new empty service) + // When we receive the result, this mean that we have flushed all previous request. + + params[INIT_COMPONENT_CMD_HANDLE_INDEX] = (t_uint16)(0x0 & 0xFFFF); + params[INIT_COMPONENT_CMD_HANDLE_INDEX+1] = (t_uint16)(0x0 >> 16); + params[INIT_COMPONENT_CMD_THIS_INDEX] = (t_uint16)(0x0 & 0xFFFF); + params[INIT_COMPONENT_CMD_THIS_INDEX+1] = (t_uint16)(0x0 >> 16); + params[INIT_COMPONENT_CMD_METHOD_INDEX] = (t_uint16)(methodAddress & 0xFFFF); + params[INIT_COMPONENT_CMD_METHOD_INDEX+1] = (t_uint16)(methodAddress >> 16); + + if (cm_COMP_generic(coreId, params, sizeof(params) / sizeof(t_uint16), NMF_DESTROY_INDEX) != CM_OK || + OSAL_SEMAPHORE_WAIT_TIMEOUT(semHandle) != SYNC_OK) + { + cm_COMP_generatePanic(coreId); + ERROR("CM_MPC_NOT_RESPONDING: can't call flush service\n", 0, 0, 0, 0, 0, 0); + } + } +} + +PUBLIC void cm_COMP_INIT_Close(t_nmf_core_id coreId) +{ + unsigned int i; + + /* wait for semaphore to be sure it would not be touch later on */ + /* in case of timeout we break and try to clean everythink */ + for(i = 0; i < DEFAULT_INITIALIZER_FIFO_SIZE; i++) { + if (OSAL_SEMAPHORE_WAIT_TIMEOUT(initializerDesc[coreId].fifoSemHandle) != SYNC_OK) + break; + } + + /* destroy semaphore */ + OSAL_DestroySemaphore(initializerDesc[coreId].fifoSemHandle); + + /* Unallocate initializerDesc[index].uplinkFifo */ + /* (who is used in this particular case to store dummy (with no data space (only descriptor)) DSP->HOST params fifo */ + fifo_free(initializerDesc[coreId].uplinkFifo); + + /* Unallocate initializerDesc[index].downlinkFifo */ + fifo_free(initializerDesc[coreId].downlinkFifo); + + /* Unallocate initializerDesc[index].dspfifoHandle */ + dspevent_destroyDspEventFifo(initializerDesc[coreId].dspfifoHandle); +} + +PUBLIC void processAsyncAcknowledge(t_nmf_core_id coreId, t_event_params_handle pParam) +{ + cm_AcknowledgeEvent(initializerDesc[coreId].uplinkFifo); + + initializerDesc[coreId].servicePending--; + OSAL_SemaphorePost(initializerDesc[coreId].fifoSemHandle,1); +} + +PUBLIC void processSyncAcknowledge(t_nmf_core_id coreId, t_event_params_handle pParam) +{ + cm_AcknowledgeEvent(initializerDesc[coreId].uplinkFifo); + + initializerDesc[coreId].servicePending--; + OSAL_SemaphorePost(initializerDesc[coreId].fifoSemHandle,1); + OSAL_SemaphorePost(semHandle,1); +} + +PUBLIC t_cm_error cm_COMP_UpdateStack( + t_nmf_core_id coreId, + t_uint32 stackSize +) +{ + t_uint16 params[2]; + + // Marshall parameter + params[0] = (t_uint16)((unsigned int)stackSize & 0xFFFF); + params[1] = (t_uint16)((unsigned int)stackSize >> 16); + + return cm_COMP_generic(coreId, params, sizeof(params) / sizeof(t_uint16), NMF_UPDATE_STACK); +} + +PUBLIC t_cm_error cm_COMP_ULPForceWakeup( + t_nmf_core_id coreId +) +{ + t_cm_error error; + + error = cm_COMP_generic(coreId, NULL, 0, NMF_ULP_FORCEWAKEUP); + + if (error == CM_OK) { + if (OSAL_SEMAPHORE_WAIT_TIMEOUT(semHandle) != SYNC_OK) { + cm_COMP_generatePanic(coreId); + error = CM_MPC_NOT_RESPONDING; + } + } + + return error; +} + +PUBLIC t_cm_error cm_COMP_ULPAllowSleep( + t_nmf_core_id coreId +) +{ + return cm_COMP_generic(coreId, NULL, 0, NMF_ULP_ALLOWSLEEP); +} + +PUBLIC t_cm_error cm_COMP_InstructionCacheLock( + t_nmf_core_id coreId, + t_uint32 mmdspAddr, + t_uint32 mmdspSize +) +{ + t_uint16 params[4]; + t_uint32 startAddr = cm_DSP_GetState(coreId)->locked_offset; + int way; + + for(way = 1; startAddr < mmdspAddr + mmdspSize; startAddr += MMDSP_CODE_CACHE_WAY_SIZE, way++) + { + if(mmdspAddr < startAddr + MMDSP_CODE_CACHE_WAY_SIZE) + { + t_cm_error error; + + // Marshall parameter + params[0] = (t_uint16)((unsigned int)startAddr & 0xFFFF); + params[1] = (t_uint16)((unsigned int)startAddr >> 16); + params[2] = (t_uint16)((unsigned int)way & 0xFFFF); + params[3] = (t_uint16)((unsigned int)way >> 16); + + if((error = cm_COMP_generic(coreId, params, sizeof(params) / sizeof(t_uint16), NMF_LOCK_CACHE)) != CM_OK) + return error; + } + } + + return CM_OK; +} + +PUBLIC t_cm_error cm_COMP_InstructionCacheUnlock( + t_nmf_core_id coreId, + t_uint32 mmdspAddr, + t_uint32 mmdspSize +) +{ + t_uint16 params[2]; + t_uint32 startAddr = cm_DSP_GetState(coreId)->locked_offset; + int way; + + for(way = 1; startAddr < mmdspAddr + mmdspSize; startAddr += MMDSP_CODE_CACHE_WAY_SIZE, way++) + { + if(mmdspAddr < startAddr + MMDSP_CODE_CACHE_WAY_SIZE) + { + t_cm_error error; + + // Marshall parameter + params[0] = (t_uint16)((unsigned int)way & 0xFFFF); + params[1] = (t_uint16)((unsigned int)way >> 16); + + if((error = cm_COMP_generic(coreId, params, sizeof(params) / sizeof(t_uint16), NMF_UNLOCK_CACHE)) != CM_OK) + return error; + } + } + + return CM_OK; +} + +/* private method */ +PRIVATE t_cm_error cm_COMP_generic( + t_nmf_core_id coreId, + t_event_params_handle paramArray, + t_uint32 paramNumber, + t_uint32 serviceIndex +) +{ + t_event_params_handle _xyuv_data; + t_cm_error error; + t_uint32 i; + + // wait for an event in fifo + if (OSAL_SEMAPHORE_WAIT_TIMEOUT(initializerDesc[coreId].fifoSemHandle) != SYNC_OK) { + cm_COMP_generatePanic(coreId); + return CM_MPC_NOT_RESPONDING; + } + + + // AllocEvent + if((_xyuv_data = cm_AllocEvent(initializerDesc[coreId].downlinkFifo)) == NULL) + { + ERROR("CM_INTERNAL_FIFO_OVERFLOW: service FIFO full\n", 0, 0, 0, 0, 0, 0); + error = CM_INTERNAL_FIFO_OVERFLOW; + goto unlock; + } + + // Copy param + for(i=0;i<paramNumber;i++) + _xyuv_data[i] = paramArray[i]; + + OSAL_LOCK_COM(); + + // Send Command + error = cm_PushEventTrace(initializerDesc[coreId].downlinkFifo, _xyuv_data, serviceIndex,0); + if(error == CM_OK) + initializerDesc[coreId].servicePending++; + +unlock: + OSAL_UNLOCK_COM(); + + return error; +} + +PRIVATE void cm_COMP_generatePanic(t_nmf_core_id coreId) +{ + const t_dsp_desc* pDspDesc = cm_DSP_GetState(coreId); + + if (pDspDesc->state != MPC_STATE_PANIC) { + cm_DSP_SetStatePanic(coreId); + OSAL_GeneratePanic(coreId, 0); + } +} diff --git a/drivers/staging/nmf-cm/cm/engine/component/src/instantiater.c b/drivers/staging/nmf-cm/cm/engine/component/src/instantiater.c new file mode 100644 index 00000000000..92c28b63171 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/src/instantiater.c @@ -0,0 +1,829 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/component/inc/instance.h> +#include <cm/engine/component/inc/bind.h> +#include <cm/engine/component/inc/initializer.h> + +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> +#include <cm/engine/configuration/inc/configuration_status.h> + +#include <cm/engine/dsp/inc/dsp.h> + +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/trace/inc/xtitrace.h> + +#include <cm/engine/memory/inc/domain.h> + +#include <cm/engine/utils/inc/string.h> +#include <cm/engine/utils/inc/mem.h> +#include <cm/engine/utils/inc/convert.h> + +#include <cm/engine/power_mgt/inc/power.h> + + +t_nmf_table ComponentTable; /**< list (table) of components */ + +static t_uint32 cm_getMaxStackValue(t_component_instance *pComponent); +static t_uint16 getNumberOfInstance(t_component_instance* component); +static t_uint16 getNumberOfStart(t_component_instance* component); + + +t_cm_error cm_COMP_Init(void) { + t_cm_error error; + error = cm_initTable(&ComponentTable); + if (error == CM_OK) + error = cm_initTable(&Host2MpcBindingTable); + return error; +} + +void cm_COMP_Destroy(void) { + cm_destroyTable(&ComponentTable); + cm_destroyTable(&Host2MpcBindingTable); +} + +/** cm_addComponent - Add an internal handler to the list + * + * 1. Increase the size of the list if it's full + * 2. Search an empty entry + * 3. Add the element to the list + * 4. Compute and return the "user handle" (= t_cm_instance_handle) + */ +static t_cm_instance_handle cm_addComponent(t_component_instance *comp) +{ + OSAL_DisableServiceMessages(); + comp->instance = cm_addEntry(&ComponentTable, comp); + OSAL_EnableServiceMessages(); + + return comp->instance; +} + +/** cm_delComponent - remove the given component from the list + * + * 1. Check if the handle is valid + * 2. Search the entry and free it + */ +static void cm_delComponent(t_component_instance *comp) +{ + if (comp == NULL) + return; + + OSAL_DisableServiceMessages(); + cm_delEntry(&ComponentTable, comp->instance & INDEX_MASK); + OSAL_EnableServiceMessages(); +} + +/** cm_lookupComponent - search the component corresponding to + * the component instance. + * + * 1. Check if the instance is valid + * 2. Return a pointer to the component + */ +t_component_instance *cm_lookupComponent(const t_cm_instance_handle hdl) +{ + return cm_lookupEntry(&ComponentTable, hdl); +} + +static void cm_DestroyComponentMemory(t_component_instance *component) +{ + int i; + + /* + * Remove instance from list + */ + cm_delComponent(component); + + /* + * Destroy instance + */ + { + struct t_client_of_singleton* cur = component->clientOfSingleton; + + for( ; cur != NULL ; ) + { + struct t_client_of_singleton* tmp = cur; + cur = cur->next; + + OSAL_Free(tmp); + } + } + + for(i = 0; i < component->Template->requireNumber; i++) + { + OSAL_Free(component->interfaceReferences[i]); + } + + cm_StringRelease(component->pathname); + + cm_ELF_FreeInstance(component->Template->dspId, component->Template->memories, component->memories); + + cm_unloadComponent(component->Template); + OSAL_Free(component); +} + +/** + * Non-Require: + * - MMDSP could be sleep (Since we access it only through HSEM) + */ +void cm_delayedDestroyComponent(t_component_instance *component) { + int i; + + if (osal_debug_ops.component_destroy) + osal_debug_ops.component_destroy(component); + + /* + * Remove component from load map here + */ + cm_DSPABI_RemoveLoadMap( + component->domainId, + component->Template->name, + component->memories, + component->pathname, + component); + + // Generate XTI/STM trace + cm_TRC_traceLoadMap(TRACE_COMPONENT_COMMAND_REMOVE, component); + + /* + * disconnect interrupt handler if needed + */ + for(i = 0; i < component->Template->provideNumber; i++) + { + if(component->Template->provides[i].interruptLine) + { + cm_unbindInterfaceStaticInterrupt(component->Template->dspId, component->Template->provides[i].interruptLine); + } + } + + /* + * Update dsp stack size if needed + */ + if (component->Template->minStackSize > MIN_STACK_SIZE) + { + if (cm_EEM_isStackUpdateNeed(component->Template->dspId, component->priority, FALSE, component->Template->minStackSize)) + { + t_uint32 newStackValue; + t_uint32 maxComponentStackSize; + + maxComponentStackSize = cm_getMaxStackValue(component); + cm_EEM_UpdateStack(component->Template->dspId, component->priority, maxComponentStackSize, &newStackValue); + if (cm_DSP_GetState(component->Template->dspId)->state == MPC_STATE_BOOTED) + cm_COMP_UpdateStack(component->Template->dspId, newStackValue); + } + } + + cm_DestroyComponentMemory(component); +} + +/** + * Pre-Require: + * - MMDSP wakeup (when loading in TCM) + */ +t_cm_error cm_instantiateComponent(const char* templateName, + t_cm_domain_id domainId, + t_nmf_ee_priority priority, + const char* pathName, + t_elfdescription *elfhandle, + t_component_instance** refcomponent) +{ + t_nmf_core_id coreId = cm_DM_GetDomainCoreId(domainId); + t_dup_char templateNameDup; + t_component_template* template; + t_component_instance *component; + /* coverity[var_decl] */ + t_cm_error error; + int i, j, k; + + *refcomponent = NULL; + + templateNameDup = cm_StringDuplicate(templateName); + if(templateNameDup == NULL) + return CM_NO_MORE_MEMORY; + + /* + * Lookup in template list + */ + template = cm_lookupTemplate(coreId, templateNameDup); + if(template != NULL) + { + if(template->classe == SINGLETON) + { + // Return same handle for singleton component + struct t_client_of_singleton* cl; + + cm_StringRelease(templateNameDup); + + cl = cm_getClientOfSingleton(template->singletonIfAvaliable, TRUE, domainDesc[domainId].client); + if(cl == NULL) + return CM_NO_MORE_MEMORY; + cl->numberOfInstance++; + + *refcomponent = template->singletonIfAvaliable; + LOG_INTERNAL(1, "##### Singleton : New handle of %s/%x component on %s provItf=%d#####\n", + template->singletonIfAvaliable->pathname, template->singletonIfAvaliable, cm_getDspName(coreId), + template->singletonIfAvaliable->providedItfUsedCount, 0, 0); + return CM_OK; + } + } + + // Get the dataFile (identity if already pass as parameter) + if((elfhandle = cm_REP_getComponentFile(templateNameDup, elfhandle)) == NULL) + { + cm_StringRelease(templateNameDup); + return CM_COMPONENT_NOT_FOUND; + } + + // Load template + if((error = cm_loadComponent(templateNameDup, domainId, elfhandle, &template)) != CM_OK) + { + cm_StringRelease(templateNameDup); + return error; + } + + // templateNameDup no more used, release it + cm_StringRelease(templateNameDup); + + // Allocated component + component = (t_component_instance*)OSAL_Alloc_Zero( + sizeof(t_component_instance) + + sizeof(t_interface_reference*) * template->requireNumber); + if(component == NULL) + { + cm_unloadComponent(template); + return CM_NO_MORE_MEMORY; + } + + component->interfaceReferences = (t_interface_reference**)((char*)component + sizeof(t_component_instance)); + component->Template = template; + + /* + * Update linked list + */ + if (cm_addComponent(component) == 0) { + cm_unloadComponent(template); + OSAL_Free(component); + return CM_NO_MORE_MEMORY; + } + + // NOTE: From here use cm_DestroyComponentMemory + + component->pathname = pathName ? cm_StringDuplicate(pathName) : cm_StringReference(anonymousDup); + if(component->pathname == NULL) + { + cm_DestroyComponentMemory(component); + return CM_NO_MORE_MEMORY; + } + + LOG_INTERNAL(1, "\n##### Instantiate %s/%x (%s) component on %s at priority %d #####\n", component->pathname, component, template->name, cm_getDspName(coreId), priority, 0); + + if((error = cm_ELF_LoadInstance(domainId, elfhandle, template->memories, component->memories, template->classe == SINGLETON)) != CM_OK) + { + cm_DestroyComponentMemory(component); + return error; + } + + if((error = cm_ELF_relocatePrivateSegments( + component->memories, + elfhandle, + template)) != CM_OK) + { + cm_DestroyComponentMemory(component); + return error; + } + + cm_ELF_FlushInstance(coreId, template->memories, component->memories); + + /* + * Create a new component instance + */ + component->priority = priority; + component->thisAddress = 0xFFFFFFFF; + component->state = STATE_NONE; + + if(component->Template->classe == SINGLETON) + { // Return same handle for singleton component + struct t_client_of_singleton* cl = cm_getClientOfSingleton(component, TRUE, domainDesc[domainId].client); + if(cl == NULL) + { + cm_DestroyComponentMemory(component); + return CM_NO_MORE_MEMORY; + } + + cl->numberOfInstance = 1; + template->singletonIfAvaliable = component; + if (cm_DM_GetDomainCoreId(domainId) == SVA_CORE_ID) + component->domainId = DEFAULT_SVA_DOMAIN; + else + component->domainId = DEFAULT_SIA_DOMAIN; + } else { + component->domainId = domainId; + } + + if(component->memories[template->thisMemory->id] != INVALID_MEMORY_HANDLE) + cm_DSP_GetDspAddress(component->memories[template->thisMemory->id], &component->thisAddress); + else { + // In case of singleton or component without data + component->thisAddress = 0; + } + + /* + * Create empty required interfaces array and set method interface to Panic + */ + for(i = 0; i < template->requireNumber; i++) // For all required interface + { + component->interfaceReferences[i] = + (t_interface_reference*)OSAL_Alloc_Zero(sizeof(t_interface_reference) * template->requires[i].collectionSize); + if(component->interfaceReferences[i] == NULL) + { + cm_DestroyComponentMemory(component); + return CM_NO_MORE_MEMORY; + } + + for(j = 0; j < template->requires[i].collectionSize; j++) // ... and for each index in collection (set THIS&method for each client) + { + component->interfaceReferences[i][j].instance = NULL; + component->interfaceReferences[i][j].bfInfoID = BF_SYNCHRONOUS; // Just to memorize no Binding component used and unbind ToVoid happy ;-). + + if(template->classe == COMPONENT && template->requires[i].indexes != NULL) + { + // If component, fill THIS to itself to detect UNBINDED panic with rigth DSP + t_interface_require_index *requireindex = &template->requires[i].indexes[j]; + for(k = 0; k < requireindex->numberOfClient; k++) + { + t_uint32 *hostAddr; + + hostAddr = (t_uint32*)( + cm_DSP_GetHostLogicalAddress( + component->memories[requireindex->memories[k].memory->id]) + + requireindex->memories[k].offset * requireindex->memories[k].memory->memEntSize); + *hostAddr++ = (t_uint32)component->thisAddress; + } + } + } + } + + /* + * Inform debugger about new component + */ + if ((error = cm_DSPABI_AddLoadMap( + domainId, + template->name, + component->pathname, + component->memories, + component)) != CM_OK) + { + cm_DestroyComponentMemory(component); + return error; + } + + // Generate XTI/STM trace + cm_TRC_traceLoadMap(TRACE_COMPONENT_COMMAND_ADD, component); + + // NOTE: From here use cm_delayedDestroyComponent + + /* + * Relocate interrupt if this is an interrupt + */ + for(i = 0; i < template->provideNumber; i++) + { + if(template->provides[i].interruptLine) + { + if ((error = cm_bindInterfaceStaticInterrupt(coreId, + template->provides[i].interruptLine, + component, + template->provides[i].name)) != CM_OK) + { + cm_delayedDestroyComponent(component); + return error; + } + } + } + + /* + * For first instance of a component; Update ee stack size if needed + */ + if(template->classe != FIRMWARE && template->numberOfInstance == 1 && template->minStackSize > MIN_STACK_SIZE) + { + t_uint32 newStackValue; + + if (cm_EEM_isStackUpdateNeed(template->dspId, priority, TRUE, template->minStackSize)) + { + error = cm_EEM_UpdateStack(template->dspId, priority, template->minStackSize, &newStackValue); + if (error != CM_OK) + { + cm_delayedDestroyComponent(component); + return error; + } + cm_COMP_UpdateStack(template->dspId, newStackValue); + } + } + + + /* + * For component or first instance + */ + if(template->classe == SINGLETON || template->classe == COMPONENT) + { + /* + * Call init function generated by the compiler (one per .elf) + */ + LOG_INTERNAL(2, "constructor call(s) <%s>\n", template->name, 0, 0, 0, 0, 0); + if (cm_DSP_GetState(template->dspId)->state != MPC_STATE_BOOTED) + { + cm_delayedDestroyComponent(component); + return CM_MPC_NOT_RESPONDING; + } + else if ((error = cm_COMP_CallService( + (priority > cm_EEM_getExecutiveEngine(coreId)->instance->priority)?NMF_CONSTRUCT_SYNC_INDEX:NMF_CONSTRUCT_INDEX, + component, + template->LCCConstructAddress)) != CM_OK) + { + if (error == CM_MPC_NOT_RESPONDING) + ERROR("CM_MPC_NOT_RESPONDING: can't call constructor '%s'\n", component->pathname, 0, 0, 0, 0, 0); + cm_delayedDestroyComponent(component); + return error; + } + } + else + { + /* be sure everything is write into memory, not required elsewhere since will be done by cm_COMP_CallService */ + OSAL_mb(); + } + + // For firmware; Directly switch to STARTED state, don't need to start it + if (template->classe == FIRMWARE) + component->state = STATE_RUNNABLE; + else + component->state = STATE_STOPPED; + + if (osal_debug_ops.component_create) + osal_debug_ops.component_create(component); + + *refcomponent = component; + return CM_OK; +} + +struct t_client_of_singleton* cm_getClientOfSingleton(t_component_instance* component, t_bool createdIfNotExist, t_nmf_client_id clientId) +{ + struct t_client_of_singleton* cur = component->clientOfSingleton; + + for( ; cur != NULL ; cur = cur->next) + { + if(cur->clientId == clientId) + { + return cur; + } + } + + //if(createdIfNotExist) + { + cur = OSAL_Alloc(sizeof(struct t_client_of_singleton)); + if(cur != NULL) + { + cur->clientId = clientId; + cur->next = component->clientOfSingleton; + cur->numberOfBind = 0; + cur->numberOfInstance= 0; + cur->numberOfStart = 0; + component->clientOfSingleton = cur; + } + } + return cur; +} + +/** + * Non-Require: + * - MMDSP could be sleep (Since we access it only through HSEM) + */ +t_cm_error cm_startComponent(t_component_instance* component, t_nmf_client_id clientId) +{ + t_cm_error error; + char value[MAX_PROPERTY_VALUE_LENGTH]; + int i; + + /* + * Special code for SINGLETON handling + */ + if(component->Template->classe == SINGLETON) + { + struct t_client_of_singleton* cl = cm_getClientOfSingleton(component, FALSE, clientId); + if(cl != NULL) + cl->numberOfStart++; + // A singleton could be started twice, thus start it only if first client starter + if(getNumberOfStart(component) > 1) + return CM_OK; + + // Fall through and start really the singleton. + } + + if(component->state == STATE_RUNNABLE) + return CM_COMPONENT_NOT_STOPPED; + + // CM_ASSERT component->state == STATE_STOPPED + + /* + * Check that all required binding have been binded! + */ + for(i = 0; i < component->Template->requireNumber; i++) + { + int nb = component->Template->requires[i].collectionSize, j; + for(j = 0; j < nb; j++) + { + if(component->interfaceReferences[i][j].instance == NULL && + (component->Template->requires[i].requireTypes & (OPTIONAL_REQUIRE | INTRINSEC_REQUIRE)) == 0) + { + ERROR("CM_REQUIRE_INTERFACE_UNBINDED: Required interface '%s'.'%s' binded\n", component->pathname, component->Template->requires[i].name, 0, 0, 0, 0); + return CM_REQUIRE_INTERFACE_UNBINDED; + } + } + } + + component->state = STATE_RUNNABLE; + + /* + * Power on, HW resources if required + */ + if(cm_getComponentProperty( + component, + "hardware", + value, + sizeof(value)) == CM_OK) + { + error = cm_PWR_EnableMPC(MPC_PWR_HWIP, component->Template->dspId); + if(error != CM_OK) + return error; + } + + /* + * Call starter if available + */ + if(component->Template->LCCStartAddress != 0) + { + if (cm_DSP_GetState(component->Template->dspId)->state != MPC_STATE_BOOTED) + { + return CM_MPC_NOT_RESPONDING; + } + else if ((error = cm_COMP_CallService( + (component->priority > cm_EEM_getExecutiveEngine(component->Template->dspId)->instance->priority)?NMF_START_SYNC_INDEX:NMF_START_INDEX, + component, + component->Template->LCCStartAddress)) != CM_OK) + { + if (error == CM_MPC_NOT_RESPONDING) + ERROR("CM_MPC_NOT_RESPONDING: can't call starter '%s'\n", component->pathname, 0, 0, 0, 0, 0); + return error; + } + } + + return CM_OK; +} + +/** + * Non-Require: + * - MMDSP could be sleep (Since we access it only through HSEM) + */ +t_cm_error cm_stopComponent(t_component_instance* component, t_nmf_client_id clientId) +{ + char value[MAX_PROPERTY_VALUE_LENGTH]; + t_cm_error error = CM_OK; + t_bool isHwProperty; + + /* + * Special code for SINGLETON handling + */ + if(component->Template->classe == SINGLETON) + { + struct t_client_of_singleton* cl = cm_getClientOfSingleton(component, FALSE, clientId); + if(cl != NULL) + cl->numberOfStart--; + // A singleton could be started twice, thus stop it only if no more client starter + if(getNumberOfStart(component) > 0) + return CM_OK; + + // Fall through and stop really the singleton. + } + + /* + * Component life cycle sanity check + */ + if(component->state == STATE_STOPPED) + return CM_COMPONENT_NOT_STARTED; + + // CM_ASSERT component->state == STATE_RUNNABLE + component->state = STATE_STOPPED; + + isHwProperty = (cm_getComponentProperty( + component, + "hardware", + value, + sizeof(value)) == CM_OK); + + if (cm_DSP_GetState(component->Template->dspId)->state != MPC_STATE_BOOTED) + { + error = CM_MPC_NOT_RESPONDING; + } + else + { + /* + * Call stopper if available + */ + if(component->Template->LCCStopAddress != 0) + { + if ((error = cm_COMP_CallService( + isHwProperty ? NMF_STOP_SYNC_INDEX : NMF_STOP_INDEX, + component, + component->Template->LCCStopAddress)) != CM_OK) + { + if (error == CM_MPC_NOT_RESPONDING) + ERROR("CM_MPC_NOT_RESPONDING: can't call stopper '%s'\n", component->pathname, 0, 0, 0, 0, 0); + } + } + } + + /* + * Power on, HW resources if required + */ + if(isHwProperty) + { + cm_PWR_DisableMPC(MPC_PWR_HWIP, component->Template->dspId); + } + + return error; +} + +t_cm_error cm_destroyInstance(t_component_instance* component, t_destroy_state forceDestroy) +{ + int i, j; + + LOG_INTERNAL(1, "\n##### Destroy %s/%x (%s) component on %s #####\n", + component->pathname, component, component->Template->name, cm_getDspName(component->Template->dspId), 0, 0); + + /* + * Component life cycle sanity check; do it only when destroying last reference. + */ + if(forceDestroy == DESTROY_NORMAL) + { + if (component->state == STATE_RUNNABLE) + return CM_COMPONENT_NOT_STOPPED; + + // CM_ASSERT component->state == STATE_STOPPED + + // Check that all required binding have been unbound! + for(i = 0; i < component->Template->requireNumber; i++) + { + int nb = component->Template->requires[i].collectionSize; + for(j = 0; j < nb; j++) + { + if(component->interfaceReferences[i][j].instance != NULL) + { + ERROR("CM_COMPONENT_NOT_UNBINDED: Required interface %s/%x.%s still binded\n", + component->pathname, component, component->Template->requires[i].name, 0, 0, 0); + return CM_COMPONENT_NOT_UNBINDED; + } + } + } + + // Check that all provided bindings have been unbound! + if (component->providedItfUsedCount != 0) + { + unsigned idx; + + ERROR("CM_COMPONENT_NOT_UNBINDED: Still %d binding to %s/%x provided interface\n", + component->providedItfUsedCount, component->pathname, component, 0, 0, 0); + + /* Find which interface is still bound to gracefully print an error message */ + for (idx=0; idx<ComponentTable.idxMax; idx++) + { + if ((componentEntry(idx) == NULL) || (componentEntry(idx) == component)) + continue; + for (i = 0; i < componentEntry(idx)->Template->requireNumber; i++) + { + for (j = 0; j < componentEntry(idx)->Template->requires[i].collectionSize; j++) + { + if(componentEntry(idx)->interfaceReferences[i][j].instance == component + && component->Template->provides[componentEntry(idx)->interfaceReferences[i][j].provideIndex].interruptLine == 0) + { + ERROR(" -> %s/%x.%s still used by %s/%x.%s\n", + component->pathname, component, + component->Template->provides[componentEntry(idx)->interfaceReferences[i][j].provideIndex].name, + componentEntry(idx)->pathname, + componentEntry(idx), + componentEntry(idx)->Template->requires[i].name); + } + } + } + } + + return CM_COMPONENT_NOT_UNBINDED; + } + } + + // Sanity check finished, here, we will do the JOB whatever error + + if (cm_DSP_GetState(component->Template->dspId)->state == MPC_STATE_BOOTED) + { + /* + * Call destroy if available + */ + /* Call the destructor only if we don't want to force the destruction */ + if(forceDestroy != DESTROY_WITHOUT_CHECK_CALL && component->Template->LCCDestroyAddress != 0) + { + if (cm_COMP_CallService( + NMF_DESTROY_INDEX, + component, + component->Template->LCCDestroyAddress) != CM_OK) + { + ERROR("CM_MPC_NOT_RESPONDING: can't call destroy '%s'\n", component->pathname, 0, 0, 0, 0, 0); + } + } + else + { + cm_COMP_Flush(component->Template->dspId); + } + } + + cm_delayedDestroyComponent(component); + + return CM_OK; +} + +/** + * Pre-Require: + * - MMDSP wakeup (when accessing loadmap) + */ +t_cm_error cm_destroyInstanceForClient(t_component_instance* component, t_destroy_state forceDestroy, t_nmf_client_id clientId) +{ + /* + * Special code for SINGLETON handling + */ + if(component->Template->classe == SINGLETON) + { + struct t_client_of_singleton* cl = cm_getClientOfSingleton(component, FALSE, clientId); + int nbinstance; + if(cl != NULL) + cl->numberOfInstance--; + + // A singleton could be instantiate twice, thus destroy it only if no more client constructor + nbinstance = getNumberOfInstance(component); + if(nbinstance > 0) + { + LOG_INTERNAL(1, "##### Singleton : Delete handle of %s/%x (%s) component on %s [%d] provItf=%d #####\n", + component->pathname, component, component->Template->name, cm_getDspName(component->Template->dspId), + nbinstance, component->providedItfUsedCount); + return CM_OK; + } + + // Fall through + } + + return cm_destroyInstance(component, forceDestroy); +} + + +static t_uint32 cm_getMaxStackValue(t_component_instance *pComponent) +{ + t_nmf_executive_engine_id executiveEngineId = cm_EEM_getExecutiveEngine(pComponent->Template->dspId)->executiveEngineId; + t_uint32 res = MIN_STACK_SIZE; + unsigned int i; + + for (i=0; i<ComponentTable.idxMax; i++) + { + if ((componentEntry(i) != NULL) && + (componentEntry(i) != pComponent) && + (pComponent->Template->dspId == componentEntry(i)->Template->dspId) && + (executiveEngineId == SYNCHRONOUS_EXECUTIVE_ENGINE || componentEntry(i)->priority == pComponent->priority)) + { + if (componentEntry(i)->Template->minStackSize > res) + res = componentEntry(i)->Template->minStackSize; + } + } + + return res; +} + +static t_uint16 getNumberOfInstance(t_component_instance* component) +{ + t_uint16 instanceNumber = 0; + struct t_client_of_singleton* cur = component->clientOfSingleton; + + for( ; cur != NULL ; cur = cur->next) + { + instanceNumber += cur->numberOfInstance; + } + + return instanceNumber; +} + +static t_uint16 getNumberOfStart(t_component_instance* component) +{ + t_uint16 startNumber = 0; + struct t_client_of_singleton* cur = component->clientOfSingleton; + + for( ; cur != NULL ; cur = cur->next) + { + startNumber += cur->numberOfStart; + } + + return startNumber; +} diff --git a/drivers/staging/nmf-cm/cm/engine/component/src/introspection.c b/drivers/staging/nmf-cm/cm/engine/component/src/introspection.c new file mode 100644 index 00000000000..4aaf8dff889 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/src/introspection.c @@ -0,0 +1,327 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/utils/inc/string.h> + +/* + * + */ +t_cm_error cm_getComponentProperty( + const t_component_instance *component, + const char *propName, + char value[MAX_PROPERTY_VALUE_LENGTH], + t_uint32 valueLength){ + t_component_template* template = component->Template; + int i; + + for(i = 0; i < template->propertyNumber; i++) { + if(cm_StringCompare(template->properties[i].name, propName, MAX_PROPERTY_NAME_LENGTH) == 0) { + cm_StringCopy( + value, + template->properties[i].value, + valueLength); + return CM_OK; + } + } + + return CM_NO_SUCH_PROPERTY; +} + +/** + * + */ +static t_attribute* cm_getAttributeDescriptor( + const t_component_instance *component, + const char *attrName) +{ + int i; + + for(i = 0; i < component->Template->attributeNumber; i++) + { + if(cm_StringCompare(component->Template->attributes[i].name, attrName, MAX_ATTRIBUTE_NAME_LENGTH) == 0) + { + return &component->Template->attributes[i]; + } + } + + return NULL; +} + +t_dsp_address cm_getAttributeMpcAddress( + const t_component_instance *component, + const char *attrName) +{ + t_attribute* attribute; + t_uint32 dspAddress; + + if((attribute = cm_getAttributeDescriptor(component, attrName)) == NULL) + return 0x0; + + cm_DSP_GetDspAddress(component->memories[attribute->memory.memory->id], &dspAddress); + + return (dspAddress + + attribute->memory.offset); +} + +t_cm_logical_address cm_getAttributeHostAddr( + const t_component_instance *component, + const char *attrName) +{ + t_attribute* attribute; + + if((attribute = cm_getAttributeDescriptor(component, attrName)) == NULL) + return 0x0; + + // TODO JPF: component->Template->attributes[i].memory.offset could be converted in byte during load + return cm_DSP_GetHostLogicalAddress(component->memories[attribute->memory.memory->id]) + + attribute->memory.offset * attribute->memory.memory->memEntSize; +} + + +t_cm_error cm_readAttribute( + const t_component_instance* component, + const char* attrName, + t_uint32* value) +{ + t_attribute* attribute; + t_cm_logical_address hostAddr; + + if((attribute = cm_getAttributeDescriptor(component, attrName)) == NULL) + { + ERROR("CM_NO_SUCH_ATTRIBUTE(%s, %s)\n", component->pathname, attrName, 0, 0, 0, 0); + return CM_NO_SUCH_ATTRIBUTE; + } + + // TODO JPF: component->Template->attributes[i].memory.offset could be converted in byte during load + hostAddr = cm_DSP_GetHostLogicalAddress(component->memories[attribute->memory.memory->id]) + + attribute->memory.offset * attribute->memory.memory->memEntSize; + + if(attribute->memory.memory->memEntSize != 2) + *value = *((t_uint32 *)hostAddr) & ~MASK_BYTE3; + else + *value = *((t_uint16 *)hostAddr); + + LOG_INTERNAL(3, "cm_readAttribute: [%s:%s, %x]=%x\n", + component->pathname, attrName, hostAddr, *value, 0, 0); + + return CM_OK; +} + +t_uint32 cm_readAttributeNoError( + const t_component_instance* component, + const char* attrName) +{ + t_uint32 value; + + if(cm_readAttribute(component, attrName, &value) != CM_OK) + value = 0; + + return value; +} + +t_cm_error cm_writeAttribute( + const t_component_instance* component, + const char* attrName, + t_uint32 value) +{ + t_attribute* attribute; + t_cm_logical_address hostAddr; + + if((attribute = cm_getAttributeDescriptor(component, attrName)) == NULL) + { + ERROR("CM_NO_SUCH_ATTRIBUTE(%s, %s)\n", component->pathname, attrName, 0, 0, 0, 0); + return CM_NO_SUCH_ATTRIBUTE; + } + + // TODO JPF: component->Template->attributes[i].memory.offset could be converted in byte during load + hostAddr = cm_DSP_GetHostLogicalAddress(component->memories[attribute->memory.memory->id]) + + attribute->memory.offset * attribute->memory.memory->memEntSize; + + if(attribute->memory.memory->memEntSize != 2) + *((t_uint32 *)hostAddr) = value & ~MASK_BYTE3; + else + *((t_uint16 *)hostAddr) = value; + + /* be sure attribute is write into memory */ + OSAL_mb(); + + LOG_INTERNAL(3, "cm_writeAttribute: [%s:%s, %x]=%x\n", + component->pathname, attrName, hostAddr, value, 0, 0); + + return CM_OK; +} + + +/** + * + */ +t_dsp_address cm_getFunction( + const t_component_instance* component, + const char* interfaceName, + const char* methodName) +{ + t_interface_provide_description itfProvide; + t_interface_provide* provide; + t_interface_provide_loaded* provideLoaded; + t_cm_error error; + int i; + + // Get interface description + if((error = cm_getProvidedInterface(component, interfaceName, &itfProvide)) != CM_OK) + return error; + + provide = &component->Template->provides[itfProvide.provideIndex]; + provideLoaded = &component->Template->providesLoaded[itfProvide.provideIndex]; + + for(i = 0; i < provide->interface->methodNumber; i++) + { + if(cm_StringCompare(provide->interface->methodNames[i], methodName, MAX_INTERFACE_METHOD_NAME_LENGTH) == 0) + { + return provideLoaded->indexesLoaded[itfProvide.collectionIndex][i].methodAddresses; + } + } + + return 0x0; +} + +/** + * + */ +PRIVATE t_uint8 compareItfName(const char* simplename, const char* complexname, int *collectionIndex) { + int i; + + // Search if simplename is a prefix of complexname ?? + for(i = 0; simplename[i] != 0; i++) { + if(simplename[i] != complexname[i]) + return 1; // NO + } + + // YES + if(complexname[i] == '[') { + // This is a collection + int value = 0; + i++; + if(complexname[i] < '0' || complexname[i] > '9') { + return 1; + } + for(; complexname[i] >= '0' && complexname[i] <= '9'; i++) { + value = value * 10 + (complexname[i] - '0'); + } + if(complexname[i++] != ']') + return 1; + *collectionIndex = value; + } else + *collectionIndex = -1; + + if(complexname[i] != 0) { + // Complexe name has not been fully parsed -> different name + return 1; + } + + return 0; +} + + +/** + * + */ +PUBLIC t_cm_error cm_getProvidedInterface(const t_component_instance* server, + const char* itfName, + t_interface_provide_description *itfProvide){ + int i; + + for(i = 0; i < server->Template->provideNumber; i++) + { + int collectionIndex; + if(compareItfName(server->Template->provides[i].name, itfName, &collectionIndex) == 0) + { + t_interface_provide *provide = &server->Template->provides[i]; + if(collectionIndex >= 0) + { + if(! (provide->provideTypes & COLLECTION_PROVIDE)) { + ERROR("CM_NO_SUCH_PROVIDED_INTERFACE(%s, %s)\n", + server->pathname, itfName, 0, 0, 0, 0); + goto out; + } + if(collectionIndex >= provide->collectionSize) { + ERROR("CM_NO_SUCH_PROVIDED_INTERFACE(%s, %s): out of range [0..%d[\n", + server->pathname, itfName, provide->collectionSize, + 0, 0, 0); + goto out; + } + } + else + { + if(provide->provideTypes & COLLECTION_PROVIDE) { + ERROR("CM_NO_SUCH_PROVIDED_INTERFACE(%s, %s): interface is a collection [0..%d[\n", + server->pathname, itfName, provide->collectionSize, + 0, 0, 0); + goto out; + } + collectionIndex = 0; + } + itfProvide->provideIndex = i; + itfProvide->server = server; + itfProvide->collectionIndex = collectionIndex; + itfProvide->origName = itfName; + return CM_OK; + } + } + + ERROR("CM_NO_SUCH_PROVIDED_INTERFACE(%s, %s)\n", server->pathname, itfName, 0, 0, 0, 0); +out: + itfProvide->provideIndex = 0; + itfProvide->server = NULL; + itfProvide->collectionIndex = 0; + itfProvide->origName = NULL; + return CM_NO_SUCH_PROVIDED_INTERFACE; +} + +/** + * + */ +t_cm_error cm_getRequiredInterface(const t_component_instance* client, + const char* itfName, + t_interface_require_description *itfRequire){ + int i; + + for(i = 0; i < client->Template->requireNumber; i++) { + int collectionIndex; + if(compareItfName(client->Template->requires[i].name, itfName, &collectionIndex) == 0) { + t_interface_require *require = &client->Template->requires[i]; + if(collectionIndex >= 0) { + if(! (require->requireTypes & COLLECTION_REQUIRE)) { + ERROR("CM_NO_SUCH_REQUIRED_INTERFACE(%s, %s)\n", + client->pathname, itfName, 0, 0, 0, 0); + return CM_NO_SUCH_REQUIRED_INTERFACE; + } + if(collectionIndex >= require->collectionSize) { + ERROR("CM_NO_SUCH_REQUIRED_INTERFACE(%s, %s): out of range [0..%d[\n", + client->pathname, itfName, require->collectionSize, + 0, 0, 0); + return CM_NO_SUCH_REQUIRED_INTERFACE; + } + } else { + if(require->requireTypes & COLLECTION_REQUIRE) { + ERROR("CM_NO_SUCH_REQUIRED_INTERFACE(%s, %s): interface is a collection [0..%d[\n", + client->pathname, itfName, require->collectionSize, + 0, 0, 0); + return CM_NO_SUCH_REQUIRED_INTERFACE; + } + collectionIndex = 0; + } + itfRequire->client = client; + itfRequire->requireIndex = i; + itfRequire->collectionIndex = collectionIndex; + itfRequire->origName = itfName; + return CM_OK; + } + } + + ERROR("CM_NO_SUCH_REQUIRED_INTERFACE(%s, %s)\n", client->pathname, itfName, 0, 0, 0, 0); + return CM_NO_SUCH_REQUIRED_INTERFACE; +} diff --git a/drivers/staging/nmf-cm/cm/engine/component/src/loader.c b/drivers/staging/nmf-cm/cm/engine/component/src/loader.c new file mode 100644 index 00000000000..3d81e8308f9 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/component/src/loader.c @@ -0,0 +1,384 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/component/inc/instance.h> +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> +#include <cm/engine/component/inc/bind.h> + +#include <cm/engine/utils/inc/string.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/utils/inc/convert.h> + +void START(void); +void END(char*); + +#undef NHASH +#define NHASH 79 //Use a prime number! +#define MULT 17 + +static t_component_template *templates[NB_CORE_IDS][NHASH]; + +static unsigned int templateHash(const char *str) +{ + unsigned int h = 0; + for(; *str; str++) + h = MULT * h + *str; + return h % NHASH; +} + +static void templateAdd(t_component_template *template) +{ + unsigned int h = templateHash(template->name); + + if(templates[template->dspId][h] != NULL) + templates[template->dspId][h]->prev = template; + template->next = templates[template->dspId][h]; + template->prev = NULL; + templates[template->dspId][h] = template; +} + +static void templateRemove(t_component_template *template) +{ + unsigned int h = templateHash(template->name); + + if(template->prev != NULL) + template->prev->next = template->next; + if(template->next != NULL) + template->next->prev = template->prev; + if(template == templates[template->dspId][h]) + templates[template->dspId][h] = template->next; +} + + +t_component_template* cm_lookupTemplate(t_nmf_core_id dspId, t_dup_char str) +{ + t_component_template *template; + + for(template = templates[dspId][templateHash(str)]; template != NULL; template = template->next) + { + if(str == template->name) + return template; + } + + return NULL; +} + +t_bool cm_isComponentOnCoreId(t_nmf_core_id coreId) { + t_uint32 i; + + for(i = 0; i < NHASH; i++) + { + if ((templates[coreId][i] != NULL) + && (templates[coreId][i]->classe != FIRMWARE)) // Skip firmware + return TRUE; + } + + return FALSE; +} + + +static t_dsp_address MemoryToDspAdress(t_component_template *template, t_memory_reference *memory) +{ + if(memory->memory == NULL) + return (t_dsp_address)memory->offset; + else + { + t_dsp_address address; + + cm_DSP_GetDspAddress(template->memories[memory->memory->id], &address); + + return (t_dsp_address)(address + memory->offset); + } +} + +/* + * Method callback + */ +t_uint32 cm_resolvSymbol( + void* context, + t_uint32 type, + t_dup_char symbolName, + char* reloc_addr) +{ + t_component_template *template = (t_component_template*)context; + t_component_instance* ee = cm_EEM_getExecutiveEngine(template->dspId)->instance; + int i, j; + + // Search if this method is provided by EE and resolve it directly + for(i = 0; i < ee->Template->provideNumber; i++) + { + t_interface_provide* provide = &ee->Template->provides[i]; + t_interface_provide_loaded* provideLoaded = &ee->Template->providesLoaded[i]; + + for(j = 0; j < provide->interface->methodNumber; j++) + { + if(provide->interface->methodNames[j] == symbolName) + { + return provideLoaded->indexesLoaded[0][j].methodAddresses; // Here we assume no collection provided !! + } + } + } + + // Lookup if the method is statically required, ands delay relocation when bind occur + for(i = 0; i < template->requireNumber; i++) + { + if((template->requires[i].requireTypes & STATIC_REQUIRE) == 0) + continue; + + for(j = 0; j < template->requires[i].interface->methodNumber; j++) + { + if(template->requires[i].interface->methodNames[j] == symbolName) + { + t_function_relocation* delayedRelocation = (t_function_relocation*)OSAL_Alloc(sizeof(t_function_relocation)); + if(delayedRelocation == NULL) + return 0xFFFFFFFE; + + delayedRelocation->type = type; + delayedRelocation->symbol_name = cm_StringReference(symbolName); + delayedRelocation->reloc_addr = reloc_addr; + delayedRelocation->next = template->delayedRelocation; + template->delayedRelocation = delayedRelocation; + + return 0xFFFFFFFF; + } + } + } + + //Symbol not found + return 0x0; +} + +/* + * Template Management + */ +t_cm_error cm_loadComponent( + t_dup_char templateName, + t_cm_domain_id domainId, + t_elfdescription* elfhandle, + t_component_template **reftemplate) +{ + t_nmf_core_id coreId = cm_DM_GetDomainCoreId(domainId); + t_cm_error error; + int i, j, k; + + /* + * Allocate new component template if first instance + */ + if(*reftemplate == NULL) + { + t_component_template *template; + + LOG_INTERNAL(1, "\n##### Load template %s on %s #####\n", templateName, cm_getDspName(coreId), 0, 0, 0, 0); + + /* + * Sanity check + */ + if(elfhandle->foundedTemplateName != templateName) + { + ERROR("CM_INVALID_ELF_FILE: template name %s != %s\n", templateName, elfhandle->foundedTemplateName, 0, 0, 0, 0); + return CM_INVALID_ELF_FILE; + } + + // Alloc & Reset variable in order to use unloadComponent either with partial constructed template + *reftemplate = template = (t_component_template*)OSAL_Alloc_Zero(sizeof(t_component_template)); + if(template == NULL) + return CM_NO_MORE_MEMORY; + template->name = cm_StringReference(elfhandle->foundedTemplateName); + + // Get information from elfhandle + template->descriptionAssociatedWithTemplate = elfhandle->temporaryDescription; + template->requireNumber = elfhandle->requireNumber; + template->requires = elfhandle->requires; + template->attributeNumber = elfhandle->attributeNumber; + template->attributes = elfhandle->attributes; + template->propertyNumber = elfhandle->propertyNumber; + template->properties = elfhandle->properties; + template->provideNumber = elfhandle->provideNumber; + template->provides = elfhandle->provides; + if(template->descriptionAssociatedWithTemplate) + { + elfhandle->requires = NULL; + elfhandle->attributes = NULL; + elfhandle->properties = NULL; + elfhandle->provides = NULL; + } + + // Compute simple information + template->numberOfInstance = 1; + template->dspId = coreId; + LOG_INTERNAL(3, "load<%x> = %s\n", (int)template, template->name, 0, 0, 0, 0); + switch(elfhandle->magicNumber) { + case MAGIC_COMPONENT: + template->classe = COMPONENT; + break; + case MAGIC_SINGLETON: + template->classe = SINGLETON; + break; + case MAGIC_FIRMWARE: + template->classe = FIRMWARE; + break; + } + template->minStackSize = elfhandle->minStackSize; + + /* + * Load shared memory from file + */ + // START(); + if((error = cm_ELF_LoadTemplate(domainId, elfhandle, template->memories, template->classe == SINGLETON)) != CM_OK) + goto out; + MMDSP_serializeMemories(elfhandle->instanceProperty, &template->codeMemory, &template->thisMemory); + // END("cm_ELF_LoadTemplate"); + + /* + * Copy LCC functions information + * Since MMDSP require Constructor & Destructor (for cache flush and debug purpose) to be called + * either if not provided by user for allowing defered breakpoint, we use Void method if not provided. + */ + template->LCCConstructAddress = MemoryToDspAdress(template, &elfhandle->memoryForConstruct); + template->LCCStartAddress = MemoryToDspAdress(template, &elfhandle->memoryForStart); + template->LCCStopAddress = MemoryToDspAdress(template, &elfhandle->memoryForStop); + template->LCCDestroyAddress = MemoryToDspAdress(template, &elfhandle->memoryForDestroy); + if(template->LCCConstructAddress == 0 && template->classe != FIRMWARE) + template->LCCConstructAddress = cm_EEM_getExecutiveEngine(coreId)->voidAddr; + + // Compute provide methodIndex + if(template->provideNumber != 0) + { + template->providesLoaded = + (t_interface_provide_loaded*)OSAL_Alloc_Zero(sizeof(t_interface_provide_loaded) * template->provideNumber); + if(template->providesLoaded == NULL) + goto oom; + + for(i = 0; i < template->provideNumber; i++) + { + template->providesLoaded[i].indexesLoaded = (t_interface_provide_index_loaded**)OSAL_Alloc_Zero( + sizeof(t_interface_provide_index_loaded*) * template->provides[i].collectionSize); + if(template->providesLoaded[i].indexesLoaded == NULL) + goto oom; + + if(template->provides[i].interface->methodNumber != 0) + { + for(j = 0; j < template->provides[i].collectionSize; j++) + { + template->providesLoaded[i].indexesLoaded[j] = (t_interface_provide_index_loaded*)OSAL_Alloc( + sizeof(t_interface_provide_index_loaded) * template->provides[i].interface->methodNumber); + if(template->providesLoaded[i].indexesLoaded[j] == NULL) + goto oom; + + for(k = 0; k < template->provides[i].interface->methodNumber; k++) + { + template->providesLoaded[i].indexesLoaded[j][k].methodAddresses = + MemoryToDspAdress(template, &template->provides[i].indexes[j][k].memory); + + LOG_INTERNAL(2, " [%d, %d] method '%s' @ %x\n", + j, k, template->provides[i].interface->methodNames[k], + template->providesLoaded[i].indexesLoaded[j][k].methodAddresses, 0, 0); + } + + } + } + } + } + + /* + * TODO + + if((error = elfhandle->errorOccured) != CM_OK) + goto out; + */ + + // START(); + if(template->classe != FIRMWARE) + { + if((error = cm_ELF_relocateSharedSegments( + template->memories, + elfhandle, + template)) != CM_OK) + goto out; + } + // END("cm_ELF_relocateSharedSegments"); + + cm_ELF_FlushTemplate(coreId, template->memories); + + templateAdd(template); + + return CM_OK; + oom: + error = CM_NO_MORE_MEMORY; + out: + cm_unloadComponent(template); + return error; + } + else + { + (*reftemplate)->numberOfInstance++; + } + + return CM_OK; +} + +PUBLIC t_cm_error cm_unloadComponent( + t_component_template *template) +{ + /* + * Destroy template if last instance + */ + if(--template->numberOfInstance == 0) { + t_function_relocation* reloc; + + LOG_INTERNAL(3, "unload<%s>\n", template->name, 0, 0, 0, 0, 0); + + templateRemove(template); + + // Free delayedRelocation + reloc = template->delayedRelocation; + while(reloc != NULL) + { + t_function_relocation *tofree = reloc; + reloc = reloc->next; + cm_StringRelease(tofree->symbol_name); + OSAL_Free(tofree); + } + + if(template->providesLoaded != NULL) + { + int i, j; + + for(i = 0; i < template->provideNumber; i++) + { + if(template->providesLoaded[i].indexesLoaded != NULL) + { + for(j = 0; j < template->provides[i].collectionSize; j++) + { + OSAL_Free(template->providesLoaded[i].indexesLoaded[j]); + } + OSAL_Free(template->providesLoaded[i].indexesLoaded); + } + } + + OSAL_Free(template->providesLoaded); + } + + if(template->descriptionAssociatedWithTemplate) + { + cm_ELF_ReleaseDescription( + template->requireNumber, template->requires, + template->attributeNumber, template->attributes, + template->propertyNumber, template->properties, + template->provideNumber, template->provides); + } + + // Free shared memories + cm_ELF_FreeTemplate(template->dspId, template->memories); + + cm_StringRelease(template->name); + + OSAL_Free(template); + } + + return CM_OK; +} + diff --git a/drivers/staging/nmf-cm/cm/engine/configuration/inc/configuration.h b/drivers/staging/nmf-cm/cm/engine/configuration/inc/configuration.h new file mode 100644 index 00000000000..98d22bba743 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/configuration/inc/configuration.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef __INC_CONFIGURATION_H_ +#define __INC_CONFIGURATION_H_ + +#include <cm/engine/api/control/configuration_engine.h> +#include <cm/engine/memory/inc/memory.h> +#include <inc/nmf-limits.h> +#include <cm/engine/dsp/inc/dsp.h> + +/******************************************************************************/ +/************************ FUNCTIONS PROTOTYPES ********************************/ +/******************************************************************************/ + +PUBLIC t_cm_error cm_CFG_ConfigureMediaProcessorCore(t_nmf_core_id coreId, + t_nmf_executive_engine_id executiveEngineId, + t_nmf_semaphore_type_id semaphoreTypeId, t_uint8 nbYramBanks, + const t_cm_system_address *mediaProcessorMappingBaseAddr, + const t_cm_domain_id eeDomain, + t_dsp_allocator_desc* sdramCodeAllocId, + t_dsp_allocator_desc* sdramDataAllocId + ); + +PUBLIC t_cm_error cm_CFG_AddMpcSdramSegment(const t_nmf_memory_segment *pDesc, + const char *memoryname, t_dsp_allocator_desc **allocDesc); + +PUBLIC t_cm_error cm_CFG_CheckMpcStatus(t_nmf_core_id coreId); + +void cm_CFG_ReleaseMpc(t_nmf_core_id coreId); + +#endif /* __INC_CONFIGURATION_H_ */ diff --git a/drivers/staging/nmf-cm/cm/engine/configuration/inc/configuration_status.h b/drivers/staging/nmf-cm/cm/engine/configuration/inc/configuration_status.h new file mode 100644 index 00000000000..0c75b9c49b0 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/configuration/inc/configuration_status.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef __INC_CONFIGSTATUS_H_ +#define __INC_CONFIGSTATUS_H + +#include <cm/inc/cm_type.h> +#include <cm/engine/utils/inc/string.h> + +/* + * Variable to active intensive check + * + * \ingroup CM_CONFIGURATION_API + */ +extern t_sint32 cmIntensiveCheckState; + +/* + * Variable to active trace level + * + * \ingroup CM_CONFIGURATION_API + */ +extern t_sint32 cm_debug_level; + +/* + * Variable to active error break + * + * \ingroup CM_CONFIGURATION_API + */ +extern t_sint32 cm_error_break; + +/* + * Variable to activate Ulp + * + * \ingroup CM_CONFIGURATION_API + */ +extern t_bool cmUlpEnable; + +extern t_dup_char anonymousDup, eventDup, skeletonDup, stubDup, traceDup; + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/configuration/inc/configuration_type.h b/drivers/staging/nmf-cm/cm/engine/configuration/inc/configuration_type.h new file mode 100644 index 00000000000..af29d584ba4 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/configuration/inc/configuration_type.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Configuration Component Manager API type. + */ +#ifndef CONFIGURATION_TYPE_H +#define CONFIGURATION_TYPE_H + +#include <cm/inc/cm_type.h> + +/*! + * @defgroup t_cm_cmd_id t_cm_cmd_id + * \brief Definition of the command ID + * \ingroup CM_CONFIGURATION_API + * + * CM_CMD_XXX designates the command ID used by the \ref CM_SetMode routine. + * + * \remarks Other command IDs are not yet implemented. + */ + +typedef t_uint32 t_cm_cmd_id; //!< Fake enumeration type \ingroup t_cm_cmd_id +#define CM_CMD_SYNC ((t_cm_cmd_id)0x01) //!< Synchronize on-going operations (no parameter) \ingroup t_cm_cmd_id + +#define CM_CMD_WARM_RESET ((t_cm_cmd_id)0x02) //!< Reset a part of the CM-engine (parameter indicates the part which must be reseted) \ingroup t_cm_cmd_id + +#define CM_CMD_PWR_MGR ((t_cm_cmd_id)0x10) //!< Enable/Disable the internal power management module (0=Disable, 1=Enable) \ingroup t_cm_cmd_id + +#define CM_CMD_DBG_MODE ((t_cm_cmd_id)0x40) //!< Enable/Disable DEBUG mode, Pwr Mgr is also disabled (0=Disable, 1=Enable) \ingroup t_cm_cmd_id + +#define CM_CMD_TRACE_ON ((t_cm_cmd_id)0x41) //!< Enable STM/XTI tracing and force network resetting and dumping \note Since MPC trace will be usable, you can enable them if not \ingroup t_cm_cmd_id +#define CM_CMD_TRACE_OFF ((t_cm_cmd_id)0x42) //!< Disable STM/XTI tracing \note Since MPC trace will not be usable, you can also disable them \ingroup t_cm_cmd_id + +#define CM_CMD_MPC_TRACE_ON ((t_cm_cmd_id)0x50) //!< Enable MPC STM/XTI tracing (param == coreId). \note This command is not execute if execution engine not started on the coreId \ingroup t_cm_cmd_id +#define CM_CMD_MPC_TRACE_OFF ((t_cm_cmd_id)0x51) //!< Disable MPC STM/XTI tracing (param == coreId) This is the default configuration. \note This command is not execute if execution engine not started on the coreId \ingroup t_cm_cmd_id + +#define CM_CMD_MPC_PRINT_OFF ((t_cm_cmd_id)0x52) //!< Set to OFF the level of MPC traces (param == coreId) \note This command is not execute if execution engine not started on the coreId \ingroup t_cm_cmd_id +#define CM_CMD_MPC_PRINT_ERROR ((t_cm_cmd_id)0x53) //!< Set to ERROR the level of MPC traces param == coreId) \note This command is not execute if execution engine not started on the coreId \ingroup t_cm_cmd_id +#define CM_CMD_MPC_PRINT_WARNING ((t_cm_cmd_id)0x54) //!< Set to WARNING the level of MPC traces param == coreId) \note This command is not execute if execution engine not started on the coreId \ingroup t_cm_cmd_id +#define CM_CMD_MPC_PRINT_INFO ((t_cm_cmd_id)0x55) //!< Set to INFO the level of MPC traces (param == coreId) \note This command is not execute if execution engine not started on the coreId This is the default configuration. \ingroup t_cm_cmd_id +#define CM_CMD_MPC_PRINT_VERBOSE ((t_cm_cmd_id)0x56) //!< Set to VERBOSE the level of MPC traces param == coreId) \note This command is not execute if execution engine not started on the coreId \ingroup t_cm_cmd_id + +/*! + * \brief Define the level of internal CM log traces + * + * Define the level of internal CM log traces (-1 to 3) + * -# <b>-1 </b> all internal LOG/ERROR traces are disabled + * -# <b> 0 </b> all internal LOG traces are disabled (<b>default/reset value</b>) + * -# <b> 1, 2, 3 </b> Most and most + * + * \ingroup t_cm_cmd_id + */ +#define CM_CMD_TRACE_LEVEL ((t_cm_cmd_id)0x80) + +/*! + * \brief Enable/Disable intensive internal check + * + * Enable/Disable intensive internal check (0=Disable, 1=Enable): + * - Component handle checking + * + * Must be used during the integration phase (additional process is time consuming). + * + * \ingroup t_cm_cmd_id + */ +#define CM_CMD_INTENSIVE_CHECK ((t_cm_cmd_id)0x100) + +/*! + * \brief Enable/Disable ulp mode + * + * Enable/Disable Ultra Low Power mode. + * + * \ingroup t_cm_cmd_id + */ +#define CM_CMD_ULP_MODE_ON ((t_cm_cmd_id)0x111) //!< Enable ULP mode \ingroup t_cm_cmd_id +#define CM_CMD_ULP_MODE_OFF ((t_cm_cmd_id)0x110) //!< Deprecated (must be removed in 2.10) !!! + +#endif /* CONFIGURATION_TYPE_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/configuration/src/configuration.c b/drivers/staging/nmf-cm/cm/engine/configuration/src/configuration.c new file mode 100644 index 00000000000..f092c7061b4 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/configuration/src/configuration.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/configuration/inc/configuration.h> +#include <cm/engine/component/inc/initializer.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> +#include <cm/engine/semaphores/inc/semaphores.h> +#include <cm/engine/communication/inc/communication.h> +#include <cm/engine/utils/inc/string.h> +#include <cm/engine/repository_mgt/inc/repository_mgt.h> +#include <inc/nmf-limits.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/memory/inc/domain.h> + +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/utils/inc/convert.h> + +#include <cm/engine/power_mgt/inc/power.h> + +t_sint32 cmIntensiveCheckState = 0; +t_sint32 cm_debug_level = 1; +t_sint32 cm_error_break = 0; +t_bool cmUlpEnable = FALSE; + + +#define MAX_EE_NAME_LENGTH 32 +typedef struct { + char eeName[MAX_EE_NAME_LENGTH]; + t_nmf_executive_engine_id executiveEngineId; + t_uint32 EEmemoryCount; +} t_cfg_mpc_desc; + +static t_cfg_mpc_desc cfgMpcDescArray[NB_CORE_IDS]; + +PUBLIC t_cm_error cm_CFG_ConfigureMediaProcessorCore( + t_nmf_core_id coreId, + t_nmf_executive_engine_id executiveEngineId, + t_nmf_semaphore_type_id semaphoreTypeId, + t_uint8 nbYramBanks, + const t_cm_system_address *mediaProcessorMappingBaseAddr, + const t_cm_domain_id eeDomain, + t_dsp_allocator_desc *sdramCodeAllocDesc, + t_dsp_allocator_desc *sdramDataAllocDesc) +{ + /* Process requested configuration (save it) */ + cfgMpcDescArray[coreId].EEmemoryCount = 0; + cfgMpcDescArray[coreId].executiveEngineId = executiveEngineId; + /* Build Executive Engine Name */ + switch(executiveEngineId) + { + case SYNCHRONOUS_EXECUTIVE_ENGINE: + cm_StringCopy(cfgMpcDescArray[coreId].eeName, "synchronous_", MAX_EE_NAME_LENGTH); + break; + case HYBRID_EXECUTIVE_ENGINE: + cm_StringCopy(cfgMpcDescArray[coreId].eeName, "hybrid_", MAX_EE_NAME_LENGTH); + break; + } + + switch(semaphoreTypeId) + { + case LOCAL_SEMAPHORES: + cm_StringConcatenate(cfgMpcDescArray[coreId].eeName, "lsem", MAX_EE_NAME_LENGTH); + break; + case SYSTEM_SEMAPHORES: + cm_StringConcatenate(cfgMpcDescArray[coreId].eeName, "hsem", MAX_EE_NAME_LENGTH); + break; + } + + cm_SEM_InitMpc(coreId, semaphoreTypeId); + + return cm_DSP_Add(coreId, nbYramBanks, mediaProcessorMappingBaseAddr, eeDomain, sdramCodeAllocDesc, sdramDataAllocDesc); +} + +// TODO JPF: Move in dsp.c +PUBLIC t_cm_error cm_CFG_AddMpcSdramSegment(const t_nmf_memory_segment *pDesc, const char* memoryname, t_dsp_allocator_desc **allocDesc) +{ + t_dsp_allocator_desc *desc; + if ( (pDesc == NULL) || + ((pDesc->systemAddr.logical & CM_MM_ALIGN_64BYTES) != 0) ) + return CM_INVALID_PARAMETER; + + //TODO, juraj, the right place and way to do this? + desc = (t_dsp_allocator_desc*)OSAL_Alloc(sizeof (t_dsp_allocator_desc)); + if (desc == 0) + return CM_NO_MORE_MEMORY; + + desc->allocDesc = cm_MM_CreateAllocator(pDesc->size, 0, memoryname); + if (desc->allocDesc == 0) { + OSAL_Free(desc); + return CM_NO_MORE_MEMORY; + } + desc->baseAddress = pDesc->systemAddr; + desc->referenceCounter = 0; + + *allocDesc = desc; + + return CM_OK; +} + +PUBLIC t_cm_error cm_CFG_CheckMpcStatus(t_nmf_core_id coreId) +{ + t_cm_error error; + + if (cm_DSP_GetState(coreId)->state == MPC_STATE_BOOTABLE) + { + /* Allocate coms fifo for a given MPC */ + if ((error = cm_COM_AllocateMpc(coreId)) != CM_OK) + return error; + + /* Launch EE */ + if ((error = cm_EEM_Init(coreId, + cfgMpcDescArray[coreId].eeName, + cfgMpcDescArray[coreId].executiveEngineId)) != CM_OK) + { + cm_COM_FreeMpc(coreId); + return error; + } + + /* Initialize coms fifo for a given MPC */ + cm_COM_InitMpc(coreId); + + /* Initialisation of the dedicated communication channel for component initialization */ + if((error = cm_COMP_INIT_Init(coreId)) != CM_OK) + { + cm_EEM_Close(coreId); + cm_COM_FreeMpc(coreId); + return error; + } + + cfgMpcDescArray[coreId].EEmemoryCount = cm_PWR_GetMPCMemoryCount(coreId); + + if(cmUlpEnable) + { + // We have finish boot, allow MMDSP to go in auto idle + cm_EEM_AllowSleep(coreId); + } + } + + if (cm_DSP_GetState(coreId)->state != MPC_STATE_BOOTED) + return CM_MPC_NOT_INITIALIZED; + + return CM_OK; +} + +void cm_CFG_ReleaseMpc(t_nmf_core_id coreId) +{ + t_uint32 memoryCount = cm_PWR_GetMPCMemoryCount(coreId); + + // If No more memory and no more component (to avoid switch off in case of component using no memory) + if( + cm_PWR_GetMode() == NORMAL_PWR_MODE && + memoryCount != 0 /* Just to see if there is something */ && + memoryCount == cfgMpcDescArray[coreId].EEmemoryCount && + cm_isComponentOnCoreId(coreId) == FALSE) + { + LOG_INTERNAL(1, "\n##### Shutdown %s #####\n", cm_getDspName(coreId), 0, 0, 0, 0, 0); + + (void)cm_EEM_ForceWakeup(coreId); + + /* remove ee from load map here */ + cm_COMP_INIT_Close(coreId); + cm_EEM_Close(coreId); + cm_COM_FreeMpc(coreId); + + cfgMpcDescArray[coreId].EEmemoryCount = 0; // For debug purpose + } +} diff --git a/drivers/staging/nmf-cm/cm/engine/configuration/src/configuration_wrapper.c b/drivers/staging/nmf-cm/cm/engine/configuration/src/configuration_wrapper.c new file mode 100644 index 00000000000..bc3952e63b4 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/configuration/src/configuration_wrapper.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/api/configuration_engine.h> +#include <cm/engine/communication/inc/communication.h> +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/memory/inc/chunk_mgr.h> +#include <cm/engine/repository_mgt/inc/repository_mgt.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/semaphores/inc/semaphores.h> +#include <cm/engine/semaphores/hw_semaphores/inc/hw_semaphores.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> +#include <cm/engine/configuration/inc/configuration.h> +#include <cm/engine/power_mgt/inc/power.h> +#include <cm/engine/utils/inc/string.h> +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/component/inc/bind.h> +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/api/executive_engine_mgt_engine.h> + +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/trace/inc/xtitrace.h> + +t_dup_char anonymousDup, eventDup, skeletonDup, stubDup, traceDup; + +PUBLIC t_cm_error CM_ENGINE_Init( + const t_nmf_hw_mapping_desc *pNmfHwMappingDesc, + const t_nmf_config_desc *pNmfConfigDesc + ) +{ + t_cm_error error; + + // The purpose of that is just to not free/unfree some String frequently used + anonymousDup = cm_StringDuplicate("anonymous"); + eventDup = cm_StringDuplicate("event"); + skeletonDup = cm_StringDuplicate("skeleton"); + stubDup = cm_StringDuplicate("stub"); + traceDup = cm_StringDuplicate("trace"); + + if (( + error = cm_OSAL_Init() + ) != CM_OK) { return error; } + + if (( + error = cm_COMP_Init() + ) != CM_OK) { return error; } + + if (( + error = cm_PWR_Init() + ) != CM_OK) { return error; } + + cm_TRC_traceReset(); + + if (( + error = cm_DM_Init() + ) != CM_OK) {return error; } + + if (( + error = cm_SEM_Init(&pNmfHwMappingDesc->hwSemaphoresMappingBaseAddr) + ) != CM_OK) { return error; } + + if ((error = cm_COM_Init(pNmfConfigDesc->comsLocation)) != CM_OK) + return error; + + cm_DSP_Init(&pNmfHwMappingDesc->esramDesc); + + return CM_OK; +} + +PUBLIC void CM_ENGINE_Destroy(void) +{ + t_component_instance *instance; + t_cm_error error; + t_uint32 i; + + /* PP: Well, on Linux (and probably on Symbian too), this is called when driver is removed + * => the module (driver) can't be removed if there are some pending clients + * => all remaining components should have been destroyed in CM_ENGINE_FlushClient() + * => So, if we found some components here, we are in BIG trouble ... + */ + /* First, stop all remaining components */ + for (i=0; i<ComponentTable.idxMax; i++) + { + t_nmf_client_id clientId; + + if ((instance = componentEntry(i)) == NULL) + continue; + clientId = domainDesc[instance->domainId].client; + LOG_INTERNAL(0, "Found a remaining component %s (%s) when destroying the CM !!!\n", instance->pathname, instance->Template->name, 0, 0, 0, 0); + if (/* skip EE */ + (instance->Template->classe == FIRMWARE) || + /* Skip all binding components */ + (cm_StringCompare(instance->Template->name, "_ev.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_st.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_sk.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_tr.", 4) == 0)) + continue; + + /* + * Special code for SINGLETON handling + */ + if(instance->Template->classe == SINGLETON) + { + struct t_client_of_singleton* cl = instance->clientOfSingleton; + + clientId = instance->clientOfSingleton->clientId; + for( ; cl != NULL ; cl = cl->next) + { + if(cl == instance->clientOfSingleton) + { + cl->numberOfStart = 1; // == 1 since it will go to 0 in cm_stopComponent + cl->numberOfInstance = 1; // == 1 since it will go to 0 in cm_destroyInstanceForClient + } + else + { + cl->numberOfStart = 0; + cl->numberOfInstance = 0; + } + cl->numberOfBind = 0; + } + } + + // Stop the component + error = cm_stopComponent(instance, clientId); + if (error != CM_OK && error != CM_COMPONENT_NOT_STARTED) + LOG_INTERNAL(0, "Error stopping component %s/%x (%s, error=%d, client=%u)\n", instance->pathname, instance, instance->Template->name, error, clientId, 0); + + // Destroy dependencies + cm_destroyRequireInterface(instance, clientId); + } + + /* Destroy all remaining components */ + for (i=0; i<ComponentTable.idxMax; i++) + { + t_nmf_client_id clientId; + + if ((instance = componentEntry(i)) == NULL) + continue; + clientId = domainDesc[instance->domainId].client; + + if (/* skip EE */ + (instance->Template->classe == FIRMWARE) || + /* Skip all binding components */ + (cm_StringCompare(instance->Template->name, "_ev.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_st.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_sk.", 4) == 0) || + (cm_StringCompare(instance->Template->name, "_tr.", 4) == 0)) { + continue; + } + + if(instance->Template->classe == SINGLETON) + { + clientId = instance->clientOfSingleton->clientId; + } + + // Destroy the component + error = cm_destroyInstanceForClient(instance, DESTROY_WITHOUT_CHECK, clientId); + + if (error != CM_OK) + { + /* FIXME : add component name instance in log message but need to make a copy before cm_flushComponent() + * because it's no more available after. + */ + LOG_INTERNAL(0, "Error flushing component (error=%d, client=%u)\n", error, clientId, 0, 0, 0, 0); + } + } + + /* This will power off all ressources and destroy EE */ + cm_PWR_SetMode(NORMAL_PWR_MODE); + cm_DSP_Destroy(); + cm_DM_Destroy(); + /* Nothing to do about SEM */ + //cm_MM_Destroy(); + cm_REP_Destroy(); + cm_COMP_Destroy(); + cm_OSAL_Destroy(); + + cm_StringRelease(traceDup); + cm_StringRelease(stubDup); + cm_StringRelease(skeletonDup); + cm_StringRelease(eventDup); + cm_StringRelease(anonymousDup); +} + +PUBLIC t_cm_error CM_ENGINE_ConfigureMediaProcessorCore( + t_nmf_core_id coreId, + t_nmf_executive_engine_id executiveEngineId, + t_nmf_semaphore_type_id semaphoreTypeId, + t_uint8 nbYramBanks, + const t_cm_system_address *mediaProcessorMappingBaseAddr, + const t_cm_domain_id eeDomain, + const t_cfg_allocator_id sdramCodeAllocId, + const t_cfg_allocator_id sdramDataAllocId + ) +{ + return cm_CFG_ConfigureMediaProcessorCore( + coreId, + executiveEngineId, + semaphoreTypeId, + nbYramBanks, + mediaProcessorMappingBaseAddr, + eeDomain, + (t_dsp_allocator_desc*)sdramCodeAllocId, + (t_dsp_allocator_desc*)sdramDataAllocId + ); +} + +PUBLIC t_cm_error CM_ENGINE_AddMpcSdramSegment( + const t_nmf_memory_segment *pDesc, + t_cfg_allocator_id *id, + const char *memoryname + ) +{ + return cm_CFG_AddMpcSdramSegment(pDesc, memoryname == NULL ? "" : memoryname, (t_dsp_allocator_desc**)id); +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_SetMode(t_cm_cmd_id aCmdID, t_sint32 aParam) +{ + t_cm_error error = CM_OK; + int i; + + OSAL_LOCK_API(); + + switch(aCmdID) { + case CM_CMD_DBG_MODE: + cm_PWR_SetMode(( aParam==1 ) ? DISABLE_PWR_MODE : NORMAL_PWR_MODE); + switch(cm_PWR_GetMode()) + { + case NORMAL_PWR_MODE: + // Release the MPC (which will switch it off if no more used) + for (i=FIRST_MPC_ID; i<NB_CORE_IDS; i++) + { + cm_CFG_ReleaseMpc(i); + } + break; + case DISABLE_PWR_MODE: + // Force the load of the EE if not already done. + for (i=FIRST_MPC_ID; i<NB_CORE_IDS;i++) + { + if((error = cm_CFG_CheckMpcStatus(i)) != CM_OK) + break; + } + break; + } + break; + case CM_CMD_TRACE_LEVEL: + if (aParam<-1) cm_debug_level = -1; + else cm_debug_level = aParam; + break; + case CM_CMD_INTENSIVE_CHECK: + cmIntensiveCheckState = aParam; + break; + + case CM_CMD_TRACE_ON: + cm_trace_enabled = TRUE; + cm_TRC_Dump(); + break; + case CM_CMD_TRACE_OFF: + cm_trace_enabled = FALSE; + break; + + case CM_CMD_MPC_TRACE_ON: + cm_EEM_setTraceMode((t_nmf_core_id)aParam, 1); + break; + case CM_CMD_MPC_TRACE_OFF: + cm_EEM_setTraceMode((t_nmf_core_id)aParam, 0); + break; + + case CM_CMD_MPC_PRINT_OFF: + cm_EEM_setPrintLevel((t_nmf_core_id)aParam, 0); + break; + case CM_CMD_MPC_PRINT_ERROR: + cm_EEM_setPrintLevel((t_nmf_core_id)aParam, 1); + break; + case CM_CMD_MPC_PRINT_WARNING: + cm_EEM_setPrintLevel((t_nmf_core_id)aParam, 2); + break; + case CM_CMD_MPC_PRINT_INFO: + cm_EEM_setPrintLevel((t_nmf_core_id)aParam, 3); + break; + case CM_CMD_MPC_PRINT_VERBOSE: + cm_EEM_setPrintLevel((t_nmf_core_id)aParam, 4); + break; + + case CM_CMD_ULP_MODE_ON: + cmUlpEnable = TRUE; + break; + + default: + error = CM_INVALID_PARAMETER; + break; + } + + OSAL_UNLOCK_API(); + + return error; +} + diff --git a/drivers/staging/nmf-cm/cm/engine/dsp/inc/dsp.h b/drivers/staging/nmf-cm/cm/engine/dsp/inc/dsp.h new file mode 100644 index 00000000000..439deac115f --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/dsp/inc/dsp.h @@ -0,0 +1,453 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief DSP abstraction layer + * + * \defgroup DSP_INTERNAL Private DSP Abstraction Layer API. + * + */ +#ifndef __INC_CM_DSP_H +#define __INC_CM_DSP_H + +#include <cm/inc/cm_type.h> +#include <share/inc/nmf.h> +#include <cm/engine/memory/inc/domain_type.h> +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/memory/inc/remote_allocator.h> + + +#define SxA_NB_BLOCK_RAM 8 /*32kworks (24-bit) */ + +#define SxA_LOCKED_WAY 1 + +/* + * Type defintion to handle dsp offset in word + */ +typedef t_uint32 t_dsp_offset; + +typedef t_uint32 t_dsp_address; + +typedef enum { + DSP2ARM_IRQ_0, + DSP2ARM_IRQ_1 +} t_mpc2host_irq_num; + +typedef enum { + ARM2DSP_IRQ_0, + ARM2DSP_IRQ_1, + ARM2DSP_IRQ_2, + ARM2DSP_IRQ_3 +} t_host2mpc_irq_num; + +typedef enum { + INTERNAL_XRAM24 = 0, /* 24-bit XRAM */ + INTERNAL_XRAM16 = 1, /* 16-bit XRAM */ + INTERNAL_YRAM24 = 2, /* 24-bit YRAM */ + INTERNAL_YRAM16 = 3, /* 16-bit YRAM */ + SDRAM_EXT24 = 4, /* 24-bit external "X" memory */ + SDRAM_EXT16 = 5, /* 16-bit external "X" memory */ + ESRAM_EXT24 = 6, /* ESRAM24 */ + ESRAM_EXT16 = 7, /* ESRAM16 */ + SDRAM_CODE = 8, /* Program memory */ + ESRAM_CODE = 9, /* ESRAM code */ + LOCKED_CODE = 10, /* For way locking */ + NB_DSP_MEMORY_TYPE, + DEFAULT_DSP_MEM_TYPE = MASK_ALL16 +} t_dsp_memory_type_id; + +typedef struct { + t_cm_allocator_desc *allocDesc; + t_cm_system_address baseAddress; + t_uint32 referenceCounter; +} t_dsp_allocator_desc; + +typedef struct { + t_cm_system_address base; + t_uint32 size; +} t_dsp_segment; + +typedef enum { +#if defined(__STN_8500) && (__STN_8500 > 10) + SDRAM_CODE_EE, + SDRAM_CODE_USER, + SDRAM_DATA_EE, + SDRAM_DATA_USER, + NB_MIGRATION_SEGMENT, + ESRAM_CODE_EE = NB_MIGRATION_SEGMENT, + ESRAM_CODE_USER, + ESRAM_DATA_EE, + ESRAM_DATA_USER, +#else + SDRAM_CODE_EE, + SDRAM_DATA_EE, + ESRAM_CODE_EE, + ESRAM_DATA_EE, +#endif + NB_DSP_SEGMENT_TYPE +} t_dsp_segment_type; + +typedef struct { + t_dsp_segment_type segmentType; + t_uint32 baseOffset; +} t_dsp_address_info; + +typedef enum { + MPC_STATE_UNCONFIGURED, + MPC_STATE_BOOTABLE, + MPC_STATE_BOOTED, + MPC_STATE_PANIC, +} t_dsp_state; + +typedef struct { + t_dsp_state state; + t_uint8 nbYramBank; + t_cm_domain_id domainEE; + t_dsp_allocator_desc *allocator[NB_DSP_MEMORY_TYPE]; + t_dsp_segment segments[NB_DSP_SEGMENT_TYPE]; + t_uint32 yram_offset; + t_uint32 yram_size; + t_uint32 locked_offset; + t_uint32 locked_size; +} t_dsp_desc; + +typedef struct { + t_nmf_core_id coreId; + t_dsp_memory_type_id memType; // Index in MPC desc allocator + t_cm_allocator_desc *alloc; +} t_dsp_chunk_info; + +PUBLIC const t_dsp_desc* cm_DSP_GetState(t_nmf_core_id coreId); +PUBLIC void cm_DSP_SetStatePanic(t_nmf_core_id coreId); + +PUBLIC void cm_DSP_Init(const t_nmf_memory_segment *pEsramDesc); +PUBLIC void cm_DSP_Destroy(void); + +/*! + * \brief Initialize the memory segments management of a given MPC + * + * \param[in] coreId Identifier of the DSP to initialize + * \param[in] pDspMapDesc DSP mapping into host space + * \param[in] memConf configuration of the DSP memories (standalone or shared) + * + * \retval t_cm_error + * + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_error cm_DSP_Add(t_nmf_core_id coreId, + t_uint8 nbYramBanks, + const t_cm_system_address *pDspMapDesc, + const t_cm_domain_id eeDomain, + t_dsp_allocator_desc *sdramCodeAllocDesc, + t_dsp_allocator_desc *sdramDataAllocDesc); + + + +/*! + * \brief Configure a given Media Processor Core + * + * This routine programs the configuration (caches, ahb wrapper, ...) registers of a given MPC. + * + * \param[in] coreId Identifier of the DSP to initialize + * + * \retval t_cm_error + * + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_error cm_DSP_Boot(t_nmf_core_id coreId); + +/*! + * \brief Boot a given DSP + * + * This routine allows after having initialized and loaded the EE into a given DSP to start it (boot it) + * + * \param[in] coreId identifier of the DSP to boot + * \param[in] panicReasonOffset offset of panic reason which will pass to NONE_PANIC when DSP booted. + * + * \retval t_cm_error + * + * \ingroup DSP_INTERNAL + */ +PUBLIC void cm_DSP_ConfigureAfterBoot(t_nmf_core_id coreId); + +PUBLIC void cm_DSP_Start(t_nmf_core_id coreId); + +PUBLIC void cm_DSP_Stop(t_nmf_core_id coreId); + +/*! + * \brief Shutdown a given DSP + * + * This routine allows to stop and shutdown a given DSP + * + * \param[in] coreId identifier of the DSP to shutdown + * + * \retval t_cm_error + * + * \ingroup DSP_INTERNAL + */ +PUBLIC void cm_DSP_Shutdown(t_nmf_core_id coreId); + +PUBLIC t_uint32 cm_DSP_ReadXRamWord(t_nmf_core_id coreId, t_uint32 dspOffset); +PUBLIC void cm_DSP_WriteXRamWord(t_nmf_core_id coreId, t_uint32 dspOffset, t_uint32 value); + +/*! + * \brief Convert a Dsp address (offset inside a given DSP memory segment) into the host address (logical) + * + * \param[in] coreId identifier of the given DSP + * \param[in] dspAddress dsp address to be converted + * \param[in] memType memory type identifier + * + * \retval t_cm_logical_address + * + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_logical_address cm_DSP_ConvertDspAddressToHostLogicalAddress(t_nmf_core_id coreId, t_shared_addr dspAddress); + +/*! + * \brief Acknowledge the local interrupt of a given DSP (when not using HW semaphore mechanisms) + * + * \param[in] coreId identifier of the given DSP + * \param[in] irqNum irq identifier + * + * \retval void + * + * \ingroup DSP_INTERNAL + */ +PUBLIC void cm_DSP_AcknowledgeDspIrq(t_nmf_core_id coreId, t_mpc2host_irq_num irqNum); + + +/* + * Memory Management API routines + */ + +/*! + * \brief Retrieve DSP information for a memory chunk. + * + * This function retrieves information stored in user-data of the allocated chunk. + * See also \ref{t_dsp_chunk_info}. + * + * \param[in] memHandle Handle to the allocated chunk. + * \param[out] info Dsp information structure. + * + * \ingroup DSP_INTERNAL + */ +PUBLIC void cm_DSP_GetDspChunkInfo(t_memory_handle memHandle, t_dsp_chunk_info *info); + +/*! + * \brief Get memory allocator for a given memory type on a DSP. + * + * \param[in] coreId Dsp identifier. + * \param[in] memType Memory type identifier. + * + * \retval reference to the allocator descriptor (or null) + * + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_allocator_desc* cm_DSP_GetAllocator(t_nmf_core_id coreId, t_dsp_memory_type_id memType); + +/*! + * \brief Get DSP internal memory (TCM) information for allocation. + * + * For DSP-internal memories (TCMX, Y 16/24), return the offset and size of the allocation zone (for domain + * mechanism) and the allocation memory type. + * + * \param[in] coreId Dsp identifier. + * \param[in] memType Memory type identifier. + * \param[out] mem_info Memory information structure. + * + * \retval CM_OK + * + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_error cm_DSP_GetInternalMemoriesInfo(t_cm_domain_id domainId, t_dsp_memory_type_id memType, + t_uint32 *offset, t_uint32 *size); + + +/*! + * \brief Convert word size to byte size. + * + * \param[in] memType Memory type identifier. + * \param[in] wordSize Word size to be converted. + * + * \retval Byte size. + * + * \ingroup DSP_INTERNAL + */ +PUBLIC t_uint32 cm_DSP_ConvertSize(t_dsp_memory_type_id memType, t_uint32 wordSize); + +/*! + * \brief Provide the Memory status of a given memory type for a given DSP + * + * \param[in] coreId dsp identifier. + * \param[in] memType Type of memory. + * \param[out] pStatus requested memory status + * + * \retval t_cm_error + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_error cm_DSP_GetAllocatorStatus(t_nmf_core_id coreId, t_dsp_memory_type_id memType, t_uint32 offset, t_uint32 size, t_cm_allocator_status *pStatus); + +/*! + * \brief Provide DSP memory host shared address + * + * \param[in] memHandle Allocated block handle + * \param[out] pAddr Returned system address. + * + * \retval t_cm_error + * \ingroup DSP_INTERNAL + */ +PUBLIC void cm_DSP_GetHostSystemAddress( t_memory_handle memHandle, t_cm_system_address *pAddr); + +/*! + * \brief Get physical address of a memory chunk. + * + * \param[in] memHandle Memory handle. + * + * \retval Physical address. + * + * \ingroup DSP_INTERNAL + */ +PUBLIC t_physical_address cm_DSP_GetPhysicalAdress(t_memory_handle memHandle); + +/*! + * \brief Return Logical Address of an allocated memory chunk. + * + * \param[in] memHandle Allocated chunk handle + * \retval t_cm_error + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_logical_address cm_DSP_GetHostLogicalAddress(t_memory_handle memHandle); + +/*! + * \brief Provide DSP memory DSP address (offset inside a given DSP memory segment) + * + * \param[in] memHandle Allocated block handle + * \param[out] dspAddress allocated block address seen by the given DSP + * + * \retval t_cm_error + * \ingroup DSP_INTERNAL + */ +PUBLIC void cm_DSP_GetDspAddress(t_memory_handle handle, t_uint32 *pDspAddress); + +/*! + * \brief Return the adress of the DSP base associated to the memory type. + * Caution, this information is valid only in normal state (not when migrated). + * + * \param[in] coreId DSP Identifier. + * \param[in] memType Type of memory. + * \param[out] pAddr Base address. + * + * \retval t_cm_error + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_error cm_DSP_GetDspBaseAddress(t_nmf_core_id coreId, t_dsp_memory_type_id memType, t_cm_system_address *pAddr); + +/*! + * \brief Return DSP memory handle offset (offset inside a given DSP memory) + * + * \param[in] coreId dsp identifier. + * \param[in] memType Type of memory. + * \param[in] memHandle Allocated block handle + * + * \retval t_uint32: Offset of memory handle inside memory + * \ingroup DSP_INTERNAL + */ +PUBLIC t_uint32 cm_DSP_GetDspMemoryHandleOffset( + t_nmf_core_id coreId, + t_dsp_memory_type_id dspMemType, + t_memory_handle memHandle); + +/*! + * \brief Provide DSP memory handle size + * + * \param[in] memHandle Allocated block handle + * \param[out] pDspSize Size of the given memory handle + + * + * \retval t_cm_error + * \ingroup DSP_INTERNAL + */ +PUBLIC void cm_DSP_GetDspMemoryHandleSize(t_memory_handle memHandle, t_uint32 *pDspSize); + +/*! + * \brief Resize xram allocator to reserve spave for stack. + * + * \param[in] coreId dsp identifier. + * \param[in] newStackSize New required stack size. + + * + * \retval t_cm_error + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_error cm_DSP_setStackSize(t_nmf_core_id coreId, t_uint32 newStackSize); + +/*! + * \brief Allow to know if nbYramBanks parameter is valid for coreId. This api is need since use of nbYramBanks + * is deferred. + * + * \param[in] coreId dsp identifier. + * \param[in] nbYramBanks number of yramBanks to use. + * + * \retval t_cm_error + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_error cm_DSP_IsNbYramBanksValid(t_nmf_core_id coreId, t_uint8 nbYramBanks); + +/*! + * \brief Allow to know stack base address according to coreId and nbYramBanks use. + * + * \param[in] coreId dsp identifier. + * \param[in] nbYramBanks number of yramBanks to use. + * + * \retval t_uint32 return stack address + * \ingroup DSP_INTERNAL + */ +PUBLIC t_uint32 cm_DSP_getStackAddr(t_nmf_core_id coreId); + +/*! + * \brief For a give dsp adress return the offset from the hardware base that the adress is relative to. + * + * \param[in] coreId DSP identifier. + * \param[in] adr DSP address. + * \param[out] info Info structure containing (hw base id, offset) + * + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_error cm_DSP_GetDspDataAddressInfo(t_nmf_core_id coreId, t_uint32 adr, t_dsp_address_info *info); + +/*! + * \brief Modify the mapping of a code hardware base. Used for memory migration. + * + * The function calculates the new hardware base so that in the DSP address-space, + * the source address will be mapped to the destination address. + * + * \param[in] coreId DSP Identifier. + * \param[in] hwSegment Identifier of the hardware segment (thus hardware base). + * \param[in] src Source address + * \param[in] dst Destination address + * + * \retval t_cm_error + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_error cm_DSP_updateCodeBase(t_nmf_core_id coreId, t_dsp_segment_type hwSegment, t_cm_system_address src, t_cm_system_address dst); + +/*! + * \brief Modify the mapping of a data hardware base. Used for memory migration. + * + * The function calculates the new hardware base so that in the DSP address-space, + * the source address will be mapped to the destination address. + * + * \param[in] coreId DSP Identifier. + * \param[in] hwSegment Identifier of the hardware segment (thus hardware base). + * \param[in] src Source address + * \param[in] dst Destination address + * + * \retval t_cm_error + * \ingroup DSP_INTERNAL + */ +PUBLIC t_cm_error cm_DSP_updateDataBase(t_nmf_core_id coreId, t_dsp_segment_type hwSegment, t_cm_system_address src, t_cm_system_address dst); + +#endif /* __INC_CM_DSP_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/dsp/inc/semaphores_dsp.h b/drivers/staging/nmf-cm/cm/engine/dsp/inc/semaphores_dsp.h new file mode 100644 index 00000000000..1bb1c34cced --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/dsp/inc/semaphores_dsp.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef __INC_CM_SEMAPHORES_DSP_H +#define __INC_CM_SEMAPHORES_DSP_H + +#include <share/semaphores/inc/semaphores.h> +#include <cm/engine/dsp/inc/dsp.h> + +PUBLIC void cm_DSP_SEM_Take(t_nmf_core_id coreId, t_semaphore_id semId); +PUBLIC void cm_DSP_SEM_Give(t_nmf_core_id coreId, t_semaphore_id semId); +PUBLIC void cm_DSP_SEM_GenerateIrq(t_nmf_core_id coreId, t_semaphore_id semId); +PUBLIC void cm_DSP_AssertDspIrq(t_nmf_core_id coreId, t_host2mpc_irq_num irqNum); + +PUBLIC void cm_DSP_AcknowledgeDspIrq(t_nmf_core_id coreId, t_mpc2host_irq_num irqNum); + +#endif /* __INC_CM_SEMAPHORES_DSP_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/dsp/mmdsp/inc/mmdsp_hwp.h b/drivers/staging/nmf-cm/cm/engine/dsp/mmdsp/inc/mmdsp_hwp.h new file mode 100644 index 00000000000..0ddc71d2c4f --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/dsp/mmdsp/inc/mmdsp_hwp.h @@ -0,0 +1,959 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef __INC_MMDSP_HWP_H +#define __INC_MMDSP_HWP_H + +#include <cm/inc/cm_type.h> + +#define MMDSP_NB_BLOCK_RAM 8 +#define MMDSP_RAM_BLOCK_SIZE 4096 /* 0x1000 */ +#define MMDSP_NB_TIMER 3 +#define MMDSP_NB_BIT_SEM 8 +#define MMDSP_NB_DMA_IF 8 +#define MMDSP_NB_DMA_CTRL 4 +#define MMDSP_NB_ITREMAP_REG 32 + +#define MMDSP_INSTRUCTION_WORD_SIZE (sizeof(t_uint64)) +#define MMDSP_ICACHE_LINE_SIZE_IN_INST (4) +#define MMDSP_ICACHE_LINE_SIZE (MMDSP_ICACHE_LINE_SIZE_IN_INST * MMDSP_INSTRUCTION_WORD_SIZE) + +#define MMDSP_DATA_WORD_SIZE (3) +#define MMDSP_DATA_WORD_SIZE_IN_HOST_SPACE (sizeof(t_uint32)) +#define MMDSP_DATA_WORD_SIZE_IN_EXT24 (sizeof(t_uint32)) +#define MMDSP_DATA_WORD_SIZE_IN_EXT16 (sizeof(t_uint16)) +#define MMDSP_DCACHE_LINE_SIZE_IN_WORDS (8) +#define MMDSP_DCACHE_LINE_SIZE (MMDSP_DCACHE_LINE_SIZE_IN_WORDS * sizeof(t_uint32)) + +#define MMDSP_NB_IO 16 + +#define MMDSP_CODE_CACHE_WAY_SIZE 256 + +//#define MMDSP_ESRAM_DSP_BASE_ADDR 0xE0000 /* 64-bit words */ +//#define MMDSP_DATA24_DSP_BASE_ADDR 0x10000 +//#define MMDSP_DATA16_DSP_BASE_ADDR 0x800000 +//#define MMDSP_MMIO_DSP_BASE_ADDR 0xF80000 + +/* Specified according MMDSP & ELF convention */ +/* Note: Here we assume that ESRAM is less than 2MB */ +#define SDRAMTEXT_BASE_ADDR 0x00000000 +#define ESRAMTEXT_BASE_ADDR 0x000E0000 + +#define SDRAMMEM24_BASE_ADDR 0x00010000 +#define ESRAMMEM24_BASE_ADDR 0x00600000 /* ELF == 0x00400000 TODO: Update it in MMDSP ELF compiler */ +#define SDRAMMEM16_BASE_ADDR 0x00800000 +#define ESRAMMEM16_BASE_ADDR 0x00D80000 /* ELF == 0x00BC0000 TODO: Update it in MMDSP ELF compiler */ + +#define MMIO_BASE_ADDR 0x00F80000 + +/* + * Definition of indirect host registers + */ +#define IHOST_ICACHE_FLUSH_REG 0x0 +#define IHOST_ICACHE_FLUSH_CMD_ENABLE (t_uint64)MASK_BIT0 +#define IHOST_ICACHE_FLUSH_ALL_ENTRIES_CMD (t_uint64)0x0 +#if 0 +#define IHOST_ICACHE_INVALID_ALL_UNLOCKED_L2_LINES_CMD (t_uint64)0x8 +#define IHOST_ICACHE_INVALID_ALL_LOCKED_L2_LINES_CMD (t_uint64)0xA +#define IHOST_ICACHE_UNLOCK_ALL_LOCKED_L2_LINES_CMD (t_uint64)0xC +#define IHOST_ICACHE_LOCK_ALL_WAYS_LESSER_THAN_LOCK_V_CMD (t_uint64)0xE +#else +#define IHOST_ICACHE_INVALID_ALL_UNLOCKED_L2_LINES_CMD (t_uint64)0x10 +#define IHOST_ICACHE_INVALID_ALL_LOCKED_L2_LINES_CMD (t_uint64)0x12 +#define IHOST_ICACHE_UNLOCK_ALL_LOCKED_L2_LINES_CMD (t_uint64)0x14 +#define IHOST_ICACHE_LOCK_ALL_WAYS_LESSER_THAN_LOCK_V_CMD (t_uint64)0x16 +#define IHOST_ICACHE_FLUSH_BY_SERVICE (t_uint64)0x18 +#define IHOST_ICACHE_FLUSH_OUTSIDE_RANGE (t_uint64)0x1A +#endif + +#define IHOST_ICACHE_LOCK_V_REG 0x1 + +#define IHOST_ICACHE_MODE_REG 0x2 +#define IHOST_ICACHE_MODE_PERFMETER_ON (t_uint64)MASK_BIT0 +#define IHOST_ICACHE_MODE_PERFMETER_OFF (t_uint64)0x0 +#define IHOST_ICACHE_MODE_L2_CACHE_ON (t_uint64)MASK_BIT1 +#define IHOST_ICACHE_MODE_L2_CACHE_OFF (t_uint64)0x0 +#define IHOST_ICACHE_MODE_L1_CACHE_ON (t_uint64)MASK_BIT2 +#define IHOST_ICACHE_MODE_L1_CACHE_OFF (t_uint64)0x0 +#define IHOST_ICACHE_MODE_FILL_MODE_ON (t_uint64)MASK_BIT3 +#define IHOST_ICACHE_MODE_FILL_MODE_OFF (t_uint64)0x0 + +#define IHOST_CLEAR_PERFMETER_REG 0x3 +#define IHOST_CLEAR_PERFMETER_ON (t_uint64)0x1 +#define IHOST_CLEAR_PERFMETER_OFF (t_uint64)0x0 + +#define IHOST_PERF_HIT_STATUS_REG 0x4 + +#define IHOST_PERF_MISS_STATUS_REG 0x5 + +#define IHOST_FILL_START_WAY_REG 0x6 +#define IHOST_FILL_START_ADDR_VALUE_SHIFT 0U +#define IHOST_FILL_WAY_NUMBER_SHIFT 20U + +#define IHOST_PRG_BASE_ADDR_REG 0x7 +#define IHOST_PRG_BASE1_ADDR_SHIFT 0 +#define IHOST_PRG_BASE2_ADDR_SHIFT 32 + +#if defined(__STN_8500) && (__STN_8500>10) +#define IHOST_PRG_BASE_34_ADDR_REG 0x1A +#define IHOST_PRG_BASE3_ADDR_SHIFT 0 +#define IHOST_PRG_BASE4_ADDR_SHIFT 32 +#endif + +#if defined(__STN_8815) /* __STN_8815 */ +#define IHOST_PRG_AHB_CONF_REG 0x8 +#define IHOST_PRG_AHB_LOCKED_SHIFT 0U +#define IHOST_PRG_AHB_PROT_SHIFT 1U + +#define AHB_LOCKED_ON (t_uint64)1 +#define AHB_LOCKED_OFF (t_uint64)0 + +#define AHB_PROT_USER (t_uint64)0 +#define AHB_PROT_PRIVILEGED (t_uint64)MASK_BIT0 +#define AHB_PROT_NONBUFFERABLE (t_uint64)0 +#define AHB_PROT_BUFFERABLE (t_uint64)MASK_BIT1 +#define AHB_PROT_NONCACHEABLE (t_uint64)0 +#define AHB_PROT_CACHEABLE (t_uint64)MASK_BIT2 + + +#define IHOST_DATA_AHB_CONF_REG 0x9 +#define IHOST_DATA_AHB_LOCKED_SHIFT 0U +#define IHOST_DATA_AHB_PROT_SHIFT 1U +#else /* def __STN_8820 or __STN_8500 */ +#define IHOST_STBUS_ID_CONF_REG 0x8 +#define SAA_STBUS_ID 176 /* = 0xB0 */ +#define SVA_STBUS_ID 4 /* = 0x4 */ +#define SIA_STBUS_ID 180 /* = 0xB4 */ + +#define IHOST_STBUF_CONF_REG 0x9 /* RESERVED */ +#endif /* __STN_8820 or __STN_8500 */ + +#define IHOST_DATA_EXT_BUS_BASE_REG 0xA +#define IHOST_DATA_EXT_BUS_BASE_16_SHIFT 32ULL +#define IHOST_DATA_EXT_BUS_BASE_24_SHIFT 0ULL + +#define IHOST_EXT_MMIO_BASE_DATA_EXT_BUS_TOP_REG 0xB +#define IHOST_EXT_MMIO_DATA_EXT_BUS_TOP_SHIFT 0ULL +#define IHOST_EXT_MMIO_BASE_ADDR_SHIFT 32ULL + +#define IHOST_DATA_EXT_BUS_BASE2_REG 0xC +#define IHOST_DATA_EXT_BUS_BASE2_16_SHIFT 32ULL +#define IHOST_DATA_EXT_BUS_BASE2_24_SHIFT 0ULL + +#if defined(__STN_8500) && (__STN_8500>10) + +#define IHOST_DATA_EXT_BUS_BASE3_REG 0x1B +#define IHOST_DATA_EXT_BUS_BASE3_16_SHIFT 32ULL +#define IHOST_DATA_EXT_BUS_BASE3_24_SHIFT 0ULL + +#define IHOST_DATA_EXT_BUS_BASE4_REG 0x1C +#define IHOST_DATA_EXT_BUS_BASE4_16_SHIFT 32ULL +#define IHOST_DATA_EXT_BUS_BASE4_24_SHIFT 0ULL + +#endif + +#define IHOST_ICACHE_STATE_REG 0xD +#define IHOST_ICACHE_STATE_RESET 0x0 +#define IHOST_ICACHE_STATE_INITAGL2 0x1 +#define IHOST_ICACHE_STATE_READY_TO_START 0x2 +#define IHOST_ICACHE_STATE_WAIT_FOR_MISS 0x3 +#define IHOST_ICACHE_STATE_FILLDATARAM0 0x4 +#define IHOST_ICACHE_STATE_FILLDATARAM1 0x5 +#define IHOST_ICACHE_STATE_FILLDATARAM2 0x6 +#define IHOST_ICACHE_STATE_FILLDATARAM3 0x7 +#define IHOST_ICACHE_STATE_FLUSH 0x8 +#define IHOST_ICACHE_STATE_FILL_INIT 0x9 +#define IHOST_ICACHE_STATE_FILL_LOOP 0xA +#define IHOST_ICACHE_STATE_FILL_LOOP0 0xB +#define IHOST_ICACHE_STATE_FILL_LOOP1 0xC +#define IHOST_ICACHE_STATE_FILL_LOOP2 0xD +#define IHOST_ICACHE_STATE_FILL_LOOP3 0xE +#define IHOST_ICACHE_STATE_FILL_END 0xF +#define IHOST_ICACHE_STATE_SPECIFIC_FLUSH_R 0x10 +#define IHOST_ICACHE_STATE_SPECIFIC_FLUSH_W 0x11 +#define IHOST_ICACHE_STATE_SPECIFIC_FLUSH_END 0x12 +#define IHOST_ICACHE_STATE_OTHERS 0x1F + +#define IHOST_EN_EXT_BUS_TIMEOUT_REG 0xE +#define IHOST_TIMEOUT_ENABLE 1ULL +#define IHOST_TIMEOUT_DISABLE 0ULL + +#define IHOST_DATA2_1624_XA_BASE_REG 0xF +#define IHOST_DATA2_24_XA_BASE_SHIFT 0ULL +#define IHOST_DATA2_16_XA_BASE_SHIFT 32ULL +#if defined(__STN_8500) && (__STN_8500>10) +#define IHOST_DATA3_24_XA_BASE_SHIFT 8ULL +#define IHOST_DATA3_16_XA_BASE_SHIFT 40ULL +#define IHOST_DATA4_24_XA_BASE_SHIFT 16ULL +#define IHOST_DATA4_16_XA_BASE_SHIFT 48ULL +#endif + +#define IHOST_PERFMETERS_MODE_REG 0x10 + +#if defined(__STN_8815) /* __STN_8815 */ +#define IHOST_EXT_MMIO_AHB_CONF_REG 0x11 +#define IHOST_EXT_MMIO_AHB_LOCKED_SHIFT 0U +#define IHOST_EXT_MMIO_AHB_PROT_SHIFT 1U +#else /* def __STN_8820 or __STN_8500 */ +#define IHOST_EXT_MMIO_STBS_CONF_REG 0x11 /* RESERVED */ +#endif /* __STN_8820 or __STN_8500 */ + +#define IHOST_PRG_BASE_SEL_REG 0x12 +#define IHOST_PRG_BASE_SEL_OFF (t_uint64)0 +#define IHOST_PRG_BASE_SEL_ON (t_uint64)1 + +#define IHOST_PRG_BASE2_ACTIV_REG 0x13 +#define IHOST_PRG_BASE2_ACTIV_OFF (t_uint64)0 +#if defined(__STN_8500) && (__STN_8500>10) +/* TODO : for the moment just divide mmdsp in fix 4 spaces */ + #define IHOST_PRG_BASE2_ACTIV_ON (t_uint64)((((t_uint64)0xf0000>>10)<<48) | (((t_uint64)0xe0000>>10)<<32) | (((t_uint64)0x70000>>10)<<16) | 1) +#else + #define IHOST_PRG_BASE2_ACTIV_ON (t_uint64)1 +#endif + +#define IHOST_DATA_EXT_BUS_TOP_16_24_REG 0x14 +#define IHOST_DATA_EXT_BUS_TOP_24_SHIFT 0ULL +#define IHOST_DATA_EXT_BUS_TOP_16_SHIFT 32ULL + +#define IHOST_DATA_TOP_16_24_CHK_REG 0x16 +#define IHOST_DATA_TOP_16_24_CHK_OFF (t_uint64)0 +#define IHOST_DATA_TOP_16_24_CHK_ON (t_uint64)1 + +#define IHOST_EXT_BUS_TOP2_16_24_REG 0x15 +#define IHOST_DATA_EXT_BUS_TOP2_24_SHIFT 0ULL +#define IHOST_DATA_EXT_BUS_TOP2_16_SHIFT 32ULL + +#if defined(__STN_8500) && (__STN_8500>10) + +#define IHOST_EXT_BUS_TOP3_16_24_REG 0x1D +#define IHOST_DATA_EXT_BUS_TOP3_24_SHIFT 0ULL +#define IHOST_DATA_EXT_BUS_TOP3_16_SHIFT 32ULL + +#define IHOST_EXT_BUS_TOP4_16_24_REG 0x1E +#define IHOST_DATA_EXT_BUS_TOP4_24_SHIFT 0ULL +#define IHOST_DATA_EXT_BUS_TOP4_16_SHIFT 32ULL + +#endif + +#define IHOST_DATA_BASE2_ACTIV_REG 0x17 +#define IHOST_DATA_BASE2_ACTIV_OFF (t_uint64)0 +#define IHOST_DATA_BASE2_ACTIV_ON (t_uint64)1 + +#define IHOST_INST_BURST_SZ_REG 0x18 +#define IHOST_INST_BURST_SZ_ALWAYS_1_LINE (t_uint64)0x0 +#define IHOST_INST_BURST_SZ_ALWAYS_2_LINES (t_uint64)0x1 +#define IHOST_INST_BURST_SZ_AUTO (t_uint64)0x2 /* 2 lines for SDRAM [0, 0xE0000[, 1 line for ESRAM [0xE0000, 0xFFFFF] */ + +#define IHOST_ICACHE_END_CLEAR_REG 0x19 +#define IHOST_ICACHE_START_CLEAR_REG IHOST_FILL_START_WAY_REG + +/* + * Definition of value of the ucmd register + */ +#define MMDSP_UCMD_WRITE 0 +#define MMDSP_UCMD_READ 4 +#define MMDSP_UCMD_CTRL_STATUS_ACCESS 0x10 // (MASK_BIT4 | !MASK_BIT3 | !MASK_BIT0) +#define MMDSP_UCMD_DECREMENT_ADDR MASK_BIT5 +#define MMDSP_UCMD_INCREMENT_ADDR MASK_BIT1 + +/* + * Definition of value of the ubkcmd register + */ +#define MMDSP_UBKCMD_EXT_CODE_MEM_ACCESS_ENABLE MASK_BIT3 +#define MMDSP_UBKCMD_EXT_CODE_MEM_ACCESS_DISABLE 0 + +/* + * Definition of value of the clockcmd register + */ +#define MMDSP_CLOCKCMD_STOP_CLOCK MASK_BIT0 +#define MMDSP_CLOCKCMD_START_CLOCK 0 + +/* + * Definition of macros used to access indirect addressed host register + */ +#define WRITE_INDIRECT_HOST_REG(pRegs, addr, value64) \ +{ \ + (pRegs)->host_reg.emul_uaddrl = addr; \ + (pRegs)->host_reg.emul_uaddrm = 0; \ + (pRegs)->host_reg.emul_uaddrh = 0; \ + (pRegs)->host_reg.emul_udata[0] = ((value64 >> 0ULL) & MASK_BYTE0); \ + (pRegs)->host_reg.emul_udata[1] = ((value64 >> 8ULL) & MASK_BYTE0); \ + (pRegs)->host_reg.emul_udata[2] = ((value64 >> 16ULL) & MASK_BYTE0); \ + (pRegs)->host_reg.emul_udata[3] = ((value64 >> 24ULL) & MASK_BYTE0); \ + (pRegs)->host_reg.emul_udata[4] = ((value64 >> 32ULL) & MASK_BYTE0); \ + (pRegs)->host_reg.emul_udata[5] = ((value64 >> 40ULL) & MASK_BYTE0); \ + (pRegs)->host_reg.emul_udata[6] = ((value64 >> 48ULL) & MASK_BYTE0); \ + (pRegs)->host_reg.emul_udata[7] = ((value64 >> 56ULL) & MASK_BYTE0); \ + (pRegs)->host_reg.emul_ucmd = (MMDSP_UCMD_CTRL_STATUS_ACCESS | MMDSP_UCMD_WRITE); \ +} + +#define READ_INDIRECT_HOST_REG(pRegs, addr, value64) \ +{ \ + (pRegs)->host_reg.emul_udata[0] = 0; \ + (pRegs)->host_reg.emul_udata[1] = 0; \ + (pRegs)->host_reg.emul_udata[2] = 0; \ + (pRegs)->host_reg.emul_udata[3] = 0; \ + (pRegs)->host_reg.emul_udata[4] = 0; \ + (pRegs)->host_reg.emul_udata[5] = 0; \ + (pRegs)->host_reg.emul_udata[6] = 0; \ + (pRegs)->host_reg.emul_udata[7] = 0; \ + (pRegs)->host_reg.emul_uaddrl = addr; \ + (pRegs)->host_reg.emul_uaddrm = 0; \ + (pRegs)->host_reg.emul_uaddrh = 0; \ + (pRegs)->host_reg.emul_ucmd = (MMDSP_UCMD_CTRL_STATUS_ACCESS | MMDSP_UCMD_READ); \ + value64 = (((t_uint64)((pRegs)->host_reg.emul_udata[0])) << 0ULL) | \ + (((t_uint64)((pRegs)->host_reg.emul_udata[1])) << 8ULL) | \ + (((t_uint64)((pRegs)->host_reg.emul_udata[2])) << 16ULL) | \ + (((t_uint64)((pRegs)->host_reg.emul_udata[3])) << 24ULL) | \ + (((t_uint64)((pRegs)->host_reg.emul_udata[4])) << 32ULL) | \ + (((t_uint64)((pRegs)->host_reg.emul_udata[5])) << 40ULL) | \ + (((t_uint64)((pRegs)->host_reg.emul_udata[6])) << 48ULL) | \ + (((t_uint64)((pRegs)->host_reg.emul_udata[7])) << 56ULL); \ +} + +/* Common type to handle 64-bit modulo field in 32-bit mode */ +typedef struct { + t_uint32 value; + t_uint32 dummy; +} t_mmdsp_field_32; + +typedef struct { + t_uint16 value; + t_uint16 dummy; +} t_mmdsp_field_16; + +/* DCache registers */ +#define DCACHE_MODE_ENABLE MASK_BIT0 +#define DCACHE_MODE_DISABLE 0 +#define DCACHE_MODE_DIVIDE_PER_2 MASK_BIT1 +#define DCACHE_MODE_DIVIDE_PER_4 MASK_BIT2 +#define DCACHE_MODE_CHECK_TAG_ENABLE MASK_BIT3 +#define DCACHE_MODE_CHECK_TAG_DISABLE 0 +#define DCACHE_MODE_FORCE_LOCK_MODE MASK_BIT4 +#define DCACHE_MODE_LOCK_BIT MASK_BIT5 + +#define DCACHE_CONTROL_PREFETCH_LINE MASK_BIT0 +#define DCACHE_CONTROL_NON_BLOCKING_REFILL 0 +#define DCACHE_CONTROL_FAST_READ_DISABLE MASK_BIT1 +#define DCACHE_CONTROL_FAST_READ_ENABLE 0 +#define DCACHE_CONTROL_ON_FLY_FILL_ACCESS_OFF MASK_BIT2 +#define DCACHE_CONTROL_ON_FLY_FILL_ACCESS_ON 0 +#define DCACHE_CONTROL_BURST_1_WRAP8 MASK_BIT3 +#define DCACHE_CONTROL_BURST_2_WRAP4 0 +#define DCACHE_CONTROL_NOT_USE_DATA_BUFFER MASK_BIT4 +#define DCACHE_CONTROL_USE_DATA_BUFFER 0 +#define DCACHE_CONTROL_WRITE_POSTING_ENABLE MASK_BIT5 +#define DCACHE_CONTROL_WRITE_POSTING_DISABLE 0 + +#define DCACHE_CMD_NOP 0 +#define DCACHE_CMD_DISCARD_WAY 2 //see Dcache_way reg +#define DCACHE_CMD_DISCARD_LINE 3 //see Dcache_line reg +#define DCACHE_CMD_FREE_WAY 4 //see Dcache_way reg +#define DCACHE_CMD_FREE_LINE 5 //see Dchache_line reg +#define DCACHE_CMD_FLUSH 7 + +#define DCACHE_STATUS_CURRENT_WAY_MASK (MASK_BIT2 | MASK_BIT1 | MASK_BIT0) +#define DCACHE_STATUS_TAG_HIT_MASK MASK_BIT3 +#define DCACHE_STATUS_TAG_LOCKED_MASK MASK_BIT4 +#define DCACHE_STATUS_PROTECTION_ERROR_MASK MASK_BIT5 + +#define DCACHE_CPTRSEL_COUNTER_1_MASK (MASK_BIT3 | MASK_BIT2 | MASK_BIT1 | MASK_BIT0) +#define DCACHE_CPTRSEL_COUNTER_1_SHIFT 0 +#define DCACHE_CPTRSEL_COUNTER_2_MASK (MASK_BIT7 | MASK_BIT6 | MASK_BIT5 | MASK_BIT4) +#define DCACHE_CPTRSEL_COUNTER_2_SHIFT 4 +#define DCACHE_CPTRSEL_COUNTER_3_MASK (MASK_BIT11 | MASK_BIT10 | MASK_BIT9 | MASK_BIT8) +#define DCACHE_CPTRSEL_COUNTER_3_SHIFT 8 +#define DCACHE_CPTRSEL_XBUS_ACCESS_TO_CACHE_RAM 1 +#define DCACHE_CPTRSEL_CACHE_HIT 2 +#define DCACHE_CPTRSEL_LINE_MATCH 3 +#define DCACHE_CPTRSEL_XBUS_WS 4 +#define DCACHE_CPTRSEL_EXTMEM_WS 5 +#define DCACHE_CPTRSEL_CACHE_READ 6 +#define DCACHE_CPTRSEL_CACHE_WRITE 7 +#define DCACHE_CPTRSEL_TAG_HIT_READ 8 +#define DCACHE_CPTRSEL_TAG_LOCKED_ACCESS 9 +#define DCACHE_CPTRSEL_TAG_MEM_READ_CYCLE 10 +#define DCACHE_CPTRSEL_TAG_MEM_WRITE_CYCLE 11 + + +typedef volatile struct { + t_uint16 padding_1[5]; + t_uint16 mode; + t_uint16 control; + t_uint16 way; + t_uint16 line; + t_uint16 command; + t_uint16 status; + t_uint16 cptr1l; + t_uint16 cptr1h; + t_uint16 cptr2l; + t_uint16 cptr2h; + t_uint16 cptr3l; + t_uint16 cptr3h; + t_uint16 cptrsel; + t_uint16 flush_base_lsb; /* only on STn8820 and STn8500 */ + t_uint16 flush_base_msb; /* only on STn8820 and STn8500 */ + t_uint16 flush_top_lsb; /* only on STn8820 and STn8500 */ + t_uint16 flush_top_msb; /* only on STn8820 and STn8500 */ + t_uint16 padding_2[10]; +} t_mmdsp_dcache_regs_16; + +typedef volatile struct { + t_uint32 padding_1[5]; + t_uint32 mode; + t_uint32 control; + t_uint32 way; + t_uint32 line; + t_uint32 command; + t_uint32 status; + t_uint32 cptr1l; + t_uint32 cptr1h; + t_uint32 cptr2l; + t_uint32 cptr2h; + t_uint32 cptr3l; + t_uint32 cptr3h; + t_uint32 cptrsel; + t_uint32 flush_base_lsb; /* only on STn8820 and STn8500 */ + t_uint32 flush_base_msb; /* only on STn8820 and STn8500 */ + t_uint32 flush_top_lsb; /* only on STn8820 and STn8500 */ + t_uint32 flush_top_msb; /* only on STn8820 and STn8500 */ + t_uint32 padding_2[10]; +} t_mmdsp_dcache_regs_32; + +/* TIMER Registers */ +typedef volatile struct { + t_mmdsp_field_16 timer_msb; + t_mmdsp_field_16 timer_lsb; +} t_mmdsp_timer_regs_16; + +typedef volatile struct { + t_mmdsp_field_32 timer_msb; + t_mmdsp_field_32 timer_lsb; +} t_mmdsp_timer_regs_32; + + +/* DMA interface Registers */ +typedef volatile struct { + t_uint16 arm_dma_sreq; /* dma0: 5e800, dma1: +0x20 ...*/ + t_uint16 arm_dma_breq; /* ... 5e802 */ + t_uint16 arm_dma_lsreq; /* ... 5e804 */ + t_uint16 arm_dma_lbreq; + t_uint16 arm_dma_maskit; + t_uint16 arm_dma_it; + t_uint16 arm_dma_auto; + t_uint16 arm_dma_lauto; + t_uint16 dma_reserved[8]; +} t_mmdsp_dma_if_regs_16; + +typedef volatile struct { + t_uint32 arm_dma_sreq; /* dma0: 3a800, dma1: +0x40 ...*/ + t_uint32 arm_dma_breq; /* ... 3a804 */ + t_uint32 arm_dma_lsreq; /* ... 3a808 */ + t_uint32 arm_dma_lbreq; + t_uint32 arm_dma_maskit; + t_uint32 arm_dma_it; + t_uint32 arm_dma_auto; + t_uint32 arm_dma_lauto; + t_uint32 dma_reserved[8]; +} t_mmdsp_dma_if_regs_32; + +/* MMDSP DMA controller Registers */ +typedef volatile struct { + t_uint16 dma_ctrl; /* dma0: 0x5d400, dma1: +0x10 ... */ + t_uint16 dma_int_base; /* ... 0x5d402 */ + t_uint16 dma_int_length; /* ... 0x5d404 */ + t_uint16 dma_ext_baseh; + t_uint16 dma_ext_basel; + t_uint16 dma_count; + t_uint16 dma_ext_length; + t_uint16 dma_it_status; +} t_mmdsp_dma_ctrl_regs_16; + +typedef volatile struct { + t_uint32 dma_ctrl; /* dma0: 0x3a800, dma1: +0x20 ... */ + t_uint32 dma_int_base; /* ... 0x3a804 */ + t_uint32 dma_int_length; /* ... 0x3a808 */ + t_uint32 dma_ext_baseh; + t_uint32 dma_ext_basel; + t_uint32 dma_count; + t_uint32 dma_ext_length; + t_uint32 dma_it_status; +} t_mmdsp_dma_ctrl_regs_32; + +/* IO registers */ +typedef volatile struct { + t_mmdsp_field_16 io_bit[MMDSP_NB_IO]; + t_mmdsp_field_16 io_lsb; + t_mmdsp_field_16 io_msb; + t_mmdsp_field_16 io_all; + t_mmdsp_field_16 io_en; +} t_mmdsp_io_regs_16; + +typedef volatile struct { + t_mmdsp_field_32 io_bit[MMDSP_NB_IO]; + t_mmdsp_field_32 io_lsb; + t_mmdsp_field_32 io_msb; + t_mmdsp_field_32 io_all; + t_mmdsp_field_32 io_en; +} t_mmdsp_io_regs_32; + +/* HOST Registers bit mapping */ +#define HOST_GATEDCLK_ITREMAP MASK_BIT0 +#define HOST_GATEDCLK_SYSDMA MASK_BIT1 +#define HOST_GATEDCLK_INTEG_REGS MASK_BIT2 +#define HOST_GATEDCLK_TIMER_GPIO MASK_BIT3 +#define HOST_GATEDCLK_XBUSDMA MASK_BIT4 +#define HOST_GATEDCLK_STACKCTRL MASK_BIT5 +#define HOST_GATEDCLK_ITC MASK_BIT6 + +/* Only for STn8820 and STn8500 */ +#define HOST_PWR_DBG_MODE MASK_BIT0 +#define HOST_PWR_DC_STATUS (MASK_BIT1 | MASK_BIT2 | MASK_BIT3 | MASK_BIT4 | MASK_BIT5) +#define HOST_PWR_DE_STATUS MASK_BIT6 +#define HOST_PWR_STOV_STATUS MASK_BIT7 + +/* HOST Registers */ +typedef volatile struct { + t_uint16 ident; /*0x...60000*/ + t_uint16 identx[4]; /*0x...60002..8*/ + t_uint16 r5; /*0x...6000a*/ + t_uint16 r6; /*0x...6000c*/ + t_uint16 inte[2]; /*0x...6000e..10*/ + t_uint16 intx[2]; /*0x...60012..14*/ + t_uint16 int_ris[2]; /*0x...60016..18*/ + t_uint16 intpol; /*0x...6001a*/ + t_uint16 pwr; /*0x...6001c*/ /* only on STn8820 and STn8500 */ + t_uint16 gatedclk; /*0x...6001e*/ + t_uint16 softreset; /*0x...60020*/ + t_uint16 int_icr[2]; /*0x...60022..24*/ + t_uint16 cmd[4]; /*0x...60026..2c*/ + t_uint16 RESERVED4; + t_uint16 int_mis0; /*0x...60030*/ + t_uint16 RESERVED5; + t_uint16 RESERVED6; + t_uint16 RESERVED7; + t_uint16 i2cdiv; /*0x...60038*/ + t_uint16 int_mis1; /*0x...6003a*/ + t_uint16 RESERVED8; + t_uint16 RESERVED9; + t_uint16 emul_udata[8]; /*0x...60040..4e*/ + t_uint16 emul_uaddrl; /*0x...60050*/ + t_uint16 emul_uaddrm; /*0x...60052*/ + t_uint16 emul_ucmd; /*0x...60054*/ + t_uint16 emul_ubkcmd; /*0x...60056*/ + t_uint16 emul_bk2addl; /*0x...60058*/ + t_uint16 emul_bk2addm; /*0x...6005a*/ + t_uint16 emul_bk2addh; /*0x...6005c*/ + t_uint16 emul_mdata[3]; /*0x...6005e..62*/ + t_uint16 emul_maddl; /*0x...60064*/ + t_uint16 emul_maddm; /*0x...60066*/ + t_uint16 emul_mcmd; /*0x...60068*/ + t_uint16 emul_maddh; /*0x...6006a*/ + t_uint16 emul_uaddrh; /*0x...6006c*/ + t_uint16 emul_bk_eql; /*0x...6006e*/ + t_uint16 emul_bk_eqh; /*0x...60070*/ + t_uint16 emul_bk_combi; /*0x...60072*/ + t_uint16 emul_clockcmd; /*0x...60074*/ + t_uint16 emul_stepcmd; /*0x...60076*/ + t_uint16 emul_scanreg; /*0x...60078*/ + t_uint16 emul_breakcountl; /*0x...6007a*/ + t_uint16 emul_breakcounth; /*0x...6007c*/ + t_uint16 emul_forcescan; /*0x...6007e*/ + t_uint16 user_area[(0x200 - 0x80)>>1]; +} t_mmdsp_host_regs_16; + +typedef volatile struct { + t_uint32 ident; /*0x...60000*/ + t_uint32 identx[4]; /*0x...60004..10*/ + t_uint32 r5; /*0x...60014*/ + t_uint32 r6; /*0x...60018*/ + t_uint32 inte[2]; /*0x...6001c..20*/ + t_uint32 intx[2]; /*0x...60024..28*/ + t_uint32 int_ris[2]; /*0x...6002c..30*/ + t_uint32 intpol; /*0x...60034*/ + t_uint32 pwr; /*0x...60038*/ /* only on STn8820 and STn8500 */ + t_uint32 gatedclk; /*0x...6003c*/ + t_uint32 softreset; /*0x...60040*/ + t_uint32 int_icr[2]; /*0x...60044..48*/ + t_uint32 cmd[4]; /*0x...6004c..58*/ + t_uint32 RESERVED4; + t_uint32 int_mis0; /*0x...60060*/ + t_uint32 RESERVED5; + t_uint32 RESERVED6; + t_uint32 RESERVED7; + t_uint32 i2cdiv; /*0x...60070*/ + t_uint32 int_mis1; /*0x...60074*/ + t_uint32 RESERVED8; + t_uint32 RESERVED9; + t_uint32 emul_udata[8]; /*0x...60080..9c*/ + t_uint32 emul_uaddrl; /*0x...600a0*/ + t_uint32 emul_uaddrm; /*0x...600a4*/ + t_uint32 emul_ucmd; /*0x...600a8*/ + t_uint32 emul_ubkcmd; /*0x...600ac*/ + t_uint32 emul_bk2addl; /*0x...600b0*/ + t_uint32 emul_bk2addm; /*0x...600b4*/ + t_uint32 emul_bk2addh; /*0x...600b8*/ + t_uint32 emul_mdata[3]; /*0x...600bc..c4*/ + t_uint32 emul_maddl; /*0x...600c8*/ + t_uint32 emul_maddm; /*0x...600cc*/ + t_uint32 emul_mcmd; /*0x...600d0*/ + t_uint32 emul_maddh; /*0x...600d4*/ + t_uint32 emul_uaddrh; /*0x...600d8*/ + t_uint32 emul_bk_eql; /*0x...600dc*/ + t_uint32 emul_bk_eqh; /*0x...600e0*/ + t_uint32 emul_bk_combi; /*0x...600e4*/ + t_uint32 emul_clockcmd; /*0x...600e8*/ + t_uint32 emul_stepcmd; /*0x...600ec*/ + t_uint32 emul_scanreg; /*0x...600f0*/ + t_uint32 emul_breakcountl; /*0x...600f4*/ + t_uint32 emul_breakcounth; /*0x...600f8*/ + t_uint32 emul_forcescan; /*0x...600fc*/ + t_uint32 user_area[(0x400 - 0x100)>>2]; +} t_mmdsp_host_regs_32; + +/* MMIO blocks */ +#if defined(__STN_8820) || defined(__STN_8500) +typedef volatile struct { + t_uint16 RESERVED1[(0xD400-0x8000)>>1]; + + t_mmdsp_dma_ctrl_regs_16 dma_ctrl[MMDSP_NB_DMA_CTRL]; + + t_uint16 RESERVED2[(0xD800-0xD440)>>1]; + + t_mmdsp_dcache_regs_16 dcache; + + t_uint16 RESERVED3[(0xE000-0xD840)>>1]; + + t_mmdsp_io_regs_16 io; + + t_uint16 RESERVED4[(0x60-0x50)>>1]; + + t_mmdsp_timer_regs_16 timer[MMDSP_NB_TIMER]; + + t_uint16 RESERVED5[(0x410-0x78)>>1]; + + t_mmdsp_field_16 sem[MMDSP_NB_BIT_SEM]; + + t_uint16 RESERVED6[(0x450-0x430)>>1]; + + t_mmdsp_field_16 ipen; + t_uint16 itip_0; + t_uint16 itip_1; + t_uint16 itip_2; + t_uint16 itip_3; + t_uint16 itop_0; + t_uint16 itop_1; + t_uint16 itop_2; + t_uint16 itop_3; + t_uint16 RESERVED7[(0x8a-0x64)>>1]; + t_uint16 itip_4; + t_uint16 itop_4; + + t_uint16 RESERVED8[(0x7e0-0x48e)>>1]; + + t_mmdsp_field_16 id[4]; + t_mmdsp_field_16 idp[4]; + + t_mmdsp_dma_if_regs_16 dma_if[MMDSP_NB_DMA_IF]; + + t_uint16 RESERVED9[(0xC00-0x900)>>1]; + + t_mmdsp_field_16 emu_unit_maskit; + t_mmdsp_field_16 RESERVED[3]; + t_mmdsp_field_16 config_data_mem; + t_mmdsp_field_16 compatibility; + + t_uint16 RESERVED10[(0xF000-0xEC18)>>1]; + + t_uint16 stbus_if_config; + t_uint16 stbus_if_mode; + t_uint16 stbus_if_status; + t_uint16 stbus_if_security; + t_uint16 stbus_if_flush; + t_uint16 stbus_reserved; + t_uint16 stbus_if_priority; + t_uint16 stbus_msb_attribut; + + t_uint16 RESERVED11[(0xFC00-0xF010)>>1]; + + t_mmdsp_field_16 itremap_reg[MMDSP_NB_ITREMAP_REG]; + t_mmdsp_field_16 itmsk_l_reg; + t_mmdsp_field_16 itmsk_h_reg; + + t_uint16 RESERVED12[(0xfc9c - 0xfc88)>>1]; + + t_mmdsp_field_16 itmemo_l_reg; + t_mmdsp_field_16 itmeme_h_reg; + + t_uint16 RESERVED13[(0xfd00 - 0xfca4)>>1]; + + t_mmdsp_field_16 itremap1_reg[MMDSP_NB_ITREMAP_REG]; + + t_uint16 RESERVED14[(0x60000 - 0x5fd80)>>1]; +} t_mmdsp_mmio_regs_16; + + +typedef volatile struct { + t_uint32 RESERVED1[(0xa800)>>2]; + + t_mmdsp_dma_ctrl_regs_32 dma_ctrl[MMDSP_NB_DMA_CTRL]; + + t_uint32 RESERVED2[(0xb000-0xa880)>>2]; + + t_mmdsp_dcache_regs_32 dcache; + + t_uint32 RESERVED3[(0xc000-0xb080)>>2]; + + t_mmdsp_io_regs_32 io; + + t_uint32 RESERVED4[(0xc0-0xa0)>>2]; + + t_mmdsp_timer_regs_32 timer[MMDSP_NB_TIMER]; + + t_uint32 RESERVED5[(0x820-0x0f0)>>2]; + + t_mmdsp_field_32 sem[MMDSP_NB_BIT_SEM]; + + t_uint32 RESERVED6[(0x8a0-0x860)>>2]; + + t_mmdsp_field_32 ipen; + t_uint32 itip_0; + t_uint32 itip_1; + t_uint32 itip_2; + t_uint32 itip_3; + t_uint32 itop_0; + t_uint32 itop_1; + t_uint32 itop_2; + t_uint32 itop_3; + t_uint32 RESERVED7[(0x914-0x8c8)>>2]; + t_uint32 itip_4; + t_uint32 itop_4; + + t_uint32 RESERVED8[(0xcfc0-0xc91c)>>2]; + + t_mmdsp_field_32 id[4]; + t_mmdsp_field_32 idp[4]; + + t_mmdsp_dma_if_regs_32 dma_if[MMDSP_NB_DMA_IF]; + + t_uint32 RESERVED9[(0x800-0x200)>>2]; + + t_mmdsp_field_32 emu_unit_maskit; + t_mmdsp_field_32 RESERVED[3]; + t_mmdsp_field_32 config_data_mem; + t_mmdsp_field_32 compatibility; + + t_uint32 RESERVED10[(0xE000-0xD830)>>2]; + + t_uint32 stbus_if_config; + t_uint32 stbus_if_mode; + t_uint32 stbus_if_status; + t_uint32 stbus_if_security; + t_uint32 stbus_if_flush; + t_uint32 stbus_reserved; + t_uint32 stbus_if_priority; + t_uint32 stbus_msb_attribut; + + t_uint32 RESERVED11[(0xF800-0xE020)>>2]; + + t_mmdsp_field_32 itremap_reg[MMDSP_NB_ITREMAP_REG]; + t_mmdsp_field_32 itmsk_l_reg; + t_mmdsp_field_32 itmsk_h_reg; + + t_uint32 RESERVED12[(0xf938 - 0xf910)>>2]; + + t_mmdsp_field_32 itmemo_l_reg; + t_mmdsp_field_32 itmeme_h_reg; + + t_uint32 RESERVED13[(0xfa00 - 0xf948)>>2]; + + t_mmdsp_field_32 itremap1_reg[MMDSP_NB_ITREMAP_REG]; + + t_uint32 RESERVED14[(0x40000 - 0x3fb00)>>2]; +} t_mmdsp_mmio_regs_32; +#endif /* __STN_8820 or __STN_8500 */ + +#ifdef __STN_8815 +typedef volatile struct { + t_uint16 RESERVED1[(0xD400-0x8000)>>1]; + + t_mmdsp_dma_ctrl_regs_16 dma_ctrl[MMDSP_NB_DMA_CTRL]; + + t_uint16 RESERVED2[(0xD800-0xD440)>>1]; + + t_mmdsp_dcache_regs_16 dcache; + + t_uint16 RESERVED3[(0xE000-0xD840)>>1]; + + t_mmdsp_io_regs_16 io; + + t_uint16 RESERVED4[(0x60-0x50)>>1]; + + t_mmdsp_timer_regs_16 timer[MMDSP_NB_TIMER]; + + t_uint16 RESERVED5[(0x410-0x78)>>1]; + + t_mmdsp_field_16 sem[MMDSP_NB_BIT_SEM]; + + t_uint16 RESERVED6[(0x450-0x430)>>1]; + + t_mmdsp_field_16 ipen; + t_uint16 itip_0; + t_uint16 itip_1; + t_uint16 itip_2; + t_uint16 itip_3; + t_uint16 itop_0; + t_uint16 itop_1; + t_uint16 itop_2; + t_uint16 itop_3; + t_uint16 RESERVED7[(0x8a-0x64)>>1]; + t_uint16 itip_4; + t_uint16 itop_4; + + t_uint16 RESERVED8[(0x7e0-0x48e)>>1]; + + t_mmdsp_field_16 id[4]; + t_mmdsp_field_16 idp[4]; + + t_mmdsp_dma_if_regs_16 dma_if[MMDSP_NB_DMA_IF]; + + t_uint16 RESERVED9[(0xC00-0x900)>>1]; + + t_mmdsp_field_16 emu_unit_maskit; + t_mmdsp_field_16 RESERVED[3]; + t_mmdsp_field_16 config_data_mem; + t_mmdsp_field_16 compatibility; + + t_uint16 RESERVED10[(0xF000-0xEC18)>>1]; + + t_uint16 ahb_if_config; + t_uint16 ahb_if_mode; + t_uint16 ahb_if_status; + t_uint16 ahb_if_security; + t_uint16 ahb_if_flush; + + t_uint16 RESERVED11[(0xFC00-0xF00A)>>1]; + + t_mmdsp_field_16 itremap_reg[MMDSP_NB_ITREMAP_REG]; + t_mmdsp_field_16 itmsk_l_reg; + t_mmdsp_field_16 itmsk_h_reg; + + t_uint16 RESERVED12[(0xfc9c - 0xfc88)>>1]; + + t_mmdsp_field_16 itmemo_l_reg; + t_mmdsp_field_16 itmeme_h_reg; + + t_uint16 RESERVED13[(0xfd00 - 0xfca4)>>1]; + + t_mmdsp_field_16 itremap1_reg[MMDSP_NB_ITREMAP_REG]; + + t_uint16 RESERVED14[(0x60000 - 0x5fd80)>>1]; +} t_mmdsp_mmio_regs_16; + + +typedef volatile struct { + t_uint32 RESERVED1[(0xa800)>>2]; + + t_mmdsp_dma_ctrl_regs_32 dma_ctrl[MMDSP_NB_DMA_CTRL]; + + t_uint32 RESERVED2[(0xb000-0xa880)>>2]; + + t_mmdsp_dcache_regs_32 dcache; + + t_uint32 RESERVED3[(0xc000-0xb080)>>2]; + + t_mmdsp_io_regs_32 io; + + t_uint32 RESERVED4[(0xc0-0xa0)>>2]; + + t_mmdsp_timer_regs_32 timer[MMDSP_NB_TIMER]; + + t_uint32 RESERVED5[(0x820-0x0f0)>>2]; + + t_mmdsp_field_32 sem[MMDSP_NB_BIT_SEM]; + + t_uint32 RESERVED6[(0x8a0-0x860)>>2]; + + t_mmdsp_field_32 ipen; + t_uint32 itip_0; + t_uint32 itip_1; + t_uint32 itip_2; + t_uint32 itip_3; + t_uint32 itop_0; + t_uint32 itop_1; + t_uint32 itop_2; + t_uint32 itop_3; + t_uint32 RESERVED7[(0x914-0x8c8)>>2]; + t_uint32 itip_4; + t_uint32 itop_4; + + t_uint32 RESERVED8[(0xcfc0-0xc91c)>>2]; + + t_mmdsp_field_32 id[4]; + t_mmdsp_field_32 idp[4]; + + t_mmdsp_dma_if_regs_32 dma_if[MMDSP_NB_DMA_IF]; + + t_uint32 RESERVED9[(0x800-0x200)>>2]; + + t_mmdsp_field_32 emu_unit_maskit; + t_mmdsp_field_32 RESERVED[3]; + t_mmdsp_field_32 config_data_mem; + t_mmdsp_field_32 compatibility; + + t_uint32 RESERVED10[(0xE000-0xD830)>>2]; + + t_uint32 ahb_if_config; + t_uint32 ahb_if_mode; + t_uint32 ahb_if_status; + t_uint32 ahb_if_security; + t_uint32 ahb_if_flush; + + t_uint32 RESERVED11[(0xF800-0xE014)>>2]; + + t_mmdsp_field_32 itremap_reg[MMDSP_NB_ITREMAP_REG]; + t_mmdsp_field_32 itmsk_l_reg; + t_mmdsp_field_32 itmsk_h_reg; + + t_uint32 RESERVED12[(0xf938 - 0xf910)>>2]; + + t_mmdsp_field_32 itmemo_l_reg; + t_mmdsp_field_32 itmeme_h_reg; + + t_uint32 RESERVED13[(0xfa00 - 0xf948)>>2]; + + t_mmdsp_field_32 itremap1_reg[MMDSP_NB_ITREMAP_REG]; + + t_uint32 RESERVED14[(0x40000 - 0x3fb00)>>2]; +} t_mmdsp_mmio_regs_32; +#endif /* __STN_8815 */ + +/* Smart xx Accelerator memory map */ +typedef volatile struct { + t_uint32 mem24[MMDSP_NB_BLOCK_RAM*MMDSP_RAM_BLOCK_SIZE]; /* 0x0000 -> 0x20000 */ + + t_uint32 RESERVED1[(0x30000 - 0x20000)>>2]; + + t_mmdsp_mmio_regs_32 mmio_32; + + t_uint16 mem16[MMDSP_NB_BLOCK_RAM*MMDSP_RAM_BLOCK_SIZE]; /* 0x40000 -> 0x50000 */ + + t_uint32 RESERVED2[(0x58000 - 0x50000)>>2]; + + t_mmdsp_mmio_regs_16 mmio_16; + + t_mmdsp_host_regs_16 host_reg; + /* + union host_reg { + t_mmdsp_host_regs_16 reg16; + t_mmdsp_host_regs_32 reg32; + }; + */ +} t_mmdsp_hw_regs; + +#endif // __INC_MMDSP_HWP_H diff --git a/drivers/staging/nmf-cm/cm/engine/dsp/mmdsp/inc/mmdsp_macros.h b/drivers/staging/nmf-cm/cm/engine/dsp/mmdsp/inc/mmdsp_macros.h new file mode 100644 index 00000000000..b8911d27609 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/dsp/mmdsp/inc/mmdsp_macros.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef __INC_MMDSP_DSP_MACROS +#define __INC_MMDSP_DSP_MACROS + +#include <cm/engine/dsp/mmdsp/inc/mmdsp_hwp.h> + +#define MMDSP_ENABLE_WRITE_POSTING(pRegs) \ +{ \ + (pRegs)->mmio_16.dcache.control |= DCACHE_CONTROL_WRITE_POSTING_ENABLE; \ +} + +#define MMDSP_FLUSH_DCACHE(pRegs) \ +{ /* Today, only full cache flush (clear all the ways) */ \ + (pRegs)->mmio_16.dcache.command = DCACHE_CMD_FLUSH; \ +} + +#define MMDSP_FLUSH_DCACHE_BY_SERVICE(pRegs, startAddr, endAddr) + +#define MMDSP_FLUSH_ICACHE(pRegs) \ +{ /* Flush the Instruction cache */ \ + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_ICACHE_FLUSH_REG, (IHOST_ICACHE_FLUSH_ALL_ENTRIES_CMD | IHOST_ICACHE_FLUSH_CMD_ENABLE)); \ +} + +#ifndef __STN_8810 +#define MMDSP_FLUSH_ICACHE_BY_SERVICE(pRegs, startAddr, endAddr) \ +{ /* Flush the Instruction cache by service */ \ + /*t_uint64 start_clear_addr = startAddr & ~(MMDSP_ICACHE_LINE_SIZE_IN_INST - 1);*/ \ + t_uint64 start_clear_addr = (startAddr)>>2; \ + t_uint64 end_clear_addr = ((endAddr) + MMDSP_ICACHE_LINE_SIZE_IN_INST) & ~(MMDSP_ICACHE_LINE_SIZE_IN_INST - 1); \ + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_ICACHE_START_CLEAR_REG, start_clear_addr); \ + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_ICACHE_END_CLEAR_REG, end_clear_addr); \ + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_ICACHE_FLUSH_REG, (IHOST_ICACHE_FLUSH_BY_SERVICE | IHOST_ICACHE_FLUSH_CMD_ENABLE)); \ +} +#else +#define MMDSP_FLUSH_ICACHE_BY_SERVICE(pRegs, startAddr, endAddr) {(void)pRegs; (void)startAddr; (void)endAddr; } +#endif + +#define MMDSP_RESET_CORE(pRegs) \ +{ /* Assert DSP core soft reset */ \ + (pRegs)->host_reg.softreset = 1; \ +} + +#define MMDSP_START_CORE(pRegs) \ +{ \ + /* Enable external memory access (set bit 3 of ubkcmd) */ \ + (pRegs)->host_reg.emul_ubkcmd |= MMDSP_UBKCMD_EXT_CODE_MEM_ACCESS_ENABLE; \ + \ + /* Start core clock */ \ + (pRegs)->host_reg.emul_clockcmd = MMDSP_CLOCKCMD_START_CLOCK; \ +} + +#define MMDSP_STOP_CORE(pRegs) \ +{ \ + /* Disable external memory access (reset bit 3 of ubkcmd) */ \ + (pRegs)->host_reg.emul_ubkcmd = MMDSP_UBKCMD_EXT_CODE_MEM_ACCESS_DISABLE; \ + \ + /* Stop core clock */ \ + (pRegs)->host_reg.emul_clockcmd = MMDSP_CLOCKCMD_STOP_CLOCK; \ +} + +#define MMDSP_ASSERT_IRQ(pRegs, irqNum) \ +{ \ + (pRegs)->host_reg.cmd[irqNum] = 1; \ +} + +#define MMDSP_ACKNOWLEDGE_IRQ(pRegs, irqNum) \ +{ \ + volatile t_uint16 dummy; \ + dummy =(pRegs)->host_reg.intx[irqNum]; \ +} + +#define MMDSP_WRITE_XWORD(pRegs, offset, value) \ +{ \ + (pRegs)->mem24[offset] = value; \ +} + +#define MMDSP_READ_XWORD(pRegs, offset) (pRegs)->mem24[offset] + +#endif /* __INC_MMDSP_DSP_MACROS */ diff --git a/drivers/staging/nmf-cm/cm/engine/dsp/src/dsp.c b/drivers/staging/nmf-cm/cm/engine/dsp/src/dsp.c new file mode 100644 index 00000000000..ef11a5265aa --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/dsp/src/dsp.c @@ -0,0 +1,1083 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/dsp/mmdsp/inc/mmdsp_macros.h> + +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/semaphores/inc/semaphores.h> +#include <cm/engine/power_mgt/inc/power.h> +#include <cm/engine/memory/inc/migration.h> +#include <cm/engine/trace/inc/trace.h> + +#include <share/inc/nomadik_mapping.h> + +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> +#include <cm/engine/component/inc/component_type.h> + +static t_dsp_allocator_desc esramDesc; +static t_dsp_desc mpcDesc[NB_CORE_IDS]; +static t_mmdsp_hw_regs *pMmdspRegs[NB_CORE_IDS]; + +struct s_base_descr +{ + t_uint32 startAddress[2 /* DSP16 = 0, DSP24 = 1*/]; + t_dsp_segment_type segmentType; +}; + +#if defined(__STN_8500) && (__STN_8500 > 10) + +#define DATA_BASE_NUMBER 4 + +// In bytes +#define SDRAM_CODE_SPACE_SPLIT 0x8000 +#define ESRAM_CODE_SPACE_SPLIT 0x4000 +#define SDRAM_DATA_SPACE_SPLIT 0x40000 // This is the modulo constraint of mmdsp +#define ESRAM_DATA_SPACE_SPLIT 0x40000 + +// In MMDSP word +static const struct s_base_descr DATA_ADDRESS_BASE[DATA_BASE_NUMBER + 1 /* For guard */] = { + {{SDRAMMEM16_BASE_ADDR, SDRAMMEM24_BASE_ADDR}, SDRAM_DATA_EE}, + {{SDRAMMEM16_BASE_ADDR + (SDRAM_DATA_SPACE_SPLIT / 2), SDRAMMEM24_BASE_ADDR + (SDRAM_DATA_SPACE_SPLIT / 4)}, SDRAM_DATA_USER}, + {{ESRAMMEM16_BASE_ADDR, ESRAMMEM24_BASE_ADDR}, ESRAM_DATA_EE}, + {{ESRAMMEM16_BASE_ADDR + (ESRAM_DATA_SPACE_SPLIT / 2), ESRAMMEM24_BASE_ADDR + (ESRAM_DATA_SPACE_SPLIT / 4)}, ESRAM_DATA_USER}, + {{MMIO_BASE_ADDR, SDRAMMEM16_BASE_ADDR}, NB_DSP_SEGMENT_TYPE /* Not used*/} +}; + +#else + +#define DATA_BASE_NUMBER 2 + +// In MMDSP word +static const struct s_base_descr DATA_ADDRESS_BASE[DATA_BASE_NUMBER + 1 /* For guard */] = { + {{SDRAMMEM16_BASE_ADDR, SDRAMMEM24_BASE_ADDR}, SDRAM_DATA_EE}, + {{ESRAMMEM16_BASE_ADDR, ESRAMMEM24_BASE_ADDR}, ESRAM_DATA_EE}, + {{MMIO_BASE_ADDR, SDRAMMEM16_BASE_ADDR}, NB_DSP_SEGMENT_TYPE /* Not used*/} +}; + +#endif + +#if defined(__STN_8500) && (__STN_8500 > 10) +// In word +static const t_uint32 CODE_ADDRESS_BASE[4] = { + SDRAMTEXT_BASE_ADDR, + SDRAMTEXT_BASE_ADDR + (SDRAM_CODE_SPACE_SPLIT / 8), + ESRAMTEXT_BASE_ADDR, + ESRAMTEXT_BASE_ADDR + (ESRAM_CODE_SPACE_SPLIT / 8) +}; +#endif + +static void arm_Init(void); +static t_cm_error mmdsp_Init(const t_cm_system_address *dspSystemAddr, + t_uint8 nbXramBlocks, t_uint8 nbYramBlocks, + t_dsp_allocator_desc *sdramCodeDesc, + t_dsp_allocator_desc *sdramDataDesc, + t_cm_domain_id eeDomain, + t_dsp_desc *pDspDesc, + t_mmdsp_hw_regs **pRegs); +static t_cm_error mmdsp_Configure(t_nmf_core_id coreId, t_mmdsp_hw_regs *pRegs, const t_dsp_desc *pDspDesc); +static t_cm_error mmdsp_ConfigureAfterBoot(t_nmf_core_id coreId, t_uint8 nbXramBlocks, t_uint8 nbYramBlocks); +static void cm_DSP_SEM_Init(t_nmf_core_id coreId); + +PUBLIC const t_dsp_desc* cm_DSP_GetState(t_nmf_core_id coreId) +{ + return &mpcDesc[coreId]; +} +PUBLIC void cm_DSP_SetStatePanic(t_nmf_core_id coreId) +{ + mpcDesc[coreId].state = MPC_STATE_PANIC; +} + +PUBLIC void cm_DSP_Init(const t_nmf_memory_segment *pEsramDesc) +{ + t_nmf_core_id coreId; + int i; + + /* Create esram desc */ + esramDesc.allocDesc = cm_MM_CreateAllocator(pEsramDesc->size, 0, "esram"); + esramDesc.baseAddress = pEsramDesc->systemAddr; + esramDesc.referenceCounter = 1; // Don't free it with destroy mechanism + + /* Create ARM */ + arm_Init(); + + mpcDesc[ARM_CORE_ID].state = MPC_STATE_BOOTED; + + /* Reset MPC configuration */ + for (coreId = FIRST_MPC_ID; coreId <= LAST_CORE_ID; coreId++) + { + mpcDesc[coreId].state = MPC_STATE_UNCONFIGURED; + + for(i = 0; i < NB_DSP_MEMORY_TYPE; i++) + mpcDesc[coreId].allocator[i] = NULL; + } + +} + +PUBLIC void cm_DSP_Destroy(void) +{ + t_nmf_core_id coreId; + int i; + + for (coreId = ARM_CORE_ID; coreId <= LAST_CORE_ID; coreId++) + { + for(i = 0; i < NB_DSP_MEMORY_TYPE; i++) + { + if (mpcDesc[coreId].allocator[i] != NULL) + { + if(--mpcDesc[coreId].allocator[i]->referenceCounter == 0) + { + cm_MM_DeleteAllocator(mpcDesc[coreId].allocator[i]->allocDesc); + + OSAL_Free(mpcDesc[coreId].allocator[i]); + } + } + } + } + + cm_MM_DeleteAllocator(esramDesc.allocDesc); +} + + +PUBLIC t_cm_error cm_DSP_Add(t_nmf_core_id coreId, + t_uint8 nbYramBanks, + const t_cm_system_address *pDspMapDesc, + const t_cm_domain_id eeDomain, + t_dsp_allocator_desc *sdramCodeAllocDesc, + t_dsp_allocator_desc *sdramDataAllocDesc) +{ + t_cm_error error; + + /* checking nbYramBanks is valid */ + if (nbYramBanks >= SxA_NB_BLOCK_RAM) + return CM_MPC_INVALID_CONFIGURATION; + + if((error = cm_DM_CheckDomain(eeDomain, DOMAIN_NORMAL)) != CM_OK) + return error; + + mpcDesc[coreId].domainEE = eeDomain; + mpcDesc[coreId].nbYramBank = nbYramBanks; + mpcDesc[coreId].state = MPC_STATE_BOOTABLE; + + return mmdsp_Init( + pDspMapDesc, + SxA_NB_BLOCK_RAM, /* nb of data tcm bank minus one (reserved for cache) */ + nbYramBanks, + sdramCodeAllocDesc, + sdramDataAllocDesc, + eeDomain, + &mpcDesc[coreId], + &pMmdspRegs[coreId] + ); +} + +PUBLIC t_cm_error cm_DSP_Boot(t_nmf_core_id coreId) +{ + t_cm_error error; + + // Enable the associated power domain + if((error = cm_PWR_EnableMPC(MPC_PWR_CLOCK, coreId)) != CM_OK) + return error; + + cm_SEM_PowerOn[coreId](coreId); + + if((error = mmdsp_Configure( + coreId, + pMmdspRegs[coreId], + &mpcDesc[coreId])) != CM_OK) + { + cm_PWR_DisableMPC(MPC_PWR_CLOCK, coreId); + } + + // Put it in auto idle mode ; it's the default in Step 2 of power implementation + if((error = cm_PWR_EnableMPC(MPC_PWR_AUTOIDLE, coreId)) != CM_OK) + return error; + + return error; +} + +/* + * This method is required since MMDSP C bootstrap set some value that must be set differently !!! + */ +PUBLIC void cm_DSP_ConfigureAfterBoot(t_nmf_core_id coreId) +{ + mpcDesc[coreId].state = MPC_STATE_BOOTED; + + mmdsp_ConfigureAfterBoot(coreId, SxA_NB_BLOCK_RAM, mpcDesc[coreId].nbYramBank); + + cm_DSP_SEM_Init(coreId); +} + +PUBLIC void cm_DSP_Stop(t_nmf_core_id coreId) +{ + MMDSP_STOP_CORE(pMmdspRegs[coreId]); + + { + volatile t_uint32 loopme = 0xfff; + while(loopme--) ; + } +} + +PUBLIC void cm_DSP_Start(t_nmf_core_id coreId) +{ + MMDSP_START_CORE(pMmdspRegs[coreId]); + + { + volatile t_uint32 loopme = 0xfff; + while(loopme--) ; + } +} + +PUBLIC void cm_DSP_Shutdown(t_nmf_core_id coreId) +{ + MMDSP_FLUSH_DCACHE(pMmdspRegs[coreId]); + MMDSP_FLUSH_ICACHE(pMmdspRegs[coreId]); + + // Due to a hardware bug that breaks MTU when DSP are powered off, don't do that + // on mop500_ed for now +#if !defined(__STN_8500) || (__STN_8500 > 10) + MMDSP_RESET_CORE(pMmdspRegs[coreId]); + { + volatile t_uint32 loopme = 0xfff; + while(loopme--) ; + } + MMDSP_STOP_CORE(pMmdspRegs[coreId]); + { + volatile t_uint32 loopme = 0xfff; + while(loopme--) ; + } +#endif + + mpcDesc[coreId].state = MPC_STATE_BOOTABLE; + + cm_SEM_PowerOff[coreId](coreId); + + cm_PWR_DisableMPC(MPC_PWR_AUTOIDLE, coreId); + cm_PWR_DisableMPC(MPC_PWR_CLOCK, coreId); +} + +PUBLIC t_uint32 cm_DSP_ReadXRamWord(t_nmf_core_id coreId, t_uint32 dspOffset) +{ + t_uint32 value; + + value = pMmdspRegs[coreId]->mem24[dspOffset]; + + LOG_INTERNAL(3, "cm_DSP_ReadXRamWord: [%x]=%x\n", + dspOffset, value, + 0, 0, 0, 0); + + return value; +} + + +PUBLIC void cm_DSP_WriteXRamWord(t_nmf_core_id coreId, t_uint32 dspOffset, t_uint32 value) +{ + LOG_INTERNAL(3, "cm_DSP_WriteXRamWord: [%x]<-%x\n", + dspOffset, value, + 0, 0, 0, 0); + + pMmdspRegs[coreId]->mem24[dspOffset] = value; +} + +static void cm_DSP_SEM_Init(t_nmf_core_id coreId) +{ + pMmdspRegs[coreId]->mmio_16.sem[1].value = 1; +} + +PUBLIC void cm_DSP_SEM_Take(t_nmf_core_id coreId, t_semaphore_id semId) +{ + /* take semaphore */ + while(pMmdspRegs[coreId]->mmio_16.sem[1].value) ; +} + +PUBLIC void cm_DSP_SEM_Give(t_nmf_core_id coreId, t_semaphore_id semId) +{ + /* release semaphore */ + pMmdspRegs[coreId]->mmio_16.sem[1].value = 1; +} + +PUBLIC void cm_DSP_SEM_GenerateIrq(t_nmf_core_id coreId, t_semaphore_id semId) +{ + MMDSP_ASSERT_IRQ(pMmdspRegs[coreId], ARM2DSP_IRQ_0); +} + + +PUBLIC void cm_DSP_AssertDspIrq(t_nmf_core_id coreId, t_host2mpc_irq_num irqNum) +{ + MMDSP_ASSERT_IRQ(pMmdspRegs[coreId], irqNum); + return; +} + +PUBLIC void cm_DSP_AcknowledgeDspIrq(t_nmf_core_id coreId, t_mpc2host_irq_num irqNum) +{ + MMDSP_ACKNOWLEDGE_IRQ(pMmdspRegs[coreId], irqNum); + return; +} + +//TODO, juraj, cleanup INTERNAL_XRAM vs INTERNAL_XRAM16/24 +static const t_uint32 dspMemoryTypeId2OffsetShifter[NB_DSP_MEMORY_TYPE] = +{ + 2, /* INTERNAL_XRAM24: Internal X memory but seen by host as 32-bit memory */ + 2, /* INTERNAL_XRAM16: Internal X memory but seen by host as 16-bit memory */ + 2, /* INTERNAL_YRAM24: Internal Y memory but seen by host as 32-bit memory */ + 2, /* INTERNAL_YRAM16: Internal Y memory but seen by host as 16-bit memory */ + 2, /* SDRAM_EXT24: 24-bit external "X" memory */ + 1, /* SDRAM_EXT16: 16-bit external "X" memory */ + 2, /* ESRAM_EXT24: ESRAM24 */ + 1, /* ESRAM_EXT16: ESRAM16 */ + 3, /* SDRAM_CODE: Program memory */ + 3, /* ESRAM_CODE: ESRAM code */ + 3, /* LOCKED_CODE: ESRAM code */ +}; + +//TODO, juraj, use these values in mmdsp_Configure +static const t_uint32 dspMemoryTypeId2DspAddressOffset[NB_DSP_MEMORY_TYPE] = +{ + 0, /* INTERNAL_XRAM24 */ + 0, /* INTERNAL_XRAM16 */ + 0, /* INTERNAL_YRAM24 */ + 0, /* INTERNAL_YRAM16 */ + SDRAMMEM24_BASE_ADDR, /* SDRAM_EXT24: 24-bit external "X" memory */ + SDRAMMEM16_BASE_ADDR, /* SDRAM_EXT16: 16-bit external "X" memory */ + ESRAMMEM24_BASE_ADDR, /* ESRAM_EXT24: ESRAM24 */ + ESRAMMEM16_BASE_ADDR, /* ESRAM_EXT16: ESRAM16 */ + SDRAMTEXT_BASE_ADDR, /* SDRAM_CODE: Program memory */ + ESRAMTEXT_BASE_ADDR, /* ESRAM_CODE: ESRAM code */ + SDRAMTEXT_BASE_ADDR, /* ESRAM_CODE: ESRAM code */ +}; + +PUBLIC t_cm_allocator_desc* cm_DSP_GetAllocator(t_nmf_core_id coreId, t_dsp_memory_type_id memType) +{ + return mpcDesc[coreId].allocator[memType] ? mpcDesc[coreId].allocator[memType]->allocDesc : NULL; +} + +PUBLIC void cm_DSP_GetDspChunkInfo(t_memory_handle memHandle, t_dsp_chunk_info *info) +{ + t_uint16 userData; + + cm_MM_GetMemoryHandleUserData(memHandle, &userData, &info->alloc); + + info->coreId = (t_nmf_core_id) ((userData >> SHIFT_BYTE1) & MASK_BYTE0); + info->memType = (t_dsp_memory_type_id)((userData >> SHIFT_BYTE0) & MASK_BYTE0); +} + +PUBLIC t_cm_error cm_DSP_GetInternalMemoriesInfo(t_cm_domain_id domainId, t_dsp_memory_type_id memType, + t_uint32 *offset, t_uint32 *size) +{ + t_nmf_core_id coreId = domainDesc[domainId].domain.coreId; + + switch(memType) + { + case INTERNAL_XRAM24: + case INTERNAL_XRAM16: + *offset = 0; + *size = mpcDesc[coreId].yram_offset; + break; + case INTERNAL_YRAM24: + case INTERNAL_YRAM16: + *offset = mpcDesc[coreId].yram_offset; + *size = mpcDesc[coreId].yram_size; + break; + case LOCKED_CODE: + *offset = mpcDesc[coreId].locked_offset; + *size = mpcDesc[coreId].locked_size; + break; + case SDRAM_EXT24: + case SDRAM_EXT16: + *offset = domainDesc[domainId].domain.sdramData.offset; + *size = domainDesc[domainId].domain.sdramData.size; + break; + case ESRAM_EXT24: + case ESRAM_EXT16: + *offset = domainDesc[domainId].domain.esramData.offset; + *size = domainDesc[domainId].domain.esramData.size; + break; + case SDRAM_CODE: + *offset = domainDesc[domainId].domain.sdramCode.offset; + *size = domainDesc[domainId].domain.sdramCode.size; + + // update domain size to take into account .locked section + if(*offset + *size > mpcDesc[coreId].locked_offset) + *size = mpcDesc[coreId].locked_offset - *offset; + break; + case ESRAM_CODE: + *offset = domainDesc[domainId].domain.esramCode.offset; + *size = domainDesc[domainId].domain.esramCode.size; + break; + default: + //return CM_INVALID_PARAMETER; + //params are checked at the level above, so this should never occur + ERROR("Invalid memType\n",0,0,0,0,0,0); + *offset = 0; + *size = 0; + CM_ASSERT(0); + } + + return CM_OK; +} + + +PUBLIC t_uint32 cm_DSP_ConvertSize(t_dsp_memory_type_id memType, t_uint32 wordSize) +{ + return wordSize << dspMemoryTypeId2OffsetShifter[memType]; +} + +PUBLIC t_cm_logical_address cm_DSP_ConvertDspAddressToHostLogicalAddress(t_nmf_core_id coreId, t_shared_addr dspAddress) +{ + t_dsp_address_info info; + cm_DSP_GetDspDataAddressInfo(coreId, dspAddress, &info); + return mpcDesc[coreId].segments[info.segmentType].base.logical + info.baseOffset; +} + +PUBLIC t_cm_error cm_DSP_GetAllocatorStatus(t_nmf_core_id coreId, t_dsp_memory_type_id dspMemType, t_uint32 offset, t_uint32 size, t_cm_allocator_status *pStatus) +{ + t_cm_error error; + + if(mpcDesc[coreId].allocator[dspMemType] == NULL) + return CM_UNKNOWN_MEMORY_HANDLE; + + error = cm_MM_GetAllocatorStatus(cm_DSP_GetAllocator(coreId, dspMemType), offset, size, pStatus); + if (error != CM_OK) + return error; + + // complete status with stack sizes, for all dsps + //NOTE, well, surely this isn't very clean, as dsp and memory allocator are different things .. + { + t_uint8 i; + for (i = 0; i < NB_CORE_IDS; i++) { + //*(pStatus->stack[i].sizes) = *(eeState[i].currentStackSize); + pStatus->stack[i].sizes[0] = eeState[i].currentStackSize[0]; + pStatus->stack[i].sizes[1] = eeState[i].currentStackSize[1]; + pStatus->stack[i].sizes[2] = eeState[i].currentStackSize[2]; + } + } + + // Change bytes to words + pStatus->global.accumulate_free_memory = pStatus->global.accumulate_free_memory >> dspMemoryTypeId2OffsetShifter[dspMemType]; + pStatus->global.accumulate_used_memory = pStatus->global.accumulate_used_memory >> dspMemoryTypeId2OffsetShifter[dspMemType]; + pStatus->global.maximum_free_size = pStatus->global.maximum_free_size >> dspMemoryTypeId2OffsetShifter[dspMemType]; + pStatus->global.minimum_free_size = pStatus->global.minimum_free_size >> dspMemoryTypeId2OffsetShifter[dspMemType]; + + return error; +} + +PUBLIC void cm_DSP_GetHostSystemAddress(t_memory_handle memHandle, t_cm_system_address *pAddr) +{ + t_dsp_chunk_info chunk_info; + t_uint32 offset; //in bytes + + cm_DSP_GetDspChunkInfo(memHandle, &chunk_info); + + offset = cm_MM_GetOffset(memHandle); + + /* MMDSP mem16 array is very specific to host access, so .... */ + /* We compute by hand the Host System address to take into account the specifities of the mmdsp mem16 array */ + /* 1 dsp word = 2 host bytes AND mem16 array is "exported" by MMDSP External Bus wrapper at the 0x40000 offet */ + if (chunk_info.memType == INTERNAL_XRAM16 || chunk_info.memType == INTERNAL_YRAM16) { + offset = (offset >> 1) + FIELD_OFFSET(t_mmdsp_hw_regs, mem16); + } + + //TODO, juraj, calculate correct value here - based on segments desc etc.. + pAddr->logical = mpcDesc[chunk_info.coreId].allocator[chunk_info.memType]->baseAddress.logical + offset; + pAddr->physical = mpcDesc[chunk_info.coreId].allocator[chunk_info.memType]->baseAddress.physical + offset; +} + + +PUBLIC t_physical_address cm_DSP_GetPhysicalAdress(t_memory_handle memHandle) +{ + t_cm_system_address addr; + cm_DSP_GetHostSystemAddress(memHandle, &addr); + return addr.physical; +} + +PUBLIC t_cm_logical_address cm_DSP_GetHostLogicalAddress(t_memory_handle memHandle) +{ + t_cm_system_address addr; + cm_DSP_GetHostSystemAddress(memHandle, &addr); + return addr.logical; +} + +PUBLIC void cm_DSP_GetDspAddress(t_memory_handle memHandle, t_uint32 *pDspAddress) +{ + t_dsp_chunk_info chunk_info; + + cm_DSP_GetDspChunkInfo(memHandle, &chunk_info); + + *pDspAddress = + (cm_MM_GetOffset(memHandle) >> dspMemoryTypeId2OffsetShifter[chunk_info.memType]) + + dspMemoryTypeId2DspAddressOffset[chunk_info.memType]; +} + +PUBLIC t_cm_error cm_DSP_GetDspBaseAddress(t_nmf_core_id coreId, t_dsp_memory_type_id memType, t_cm_system_address *pAddr) +{ + cm_migration_check_state(coreId, STATE_NORMAL); + if (mpcDesc[coreId].allocator[memType] == NULL) + return CM_INVALID_PARAMETER; + *pAddr = mpcDesc[coreId].allocator[memType]->baseAddress; + return CM_OK; +} + +PUBLIC void cm_DSP_GetDspMemoryHandleSize(t_memory_handle memHandle, t_uint32 *pDspSize) +{ + t_dsp_chunk_info chunk_info; + cm_DSP_GetDspChunkInfo(memHandle, &chunk_info); + *pDspSize = cm_MM_GetSize(memHandle) >> dspMemoryTypeId2OffsetShifter[chunk_info.memType]; +} + +PUBLIC t_cm_error cm_DSP_setStackSize(t_nmf_core_id coreId, t_uint32 newStackSize) +{ + t_uint8 nbXramBanks; + t_uint32 xramSize; + t_cm_error error; + + /* compute size of xram allocator */ + nbXramBanks = SxA_NB_BLOCK_RAM - mpcDesc[coreId].nbYramBank; + + /* check first that required stack size is less then xram memory ....*/ + if (newStackSize >= nbXramBanks * 4 * ONE_KB) { + ERROR("CM_NO_MORE_MEMORY: cm_DSP_setStackSize(), required stack size doesn't fit in XRAM.\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + /* compute new xram allocator size */ + xramSize = nbXramBanks * 4 * ONE_KB - newStackSize; + + /* try to resize it */ + if ((error = cm_MM_ResizeAllocator(cm_DSP_GetAllocator(coreId, INTERNAL_XRAM24), + xramSize << dspMemoryTypeId2OffsetShifter[INTERNAL_XRAM24])) == CM_NO_MORE_MEMORY) { + ERROR("CM_NO_MORE_MEMORY: Couldn't resize stack in cm_DSP_setStackSize()\n", 0, 0, 0, 0, 0, 0); + } + + return error; +} + +PUBLIC t_cm_error cm_DSP_IsNbYramBanksValid(t_nmf_core_id coreId, t_uint8 nbYramBanks) +{ + /* we use one bank for cache */ + t_uint8 nbOfRamBanksWithCacheReserved = SxA_NB_BLOCK_RAM; + + /* we want to keep at least one bank of xram */ + if (nbYramBanks < nbOfRamBanksWithCacheReserved) {return CM_OK;} + else {return CM_MPC_INVALID_CONFIGURATION;} +} + +PUBLIC t_uint32 cm_DSP_getStackAddr(t_nmf_core_id coreId) +{ + /* we use one bank for cache */ + //t_uint8 nbOfRamBanksWithCacheReserved = SxA_NB_BLOCK_RAM; + /* */ + //return ((nbOfRamBanksWithCacheReserved * MMDSP_RAM_BLOCK_SIZE * MMDSP_DATA_WORD_SIZE_IN_HOST_SPACE) - mpcDesc[coreId].yram_offset); + return mpcDesc[coreId].yram_offset / MMDSP_DATA_WORD_SIZE_IN_HOST_SPACE; +} + +static void arm_Init(void) +{ + mpcDesc[ARM_CORE_ID].allocator[INTERNAL_XRAM24] = 0; + mpcDesc[ARM_CORE_ID].allocator[INTERNAL_XRAM16] = 0; + + mpcDesc[ARM_CORE_ID].allocator[INTERNAL_YRAM24] = 0; + mpcDesc[ARM_CORE_ID].allocator[INTERNAL_YRAM16] = 0; + + mpcDesc[ARM_CORE_ID].allocator[SDRAM_CODE] = 0; + mpcDesc[ARM_CORE_ID].allocator[ESRAM_CODE] = 0; + + mpcDesc[ARM_CORE_ID].allocator[SDRAM_EXT16] = 0; + mpcDesc[ARM_CORE_ID].allocator[SDRAM_EXT24] = 0; + + mpcDesc[ARM_CORE_ID].allocator[ESRAM_EXT16] = &esramDesc; + mpcDesc[ARM_CORE_ID].allocator[ESRAM_EXT16]->referenceCounter++; + mpcDesc[ARM_CORE_ID].allocator[ESRAM_EXT24] = &esramDesc; + mpcDesc[ARM_CORE_ID].allocator[ESRAM_EXT24]->referenceCounter++; +} + +static void _init_Segment( + t_dsp_segment *seg, + const t_cm_system_address base, const t_uint32 arm_offset, + const t_uint32 size) +{ + seg->base.logical = base.logical + arm_offset; + seg->base.physical = base.physical + arm_offset; + seg->size = size; +} + +static t_cm_error mmdsp_Init( + const t_cm_system_address *dspSystemAddr, + t_uint8 nbXramBlocks, t_uint8 nbYramBlocks, + t_dsp_allocator_desc *sdramCodeDesc, + t_dsp_allocator_desc *sdramDataDesc, + t_cm_domain_id eeDomain, + t_dsp_desc *pDspDesc, + t_mmdsp_hw_regs **pRegs) +{ + t_cm_system_address xramSysAddr; + t_uint32 sizeInBytes; + + /* Initialize reference on hw ressources */ + *pRegs = (t_mmdsp_hw_regs *) dspSystemAddr->logical; + + /* Initialize memory segments management */ + xramSysAddr.logical = (t_cm_logical_address)(((t_mmdsp_hw_regs *)dspSystemAddr->logical)->mem24); + xramSysAddr.physical = (t_cm_physical_address)(((t_mmdsp_hw_regs *)dspSystemAddr->physical)->mem24); + + /* The last (x)ram block will be used by cache, so ... */ + /* And the NB_YRAM_BLOCKS last available block(s) will be used as YRAM */ + + /* XRAM*/ + pDspDesc->allocator[INTERNAL_XRAM16] = pDspDesc->allocator[INTERNAL_XRAM24] = (t_dsp_allocator_desc*)OSAL_Alloc(sizeof (t_dsp_allocator_desc)); + if (pDspDesc->allocator[INTERNAL_XRAM24] == NULL) + return CM_NO_MORE_MEMORY; + + pDspDesc->allocator[INTERNAL_XRAM24]->allocDesc = cm_MM_CreateAllocator( + ((nbXramBlocks-nbYramBlocks)*MMDSP_RAM_BLOCK_SIZE)*MMDSP_DATA_WORD_SIZE_IN_HOST_SPACE, + 0, + "XRAM"); + pDspDesc->allocator[INTERNAL_XRAM24]->baseAddress = xramSysAddr; + pDspDesc->allocator[INTERNAL_XRAM24]->referenceCounter = 2; + + /* YRAM */ + pDspDesc->allocator[INTERNAL_YRAM16] = pDspDesc->allocator[INTERNAL_YRAM24] = (t_dsp_allocator_desc*)OSAL_Alloc(sizeof (t_dsp_allocator_desc)); + if (pDspDesc->allocator[INTERNAL_YRAM24] == 0) { + OSAL_Free(pDspDesc->allocator[INTERNAL_XRAM24]); + return CM_NO_MORE_MEMORY; + } + + pDspDesc->allocator[INTERNAL_YRAM24]->allocDesc = cm_MM_CreateAllocator( + (nbYramBlocks*MMDSP_RAM_BLOCK_SIZE)*MMDSP_DATA_WORD_SIZE_IN_HOST_SPACE, + ((nbXramBlocks-nbYramBlocks)*MMDSP_RAM_BLOCK_SIZE)*MMDSP_DATA_WORD_SIZE_IN_HOST_SPACE, + "YRAM"); + pDspDesc->allocator[INTERNAL_YRAM24]->baseAddress = xramSysAddr; /* use xram base address but offset is not null */ + pDspDesc->allocator[INTERNAL_YRAM24]->referenceCounter = 2; + + pDspDesc->yram_offset = ((nbXramBlocks-nbYramBlocks)*MMDSP_RAM_BLOCK_SIZE)*MMDSP_DATA_WORD_SIZE_IN_HOST_SPACE; + pDspDesc->yram_size = (nbYramBlocks*MMDSP_RAM_BLOCK_SIZE)*MMDSP_DATA_WORD_SIZE_IN_HOST_SPACE; + + /* SDRAM & ESRAM */ + pDspDesc->allocator[SDRAM_CODE] = sdramCodeDesc; + pDspDesc->allocator[SDRAM_CODE]->referenceCounter++; + pDspDesc->allocator[ESRAM_CODE] = &esramDesc; + pDspDesc->allocator[ESRAM_CODE]->referenceCounter++; + + /* LOCKED CODE at end of SDRAM code*/ + pDspDesc->allocator[LOCKED_CODE] = sdramCodeDesc; + pDspDesc->allocator[LOCKED_CODE]->referenceCounter++; + + pDspDesc->locked_offset = cm_MM_GetAllocatorSize(pDspDesc->allocator[SDRAM_CODE]->allocDesc) - MMDSP_CODE_CACHE_WAY_SIZE * 8 * SxA_LOCKED_WAY; + pDspDesc->locked_size = MMDSP_CODE_CACHE_WAY_SIZE * 8 * SxA_LOCKED_WAY; + + /* Data_16/24 memory management */ + pDspDesc->allocator[SDRAM_EXT16] = sdramDataDesc; + pDspDesc->allocator[SDRAM_EXT16]->referenceCounter++; + pDspDesc->allocator[SDRAM_EXT24] = sdramDataDesc; + pDspDesc->allocator[SDRAM_EXT24]->referenceCounter++; + + pDspDesc->allocator[ESRAM_EXT16] = &esramDesc; + pDspDesc->allocator[ESRAM_EXT16]->referenceCounter++; + pDspDesc->allocator[ESRAM_EXT24] = &esramDesc; + pDspDesc->allocator[ESRAM_EXT24]->referenceCounter++; + + sizeInBytes = cm_MM_GetAllocatorSize(pDspDesc->allocator[SDRAM_CODE]->allocDesc); +#if defined(__STN_8500) && (__STN_8500 > 10) + _init_Segment(&pDspDesc->segments[SDRAM_CODE_EE], + pDspDesc->allocator[SDRAM_CODE]->baseAddress, + domainDesc[eeDomain].domain.sdramCode.offset, + domainDesc[eeDomain].domain.sdramCode.size); + _init_Segment(&pDspDesc->segments[SDRAM_CODE_USER], + pDspDesc->allocator[SDRAM_CODE]->baseAddress, + domainDesc[eeDomain].domain.sdramCode.offset + domainDesc[eeDomain].domain.sdramCode.size, + sizeInBytes - domainDesc[eeDomain].domain.sdramCode.size); +#else + _init_Segment(&pDspDesc->segments[SDRAM_CODE_EE], + pDspDesc->allocator[SDRAM_CODE]->baseAddress, + 0x0, + sizeInBytes); +#endif + + sizeInBytes = cm_MM_GetAllocatorSize(pDspDesc->allocator[ESRAM_CODE]->allocDesc); +#if defined(__STN_8500) && (__STN_8500 > 10) + _init_Segment(&pDspDesc->segments[ESRAM_CODE_EE], + pDspDesc->allocator[ESRAM_CODE]->baseAddress, + domainDesc[eeDomain].domain.esramCode.offset, + domainDesc[eeDomain].domain.esramCode.size); + _init_Segment(&pDspDesc->segments[ESRAM_CODE_USER], + pDspDesc->allocator[ESRAM_CODE]->baseAddress, + domainDesc[eeDomain].domain.esramCode.offset + domainDesc[eeDomain].domain.esramCode.size, + sizeInBytes - domainDesc[eeDomain].domain.esramCode.size); +#else + _init_Segment(&pDspDesc->segments[ESRAM_CODE_EE], + pDspDesc->allocator[ESRAM_CODE]->baseAddress, + 0x0, + sizeInBytes); +#endif + + //the difference in the following code is the segment size used to calculate the top!! + sizeInBytes = cm_MM_GetAllocatorSize(pDspDesc->allocator[SDRAM_EXT16]->allocDesc); +#if defined(__STN_8500) && (__STN_8500 > 10) + _init_Segment(&pDspDesc->segments[SDRAM_DATA_EE], + pDspDesc->allocator[SDRAM_EXT16]->baseAddress, + domainDesc[eeDomain].domain.sdramData.offset, + domainDesc[eeDomain].domain.sdramData.size); + _init_Segment(&pDspDesc->segments[SDRAM_DATA_USER], + pDspDesc->allocator[SDRAM_EXT16]->baseAddress, + domainDesc[eeDomain].domain.sdramData.offset + domainDesc[eeDomain].domain.sdramData.size, + sizeInBytes - domainDesc[eeDomain].domain.sdramData.size); +#else + _init_Segment(&pDspDesc->segments[SDRAM_DATA_EE], + pDspDesc->allocator[SDRAM_EXT16]->baseAddress, + 0x0, + sizeInBytes); +#endif + + sizeInBytes = cm_MM_GetAllocatorSize(pDspDesc->allocator[ESRAM_EXT16]->allocDesc); +#if defined(__STN_8500) && (__STN_8500 > 10) + _init_Segment(&pDspDesc->segments[ESRAM_DATA_EE], + pDspDesc->allocator[ESRAM_EXT16]->baseAddress, + domainDesc[eeDomain].domain.esramData.offset, + domainDesc[eeDomain].domain.esramData.size); + _init_Segment(&pDspDesc->segments[ESRAM_DATA_USER], + pDspDesc->allocator[ESRAM_EXT16]->baseAddress, + domainDesc[eeDomain].domain.esramData.offset + domainDesc[eeDomain].domain.esramData.size, + sizeInBytes - domainDesc[eeDomain].domain.esramData.size); +#else + _init_Segment(&pDspDesc->segments[ESRAM_DATA_EE], + pDspDesc->allocator[ESRAM_EXT16]->baseAddress, + 0x0, + sizeInBytes); +#endif + + return CM_OK; +} + +//TODO, juraj, reuse cm_DSP_UpdateBase functions +static t_cm_error mmdsp_Configure(t_nmf_core_id coreId, t_mmdsp_hw_regs *pRegs, const t_dsp_desc *pDspDesc) +{ + t_uint64 regValue; + static const t_uint64 coreId2stbusId[NB_CORE_IDS] = + { + 0, /* ARM_CORE_ID no meaning */ + SVA_STBUS_ID, /* SVA_CORE_ID */ + SIA_STBUS_ID /* SIA_CORE_ID */ + }; + + //t_cm_system_address sysAddr; + //t_cm_size sizeInBytes; + + /* Stop core (stop clock) */ + MMDSP_RESET_CORE(pRegs); + { + volatile t_uint32 loopme = 0xfff; + while(loopme--) ; + } + MMDSP_STOP_CORE(pRegs); + { + volatile t_uint32 loopme = 0xfff; + while(loopme--) ; + } + +#if 0 + /* Reset DSP internal memory (xram) */ + { + t_uint32 *pSrc = (t_uint32 *)(pRegs->mem24); + t_uint32 tcmSize; + int i; + cm_MM_GetAllocatorSize(pDspDesc->allocator[INTERNAL_XRAM], &sizeInBytes); + tcmSize = sizeInBytes; + cm_MM_GetAllocatorSize(pDspDesc->allocator[INTERNAL_YRAM], &sizeInBytes); + tcmSize += sizeInBytes; + for (i = 0; i < (tcmSize/sizeof(t_uint32)); i++) + *(pSrc++) = 0; + } +#endif + + /* Configure all blocks as X only, except the Y ones (MOVED TO mmdsp_InitAfterBoot()) */ + + /* __STN_8815 --> __STN_8820 or __STN_8500 */ + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_STBUS_ID_CONF_REG, coreId2stbusId[coreId]); + + /* Configure External Bus timeout reg */ + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_EN_EXT_BUS_TIMEOUT_REG, IHOST_TIMEOUT_ENABLE); + + /* Program memory management */ +#if defined(__STN_8500) && (__STN_8500 > 10) + { + const t_uint32 r0 = CODE_ADDRESS_BASE[1] >> 10; + const t_uint32 r1 = CODE_ADDRESS_BASE[2] >> 10; + const t_uint32 r2 = CODE_ADDRESS_BASE[3] >> 10; + const t_uint32 sdram0 = pDspDesc->segments[SDRAM_CODE_EE].base.physical; + const t_uint32 sdram1 = pDspDesc->segments[SDRAM_CODE_USER].base.physical; + const t_uint32 esram0 = pDspDesc->segments[ESRAM_CODE_EE].base.physical; + const t_uint32 esram1 = pDspDesc->segments[ESRAM_CODE_USER].base.physical; + + /* Bases for first two segments, going to sdram */ + regValue = ((t_uint64)(sdram1) << IHOST_PRG_BASE2_ADDR_SHIFT) + (t_uint64)sdram0; + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_PRG_BASE_ADDR_REG, regValue); + + /* Bases for second two segments, going to esram */ + regValue = ((t_uint64)(esram1) << IHOST_PRG_BASE4_ADDR_SHIFT) + (t_uint64)esram0; + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_PRG_BASE_34_ADDR_REG, regValue); + + /* Split mmdsp program adress-space and activate the mechanism */ + regValue = (t_uint64)((t_uint64)(r2) << 48 | (t_uint64)(r1) <<32 | (t_uint64)(r0) << 16 | 1); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_PRG_BASE2_ACTIV_REG, regValue); + } +#else + { + const t_uint32 sdram0 = pDspDesc->segments[SDRAM_CODE_EE].base.physical; + const t_uint32 esram0 = pDspDesc->segments[ESRAM_CODE_EE].base.physical; + + regValue = (t_uint64)sdram0 | ( ((t_uint64)esram0) << IHOST_PRG_BASE2_ADDR_SHIFT ); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_PRG_BASE_ADDR_REG, regValue); + + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_PRG_BASE2_ACTIV_REG, IHOST_PRG_BASE2_ACTIV_ON); + } +#endif + + /* Data_16/24 memory management */ +#if defined(__STN_8500) && (__STN_8500 > 10) + /* Segments 1 and 2 for 16/24 map to sdram continuously */ + /* Base 1 */ + regValue = (((t_uint64)pDspDesc->segments[SDRAM_DATA_EE].base.physical) << IHOST_DATA_EXT_BUS_BASE_24_SHIFT) | + (((t_uint64)pDspDesc->segments[SDRAM_DATA_EE].base.physical) << IHOST_DATA_EXT_BUS_BASE_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA_EXT_BUS_BASE_REG, regValue); + /* Top 1 */ + regValue = (((t_uint64)(pDspDesc->segments[SDRAM_DATA_EE].base.physical + pDspDesc->segments[SDRAM_DATA_EE].size - 1)) << IHOST_DATA_EXT_BUS_TOP_24_SHIFT) | + (((t_uint64)(pDspDesc->segments[SDRAM_DATA_EE].base.physical + pDspDesc->segments[SDRAM_DATA_EE].size - 1)) << IHOST_DATA_EXT_BUS_TOP_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA_EXT_BUS_TOP_16_24_REG, regValue); + + /* Base 2 */ + regValue = (((t_uint64)pDspDesc->segments[SDRAM_DATA_USER].base.physical) << IHOST_DATA_EXT_BUS_BASE2_24_SHIFT) | + (((t_uint64)pDspDesc->segments[SDRAM_DATA_USER].base.physical) << IHOST_DATA_EXT_BUS_BASE2_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA_EXT_BUS_BASE2_REG, regValue); + /* Top 2 */ + regValue = (((t_uint64)(pDspDesc->segments[SDRAM_DATA_USER].base.physical + pDspDesc->segments[SDRAM_DATA_USER].size - 1)) << IHOST_DATA_EXT_BUS_TOP2_24_SHIFT) | + (((t_uint64)(pDspDesc->segments[SDRAM_DATA_USER].base.physical + pDspDesc->segments[SDRAM_DATA_USER].size - 1)) << IHOST_DATA_EXT_BUS_TOP2_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_EXT_BUS_TOP2_16_24_REG, regValue); + + /* Segments 3 and 4 for 16/24 map to esram continuously */ + /* Base 3 */ + regValue = (((t_uint64)pDspDesc->segments[ESRAM_DATA_EE].base.physical) << IHOST_DATA_EXT_BUS_BASE3_24_SHIFT) | + (((t_uint64)pDspDesc->segments[ESRAM_DATA_EE].base.physical) << IHOST_DATA_EXT_BUS_BASE3_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA_EXT_BUS_BASE3_REG, regValue); + /* Top 3 */ + regValue = (((t_uint64)(pDspDesc->segments[ESRAM_DATA_EE].base.physical + pDspDesc->segments[ESRAM_DATA_EE].size - 1)) << IHOST_DATA_EXT_BUS_TOP3_24_SHIFT) | + (((t_uint64)(pDspDesc->segments[ESRAM_DATA_EE].base.physical + pDspDesc->segments[ESRAM_DATA_EE].size - 1)) << IHOST_DATA_EXT_BUS_TOP3_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_EXT_BUS_TOP3_16_24_REG, regValue); + + /* Base 4 */ + regValue = (((t_uint64)pDspDesc->segments[ESRAM_DATA_USER].base.physical) << IHOST_DATA_EXT_BUS_BASE4_24_SHIFT) | + (((t_uint64)pDspDesc->segments[ESRAM_DATA_USER].base.physical) << IHOST_DATA_EXT_BUS_BASE4_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA_EXT_BUS_BASE4_REG, regValue); + /* Top 4 */ + regValue = (((t_uint64)(pDspDesc->segments[ESRAM_DATA_USER].base.physical + pDspDesc->segments[ESRAM_DATA_USER].size - 1)) << IHOST_DATA_EXT_BUS_TOP4_24_SHIFT) | + (((t_uint64)(pDspDesc->segments[ESRAM_DATA_USER].base.physical + pDspDesc->segments[ESRAM_DATA_USER].size - 1)) << IHOST_DATA_EXT_BUS_TOP4_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_EXT_BUS_TOP4_16_24_REG, regValue); + + /* Define base 2 thresholds/offset (1MB for each up segment) */ + regValue = ((t_uint64)DATA_ADDRESS_BASE[1].startAddress[1]>>SHIFT_HALFWORD1)<< IHOST_DATA2_24_XA_BASE_SHIFT; + regValue |= ((t_uint64)DATA_ADDRESS_BASE[1].startAddress[0]>>SHIFT_HALFWORD1)<< IHOST_DATA2_16_XA_BASE_SHIFT; + + /* Define base 3 thresholds/offset (1MB for each up segment) */ + regValue |= ((t_uint64)DATA_ADDRESS_BASE[2].startAddress[1]>>SHIFT_HALFWORD1)<< IHOST_DATA3_24_XA_BASE_SHIFT; + regValue |= ((t_uint64)DATA_ADDRESS_BASE[2].startAddress[0]>>SHIFT_HALFWORD1)<< IHOST_DATA3_16_XA_BASE_SHIFT; + + /* Define base 4 thresholds/offset (1MB for each up segment) */ + regValue |= ((t_uint64)DATA_ADDRESS_BASE[3].startAddress[1]>>SHIFT_HALFWORD1)<< IHOST_DATA4_24_XA_BASE_SHIFT; + regValue |= ((t_uint64)DATA_ADDRESS_BASE[3].startAddress[0]>>SHIFT_HALFWORD1)<< IHOST_DATA4_16_XA_BASE_SHIFT; + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA2_1624_XA_BASE_REG, regValue); + +#else + /* Program data24/16 base 1 */ + regValue = (((t_uint64)pDspDesc->segments[SDRAM_DATA_EE].base.physical) << IHOST_DATA_EXT_BUS_BASE_24_SHIFT) | + (((t_uint64)pDspDesc->segments[SDRAM_DATA_EE].base.physical) << IHOST_DATA_EXT_BUS_BASE_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA_EXT_BUS_BASE_REG, regValue); + + /* Program data24/16 top 1 */ + regValue = (((t_uint64)(pDspDesc->segments[SDRAM_DATA_EE].base.physical + pDspDesc->segments[SDRAM_DATA_EE].size - 1)) << IHOST_DATA_EXT_BUS_TOP_24_SHIFT) | + (((t_uint64)(pDspDesc->segments[SDRAM_DATA_EE].base.physical + pDspDesc->segments[SDRAM_DATA_EE].size - 1)) << IHOST_DATA_EXT_BUS_TOP_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA_EXT_BUS_TOP_16_24_REG, regValue); + + /* Program data24/16 base 2 */ + regValue = (((t_uint64)pDspDesc->segments[ESRAM_DATA_EE].base.physical) << IHOST_DATA_EXT_BUS_BASE2_24_SHIFT) | + (((t_uint64)pDspDesc->segments[ESRAM_DATA_EE].base.physical) << IHOST_DATA_EXT_BUS_BASE2_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA_EXT_BUS_BASE2_REG, regValue); + + /* Program data24/16 top 2 */ + regValue = (((t_uint64)(pDspDesc->segments[ESRAM_DATA_EE].base.physical + pDspDesc->segments[ESRAM_DATA_EE].size - 1)) << IHOST_DATA_EXT_BUS_TOP2_24_SHIFT) | + (((t_uint64)(pDspDesc->segments[ESRAM_DATA_EE].base.physical + pDspDesc->segments[ESRAM_DATA_EE].size - 1)) << IHOST_DATA_EXT_BUS_TOP2_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_EXT_BUS_TOP2_16_24_REG, regValue); + + /* Define base 2 thresholds/offset (1MB for each up segment) */ + regValue = ((t_uint64)(DATA_ADDRESS_BASE[1].startAddress[1]>>SHIFT_HALFWORD1))<< IHOST_DATA2_24_XA_BASE_SHIFT; // Top address minus ONE_MB => 256KW (24/32-bit) + regValue |= ((t_uint64)(DATA_ADDRESS_BASE[1].startAddress[0]>>SHIFT_HALFWORD1))<< IHOST_DATA2_16_XA_BASE_SHIFT; // Top address minus ONE_MB => 512KW (16-bit) + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA2_1624_XA_BASE_REG, regValue); +#endif + + /* Enable top check */ + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA_TOP_16_24_CHK_REG, IHOST_DATA_TOP_16_24_CHK_ON); + + /* Enable both bases */ + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_DATA_BASE2_ACTIV_REG, IHOST_DATA_BASE2_ACTIV_ON); + + /* MMIO management */ + regValue = (((t_uint64)STM_BASE_ADDR) << IHOST_EXT_MMIO_BASE_ADDR_SHIFT) | + (((t_uint64)DMA_CTRL_END_ADDR) << IHOST_EXT_MMIO_DATA_EXT_BUS_TOP_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_EXT_MMIO_BASE_DATA_EXT_BUS_TOP_REG, regValue); + + /* Configure Icache */ + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_INST_BURST_SZ_REG, IHOST_INST_BURST_SZ_AUTO); + + regValue = (t_uint64)(IHOST_ICACHE_MODE_PERFMETER_OFF | IHOST_ICACHE_MODE_L2_CACHE_ON | + IHOST_ICACHE_MODE_L1_CACHE_ON | IHOST_ICACHE_MODE_FILL_MODE_OFF); + WRITE_INDIRECT_HOST_REG(pRegs, IHOST_ICACHE_MODE_REG, regValue); + + return CM_OK; +} + +PUBLIC t_cm_error cm_DSP_updateCodeBase( + t_nmf_core_id coreId, + t_dsp_segment_type hwSegment, + t_cm_system_address src, + t_cm_system_address dst + ) +{ +#if defined(__STN_8500) && (__STN_8500 > 10) + t_mmdsp_hw_regs *pRegs = pMmdspRegs[coreId]; + t_uint32 offset = src.physical - mpcDesc[coreId].segments[hwSegment].base.physical; + t_cm_system_address base; + t_uint32 altBase = 0; + t_uint64 regValue = 0; + t_uint8 reg = 0; + + base.physical = dst.physical - offset; + base.logical = dst.logical - offset; + + switch(hwSegment) { + case SDRAM_CODE_EE: + altBase = mpcDesc[coreId].segments[SDRAM_CODE_USER].base.physical; + regValue = ((t_uint64)(altBase) << IHOST_PRG_BASE2_ADDR_SHIFT) + (t_uint64)base.physical; + reg = IHOST_PRG_BASE_ADDR_REG; + break; + case SDRAM_CODE_USER: + altBase = mpcDesc[coreId].segments[SDRAM_CODE_EE].base.physical; + regValue = ((t_uint64)(base.physical) << IHOST_PRG_BASE2_ADDR_SHIFT) + (t_uint64)altBase; + reg = IHOST_PRG_BASE_ADDR_REG; + break; + case ESRAM_CODE_EE: + altBase = mpcDesc[coreId].segments[ESRAM_CODE_USER].base.physical; + regValue = ((t_uint64)(altBase) << IHOST_PRG_BASE4_ADDR_SHIFT) + (t_uint64)base.physical; + reg = IHOST_PRG_BASE_34_ADDR_REG; + break; + case ESRAM_CODE_USER: + altBase = mpcDesc[coreId].segments[ESRAM_CODE_EE].base.physical; + regValue = ((t_uint64)(base.physical) << IHOST_PRG_BASE4_ADDR_SHIFT) + (t_uint64)altBase; + reg = IHOST_PRG_BASE_34_ADDR_REG; + break; + default: + CM_ASSERT(0); + } + + LOG_INTERNAL(1, "##### DSP Code Base Update [%d]: 0x%x -> 0x%x (0x%x)\n", + hwSegment, mpcDesc[coreId].segments[hwSegment].base.physical, base.physical, base.logical, 0, 0); + + WRITE_INDIRECT_HOST_REG(pRegs, reg, regValue); + + mpcDesc[coreId].segments[hwSegment].base = base; +#endif + return CM_OK; +} + +PUBLIC t_cm_error cm_DSP_updateDataBase( + t_nmf_core_id coreId, + t_dsp_segment_type hwSegment, + t_cm_system_address src, + t_cm_system_address dst + ) +{ +#if defined(__STN_8500) && (__STN_8500 > 10) + t_mmdsp_hw_regs *pRegs = pMmdspRegs[coreId]; + t_uint32 offset = src.physical - mpcDesc[coreId].segments[hwSegment].base.physical; + t_cm_system_address base; + t_uint32 size = mpcDesc[coreId].segments[hwSegment].size; //in bytes + t_uint64 regValue; + t_uint8 reg = 0; + t_uint8 top = 0; + + base.physical = dst.physical - offset; + base.logical = dst.logical - offset; + + switch(hwSegment) { + case SDRAM_DATA_EE: + reg = IHOST_DATA_EXT_BUS_BASE_REG; + top = IHOST_DATA_EXT_BUS_TOP_16_24_REG; + break; + case SDRAM_DATA_USER: + reg = IHOST_DATA_EXT_BUS_BASE2_REG; + top = IHOST_EXT_BUS_TOP2_16_24_REG; + break; + case ESRAM_DATA_EE: + reg = IHOST_DATA_EXT_BUS_BASE3_REG; + top = IHOST_EXT_BUS_TOP3_16_24_REG; + break; + case ESRAM_DATA_USER: + reg = IHOST_DATA_EXT_BUS_BASE4_REG; + top = IHOST_EXT_BUS_TOP4_16_24_REG; + break; + default: + CM_ASSERT(0); + } + + LOG_INTERNAL(1, "##### DSP Data Base Update [%d]: 0x%x -> 0x%x (0x%x)\n", + hwSegment, mpcDesc[coreId].segments[hwSegment].base.physical, base.physical, base.logical, 0, 0); + + /* Program data24/16 base */ + regValue = (((t_uint64)(base.physical)) << IHOST_DATA_EXT_BUS_BASE2_24_SHIFT) | + (((t_uint64)(base.physical)) << IHOST_DATA_EXT_BUS_BASE2_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, reg, regValue); + + /* Program data24/16 top */ + regValue = (((t_uint64)(base.physical + size - 1)) << IHOST_DATA_EXT_BUS_TOP2_24_SHIFT) | + (((t_uint64)(base.physical + size - 1)) << IHOST_DATA_EXT_BUS_TOP2_16_SHIFT); + WRITE_INDIRECT_HOST_REG(pRegs, top, regValue); + + mpcDesc[coreId].segments[hwSegment].base = base; +#endif + return CM_OK; +} + +PUBLIC t_cm_error cm_DSP_GetDspDataAddressInfo(t_nmf_core_id coreId, t_uint32 addr, t_dsp_address_info *info) +{ + t_uint32 i, j; + + for(j = 0; j < 2; j++) + { + for(i = 0; i < DATA_BASE_NUMBER; i++) + { + if(DATA_ADDRESS_BASE[i].startAddress[j] <= addr && addr < DATA_ADDRESS_BASE[i + 1].startAddress[j]) + { + info->segmentType = DATA_ADDRESS_BASE[i].segmentType; + info->baseOffset = (addr - DATA_ADDRESS_BASE[i].startAddress[j]) * (2 + j * 2); + + return CM_OK; + } + } + } + + CM_ASSERT(0); + //return CM_INVALID_PARAMETER; +} + +static t_cm_error mmdsp_ConfigureAfterBoot(t_nmf_core_id coreId, t_uint8 nbXramBlocks, t_uint8 nbYramBlocks) +{ + /* Configure all blocks as X only, except the Y ones */ + pMmdspRegs[coreId]->mmio_16.config_data_mem.value = (t_uint16)(~(((1U << nbYramBlocks) - 1) << (nbXramBlocks-nbYramBlocks))); + +#if defined(__STN_8500) && (__STN_8500 > 10) + /* enable write posting */ + MMDSP_ENABLE_WRITE_POSTING(pMmdspRegs[coreId]); +#endif + + return CM_OK; +} + + diff --git a/drivers/staging/nmf-cm/cm/engine/elf/inc/bfd.h b/drivers/staging/nmf-cm/cm/engine/elf/inc/bfd.h new file mode 100644 index 00000000000..2bccf9c073b --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/inc/bfd.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Elf bfd relocation. + * + * \defgroup ELFLOADER MMDSP ELF loader. + */ +#ifndef __INC_CM_ELF_BFD_H +#define __INC_CM_ELF_BFD_H + +#include <cm/inc/cm_type.h> + +/* + * Relocation spcification + */ +enum complain_overflow +{ + /* Do not complain on overflow. */ + complain_overflow_dont, + + /* Complain if the bitfield overflows, whether it is considered + as signed or unsigned. */ + complain_overflow_bitfield, + + /* Complain if the value overflows when considered as signed + number. */ + complain_overflow_signed, + + /* Complain if the value overflows when considered as an + unsigned number. */ + complain_overflow_unsigned +}; + +struct reloc_howto_struct +{ + /* The type field has mainly a documentary use - the back end can + do what it wants with it, though normally the back end's + external idea of what a reloc number is stored + in this field. For example, a PC relative word relocation + in a coff environment has the type 023 - because that's + what the outside world calls a R_PCRWORD reloc. */ + unsigned int type; + + /* The value the final relocation is shifted right by. This drops + unwanted data from the relocation. */ + unsigned int rightshift; + + /* The size of the item to be relocated. This is *not* a + power-of-two measure. To get the number of bytes operated + on by a type of relocation, use bfd_get_reloc_size. */ + int size; + + /* The number of bits in the item to be relocated. This is used + when doing overflow checking. */ + unsigned int bitsize; + + /* Notes that the relocation is relative to the location in the + data section of the addend. The relocation function will + subtract from the relocation value the address of the location + being relocated. */ + t_uint64 pc_relative; + + /* The bit position of the reloc value in the destination. + The relocated value is left shifted by this amount. */ + unsigned int bitpos; + + /* What type of overflow error should be checked for when + relocating. */ + enum complain_overflow complain_on_overflow; + + void (*special_function)(void); + + /* The textual name of the relocation type. */ + char *name; + + /* Some formats record a relocation addend in the section contents + rather than with the relocation. For ELF formats this is the + distinction between USE_REL and USE_RELA (though the code checks + for USE_REL == 1/0). The value of this field is TRUE if the + addend is recorded with the section contents; when performing a + partial link (ld -r) the section contents (the data) will be + modified. The value of this field is FALSE if addends are + recorded with the relocation (in arelent.addend); when performing + a partial link the relocation will be modified. + All relocations for all ELF USE_RELA targets should set this field + to FALSE (values of TRUE should be looked on with suspicion). + However, the converse is not true: not all relocations of all ELF + USE_REL targets set this field to TRUE. Why this is so is peculiar + to each particular target. For relocs that aren't used in partial + links (e.g. GOT stuff) it doesn't matter what this is set to. */ + char partial_inplace; + + /* src_mask selects the part of the instruction (or data) to be used + in the relocation sum. If the target relocations don't have an + addend in the reloc, eg. ELF USE_REL, src_mask will normally equal + dst_mask to extract the addend from the section contents. If + relocations do have an addend in the reloc, eg. ELF USE_RELA, this + field should be zero. Non-zero values for ELF USE_RELA targets are + bogus as in those cases the value in the dst_mask part of the + section contents should be treated as garbage. */ + t_uint64 src_mask; + + /* dst_mask selects which parts of the instruction (or data) are + replaced with a relocated value. */ + t_uint64 dst_mask; + + /* When some formats create PC relative instructions, they leave + the value of the pc of the place being relocated in the offset + slot of the instruction, so that a PC relative relocation can + be made just by adding in an ordinary offset (e.g., sun3 a.out). + Some formats leave the displacement part of an instruction + empty (e.g., m88k bcs); this flag signals the fact. */ + char pcrel_offset; +}; + +#define HOWTO(C, R, S, B, P, BI, O, SF, NAME, INPLACE, MASKSRC, MASKDST, PC) \ + { (unsigned) C, R, S, B, P, BI, O, SF, NAME, INPLACE, MASKSRC, MASKDST, PC } + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/elf/inc/common.h b/drivers/staging/nmf-cm/cm/engine/elf/inc/common.h new file mode 100644 index 00000000000..c51845d5f96 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/inc/common.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Elf common definition. + */ +#ifndef __INC_CM_ELF_COMMON_H +#define __INC_CM_ELF_COMMON_H + +#include <cm/engine/component/inc/nmfheaderabi.h> +#include <cm/engine/elf/inc/elfabi.h> +#include <cm/engine/elf/inc/reloc.h> +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/component/inc/description.h> +#include <cm/engine/utils/inc/string.h> + + +#define MAX_SEGMENT 20 // Just in order to not allocate them dynamically + +struct XXElf; + +/** + * \brief Structure used as database of pushed component. + */ +typedef struct { + t_instance_property instanceProperty; + t_uint32 magicNumber; //!< Magic Number + t_dup_char foundedTemplateName; + t_uint32 minStackSize; //!< Minimum stack size + + struct XXElf *ELF; + + t_elfSegment segments[NUMBER_OF_MMDSP_MEMORY]; + + t_bool temporaryDescription; + + t_memory_reference memoryForConstruct; + t_memory_reference memoryForStart; + t_memory_reference memoryForStop; + t_memory_reference memoryForDestroy; + + t_uint8 requireNumber; //!< Number of interface required by this template + t_uint8 attributeNumber; //!< Number of attributes in this template + t_uint8 propertyNumber; //!< Number of properties in this template + t_uint8 provideNumber; //!< Number of interface provided by this template + + t_interface_require *requires; //!< Array of interface required by this template + t_attribute *attributes; //!< Array of attributes in this template + t_property *properties; //!< Array of properties in this template + t_interface_provide *provides; //!< Array of interface provided by this template + +} t_elfdescription; + +/** + * \brief Temporary structure used as database when pushing component. + */ +typedef struct +{ + const char *elfdata; + const char *sectionData[50]; // YES it must be dynamic, but i'm tired. + + t_bool isExecutable; + + t_sint32 nmfSectionIndex; + const void *relaNmfSegment, *relaNmfSegmentEnd; + const void *relaNmfSegmentSymbols; + const char *relaNmfSegmentStrings; + + const t_elf_component_header*elfheader; + + +} t_tmp_elfdescription; + + +t_cm_error ELF64_LoadComponent( + t_uint16 e_machine, + const char *elfdata, + t_elfdescription **elfhandlePtr, + t_tmp_elfdescription *elftmp); +t_cm_error ELF64_ComputeSegment( + t_elfdescription *elfhandle, + t_tmp_elfdescription *elftmp); + +void ELF64_UnloadComponent( + t_elfdescription *elfhandle); + +t_cm_error ELF64_loadSegment( + t_elfdescription *elfhandle, + t_memory_handle *memory, + t_memory_property property); +t_cm_error ELF64_relocateSegments( + t_memory_handle *memories, + t_elfdescription *elf, + t_memory_property property, + void *cbContext); +t_cm_error ELF64_getRelocationMemory( + t_elfdescription *elfhandle, + t_tmp_elfdescription *elftmp, + t_uint32 offsetInNmf, + t_memory_reference *memory); + +const t_elfmemory* MMDSP_getMappingById(t_memory_id memId); +const t_elfmemory* MMDSP_getMappingByName(const char* sectionName, t_instance_property property); +void MMDSP_serializeMemories(t_instance_property property, + const t_elfmemory** codeMemory, const t_elfmemory** thisMemory); +void MMDSP_copySection(t_uint32 origAddr, t_uint32 remoteAddr, t_uint32 sizeInByte); +void MMDSP_bzeroSection(t_uint32 remoteAddr, t_uint32 sizeInByte); +void MMDSP_loadedSection(t_nmf_core_id coreId, t_memory_id memId, t_memory_handle handle); +void MMDSP_unloadedSection(t_nmf_core_id coreId, t_memory_id memId, t_memory_handle handle); + +void MMDSP_copyCode(t_uint64 * remoteAddr64, const char* origAddr, int nb); +void MMDSP_copyData24(t_uint32 * remoteAddr32, const char* origAddr, int nb); +void MMDSP_copyData16(t_uint16 * remoteAddr16, const char* origAddr, int nb); + +t_uint32 cm_resolvSymbol( + void* context, + t_uint32 type, + t_dup_char symbolName, + char* reloc_addr); + + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/elf/inc/elfabi.h b/drivers/staging/nmf-cm/cm/engine/elf/inc/elfabi.h new file mode 100644 index 00000000000..cbcc6db3b9b --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/inc/elfabi.h @@ -0,0 +1,539 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#ifndef _CM_ELF_H +#define _CM_ELF_H 1 + +typedef t_uint16 Elf32_Half; +typedef t_uint16 Elf64_Half; + +typedef t_uint32 Elf32_Word; +typedef t_sint32 Elf32_Sword; +typedef t_uint32 Elf64_Word; +typedef t_sint32 Elf64_Sword; + +typedef t_uint64 Elf32_Xword; +typedef t_sint64 Elf32_Sxword; +typedef t_uint64 Elf64_Xword; +typedef t_sint64 Elf64_Sxword; + +typedef t_uint32 Elf32_Addr; +typedef t_uint64 Elf64_Addr; + +typedef t_uint32 Elf32_Off; +typedef t_uint64 Elf64_Off; + +typedef t_uint16 Elf32_Section; +typedef t_uint16 Elf64_Section; + +typedef Elf32_Half Elf32_Versym; +typedef Elf64_Half Elf64_Versym; + + +/********************************************* + * Header + *********************************************/ +#define EI_NIDENT (16) //!< Size of e_ident[] + +#define EI_MAG0 0 //!< File identification +#define ELFMAG0 0x7f + +#define EI_MAG1 1 //!< File identification +#define ELFMAG1 'E' + +#define EI_MAG2 2 //!< File identification +#define ELFMAG2 'L' + +#define EI_MAG3 3 //!< File identification +#define ELFMAG3 'F' + +#define EI_CLASS 4 //!< File class +#define ELFCLASSNONE 0 //!< Invalid class +#define ELFCLASS32 1 //!< 32-bit objects +#define ELFCLASS64 2 //!< 64-bit objects + +#define EI_DATA 5 //!< Data encoding +#define ELFDATANONE 0 //!< Invalid data encoding +#define ELFDATA2LSB 1 //!< 2's complement, little endian +#define ELFDATA2MSB 2 //!< 2's complement, big endian + +#define EI_VERSION 6 //!< File version + +#define EI_OSABI 7 //!< OS ABI identification +#define ELFOSABI_NONE 0 //!< No extension +#define ELFOSABI_HPUX 1 //!< HP-UX +#define ELFOSABI_NETBSD 2 //!< NetBSD +#define ELFOSABI_LINUX 3 //!< Linux +#define ELFOSABI_SOLARIS 6 //!< Sun Solaris +#define ELFOSABI_AIX 7 //!< AIX +#define ELFOSABI_IRIX 8 //!< IRIX +#define ELFOSABI_FREEBSD 9 //!< FreeBSD +#define ELFOSABI_TRU64 10 //!< Compaq TRU64 UNIX +#define ELFOSABI_MODESTO 11 //!< Novell Modesto +#define ELFOSABI_OPENBSD 12 //!< Open BSD +#define ELFOSABI_OPENVMS 13 //!< Open VMS +#define ELFOSABI_NSK 14 //!< HP Non-Stop-Kernel + +#define EI_ABIVERSION 8 //!< ABI version + +#define EI_PAD 9 //!< Start of padding byte + + +typedef struct +{ + unsigned char e_ident[EI_NIDENT]; //!< The initial bytes mark the file as an object file and provide machine-independent data with which to decode and interpret the file's contents + Elf32_Half e_type; //!< This member identifies the object file type + Elf32_Half e_machine; //!< This member's value specifies the required architecture for an individual file + Elf32_Word e_version; //!< This member identifies the object file version + Elf32_Addr e_entry; //!< This member gives the virtual address to which the system first transfers control, thus starting the process + Elf32_Off e_phoff; //!< This member holds the program header table's file offset in bytes + Elf32_Off e_shoff; //!< This member holds the section header table's file offset in bytes + Elf32_Word e_flags; //!< This member holds processor-specific flags associated with the file + Elf32_Half e_ehsize; //!< This member holds the ELF header's size in bytes + Elf32_Half e_phentsize; //!< This member holds the size in bytes of one entry in the file's program header table; all entries are the same size + Elf32_Half e_phnum; //!< This member holds the number of entries in the program header table + Elf32_Half e_shentsize; //!< This member holds a section header's size in bytes + Elf32_Half e_shnum; //!< This member holds the number of entries in the section header table + Elf32_Half e_shstrndx; //!< This member holds the section header table index of the entry associated with the section name string table +} Elf32_Ehdr; //!< 32bit Entry Header + +typedef struct +{ + unsigned char e_ident[EI_NIDENT]; //!< The initial bytes mark the file as an object file and provide machine-independent data with which to decode and interpret the file's contents + Elf64_Half e_type; //!< This member identifies the object file type + Elf64_Half e_machine; //!< This member's value specifies the required architecture for an individual file + Elf64_Word e_version; //!< This member identifies the object file version + Elf64_Addr e_entry; //!< This member gives the virtual address to which the system first transfers control, thus starting the process + Elf64_Off e_phoff; //!< This member holds the program header table's file offset in bytes + Elf64_Off e_shoff; //!< This member holds the section header table's file offset in bytes + Elf64_Word e_flags; //!< This member holds processor-specific flags associated with the file + Elf64_Half e_ehsize; //!< This member holds the ELF header's size in bytes + Elf64_Half e_phentsize; //!< This member holds the size in bytes of one entry in the file's program header table; all entries are the same size + Elf64_Half e_phnum; //!< This member holds the number of entries in the program header table + Elf64_Half e_shentsize; //!< This member holds a section header's size in bytes + Elf64_Half e_shnum; //!< This member holds the number of entries in the section header table + Elf64_Half e_shstrndx; //!< This member holds the section header table index of the entry associated with the section name string table +} Elf64_Ehdr; //!< 64bit Entry Header + +/* + * e_type + */ +#define ET_NONE 0 //!< No file type +#define ET_REL 1 //!< Relocatable file +#define ET_EXEC 2 //!< Executable file +#define ET_DYN 3 //!< Shared object file +#define ET_CORE 4 //!< Core file +#define ET_LOOS 0xfe00 //!< Operating system-specific +#define ET_HIOS 0xfeff //!< Operating system-specific +#define ET_LOPROC 0xff00 //!< Processor-specific +#define ET_HIPROC 0xffff //!< Processor-specific + +/* + * e_machine + */ +#define EM_NONE 0 //!< No machine +#define EM_M32 1 //!< AT&T WE 32100 +#define EM_SPARC 2 //!< SUN SPARC +#define EM_386 3 //!< Intel 80386 +#define EM_68K 4 //!< Motorola 68000 +#define EM_88K 5 //!< Motorola 88000 +#define EM_860 7 //!< Intel 80860 +#define EM_MIPS 8 //!< MIPS I architecture +#define EM_S370 9 //!< IBM System/370 +#define EM_MIPS_RS3_LE 10 //!< MIPS R3000 little-endian +#define EM_PARISC 15 //!< HPPA +#define EM_VPP500 17 //!< Fujitsu VPP500 +#define EM_SPARC32PLUS 18 //!< Enhanced instruction set SPARC +#define EM_960 19 //!< Intel 80960 +#define EM_PPC 20 //!< PowerPC +#define EM_PPC64 21 //!< 64-bit PowerPC +#define EM_S390 22 //!< IBM System/390 Processor +#define EM_V800 36 //!< NEC V800 +#define EM_FR20 37 //!< Fujitsu FR20 +#define EM_RH32 38 //!< TRW RH-32 +#define EM_RCE 39 //!< Motorola RCE +#define EM_ARM 40 //!< Advanced RISC Machines ARM +#define EM_FAKE_ALPHA 41 //!< Digital Alpha +#define EM_SH 42 //!< Hitachi SH +#define EM_SPARCV9 43 //!< SPARC Version 9 +#define EM_TRICORE 44 //!< Siemens TriCore embedded processor +#define EM_ARC 45 //!< Argonaut RISC Core, Argonaut Technologies Inc +#define EM_H8_300 46 //!< Hitachi H8/300 +#define EM_H8_300H 47 //!< Hitachi H8/300H +#define EM_H8S 48 //!< Hitachi H8S +#define EM_H8_500 49 //!< Hitachi H8/500 +#define EM_IA_64 50 //!< Intel IA-64 processor architecture +#define EM_MIPS_X 51 //!< Stanford MIPS-X +#define EM_COLDFIRE 52 //!< Motorola ColdFire +#define EM_68HC12 53 //!< Motorola M68HC12 +#define EM_MMA 54 //!< Fujitsu MMA Multimedia Accelerator +#define EM_PCP 55 //!< Siemens PCP +#define EM_NCPU 56 //!< Sony nCPU embedded RISC processor +#define EM_NDR1 57 //!< Denso NDR1 microprocessor +#define EM_STARCORE 58 //!< Motorola Start*Core processor +#define EM_ME16 59 //!< Toyota ME16 processor +#define EM_ST100 60 //!< STMicroelectronics ST100 processor +#define EM_TINYJ 61 //!< Advanced Logic Corp. TinyJ embedded processor family +#define EM_X86_64 62 //!< AMD x86-64 architecture +#define EM_PDSP 63 //!< Sony DSP Processor +#define EM_PDP10 64 //!< Digital Equipment Corp. PDP-10 +#define EM_PDP11 65 //!< Digital Equipment Corp. PDP-11 +#define EM_FX66 66 //!< Siemens FX66 microcontroller +#define EM_ST9PLUS 67 //!< STMicroelectronics ST9+ 8/16 bit microcontroller +#define EM_ST7 68 //!< STMicroelectronics ST7 8-bit microcontroller +#define EM_68HC16 69 //!< Motorola MC68HC16 Microcontroller +#define EM_68HC11 70 //!< Motorola MC68HC11 Microcontroller +#define EM_68HC08 71 //!< Motorola MC68HC08 Microcontroller +#define EM_68HC05 72 //!< Motorola MC68HC05 Microcontroller +#define EM_SVX 73 //!< Silicon Graphics SVx +#define EM_ST19 74 //!< STMicroelectronics ST19 8-bit microcontroller +#define EM_VAX 75 //!< Digital VAX +#define EM_CRIS 76 //!< Axis Communications 32-bit embedded processor +#define EM_JAVELIN 77 //!< Infifineon Technologies 32-bit embedded processor +#define EM_FIREPATH 78 //!< Element 14 64-bit DSP Processor +#define EM_ZSP 79 //!< LSI Logic 16-bit DSP Processor +#define EM_MMIX 80 //!< Donald Knuth's educational 64-bit processor +#define EM_HUANY 81 //!< Harvard University machine-independent object files +#define EM_PRISM 82 //!< SiTera Prism +#define EM_AVR 83 //!< Atmel AVR 8-bit microcontroller +#define EM_FR30 84 //!< Fujitsu FR30 +#define EM_D10V 85 //!< Mitsubishi D10V +#define EM_D30V 86 //!< Mitsubishi D30V +#define EM_V850 87 //!< NEC v850 +#define EM_M32R 88 //!< Mitsubishi M32R +#define EM_MN10300 89 //!< Matsushita MN10300 +#define EM_MN10200 90 //!< Matsushita MN10200 +#define EM_PJ 91 //!< picoJava +#define EM_OPENRISC 92 //!< OpenRISC 32-bit embedded processor +#define EM_ARC_A5 93 //!< ARC International ARCompact processor (old spelling/synonym: EM_ARC_A5) +#define EM_XTENSA 94 //!< Tensilica Xtensa Architecture +#define EM_VIDEOCORE 95 //!< Alphamosaic VideoCore processor +#define EM_TMM_GPP 96 //!< Thompson Multimedia General Purpose Processor +#define EM_NS32K 97 //!< National Semiconductor 32000 series +#define EM_TPC 98 //!< Tenor Network TPC processor +#define EM_SNP1K 99 //!< Trebia SNP 1000 processor +#define EM_ST200 100 //!< STMicroelectronics (www.st.com) ST200 microcontroller +#define EM_IP2K 101 //!< Ubicom IP2xxx microcontroller family +#define EM_MAX 102 //!< MAX Processor +#define EM_CR 103 //!< National Semiconductor CompactRISC microprocessor +#define EM_F2MC16 104 //!< Fujitsu F2MC16 +#define EM_MSP430 105 //!< Texaxas Instruments embedded microcontroller msp430 +#define EM_BLACKFIN 106 //!< Analog Devices Blackfin (DSP) processor +#define EM_SE_C33 107 //!< S1C33 Family of Seiko Epspson processors +#define EM_SEP 108 //!< Sharp embedded microprocessor +#define EM_ARCA 109 //!< Arca RISC Microprocessor +#define EM_UNICORE 110 //!< Microprocessor series from PKU-Unity Ltd. and MPRC of Peking University +#define EM_EXCESS 111 //!< eXcess: 16/32/64-bit configurable embedded CPU +#define EM_DXP 112 //!< Icera Semiconductor Inc. Deep Execution Processor +#define EM_ALTERA_NIOS2 113 //!< Altera Nios II soft-core processor +#define EM_CRX 114 //!< National Semiconductor CompactRISC CRX microprocessor +#define EM_XGATE 115 //!< Motorola XGATE embedded processor +#define EM_C166 116 //!< Infifineon C16x/XC16x processor +#define EM_M16C 117 //!< Renesas M16C series microprocessors +#define EM_DSPIC30F 118 //!< Microchip Technology dsPIC30F Digital Signal Controller +#define EM_CE 119 //!< Freescale Communication Engine RISC core +#define EM_M32C 120 //!< Renesas M32C series microprocessors +#define EM_TSK3000 131 //!< Altium TSK3000 core +#define EM_RS08 132 //!< Freescale RS08 embedded processor +#define EM_ECOG2 134 //!< Cyan Technology eCOG2 microprocessor +#define EM_SCORE7 135 //!< Sunplus S+core7 RISC processor +#define EM_DSP24 136 //!< New Japan Radio (NJR) 24-bit DSP Processor +#define EM_VIDEOCORE3 137 //!< Broadcom VideoCore III processor +#define EM_LATTICEMICO32 138 //!< RISC processor for Lattice FPGA architecture +#define EM_SE_C17 139 //!< Seiko Epspson C17 family +#define EM_TI_C6000 140 //!< The Texaxas Instruments TMS320C6000 DSP family +#define EM_TI_C2000 141 //!< The Texaxas Instruments TMS320C2000 DSP family +#define EM_TI_C5500 142 //!< The Texaxas Instruments TMS320C55x DSP family +#define EM_MMDSP_PLUS 160 //!< STMicroelectronics 64bit VLIW Data Signal Processor +#define EM_CYPRESS_M8C 161 //!< Cypress M8C microprocessor +#define EM_R32C 162 //!< Renesas R32C series microprocessors +#define EM_TRIMEDIA 163 //!< NXP Semiconductors TriMedia architecture family +#define EM_QDSP6 164 //!< QUALCOMM DSP6 Processor +#define EM_8051 165 //!< Intel 8051 and variants +#define EM_STXP7X 166 //!< STMicroelectronics STxP7x family of configurable and extensible RISC processors +#define EM_NDS32 167 //!< Andes Technology compact code size embedded RISC processor family +#define EM_ECOG1 168 //!< Cyan Technology eCOG1X family +#define EM_ECOG1X 168 //!< Cyan Technology eCOG1X family +#define EM_MAXQ30 169 //!< Dallas Semiconductor MAXQ30 Core Micro-controllers +#define EM_XIMO16 170 //!< New Japan Radio (NJR) 16-bit DSP Processor +#define EM_MANIK 171 //!< M2000 Reconfigurable RISC Microprocessor +#define EM_CRAYNV2 172 //!< Cray Inc. NV2 vector architecture +#define EM_RX 173 //!< Renesas RX family +#define EM_METAG 174 //!< Imagination Technologies META processor architecture +#define EM_MCST_ELBRUS 175 //!< MCST Elbrus general purpose hardware architecture +#define EM_ECOG16 176 //!< Cyan Technology eCOG16 family +#define EM_CR16 177 //!< National Semiconductor CompactRISC CR16 16-bit microprocessor +#define EM_ETPU 178 //!< Freescale Extended Time Processing Unit +#define EM_SLE9X 179 //!< Infifineon Technologies SLE9X core +#define EM_AVR32 185 //!< Atmel Corporation 32-bit microprocessor family +#define EM_STM8 186 //!< STMicroeletronics STM8 8-bit microcontroller +#define EM_TILE64 187 //!< Tilera TILE64 multicore architecture family +#define EM_TILEPRO 188 //!< Tilera TILEPro multicore architecture family +#define EM_MICROBLAZE 189 //!< Xilinx MicroBlaze 32-bit RISC soft processor core +#define EM_CUDA 190 //!< NVIDIA CUDA architecture +#define EM_TILEGX 191 //!< Tilera TILE-Gx multicore architecture family + +/* + * e_version (version) + */ +#define EV_NONE 0 //!< Invalid version +#define EV_CURRENT 1 //!< Current version + + +/********************************************* + * Section + *********************************************/ +typedef struct +{ + Elf32_Word sh_name; //!< This member specifies the name of the section + Elf32_Word sh_type; //!< This member categorizes the section's contents and semantics + Elf32_Word sh_flags; //!< Sections support 1-bit flags that describe miscellaneous attributes + Elf32_Addr sh_addr; //!< If the section will appear in the memory image of a process, this member gives the address at which the section's first byte should reside + Elf32_Off sh_offset; //!< This member's value gives the byte offset from the beginning of the file to the first byte in the section + Elf32_Word sh_size; //!< This member gives the section's size in bytes + Elf32_Word sh_link; //!< This member holds a section header table index link, whose interpretation depends on the section type + Elf32_Word sh_info; //!< This member holds extra information, whose interpretation depends on the section type + Elf32_Word sh_addralign; //!< Some sections have address alignment constraints + Elf32_Word sh_entsize; //!< Some sections hold a table of fixed-size entries, such as a symbol table +} Elf32_Shdr; //!< 32bit Section header + +typedef struct +{ + Elf64_Word sh_name; //!< This member specifies the name of the section + Elf64_Word sh_type; //!< This member categorizes the section's contents and semantics + Elf64_Xword sh_flags; //!< Sections support 1-bit flags that describe miscellaneous attributes + Elf64_Addr sh_addr; //!< If the section will appear in the memory image of a process, this member gives the address at which the section's first byte should reside + Elf64_Off sh_offset; //!< This member's value gives the byte offset from the beginning of the file to the first byte in the section + Elf64_Xword sh_size; //!< This member gives the section's size in bytes + Elf64_Word sh_link; //!< This member holds a section header table index link, whose interpretation depends on the section type + Elf64_Word sh_info; //!< This member holds extra information, whose interpretation depends on the section type + Elf64_Xword sh_addralign; //!< Some sections have address alignment constraints + Elf64_Xword sh_entsize; //!< Some sections hold a table of fixed-size entries, such as a symbol table +} Elf64_Shdr; //!< 64bit Section header + +/* + * Special Section Indexes + */ +#define SHN_UNDEF 0 //!< This value marks an undefined, missing, irrelevant, or otherwise meaningless section reference +#define SHN_LORESERVE 0xff00 //!< This value specifies the lower bound of the range of reserved indexes +#define SHN_LOPROC 0xff00 //!< Values in this inclusive range are reserved for processor-specific semantics +#define SHN_HIPROC 0xff1f //!< Values in this inclusive range are reserved for processor-specific semantics +#define SHN_LOOS 0xff20 //!< Values in this inclusive range are reserved for operating system-specific semantics +#define SHN_HIOS 0xff3f //!< Values in this inclusive range are reserved for operating system-specific semantics +#define SHN_ABS 0xfff1 //!< This value specifies absolute values for the corresponding reference +#define SHN_COMMON 0xfff2 //!< Symbols defined relative to this section are common symbols +#define SHN_XINDEX 0xffff //!< This value is an escape value +#define SHN_HIRESERVE 0xffff //!< This value specifies the upper bound of the range of reserved indexes + +/* + * sh_type + */ +#define SHT_NULL 0 //!< This value marks the section header as inactive +#define SHT_PROGBITS 1 //!< The section holds information defined by the program +#define SHT_SYMTAB 2 //!< These sections hold a symbol table +#define SHT_STRTAB 3 //!< The section holds a string table +#define SHT_RELA 4 //!< The section holds relocation entries with explicit addends, such as type Elf32_Rela for the 32-bit class of object files or type Elf64_Rela for the 64-bit class of object files +#define SHT_HASH 5 //!< The section holds a symbol hash table +#define SHT_DYNAMIC 6 //!< The section holds information for dynamic linking +#define SHT_NOTE 7 //!< The section holds information that marks the file in some way +#define SHT_NOBITS 8 //!< A section of this type occupies no space in the file but otherwise resembles SHT_PROGBITS +#define SHT_REL 9 //!< The section holds relocation entries without explicit addends, such as type Elf32_Rel for the 32-bit class of object files or type Elf64_Rel for the 64-bit class of object files +#define SHT_SHLIB 10 //!< This section type is reserved but has unspecified semantics +#define SHT_DYNSYM 11 //!< +#define SHT_INIT_ARRAY 14 //!< This section contains an array of pointers to initialization functions +#define SHT_FINI_ARRAY 15 //!< This section contains an array of pointers to termination functions +#define SHT_PREINIT_ARRAY 16 //!< This section contains an array of pointers to functions that are invoked before all other initialization functions +#define SHT_GROUP 17 //!< This section defines a section group +#define SHT_SYMTAB_SHNDX 18 //!< This section is associated with a section of type SHT_SYMTAB and is required if any of the section header indexes referenced by that symbol table contain the escape value SHN_XINDEX +#define SHT_LOOS 0x60000000 //!< Values in this inclusive range are reserved for operating system-specific semantics +#define SHT_HIOS 0x6fffffff //!< Values in this inclusive range are reserved for operating system-specific semantics +#define SHT_LOPROC 0x70000000 //!< Values in this inclusive range are reserved for processor-specific semantics +#define SHT_HIPROC 0x7fffffff //!< Values in this inclusive range are reserved for processor-specific semantics +#define SHT_LOUSER 0x80000000 //!< This value specifies the upper bound of the range of indexes reserved for application programs +#define SHT_HIUSER 0x8fffffff //!< This value specifies the upper bound of the range of indexes reserved for application programs + +/* + * sh_flags + */ +#define SHF_WRITE 0x1 //!< The section contains data that should be writable during process execution +#define SHF_ALLOC 0x2 //!< The section occupies memory during process execution +#define SHF_EXECINSTR 0x4 //!< The section contains executable machine instructions +#define SHF_MERGE 0x10 //!< The data in the section may be merged to eliminate duplication +#define SHF_STRINGS 0x20 //!< The data elements in the section consist of null-terminated character strings +#define SHF_INFO_LINK 0x40 //!< The sh_info field of this section header holds a section header table index +#define SHF_LINK_ORDER 0x80 //!< This flag adds special ordering requirements for link editors +#define SHF_OS_NONCONFORMING 0x100 //!< This section requires special OS-specific processing (beyond the standard linking rules) to avoid incorrect behavior +#define SHF_GROUP 0x200 //!< This section is a member (perhaps the only one) of a section group +#define SHF_TLS 0x400 //!< This section holds Thread-Local Storage, meaning that each separate execution flow has its own distinct instance of this data +#define SHF_MASKOS 0x0ff00000 //!< All bits included in this mask are reserved for operating system-specific semantics +#define SHF_MASKPROC 0xf0000000 //!< All bits included in this mask are reserved for processor-specific semantics + + +/********************************************* + * Symbol + *********************************************/ +typedef struct +{ + Elf32_Word st_name; //!< This member holds an index into the object file's symbol string table, which holds the character representations of the symbol names + Elf32_Addr st_value; //!< This member gives the value of the associated symbol + Elf32_Word st_size; //!< Many symbols have associated sizes + unsigned char st_info; //!< This member specifies the symbol's type and binding attributes + unsigned char st_other; //!< This member currently specifies a symbol's visibility + Elf32_Section st_shndx; //!< Every symbol table entry is defined in relation to some section +} Elf32_Sym; + +typedef struct +{ + Elf64_Word st_name; //!< This member holds an index into the object file's symbol string table, which holds the character representations of the symbol names + unsigned char st_info; //!< This member specifies the symbol's type and binding attributes + unsigned char st_other; //!< This member currently specifies a symbol's visibility + Elf64_Section st_shndx; //!< Every symbol table entry is defined in relation to some section + Elf64_Addr st_value; //!< This member gives the value of the associated symbol + Elf64_Xword st_size; //!< Many symbols have associated sizes +} Elf64_Sym; + +/* + * st_info + */ +#define ELF32_ST_BIND(i) ((i)>>4) +#define ELF32_ST_TYPE(i) ((i)&0xf) +#define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf)) + +#define ELF64_ST_BIND(i) ((i)>>4) +#define ELF64_ST_TYPE(i) ((i)&0xf) +#define ELF64_ST_INFO(b,t) (((b)<<4)+((t)&0xf)) + + +/* st_info (symbol binding) */ +#define STB_LOCAL 0 //!< Local symbols are not visible outside the object file containing their definition +#define STB_GLOBAL 1 //!< Global symbols are visible to all object files being combined +#define STB_WEAK 2 //!< Weak symbols resemble global symbols, but their definitions have lower precedence +#define STB_LOOS 10 //!< Values in this inclusive range are reserved for operating system-specific semantics +#define STB_HIOS 12 //!< Values in this inclusive range are reserved for operating system-specific semantics +#define STB_LOPROC 13 //!< Values in this inclusive range are reserved for processor-specific semantics +#define STB_HIPROC 15 //!< Values in this inclusive range are reserved for processor-specific semantics + +/* st_info (symbol type) */ +#define STT_NOTYPE 0 //!< The symbol's type is not specified +#define STT_OBJECT 1 //!< The symbol is associated with a data object, such as a variable, an array, and so on +#define STT_FUNC 2 //!< The symbol is associated with a function or other executable code +#define STT_SECTION 3 //!< The symbol is associated with a section +#define STT_FILE 4 //!< Conventionally, the symbol's name gives the name of the source file associated with the object file +#define STT_COMMON 5 //!< The symbol labels an uninitialized common block +#define STT_TLS 6 //!< The symbol specifies a Thread-Local Storage entity +#define STT_LOOS 10 //!< Values in this inclusive range are reserved for operating system-specific semantics +#define STT_HIOS 12 //!< Values in this inclusive range are reserved for operating system-specific semantics +#define STT_LOPROC 13 //!< Values in this inclusive range are reserved for processor-specific semantics +#define STT_HIPROC 15 //!< Values in this inclusive range are reserved for processor-specific semantics + +/* + * st_other + */ +#define ELF32_ST_VISIBILITY(o) ((o)&0x3) +#define ELF64_ST_VISIBILITY(o) ((o)&0x3) + + +#define STV_DEFAULT 0 //!< The visibility of symbols with the STV_DEFAULT attribute is as specified by the symbol's binding type +#define STV_INTERNAL 1 //!< A symbol defined in the current component is protected if it is visible in other components but not preemptable, meaning that any reference to such a symbol from within the defining component must be resolved to the definition in that component, even if there is a definition in another component that would preempt by the default rules +#define STV_HIDDEN 2 //!< A symbol defined in the current component is hidden if its name is not visible to other components +#define STV_PROTECTED 3 //!< The meaning of this visibility attribute may be defined by processor supplements to further constrain hidden symbols + + +/********************************************* + * Relocation + *********************************************/ +typedef struct +{ + Elf32_Addr r_offset; //!< This member gives the location at which to apply the relocation action + Elf32_Word r_info; //!< This member gives both the symbol table index with respect to which the relocation must be made, and the type of relocation to apply +} Elf32_Rel; //!< 32bits Relocation Entries + +typedef struct +{ + Elf64_Addr r_offset; //!< This member gives the location at which to apply the relocation action + Elf64_Xword r_info; //!< This member gives both the symbol table index with respect to which the relocation must be made, and the type of relocation to apply +} Elf64_Rel; //!< 32bits Relocation Entries + +typedef struct +{ + Elf32_Addr r_offset; //!< This member gives the location at which to apply the relocation action + Elf32_Word r_info; //!< This member gives both the symbol table index with respect to which the relocation must be made, and the type of relocation to apply + Elf32_Sword r_addend; //!< This member specifies a constant addend used to compute the value to be stored into the relocatable field +} Elf32_Rela; //!< 32bits Relocation Addend Entries + +typedef struct +{ + Elf64_Addr r_offset; //!< This member gives the location at which to apply the relocation action + Elf64_Xword r_info; //!< This member gives both the symbol table index with respect to which the relocation must be made, and the type of relocation to apply + Elf64_Sxword r_addend; //!< This member specifies a constant addend used to compute the value to be stored into the relocatable field +} Elf64_Rela; //!< 32bits Relocation Addend Entries + + +/* + * r_info + */ +#define ELF32_R_SYM(i) ((i)>>8) +#define ELF32_R_TYPE(i) ((unsigned char)(i)) +#define ELF32_R_INFO(s,t) (((s)<<8)+(unsigned char)(t)) + +#define ELF64_R_SYM(i) ((i)>>32) +#define ELF64_R_TYPE(i) ((i)&0xffffffffL) +#define ELF64_R_INFO(s,t) (((s)<<32)+((t)&0xffffffffL)) + + + +/********************************************* + * Program + *********************************************/ +typedef struct +{ + Elf32_Word p_type; //!< This member tells what kind of segment this array element describes or how to interpret the array element's information + Elf32_Off p_offset; //!< This member gives the offset from the beginning of the file at which the first byte of the segment resides + Elf32_Addr p_vaddr; //!< This member gives the virtual address at which the first byte of the segment resides in memory + Elf32_Addr p_paddr; //!< On systems for which physical addressing is relevant, this member is reserved for the segment's physical address + Elf32_Word p_filesz; //!< This member gives the number of bytes in the file image of the segment; it may be zero + Elf32_Word p_memsz; //!< This member gives the number of bytes in the memory image of the segment; it may be zero + Elf32_Word p_flags; //!< This member gives flags relevant to the segment + Elf32_Word p_align; //!< As ``Program Loading'' describes in this chapter of the processor supplement, loadable process segments must have congruent values for p_vaddr and p_offset, modulo the page size +} Elf32_Phdr; //!< 32bits Program header + +typedef struct +{ + Elf64_Word p_type; //!< This member tells what kind of segment this array element describes or how to interpret the array element's information + Elf64_Word p_flags; //!< This member gives flags relevant to the segment + Elf64_Off p_offset; //!< This member gives the offset from the beginning of the file at which the first byte of the segment resides + Elf64_Addr p_vaddr; //!< This member gives the virtual address at which the first byte of the segment resides in memory + Elf64_Addr p_paddr; //!< On systems for which physical addressing is relevant, this member is reserved for the segment's physical address + Elf64_Xword p_filesz; //!< This member gives the number of bytes in the file image of the segment; it may be zero + Elf64_Xword p_memsz; //!< This member gives the number of bytes in the memory image of the segment; it may be zero + Elf64_Xword p_align; //!< As ``Program Loading'' describes in this chapter of the processor supplement, loadable process segments must have congruent values for p_vaddr and p_offset, modulo the page size +} Elf64_Phdr; //!< 64bits Program header + +/* + * p_type + */ +#define PT_NULL 0 //!< The array element is unused; other members' values are undefined +#define PT_LOAD 1 //!< The array element specifies a loadable segment, described by p_filesz and p_memsz +#define PT_DYNAMIC 2 //!< The array element specifies dynamic linking information +#define PT_INTERP 3 //!< The array element specifies the location and size of a null-terminated path name to invoke as an interpreter +#define PT_NOTE 4 //!< The array element specifies the location and size of auxiliary information +#define PT_SHLIB 5 //!< This segment type is reserved but has unspecified semantics +#define PT_PHDR 6 //!< The array element, if present, specifies the location and size of the program header table itself, both in the file and in the memory image of the program +#define PT_TLS 7 //!< The array element specifies the Thread-Local Storage template +#define PT_LOOS 0x60000000 //!< Values in this inclusive range are reserved for operating system-specific semantics +#define PT_HIOS 0x6fffffff //!< Values in this inclusive range are reserved for operating system-specific semantics +#define PT_LOPROC 0x70000000 //!< Values in this inclusive range are reserved for processor-specific semantics +#define PT_HIPROC 0x7fffffff //!< Values in this inclusive range are reserved for processor-specific semantics + +/* + * p_flags + */ +#define PF_X (1 << 0) //!< Execute +#define PF_W (1 << 1) //!< Write +#define PF_R (1 << 2) //!< Read +#define PF_MASKOS 0x0ff00000 //!< Unspecified +#define PF_MASKPROC 0xf0000000 //!< Unspecified + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/elf/inc/elfapi.h b/drivers/staging/nmf-cm/cm/engine/elf/inc/elfapi.h new file mode 100644 index 00000000000..cce6d158b4e --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/inc/elfapi.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Elf loder internal methods. + * + * \defgroup ELFLOADER MMDSP ELF loader. + */ +#ifndef __INC_CM_ELFLOADER_H +#define __INC_CM_ELFLOADER_H + +#include <cm/engine/elf/inc/common.h> + +/*! + * \internal + * \brief ELF Parsing & checking + * \ingroup ELFLOADER + */ +t_cm_error cm_ELF_CheckFile( + const char *elfdata, + t_bool temporaryDescription, + t_elfdescription **elfhandlePtr); + +void cm_ELF_ReleaseDescription( + t_uint32 requireNumber, t_interface_require *requires, + t_uint32 attributeNumber, t_attribute *attributes, + t_uint32 propertyNumber, t_property *properties, + t_uint32 provideNumber, t_interface_provide *provides); + +/*! + * \internal + * \brief ELF closing + * \ingroup ELFLOADER + */ +void cm_ELF_CloseFile( + t_bool temporaryDescription, + t_elfdescription *elfhandle); + +/*! + * \internal + * \brief Load a component template shared memories. + * + * \note In case of error, part of memory could have been allocated and must be free by calling cm_DSPABI_FreeTemplate. + */ +t_cm_error cm_ELF_LoadTemplate( + t_cm_domain_id domainId, + t_elfdescription *elfhandle, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY], + t_bool isSingleton); + +/*! + * \internal + * \brief Clean cache memory of a component template shared code. + */ +void cm_ELF_FlushTemplate( + t_nmf_core_id coreId, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY]); + +void cm_ELF_FlushInstance( + t_nmf_core_id coreId, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY], + t_memory_handle privateMemories[NUMBER_OF_MMDSP_MEMORY]); + +/*! + * \internal + * \brief Load a component instance private memories. + * + * \note In case of error, part of memory could have been allocated and must be free by calling cm_DSPABI_FreeInstance. + */ +t_cm_error cm_ELF_LoadInstance( + t_cm_domain_id domainId, + t_elfdescription *elfhandle, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY], + t_memory_handle privateMemories[NUMBER_OF_MMDSP_MEMORY], + t_bool isSingleton); + +void cm_ELF_FreeInstance( + t_nmf_core_id coreId, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY], + t_memory_handle privateMemories[NUMBER_OF_MMDSP_MEMORY]); +void cm_ELF_FreeTemplate( + t_nmf_core_id coreId, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY]); + + +t_cm_error cm_ELF_relocateSharedSegments( + t_memory_handle *memories, + t_elfdescription *elfhandle, + void *cbContext); +t_cm_error cm_ELF_relocatePrivateSegments( + t_memory_handle *memories, + t_elfdescription *elfhandle, + void *cbContext); +void cm_ELF_performRelocation( + t_uint32 type, + const char *symbol_name, + t_uint32 symbol_addr, + char *reloc_addr); +t_cm_error cm_ELF_GetMemory( + t_elfdescription *elf, + t_tmp_elfdescription *elftmp, + t_uint32 address, + t_memory_purpose purpose, + t_memory_reference *memory); + + +#include <cm/engine/component/inc/component_type.h> + +t_cm_error cm_DSPABI_AddLoadMap( + t_cm_domain_id domainId, + const char* templateName, + const char* localname, + t_memory_handle *memories, + void *componentHandle); +t_cm_error cm_DSPABI_RemoveLoadMap( + t_cm_domain_id domainId, + const char* templateName, + t_memory_handle *memories, + const char* localname, + void *componentHandle); + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/elf/inc/memory.h b/drivers/staging/nmf-cm/cm/engine/elf/inc/memory.h new file mode 100644 index 00000000000..9eab94f173c --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/inc/memory.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Elf memory. + */ +#ifndef __INC_CM_ELF_MEMORY_H +#define __INC_CM_ELF_MEMORY_H + +#include <cm/engine/dsp/inc/dsp.h> + +/** + * \brief Memory identifier + */ +typedef t_uint8 t_memory_id; + +/** + * \brief Memory property + */ +typedef enum { + MEM_FOR_MULTIINSTANCE, + MEM_FOR_SINGLETON, + MEM_FOR_LAST +} t_instance_property; + +/** + * \brief Memory prupose (for processor with different address space for code and data/ + */ +typedef enum { + MEM_CODE, + MEM_DATA +} t_memory_purpose; + +/** + * \brief Memory property + */ +typedef enum { + MEM_PRIVATE, + MEM_SHARABLE, +} t_memory_property; + +/** + * \brief Elf memory mapping description + */ +typedef struct +{ + t_memory_id id; + t_dsp_memory_type_id dspMemType; + t_uint32 startAddr; + t_cm_memory_alignment memAlignement; + t_memory_property property; + t_memory_purpose purpose; + t_uint8 fileEntSize; + t_uint8 memEntSize; + char* memoryName; +} t_elfmemory; + +#define NUMBER_OF_MMDSP_MEMORY 15 + +/* + * \brief Elf segment description + */ +typedef struct { + // Data in Bytes + t_uint32 sumSize; + t_bool sumSizeSetted; + t_cm_logical_address hostAddr; // Valid only if section Load in memory + t_uint32 maxAlign; + // Data in word + t_uint32 mpcAddr; // Valid only if section Load in memory +} t_elfSegment; + + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/elf/inc/mmdsp-loadmap.h b/drivers/staging/nmf-cm/cm/engine/elf/inc/mmdsp-loadmap.h new file mode 100644 index 00000000000..bb65c0b1244 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/inc/mmdsp-loadmap.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Elf writer internal methods. + * + * \defgroup LOADMAP MMDSP ELF writer (a linker in fact). + */ +#ifndef __INC_CM_LOADMAP_H +#define __INC_CM_LOADMAP_H + +#include <cm/inc/cm_type.h> + +/* + * Align with loadmap : + * https://codex.cro.st.com/wiki/index.php?pagename=Specification%2FLoadmap%2Fv1.2&group_id=310 + */ +#define LOADMAP_MAGIC_NUMBER 0xFBBF + +#define LOADMAP_VERSION_MSB 1 +#define LOADMAP_VERSION_LSB 2 + +struct LoadMapItem +{ + const char* pSolibFilename; // Filename of shared library object + void* pAddrProg; // Load address of program section + void* pAddrEmbProg; // Load address of embedded program section + void* pThis; // Data base address of component instance + void* pARMThis; // ARM component debug ID + const char* pComponentName; // Pretty name of the component instance, NULL if none. + struct LoadMapItem* pNextItem;// Pointer on the next list item, NULL if last one. + void* pXROM; // Start address of XROM + void* pYROM; // Start address of YROM +}; + +struct LoadMapHdr +{ + t_uint16 nMagicNumber; // Equal to 0xFBBF. + t_uint16 nVersion; // The version of the load map format. + t_uint32 nRevision; // A counter incremented at each load map list modification. + struct LoadMapItem* pFirstItem;// Pointer on the first item, NULL if no shared library loaded. +}; + +#endif /* __INC_CM_LOADMAP_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/elf/inc/mmdsp.h b/drivers/staging/nmf-cm/cm/engine/elf/inc/mmdsp.h new file mode 100644 index 00000000000..1662def6c1a --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/inc/mmdsp.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief MMDSP elf. + */ +#ifndef __INC_CM_ELF_MMDSP_H +#define __INC_CM_ELF_MMDSP_H + +#include <cm/engine/elf/inc/common.h> + +#define CODE_MEMORY_INDEX 0 +#define ECODE_MEMORY_INDEX 7 + +#define XROM_MEMORY_INDEX 1 +#define YROM_MEMORY_INDEX 2 +#define PRIVATE_DATA_MEMORY_INDEX 8 +#define SHARE_DATA_MEMORY_INDEX 1 + +/* + * Relocation + */ +#define R_MMDSP_IMM16 5 +#define R_MMDSP_IMM20_16 6 +#define R_MMDSP_IMM20_4 7 +#define R_MMDSP_24 13 + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/elf/inc/mpcal.h b/drivers/staging/nmf-cm/cm/engine/elf/inc/mpcal.h new file mode 100644 index 00000000000..718b7f61ceb --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/inc/mpcal.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief MPC Abraction Layer. + * + * \defgroup MPCAL MPC Abraction Layer. + */ +#ifndef __INC_CM_DSP_MPCAL_H +#define __INC_CM_DSP_MPCAL_H + +#include <cm/inc/cm_type.h> +#include <share/inc/nmf.h> + +#include <cm/engine/elf/inc/common.h> + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/elf/inc/reloc.h b/drivers/staging/nmf-cm/cm/engine/elf/inc/reloc.h new file mode 100644 index 00000000000..b38be48d689 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/inc/reloc.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Elf relocation. + */ +#ifndef __INC_CM_ELF_RELOC_H +#define __INC_CM_ELF_RELOC_H + + +void MMDSP_performRelocation( + t_uint32 type, + const char* symbol_name, + t_uint32 symbol_addr, + char* reloc_addr, + const char* inPlaceAddr, + t_uint32 reloc_offset); + +/* + * + * Return: + * 0x0 returned if symbol not found + * 0xFFFFFFFE returned if out of memory + * 0xFFFFFFFF returned if symbol found in static required binding + */ +typedef t_uint32 (*CBresolvSymbol)( + void* context, + t_uint32 type, + const char* symbolName, + char* reloc_addr); + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/elf/src/elf64.c b/drivers/staging/nmf-cm/cm/engine/elf/src/elf64.c new file mode 100644 index 00000000000..2e0f5928ffd --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/src/elf64.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/elf/inc/common.h> +#include <cm/engine/elf/inc/elfabi.h> + +#include <cm/engine/utils/inc/swap.h> +#include <cm/engine/trace/inc/trace.h> + +typedef Elf64_Half ElfXX_Half; +typedef Elf64_Word ElfXX_Word; +typedef Elf64_Addr ElfXX_Addr; +typedef Elf64_Off ElfXX_Off; + +typedef Elf64_Xword ElfXX_Xword; + +typedef Elf64_Ehdr ElfXX_Ehdr; +typedef Elf64_Shdr ElfXX_Shdr; +typedef Elf64_Sym ElfXX_Sym; +typedef Elf64_Rela ElfXX_Rela; + +#undef ELFXX_R_SYM +#define ELFXX_R_SYM ELF64_R_SYM +#undef ELFXX_R_TYPE +#define ELFXX_R_TYPE ELF64_R_TYPE +#undef ELFXX_R_INFO +#define ELFXX_R_INFO ELF64_R_INFO + +// TODO Here we assume big endian (MMDSP !) +static Elf64_Half swapHalf(Elf64_Half half) +{ + return (Elf64_Half)swap16(half); +} + +static Elf64_Word swapWord(Elf64_Word word) +{ + return (Elf64_Word)swap32(word); +} + +static Elf64_Xword swapXword(Elf64_Xword xword) +{ + return (Elf64_Xword)swap64(xword); +} + +#include "elfxx.c" diff --git a/drivers/staging/nmf-cm/cm/engine/elf/src/elfload.c b/drivers/staging/nmf-cm/cm/engine/elf/src/elfload.c new file mode 100644 index 00000000000..274a1b6b59f --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/src/elfload.c @@ -0,0 +1,773 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + */ +#include <cm/engine/elf/inc/elfapi.h> +#include <cm/engine/elf/inc/mpcal.h> +#include <cm/inc/cm_def.h> + +//#include <cm/engine/component/inc/introspection.h> + +#include <cm/engine/utils/inc/mem.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/utils/inc/swap.h> +#include <cm/engine/utils/inc/string.h> + +static void* getElfHeaderReference(t_tmp_elfdescription *elftmp, void* hdrref) +{ + if(hdrref != NULL) + return (void*)((int)swap32((t_uint32)hdrref) + (int)elftmp->elfheader); + else + return NULL; +} + +static t_dup_char copyElfString(t_tmp_elfdescription *elftmp, char* idx) +{ + return cm_StringDuplicate((char*)getElfHeaderReference(elftmp, (void*)idx)); +} + +static t_cm_error getMemoryOffset( + t_elfdescription *elfhandle, + t_tmp_elfdescription *elftmp, + t_memory_purpose purpose, + const t_uint32 *addressInNmf, + t_memory_reference *memory) { + + if(elftmp->isExecutable) { + return cm_ELF_GetMemory(elfhandle, elftmp, + swap32(*addressInNmf), + purpose, + memory); + } else { + return ELF64_getRelocationMemory(elfhandle, elftmp, + (t_uint32)addressInNmf - (t_uint32)elftmp->elfheader, + memory); + } +} + +static t_cm_error getAdressForExecutableOffsetElsewhere( + t_elfdescription *elfhandle, + t_tmp_elfdescription *elftmp, + const t_uint32 *addressInNmf, + t_memory_reference *memory) { + t_uint32 address; + + address = swap32(*addressInNmf); + if(address == 0xFFFFFFFF) + { + memory->offset = 0x0; + memory->memory = NULL; + return CM_OK; + } + + if(elftmp->isExecutable) + { + memory->offset = address; + memory->memory = NULL; + return CM_OK; + } + + // Error log in elfhandle by previous call will be check in loadTemplate + return ELF64_getRelocationMemory(elfhandle, elftmp, + (t_uint32)addressInNmf - (t_uint32)elftmp->elfheader, + memory); +} + +/* + * Interface Management + */ +static t_interface_description* interfaceList = NULL; + +static t_interface_description* getInterfaceDescription(t_tmp_elfdescription *elftmp, t_elf_interface_description* elfitf) { + t_dup_char itfType; + t_interface_description* itf; + int i; + + itfType = copyElfString(elftmp, elfitf->type); + if(itfType == NULL) + return NULL; + + // Search if interfane already loaded + for(itf = interfaceList; itf != NULL; itf = itf->next) { + if(itf->type == itfType) { + if (itf->methodNumber != elfitf->methodNumber) { + ERROR("When loading component template %s:\n\tNumber of methods in interface type %s\n\tdiffers from previous declaration: was %d, found %d\n", + getElfHeaderReference(elftmp, (void*)elftmp->elfheader->templateName), itfType, itf->methodNumber, elfitf->methodNumber, 0, 0); + //Do not fail for now for compatibility reason + //goto out_itf_type; + } + if (cmIntensiveCheckState) { + for(i = 0; i < itf->methodNumber; i++) { + if (cm_StringCompare(itf->methodNames[i], getElfHeaderReference(elftmp, (void*)elfitf->methodNames[i]), MAX_INTERNAL_STRING_LENGTH) != 0) { + ERROR("When loading component template %s:\n" + "\tName of method number %d in interface type %s\n" + "\tdiffers from previous declaration: previous name was %s, new name found is %s\n", + getElfHeaderReference(elftmp, (void*)elftmp->elfheader->templateName), i, + itfType, itf->methodNames[i], + getElfHeaderReference(elftmp, (void*)elfitf->methodNames[i]), 0); + //Do not fail for now for compatibility reason + //goto out_itf_type; + } + } + } + itf->referenceCounter++; + cm_StringRelease(itfType); + return itf; + } + } + + // Create a new interface if not exists + itf = (t_interface_description*)OSAL_Alloc_Zero(sizeof(t_interface_description) + sizeof(t_dup_char) * (elfitf->methodNumber - 1)); + if(itf == NULL) + goto out_itf_type; + itf->referenceCounter = 1; + itf->type = itfType; + itf->methodNumber = elfitf->methodNumber; + for(i = 0; i < itf->methodNumber; i++) { + itf->methodNames[i] = copyElfString(elftmp, elfitf->methodNames[i]); + if(itf->methodNames[i] == NULL) + goto out_method; + } + + // Put it in Top + itf->next = interfaceList; + interfaceList = itf; + + return itf; + +out_method: + for(i = 0; i < itf->methodNumber; i++) + cm_StringRelease(itf->methodNames[i]); + OSAL_Free(itf); +out_itf_type: + cm_StringRelease(itfType); + return NULL; +} + +static void releaseInterfaceDescription(t_interface_description* itf) { + if(itf == NULL) + return; + + if(--itf->referenceCounter == 0) { + int i; + + // Remove it from list + if(interfaceList == itf) { + interfaceList = interfaceList->next; + } else { + t_interface_description* prev = interfaceList; + while(prev->next != itf) + prev = prev->next; + prev->next = itf->next; + } + + // Destroy interface description + for(i = 0; i < itf->methodNumber; i++) { + cm_StringRelease(itf->methodNames[i]); + } + cm_StringRelease(itf->type); + OSAL_Free(itf); + } +} + + +t_cm_error cm_ELF_CheckFile( + const char *elfdata, + t_bool temporaryDescription, + t_elfdescription **elfhandlePtr) +{ + t_elfdescription *elfhandle; + t_tmp_elfdescription elftmp; + t_cm_error error; + t_uint32 version; + t_uint32 compatibleVersion; + int i, j, k; + + /* + * Sanity check + */ + if (elfdata[EI_MAG0] != ELFMAG0 || + elfdata[EI_MAG1] != ELFMAG1 || + elfdata[EI_MAG2] != ELFMAG2 || + elfdata[EI_MAG3] != ELFMAG3 || + elfdata[EI_CLASS] != ELFCLASS64) + { + ERROR("CM_INVALID_ELF_FILE: component file is not a MMDSP ELF file\n", 0, 0, 0, 0, 0, 0); + return CM_INVALID_ELF_FILE; + } + + /* + * Create elf data + */ + if((error = ELF64_LoadComponent(EM_MMDSP_PLUS, elfdata, elfhandlePtr, &elftmp)) != CM_OK) + return error; + + elfhandle = *elfhandlePtr; + + elfhandle->temporaryDescription = temporaryDescription; + + version = swap32(elftmp.elfheader->nmfVersion); + + compatibleVersion = (VERSION_MAJOR(version) == VERSION_MAJOR(NMF_VERSION)); + if(compatibleVersion) + { + switch(VERSION_MINOR(NMF_VERSION)) + { + case 10: // Compatible with 2.9, 2.10 + compatibleVersion = + (VERSION_MINOR(version) == 9) || + (VERSION_MINOR(version) == 10); + break; + default: // Strict compatibility 2.x == 2.x + compatibleVersion = (VERSION_MINOR(version) == VERSION_MINOR(NMF_VERSION)); + } + } + + if(! compatibleVersion) + { + ERROR("CM_INVALID_ELF_FILE: incompatible version for Component %d.%d.x != CM:%d.%d.x\n", + VERSION_MAJOR(version), VERSION_MINOR(version), + VERSION_MAJOR(NMF_VERSION), VERSION_MINOR(NMF_VERSION), 0, 0); + error = CM_INVALID_ELF_FILE; + goto onerror; + } + + + /* + * Commented since to many noise !!!! + if(VERSION_PATCH(version) != VERSION_PATCH(NMF_VERSION)) + { + WARNING("CM_INVALID_ELF_FILE: incompatible version, Component:%d.%d.%d != CM:%d.%d.%d\n", + VERSION_MAJOR(version), VERSION_MINOR(version), VERSION_PATCH(version), + VERSION_MAJOR(NMF_VERSION), VERSION_MINOR(NMF_VERSION), VERSION_PATCH(NMF_VERSION)); + } + */ + + if((error = ELF64_ComputeSegment(elfhandle, &elftmp)) != CM_OK) + goto onerror; + + // + elfhandle->foundedTemplateName = copyElfString(&elftmp, elftmp.elfheader->templateName); + if(elfhandle->foundedTemplateName == NULL) + goto oom; + elfhandle->minStackSize = swap32(elftmp.elfheader->minStackSize); + + // Get Life-cycle memory + if((error = getAdressForExecutableOffsetElsewhere(elfhandle, &elftmp, &elftmp.elfheader->LCCConstruct, &elfhandle->memoryForConstruct)) != CM_OK) + goto onerror; + if((error = getAdressForExecutableOffsetElsewhere(elfhandle, &elftmp, &elftmp.elfheader->LCCStart, &elfhandle->memoryForStart)) != CM_OK) + goto onerror; + if((error = getAdressForExecutableOffsetElsewhere(elfhandle, &elftmp, &elftmp.elfheader->LCCStop, &elfhandle->memoryForStop)) != CM_OK) + goto onerror; + if((error = getAdressForExecutableOffsetElsewhere(elfhandle, &elftmp, &elftmp.elfheader->LCCDestroy, &elfhandle->memoryForDestroy)) != CM_OK) + goto onerror; + + // Copy attributes information + elfhandle->attributeNumber = swap32(elftmp.elfheader->attributeNumber); + if(elfhandle->attributeNumber > 0) + { + elfhandle->attributes = + (t_attribute*)OSAL_Alloc_Zero(sizeof(t_attribute) * elfhandle->attributeNumber); + if(elfhandle->attributes == NULL) + goto oom; + + if(elfhandle->attributeNumber > 0) + { + t_elf_attribute *attributes = (t_elf_attribute*)getElfHeaderReference(&elftmp, (void*)elftmp.elfheader->attributes); + + for(i = 0; i < elfhandle->attributeNumber; i++) + { + elfhandle->attributes[i].name = copyElfString(&elftmp, attributes[i].name); + if(elfhandle->attributes[i].name == NULL) + goto oom; + + if((error = getMemoryOffset(elfhandle, &elftmp, + MEM_DATA, + &attributes[i].symbols, + &elfhandle->attributes[i].memory)) != CM_OK) + goto onerror; + LOG_INTERNAL(2, " attribute %s mem=%s offset=%x\n", + elfhandle->attributes[i].name, + elfhandle->attributes[i].memory.memory->memoryName, + elfhandle->attributes[i].memory.offset, + 0, 0, 0); + } + } + } + + // Copy properties information + elfhandle->propertyNumber = swap32(elftmp.elfheader->propertyNumber); + if(elfhandle->propertyNumber > 0) + { + elfhandle->properties = + (t_property*)OSAL_Alloc_Zero(sizeof(t_property) * elfhandle->propertyNumber); + if(elfhandle->properties == NULL) + goto oom; + + if(elfhandle->propertyNumber > 0) + { + t_elf_property *properties = (t_elf_property*)getElfHeaderReference(&elftmp, (void*)elftmp.elfheader->properties); + + for(i = 0; i < elfhandle->propertyNumber; i++) + { + elfhandle->properties[i].name = copyElfString(&elftmp, properties[i].name); + if(elfhandle->properties[i].name == NULL) + goto oom; + + elfhandle->properties[i].value = copyElfString(&elftmp, properties[i].value); + if(elfhandle->properties[i].value == NULL) + goto oom; + + LOG_INTERNAL(3, " property %s = %s\n", + elfhandle->properties[i].name, + elfhandle->properties[i].value, + 0, 0, 0, 0); + } + } + } + + // Copy requires information + elfhandle->requireNumber = swap32(elftmp.elfheader->requireNumber); + if(elfhandle->requireNumber > 0) + { + char *ref = getElfHeaderReference(&elftmp, (void*)elftmp.elfheader->requires); + + elfhandle->requires = (t_interface_require*)OSAL_Alloc_Zero(sizeof(t_interface_require) * elfhandle->requireNumber); + if(elfhandle->requires == NULL) + goto oom; + + for(i = 0; i < elfhandle->requireNumber; i++) + { + t_elf_required_interface *require = (t_elf_required_interface*)ref; + t_elf_interface_description *interface = (t_elf_interface_description*)getElfHeaderReference(&elftmp, (void*)require->interface); + + elfhandle->requires[i].name = copyElfString(&elftmp, require->name); + if(elfhandle->requires[i].name == NULL) + goto oom; + + elfhandle->requires[i].requireTypes = require->requireTypes; + elfhandle->requires[i].collectionSize = require->collectionSize; + elfhandle->requires[i].interface = getInterfaceDescription(&elftmp, interface); + if(elfhandle->requires[i].interface == NULL) + goto oom; + + LOG_INTERNAL(2, " require %s <%s> %x\n", + elfhandle->requires[i].name, + elfhandle->requires[i].interface->type, + elfhandle->requires[i].requireTypes, 0, 0, 0); + CM_ASSERT(elfhandle->requires[i].collectionSize != 0); + + ref = (char*)&require->indexes[0]; + + if((elfhandle->requires[i].requireTypes & VIRTUAL_REQUIRE) == 0 && + (elfhandle->requires[i].requireTypes & STATIC_REQUIRE) == 0) + { + elfhandle->requires[i].indexes = + (t_interface_require_index*)OSAL_Alloc_Zero(sizeof(t_interface_require_index) * elfhandle->requires[i].collectionSize); + if(elfhandle->requires[i].indexes == NULL) + goto oom; + + for(j = 0; j < elfhandle->requires[i].collectionSize; j++) + { + t_elf_interface_require_index* index = (t_elf_interface_require_index*)ref; + + elfhandle->requires[i].indexes[j].numberOfClient = swap32(index->numberOfClient); + if(elfhandle->requires[i].indexes[j].numberOfClient != 0) + { + elfhandle->requires[i].indexes[j].memories = + (t_memory_reference*)OSAL_Alloc(sizeof(t_memory_reference) * elfhandle->requires[i].indexes[j].numberOfClient); + if(elfhandle->requires[i].indexes[j].memories == NULL) + goto oom; + + for(k = 0; k < elfhandle->requires[i].indexes[j].numberOfClient; k++) { + if((error = getMemoryOffset(elfhandle,&elftmp, + MEM_DATA, + &index->symbols[k], + &elfhandle->requires[i].indexes[j].memories[k])) != CM_OK) + goto onerror; + LOG_INTERNAL(2, " [%d, %d] mem=%s offset=%x\n", + j, k, + elfhandle->requires[i].indexes[j].memories[k].memory->memoryName, + elfhandle->requires[i].indexes[j].memories[k].offset, + 0, 0); + } + } + + ref += sizeof(index->numberOfClient) + elfhandle->requires[i].indexes[j].numberOfClient * sizeof(index->symbols[0]); + } + } + } + } + + // Copy provides informations + elfhandle->provideNumber = swap32(elftmp.elfheader->provideNumber); + if(elfhandle->provideNumber != 0) + { + elfhandle->provides = + (t_interface_provide*)OSAL_Alloc_Zero(sizeof(t_interface_provide) * elfhandle->provideNumber); + if(elfhandle->provides == NULL) + goto oom; + + if(elfhandle->provideNumber > 0) + { + char *ref = getElfHeaderReference(&elftmp, (void*)elftmp.elfheader->provides); + + for(i = 0; i < elfhandle->provideNumber; i++) + { + t_elf_provided_interface *provide = (t_elf_provided_interface*)ref; + t_elf_interface_description *interface = (t_elf_interface_description*)getElfHeaderReference(&elftmp, (void*)provide->interface); + + elfhandle->provides[i].name = copyElfString(&elftmp, provide->name); + if(elfhandle->provides[i].name == NULL) + goto oom; + + elfhandle->provides[i].provideTypes = provide->provideTypes; + elfhandle->provides[i].interruptLine = provide->interruptLine; + elfhandle->provides[i].collectionSize = provide->collectionSize; + elfhandle->provides[i].interface = getInterfaceDescription(&elftmp, interface); + if(elfhandle->provides[i].interface == NULL) + goto oom; + + LOG_INTERNAL(2, " provide %s <%s>\n", + elfhandle->provides[i].name, + elfhandle->provides[i].interface->type, + 0,0, 0, 0); + CM_ASSERT(elfhandle->provides[i].collectionSize != 0); + + ref = (char*)&provide->methodSymbols[0]; + + { + t_uint32 *methodSymbols = (t_uint32*)ref; + + elfhandle->provides[i].indexes = (t_interface_provide_index**)OSAL_Alloc_Zero( + sizeof(t_interface_provide_index*) * elfhandle->provides[i].collectionSize); + if(elfhandle->provides[i].indexes == NULL) + goto oom; + + if(elfhandle->provides[i].interface->methodNumber != 0) + { + for(j = 0; j < elfhandle->provides[i].collectionSize; j++) + { + elfhandle->provides[i].indexes[j] = (t_interface_provide_index*)OSAL_Alloc( + sizeof(t_interface_provide_index) * elfhandle->provides[i].interface->methodNumber); + if(elfhandle->provides[i].indexes[j] == NULL) + goto oom; + + for(k = 0; k < elfhandle->provides[i].interface->methodNumber; k++) + { + if((error = getAdressForExecutableOffsetElsewhere(elfhandle, &elftmp, + methodSymbols++, + &elfhandle->provides[i].indexes[j][k].memory)) != CM_OK) + goto onerror; + + if(elfhandle->provides[i].indexes[j][k].memory.memory != NULL) + LOG_INTERNAL(2, " [%d, %d] method '%s' mem=%s offset=%x\n", + j, k, + elfhandle->provides[i].interface->methodNames[k], + elfhandle->provides[i].indexes[j][k].memory.memory->memoryName, + elfhandle->provides[i].indexes[j][k].memory.offset, + 0); + else + LOG_INTERNAL(2, " [%d, %d] method '%s' address=%x\n", + j, k, + elfhandle->provides[i].interface->methodNames[k], + elfhandle->provides[i].indexes[j][k].memory.offset, + 0, 0); + } + } + } + + ref += elfhandle->provides[i].collectionSize * elfhandle->provides[i].interface->methodNumber * sizeof(methodSymbols[0]); + } + } + } + } + + return CM_OK; + +oom: + error = CM_NO_MORE_MEMORY; +onerror: + cm_ELF_CloseFile(temporaryDescription, elfhandle); + *elfhandlePtr = NULL; + return error; +} + +void cm_ELF_ReleaseDescription( + t_uint32 requireNumber, t_interface_require *requires, + t_uint32 attributeNumber, t_attribute *attributes, + t_uint32 propertyNumber, t_property *properties, + t_uint32 provideNumber, t_interface_provide *provides) +{ + int i, j; + + // Free provides (Number set when array allocated) + if(provides != NULL) + { + for(i = 0; i < provideNumber; i++) + { + if(provides[i].indexes != NULL) + { + for(j = 0; j < provides[i].collectionSize; j++) + { + OSAL_Free(provides[i].indexes[j]); + } + OSAL_Free(provides[i].indexes); + } + releaseInterfaceDescription(provides[i].interface); + cm_StringRelease(provides[i].name); + } + OSAL_Free(provides); + } + + // Free requires (Number set when array allocated) + if(requires != NULL) + { + for(i = 0; i < requireNumber; i++) + { + if(requires[i].indexes != 0) + { + for(j = 0; j < requires[i].collectionSize; j++) + { + OSAL_Free(requires[i].indexes[j].memories); + } + OSAL_Free(requires[i].indexes); + } + releaseInterfaceDescription(requires[i].interface); + cm_StringRelease(requires[i].name); + } + OSAL_Free(requires); + } + + // Free properties (Number set when array allocated) + if(properties != NULL) + { + for(i = 0; i < propertyNumber; i++) + { + cm_StringRelease(properties[i].value); + cm_StringRelease(properties[i].name); + } + OSAL_Free(properties); + } + + // Free Attributes (Number set when array allocated) + if(attributes != NULL) + { + for(i = 0; i < attributeNumber; i++) + { + cm_StringRelease(attributes[i].name); + } + OSAL_Free(attributes); + } +} + +void cm_ELF_CloseFile( + t_bool temporaryDescription, + t_elfdescription *elfhandle) +{ + if(elfhandle == NULL) + return; + + if(temporaryDescription && ! elfhandle->temporaryDescription) + return; + + // Release description if not moved to template + cm_ELF_ReleaseDescription( + elfhandle->requireNumber, elfhandle->requires, + elfhandle->attributeNumber, elfhandle->attributes, + elfhandle->propertyNumber, elfhandle->properties, + elfhandle->provideNumber, elfhandle->provides); + + cm_StringRelease(elfhandle->foundedTemplateName); + + ELF64_UnloadComponent(elfhandle); +} + + +static t_cm_error allocSegment( + t_cm_domain_id domainId, + t_elfdescription *elfhandle, + t_memory_handle memories[NUMBER_OF_MMDSP_MEMORY], + t_memory_property property, + t_bool isSingleton) { + t_memory_id memId; + const t_elfmemory *thisMemory; //!< Memory used to determine this + const t_elfmemory *codeMemory; //!< Memory used to determine code + + MMDSP_serializeMemories(elfhandle->instanceProperty, &codeMemory, &thisMemory); + + for(memId = 0; memId < NUMBER_OF_MMDSP_MEMORY; memId++) + { + const t_elfmemory* mapping; + + if(elfhandle->segments[memId].sumSize == 0x0) + continue; + + mapping = MMDSP_getMappingById(memId); + + if( + (mapping->property == property && elfhandle->instanceProperty != MEM_FOR_SINGLETON) || + (property == MEM_SHARABLE && elfhandle->instanceProperty == MEM_FOR_SINGLETON) ) + { + // Allocate segment + memories[memId] = cm_DM_Alloc(domainId, mapping->dspMemType, + elfhandle->segments[memId].sumSize / mapping->fileEntSize, + mapping->memAlignement, TRUE); + + if(memories[memId] == INVALID_MEMORY_HANDLE) + { + ERROR("CM_NO_MORE_MEMORY(%s): %x too big\n", mapping->memoryName, elfhandle->segments[memId].sumSize / mapping->fileEntSize, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + // Get reference in memory + elfhandle->segments[memId].hostAddr = cm_DSP_GetHostLogicalAddress(memories[memId]); + + cm_DSP_GetDspAddress(memories[memId], &elfhandle->segments[memId].mpcAddr); + + if (isSingleton) + cm_DM_SetDefaultDomain(memories[memId], cm_DM_GetDomainCoreId(domainId)); + + // Log it + LOG_INTERNAL(1, "\t%s%s: 0x%x..+0x%x (0x%x)\n", + mapping->memoryName, + (thisMemory == mapping) ? "(THIS)" : "", + elfhandle->segments[memId].mpcAddr, + elfhandle->segments[memId].sumSize / mapping->fileEntSize, + elfhandle->segments[memId].hostAddr, 0); + } + else if(property == MEM_PRIVATE) // Since we allocate private segment, if not allocate, it's a share one + { + // In order to allow further relocation based on cached address like mpcAddr & hostAddr, + // initialize them also ! + + // Get reference in memory + elfhandle->segments[memId].hostAddr = cm_DSP_GetHostLogicalAddress(memories[memId]); + + cm_DSP_GetDspAddress(memories[memId], &elfhandle->segments[memId].mpcAddr); + } + } + + return CM_OK; +} + +/* + * Note: in case of error, part of memory could have been allocated and must be free by calling cm_DSPABI_FreeTemplate + */ +t_cm_error cm_ELF_LoadTemplate( + t_cm_domain_id domainId, + t_elfdescription *elfhandle, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY], + t_bool isSingleton) +{ + t_cm_error error; + + if((error = allocSegment(domainId, elfhandle, sharedMemories, MEM_SHARABLE, isSingleton)) != CM_OK) + return error; + + // Load each readonly segment + if((error = ELF64_loadSegment(elfhandle, sharedMemories, MEM_SHARABLE)) != CM_OK) + return error; + + return CM_OK; +} + +t_cm_error cm_ELF_LoadInstance( + t_cm_domain_id domainId, + t_elfdescription *elfhandle, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY], + t_memory_handle privateMemories[NUMBER_OF_MMDSP_MEMORY], + t_bool isSingleton) +{ + t_memory_id memId; + t_cm_error error; + + // Erase whole memories to make free in case of error + for(memId = 0; memId < NUMBER_OF_MMDSP_MEMORY; memId++) + { + privateMemories[memId] = sharedMemories[memId]; + } + + if((error = allocSegment(domainId, elfhandle, privateMemories, MEM_PRIVATE, isSingleton)) != CM_OK) + return error; + + // Load each writable memory + if((error = ELF64_loadSegment(elfhandle, privateMemories, MEM_PRIVATE)) != CM_OK) + return error; + + return CM_OK; +} + +void cm_ELF_FlushTemplate( + t_nmf_core_id coreId, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY]) +{ + t_memory_id memId; + + for(memId = 0; memId < NUMBER_OF_MMDSP_MEMORY; memId++) + { + if(sharedMemories[memId] != INVALID_MEMORY_HANDLE) + MMDSP_loadedSection( + coreId, memId, + sharedMemories[memId]); + } +} + +void cm_ELF_FlushInstance( + t_nmf_core_id coreId, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY], + t_memory_handle privateMemories[NUMBER_OF_MMDSP_MEMORY]) +{ + t_memory_id memId; + + for(memId = 0; memId < NUMBER_OF_MMDSP_MEMORY; memId++) + { + if(privateMemories[memId] != INVALID_MEMORY_HANDLE && privateMemories[memId] != sharedMemories[memId]) + MMDSP_loadedSection( + coreId, memId, + privateMemories[memId]); + } +} + +void cm_ELF_FreeInstance( + t_nmf_core_id coreId, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY], + t_memory_handle privateMemories[NUMBER_OF_MMDSP_MEMORY]) +{ + t_memory_id memId; + + if(privateMemories == NULL) + return; + + for(memId = 0; memId < NUMBER_OF_MMDSP_MEMORY; memId++) + { + if(privateMemories[memId] != INVALID_MEMORY_HANDLE && privateMemories[memId] != sharedMemories[memId]) + { + MMDSP_unloadedSection(coreId, memId, privateMemories[memId]); + cm_DM_Free(privateMemories[memId], TRUE); + } + } +} + +void cm_ELF_FreeTemplate( + t_nmf_core_id coreId, + t_memory_handle sharedMemories[NUMBER_OF_MMDSP_MEMORY]) +{ + t_memory_id memId; + + if(sharedMemories == NULL) + return; + + for(memId = 0; memId < NUMBER_OF_MMDSP_MEMORY; memId++) + { + if(sharedMemories[memId] != INVALID_MEMORY_HANDLE) + { + MMDSP_unloadedSection(coreId, memId, sharedMemories[memId]); + cm_DM_Free(sharedMemories[memId], TRUE); + } + } +} diff --git a/drivers/staging/nmf-cm/cm/engine/elf/src/elfmmdsp.c b/drivers/staging/nmf-cm/cm/engine/elf/src/elfmmdsp.c new file mode 100644 index 00000000000..5f6641b188d --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/src/elfmmdsp.c @@ -0,0 +1,575 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/elf/inc/mmdsp.h> +#include <cm/engine/elf/inc/bfd.h> +#include <cm/engine/elf/inc/mpcal.h> + +#include <cm/engine/component/inc/initializer.h> + +#include <cm/engine/utils/inc/string.h> +#include <cm/engine/utils/inc/swap.h> +#include <cm/engine/trace/inc/trace.h> + +#include <cm/engine/dsp/mmdsp/inc/mmdsp_hwp.h> + +static const t_elfmemory mmdspMemories[NUMBER_OF_MMDSP_MEMORY] = { + {0, SDRAM_CODE, SDRAMTEXT_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_SHARABLE, MEM_CODE, 8, 8, "SDRAM_CODE"}, /* 0: Program memory */ + {1, INTERNAL_XRAM24, 0, CM_MM_ALIGN_2WORDS, MEM_SHARABLE, MEM_DATA, 3, 4, "XROM"}, /* 1: Internal X memory */ + {2, INTERNAL_YRAM24, 0, CM_MM_ALIGN_2WORDS, MEM_SHARABLE, MEM_DATA, 3, 4, "YROM"}, /* 2: Y memory */ + {3, SDRAM_EXT24, SDRAMMEM24_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_SHARABLE, MEM_DATA, 3, 4, "SDR0M24"}, /* 5: SDRAM24 */ + {4, SDRAM_EXT16, SDRAMMEM16_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_SHARABLE, MEM_DATA, 3, 2, "SDROM16"}, /* 6: SDRAM16 */ + {5, ESRAM_EXT24, ESRAMMEM24_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_SHARABLE, MEM_DATA, 3, 4, "ESROM24"}, /* 8: ESRAM24 */ + {6, ESRAM_EXT16, ESRAMMEM16_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_SHARABLE, MEM_DATA, 3, 2, "ESROM16"}, /* 9: ESRAM16 */ + {7, ESRAM_CODE, ESRAMTEXT_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_SHARABLE, MEM_CODE, 8, 8, "ESRAM_CODE"}, /*10: ESRAM code */ + {8, INTERNAL_XRAM24, 0, CM_MM_ALIGN_2WORDS, MEM_PRIVATE, MEM_DATA, 3, 4, "XRAM"}, /* 1: Internal X memory */ + {9, INTERNAL_YRAM24, 0, CM_MM_ALIGN_2WORDS, MEM_PRIVATE, MEM_DATA, 3, 4, "YRAM"}, /* 2: Y memory */ + {10, SDRAM_EXT24, SDRAMMEM24_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_PRIVATE, MEM_DATA, 3, 4, "SDRAM24"}, /* 5: SDRAM24 */ + {11, SDRAM_EXT16, SDRAMMEM16_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_PRIVATE, MEM_DATA, 3, 2, "SDRAM16"}, /* 6: SDRAM16 */ + {12, ESRAM_EXT24, ESRAMMEM24_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_PRIVATE, MEM_DATA, 3, 4, "ESRAM24"}, /* 8: ESRAM24 */ + {13, ESRAM_EXT16, ESRAMMEM16_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_PRIVATE, MEM_DATA, 3, 2, "ESRAM16"}, /* 9: ESRAM16 */ + {14, LOCKED_CODE, SDRAMTEXT_BASE_ADDR, CM_MM_ALIGN_2WORDS, MEM_SHARABLE, MEM_CODE, 8, 8, "LOCKED_CODE"}, /* : .locked */ +}; + +#define MAX_ELFSECTIONNAME 10 +struct memoryMapping { + char *elfSectionName; + t_uint32 memoryIndex[MEM_FOR_LAST]; // memoryIndex[t_instance_property] +}; + +static const struct memoryMapping mappingmem0[] = { + {"mem0.0", {0, 0}}, + {"mem0.1", {0, 0}}, + {"mem0.2", {0, 0}} +}; +static const struct memoryMapping mappingmem10 = + {"mem10", {7, 7}}; +static const struct memoryMapping mappinglocked = + {".locked", {14, 14}}; +static const struct memoryMapping mappingmem1[] = { + {"", {0xff, 0xff}}, + {"mem1.1", {1, 1}}, + {"mem1.2", {8, 1}}, + {"mem1.3", {1, 1}}, + {"mem1.4", {8, 1}}, + {"mem1.stack", {8, 1}} +}; +static const struct memoryMapping mappingmem2[] = { + {"", {0xff, 0xff}}, + {"mem2.1", {2, 2}}, + {"mem2.2", {9, 2}}, + {"mem2.3", {2, 2}}, + {"mem2.4", {9, 2}} +}; +static const struct memoryMapping mappingmem5[] = { + {"", {0xff, 0xff}}, + {"mem5.1", {3, 3}}, + {"mem5.2", {10, 3}}, + {"mem5.3", {3, 3}}, + {"mem5.4", {10, 3}} +}; +static const struct memoryMapping mappingmem6[] = { + {"", {0xff, 0xff}}, + {"mem6.1", {4, 4}}, + {"mem6.2", {11, 4}}, + {"mem6.3", {4, 4}}, + {"mem6.4", {11, 4}} +}; +static const struct memoryMapping mappingmem8[] = { + {"", {0xff, 0xff}}, + {"mem8.1", {5, 5}}, + {"mem8.2", {12, 5}}, + {"mem8.3", {5, 5}}, + {"mem8.4", {12, 5}} +}; +static const struct memoryMapping mappingmem9[] = { + {"", {0xff, 0xff}}, + {"mem9.1", {6, 6}}, + {"mem9.2", {13, 6}}, + {"mem9.3", {6, 6}}, + {"mem9.4", {13, 6}} +}; + +static const struct { + const struct memoryMapping* mapping; + unsigned int number; +} hashMappings[10] = { + {mappingmem0, sizeof(mappingmem0) / sizeof(mappingmem0[0])}, + {mappingmem1, sizeof(mappingmem1) / sizeof(mappingmem1[0])}, + {mappingmem2, sizeof(mappingmem2) / sizeof(mappingmem2[0])}, + {0x0, 0}, + {0x0, 0}, + {mappingmem5, sizeof(mappingmem5) / sizeof(mappingmem5[0])}, + {mappingmem6, sizeof(mappingmem6) / sizeof(mappingmem6[0])}, + {0x0, 0}, + {mappingmem8, sizeof(mappingmem8) / sizeof(mappingmem8[0])}, + {mappingmem9, sizeof(mappingmem9) / sizeof(mappingmem9[0])}, +}; + +const t_elfmemory* MMDSP_getMappingById(t_memory_id memId) +{ + return &mmdspMemories[memId]; +} + +const t_elfmemory* MMDSP_getMappingByName(const char* sectionName, t_instance_property property) +{ + if(sectionName[0] == 'm' && sectionName[1] == 'e' && sectionName[2] == 'm') + { + if(sectionName[4] == '.') + { + if(sectionName[5] >= '0' && sectionName[5] <= '9') + { + if(sectionName[3] >= '0' && sectionName[3] <= '9') + { + unsigned int m, sm; + + m = sectionName[3] - '0'; + sm = sectionName[5] - '0'; + if(sm < hashMappings[m].number) + return &mmdspMemories[hashMappings[m].mapping[sm].memoryIndex[property]]; + } + } else if(sectionName[3] == '1' && sectionName[5] == 's') + return &mmdspMemories[mappingmem1[5].memoryIndex[property]]; + } + else if(sectionName[3] == '1' && sectionName[4] == '0') + return &mmdspMemories[mappingmem10.memoryIndex[property]]; + } + else if(sectionName[0] == '.' && sectionName[1] == 'l' && sectionName[2] == 'o' && sectionName[3] == 'c' && + sectionName[4] == 'k' && sectionName[5] == 'e' && sectionName[6] == 'd') + { + return &mmdspMemories[mappinglocked.memoryIndex[property]]; + } + + return NULL; +} + +void MMDSP_serializeMemories(t_instance_property property, + const t_elfmemory** codeMemory, const t_elfmemory** thisMemory) { + // Return meory reference + *codeMemory = &mmdspMemories[0]; + if(property == MEM_FOR_SINGLETON) + { + *thisMemory = &mmdspMemories[1]; + } + else + { + *thisMemory = &mmdspMemories[8]; + } +} + +void MMDSP_copyCode(t_uint64 * remoteAddr64, const char* origAddr, int nb) +{ + int m; + + // Linux allow unaligned access +#ifdef LINUX + t_uint64 *origAddr64 = (t_uint64*)origAddr; +#else + __packed t_uint64 *origAddr64 = (__packed t_uint64*)origAddr; +#endif + + for (m = 0; m < nb; m += 8) + { + *remoteAddr64++ = swap64(*origAddr64++); + } +} + +void MMDSP_copyData24(t_uint32 * remoteAddr32, const char* origAddr, int nb) +{ + int m; + + for (m = 0; m < nb; m+=4) + { + t_uint32 value1; + + value1 = (*origAddr++ << 16); + value1 |= (*origAddr++ << 8); + value1 |= (*origAddr++ << 0); + *remoteAddr32++ = value1; + } +} + +void MMDSP_copyData16(t_uint16 * remoteAddr16, const char* origAddr, int nb) +{ + int m; + + for (m = 0; m < nb; m+=2) + { + t_uint16 value1; + + origAddr++; // Skip this byte (which is put in elf file for historical reason) + value1 = (*origAddr++ << 8); + value1 |= (*origAddr++ << 0); + *remoteAddr16++ = value1; + } +} + +#if 0 +__asm void MMDSP_copyCode(void* dst, const void* src, int nb) +{ + PUSH {r4-r8, lr} + SUBS r2,r2,#0x20 + BCC l4 + +l5 + SETEND BE + LDR r4, [r1], #0x4 + LDR r3, [r1], #0x4 + LDR r6, [r1], #0x4 + LDR r5, [r1], #0x4 + LDR r8, [r1], #0x4 + LDR r7, [r1], #0x4 + LDR lr, [r1], #0x4 + LDR r12, [r1], #0x4 + + SETEND LE + STM r0!,{r3-r8,r12, lr} + SUBS r2,r2,#0x20 + BCS l5 + +l4 + LSLS r12,r2,#28 + + SETEND BE + LDRCS r4, [r1], #0x4 + LDRCS r3, [r1], #0x4 + LDRCS r6, [r1], #0x4 + LDRCS r5, [r1], #0x4 + SETEND LE + STMCS r0!,{r3-r6} + + SETEND BE + LDRMI r4, [r1], #0x4 + LDRMI r3, [r1], #0x4 + SETEND LE + STMMI r0!,{r3-r4} + + POP {r4-r8, pc} +} +#endif + +#ifdef LINUX +static void PLD5(int r) +{ + asm volatile ( + "PLD [r0, #0x20] \n\t" + "PLD [r0, #0x40] \n\t" + "PLD [r0, #0x60] \n\t" + "PLD [r0, #0x80] \n\t" + "PLD [r0, #0xA0]" ); +} + +static void PLD1(int r) +{ + asm volatile ( + "PLD [r0, #0xC0]" ); +} +#else /* Symbian, Think -> We assume ARMCC */ +static __asm void PLD5(int r) +{ + PLD [r0, #0x20] + PLD [r0, #0x40] + PLD [r0, #0x60] + PLD [r0, #0x80] + PLD [r0, #0xA0] + + bx lr +} + +static __asm void PLD1(int r) +{ + PLD [r0, #0xC0] + + bx lr +} +#endif + +#if 0 +__asm void COPY(void* dst, const void* src, int nb) +{ + PUSH {r4-r8, lr} + SUBS r2,r2,#0x20 + BCC l4a + PLD [r1, #0x20] + PLD [r1, #0x40] + PLD [r1, #0x60] + PLD [r1, #0x80] + PLD [r1, #0xA0] + +l5a + PLD [r1, #0xC0] + LDM r1!,{r3-r8,r12,lr} + STM r0!,{r3-r8,r12,lr} + SUBS r2,r2,#0x20 + BCS l5a + +l4a + LSLS r12,r2,#28 + LDMCS r1!,{r3,r4,r12,lr} + STMCS r0!,{r3,r4,r12,lr} + LDMMI r1!,{r3,r4} + STMMI r0!,{r3,r4} + POP {r4-r8,lr} + LSLS r12,r2,#30 + LDRCS r3,[r1],#4 + STRCS r3,[r0],#4 + BXEQ lr +l6b + LSLS r2,r2,#31 + LDRHCS r3,[r1],#2 + LDRBMI r2,[r1],#1 + STRHCS r3,[r0],#2 + STRBMI r2,[r0],#1 + BX lr +} +#endif + + +void MMDSP_copySection(t_uint32 origAddr, t_uint32 remoteAddr, t_uint32 sizeInByte) { + t_uint32 endAddr = remoteAddr + sizeInByte; + + PLD5(origAddr); + + // Align on 32bits + if((remoteAddr & 0x3) != 0) + { + *(t_uint16*)remoteAddr = *(t_uint16*)origAddr; + remoteAddr += sizeof(t_uint16); + origAddr += sizeof(t_uint16); + } + + // Align on 64bits + if((remoteAddr & 0x7) != 0 && (remoteAddr <= endAddr - sizeof(t_uint32))) + { + *(t_uint32*)remoteAddr = *(t_uint32*)origAddr; + remoteAddr += sizeof(t_uint32); + origAddr += sizeof(t_uint32); + } + + // 64bits burst access + for(; remoteAddr <= endAddr - sizeof(t_uint64); remoteAddr += sizeof(t_uint64), origAddr += sizeof(t_uint64)) + { + PLD1(origAddr); + *(volatile t_uint64*)remoteAddr = *(t_uint64*)origAddr; + } + + // Remain 32bits access + if(remoteAddr <= endAddr - sizeof(t_uint32)) + { + *(t_uint32*)remoteAddr = *(t_uint32*)origAddr; + remoteAddr += sizeof(t_uint32); + origAddr += sizeof(t_uint32); + } + + // Remain 16bits access + if(remoteAddr <= endAddr - sizeof(t_uint16)) + *(t_uint16*)remoteAddr = *(t_uint16*)origAddr; +} + + +void MMDSP_bzeroSection(t_uint32 remoteAddr, t_uint32 sizeInByte) { + t_uint32 endAddr = remoteAddr + sizeInByte; + + // Align on 32bits + if((remoteAddr & 0x3) != 0) + { + *(t_uint16*)remoteAddr = 0; + remoteAddr += sizeof(t_uint16); + } + + // Align on 64bits + if((remoteAddr & 0x7) != 0 && (remoteAddr <= endAddr - sizeof(t_uint32))) + { + *(t_uint32*)remoteAddr = 0; + remoteAddr += sizeof(t_uint32); + } + + // 64bits burst access + for(; remoteAddr <= endAddr - sizeof(t_uint64); remoteAddr += sizeof(t_uint64)) + *(volatile t_uint64*)remoteAddr = 0ULL; + + // Remain 32bits access + if(remoteAddr <= endAddr - sizeof(t_uint32)) + { + *(t_uint32*)remoteAddr = 0; + remoteAddr += sizeof(t_uint32); + } + + // Remain 16bits access + if(remoteAddr <= endAddr - sizeof(t_uint16)) + *(t_uint16*)remoteAddr = 0; +} + +void MMDSP_loadedSection(t_nmf_core_id coreId, t_memory_id memId, t_memory_handle handle) +{ + if(mmdspMemories[memId].purpose == MEM_CODE) + { + OSAL_CleanDCache(cm_DSP_GetHostLogicalAddress(handle), cm_MM_GetSize(handle)); + } + + if(memId == LOCKED_CODE) + { + t_uint32 DspAddress, DspSize; + + cm_DSP_GetDspMemoryHandleSize(handle, &DspSize); + cm_DSP_GetDspAddress(handle, &DspAddress); + + cm_COMP_InstructionCacheLock(coreId, DspAddress, DspSize); + } +} + +void MMDSP_unloadedSection(t_nmf_core_id coreId, t_memory_id memId, t_memory_handle handle) +{ + if(memId == LOCKED_CODE) + { + t_uint32 DspAddress, DspSize; + + cm_DSP_GetDspMemoryHandleSize(handle, &DspSize); + cm_DSP_GetDspAddress(handle, &DspAddress); + + cm_COMP_InstructionCacheUnlock(coreId, DspAddress, DspSize); + } + +} + +static struct reloc_howto_struct elf64_mmdsp_howto_table[] = +{ + HOWTO (R_MMDSP_IMM20_16, /* type */ + 0, /* rightshift */ + 4, /* size (0 = byte, 1 = short, 2 = long) */ + 16, /* bitsize */ + FALSE, /* pc_relative */ + 8, /* bitpos */ + complain_overflow_dont, /* complain_on_overflow */ + 0x0, /* special_function */ + "R_MMDSP_IMM20_16", /* name */ + FALSE, /* partial_inplace */ + 0x0, /* src_mask */ + 0x0000000000ffff00, /* dst_mask */ + FALSE), /* pcrel_offset */ + + /* A 4-bit absolute relocation for splitted 20 bits immediate, shifted by 56 */ + + HOWTO (R_MMDSP_IMM20_4, /* type */ + 16, /* rightshift */ + 4, /* size (0 = byte, 1 = short, 2 = long) */ + 4, /* bitsize */ + FALSE, /* pc_relative */ + 56, /* bitpos */ + complain_overflow_dont, /* complain_on_overflow */ + 0x0, /* special_function */ + "R_MMDSP_IMM20_4", /* name */ + FALSE, /* partial_inplace */ + 0x0, /* src_mask */ + 0x0f00000000000000LL, /* dst_mask */ + FALSE), /* pcrel_offset */ + + HOWTO (R_MMDSP_24, /* type */ + 0, /* rightshift */ + 2, /* size (0 = byte, 1 = short, 2 = long) */ + 24, /* bitsize */ + FALSE, /* pc_relative */ + 0, /* bitpos */ + complain_overflow_bitfield, /* complain_on_overflow */ + 0x0, /* special_function */ + "R_MMDSP_24", /* name */ + FALSE, /* partial_inplace */ + 0x0, /* src_mask */ + 0xffffffff, /* dst_mask */ + FALSE), /* pcrel_offset */ + + HOWTO (R_MMDSP_IMM16, /* type */ + 0, /* rightshift */ + 4, /* size (0 = byte, 1 = short, 2 = long) */ + 16, /* bitsize */ + FALSE, /* pc_relative */ + 8, /* bitpos */ + complain_overflow_bitfield, /* complain_on_overflow */ + 0x0, /* special_function */ + "R_MMDSP_IMM16", /* name */ + FALSE, /* partial_inplace */ + 0x0, /* src_mask */ + 0x0000000000ffff00, /* dst_mask */ + FALSE), /* pcrel_offset */ +}; + +static const char* lastInPlaceAddr = 0; +static long long lastInPlaceValue; + +void MMDSP_performRelocation( + t_uint32 type, + const char* symbol_name, + t_uint32 symbol_addr, + char* reloc_addr, + const char* inPlaceAddr, + t_uint32 reloc_offset) { + int i; + + for(i = 0; i < sizeof(elf64_mmdsp_howto_table) / sizeof(elf64_mmdsp_howto_table[0]); i++) + { + struct reloc_howto_struct* howto = &elf64_mmdsp_howto_table[i]; + if(howto->type == type) + { + t_uint64 relocation; + + LOG_INTERNAL(2, "reloc '%s:0x%x' type %s at 0x%x (0x%x)\n", + symbol_name ? symbol_name : "??", symbol_addr, + howto->name, + reloc_offset, reloc_addr, 0); + + relocation = symbol_addr; + + if (howto->pc_relative) { + // Not handle yet + } + + if (howto->complain_on_overflow != complain_overflow_dont) { + // Not handle yet + } + + relocation >>= howto->rightshift; + + relocation <<= howto->bitpos; + +#define DOIT(x) \ + x = ( (x & ~howto->dst_mask) | (((x & howto->src_mask) + relocation) & howto->dst_mask)) + + switch (howto->size) { + case 2: { + long x = *(long*)inPlaceAddr; + + // CM_ASSERT(*(long*)inPlaceAddr == *(long*)reloc_addr); + + DOIT (x); + *(long*)reloc_addr = x; + } + break; + case 4: { + long long x; + if(lastInPlaceAddr == inPlaceAddr) + { + x = lastInPlaceValue; + } + else + { + // CM_ASSERT(*(__packed long long*)inPlaceAddr == *(long long*)reloc_addr); + x = *(long long*)inPlaceAddr; + lastInPlaceAddr = inPlaceAddr; + } + + DOIT (x); + *(long long*)reloc_addr = lastInPlaceValue = x; + } + break; + default: + CM_ASSERT(0); + } + + return; + } + } + + ERROR("Relocation type %d not supported for '%s'\n", type, symbol_name, 0, 0, 0, 0); +} diff --git a/drivers/staging/nmf-cm/cm/engine/elf/src/elfrelocate.c b/drivers/staging/nmf-cm/cm/engine/elf/src/elfrelocate.c new file mode 100644 index 00000000000..b08ac6a361e --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/src/elfrelocate.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + */ +#include <cm/engine/elf/inc/bfd.h> +#include <cm/engine/elf/inc/mpcal.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/utils/inc/string.h> + +t_cm_error cm_ELF_relocateSharedSegments( + t_memory_handle *memories, + t_elfdescription *elfhandle, + void *cbContext) +{ + return ELF64_relocateSegments( + memories, + elfhandle, + MEM_SHARABLE, + cbContext); +} + +t_cm_error cm_ELF_relocatePrivateSegments( + t_memory_handle *memories, + t_elfdescription *elfhandle, + void *cbContext) +{ + return ELF64_relocateSegments( + memories, + elfhandle, + MEM_PRIVATE, + cbContext); +} + +void cm_ELF_performRelocation( + t_uint32 type, + const char* symbol_name, + t_uint32 symbol_addr, + char* reloc_addr) +{ + MMDSP_performRelocation( + type, + symbol_name, + symbol_addr, + reloc_addr, + reloc_addr, + 0xBEEF); + + OSAL_CleanDCache((t_uint32)reloc_addr, 8); +} + +t_cm_error cm_ELF_GetMemory( + t_elfdescription *elf, + t_tmp_elfdescription *elftmp, + t_uint32 address, + t_memory_purpose purpose, + t_memory_reference *memory) { + t_memory_id memId; + + for(memId = 0; memId < NUMBER_OF_MMDSP_MEMORY; memId++) + { + const t_elfmemory* mem = MMDSP_getMappingById(memId); + + if(mem->purpose == purpose && // Memory correspond + elf->segments[mem->id].sumSize != 0 && // Segment allocated + (elf->segments[mem->id].mpcAddr <= address) && + (address < elf->segments[mem->id].mpcAddr + elf->segments[mem->id].sumSize / mem->fileEntSize)) { + memory->memory = mem; + memory->offset = address - elf->segments[mem->id].mpcAddr; + return CM_OK; + } + } + + ERROR("Memory %x,%d not found\n", address, purpose, 0, 0, 0, 0); + return CM_INVALID_ELF_FILE; +} diff --git a/drivers/staging/nmf-cm/cm/engine/elf/src/elfxx.c b/drivers/staging/nmf-cm/cm/engine/elf/src/elfxx.c new file mode 100644 index 00000000000..4a2976a6bc1 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/src/elfxx.c @@ -0,0 +1,591 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/elf/inc/mpcal.h> + +#include <cm/engine/utils/inc/string.h> +#include <cm/engine/utils/inc/mem.h> + + +static t_uint32 max(t_uint32 a, t_uint32 b) +{ + return (a >= b) ? a : b; +} +/* +static t_uint32 min(t_uint32 a, t_uint32 b) +{ + return (a <= b) ? a : b; +} +*/ + +struct XXrelocation +{ + t_uint32 st_value; + ElfXX_Half st_shndx; + Elf64_Sxword r_addend; + t_uint32 OffsetInElf; + t_uint32 type; + + t_dup_char symbol_name; // Valid only if st_shndx == SHN_UNDEF +}; + +struct XXSection { + ElfXX_Word sh_type; /* Section type */ + t_uint32 sh_size; /* Section size in bytes */ + ElfXX_Word sh_info; /* Additional section information */ + ElfXX_Word sh_link; /* Link to another section */ + t_uint32 sh_addralign; /* Some sections have address alignment constraints */ + t_uint32 sh_addr; /* Section addr */ + ElfXX_Xword sh_flags; /* Section flags */ + + const char *data; + t_uint32 trueDataSize; /* Valid if different from sh_size */ + const char *sectionName; + + t_uint32 offsetInSegment; + const t_elfmemory *meminfo; + + t_uint32 relocationNumber; + struct XXrelocation *relocations; +}; + +struct XXElf { + t_uint32 e_shnum; + struct XXSection sectionss[1]; +}; + +t_cm_error ELF64_LoadComponent( + t_uint16 e_machine, + const char *elfdata, + t_elfdescription **elfhandlePtr, + t_tmp_elfdescription *elftmp) +{ + t_elfdescription *elfhandle; + const ElfXX_Ehdr *header = (ElfXX_Ehdr*)elfdata; + const ElfXX_Shdr *sections; + const char *strings; + struct XXElf* ELF; + int i, nb; + + elftmp->elfdata = elfdata; + + /* Sanity check */ + if (swapHalf(header->e_machine) != e_machine) + { + ERROR("This is not a executable for such MPC\n", 0, 0, 0, 0, 0, 0); + return CM_INVALID_ELF_FILE; + } + + // Cache elf file informations + nb = swapHalf(header->e_shnum); + elftmp->isExecutable = (swapHalf(header->e_type) == ET_EXEC); + + elfhandle = (t_elfdescription*)OSAL_Alloc_Zero( + sizeof(t_elfdescription) + sizeof(struct XXElf) + sizeof(struct XXSection) * (nb - 1)); + if(elfhandle == NULL) + return CM_NO_MORE_MEMORY; + + ELF = elfhandle->ELF = (struct XXElf*)(elfhandle + 1); + + ELF->e_shnum = nb; + + sections = (ElfXX_Shdr*)&elfdata[swapXword(header->e_shoff)]; + // Compute and swap section infromation + for(i = 0; i < ELF->e_shnum; i++) + { + ELF->sectionss[i].sh_type = swapWord(sections[i].sh_type); + ELF->sectionss[i].sh_info = swapWord(sections[i].sh_info); + ELF->sectionss[i].sh_link = swapWord(sections[i].sh_link); + ELF->sectionss[i].sh_size = (t_uint32)swapXword(sections[i].sh_size); + ELF->sectionss[i].sh_addralign = (t_uint32)swapXword(sections[i].sh_addralign); + ELF->sectionss[i].sh_addr = (t_uint32)swapXword(sections[i].sh_addr); + ELF->sectionss[i].sh_flags = swapXword(sections[i].sh_flags); + + elftmp->sectionData[i] = &elfdata[(t_uint32)swapXword(sections[i].sh_offset)]; + } + + /* + * search nmf_segment + */ + strings = elftmp->sectionData[swapHalf(header->e_shstrndx)]; + for(i = 0; i < ELF->e_shnum; i++) + { + ELF->sectionss[i].sectionName = &strings[swapWord(sections[i].sh_name)]; + + // Found nmf_segment to see if it's + if(cm_StringCompare("nmf_segment", ELF->sectionss[i].sectionName, 11) == 0) { + elftmp->nmfSectionIndex = i; + elftmp->elfheader = (const t_elf_component_header*)elftmp->sectionData[i]; + } + } + + if(elftmp->nmfSectionIndex == 0) + { + ERROR("This is not a NMF component\n", 0, 0, 0, 0, 0, 0); + goto invalid; + } + + /* + * Determine component type + */ + elfhandle->magicNumber = swap32(elftmp->elfheader->magic); + switch(elfhandle->magicNumber) { + case MAGIC_COMPONENT: + elfhandle->instanceProperty = MEM_FOR_MULTIINSTANCE; + break; + case MAGIC_SINGLETON: + case MAGIC_FIRMWARE: + elfhandle->instanceProperty = MEM_FOR_SINGLETON; + break; + } + + // Copy content + for(i = 0; i < ELF->e_shnum; i++) + { + ELF->sectionss[i].meminfo = MMDSP_getMappingByName( + ELF->sectionss[i].sectionName, + elfhandle->instanceProperty); + + if(ELF->sectionss[i].meminfo != NULL) + ELF->sectionss[i].trueDataSize = (ELF->sectionss[i].sh_size / ELF->sectionss[i].meminfo->fileEntSize) * ELF->sectionss[i].meminfo->memEntSize; + + if(ELF->sectionss[i].sh_size != 0 && + ELF->sectionss[i].sh_type == SHT_PROGBITS && + (ELF->sectionss[i].sh_flags & SHF_ALLOC) != 0) + { + const char* elfAddr = elftmp->sectionData[i]; + + ELF->sectionss[i].data = OSAL_Alloc(ELF->sectionss[i].trueDataSize); + if(ELF->sectionss[i].data == NULL) + goto oom; + + if(ELF->sectionss[i].meminfo->purpose == MEM_CODE) + { + MMDSP_copyCode( + (t_uint64*)ELF->sectionss[i].data, + elfAddr, + ELF->sectionss[i].trueDataSize); + } + else if(ELF->sectionss[i].meminfo->purpose == MEM_DATA && + // Always 3 for data ELF->sectionss[i].meminfo->fileEntSize == 3 && + ELF->sectionss[i].meminfo->memEntSize == 4) + { + MMDSP_copyData24( + (t_uint32*)ELF->sectionss[i].data, + elfAddr, + ELF->sectionss[i].trueDataSize); + } + else if(ELF->sectionss[i].meminfo->purpose == MEM_DATA && + // Always 3 for data ELF->sectionss[i].meminfo->fileEntSize == 3 && + ELF->sectionss[i].meminfo->memEntSize == 2) + { + MMDSP_copyData16( + (t_uint16*)ELF->sectionss[i].data, + elfAddr, + ELF->sectionss[i].trueDataSize); + } + else + CM_ASSERT(0); + } + } + + // Copy relocation + // Loop on all relocation section + for(i=0; i < ELF->e_shnum; i++) + { + int sh_info; + + // Does this section is a relocation table (only RELA supported) + if((ELF->sectionss[i].sh_type != SHT_RELA) || + ELF->sectionss[i].sh_size == 0) continue; + + // Copy only relocation for loaded section + sh_info = ELF->sectionss[i].sh_info; + if(ELF->sectionss[sh_info].meminfo != NULL) + { + const ElfXX_Sym* symtab; + const char* strtab; + ElfXX_Rela* rel_start; + int n; + + ELF->sectionss[sh_info].relocationNumber = ELF->sectionss[i].sh_size / sizeof(ElfXX_Rela); + ELF->sectionss[sh_info].relocations = (struct XXrelocation*)OSAL_Alloc_Zero(sizeof(struct XXrelocation) * ELF->sectionss[sh_info].relocationNumber); + if(ELF->sectionss[sh_info].relocations == NULL) + goto oom; + + symtab = (ElfXX_Sym *)elftmp->sectionData[ELF->sectionss[i].sh_link]; + strtab = elftmp->sectionData[ELF->sectionss[ELF->sectionss[i].sh_link].sh_link]; + rel_start = (ElfXX_Rela*)elftmp->sectionData[i]; + for(n = 0; n < ELF->sectionss[sh_info].relocationNumber; n++, rel_start++) + { + struct XXrelocation* relocation = &ELF->sectionss[sh_info].relocations[n]; + ElfXX_Xword r_info = swapXword(rel_start->r_info); + int strtab_index = ELFXX_R_SYM(r_info); + const char* symbol_name = &strtab[swapWord(symtab[strtab_index].st_name)]; + + relocation->st_shndx = swapHalf(symtab[strtab_index].st_shndx); + relocation->st_value = (t_uint32)swapXword(symtab[strtab_index].st_value); + relocation->r_addend = swapXword(rel_start->r_addend); + relocation->OffsetInElf = (t_uint32)swapXword(rel_start->r_offset) / ELF->sectionss[sh_info].meminfo->fileEntSize; + relocation->type = ELFXX_R_TYPE(r_info); + + switch(relocation->st_shndx) { + case SHN_UNDEF: + relocation->symbol_name = cm_StringDuplicate(symbol_name + 1); /* Remove '_' prefix */ + if(relocation->symbol_name == NULL) + goto oom; + break; + case SHN_COMMON: + ERROR("SHN_COMMON not handle for %s\n", symbol_name, 0, 0, 0, 0, 0); + goto invalid; + } + } + } + } + + *elfhandlePtr = elfhandle; + return CM_OK; +invalid: + ELF64_UnloadComponent(elfhandle); + return CM_INVALID_ELF_FILE; +oom: + ELF64_UnloadComponent(elfhandle); + return CM_NO_MORE_MEMORY; +} + +t_cm_error ELF64_ComputeSegment( + t_elfdescription *elfhandle, + t_tmp_elfdescription *elftmp) +{ + struct XXElf* ELF = elfhandle->ELF; + int i; + + for(i = 0; i < ELF->e_shnum; i++) + { + ELF->sectionss[i].offsetInSegment = 0xFFFFFFFF; + + if(ELF->sectionss[i].sh_type == SHT_PROGBITS || ELF->sectionss[i].sh_type == SHT_NOBITS) { + // This is a loadable memory (memory size could be zero since we can have symbol on it)... + const t_elfmemory* meminfo = ELF->sectionss[i].meminfo; + + if(meminfo != NULL) { + // Which correspond to MPC memory + + if(elftmp->isExecutable) + { + if(! elfhandle->segments[meminfo->id].sumSizeSetted) + { + CM_ASSERT(ELF->sectionss[i].sh_addr >= meminfo->startAddr * meminfo->fileEntSize); + + elfhandle->segments[meminfo->id].sumSizeSetted = TRUE; + elfhandle->segments[meminfo->id].sumSize = ELF->sectionss[i].sh_addr - meminfo->startAddr * meminfo->fileEntSize; + } + else + CM_ASSERT(elfhandle->segments[meminfo->id].sumSize == ELF->sectionss[i].sh_addr - meminfo->startAddr * meminfo->fileEntSize); + } + else + { + while(elfhandle->segments[meminfo->id].sumSize % ELF->sectionss[i].sh_addralign != 0) + elfhandle->segments[meminfo->id].sumSize++; + } + + elfhandle->segments[meminfo->id].maxAlign = max(elfhandle->segments[meminfo->id].maxAlign, ELF->sectionss[i].sh_addralign); + ELF->sectionss[i].offsetInSegment = elfhandle->segments[meminfo->id].sumSize / meminfo->fileEntSize; + elfhandle->segments[meminfo->id].sumSize += ELF->sectionss[i].sh_size; + } + } else if(ELF->sectionss[i].sh_type == SHT_RELA && ELF->sectionss[i].sh_info == elftmp->nmfSectionIndex) { + int secsym = ELF->sectionss[i].sh_link; + elftmp->relaNmfSegment = (ElfXX_Rela*)elftmp->sectionData[i]; + elftmp->relaNmfSegmentEnd = (ElfXX_Rela*)((t_uint32)elftmp->relaNmfSegment + ELF->sectionss[i].sh_size); + elftmp->relaNmfSegmentSymbols = (ElfXX_Sym*)elftmp->sectionData[secsym]; + elftmp->relaNmfSegmentStrings = elftmp->sectionData[ELF->sectionss[secsym].sh_link]; + } + } + + return CM_OK; +} + +void ELF64_UnloadComponent( + t_elfdescription *elfhandle) +{ + struct XXElf* ELF = elfhandle->ELF; + int i, n; + + for(i = 0; i < ELF->e_shnum; i++) + { + if(ELF->sectionss[i].relocations != NULL) + { + for(n = 0; n < ELF->sectionss[i].relocationNumber; n++) + cm_StringRelease(ELF->sectionss[i].relocations[n].symbol_name); + OSAL_Free(ELF->sectionss[i].relocations); + } + + OSAL_Free((void*)ELF->sectionss[i].data); + } + OSAL_Free(elfhandle); +} + +t_cm_error ELF64_loadSegment( + t_elfdescription *elfhandle, + t_memory_handle *memory, + t_memory_property property) +{ + struct XXElf* ELF = elfhandle->ELF; + int i; + + /* + * Copy ELF data in this segment + */ + for(i = 0; i < ELF->e_shnum; i++) + { + const t_elfmemory* mapping = ELF->sectionss[i].meminfo; + + if(mapping == NULL) + continue; + if((! (ELF->sectionss[i].sh_flags & SHF_ALLOC)) || (ELF->sectionss[i].sh_size == 0)) + continue; + + // This is a loadable memory ... + if( + (mapping->property == property && elfhandle->instanceProperty != MEM_FOR_SINGLETON) || + (property == MEM_SHARABLE && elfhandle->instanceProperty == MEM_FOR_SINGLETON) ) + { + // Where memory exist and waited share/private correspond + t_uint32 remoteData = elfhandle->segments[mapping->id].hostAddr + + ELF->sectionss[i].offsetInSegment * mapping->memEntSize; + + if(ELF->sectionss[i].sh_type != SHT_NOBITS) + { + LOG_INTERNAL(2, "loadSection(%s, 0x%x, 0x%x, 0x%08x)\n", + ELF->sectionss[i].sectionName, remoteData, ELF->sectionss[i].trueDataSize, + (t_uint32)ELF->sectionss[i].data, 0, 0); + + MMDSP_copySection((t_uint32)ELF->sectionss[i].data, remoteData, ELF->sectionss[i].trueDataSize); + } + else + { + LOG_INTERNAL(2, "bzeroSection(%s, 0x%x, 0x%x)\n", + ELF->sectionss[i].sectionName, remoteData, ELF->sectionss[i].trueDataSize, 0, 0, 0); + + MMDSP_bzeroSection(remoteData, ELF->sectionss[i].trueDataSize); + } + } + } + + return CM_OK; +} + + + +static const t_elfmemory* getSectionAddress( + t_memory_handle *memories, + t_elfdescription *elfhandle, + t_uint32 sectionIdx, + t_uint32 *sectionOffset, + t_cm_logical_address *sectionAddr) { + struct XXElf* ELF = elfhandle->ELF; + const t_elfmemory* mapping = ELF->sectionss[sectionIdx].meminfo; + + if(mapping != NULL) { + *sectionOffset = (elfhandle->segments[mapping->id].mpcAddr + + ELF->sectionss[sectionIdx].offsetInSegment); + + *sectionAddr = (t_cm_logical_address)(elfhandle->segments[mapping->id].hostAddr + + ELF->sectionss[sectionIdx].offsetInSegment * mapping->memEntSize); + } + + return mapping; +} + +static t_uint32 getSymbolAddress( + t_memory_handle *memories, + t_elfdescription *elfhandle, + t_uint32 symbolSectionIdx, + t_uint32 symbolOffet) { + struct XXElf* ELF = elfhandle->ELF; + const t_elfmemory* mapping = ELF->sectionss[symbolSectionIdx].meminfo; + + if(mapping == NULL) + return 0xFFFFFFFF; + // CM_ASSERT(elfhandle->segments[mapping->id].sumSize != 0); + // CM_ASSERT(elfhandle->sections[symbolSectionIdx].offsetInSegment != 0xFFFFFFFF); + + return elfhandle->segments[mapping->id].mpcAddr + + ELF->sectionss[symbolSectionIdx].offsetInSegment + + symbolOffet; +} + +#if 0 +t_bool ELFXX_getSymbolLocation( + const t_mpcal_memory *mpcalmemory, + t_elfdescription *elf, + char *symbolName, + const t_elfmemory **memory, + t_uint32 *offset) { + const ElfXX_Ehdr *header = (ElfXX_Ehdr*)elf->elfdata; + const ElfXX_Shdr *sections = (ElfXX_Shdr*)&elf->elfdata[swapXword(header->e_shoff)]; + const char *strings = &elf->elfdata[swapXword(sections[swapHalf(header->e_shstrndx)].sh_offset)]; + int len = cm_StringLength(symbolName, 256); // TO BE FIXED + int i; + + for(i = 0; i < ELF->e_shnum; i++) + { + ElfXX_Sym* symtab; + const char* strtab; + unsigned int size, j; + + if(ELF->sectionss[i].sh_type != SHT_SYMTAB && ELF->sectionss[i].sh_type != SHT_DYNSYM) continue; + + // Section is a symbol table + symtab = (ElfXX_Sym*)&elf->elfdata[swapXword(sections[i].sh_offset)]; + strtab = &elf->elfdata[swapXword(sections[swapWord(sections[i].sh_link)].sh_offset)]; + size = ELF->sectionss[i].sh_size / (unsigned int)swapXword(sections[i].sh_entsize); + + for(j = 0; j < size; j++) { + const char* foundName = &strtab[swapWord(symtab[j].st_name)]; + + if(cm_StringCompare(symbolName, foundName, len) == 0) { + if(swapHalf(symtab[j].st_shndx) != SHN_UNDEF) { + int sectionIdx = (int)swapHalf(symtab[j].st_shndx); + ElfXX_Xword sh_flags = swapXword(sections[sectionIdx].sh_flags); + + *memory = mpcalmemory->getMappingByName(&strings[swapWord(sections[sectionIdx].sh_name)], + sh_flags & SHF_WRITE ? MEM_RW : (sh_flags & SHF_EXECINSTR ? MEM_X : MEM_RO)); + *offset = (t_uint32)swapXword(symtab[j].st_value); + + return 1; + } + } + } + } + return 0; +} +#endif + +t_cm_error ELF64_relocateSegments( + t_memory_handle *memories, + t_elfdescription *elfhandle, + t_memory_property property, + void *cbContext) { + struct XXElf* ELF = elfhandle->ELF; + int sec, n; + + // Loop on all relocation section + for(sec=0; sec < ELF->e_shnum; sec++) + { + t_cm_logical_address sectionAddr = 0; + t_uint32 sectionOffset = 0; + const t_elfmemory* mapping; + + if(ELF->sectionss[sec].relocations == NULL) + continue; + + // Relocate only section in memory + mapping = getSectionAddress(memories, + elfhandle, + sec, + §ionOffset, + §ionAddr); + if(mapping == NULL) + continue; + + if( + (mapping->property == property && elfhandle->instanceProperty != MEM_FOR_SINGLETON) || + (property == MEM_SHARABLE && elfhandle->instanceProperty == MEM_FOR_SINGLETON) ) + { + LOG_INTERNAL(2, "relocSection(%s)\n", ELF->sectionss[sec].sectionName, 0, 0, 0, 0, 0); + + for(n = 0; n < ELF->sectionss[sec].relocationNumber; n++) + { + struct XXrelocation* relocation = &ELF->sectionss[sec].relocations[n]; + t_uint32 symbol_addr; + char* relocAddr = (char*)(sectionAddr + relocation->OffsetInElf * mapping->memEntSize); + + switch(relocation->st_shndx) { + case SHN_ABS: // Absolute external reference + symbol_addr = relocation->st_value; + break; + case SHN_UNDEF: // External reference + // LOG_INTERNAL(0, "cm_resolvSymbol(%d, %s)\n", relocation->type, relocation->symbol_name, 0,0, 0, 0); + symbol_addr = cm_resolvSymbol(cbContext, + relocation->type, + relocation->symbol_name, + relocAddr); + if(symbol_addr == 0x0) { // Not defined symbol + ERROR("Symbol %s not found\n", relocation->symbol_name, 0, 0, 0, 0, 0); + return CM_INVALID_ELF_FILE; + } else if(symbol_addr == 0xFFFFFFFE) { // OOM + return CM_NO_MORE_MEMORY; + } else if(symbol_addr == 0xFFFFFFFF) { // Defined inside static binding + continue; + } + break; + default: // Internal reference in loaded section + symbol_addr = getSymbolAddress( + memories, + elfhandle, + (t_uint32)relocation->st_shndx, + relocation->st_value); + if(symbol_addr == 0xFFFFFFFF) { + ERROR("Symbol in section %s+%d not loaded\n", + ELF->sectionss[relocation->st_shndx].sectionName, + relocation->st_value, 0, 0, 0, 0); + return CM_INVALID_ELF_FILE; + } + break; + } + + symbol_addr += relocation->r_addend; + + MMDSP_performRelocation( + relocation->type, + relocation->symbol_name, + symbol_addr, + relocAddr, + ELF->sectionss[sec].data + relocation->OffsetInElf * mapping->memEntSize, + sectionOffset + relocation->OffsetInElf); + } + } + } + + return CM_OK; +} + +t_cm_error ELF64_getRelocationMemory( + t_elfdescription *elfhandle, + t_tmp_elfdescription *elftmp, + t_uint32 offsetInNmf, + t_memory_reference *memory) { + struct XXElf* ELF = elfhandle->ELF; + const ElfXX_Rela* rel_start; + const ElfXX_Sym* relaNmfSegmentSymbols = (ElfXX_Sym*)elftmp->relaNmfSegmentSymbols; + + for(rel_start = (ElfXX_Rela*)elftmp->relaNmfSegment; rel_start < (ElfXX_Rela*)elftmp->relaNmfSegmentEnd; rel_start++) + { + if((t_uint32)swapXword(rel_start->r_offset) == offsetInNmf) + { + int strtab_index = ELFXX_R_SYM(swapXword(rel_start->r_info)); + int sectionIdx = (int)swapHalf(relaNmfSegmentSymbols[strtab_index].st_shndx); + + memory->memory = ELF->sectionss[sectionIdx].meminfo; + + if(memory->memory != NULL) { + memory->offset = ( + ELF->sectionss[sectionIdx].offsetInSegment + // Offset in Segment + (t_uint32)swapXword(relaNmfSegmentSymbols[strtab_index].st_value) + // Offset in Elf Section + (t_uint32)swapXword(rel_start->r_addend)); // Addend + + return CM_OK; + } else { + const char* symbol_name = &elftmp->relaNmfSegmentStrings[swapWord(relaNmfSegmentSymbols[strtab_index].st_name)]; + ERROR("Symbol %s not found\n", symbol_name, 0, 0, 0, 0, 0); + return CM_INVALID_ELF_FILE; + } + } + } + + ERROR("Unknown relocation error\n", 0, 0, 0, 0, 0, 0); + return CM_INVALID_ELF_FILE; +} diff --git a/drivers/staging/nmf-cm/cm/engine/elf/src/mmdsp-debug.c b/drivers/staging/nmf-cm/cm/engine/elf/src/mmdsp-debug.c new file mode 100644 index 00000000000..c6c316046b3 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/src/mmdsp-debug.c @@ -0,0 +1,435 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + */ +#include <cm/engine/elf/inc/mmdsp-loadmap.h> +#include <cm/engine/elf/inc/mmdsp.h> +#include <cm/engine/dsp/inc/semaphores_dsp.h> +#include <cm/engine/dsp/mmdsp/inc/mmdsp_hwp.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> + +#include <cm/engine/power_mgt/inc/power.h> + +#include <cm/engine/utils/inc/string.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/component/inc/instance.h> +#include <cm/engine/component/inc/component_type.h> +#include <inc/nmf-limits.h> + +#define LOADMAP_SEMAPHORE_USE_NB 7 + +static t_memory_handle headerHandle[NB_CORE_IDS] = {INVALID_MEMORY_HANDLE, }; +static struct LoadMapHdr *headerAddresses[NB_CORE_IDS] = {0, }; +static t_uint32 headerOffsets[NB_CORE_IDS] = {0, }; +static t_uint32 entryNumber[NB_CORE_IDS] = {0, }; + +#undef myoffsetof +#define myoffsetof(TYPE, MEMBER) ((unsigned int) &((TYPE *)0)->MEMBER) + +t_cm_error cm_DSPABI_AddLoadMap( + t_cm_domain_id domainId, + const char* templateName, + const char* localname, + t_memory_handle *memories, + void *componentHandle) +{ + t_nmf_core_id coreId = cm_DM_GetDomainCoreId(domainId); + int count=0; + struct LoadMapItem* curItem = NULL; + + if (headerHandle[coreId] == 0) /* Create loadmap header */ + { + headerHandle[coreId] = cm_DM_Alloc(domainId, SDRAM_EXT16, + sizeof(struct LoadMapHdr)/2, CM_MM_ALIGN_2WORDS, TRUE); + if (headerHandle[coreId] == INVALID_MEMORY_HANDLE) { + ERROR("CM_NO_MORE_MEMORY: Unable to allocate loadmap in cm_DSPABI_AddLoadMap()\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + headerAddresses[coreId] = (struct LoadMapHdr*)cm_DSP_GetHostLogicalAddress(headerHandle[coreId]); + + headerAddresses[coreId]->nMagicNumber = LOADMAP_MAGIC_NUMBER; + headerAddresses[coreId]->nVersion = (LOADMAP_VERSION_MSB<<8)|(LOADMAP_VERSION_LSB); + headerAddresses[coreId]->nRevision = 0; + headerAddresses[coreId]->pFirstItem = 0; + + //Register Header into XRAM:2 + cm_DSP_GetDspAddress(headerHandle[coreId], &headerOffsets[coreId]); + cm_DSP_WriteXRamWord(coreId, 2, headerOffsets[coreId]); + } + + // update Header nRevision field + headerAddresses[coreId]->nRevision++; + + /* + * Build loadmap entry + */ + { + t_memory_handle handle; + struct LoadMapItem* pItem; + t_uint32 dspentry; + unsigned char* pos; + t_uint32 fnlen, lnlen; + t_uint32 fnlenaligned, lnlenaligned; + t_uint32 address; + t_uint32 postStringLength; + int i; + + postStringLength = cm_StringLength(".elf", 16); + fnlenaligned = fnlen = cm_StringLength(templateName, MAX_COMPONENT_FILE_PATH_LENGTH) + postStringLength + 2; + if((fnlenaligned % 2) != 0) fnlenaligned++; + lnlenaligned = lnlen = cm_StringLength(localname, MAX_TEMPLATE_NAME_LENGTH); + if((lnlenaligned % 2) != 0) lnlenaligned++; + + // Allocate new loap map + handle = cm_DM_Alloc(domainId, SDRAM_EXT16, + sizeof(struct LoadMapItem)/2 + (1 + fnlenaligned/2) + (1 + lnlenaligned/2), + CM_MM_ALIGN_2WORDS, TRUE); + if (handle == INVALID_MEMORY_HANDLE) { + ERROR("CM_NO_MORE_MEMORY: Unable to allocate loadmap entry in cm_DSPABI_AddLoadMap\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + pItem = (struct LoadMapItem*)cm_DSP_GetHostLogicalAddress(handle); + cm_DSP_GetDspAddress(handle, &dspentry); + count++; + entryNumber[coreId]++; + + // Link this new loadmap with the previous one + if(headerAddresses[coreId]->pFirstItem == NULL) + headerAddresses[coreId]->pFirstItem = (struct LoadMapItem *)dspentry; + else + { + const t_dsp_desc* pDspDesc = cm_DSP_GetState(coreId); + t_uint32 endSegmentAddr = SDRAMMEM16_BASE_ADDR + pDspDesc->segments[SDRAM_DATA_USER].size / 2; + struct LoadMapItem* curItem, *prevItem = NULL; + t_uint32 curItemDspAdress; + + if( + ((t_uint32)headerAddresses[coreId]->pFirstItem < SDRAMMEM16_BASE_ADDR) || + ((t_uint32)headerAddresses[coreId]->pFirstItem > endSegmentAddr)) + { + ERROR("Memory corruption in MMDSP: at data DSP address=%x or ARM address=%x\n", + headerOffsets[coreId], &headerAddresses[coreId]->pFirstItem, 0, 0, 0, 0); + + return CM_INVALID_DATA; + } + curItemDspAdress = (t_uint32)headerAddresses[coreId]->pFirstItem; + curItem = (struct LoadMapItem*)((curItemDspAdress - headerOffsets[coreId]) * 2 + (t_uint32)headerAddresses[coreId]); // To ARM address + count++; + while(curItem->pNextItem != NULL) + { + if(((t_uint32)curItem->pNextItem < SDRAMMEM16_BASE_ADDR) || ((t_uint32)curItem->pNextItem > endSegmentAddr)) + { + if (prevItem == NULL) + ERROR("AddLoadMap: Memory corruption in MMDSP: at data DSP address=%x or ARM address=%x\n", + curItemDspAdress + myoffsetof(struct LoadMapItem, pNextItem), &curItem->pNextItem, + 0, 0, 0, 0); + else + ERROR("AddLoadMap: Memory corruption in MMDSP: at data DSP address=%x or ARM address=%x\n", + curItemDspAdress + myoffsetof(struct LoadMapItem, pNextItem), &curItem->pNextItem, + 0, 0, 0, 0); + return CM_INVALID_DATA; + } + curItemDspAdress = (t_uint32)curItem->pNextItem; + prevItem = curItem; + curItem = (struct LoadMapItem*)((curItemDspAdress - headerOffsets[coreId]) * 2 + (t_uint32)headerAddresses[coreId]); // To ARM address + count++; + } + curItem->pNextItem = (struct LoadMapItem *)dspentry; + } + + // DSP Address of the string at the end of the load map + pos = (unsigned char*)pItem + sizeof(struct LoadMapItem); + + /* + * Set SolibFilename address information + * -> string = "./origfilename" + */ + pItem->pSolibFilename = (char*)(dspentry + sizeof(struct LoadMapItem) / 2); + *(t_uint16*)pos = fnlen; + pos += 2; + *pos++ = '.'; + *pos++ = '\\'; + for(i = 0; i < fnlen - 2 - postStringLength; i++) + { + *pos++ = (templateName[i] == '.') ? '\\' : templateName[i]; + } + *pos++ = '.'; + *pos++ = 'e'; + *pos++ = 'l'; + *pos++ = 'f'; + // add padding if needed + if ((t_uint32)pos & 1) + *pos++ = '\0'; + + /* + * Set Component Name address information + */ + if (lnlen != 0) + { + pItem->pComponentName = (char*)(dspentry + sizeof(struct LoadMapItem) / 2 + 1 + fnlenaligned / 2); + + *(t_uint16*)pos = lnlen; + pos += 2; + for(i = 0; i < lnlenaligned; i++) + { + // If not aligned null ending copied + *pos++ = localname[i]; + } + } + else + { + pItem->pComponentName = 0; + } + + /* + * Set PROG information + */ + if(memories[CODE_MEMORY_INDEX] == INVALID_MEMORY_HANDLE) + address = 0; + else + cm_DSP_GetDspAddress(memories[CODE_MEMORY_INDEX], &address); + pItem->pAddrProg = (void*)address; + + /* + * Set ERAMCODE information + */ + if(memories[ECODE_MEMORY_INDEX] == INVALID_MEMORY_HANDLE) + address = 0; + else + cm_DSP_GetDspAddress(memories[ECODE_MEMORY_INDEX], &address); + pItem->pAddrEmbProg = (void*)address; + + /* + * Set THIS information + */ + if(memories[PRIVATE_DATA_MEMORY_INDEX] != INVALID_MEMORY_HANDLE) { + // Standard component + cm_DSP_GetDspAddress(memories[PRIVATE_DATA_MEMORY_INDEX], &address); + } else if(memories[SHARE_DATA_MEMORY_INDEX] != INVALID_MEMORY_HANDLE) { + // Singleton component where data are shared (simulate THIS with shared memory) + cm_DSP_GetDspAddress(memories[SHARE_DATA_MEMORY_INDEX], &address); + } else { + // Component without data (take unique identifier -> arbitrary take host component handle) + address = (t_uint32)componentHandle; + } + pItem->pThis = (void*)address; + + /* + * Set ARM THIS information + */ + pItem->pARMThis = componentHandle; + + /* + * Set Link to null (end of list) + */ + pItem->pNextItem = 0; + + /* + * Set XROM information + */ + if(memories[XROM_MEMORY_INDEX] == INVALID_MEMORY_HANDLE) + address = 0; + else + cm_DSP_GetDspAddress(memories[XROM_MEMORY_INDEX], &address); + pItem->pXROM = (void*)address; + + /* + * Set YROM information + */ + if(memories[YROM_MEMORY_INDEX] == INVALID_MEMORY_HANDLE) + address = 0; + else + cm_DSP_GetDspAddress(memories[YROM_MEMORY_INDEX], &address); + pItem->pYROM = (void*)address; + + /* + * Set memory handle (not used externally) + */ + ((t_component_instance *)componentHandle)->loadMapHandle = handle; + } + + OSAL_mb(); + + if (count != entryNumber[coreId]) { + ERROR("AddLoadMap: corrumption, number of component differs: count=%d, expected %d (last item @ %p)\n", + count, entryNumber[coreId], curItem, 0, 0, 0); + return CM_INVALID_DATA; + } + return CM_OK; +} + +t_cm_error cm_DSPABI_RemoveLoadMap( + t_cm_domain_id domainId, + const char* templateName, + t_memory_handle *memories, + const char* localname, + void *componentHandle) +{ + struct LoadMapItem **prevItemReference; + t_uint32 prevItemReferenceDspAddress, curItemDspAdress; + t_nmf_core_id coreId = cm_DM_GetDomainCoreId(domainId); + const t_dsp_desc* pDspDesc = cm_DSP_GetState(coreId); + t_uint32 endSegmentAddr = SDRAMMEM16_BASE_ADDR + pDspDesc->segments[SDRAM_DATA_USER].size / 2; + struct LoadMapItem* curItem = NULL; + + CM_ASSERT (headerHandle[coreId] != INVALID_MEMORY_HANDLE); + + /* parse list until we find this */ + prevItemReferenceDspAddress = 0x2; // DSP address of load map head pointer + prevItemReference = &headerAddresses[coreId]->pFirstItem; + curItemDspAdress = (t_uint32)*prevItemReference; + while(curItemDspAdress != 0x0) + { + if((curItemDspAdress < SDRAMMEM16_BASE_ADDR) || (curItemDspAdress > endSegmentAddr)) + { + ERROR("Memory corruption in MMDSP: at data DSP address=%x or ARM address=%x\n", + prevItemReferenceDspAddress, prevItemReference, 0, 0, 0, 0); + + /* free the entry anyway to avoid leakage */ + cm_DM_Free(((t_component_instance *)componentHandle)->loadMapHandle, TRUE); + + return CM_OK; + } + + curItem = (struct LoadMapItem*)((curItemDspAdress - headerOffsets[coreId]) * 2 + (t_uint32)headerAddresses[coreId]); // To ARM address + + if(curItem->pARMThis == componentHandle) + { + // Remove component from loadmap + + /* take local semaphore */ + cm_DSP_SEM_Take(coreId,LOADMAP_SEMAPHORE_USE_NB); + + /* remove element from list */ + *prevItemReference = curItem->pNextItem; + + /* update nRevision field in header */ + headerAddresses[coreId]->nRevision++; + + /* If this is the last item, deallocate !!! */ + if(headerAddresses[coreId]->pFirstItem == NULL) + { + // Deallocate memory + cm_DM_Free(headerHandle[coreId], TRUE); + headerHandle[coreId] = INVALID_MEMORY_HANDLE; + + //Register Header into XRAM:2 + cm_DSP_WriteXRamWord(coreId, 2, 0); + } + + /* deallocate memory */ + cm_DM_Free(((t_component_instance *)componentHandle)->loadMapHandle, TRUE); + + /* be sure memory is updated before releasing local semaphore */ + OSAL_mb(); + + /* release local semaphore */ + cm_DSP_SEM_Give(coreId,LOADMAP_SEMAPHORE_USE_NB); + + entryNumber[coreId]--; + + return CM_OK; + } + + prevItemReferenceDspAddress = curItemDspAdress + myoffsetof(struct LoadMapItem, pNextItem); + prevItemReference = &curItem->pNextItem; + curItemDspAdress = (t_uint32)*prevItemReference; + }; + + ERROR("Memory corruption in MMDSP: component not in LoadMap %s\n", localname, 0, 0, 0, 0, 0); + + /* free the entry anyway to avoid leakage */ + cm_DM_Free(((t_component_instance *)componentHandle)->loadMapHandle, TRUE); + + return CM_OK; +} + +#if 0 +t_cm_error cm_DSPABI_CheckLoadMap_nolock(t_nmf_core_id coreId) +{ + int count=0; + static int dump = 5; + struct LoadMapItem* curItem = NULL; + + if (!dump) + return CM_OK; + if (headerHandle[coreId] == 0) /* No load map yet */ + return CM_OK; + + { + // No entry in loadmap + if(headerAddresses[coreId]->pFirstItem == NULL) + return CM_OK; + + { + const t_dsp_desc* pDspDesc = cm_DSP_GetState(coreId); + t_uint32 endSegmentAddr = SDRAMMEM16_BASE_ADDR + pDspDesc->segments[SDRAM_DATA_USER].size / 2; + struct LoadMapItem *prevItem=NULL; + t_uint32 curItemDspAdress; + + if (((t_uint32)headerAddresses[coreId]->pFirstItem < SDRAMMEM16_BASE_ADDR) || + ((t_uint32)headerAddresses[coreId]->pFirstItem > endSegmentAddr)) + { + ERROR("CheckLoadMap: Memory corruption in MMDSP at first item: at data DSP address=%x or ARM address=%x\n", + headerOffsets[coreId], &headerAddresses[coreId]->pFirstItem, 0, 0, 0, 0); + dump--; + return CM_INVALID_COMPONENT_HANDLE; + } + curItemDspAdress = (t_uint32)headerAddresses[coreId]->pFirstItem; + curItem = (struct LoadMapItem*)((curItemDspAdress - headerOffsets[coreId]) * 2 + (t_uint32)headerAddresses[coreId]); + count++; + while(curItem->pNextItem != NULL) + { + if(((t_uint32)curItem->pNextItem < SDRAMMEM16_BASE_ADDR) || ((t_uint32)curItem->pNextItem > endSegmentAddr)) + { + if (!prevItem) + ERROR("CheckLoadMap: Memory corruption in MMDSP (count=%d): at data DSP address=%x or ARM address=%x\n" + "Previous (first) component name %s<%s>\n", + count, + curItemDspAdress + myoffsetof(struct LoadMapItem, pNextItem), &curItem->pNextItem, + (char*)(((t_component_instance *)&curItem->pARMThis)->pathname), + (char*)(((t_component_instance *)&curItem->pARMThis)->Template->name), 0); + else + ERROR("CheckLoadMap: Memory corruption in MMDSP (count=%d): at data DSP address=%x or ARM address=%x\n" + "Previous valid component name %s<%s>", + count, + curItemDspAdress + myoffsetof(struct LoadMapItem, pNextItem), &curItem->pNextItem, + (char*)(((t_component_instance *)&prevItem->pARMThis)->pathname), + (char*)(((t_component_instance *)&prevItem->pARMThis)->Template->name), 0); + dump--; + return CM_INVALID_COMPONENT_HANDLE; + } + curItemDspAdress = (t_uint32)curItem->pNextItem; + prevItem = curItem; + curItem = (struct LoadMapItem*)((curItemDspAdress - headerOffsets[coreId]) * 2 + (t_uint32)headerAddresses[coreId]); // To ARM address + count++; + } + } + + } + + if (count != entryNumber[coreId]) { + ERROR("CheckLoadMap: number of component differs: count=%d, expected %d (last item @ %p)\n", count, entryNumber[coreId], + curItem, 0, 0, 0); + dump--; + return CM_INVALID_COMPONENT_HANDLE; + } + return CM_OK; +} + +t_cm_error cm_DSPABI_CheckLoadMap(t_nmf_core_id coreId) +{ + t_cm_error error; + OSAL_LOCK_API(); + error = cm_DSPABI_CheckLoadMap_nolock(coreId); + OSAL_UNLOCK_API(); + return error; +} +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/elf/src/mpcal.c b/drivers/staging/nmf-cm/cm/engine/elf/src/mpcal.c new file mode 100644 index 00000000000..93d910a5ed6 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/elf/src/mpcal.c @@ -0,0 +1,6 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/elf/inc/mpcal.h> diff --git a/drivers/staging/nmf-cm/cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h b/drivers/staging/nmf-cm/cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h new file mode 100644 index 00000000000..0894410ae0d --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef __INC_EE_MGT_H +#define __INC_EE_MGT_H + +#include <cm/engine/component/inc/instance.h> +#include <cm/engine/dsp/inc/dsp.h> + +typedef struct { + t_component_instance *instance; + t_nmf_executive_engine_id executiveEngineId; + t_uint32 currentStackSize[NMF_SCHED_URGENT + 1]; + t_uint32 voidAddr; + t_uint32 traceState; + t_uint32 printLevel; + t_uint32 nbOfForceWakeup; + struct { + t_memory_handle handle; + t_cm_logical_address addr; + } panicArea; + + // Trace Management + t_uint32 readTracePointer; + t_uint32 lastReadedTraceRevision; + t_memory_handle traceDataHandle; + struct t_nmf_trace *traceDataAddr; +} t_ee_state; + +//TODO, juraj, this should be done more properly, like accessor method, instead making this global variable.. +extern t_ee_state eeState[NB_CORE_IDS]; + +/******************************************************************************/ +/************************ FUNCTIONS PROTOTYPES ********************************/ +/******************************************************************************/ + +PUBLIC t_cm_error cm_EEM_Init(t_nmf_core_id coreId, const char *eeName, t_nmf_executive_engine_id executiveEngineId); +PUBLIC void cm_EEM_Close(t_nmf_core_id coreId); +PUBLIC t_uint32 cm_EEM_isStackUpdateNeed(t_nmf_core_id coreId, t_nmf_ee_priority priority, t_uint32 isInstantiate, t_uint32 needMinStackSize); +PUBLIC t_cm_error cm_EEM_UpdateStack(t_nmf_core_id coreId, t_nmf_ee_priority priority, t_uint32 needMinStackSize, t_uint32 *pNewStackValue); +PUBLIC t_ee_state* cm_EEM_getExecutiveEngine(t_nmf_core_id coreId); +PUBLIC void cm_EEM_setTraceMode(t_nmf_core_id coreId, t_uint32 state); +PUBLIC void cm_EEM_setPrintLevel(t_nmf_core_id coreId, t_uint32 level); +t_cm_error cm_EEM_ForceWakeup(t_nmf_core_id coreId); +void cm_EEM_AllowSleep(t_nmf_core_id coreId); + +#endif /* __INC_EE_MGT_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/executive_engine_mgt/src/executive_engine_mgt.c b/drivers/staging/nmf-cm/cm/engine/executive_engine_mgt/src/executive_engine_mgt.c new file mode 100644 index 00000000000..4df3d7ee0f5 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/executive_engine_mgt/src/executive_engine_mgt.c @@ -0,0 +1,405 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*----------------------------------------------------------------------------* + * This module provides functions that allow to manage DSPs' Firmwares. * + ******************************************************************************/ + + +/******************************************************************* Includes + ****************************************************************************/ + +#include "../inc/executive_engine_mgt.h" +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/utils/inc/convert.h> +#include <cm/engine/component/inc/initializer.h> +#include <cm/engine/power_mgt/inc/power.h> +#include <cm/engine/perfmeter/inc/mpcload.h> + +#include <cm/engine/trace/inc/xtitrace.h> + +#include <share/communication/inc/nmf_service.h> + +t_ee_state eeState[NB_CORE_IDS]; + +/****************************************************************** Functions + ****************************************************************************/ +static t_cm_error cm_EEM_allocPanicArea(t_nmf_core_id coreId, t_cm_domain_id domainId); +static void cm_EEM_freePanicArea(t_nmf_core_id coreId); + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetExecutiveEngineHandle( + t_cm_domain_id domainId, + t_cm_instance_handle *executiveEngineHandle) +{ + t_nmf_core_id coreId; + + if (cm_DM_CheckDomain(domainId, DOMAIN_NORMAL) != CM_OK) { + return CM_INVALID_DOMAIN_HANDLE; + } + + coreId = cm_DM_GetDomainCoreId(domainId); + //in case someone ask for ee on component manager !!!! + if (coreId == ARM_CORE_ID) {*executiveEngineHandle = 0;} + else {*executiveEngineHandle = eeState[coreId].instance->instance;} + + return CM_OK; +} + +PUBLIC t_cm_error cm_EEM_Init( + t_nmf_core_id coreId, + const char *eeName, + t_nmf_executive_engine_id executiveEngineId) +{ + t_rep_component *pRepComponent; + t_cm_error error; + t_uint32 i; + + eeState[coreId].instance = (t_component_instance *)0; + eeState[coreId].executiveEngineId = executiveEngineId; + for(i = NMF_SCHED_BACKGROUND; i < NMF_SCHED_URGENT + 1;i++) + { + eeState[coreId].currentStackSize[i] = MIN_STACK_SIZE; + } + + // Try to load component file + if((error = cm_REP_lookupComponent(eeName, &pRepComponent)) != CM_OK) + { + if (error == CM_COMPONENT_NOT_FOUND) + ERROR("CM_COMPONENT_NOT_FOUND: Execution Engine %s\n", eeName, 0, 0, 0, 0, 0); + return error; + } + + // Set to 1 during bootstrap since MMDSP forceWakeup is to one also in order to not go in idle state + // while configuration not finish !!! + eeState[coreId].nbOfForceWakeup = 1; + + if ((error = cm_DSP_Boot(coreId)) != CM_OK) + return error; + + if((error = cm_instantiateComponent( + eeName, + cm_DSP_GetState(coreId)->domainEE, + NMF_SCHED_URGENT, + eeName, + pRepComponent->elfhandle, + &eeState[coreId].instance)) != CM_OK) + { + cm_DSP_Shutdown(coreId); + return error; + } + + /* Get Void Function */ + eeState[coreId].voidAddr = cm_getFunction(eeState[coreId].instance, "helper", "Void"); + + /* allocate xram space for stack */ + if (executiveEngineId == SYNCHRONOUS_EXECUTIVE_ENGINE) + { + error = cm_DSP_setStackSize(coreId, MIN_STACK_SIZE); + } + else + { + error = cm_DSP_setStackSize(coreId, (NMF_SCHED_URGENT + 1) * MIN_STACK_SIZE); + } + if (error != CM_OK) + { + cm_delayedDestroyComponent(eeState[coreId].instance); + eeState[coreId].instance = (t_component_instance *)0; + cm_DSP_Shutdown(coreId); + return error; + } + + /* allocate sdram memory for panic area */ + error = cm_EEM_allocPanicArea(coreId, cm_DSP_GetState(coreId)->domainEE); + if (error != CM_OK) { + cm_delayedDestroyComponent(eeState[coreId].instance); + eeState[coreId].instance = (t_component_instance *)0; + cm_DSP_Shutdown(coreId); + return error; + } + + /* allocate sdram memory to share perfmeters data */ + error = cm_PFM_allocatePerfmeterDataMemory(coreId, cm_DSP_GetState(coreId)->domainEE); + if (error != CM_OK) { + cm_EEM_freePanicArea(coreId); + cm_delayedDestroyComponent(eeState[coreId].instance); + eeState[coreId].instance = (t_component_instance *)0; + cm_DSP_Shutdown(coreId); + return error; + } + + if((error = cm_SRV_allocateTraceBufferMemory(coreId, cm_DSP_GetState(coreId)->domainEE)) != CM_OK) + { + cm_PFM_deallocatePerfmeterDataMemory(coreId); + cm_EEM_freePanicArea(coreId); + cm_delayedDestroyComponent(eeState[coreId].instance); + eeState[coreId].instance = (t_component_instance *)0; + cm_DSP_Shutdown(coreId); + return error; + } + + /* set initial stack value */ + cm_writeAttribute(eeState[coreId].instance, "rtos/scheduler/topOfStack", cm_DSP_getStackAddr(coreId)); + + /* set myCoreId for trace */ + cm_writeAttribute(eeState[coreId].instance, "xti/myCoreId", coreId - 1); + +#if defined(__STN_8500) && (__STN_8500 > 10) + /* set myCoreId for prcmu if exist */ + cm_writeAttribute(eeState[coreId].instance, "sleep/prcmu/myCoreId", coreId + 1); +#endif + + /* go go go ... */ + cm_DSP_Start(coreId); + + /* Waiting for End Of Boot */ + //TODO : remove infinite while loop + //TODO : to be paranoiac, add a read to serviceReasonOffset before starting core and check value is MPC_SERVICE_BOOT as it should be + { + while(cm_readAttributeNoError(eeState[coreId].instance, "rtos/commonpart/serviceReason") == MPC_SERVICE_BOOT) + { + volatile t_uint32 i; + for (i=0; i < 1000; i++); + } + } + + /* set some attributes after boot to avoid being erase by mmdsp boot */ + cm_writeAttribute(eeState[coreId].instance, "xti/traceActive", eeState[coreId].traceState); + cm_writeAttribute(eeState[coreId].instance, "rtos/commonpart/printLevel", eeState[coreId].printLevel); + + cm_DSP_ConfigureAfterBoot(coreId); + + return CM_OK; +} + +/****************************************************************************/ +/* NAME: cm_EEM_Close */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: Inform us that ee for coreId has been destroyed */ +/* */ +/* PARAMETERS: id: dsp identifier */ +/* */ +/* RETURN: none */ +/* */ +/****************************************************************************/ +PUBLIC void cm_EEM_Close(t_nmf_core_id coreId) +{ + cm_DSP_setStackSize(coreId, 0); + cm_delayedDestroyComponent(eeState[coreId].instance); + eeState[coreId].instance = (t_component_instance *)0; + cm_SRV_deallocateTraceBufferMemory(coreId); + cm_PFM_deallocatePerfmeterDataMemory(coreId); + cm_EEM_freePanicArea(coreId); + cm_DSP_Shutdown(coreId); +} + +/****************************************************************************/ +/* NAME: cm_EEM_isStackUpdateNeed( */ +/* t_nmf_core_id id, */ +/* t_nmf_ee_priority priority, */ +/* t_uint32 isInstantiate, */ +/* t_uint32 needMinStackSize */ +/* ) */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: Return a boolean to inform if a ee stack size update is need*/ +/* when instantiate or destroying a component */ +/****************************************************************************/ +PUBLIC t_uint32 cm_EEM_isStackUpdateNeed( + t_nmf_core_id coreId, + t_nmf_ee_priority priority, + t_uint32 isInstantiate, + t_uint32 needMinStackSize) +{ + /* in case of SYNCHRONOUS_EXECUTIVE_ENGINE we only use currentStackSize[NMF_SCHED_BACKGROUND] */ + if (eeState[coreId].executiveEngineId == SYNCHRONOUS_EXECUTIVE_ENGINE) {priority = NMF_SCHED_BACKGROUND;} + if (isInstantiate) + { + if (needMinStackSize > eeState[coreId].currentStackSize[priority]) {return TRUE;} + } + else + { + if (needMinStackSize == eeState[coreId].currentStackSize[priority]) {return TRUE;} + } + + return FALSE; +} + +/****************************************************************************/ +/* NAME: cm_EEM_UpdateStack( */ +/* t_nmf_core_id id, */ +/* t_nmf_ee_priority priority, */ +/* t_uint32 needMinStackSize, */ +/* t_uint32 *pNewStackValue */ +/* ) */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: If cm_EEM_isStackUpdateNeed() has return true then caller */ +/* must inform EEM about new stack value for priority. */ +/* cm_EEM_UpdateStack() will return new global stack size to */ +/* provide to ee. */ +/****************************************************************************/ +PUBLIC t_cm_error cm_EEM_UpdateStack( + t_nmf_core_id coreId, + t_nmf_ee_priority priority, + t_uint32 needMinStackSize, + t_uint32 *pNewStackValue) +{ + t_cm_error error; + t_uint32 recoveryStackSize = eeState[coreId].currentStackSize[priority]; + t_uint32 i; + + /* in case of SYNCHRONOUS_EXECUTIVE_ENGINE we only use currentStackSize[NMF_SCHED_BACKGROUND] */ + if (eeState[coreId].executiveEngineId == SYNCHRONOUS_EXECUTIVE_ENGINE) {priority = NMF_SCHED_BACKGROUND;} + eeState[coreId].currentStackSize[priority] = needMinStackSize; + if (eeState[coreId].executiveEngineId == SYNCHRONOUS_EXECUTIVE_ENGINE) {*pNewStackValue = needMinStackSize;} + else + { + *pNewStackValue = 0; + for(i = NMF_SCHED_BACKGROUND; i < NMF_SCHED_URGENT + 1;i++) + { + *pNewStackValue += eeState[coreId].currentStackSize[i]; + } + } + + /* try to increase size of stack by modifying xram allocator size */ + error = cm_DSP_setStackSize(coreId, *pNewStackValue); + if (error != CM_OK) { + eeState[coreId].currentStackSize[priority] = recoveryStackSize; + } else { + LOG_INTERNAL(1, "\n##### Stack update: size=%d, prio=%d on %s #####\n", *pNewStackValue, priority, cm_getDspName(coreId), 0, 0, 0); + } + + return error; +} + +/****************************************************************************/ +/* NAME: t_nmf_executive_engine_id( */ +/* t_nmf_core_id id */ +/* ) */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: return executive engine load on id core. */ +/****************************************************************************/ +PUBLIC t_ee_state * cm_EEM_getExecutiveEngine(t_nmf_core_id coreId) +{ + return &eeState[coreId]; +} + +/****************************************************************************/ +/* NAME: cm_EEM_setTraceMode( */ +/* t_nmf_core_id id, */ +/* t_uint32 state */ +/* ) */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: activate/deactivate trace for ee running on id. In case ee */ +/* is not yet load then information is store. */ +/****************************************************************************/ +PUBLIC void cm_EEM_setTraceMode(t_nmf_core_id coreId, t_uint32 state) +{ + eeState[coreId].traceState = state; + if (eeState[coreId].instance) + { + if(cm_EEM_ForceWakeup(coreId) == CM_OK) + { + cm_writeAttribute(eeState[coreId].instance, "xti/traceActive", eeState[coreId].traceState); + + cm_EEM_AllowSleep(coreId); + } + } +} + +/****************************************************************************/ +/* NAME: cm_EEM_setPrintLevel( */ +/* t_nmf_core_id id, */ +/* t_uint32 level */ +/* ) */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: set print level for ee running on id. In case ee */ +/* is not yet load then information is store. */ +/****************************************************************************/ +PUBLIC void cm_EEM_setPrintLevel(t_nmf_core_id coreId, t_uint32 level) +{ + eeState[coreId].printLevel = level; + if (eeState[coreId].instance) + { + if(cm_EEM_ForceWakeup(coreId) == CM_OK) + { + cm_writeAttribute(eeState[coreId].instance, "rtos/commonpart/printLevel", eeState[coreId].printLevel); + + cm_EEM_AllowSleep(coreId); + } + } +} + +t_cm_error cm_EEM_ForceWakeup(t_nmf_core_id coreId) +{ + if(eeState[coreId].nbOfForceWakeup++ == 0) + { + t_cm_error error; + + LOG_INTERNAL(2, "ARM: Try to wake up\n", 0, 0, 0, 0, 0, 0); + + if (cm_DSP_GetState(coreId)->state != MPC_STATE_BOOTED) + { + return CM_MPC_NOT_RESPONDING; + } + else if ((error = cm_COMP_ULPForceWakeup(coreId)) != CM_OK) + { + if (error == CM_MPC_NOT_RESPONDING) { + if(cm_DSP_GetState(coreId)->state == MPC_STATE_PANIC) + /* Don't print error which has been done by Panic handling */; + else + { + ERROR("CM_MPC_NOT_RESPONDING: DSP %s can't be wakeup'ed\n", cm_getDspName(coreId), 0, 0, 0, 0, 0); + cm_DSP_SetStatePanic(coreId); + } + } + return error; + } + } + return CM_OK; +} + +void cm_EEM_AllowSleep(t_nmf_core_id coreId) +{ + if(--eeState[coreId].nbOfForceWakeup == 0) + { + LOG_INTERNAL(2, "ARM: Allow sleep\n", 0, 0, 0, 0, 0, 0); + + if (cm_DSP_GetState(coreId)->state != MPC_STATE_BOOTED) + { + } + else if (cm_COMP_ULPAllowSleep(coreId) != CM_OK) + { + ERROR("CM_MPC_NOT_RESPONDING: DSP %s can't be allow sleep'ed\n", cm_getDspName(coreId), 0, 0, 0, 0, 0); + } + } +} + +/* internal api */ +t_cm_error cm_EEM_allocPanicArea(t_nmf_core_id coreId, t_cm_domain_id domainId) +{ + t_cm_error error = CM_OK; + + eeState[coreId].panicArea.handle = cm_DM_Alloc(cm_DSP_GetState(coreId)->domainEE, SDRAM_EXT24, 45 /* 42 registers, pc, 2 magic words */,CM_MM_ALIGN_WORD, TRUE); + if (eeState[coreId].panicArea.handle == INVALID_MEMORY_HANDLE) + error = CM_NO_MORE_MEMORY; + else { + t_uint32 mmdspAddr; + + eeState[coreId].panicArea.addr = cm_DSP_GetHostLogicalAddress(eeState[coreId].panicArea.handle); + cm_DSP_GetDspAddress(eeState[coreId].panicArea.handle, &mmdspAddr); + + cm_writeAttribute(eeState[coreId].instance, "rtos/commonpart/panicDataAddr", mmdspAddr); + } + + return error; +} + +void cm_EEM_freePanicArea(t_nmf_core_id coreId) +{ + eeState[coreId].panicArea.addr = 0; + cm_DM_Free(eeState[coreId].panicArea.handle, TRUE); +} diff --git a/drivers/staging/nmf-cm/cm/engine/memory/inc/chunk_mgr.h b/drivers/staging/nmf-cm/cm/engine/memory/inc/chunk_mgr.h new file mode 100644 index 00000000000..340301a9259 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/inc/chunk_mgr.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef CHUNK_MGR_H_ +#define CHUNK_MGR_H_ + +#include <cm/engine/memory/inc/remote_allocator.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> + +t_cm_error allocChunkPool(void); +t_cm_error fillChunkPool(void); +void freeChunkPool(void); + +/***************************************************************************/ +/* + * allocChunk + * param current : Pointer on chunck to free + * + * Add a chunk in the chunck list + * + */ +/***************************************************************************/ +t_cm_chunk* allocChunk(void); + +/***************************************************************************/ +/* + * freeChunk + * param current : Pointer on chunck to free + * + * Remove a chunk in the chunck list + * + */ +/***************************************************************************/ +void freeChunk(t_cm_chunk *chunk); + +#endif /*CHUNK_MGR_H_*/ diff --git a/drivers/staging/nmf-cm/cm/engine/memory/inc/domain.h b/drivers/staging/nmf-cm/cm/engine/memory/inc/domain.h new file mode 100644 index 00000000000..c9b39956c63 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/inc/domain.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/***************************************************************************/ +/* file : domain.h + * author : NMF team + * version : 1.0 + * + * brief : NMF domain definitions + */ +/***************************************************************************/ + +#ifndef DOMAIN_H_ +#define DOMAIN_H_ + +#include <cm/inc/cm_type.h> +#include <cm/engine/memory/inc/domain_type.h> +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/dsp/inc/dsp.h> + +/* These default domains are used for singleton only ! */ +#define DEFAULT_SVA_DOMAIN (t_cm_domain_id)1 +#define DEFAULT_SIA_DOMAIN (t_cm_domain_id)2 + +/*! + * \brief Domain type. + * \internal + * \ingroup CM_DOMAIN_API + */ +typedef enum { + DOMAIN_ANY = 0, + DOMAIN_NORMAL, + DOMAIN_SCRATCH_PARENT, + DOMAIN_SCRATCH_CHILD +} t_cm_domain_type; + +/*! + * \brief Domain descriptor. Holds offsets for all memory types present in the system. + * \internal + * \ingroup CM_DOMAIN_API + */ +typedef struct { + t_cm_domain_memory domain; // the actual memory ranges + t_cm_domain_type type; // domain type + t_uint32 refcount; // reference counter for scratch domain dependencies + t_nmf_client_id client; // client id for cleaning + + union { + struct { + t_memory_handle handle; // memory handle of the allocated chunk the covers the esram-data scratch region + } parent; + struct { + t_cm_allocator_desc *alloc; //allocator descriptor for the scratch domain + t_cm_domain_id parent_ref; //parent domain reference + } child; + } scratch; + void *dbgCooky; //pointer to OS internal data +} t_cm_domain_desc; + +#ifdef DEBUG +#define DOMAIN_DEBUG(handle) \ + handle = handle & ~0xc0; +#else +#define DOMAIN_DEBUG(handle) +#endif + +/*! + * \brief Domain descriptor array. + */ +extern t_cm_domain_desc domainDesc[]; + +typedef struct { + t_cm_domain_id parentId; + t_cm_domain_id domainId; + t_cm_allocator_desc *allocDesc; +} t_cm_domain_scratch_desc; + +extern t_cm_domain_scratch_desc domainScratchDesc[]; + +typedef struct { + t_cm_system_address sdramCode; + t_cm_system_address sdramData; + t_cm_system_address esramCode; + t_cm_system_address esramData; +} t_cm_domain_info; + +/*! + * \brief Init of the domain subsystem. + */ +PUBLIC t_cm_error cm_DM_Init(void); + +/*! + * \brief Clean-up of the domain subsystem. + */ +PUBLIC void cm_DM_Destroy(void); + +/*! + * \brief Domain creation. + * + * Allocates in slot in the domain descriptors array and copies segment infos from the domain + * parameter to the descriptor. The resulting handle is returned via @param handle. + * + * Returns: CM_DOMAIN_INVALID in case of error, otherwise CM_OK. + */ +PUBLIC t_cm_error cm_DM_CreateDomain(const t_nmf_client_id client, const t_cm_domain_memory *domain, t_cm_domain_id *handle); + +/*! + * \brief Scratch (or overlap) domain creation. + * + * Create a scratch domain, ie domain where allocation may overlap. + */ +PUBLIC t_cm_error cm_DM_CreateDomainScratch(const t_nmf_client_id client, const t_cm_domain_id parentId, const t_cm_domain_memory *domain, t_cm_domain_id *handle); + +/* ! + * \brief Retrieve the coreId from a given domain. Utility. + */ +PUBLIC t_nmf_core_id cm_DM_GetDomainCoreId(const t_cm_domain_id domainId); + +/*! + * \brief Destroy all domains belonging to a given client. + */ +PUBLIC t_cm_error cm_DM_DestroyDomains(const t_nmf_client_id client); + +/*! + * \brief Destroy a given domain. + */ +PUBLIC t_cm_error cm_DM_DestroyDomain(t_cm_domain_id handle); + +/*! + * \brief Check if the handle is valid. + */ +PUBLIC t_cm_error cm_DM_CheckDomain(t_cm_domain_id handle, t_cm_domain_type type); +PUBLIC t_cm_error cm_DM_CheckDomainWithClient(t_cm_domain_id handle, t_cm_domain_type type, t_nmf_client_id client); + +/*! + * \brief Memory allocation in a given domain, for a given memory type (see CM_AllocMpcMemory). + */ +PUBLIC t_memory_handle cm_DM_Alloc(t_cm_domain_id domainId, t_dsp_memory_type_id memType, t_uint32 size, t_cm_mpc_memory_alignment memAlignment, t_bool powerOn); + +/*! + * \brief Memory free using a given domain handle + */ +PUBLIC void cm_DM_FreeWithInfo(t_memory_handle memHandle, t_nmf_core_id *coreId, t_dsp_memory_type_id *memType, t_bool powerOff); + +/*! + * \brief Memory free using a given domain handle + */ +PUBLIC void cm_DM_Free(t_memory_handle memHandle, t_bool powerOff); + +/*! + * \brief Wrapper function for CM_GetMpcMemoryStatus. + */ +PUBLIC t_cm_error cm_DM_GetAllocatorStatus(t_cm_domain_id domainId, t_dsp_memory_type_id memType, t_cm_allocator_status *pStatus); + +PUBLIC t_cm_error cm_DM_GetDomainAbsAdresses(t_cm_domain_id domainId, t_cm_domain_info *info); + +/*! + * \brief Change the domain for the given allocated chunk + */ +PUBLIC void cm_DM_SetDefaultDomain(t_memory_handle memHandle, t_nmf_core_id coreId); +#endif /* DOMAIN_H_ */ diff --git a/drivers/staging/nmf-cm/cm/engine/memory/inc/domain_type.h b/drivers/staging/nmf-cm/cm/engine/memory/inc/domain_type.h new file mode 100644 index 00000000000..354315d4d72 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/inc/domain_type.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/***************************************************************************/ +/* file : domain.h + * author : NMF team + * version : 1.0 + * + * brief : NMF domain definitions + */ +/***************************************************************************/ + +#ifndef DOMAIN_TYPE_H_ +#define DOMAIN_TYPE_H_ + +#include <cm/inc/cm_type.h> +#include <cm/engine/memory/inc/memory_type.h> + +/*! + * \brief Domain identifier + * \ingroup CM_DOMAIN_API + */ +typedef t_uint8 t_cm_domain_id; + +/*! + * \brief Client identifier + * 0 (zero) is considered as an invalid or 'NO' client identifier + * \ingroup CM_DOMAIN_API + */ +typedef t_uint32 t_nmf_client_id; +#define NMF_CORE_CLIENT (t_nmf_client_id)-1 +#define NMF_CURRENT_CLIENT (t_nmf_client_id)0 + +typedef struct { + t_uint32 offset; //!< offset relativ to segment start in memory (in bytes) + t_uint32 size; //!< size in bytes of the domain segment +} t_cm_domain_segment; + +/*! + * \brief Domain memory description structure + * \ingroup CM_DOMAIN_API + */ +typedef struct { + t_nmf_core_id coreId; //!< MMDSP Core Id for this domain (used for TCM-X and TCM-Y at instantiate) + t_cm_domain_segment esramCode; //!< ESRAM code segment + t_cm_domain_segment esramData; //!< ESRAM data segment + t_cm_domain_segment sdramCode; //!< SDRAM code segment + t_cm_domain_segment sdramData; //!< SDRAM data segment +} t_cm_domain_memory; + +#define INIT_DOMAIN_SEGMENT {0, 0} +#define INIT_DOMAIN {MASK_ALL8, INIT_DOMAIN_SEGMENT, INIT_DOMAIN_SEGMENT, INIT_DOMAIN_SEGMENT, INIT_DOMAIN_SEGMENT} + + +#endif /* DOMAIN_TYPE_H_ */ diff --git a/drivers/staging/nmf-cm/cm/engine/memory/inc/memory.h b/drivers/staging/nmf-cm/cm/engine/memory/inc/memory.h new file mode 100644 index 00000000000..c6d1fb2dfff --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/inc/memory.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Internal Memory Management API. + * + * \defgroup MEMORY_INTERNAL Private Memory API. + * + */ +#ifndef __INC_MEMORY_H +#define __INC_MEMORY_H + +#include <cm/engine/api/control/configuration_engine.h> +#include <cm/engine/memory/inc/remote_allocator.h> + +#endif /* __INC_MEMORY_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/memory/inc/memory_type.h b/drivers/staging/nmf-cm/cm/engine/memory/inc/memory_type.h new file mode 100644 index 00000000000..dbdba4c3d9d --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/inc/memory_type.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Public Component Manager Memory API type. + * + * This file contains the Component Manager API type for manipulating memory. + */ +#ifndef __INC_MEMORY_TYPE_H +#define __INC_MEMORY_TYPE_H + +#include <cm/inc/cm_type.h> + +/*! + * @defgroup t_cm_mpc_memory_type t_cm_mpc_memory_type + * \brief Definition of symbols used to reference the various type of Media Processor Core adressable memory + * @{ + * \ingroup MEMORY + */ +typedef t_uint8 t_cm_mpc_memory_type; //!< Fake enumeration type +#define CM_MM_MPC_TCM16_X ((t_cm_mpc_memory_type)0) +#define CM_MM_MPC_TCM24_X ((t_cm_mpc_memory_type)1) +#define CM_MM_MPC_ESRAM16 ((t_cm_mpc_memory_type)2) +#define CM_MM_MPC_ESRAM24 ((t_cm_mpc_memory_type)3) +#define CM_MM_MPC_SDRAM16 ((t_cm_mpc_memory_type)4) +#define CM_MM_MPC_SDRAM24 ((t_cm_mpc_memory_type)5) +#define CM_MM_MPC_TCM16_Y ((t_cm_mpc_memory_type)6) +#define CM_MM_MPC_TCM24_Y ((t_cm_mpc_memory_type)7) +#define CM_MM_MPC_TCM16 CM_MM_MPC_TCM16_X +#define CM_MM_MPC_TCM24 CM_MM_MPC_TCM24_X + +/* @} */ + +/*! + * @defgroup t_cm_memory_alignment t_cm_memory_alignment + * \brief Definition of symbols used to constraint the alignment of the allocated memory + * @{ + * \ingroup MEMORY + */ +typedef t_uint16 t_cm_memory_alignment; //!< Fake enumeration type +#define CM_MM_ALIGN_NONE ((t_cm_memory_alignment)0x00000000) +#define CM_MM_ALIGN_BYTE ((t_cm_memory_alignment)CM_MM_ALIGN_NONE) +#define CM_MM_ALIGN_HALFWORD ((t_cm_memory_alignment)0x00000001) +#define CM_MM_ALIGN_WORD ((t_cm_memory_alignment)0x00000003) +#define CM_MM_ALIGN_2WORDS ((t_cm_memory_alignment)0x00000007) +#define CM_MM_ALIGN_16BYTES ((t_cm_memory_alignment)0x0000000F) +#define CM_MM_ALIGN_4WORDS ((t_cm_memory_alignment)0x0000000F) +#define CM_MM_ALIGN_AHB_BURST ((t_cm_memory_alignment)0x0000000F) +#define CM_MM_ALIGN_32BYTES ((t_cm_memory_alignment)0x0000001F) +#define CM_MM_ALIGN_8WORDS ((t_cm_memory_alignment)0x0000001F) +#define CM_MM_ALIGN_64BYTES ((t_cm_memory_alignment)0x0000003F) +#define CM_MM_ALIGN_16WORDS ((t_cm_memory_alignment)0x0000003F) +#define CM_MM_ALIGN_128BYTES ((t_cm_memory_alignment)0x0000007F) +#define CM_MM_ALIGN_32WORDS ((t_cm_memory_alignment)0x0000007F) +#define CM_MM_ALIGN_256BYTES ((t_cm_memory_alignment)0x000000FF) +#define CM_MM_ALIGN_64WORDS ((t_cm_memory_alignment)0x000000FF) +#define CM_MM_ALIGN_512BYTES ((t_cm_memory_alignment)0x000001FF) +#define CM_MM_ALIGN_128WORDS ((t_cm_memory_alignment)0x000001FF) +#define CM_MM_ALIGN_1024BYTES ((t_cm_memory_alignment)0x000003FF) +#define CM_MM_ALIGN_256WORDS ((t_cm_memory_alignment)0x000003FF) +#define CM_MM_ALIGN_2048BYTES ((t_cm_memory_alignment)0x000007FF) +#define CM_MM_ALIGN_512WORDS ((t_cm_memory_alignment)0x000007FF) +#define CM_MM_ALIGN_4096BYTES ((t_cm_memory_alignment)0x00000FFF) +#define CM_MM_ALIGN_1024WORDS ((t_cm_memory_alignment)0x00000FFF) +#define CM_MM_ALIGN_65536BYTES ((t_cm_memory_alignment)0x0000FFFF) +#define CM_MM_ALIGN_16384WORDS ((t_cm_memory_alignment)0x0000FFFF) +/* @} */ + +/*! + * @defgroup t_cm_mpc_memory_alignment t_cm_mpc_memory_alignment + * \brief Definition of symbols used to constraint the alignment of the allocated mpc memory + * @{ + * \ingroup MEMORY + */ +typedef t_uint16 t_cm_mpc_memory_alignment; //!< Fake enumeration type +#define CM_MM_MPC_ALIGN_NONE ((t_cm_mpc_memory_alignment)0x00000000) +#define CM_MM_MPC_ALIGN_HALFWORD ((t_cm_mpc_memory_alignment)0x00000001) +#define CM_MM_MPC_ALIGN_WORD ((t_cm_mpc_memory_alignment)0x00000003) +#define CM_MM_MPC_ALIGN_2WORDS ((t_cm_mpc_memory_alignment)0x00000007) +#define CM_MM_MPC_ALIGN_4WORDS ((t_cm_mpc_memory_alignment)0x0000000F) +#define CM_MM_MPC_ALIGN_8WORDS ((t_cm_mpc_memory_alignment)0x0000001F) +#define CM_MM_MPC_ALIGN_16WORDS ((t_cm_mpc_memory_alignment)0x0000003F) +#define CM_MM_MPC_ALIGN_32WORDS ((t_cm_mpc_memory_alignment)0x0000007F) +#define CM_MM_MPC_ALIGN_64WORDS ((t_cm_mpc_memory_alignment)0x000000FF) +#define CM_MM_MPC_ALIGN_128WORDS ((t_cm_mpc_memory_alignment)0x000001FF) +#define CM_MM_MPC_ALIGN_256WORDS ((t_cm_mpc_memory_alignment)0x000003FF) +#define CM_MM_MPC_ALIGN_512WORDS ((t_cm_mpc_memory_alignment)0x000007FF) +#define CM_MM_MPC_ALIGN_1024WORDS ((t_cm_mpc_memory_alignment)0x00000FFF) +#define CM_MM_MPC_ALIGN_65536BYTES ((t_cm_mpc_memory_alignment)0x0000FFFF) +#define CM_MM_MPC_ALIGN_16384WORDS ((t_cm_mpc_memory_alignment)0x0000FFFF) +/* @} */ + +/*! + * \brief Identifier of a memory handle + * \ingroup MEMORY + */ +typedef t_uint32 t_cm_memory_handle; + +/*! + * \brief Description of a memory segment + * + * <=> allocable addressable space + * \ingroup MEMORY + */ +typedef struct { + t_cm_system_address systemAddr; //!< Logical AND physical segment start address + t_uint32 size; //!< segment size (in bytes) +} t_nmf_memory_segment; +#define INIT_MEMORY_SEGMENT {{0, 0}, 0} + +/*! + * \brief Definition of structure used for an allocator status + * \ingroup MEMORY + */ +typedef struct +{ + struct { + t_uint32 size; //!< size of the allocator + /* Block counters */ + t_uint16 used_block_number; //!< used block number + t_uint16 free_block_number; //!< free block number + + /* Free memory min/max */ + t_uint32 maximum_free_size; //!< maximum free size + t_uint32 minimum_free_size; //!< minimum free size + + /* Accumulation of free and used memory */ + t_uint32 accumulate_free_memory; //!< accumulate free memory + t_uint32 accumulate_used_memory; //!< accumulate used memory + } global; + + struct { + t_uint32 size; //!< size of the domain + t_uint32 maximum_free_size; //!< maximum free size in the given domain + t_uint32 minimum_free_size; //!< minimum free size in the given domain + t_uint32 accumulate_free_memory; //all free memory of the given domain + t_uint32 accumulate_used_memory; //all used memory of the given domain + } domain; + + struct { + t_uint32 sizes[3]; + } stack[NB_CORE_IDS]; + +} t_cm_allocator_status; + +#endif /* __INC_MEMORY_TYPE_H */ + diff --git a/drivers/staging/nmf-cm/cm/engine/memory/inc/migration.h b/drivers/staging/nmf-cm/cm/engine/memory/inc/migration.h new file mode 100644 index 00000000000..824d25374b3 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/inc/migration.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Migration API. + * + * \defgroup + * + */ +#ifndef __INC_MIGRATION_H +#define __INC_MIGRATION_H + +#include <cm/engine/memory/inc/domain_type.h> +#include <cm/engine/dsp/inc/dsp.h> + +typedef enum { + STATE_MIGRATED = 1, + STATE_NORMAL = 0, +} t_cm_migration_state; + +PUBLIC t_cm_error cm_migrate(const t_cm_domain_id srcShared, const t_cm_domain_id src, const t_cm_domain_id dst); + +PUBLIC t_cm_error cm_unmigrate(void); + +PUBLIC t_uint32 cm_migration_translate(t_dsp_segment_type segmentType, t_uint32 addr); + +PUBLIC void cm_migration_check_state(t_nmf_core_id coreId, t_cm_migration_state expected); + +#endif /* __INC_MIGRATION_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/memory/inc/remote_allocator.h b/drivers/staging/nmf-cm/cm/engine/memory/inc/remote_allocator.h new file mode 100644 index 00000000000..36994f37daa --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/inc/remote_allocator.h @@ -0,0 +1,275 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + * + * \note: In this module, we assume that parameters were checked !! + */ +#ifndef __REMOTE_ALLOCATOR_H_ +#define __REMOTE_ALLOCATOR_H_ + +/* + * Include + */ +#include <cm/inc/cm_type.h> +#include <cm/engine/memory/inc/memory_type.h> + + +/* + * Description of the memory block status + */ +typedef enum { + MEM_USED = 0, /* Memory block is used */ + MEM_FREE = 1 /* Memory block is free */ +} t_mem_status; + +/* + * Chunk structure. + */ +struct cm_allocator_desc; +typedef struct chunk_struct +{ + /* Double linked list of chunks */ + struct chunk_struct *prev; + struct chunk_struct *next; + + /* Double linked list of free memory */ + struct chunk_struct *prev_free_mem; + struct chunk_struct *next_free_mem; + + /* Offset of the block memory */ + t_uint32 offset; + + /* Size of the block memory */ + t_cm_size size; + + /* Status of the block memory */ + t_mem_status status; + + /* User data */ + t_uint16 userData; + + /* Alloc debug info*/ + t_uint32 domainId; + + /* Alloc desc backlink */ + struct cm_allocator_desc *alloc; +} t_cm_chunk; + +/*! + * \brief Identifier of an internal memory handle + * \ingroup MEMORY_INTERNAL + */ +typedef t_cm_chunk* t_memory_handle; + +#define INVALID_MEMORY_HANDLE ((t_cm_chunk*)NULL) + + +/* + * Context structure + */ +#define BINS 63 + +//TODO, juraj, add memType to alloc struct ? +typedef struct cm_allocator_desc { + const char *pAllocName; /* Name of the allocator */ + t_uint32 maxSize; /* Max size of the allocator -> Potentially increase/decrease by stack management */ + t_uint32 sbrkSize; /* Current size of allocator */ + t_uint32 offset; /* Offset of the allocator */ + t_cm_chunk *chunks; /* Array of chunk */ + t_cm_chunk *lastChunk; /* Null terminated last chunk of previous array declaration */ + t_cm_chunk *free_mem_chunks[BINS]; /* List of free memory */ + struct cm_allocator_desc* next; /* List of allocator */ +} t_cm_allocator_desc; + +int bin_index(unsigned int sz); + +/* + * Functions + */ +/*! + * \brief Create a new allocator for a piece of memory (hw mapped (xram, yram)) + * Any further allocation into this piece of memory will return an offset inside it. + * (a constant offset value can be added to this offset) + * + * \retval t_cm_allocator_desc* new memory allocator identifier + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC t_cm_allocator_desc* cm_MM_CreateAllocator( + t_cm_size size, //!< [in] Size of the addressable space in bytes + t_uint32 offset, //!< [in] Constant offset to add to each allocated block base address + const char* name //!< [in] Name of the allocator + ); + +/*! + * \brief Free a memory allocator descriptor + * + * \retval t_cm_error + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC t_cm_error cm_MM_DeleteAllocator( + t_cm_allocator_desc* alloc //!< [in] Identifier of the memory allocator to be freed + ); + + +/*! + * \brief Resize an allocator to the size value. + * + * \retval t_cm_error + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC t_cm_error cm_MM_ResizeAllocator( + t_cm_allocator_desc* alloc, //!< [in] Identifier of the memory allocator used to allocate the piece of memory + t_cm_size size //!< [in] Size of the addressable space in allocDesc granularity + ); + +/*! + * \brief Check validity of a user handle + */ +t_cm_error cm_MM_getValidMemoryHandle(t_cm_memory_handle handle, t_memory_handle* validHandle); + +/*! + * \brief Wrapper routine to allocate some memory into a given allocator + * + * \retval t_memory_handle handle on the new allocated piece of memory + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC t_memory_handle cm_MM_Alloc( + t_cm_allocator_desc* alloc, //!< [in] Identifier of the memory allocator + t_cm_size size, //!< [in] Size of the addressable space + t_cm_memory_alignment memAlignment, //!< [in] Alignment constraint + t_uint32 seg_offset, //!< [in] Offset of range where allocating + t_uint32 seg_size, //!< [in] Size of range where allocating + t_uint32 domainId + ); + + +/*! + * \brief Routine to reallocate memory for a given handle + * + * Routine to reallocate memory for a given handle. The chunk can be extended or shrinked in both + * directions - top and bottom, depending on the offset and size arguments. + * + * \retval t_memory_handle handle on the reallocated piece of memory + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC t_cm_error cm_MM_Realloc( + t_cm_allocator_desc* alloc, + const t_cm_size size, + const t_uint32 offset, + t_memory_handle *handle); +/*! + * \brief Frees the allocated chunk + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC void cm_MM_Free( + t_cm_allocator_desc* alloc, //!< [in] Identifier of the memory allocator + t_memory_handle memHandle //!< [in] Memory handle to free + ); + + +/*! + * \brief Get the allocator status + * + * \param[in] alloc Identifier of the memory allocator + * \param[out] pStatus Status of the allocator + * + * \retval t_cm_error + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC t_cm_error cm_MM_GetAllocatorStatus(t_cm_allocator_desc* alloc, t_uint32 offset, t_uint32 size, t_cm_allocator_status *pStatus); + +/*! + * \brief Returns the offset into a given memory allocator of an allocated piece of memory + * + * \param[in] memHandle handle on the given memory + * + * \retval t_uint32 offset into the given memory allocator + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC t_uint32 cm_MM_GetOffset(t_memory_handle memHandle); + + +/*! + * \brief Returns the size in word size for a given memory allocator of an allocated piece of memory + * + * \param[in] memHandle handle on the given memory + * + * \retval t_uint32 size in wordsize for the given memory allocator + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC t_uint32 cm_MM_GetSize(t_memory_handle memHandle); + +/*! + * \brief Returns the size in bytes for a given memory allocator + * + * \param[in] allocDesc Identifier of the memory allocator + * \retval size + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC t_uint32 cm_MM_GetAllocatorSize(t_cm_allocator_desc* allocDesc); + + +/*! + * \brief Set the user data of an allocated piece of memory + * + * \param[in] memHandle handle on the given memory + * \param[in] userData UsedData of the given memory piece + * + * \retval t_cm_error + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC void cm_MM_SetMemoryHandleUserData (t_memory_handle memHandle, t_uint16 userData); + + +/*! + * \brief Return the user data of an allocated piece of memory + * + * \param[in] memHandle handle on the given memory + * \param[out] pUserData returned UsedData of the given memory piece + * + * \retval t_cm_error + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC void cm_MM_GetMemoryHandleUserData(t_memory_handle memHandle, t_uint16 *pUserData, t_cm_allocator_desc **alloc); + +/*! + * \brief Dump chunkd in the range of [start:end] + * + * \param[in] alloc Allocator descriptor + * \param[in] start Range start + * \param[in] end Range end + * + * \retval void + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC void cm_MM_DumpMemory(t_cm_allocator_desc* alloc, t_uint32 start, t_uint32 end); + +/*! + * \brief Change the domain for the given chunk of memory + * + * \param[in] memHandle The given chunk of memory + * \param[in] domainId The new domain id to set + * + * \retval void + * + * \ingroup MEMORY_INTERNAL + */ +PUBLIC void cm_MM_SetDefaultDomain(t_memory_handle memHandle, t_uint32 domainId); +#endif /* _REMOTE_ALLOCATOR_H_*/ diff --git a/drivers/staging/nmf-cm/cm/engine/memory/inc/remote_allocator_utils.h b/drivers/staging/nmf-cm/cm/engine/memory/inc/remote_allocator_utils.h new file mode 100644 index 00000000000..0a7c901187b --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/inc/remote_allocator_utils.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef REMOTE_ALLOCATOR_UTILS_H_ +#define REMOTE_ALLOCATOR_UTILS_H_ + +#include <cm/engine/memory/inc/remote_allocator.h> +#include <cm/engine/memory/inc/chunk_mgr.h> + +typedef enum { + FREE_CHUNK_BEFORE, + FREE_CHUNK_AFTER, +} t_mem_split_position; + + +PUBLIC void updateFreeList(t_cm_allocator_desc* alloc, t_cm_chunk* chunk); + +PUBLIC void linkChunk(t_cm_allocator_desc* alloc, t_cm_chunk* prev,t_cm_chunk* add); +PUBLIC void unlinkChunk(t_cm_allocator_desc* alloc,t_cm_chunk* current); + +PUBLIC void unlinkFreeMem(t_cm_allocator_desc* alloc,t_cm_chunk* current); +PUBLIC void linkFreeMemBefore(t_cm_chunk* add, t_cm_chunk* next); +PUBLIC void linkFreeMemAfter(t_cm_chunk* prev,t_cm_chunk* add); + +PUBLIC t_cm_chunk* splitChunk(t_cm_allocator_desc* alloc, t_cm_chunk *chunk, t_uint32 offset, t_mem_split_position position); + +#endif /*REMOTE_ALLOCATOR_UTILS_H_*/ diff --git a/drivers/staging/nmf-cm/cm/engine/memory/src/chunk_mgr.c b/drivers/staging/nmf-cm/cm/engine/memory/src/chunk_mgr.c new file mode 100644 index 00000000000..78a549a74ce --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/src/chunk_mgr.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * Include + */ +#include <cm/inc/cm_type.h> +#include "../inc/chunk_mgr.h" +#include <cm/engine/trace/inc/trace.h> + +#define CHUNKS_PER_PAGE 500 +#define CHUNK_THRESOLD 5 + +struct t_page_chuncks { + struct t_page_chuncks *nextPage; + // unsigned int freeChunkInPage; + t_cm_chunk chunks[CHUNKS_PER_PAGE]; +}; + +static struct t_page_chuncks *firstPage = 0; + +static unsigned int freeChunks = 0; +static t_cm_chunk *firstFreeChunk = 0; + +t_cm_chunk* allocChunk() +{ + t_cm_chunk* chunk = firstFreeChunk; + + firstFreeChunk = chunk->next; + + chunk->next_free_mem = 0; + chunk->prev_free_mem = 0; + chunk->prev = 0; + chunk->next = 0; + chunk->status = MEM_FREE; + // chunk->offset = 0; + // chunk->size = 0; + // chunk->alloc = 0; + // chunk->userData = 0; + + freeChunks--; + + return chunk; +} + +void freeChunk(t_cm_chunk* chunk) +{ + // Link chunk in free list + chunk->next = firstFreeChunk; + firstFreeChunk = chunk; + + // Increase counter + freeChunks++; +} + +t_cm_error allocChunkPool(void) +{ + struct t_page_chuncks* newPage; + int i; + + newPage = (struct t_page_chuncks*)OSAL_Alloc(sizeof(struct t_page_chuncks)); + if(newPage == NULL) + return CM_NO_MORE_MEMORY; + + // Link page + newPage->nextPage = firstPage; + firstPage = newPage; + + // Put chunk in free list + for(i = 0; i < CHUNKS_PER_PAGE; i++) + freeChunk(&newPage->chunks[i]); + + return CM_OK; +} + +t_cm_error fillChunkPool(void) +{ + if(freeChunks < CHUNK_THRESOLD) + return allocChunkPool(); + + return CM_OK; +} + +void freeChunkPool(void) +{ + while(firstPage != NULL) + { + struct t_page_chuncks* tofree = firstPage; + firstPage = firstPage->nextPage; + OSAL_Free(tofree); + } + + firstFreeChunk = 0; + freeChunks = 0; +} diff --git a/drivers/staging/nmf-cm/cm/engine/memory/src/domain.c b/drivers/staging/nmf-cm/cm/engine/memory/src/domain.c new file mode 100644 index 00000000000..a605cc475a9 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/src/domain.c @@ -0,0 +1,608 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/inc/cm_type.h> +#include <inc/nmf-limits.h> + +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/memory/inc/migration.h> +#include <cm/engine/memory/inc/chunk_mgr.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/component/inc/instance.h> +#include <cm/engine/power_mgt/inc/power.h> +#include <cm/engine/trace/inc/trace.h> + +/* + * domain_memory structure is all we need + */ +#define MAX_USER_DOMAIN_NB 64 +#define MAX_SCRATCH_DOMAIN_NB 16 + +t_cm_domain_desc domainDesc[MAX_USER_DOMAIN_NB]; +t_cm_domain_scratch_desc domainScratchDesc[MAX_SCRATCH_DOMAIN_NB]; + +static t_cm_allocator_desc *cm_DM_getAllocator(t_cm_domain_id domainId, t_dsp_memory_type_id memType); +static void cm_DM_DomainError(const t_cm_domain_id parentId, const t_nmf_client_id client); + +#define INIT_DOMAIN_STRUCT(domainDesc) do { \ + domainDesc.client = 0; \ + domainDesc.type = DOMAIN_NORMAL; \ + domainDesc.refcount = 0; \ + domainDesc.domain.coreId = MASK_ALL8; \ + domainDesc.domain.esramCode.offset = 0; \ + domainDesc.domain.esramCode.size = 0; \ + domainDesc.domain.esramData.offset = 0; \ + domainDesc.domain.esramData.size = 0; \ + domainDesc.domain.sdramCode.offset = 0; \ + domainDesc.domain.sdramCode.size = 0; \ + domainDesc.domain.sdramData.offset = 0; \ + domainDesc.domain.sdramData.size = 0; \ + domainDesc.scratch.parent.handle = 0; \ + domainDesc.scratch.child.alloc = 0; \ + domainDesc.scratch.child.parent_ref = 0; \ + domainDesc.dbgCooky = NULL; \ + } while (0) + +#define FIND_DOMAIN_ID(domainId) \ + { \ + domainId = 0; \ + while (domainDesc[domainId].client != 0 && domainId < MAX_USER_DOMAIN_NB) { \ + domainId++; \ + } \ + if (domainId >= MAX_USER_DOMAIN_NB) { \ + return CM_INTERNAL_DOMAIN_OVERFLOW; \ + } \ + } + +#define FIND_SCRATCH_DOMAIN_ID(domainId) \ + { \ + domainId = 0; \ + while (domainScratchDesc[domainId].allocDesc != 0 && domainId < MAX_SCRATCH_DOMAIN_NB) { \ + domainId++; \ + } \ + if (domainId >= MAX_SCRATCH_DOMAIN_NB) { \ + return CM_INTERNAL_DOMAIN_OVERFLOW; \ + } \ + } + +PUBLIC t_cm_error cm_DM_CheckDomain(t_cm_domain_id handle, t_cm_domain_type type) +{ + if ((handle <= 3) + || (handle >= MAX_USER_DOMAIN_NB)) { //remember, domain[0-3] are reserved + return CM_INVALID_DOMAIN_HANDLE; + } + + if (domainDesc[handle].client == 0) { + return CM_INVALID_DOMAIN_HANDLE; + } + + if (type != DOMAIN_ANY) { + if (domainDesc[handle].type != type) { + return CM_INVALID_DOMAIN_HANDLE; + } + } + + return CM_OK; +} + +PUBLIC t_cm_error cm_DM_CheckDomainWithClient(t_cm_domain_id handle, t_cm_domain_type type, t_nmf_client_id client) +{ + t_cm_error error; + + if((error = cm_DM_CheckDomain(handle, type)) != CM_OK) + return error; + +#ifdef CHECK_TO_BE_REACTIVATED_IN_2_11 + if(domainDesc[handle].client != client) + { + ERROR("CM_DOMAIN_VIOLATION: domain %d created by client %d not usable by client %d.", handle, domainDesc[handle].client, client, 0, 0, 0); + return CM_DOMAIN_VIOLATION; + } +#endif + + return CM_OK; +} + +PUBLIC t_cm_error cm_DM_Init(void) +{ + t_cm_error error; + + int i = 0; + for(i = 0; i < MAX_USER_DOMAIN_NB; i++) { + INIT_DOMAIN_STRUCT(domainDesc[i]); + } + + //domains[0-3] are reserved - allows to catch some cases of incorrect usage, + //especially when user uses coreId instead of domainId, ie id = 1, 2, 3 + domainDesc[0].client = NMF_CORE_CLIENT; + domainDesc[1].client = NMF_CORE_CLIENT; + domainDesc[2].client = NMF_CORE_CLIENT; + domainDesc[3].client = NMF_CORE_CLIENT; + + /* We use domain 1 and 2 for the singleton, only used for components structure */ + domainDesc[DEFAULT_SVA_DOMAIN].type = DOMAIN_NORMAL; + domainDesc[DEFAULT_SVA_DOMAIN].domain.coreId= SVA_CORE_ID; + domainDesc[DEFAULT_SVA_DOMAIN].domain.esramCode.size = (t_uint32)-1; + domainDesc[DEFAULT_SVA_DOMAIN].domain.esramData.size = (t_uint32)-1; + domainDesc[DEFAULT_SVA_DOMAIN].domain.sdramCode.size = (t_uint32)-1; + domainDesc[DEFAULT_SVA_DOMAIN].domain.sdramData.size = (t_uint32)-1; + domainDesc[DEFAULT_SIA_DOMAIN].type = DOMAIN_NORMAL; + domainDesc[DEFAULT_SIA_DOMAIN].domain.coreId= SIA_CORE_ID; + domainDesc[DEFAULT_SIA_DOMAIN].domain.esramCode.size = (t_uint32)-1; + domainDesc[DEFAULT_SIA_DOMAIN].domain.esramData.size = (t_uint32)-1; + domainDesc[DEFAULT_SIA_DOMAIN].domain.sdramCode.size = (t_uint32)-1; + domainDesc[DEFAULT_SIA_DOMAIN].domain.sdramData.size = (t_uint32)-1; + + for(i = 0; i < MAX_SCRATCH_DOMAIN_NB; i++) { + domainScratchDesc[i].domainId = 0; + domainScratchDesc[i].parentId = 0; + domainScratchDesc[i].allocDesc = 0; + } + + // Alloc twice for having comfortable chunk + if((error = allocChunkPool()) != CM_OK) + return error; + if((error = allocChunkPool()) != CM_OK) + { + freeChunkPool(); + return error; + } + + return CM_OK; +} + +PUBLIC void cm_DM_Destroy(void) +{ + //cm_DM_Init(); + freeChunkPool(); +} + +PUBLIC t_nmf_core_id cm_DM_GetDomainCoreId(const t_cm_domain_id domainId) +{ + + return domainDesc[domainId].domain.coreId; +} + +#if 0 +static t_uint32 cm_DM_isSegmentOverlaping(const t_cm_domain_segment *d0, const t_cm_domain_segment *d1) +{ + t_uint32 min0 = d0->offset; + t_uint32 max0 = d0->offset + d0->size; + t_uint32 min1 = d1->offset; + t_uint32 max1 = d1->offset + d1->size; + + if ( (min0 < min1) && (min1 < max0) ){ /* min0 < min1 < max0 OR min1 in [min0:max0] */ + return 1; + } + if ( (min1 < min0) && (min0 <= max1) ){ /* min1 < min0 < max0 OR min0 in [min1:max1] */ + return 1; + } + + return 0; +} +{ + ... + + t_uint32 i; + //check non-overlapp with other domains + for (i = 0; i < MAX_USER_DOMAIN_NB; i++) { + if (domainDesc[i].client != 0) { + if (cm_DM_isSegmentOverlaping(&domainDesc[i].domain.esramData, &domain->esramData)) { + return CM_DOMAIN_OVERLAP; + } + /* + if (cm_DM_isSegmentOverlaping(&domainDesc[i].domain.esramData, &domain->esramData)) { + return CM_DOMAIN_OVERLAP; + } + */ + } + } + + ... +} +#endif + +PUBLIC t_cm_error cm_DM_CreateDomain(const t_nmf_client_id client, const t_cm_domain_memory *domain, t_cm_domain_id *handle) +{ + t_cm_domain_id domainId; + FIND_DOMAIN_ID(domainId); + + if (client == 0) + return CM_INVALID_PARAMETER; + + if (domain->coreId > LAST_CORE_ID) + return CM_INVALID_DOMAIN_DEFINITION; + + //FIXME, juraj, check invalid domain definition + domainDesc[domainId].client = client; + domainDesc[domainId].domain = *domain; + + if (osal_debug_ops.domain_create) + osal_debug_ops.domain_create(domainId); + + *handle = domainId; + + return CM_OK; +} + +//TODO, juraj, add assert to cm_MM_GetOffset(), if domain is scratch parent +PUBLIC t_cm_error cm_DM_CreateDomainScratch(const t_nmf_client_id client, const t_cm_domain_id parentId, const t_cm_domain_memory *domain, t_cm_domain_id *handle) +{ + t_cm_error error; + t_memory_handle memhandle; + t_cm_allocator_desc *alloc; + t_uint32 parentMin, parentMax; + t_uint32 scratchMin, scratchMax; + + /* check if the parent domain exists */ + /* parent could be DOMAIN_NORMAL (1st call) or DOMAIN_SCRATCH_PARENT (other calls) */ + if ((error = cm_DM_CheckDomain(parentId, DOMAIN_ANY)) != CM_OK) { + return error; + } + + parentMin = domainDesc[parentId].domain.esramData.offset; + parentMax = domainDesc[parentId].domain.esramData.offset + domainDesc[parentId].domain.esramData.size; + scratchMin = domain->esramData.offset; + scratchMax = domain->esramData.offset + domain->esramData.size; + /* check if the scratch domain respects the parent domain (esram data only )*/ + if ( (parentMin > scratchMin) || (parentMax < scratchMax) ) { + return CM_INVALID_DOMAIN_DEFINITION; + } + + /* create the scratch domain */ + if ((error = cm_DM_CreateDomain(client, domain, handle)) != CM_OK) { + return error; + } + + /* check if this is the first scratch domain */ + if (domainDesc[parentId].scratch.parent.handle == 0) { + /* 1st scratch domain */ + t_cm_domain_segment tmp; + + /* reserve the zone for the scratch domain */ + tmp = domainDesc[parentId].domain.esramData; + domainDesc[parentId].domain.esramData = domain->esramData; + memhandle = cm_DM_Alloc(parentId, ESRAM_EXT16, domain->esramData.size / 2, CM_MM_ALIGN_NONE, FALSE); //note byte to 16bit-word conversion + domainDesc[parentId].domain.esramData = tmp; + if (memhandle == 0) { + cm_DM_DestroyDomain(*handle); + cm_DM_DomainError(parentId, client); + return CM_NO_MORE_MEMORY; + } + + domainDesc[parentId].type = DOMAIN_SCRATCH_PARENT; + domainDesc[parentId].refcount = 0; //reinit the refcount + domainDesc[parentId].scratch.parent.handle = memhandle; + + } else { + /* nth scratch domain */ + t_uint32 i; + t_uint32 oldMin = domainDesc[parentId].domain.esramData.offset + domainDesc[parentId].domain.esramData.offset; + t_uint32 oldMax = 0; + + /* compute the new scratch zone size */ + for(i = 0; i < MAX_USER_DOMAIN_NB; i++) { + if ((domainDesc[i].type == DOMAIN_SCRATCH_CHILD) && (domainDesc[i].scratch.child.parent_ref == parentId)) { + /* ok, here we have a scratch domain created from the same child domain */ + t_uint32 min = domainDesc[i].domain.esramData.offset; + t_uint32 max = domainDesc[i].domain.esramData.offset + domainDesc[i].domain.esramData.size; + + oldMin = (min < oldMin)?min:oldMin; + oldMax = (max > oldMax)?max:oldMax; + } + } + + /* resize the scratch zone */ + if ((oldMin > scratchMin) || (oldMax < scratchMax)) { + t_uint32 newMin = (oldMin > scratchMin)?scratchMin:oldMin; + t_uint32 newMax = (oldMax < scratchMax)?scratchMax:oldMax; + + if(cm_MM_Realloc(cm_DM_getAllocator(parentId, ESRAM_EXT16), newMax - newMin, newMin, + &domainDesc[parentId].scratch.parent.handle) != CM_OK) + { + /* failed to extend the zone */ + cm_DM_DestroyDomain(*handle); + cm_DM_DomainError(parentId, client); + return CM_NO_MORE_MEMORY; + } + } + } + + /* create esram-data allocator in the scratch domain */ + alloc = cm_MM_CreateAllocator(domainDesc[*handle].domain.esramData.size, + domainDesc[*handle].domain.esramData.offset, + "scratch"); + + domainDesc[*handle].type = DOMAIN_SCRATCH_CHILD; + domainDesc[*handle].scratch.child.parent_ref = parentId; + domainDesc[*handle].scratch.child.alloc = alloc; + domainDesc[parentId].refcount++; + + return error; +} + +PUBLIC t_cm_error cm_DM_DestroyDomains(const t_nmf_client_id client) +{ + t_cm_domain_id handle; + t_cm_error error, status=CM_OK; + + for (handle=0; handle<MAX_USER_DOMAIN_NB; handle++) { + if ((domainDesc[handle].client == client) + && ((error=cm_DM_DestroyDomain(handle)) != CM_OK)) { + LOG_INTERNAL(0, "Error (%d) destroying remaining domainId %d for client %u\n", error, handle, client, 0, 0, 0); + status = error; + } + } + return status; +} + +PUBLIC t_cm_error cm_DM_DestroyDomain(t_cm_domain_id handle) +{ + t_cm_error error = CM_OK; + t_uint32 i; + + if ((error = cm_DM_CheckDomain(handle, DOMAIN_ANY)) != CM_OK) { + return error; + } + + //forbid destruction of cm domains + //if (handle == cm_DSP_GetState(domainDesc[handle].domain.coreId)->domainEE) + // return CM_INVALID_DOMAIN_HANDLE; + + /* loop all components and check if there are still components instantiated with this handle */ + //actually this check is redundant with the usage counters as component instantiations allocate memory + for (i=0; i<ComponentTable.idxMax; i++) + { + if (NULL != componentEntry(i) && componentEntry(i)->domainId == handle) { + return CM_ILLEGAL_DOMAIN_OPERATION; + } + } + + //perform check based on usage counters + if (domainDesc[handle].refcount != 0) { + return CM_ILLEGAL_DOMAIN_OPERATION; + } + + if (domainDesc[handle].type == DOMAIN_SCRATCH_PARENT) { + return CM_ILLEGAL_DOMAIN_OPERATION; //parent destroyed implicitly with the last scratch + } else if (domainDesc[handle].type == DOMAIN_SCRATCH_CHILD) { + t_cm_allocator_status status; + t_cm_domain_id parentId = domainDesc[handle].scratch.child.parent_ref; + + cm_MM_GetAllocatorStatus(domainDesc[handle].scratch.child.alloc, 0, 0xffff, &status); + if (status.global.accumulate_used_memory != 0) { + //something is still allocated + return CM_ILLEGAL_DOMAIN_OPERATION; + } + + domainDesc[parentId].refcount--; + cm_MM_DeleteAllocator(domainDesc[handle].scratch.child.alloc); //returns no error + + if (domainDesc[parentId].refcount == 0) { + /* last scratch domain */ + cm_DM_Free(domainDesc[parentId].scratch.parent.handle, FALSE); + domainDesc[parentId].scratch.parent.handle = 0; + domainDesc[parentId].type = DOMAIN_NORMAL; + } else { + /* other child scratch domains exist, check if the reserved zone needs resize, ie reduce */ + + t_uint32 i; + /* init oldMin and oldMax to values we are sure will get overwritten below */ + t_uint32 oldMin = 0xffffffff; + t_uint32 oldMax = 0x0; + t_uint32 scratchMin = domainDesc[handle].domain.esramData.offset; + t_uint32 scratchMax = domainDesc[handle].domain.esramData.offset + domainDesc[handle].domain.esramData.size; + + /* compute the remaining reserved zone size */ + for(i = 0; i < MAX_USER_DOMAIN_NB; i++) { + if (i == handle) + continue; //do not consider the current domain to be destroyed later in this function + if ((domainDesc[i].type == DOMAIN_SCRATCH_CHILD) && (domainDesc[i].scratch.child.parent_ref == parentId)) { + /* ok, here we have a scratch domain created from the same child domain */ + t_uint32 min = domainDesc[i].domain.esramData.offset; + t_uint32 max = domainDesc[i].domain.esramData.offset + domainDesc[i].domain.esramData.size; + + oldMin = (min < oldMin)?min:oldMin; + oldMax = (max > oldMax)?max:oldMax; + } + } + + /* resize the scratch zone */ + if ((oldMin > scratchMin) || (oldMax < scratchMax)) { + CM_ASSERT(cm_MM_Realloc(cm_DM_getAllocator(parentId, ESRAM_EXT16), oldMax - oldMin, oldMin, + &domainDesc[parentId].scratch.parent.handle) == CM_OK); //the realloc shouldn't fail.. + } + } + } + + if (osal_debug_ops.domain_destroy) + osal_debug_ops.domain_destroy(handle); + + //reset the domain desc + INIT_DOMAIN_STRUCT(domainDesc[handle]); + + return CM_OK; +} + +/* + * - if the domainId is scratch parent, all allocations are done as in normal domains + * - if the domainId is scratch child + * if allocation type is esram, retrieve the allocator from the domainDesc + * else allocation is done as for normal domain + * - if the domainId is normal, allocator is retrieved from mpcDesc via cm_DSP_GetAllocator() + */ +static t_cm_allocator_desc *cm_DM_getAllocator(t_cm_domain_id domainId, t_dsp_memory_type_id memType) +{ + t_cm_allocator_desc *alloc = 0; + + if ((domainDesc[domainId].type == DOMAIN_SCRATCH_CHILD) + && ((memType == ESRAM_EXT16) || (memType == ESRAM_EXT24))) { + alloc = domainDesc[domainId].scratch.child.alloc; + } else { + alloc = cm_DSP_GetAllocator(domainDesc[domainId].domain.coreId, memType); + } + + return alloc; +} + +void START(void); +void END(const char*); + +//TODO, juraj, alloc would need to return finer errors then 0 +PUBLIC t_memory_handle cm_DM_Alloc(t_cm_domain_id domainId, t_dsp_memory_type_id memType, t_uint32 wordSize, t_cm_mpc_memory_alignment memAlignment, t_bool powerOn) +{ + t_nmf_core_id coreId = domainDesc[domainId].domain.coreId; + t_memory_handle handle; + t_cm_allocator_desc *alloc; + t_uint32 offset; + t_uint32 size; + + cm_DSP_GetInternalMemoriesInfo(domainId, memType, &offset, &size); + + if ((alloc = cm_DM_getAllocator(domainId, memType)) == 0) { + return 0; + } + + handle = cm_MM_Alloc(alloc, + cm_DSP_ConvertSize(memType, wordSize), + (t_cm_memory_alignment) memAlignment, + offset, size, domainId); + + if(handle != INVALID_MEMORY_HANDLE) + { + cm_MM_SetMemoryHandleUserData(handle, (coreId << SHIFT_BYTE1) | (memType << SHIFT_BYTE0)); + + if (powerOn) { + // [Pwr] The associated power domain can be enabled only after the Alloc request. + // Associated MPC memory chunk is not accessed (Remote allocator feature) + cm_PWR_EnableMemory( + coreId, + memType, + /* + * Compute physical address based on cm_DSP_GetHostSystemAddress but in optimized way + * -> See it for information + * -> Note TCM memory is not correctly compute, but it's not used + */ + cm_DSP_GetState(coreId)->allocator[memType]->baseAddress.physical + cm_MM_GetOffset(handle), + cm_MM_GetSize(handle)); + } + } else { + LOG_INTERNAL(0, "CM_NO_MORE_MEMORY domainId: %d, memType %d, wordSize %d, alignement %d\n", + domainId, memType, wordSize, memAlignment, 0, 0); + cm_MM_DumpMemory(alloc, offset, offset + size); + } + + return handle; +} + +PUBLIC void cm_DM_FreeWithInfo(t_memory_handle memHandle, t_nmf_core_id *coreId, t_dsp_memory_type_id *memType, t_bool powerOff) +{ + t_dsp_chunk_info chunk_info; + + cm_DSP_GetDspChunkInfo(memHandle, &chunk_info); + + if (powerOff) { + cm_PWR_DisableMemory( + chunk_info.coreId, + chunk_info.memType, + cm_DSP_GetPhysicalAdress(memHandle), + cm_MM_GetSize(memHandle)); + } + + cm_MM_Free(chunk_info.alloc, memHandle); + + *coreId = chunk_info.coreId; + *memType = chunk_info.memType; +} + +PUBLIC void cm_DM_Free(t_memory_handle memHandle, t_bool powerOff) +{ + t_nmf_core_id coreId; + t_dsp_memory_type_id memType; + + cm_DM_FreeWithInfo(memHandle, &coreId, &memType, powerOff); +} + +PUBLIC t_cm_error cm_DM_GetAllocatorStatus(t_cm_domain_id domainId, t_dsp_memory_type_id memType, t_cm_allocator_status *pStatus) +{ + t_cm_error error; + t_uint32 dOffset; + t_uint32 dSize; + + //TODO, scratch + error = cm_DM_CheckDomain(domainId, DOMAIN_ANY); + if (error != CM_OK) { + return error; + } + + cm_DSP_GetInternalMemoriesInfo(domainId, memType, &dOffset, &dSize); + + return cm_DSP_GetAllocatorStatus(domainDesc[domainId].domain.coreId, memType, + dOffset, dSize, pStatus); +} + +//WARNING: this function is only correct *before* migration! because +//the computation of absolute adresses of a domain is based on the allocator for the given +//segment (this is hidden in cm_DSP_GetDspBaseAddress and this info is not valid +//after migration (non-contiguous address-space from the ARM-side) +PUBLIC t_cm_error cm_DM_GetDomainAbsAdresses(t_cm_domain_id domainId, t_cm_domain_info *info) +{ + t_cm_error error; + t_nmf_core_id coreId = domainDesc[domainId].domain.coreId; + + cm_migration_check_state(coreId, STATE_NORMAL); + + error = cm_DM_CheckDomain(domainId, DOMAIN_NORMAL); + if (error != CM_OK) { + return error; + } + + cm_DSP_GetDspBaseAddress(coreId, SDRAM_CODE, &info->sdramCode); + cm_DSP_GetDspBaseAddress(coreId, ESRAM_CODE, &info->esramCode); + cm_DSP_GetDspBaseAddress(coreId, SDRAM_EXT24, &info->sdramData); + cm_DSP_GetDspBaseAddress(coreId, ESRAM_EXT24, &info->esramData); + + info->sdramCode.physical += domainDesc[domainId].domain.sdramCode.offset; + info->sdramCode.logical += domainDesc[domainId].domain.sdramCode.offset; + info->esramCode.physical += domainDesc[domainId].domain.esramCode.offset; + info->esramCode.logical += domainDesc[domainId].domain.esramCode.offset; + info->sdramData.physical += domainDesc[domainId].domain.sdramData.offset; + info->sdramData.logical += domainDesc[domainId].domain.sdramData.offset; + info->esramData.physical += domainDesc[domainId].domain.esramData.offset; + info->esramData.logical += domainDesc[domainId].domain.esramData.offset; + + return CM_OK; +} + +static void cm_DM_DomainError(const t_cm_domain_id parentId, const t_nmf_client_id client) +{ + int i; + LOG_INTERNAL(0, "NMF_DEBUG_SCRATCH failed to allocate domain (client %u): 0x%08x -> 0x%08x\n", + client, + domainDesc[parentId].domain.esramData.offset, + domainDesc[parentId].domain.esramData.offset + domainDesc[parentId].domain.esramData.size, + 0, 0, 0); + for(i = 0; i < MAX_USER_DOMAIN_NB; i++) { + if (domainDesc[i].type == DOMAIN_SCRATCH_CHILD) { + LOG_INTERNAL(0, "NMF_DEBUG_SCRATCH scratch domain %d allocated (client %u): 0x%08x -> 0x%08x\n", + i, domainDesc[i].client, + domainDesc[i].domain.esramData.offset, + domainDesc[i].domain.esramData.offset + domainDesc[i].domain.esramData.size, + 0, 0); + } + } + cm_MM_DumpMemory(cm_DM_getAllocator(parentId, ESRAM_EXT16), + domainDesc[parentId].domain.esramData.offset, + domainDesc[parentId].domain.esramData.offset + domainDesc[parentId].domain.esramData.size); +} + +PUBLIC void cm_DM_SetDefaultDomain(t_memory_handle memHandle, t_nmf_core_id coreId) +{ + if (coreId == SVA_CORE_ID) + cm_MM_SetDefaultDomain(memHandle, DEFAULT_SVA_DOMAIN); + else if (coreId == SIA_CORE_ID) + cm_MM_SetDefaultDomain(memHandle, DEFAULT_SIA_DOMAIN); +} diff --git a/drivers/staging/nmf-cm/cm/engine/memory/src/domain_wrapper.c b/drivers/staging/nmf-cm/cm/engine/memory/src/domain_wrapper.c new file mode 100644 index 00000000000..ec305812f15 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/src/domain_wrapper.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/api/domain_engine.h> +#include <cm/engine/api/migration_engine.h> +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/memory/inc/migration.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_CreateMemoryDomain( + const t_nmf_client_id client, + const t_cm_domain_memory *domain, + t_cm_domain_id *handle + ) +{ + t_cm_error error; + + OSAL_LOCK_API(); + error = cm_DM_CreateDomain(client, domain, handle); + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_CreateMemoryDomainScratch( + const t_nmf_client_id client, + const t_cm_domain_id parentId, + const t_cm_domain_memory *domain, + t_cm_domain_id *handle + ) +{ + t_cm_error error; + + OSAL_LOCK_API(); + error = cm_DM_CreateDomainScratch(client, parentId, domain, handle); + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_DestroyMemoryDomain( + t_cm_domain_id handle) +{ + t_cm_error error; + + OSAL_LOCK_API(); + error = cm_DM_DestroyDomain(handle); + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_FlushMemoryDomains( + t_nmf_client_id client) +{ + t_cm_error error; + + OSAL_LOCK_API(); + error = cm_DM_DestroyDomains(client); + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetDomainCoreId(const t_cm_domain_id domainId, t_nmf_core_id *coreId) +{ + t_cm_error error; + OSAL_LOCK_API(); + //TODO, scratch + error = cm_DM_CheckDomain(domainId, DOMAIN_NORMAL); + if (error != CM_OK) { + OSAL_UNLOCK_API(); + return error; + } + + *coreId = cm_DM_GetDomainCoreId(domainId); + OSAL_UNLOCK_API(); + return CM_OK; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_Migrate(const t_cm_domain_id srcShared, const t_cm_domain_id src, const t_cm_domain_id dst) +{ + t_cm_error error; + OSAL_LOCK_API(); + error = cm_migrate(srcShared, src, dst); + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_Unmigrate(void) +{ + t_cm_error error; + OSAL_LOCK_API(); + error = cm_unmigrate(); + OSAL_UNLOCK_API(); + return error; +} diff --git a/drivers/staging/nmf-cm/cm/engine/memory/src/memory_wrapper.c b/drivers/staging/nmf-cm/cm/engine/memory/src/memory_wrapper.c new file mode 100644 index 00000000000..37bea690e48 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/src/memory_wrapper.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/component/inc/instance.h> +#include <cm/engine/configuration/inc/configuration.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> +#include <cm/engine/trace/inc/trace.h> + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_AllocMpcMemory( + t_cm_domain_id domainId, + t_nmf_client_id clientId, + t_cm_mpc_memory_type memType, + t_cm_size size, + t_cm_mpc_memory_alignment memAlignment, + t_cm_memory_handle *pHandle + ) +{ + t_dsp_memory_type_id dspMemType; + + switch(memType) + { + case CM_MM_MPC_TCM16_X: + dspMemType = INTERNAL_XRAM16; + break; + case CM_MM_MPC_TCM24_X: + dspMemType = INTERNAL_XRAM24; + break; + case CM_MM_MPC_TCM16_Y: + dspMemType = INTERNAL_YRAM16; + break; + case CM_MM_MPC_TCM24_Y: + dspMemType = INTERNAL_YRAM24; + break; +#ifndef __STN_8810 + case CM_MM_MPC_ESRAM16: + dspMemType = ESRAM_EXT16; + break; + case CM_MM_MPC_ESRAM24: + dspMemType = ESRAM_EXT24; + break; +#endif /* ndef __STN_8810 */ + case CM_MM_MPC_SDRAM16: + dspMemType = SDRAM_EXT16; + break; + case CM_MM_MPC_SDRAM24: + dspMemType = SDRAM_EXT24; + break; + default: + return CM_INVALID_PARAMETER; + } + + OSAL_LOCK_API(); + { + t_cm_error error; + error = cm_DM_CheckDomainWithClient(domainId, DOMAIN_ANY, clientId); + if (error != CM_OK) { + OSAL_UNLOCK_API(); + return error; + } + } + + switch(memAlignment) { + case CM_MM_MPC_ALIGN_NONE : + case CM_MM_MPC_ALIGN_HALFWORD : + case CM_MM_MPC_ALIGN_WORD : + case CM_MM_MPC_ALIGN_2WORDS : + case CM_MM_MPC_ALIGN_4WORDS : + case CM_MM_MPC_ALIGN_8WORDS : + case CM_MM_MPC_ALIGN_16WORDS : + case CM_MM_MPC_ALIGN_32WORDS : + case CM_MM_MPC_ALIGN_64WORDS : + case CM_MM_MPC_ALIGN_128WORDS : + case CM_MM_MPC_ALIGN_256WORDS : + case CM_MM_MPC_ALIGN_512WORDS : + case CM_MM_MPC_ALIGN_1024WORDS : + case CM_MM_MPC_ALIGN_65536BYTES : + //case CM_MM_MPC_ALIGN_16384WORDS : maps to the same value as above + break; + default: + OSAL_UNLOCK_API(); + return CM_INVALID_PARAMETER; + } + + /* in case we allocate in tcm x be sure ee is load before */ + if ( memType == CM_MM_MPC_TCM16_X || memType == CM_MM_MPC_TCM24_X || + memType == CM_MM_MPC_TCM16_Y || memType == CM_MM_MPC_TCM24_Y ) + { + t_cm_error error; + if ((error = cm_CFG_CheckMpcStatus(cm_DM_GetDomainCoreId(domainId))) != CM_OK) + { + OSAL_UNLOCK_API(); + return error; + } + } + + /* alloc memory */ + *pHandle = (t_cm_memory_handle)cm_DM_Alloc(domainId, dspMemType, size, memAlignment, TRUE); + if(*pHandle == (t_cm_memory_handle)INVALID_MEMORY_HANDLE) + { + OSAL_UNLOCK_API(); + ERROR("CM_NO_MORE_MEMORY: CM_AllocMpcMemory() failed\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + OSAL_UNLOCK_API(); + return CM_OK; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_FreeMpcMemory(t_cm_memory_handle handle) +{ + t_cm_error error; + t_memory_handle validHandle; + t_nmf_core_id coreId; + t_dsp_memory_type_id memType; + + OSAL_LOCK_API(); + + if((error = cm_MM_getValidMemoryHandle(handle, &validHandle)) != CM_OK) + { + OSAL_UNLOCK_API(); + return error; + } + + cm_DM_FreeWithInfo(validHandle, &coreId, &memType, TRUE); + + /* in case we allocate in tcm x be sure ee is load before */ + if ( memType == INTERNAL_XRAM16 || memType == INTERNAL_XRAM24 || + memType == INTERNAL_YRAM16 || memType == INTERNAL_YRAM24 ) + { + cm_CFG_ReleaseMpc(coreId); + } + + OSAL_UNLOCK_API(); + return CM_OK; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetMpcMemorySystemAddress(t_cm_memory_handle handle, t_cm_system_address *pSystemAddress) +{ + t_cm_error error; + t_memory_handle validHandle; + + OSAL_LOCK_API(); + + if((error = cm_MM_getValidMemoryHandle(handle, &validHandle)) != CM_OK) + { + OSAL_UNLOCK_API(); + return error; + } + + cm_DSP_GetHostSystemAddress(validHandle, pSystemAddress); + + OSAL_UNLOCK_API(); + return CM_OK; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetMpcMemoryMpcAddress(t_cm_memory_handle handle, t_uint32 *pDspAddress) +{ + t_cm_error error; + t_memory_handle validHandle; + + OSAL_LOCK_API(); + + if((error = cm_MM_getValidMemoryHandle(handle, &validHandle)) != CM_OK) + { + OSAL_UNLOCK_API(); + return error; + } + + cm_DSP_GetDspAddress(validHandle, pDspAddress); + + OSAL_UNLOCK_API(); + return CM_OK; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetMpcMemoryStatus(t_nmf_core_id coreId, t_cm_mpc_memory_type memType, t_cm_allocator_status *pStatus) +{ + t_dsp_memory_type_id dspMemType; + t_cm_error error; + + switch(memType) + { + case CM_MM_MPC_TCM16_X: + dspMemType = INTERNAL_XRAM16; + break; + case CM_MM_MPC_TCM24_X: + dspMemType = INTERNAL_XRAM24; + break; + case CM_MM_MPC_TCM16_Y: + dspMemType = INTERNAL_YRAM16; + break; + case CM_MM_MPC_TCM24_Y: + dspMemType = INTERNAL_YRAM24; + break; +#ifndef __STN_8810 + case CM_MM_MPC_ESRAM16: + dspMemType = ESRAM_EXT16; + break; + case CM_MM_MPC_ESRAM24: + dspMemType = ESRAM_EXT24; + break; +#endif /* ndef __STN_8810 */ + case CM_MM_MPC_SDRAM16: + dspMemType = SDRAM_EXT16; + break; + case CM_MM_MPC_SDRAM24: + dspMemType = SDRAM_EXT24; + break; + default: + return CM_INVALID_PARAMETER; + } + + OSAL_LOCK_API(); + error = cm_DSP_GetAllocatorStatus(coreId, dspMemType, 0, 0, pStatus); + OSAL_UNLOCK_API(); + + return error; +} + diff --git a/drivers/staging/nmf-cm/cm/engine/memory/src/migration.c b/drivers/staging/nmf-cm/cm/engine/memory/src/migration.c new file mode 100644 index 00000000000..d68898d830e --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/src/migration.c @@ -0,0 +1,392 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/inc/cm_type.h> +#include <inc/type.h> +#include <inc/nmf-limits.h> + +#include <cm/engine/communication/fifo/inc/nmf_fifo_arm.h> +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/memory/inc/domain.h> +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/memory/inc/migration.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/utils/inc/mem.h> + +#if defined(__STN_8500) && (__STN_8500 > 10) + +typedef enum { + CM_MIGRATION_OK = 0, + CM_MIGRATION_INVALID_ARGUMENT = 1, + CM_MIGRATION_ERROR = 2, +} t_cm_migration_error; + +extern t_nmf_fifo_arm_desc* mpc2mpcComsFifoId[NB_CORE_IDS][NB_CORE_IDS]; + +/*! + * \brief Data structure representing a segment to migrate + * + * segment: + * - used to determine which mmdsp-hw base is to be updated, index in mpcDesc->segments[] structure + * * this is hard-coded in cm_migrate(), could be computed (would be nice) LIMITATION + * srcAdr.physical: + * - new base setting + * * computed from the src domain in cm_DM_GetAbsAdresses() which uses the start of the allocator for the memory + * this is a LIMITATION, as this information is valid only before migration + * srcAdr.logical: + * - cm_MemCopy() + * * computed as srcAdr.logical + * dstAdr.physical: see srcAdr.physical + * dstAdr.logical: see srcAdr.logical + * size: + * - cm_MemCopy() + * - setting the top when new base is set + */ +typedef struct { + t_dsp_segment_type segment; //!< the link to the segment type + t_cm_system_address srcAdr; //!< source address + t_cm_system_address dstAdr; //!< destination address + t_uint32 size; //!< size of the segment +} t_cm_migration_segment; + +/*! + * \brief Internal data structure 1/ during migration, and 2/ between migration and unmigration calls + * + * all needed information are computed before calling _cm_migration_move() + */ +typedef struct { + t_cm_migration_state state; //!< migration state + t_nmf_core_id coreId; //!< migration only on one mpc + t_cm_migration_segment segments[NB_MIGRATION_SEGMENT]; //!< segments to migrate (selected on migration_move) + t_memory_handle handles[NB_MIGRATION_SEGMENT]; //!< memory handles for destination chunks allocated prior migration +} t_cm_migration_internal_state; + +static t_cm_migration_internal_state migrationState = {STATE_NORMAL, }; + +static t_cm_error _cm_migration_initSegment( + t_dsp_segment_type dspSegment, + t_cm_system_address *srcAdr, + t_uint32 size, + t_cm_domain_id dst, + t_cm_migration_internal_state *info + ) +{ + t_cm_system_address dstAdr; + t_cm_migration_segment *segment = &info->segments[dspSegment]; + t_memory_handle handle; + + handle = cm_DM_Alloc(dst, ESRAM_EXT16, size >> 1, CM_MM_ALIGN_AHB_BURST, TRUE); //note: byte to half-word conversion + if (handle == 0) { + ERROR("CM_NO_MORE_MEMORY: Unable to init segment for migration\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + info->handles[dspSegment] = handle; + + cm_DSP_GetHostSystemAddress(handle, &dstAdr); + + segment->segment = dspSegment; //this is redundant and could be avoided by recoding move(), but nice to have for debug + segment->size = size; + segment->srcAdr = *srcAdr; + segment->dstAdr = dstAdr; + + return CM_OK; +} + +static void _cm_migration_releaseSegment(t_cm_migration_internal_state *info, t_dsp_segment_type segId) +{ + cm_DM_Free(info->handles[segId], TRUE); +} + +static t_cm_migration_error _cm_migration_release(t_cm_migration_internal_state *info) +{ + t_uint32 i = 0; + for (i = 0; i < NB_MIGRATION_SEGMENT; i++) { + cm_DM_Free(info->handles[i], TRUE); + } + + return CM_MIGRATION_OK; +} + +#define SEGMENT_START(seg) \ + seg.offset + +#define SEGMENT_END(seg) \ + seg.offset + seg.size + +static t_cm_error _cm_migration_check( + const t_cm_domain_id srcShared, + const t_cm_domain_id src, + const t_cm_domain_id dst, + t_cm_migration_internal_state *info + ) +{ + t_cm_error error = CM_OK; + t_cm_domain_info domainInfoSrc; + t_cm_domain_info domainInfoShared; + t_cm_domain_desc *domainEE; + t_cm_domain_desc *domainShared; + t_nmf_core_id coreId = cm_DM_GetDomainCoreId(src); + + //coreIds in src, srcShared and dst match + if (!((domainDesc[src].domain.coreId == domainDesc[srcShared].domain.coreId) + && (domainDesc[src].domain.coreId == domainDesc[dst].domain.coreId))) { + return CM_INVALID_PARAMETER; + } + + //check srcShared starts at 0 + //FIXME, juraj, today EE code is in SDRAM, but this is flexible, so must find out where EE is instantiated + if (domainDesc[srcShared].domain.sdramCode.offset != 0x0) { + return CM_INVALID_PARAMETER; + } + + //check srcShared contains EE domain + domainEE = &domainDesc[cm_DSP_GetState(coreId)->domainEE]; + domainShared = &domainDesc[srcShared]; + if ((SEGMENT_START(domainEE->domain.esramCode) < SEGMENT_START(domainShared->domain.esramCode)) + ||(SEGMENT_END(domainEE->domain.esramCode) > SEGMENT_END(domainShared->domain.esramCode)) + ||(SEGMENT_START(domainEE->domain.esramData) < SEGMENT_START(domainShared->domain.esramData)) + ||(SEGMENT_END(domainEE->domain.esramData) > SEGMENT_END(domainShared->domain.esramData)) + ||(SEGMENT_START(domainEE->domain.sdramCode) < SEGMENT_START(domainShared->domain.sdramCode)) + ||(SEGMENT_END(domainEE->domain.sdramCode) > SEGMENT_END(domainShared->domain.sdramCode)) + ||(SEGMENT_START(domainEE->domain.sdramData) < SEGMENT_START(domainShared->domain.sdramData)) + ||(SEGMENT_END(domainEE->domain.sdramData) > SEGMENT_END(domainShared->domain.sdramData)) + ) { + return CM_INVALID_PARAMETER; + } + + info->coreId = coreId; + cm_DM_GetDomainAbsAdresses(srcShared, &domainInfoShared); + cm_DM_GetDomainAbsAdresses(src, &domainInfoSrc); + + if ((error = _cm_migration_initSegment(SDRAM_CODE_EE, &domainInfoShared.sdramCode, + domainDesc[srcShared].domain.sdramCode.size, dst, info)) != CM_OK) + goto _migration_error1; + if ((error = _cm_migration_initSegment(SDRAM_CODE_USER, &domainInfoSrc.sdramCode, + domainDesc[src].domain.sdramCode.size, dst, info)) != CM_OK) + goto _migration_error2; + if ((error = _cm_migration_initSegment(SDRAM_DATA_EE, &domainInfoShared.sdramData, + domainDesc[srcShared].domain.sdramData.size, dst, info)) != CM_OK) + goto _migration_error3; + if ((error = _cm_migration_initSegment(SDRAM_DATA_USER, &domainInfoSrc.sdramData, + domainDesc[src].domain.sdramData.size, dst, info)) != CM_OK) + goto _migration_error4; + return error; + +_migration_error4: _cm_migration_releaseSegment(info, SDRAM_DATA_EE); +_migration_error3: _cm_migration_releaseSegment(info, SDRAM_CODE_USER); +_migration_error2: _cm_migration_releaseSegment(info, SDRAM_CODE_EE); +_migration_error1: + OSAL_Log("Couldn't allocate memory for migration\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; +} + +typedef t_cm_error (*updateBase_t)(t_nmf_core_id, t_dsp_segment_type, t_cm_system_address, t_cm_system_address); + +static t_cm_migration_error _cm_migration_move( + t_nmf_core_id coreId, + t_cm_migration_segment *seg, + updateBase_t updateBase, + char* name + ) +{ + LOG_INTERNAL(1, "##### Migration %s: 0x%x -> 0x%x\n", name, seg->srcAdr.logical, seg->dstAdr.logical, 0, 0, 0); + cm_MemCopy((void*)seg->dstAdr.logical, (void*)seg->srcAdr.logical, seg->size); + updateBase(coreId, seg->segment, seg->srcAdr, seg->dstAdr); + cm_MemSet((void*)seg->srcAdr.logical, 0xdead, seg->size); //for debug, to be sure that we have actually moved the code and bases + + return CM_MIGRATION_OK; +} + +static t_cm_migration_error _cm_migration_update_internal( + t_cm_migration_internal_state *info, + t_cm_migration_state state + ) +{ + t_nmf_fifo_arm_desc *pArmFifo; + + migrationState.state = state; + + switch(state) { + case STATE_MIGRATED: + //move fifos + pArmFifo = mpc2mpcComsFifoId[ARM_CORE_ID][info->coreId]; + pArmFifo->fifoDesc = (t_nmf_fifo_desc*)cm_migration_translate(pArmFifo->dspAddressInfo.segmentType, (t_shared_addr)pArmFifo->fifoDescShadow); + pArmFifo = mpc2mpcComsFifoId[info->coreId][ARM_CORE_ID]; + pArmFifo->fifoDesc = (t_nmf_fifo_desc*)cm_migration_translate(pArmFifo->dspAddressInfo.segmentType, (t_shared_addr)pArmFifo->fifoDescShadow); + break; + + case STATE_NORMAL: + //move fifos + pArmFifo = mpc2mpcComsFifoId[ARM_CORE_ID][info->coreId]; + pArmFifo->fifoDesc = pArmFifo->fifoDescShadow; + pArmFifo = mpc2mpcComsFifoId[info->coreId][ARM_CORE_ID]; + pArmFifo->fifoDesc = pArmFifo->fifoDescShadow; + break; + + default: + OSAL_Log("unknown state", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + + return CM_MIGRATION_OK; +} + +PUBLIC t_cm_error cm_migrate(const t_cm_domain_id srcShared, const t_cm_domain_id src, const t_cm_domain_id dst) +{ + t_cm_migration_error mError; + t_cm_error error; + + if ((error = _cm_migration_check(srcShared, src, dst, &migrationState)) != CM_OK) { + return error; + } + + /* stop DSP execution */ + cm_DSP_Stop(migrationState.coreId); + + /* migrate EE and FX */ + mError = _cm_migration_move(migrationState.coreId, &migrationState.segments[SDRAM_CODE_EE], cm_DSP_updateCodeBase, "code"); + if (mError) { + OSAL_Log("EE code migration failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + mError = _cm_migration_move(migrationState.coreId, &migrationState.segments[SDRAM_DATA_EE], cm_DSP_updateDataBase, "data"); + if (mError) { + OSAL_Log("EE data migration failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + /* migrate user domain */ + mError = _cm_migration_move(migrationState.coreId, &migrationState.segments[SDRAM_CODE_USER], cm_DSP_updateCodeBase, "code"); + if (mError) { + OSAL_Log("User code migration failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + mError = _cm_migration_move(migrationState.coreId, &migrationState.segments[SDRAM_DATA_USER], cm_DSP_updateDataBase, "data"); + if (mError) { + OSAL_Log("User data migration failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + /* update CM internal structures */ + mError = _cm_migration_update_internal(&migrationState, STATE_MIGRATED); + if (mError) { + OSAL_Log("Update internal data failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + + /* Be sure everything has been write before restarting mmdsp */ + OSAL_mb(); + + /* resume DSP execution */ + cm_DSP_Start(migrationState.coreId); + + return CM_OK; +} + +static void _cm_migration_swapSegments( + t_cm_migration_segment *segment + ) +{ + t_cm_system_address tmp; + tmp = segment->dstAdr; + segment->dstAdr = segment->srcAdr; + segment->srcAdr = tmp; +} + +PUBLIC t_cm_error cm_unmigrate(void) +{ + t_cm_migration_error merror; + + if (migrationState.state != STATE_MIGRATED) + return CM_INVALID_PARAMETER; //TODO, juraj, define a proper error for this migration case + + cm_DSP_Stop(migrationState.coreId); + + _cm_migration_swapSegments(&migrationState.segments[SDRAM_CODE_EE]); + _cm_migration_swapSegments(&migrationState.segments[SDRAM_DATA_EE]); + _cm_migration_swapSegments(&migrationState.segments[SDRAM_CODE_USER]); + _cm_migration_swapSegments(&migrationState.segments[SDRAM_DATA_USER]); + + merror = _cm_migration_move(migrationState.coreId, &migrationState.segments[SDRAM_CODE_EE], cm_DSP_updateCodeBase, "code"); + if (merror) { + OSAL_Log("EE code unmigration failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + merror = _cm_migration_move(migrationState.coreId, &migrationState.segments[SDRAM_DATA_EE], cm_DSP_updateDataBase, "data"); + if (merror) { + OSAL_Log("EE data unmigration failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + merror = _cm_migration_move(migrationState.coreId, &migrationState.segments[SDRAM_CODE_USER], cm_DSP_updateCodeBase, "code"); + if (merror) { + OSAL_Log("User code unmigration failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + merror = _cm_migration_move(migrationState.coreId, &migrationState.segments[SDRAM_DATA_USER], cm_DSP_updateDataBase, "data"); + if (merror) { + OSAL_Log("User data unmigration failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + + /* update CM internal structures */ + merror = _cm_migration_update_internal(&migrationState, STATE_NORMAL); + if (merror) { + OSAL_Log("Update internal data failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + + /* Be sure everything has been write before restarting mmdsp */ + OSAL_mb(); + + cm_DSP_Start(migrationState.coreId); + + /* update CM internal structures */ + merror = _cm_migration_release(&migrationState); + if (merror) { + OSAL_Log("Update internal data failed", 0, 0, 0, 0, 0, 0); + CM_ASSERT(0); + } + + return CM_OK; +} + +// here we make the assumption that the offset doesn't depend from the dsp!! +PUBLIC t_uint32 cm_migration_translate(t_dsp_segment_type segmentType, t_uint32 addr) +{ + //TODO, juraj, save delta instead of recalculating it + t_sint32 offset; + if (migrationState.state == STATE_MIGRATED) { + offset = migrationState.segments[segmentType].dstAdr.logical - migrationState.segments[segmentType].srcAdr.logical; + } else { + offset = 0; + } + return addr + offset; +} + +PUBLIC void cm_migration_check_state(t_nmf_core_id coreId, t_cm_migration_state expected) +{ + CM_ASSERT(migrationState.state == expected); +} + +#else +PUBLIC t_cm_error cm_migrate(const t_cm_domain_id srcShared, const t_cm_domain_id src, const t_cm_domain_id dst) +{ + return CM_OK; +} + +PUBLIC t_cm_error cm_unmigrate(void) +{ + return CM_OK; +} + +PUBLIC t_uint32 cm_migration_translate(t_dsp_segment_type segmentType, t_uint32 addr) +{ + return addr; +} + +PUBLIC void cm_migration_check_state(t_nmf_core_id coreId, t_cm_migration_state expected) +{ + return; +} +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/memory/src/remote_allocator.c b/drivers/staging/nmf-cm/cm/engine/memory/src/remote_allocator.c new file mode 100644 index 00000000000..0d000d37371 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/src/remote_allocator.c @@ -0,0 +1,656 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* +Â * Copyright (C) ST-Ericsson SA 2010 +Â *Â Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com>Â for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. +Â */ +/* + * Include + */ +#include "../inc/remote_allocator.h" +#include "../inc/remote_allocator_utils.h" +#include "../inc/chunk_mgr.h" + +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/trace/inc/xtitrace.h> + +static void cm_MM_RA_checkAllocator(t_cm_allocator_desc* alloc); +//static void cm_MM_RA_checkAlloc(t_cm_allocator_desc* alloc, t_uint32 size, t_uint32 align, t_uint32 min, t_uint32 max); + +int bin_index(unsigned int sz) { + /* + * 32 bins of size 2 + * 16 bins of size 16 + * 8 bins of size 128 + * 4 bins of size 1024 + * 2 bins of size 8192 + * 1 bin of size what's left + * + */ + return (((sz >> 6) == 0) ? (sz >> 1): // 0 -> 0 .. 31 + ((sz >> 6) <= 4) ? 28 + (sz >> 4): // 64 -> 32 .. 47 + ((sz >> 6) <= 20) ? 46 + (sz >> 7): // 320 -> 48 .. 55 + ((sz >> 6) <= 84) ? 55 + (sz >> 10): // 1344 -> 56 .. 59 + ((sz >> 6) <= 340) ? 59 + (sz >> 13): // 5440 -> 60 .. 61 + 62); // 21824.. +} + +static t_cm_allocator_desc* ListOfAllocators = NULL; + +PUBLIC t_cm_allocator_desc* cm_MM_CreateAllocator(t_cm_size size, t_uint32 offset, const char* name) +{ + t_cm_allocator_desc *alloc; + + CM_ASSERT(fillChunkPool() == CM_OK); + + /* Alloc structure */ + alloc = (t_cm_allocator_desc*)OSAL_Alloc_Zero(sizeof(t_cm_allocator_desc)); + CM_ASSERT(alloc != NULL); + + // Add allocator in list + alloc->next = ListOfAllocators; + ListOfAllocators = alloc; + + /* assign name */ + alloc->pAllocName = name; + + alloc->maxSize = size; + alloc->sbrkSize = 0; + alloc->offset = offset; + + //TODO, juraj, alloc impacts trace format + cm_TRC_traceMemAlloc(TRACE_ALLOCATOR_COMMAND_CREATE, 0, size, name); + + return alloc; +} + +PUBLIC t_cm_error cm_MM_DeleteAllocator(t_cm_allocator_desc *alloc) +{ + t_cm_chunk *chunk, *next_cm_chunk; + + cm_TRC_traceMemAlloc(TRACE_ALLOCATOR_COMMAND_DESTROY, 0, 0, alloc->pAllocName); + + /* Parse all chunks and free them */ + chunk = alloc->chunks; + while(chunk != 0) + { + next_cm_chunk = chunk->next; + unlinkChunk(alloc, chunk); + freeChunk(chunk); + + chunk = next_cm_chunk; + } + + // Remove allocator from the list + if(ListOfAllocators == alloc) + ListOfAllocators = alloc->next; + else { + t_cm_allocator_desc *prev = ListOfAllocators; + while(prev->next != alloc) + prev = prev->next; + prev->next = alloc->next; + } + + + /* Free allocator descriptor */ + OSAL_Free(alloc); + + return CM_OK; +} + +PUBLIC t_cm_error cm_MM_ResizeAllocator(t_cm_allocator_desc *alloc, t_cm_size size) +{ + /* sanity check */ + if (size == 0) + return CM_INVALID_PARAMETER; + + if(alloc->sbrkSize > size) + return CM_NO_MORE_MEMORY; + + alloc->maxSize = size; + + if (cmIntensiveCheckState) + cm_MM_RA_checkAllocator(alloc); + + return CM_OK; +} + +t_cm_error cm_MM_getValidMemoryHandle(t_cm_memory_handle handle, t_memory_handle* validHandle) +{ +#ifdef LINUX + /* On linux, there is already a check within the linux part + * => we don't need to check twice */ + *validHandle = (t_memory_handle)handle; + return CM_OK; +#else + t_cm_allocator_desc *alloc = ListOfAllocators; + + for(; alloc != NULL; alloc = alloc->next) + { + t_cm_chunk* chunk = alloc->chunks; + + /* Parse all chunks */ + for(; chunk != NULL; chunk = chunk->next) + { + if(chunk == (t_memory_handle)handle) + { + if(chunk->status == MEM_FREE) + return CM_MEMORY_HANDLE_FREED; + + *validHandle = (t_memory_handle)handle; + + return CM_OK; + } + } + } + + return CM_UNKNOWN_MEMORY_HANDLE; +#endif +} + +//TODO, juraj, add appartenance to allocHandle (of chunk) and degage setUserData +PUBLIC t_memory_handle cm_MM_Alloc( + t_cm_allocator_desc* alloc, + t_cm_size size, + t_cm_memory_alignment memAlignment, + t_uint32 seg_offset, + t_uint32 seg_size, + t_uint32 domainId) +{ + t_cm_chunk* chunk; + t_uint32 aligned_offset; + t_uint32 aligned_end; + t_uint32 seg_end = seg_offset + seg_size; + int i; + + /* Sanity check */ + if ( (size == 0) || (size > seg_size) ) + return INVALID_MEMORY_HANDLE; + + if(fillChunkPool() != CM_OK) + return INVALID_MEMORY_HANDLE; + + /* Get first chunk available for the specific size */ + // Search a list with a free chunk + for(i = bin_index(size); i < BINS; i++) + { + chunk = alloc->free_mem_chunks[i]; + while (chunk != 0) + { + /* Alignment of the lower boundary */ + aligned_offset = ALIGN_VALUE(MAX(chunk->offset, seg_offset), (memAlignment + 1)); + + aligned_end = aligned_offset + size; + + if ((aligned_end <= seg_end) + && aligned_end <= (chunk->offset + chunk->size) + && aligned_offset >= seg_offset + && aligned_offset >= chunk->offset) + goto found; + + chunk = chunk->next_free_mem; + } + } + + // Try to increase sbrkSize through maxSize + aligned_offset = ALIGN_VALUE(MAX((alloc->offset + alloc->sbrkSize), seg_offset), (memAlignment + 1)); + + aligned_end = aligned_offset + size; + + if ((aligned_end <= seg_end) + && aligned_end <= (alloc->offset + alloc->maxSize) + && aligned_offset >= seg_offset + && aligned_offset >= (alloc->offset + alloc->sbrkSize)) + { + /* If that fit requirement, create a new free chunk at the end of current allocator */ + chunk = allocChunk(); + + /* Update chunk size */ + chunk->offset = alloc->offset + alloc->sbrkSize; // offset start at end of current allocator + chunk->size = aligned_end - chunk->offset; + chunk->alloc = alloc; + + /* Chain it with latest chunk */ + linkChunk(alloc, alloc->lastChunk, chunk); + + /* Increase sbrkSize to end of this new chunk */ + alloc->sbrkSize += chunk->size; + + goto foundNew; + } + + return INVALID_MEMORY_HANDLE; + +found: + /* Remove chunk from free list */ + unlinkFreeMem(alloc, chunk); + +foundNew: + //create an empty chunk before the allocated one + if (chunk->offset < aligned_offset) { + chunk = splitChunk(alloc, chunk, aligned_offset, FREE_CHUNK_BEFORE); + } + //create an empty chunk after the allocated one + if (chunk->offset + chunk->size > aligned_end) { + splitChunk(alloc, chunk, aligned_end, FREE_CHUNK_AFTER); + } + + chunk->status = MEM_USED; + chunk->prev_free_mem = 0; + chunk->next_free_mem = 0; + chunk->domainId = domainId; + + //TODO, juraj, alloc impacts trace format + cm_TRC_traceMem(TRACE_ALLOC_COMMAND_ALLOC, 0, chunk->offset, chunk->size); + + if (cmIntensiveCheckState) { + cm_MM_RA_checkAllocator(alloc); + } + + return (t_memory_handle) chunk; +} + +//caution - if successfull, the chunk offset will be aligned with seg_offset +//caution++ the offset of the allocated chunk changes implicitly +PUBLIC t_cm_error cm_MM_Realloc( + t_cm_allocator_desc* alloc, + const t_cm_size size, + const t_uint32 offset, + t_memory_handle *handle) +{ + t_cm_chunk *chunk = (t_cm_chunk*)*handle; + t_uint32 oldOffset = chunk->offset; + t_uint32 oldSize = chunk->size; + t_uint32 oldDomainId = chunk->domainId; + t_uint16 userData = chunk->userData; + + cm_MM_Free(alloc, *handle); + + *handle = cm_MM_Alloc(alloc, size, CM_MM_ALIGN_NONE, offset, size, oldDomainId); + + if(*handle == INVALID_MEMORY_HANDLE) + { + *handle = cm_MM_Alloc(alloc, oldSize, CM_MM_ALIGN_NONE, oldOffset, oldSize, oldDomainId); + + CM_ASSERT(*handle != INVALID_MEMORY_HANDLE); + + chunk = (t_cm_chunk*)*handle; + chunk->userData = userData; + + return CM_NO_MORE_MEMORY; + } + + chunk = (t_cm_chunk*)*handle; + chunk->userData = userData; + + return CM_OK; + +#if 0 + /* check reallocation is related to this chunk! */ + CM_ASSERT(chunk->offset <= (offset + size)); + CM_ASSERT(offset <= (chunk->offset + chunk->size)); + CM_ASSERT(size); + + /* check if extend low */ + if (offset < chunk->offset) { + /* note: it is enough to check only the previous chunk, + * because adjacent chunks of same status are merged + */ + if ((chunk->prev == 0) + ||(chunk->prev->status != MEM_FREE) + ||(chunk->prev->offset > offset)) { + return INVALID_MEMORY_HANDLE; + } + } + + /* check if extend high, extend sbrk if necessary */ + if ( (offset + size) > (chunk->offset + chunk->size)) { + if(chunk->next == 0) + { + // check if allocator can be extended to maxSize + if((offset + size) > (alloc->offset + alloc->maxSize)) + return INVALID_MEMORY_HANDLE; + } + else + { + if ((chunk->next->status != MEM_FREE) + ||( (chunk->next->offset + chunk->next->size) < (offset + size))) { + return INVALID_MEMORY_HANDLE; + } + } + } + + if(fillChunkPool() != CM_OK) + return INVALID_MEMORY_HANDLE; + + + /* extend low + * all conditions should have been checked + * this must not fail + */ + if (offset < chunk->offset) { + t_uint32 delta = chunk->prev->offset + chunk->prev->size - offset; + t_cm_chunk *prev = chunk->prev; + + chunk->offset -= delta; + chunk->size += delta; + + CM_ASSERT(prev->status == MEM_FREE); //TODO, juraj, already checked + unlinkFreeMem(alloc, prev); + prev->size -= delta; + if(prev->size == 0) + { + unlinkChunk(alloc, prev); + freeChunk(prev); + } else { + updateFreeList(alloc, prev); + } + } + + /* extend high */ + if ( (offset + size) > (chunk->offset + chunk->size)) { + t_uint32 delta = size - chunk->size; + t_cm_chunk *next = chunk->next; + + chunk->size += delta; + + if(next == 0) + { + alloc->sbrkSize += delta; + } else { + CM_ASSERT(next->status == MEM_FREE); + unlinkFreeMem(alloc, next); + next->offset += delta; + next->size -= delta; + if(next->size == 0) + { + unlinkChunk(alloc, next); + freeChunk(next); + } else { + updateFreeList(alloc, next); + } + } + } + + /* reduce top */ + if ((offset + size) < (chunk->offset + chunk->size)) { + t_uint32 delta = chunk->size - size; + + if(chunk->next == 0) { + alloc->sbrkSize -= delta; + chunk->size -= delta; + + } else if (chunk->next->status == MEM_FREE) { + unlinkFreeMem(alloc, chunk->next); + chunk->size -= delta; + chunk->next->offset -= delta; + chunk->next->size += delta; + updateFreeList(alloc, chunk->next); + } else { + t_cm_chunk *tmp = splitChunk(alloc, chunk, offset + size, FREE_CHUNK_AFTER); //tmp = chunk, chunk = result + tmp->status = MEM_USED; + tmp->next->status = MEM_FREE; + } + } + + /* reduce bottom */ + if (offset > chunk->offset) { + if (chunk->prev->status == MEM_FREE) { + t_uint32 delta = offset - chunk->offset; + unlinkFreeMem(alloc, chunk->prev); + chunk->prev->size += delta; + chunk->offset = offset; + chunk->size -= delta; + updateFreeList(alloc, chunk->prev); + } else { + t_cm_chunk *tmp = splitChunk(alloc, chunk, offset, FREE_CHUNK_BEFORE); //tmp->next = chunk, tmp = result + tmp->status = MEM_USED; + tmp->prev->status = MEM_FREE; + } + } + + cm_MM_RA_checkAllocator(alloc); + + return (t_memory_handle)chunk; +#endif +} + +PUBLIC void cm_MM_Free(t_cm_allocator_desc* alloc, t_memory_handle memHandle) +{ + t_cm_chunk* chunk = (t_cm_chunk*)memHandle; + + //TODO, juraj, alloc impacts trace format + cm_TRC_traceMem(TRACE_ALLOC_COMMAND_FREE, 0, + chunk->offset, chunk->size); + + /* Update chunk status */ + chunk->status = MEM_FREE; + chunk->domainId = 0x0; + + // Invariant: Current chunk is free but not in free list + + /* Check if the previous chunk is free */ + if((chunk->prev != 0) && (chunk->prev->status == MEM_FREE)) + { + t_cm_chunk* prev = chunk->prev; + + // Remove chunk to be freed from memory list + unlinkChunk(alloc, chunk); + + // Remove previous from free list + unlinkFreeMem(alloc, prev); + + // Update previous size + prev->size += chunk->size; + + freeChunk(chunk); + + chunk = prev; + } + + /* Check if the next chunk is free */ + if((chunk->next != 0) && (chunk->next->status == MEM_FREE)) + { + t_cm_chunk* next = chunk->next; + + // Remove next from memory list + unlinkChunk(alloc, next); + + // Remove next from free list + unlinkFreeMem(alloc, next); + + // Update previous size + chunk->size += next->size; + + freeChunk(next); + } + + if(chunk->next == 0) + { + // If we are the last one, decrease sbrkSize + alloc->sbrkSize -= chunk->size; + + unlinkChunk(alloc, chunk); + freeChunk(chunk); + + } + else + { + // Add it in free list + updateFreeList(alloc, chunk); + } + + if (cmIntensiveCheckState) { + cm_MM_RA_checkAllocator(alloc); + } +} + +PUBLIC t_cm_error cm_MM_GetAllocatorStatus(t_cm_allocator_desc* alloc, t_uint32 offset, t_uint32 size, t_cm_allocator_status *pStatus) +{ + t_cm_chunk* chunk = alloc->chunks; + t_uint32 sbrkFree = alloc->maxSize - alloc->sbrkSize; + t_uint8 min_free_size_updated = FALSE; + + /* Init status */ + pStatus->global.used_block_number = 0; + pStatus->global.free_block_number = 0; + pStatus->global.maximum_free_size = 0; + pStatus->global.minimum_free_size = 0xFFFFFFFF; + pStatus->global.accumulate_free_memory = 0; + pStatus->global.accumulate_used_memory = 0; + pStatus->global.size = alloc->maxSize; + pStatus->domain.maximum_free_size = 0; + pStatus->domain.minimum_free_size = 0xFFFFFFFF; + pStatus->domain.accumulate_free_memory = 0; + pStatus->domain.accumulate_used_memory = 0; + pStatus->domain.size= size; + + /* Parse all chunks */ + while(chunk != 0) + { + + /* Chunk is free */ + if (chunk->status == MEM_FREE) { + pStatus->global.free_block_number++; + pStatus->global.accumulate_free_memory += chunk->size; + + /* Check max size */ + if (chunk->size > pStatus->global.maximum_free_size) + { + pStatus->global.maximum_free_size = chunk->size; + } + + /* Check min size */ + if (chunk->size < pStatus->global.minimum_free_size) + { + pStatus->global.minimum_free_size = chunk->size; + min_free_size_updated = TRUE; + } + } else {/* Chunk used */ + pStatus->global.used_block_number++; + pStatus->global.accumulate_used_memory += chunk->size; + } + + chunk = chunk->next; + } + + /* Accumulate free space between sbrkSize and maxSize */ + pStatus->global.accumulate_free_memory += sbrkFree; + if (sbrkFree > 0) + pStatus->global.free_block_number++; + if (pStatus->global.maximum_free_size < sbrkFree) + pStatus->global.maximum_free_size = sbrkFree; + if (pStatus->global.minimum_free_size > sbrkFree) { + pStatus->global.minimum_free_size = sbrkFree; + min_free_size_updated = TRUE; + } + + /* Put max free size to min free size */ + if (min_free_size_updated == FALSE) { + pStatus->global.minimum_free_size = pStatus->global.maximum_free_size; + } + + return CM_OK; +} + +PUBLIC t_uint32 cm_MM_GetOffset(t_memory_handle memHandle) +{ + /* Provide offset */ + return ((t_cm_chunk*)memHandle)->offset; +} + +PUBLIC t_uint32 cm_MM_GetSize(t_memory_handle memHandle) +{ + return ((t_cm_chunk*)memHandle)->size; +} + +PUBLIC t_uint32 cm_MM_GetAllocatorSize(t_cm_allocator_desc* alloc) +{ + return alloc->maxSize; +} + +PUBLIC void cm_MM_SetMemoryHandleUserData(t_memory_handle memHandle, t_uint16 userData) +{ + ((t_cm_chunk*)memHandle)->userData = userData; +} + +PUBLIC void cm_MM_GetMemoryHandleUserData(t_memory_handle memHandle, t_uint16 *pUserData, t_cm_allocator_desc **alloc) +{ + *pUserData = ((t_cm_chunk*)memHandle)->userData; + if (alloc) + *alloc = ((t_cm_chunk*)memHandle)->alloc; +} + +/* + * check free list is ordered + * check all chunks are correctly linked + * check adjacent chunks are not FREE + */ +static void cm_MM_RA_checkAllocator(t_cm_allocator_desc* alloc) +{ + t_cm_chunk *chunk = alloc->chunks; + t_uint32 size = 0; + int i; + + CM_ASSERT(alloc->sbrkSize <= alloc->maxSize); + + while(chunk != 0) { + if(chunk == alloc->chunks) + CM_ASSERT(chunk->prev == 0); + if(chunk == alloc->lastChunk) + CM_ASSERT(chunk->next == 0); + + CM_ASSERT(chunk->alloc == alloc); + + if (chunk->next != 0) { + CM_ASSERT(!((chunk->status == MEM_FREE) && (chunk->next->status == MEM_FREE))); //two free adjacent blocks + CM_ASSERT(chunk->offset < chunk->next->offset); //offsets reverted + CM_ASSERT(chunk->offset + chunk->size == chunk->next->offset); // Not hole in allocator + } + size += chunk->size; + chunk = chunk->next; + } + + CM_ASSERT(size == alloc->sbrkSize); + + for(i = 0; i < BINS; i++) + { + chunk = alloc->free_mem_chunks[i]; + while(chunk != 0) { + if (chunk->next_free_mem != 0) { + CM_ASSERT(chunk->size <= chunk->next_free_mem->size); //free list not ordered + } + chunk = chunk->next_free_mem; + } + } +} + +PUBLIC void cm_MM_DumpMemory(t_cm_allocator_desc* alloc, t_uint32 start, t_uint32 end) +{ + t_cm_chunk *chunk = alloc->chunks; + + LOG_INTERNAL(0, "ALLOCATOR Dumping allocator \"%s\" [0x%08x:0x%08x]\n", alloc->pAllocName, start, end, 0, 0, 0); + while(chunk != 0) { + if (((chunk->offset < start) && (chunk->offset + chunk->size > start)) + || ((chunk->offset < end) && (chunk->offset + chunk->size > end)) + || ((chunk->offset > start) && (chunk->offset + chunk->size < end)) + || ((chunk->offset < start) && (chunk->offset + chunk->size > end))) + { + LOG_INTERNAL(0, "ALLOCATOR chunk [0x%08x -> 0x%08x[: status:%s, domainId: 0x%x\n", + chunk->offset, + chunk->offset + chunk->size, + chunk->status?"FREE":"USED", + chunk->domainId, 0, 0); + } + chunk = chunk->next; + } +} + +PUBLIC void cm_MM_SetDefaultDomain(t_memory_handle memHandle, t_uint32 domainId) +{ + ((t_cm_chunk *) memHandle)->domainId = domainId; +} diff --git a/drivers/staging/nmf-cm/cm/engine/memory/src/remote_allocator_utils.c b/drivers/staging/nmf-cm/cm/engine/memory/src/remote_allocator_utils.c new file mode 100644 index 00000000000..4e800376dbb --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/memory/src/remote_allocator_utils.c @@ -0,0 +1,250 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/memory/inc/remote_allocator_utils.h> +#include <cm/engine/trace/inc/trace.h> + +/***************************************************************************/ +/* + * linkChunk + * param prev : Pointer on previous chunk where the chunk will be added + * param add : Pointer on chunk to add + * + * Add a chunk in the memory list + * + */ +/***************************************************************************/ +PUBLIC void linkChunk(t_cm_allocator_desc* alloc, t_cm_chunk* prev, t_cm_chunk* add) +{ + // Link previous + if(prev == 0) + { + add->next = alloc->chunks; + alloc->chunks = add; + } + else + { + add->prev = prev; + add->next = prev->next; + prev->next = add; + } + + // Link next + if(add->next == 0) + { + // Link at the end + alloc->lastChunk = add; + } + else + add->next->prev = add; +} + +/***************************************************************************/ +/* + * unlinkChunk + * param allocHandle : Allocator handle + * param current : Pointer on chunk to remove + * + * Remove a chunk in the memory list and update first pointer + * + */ +/***************************************************************************/ +PUBLIC void unlinkChunk(t_cm_allocator_desc* alloc, t_cm_chunk* current) +{ + /* Link previous with next */ + if (current->prev != 0) + current->prev->next = current->next; + else + { + CM_ASSERT(alloc->chunks == current); + + // We remove the first, update chunks + alloc->chunks = current->next; + } + + /* Link next with previous */ + if(current->next != 0) + current->next->prev= current->prev; + else + { + CM_ASSERT(alloc->lastChunk == current); + + // We remove the last, update lastChunk + alloc->lastChunk = current->prev; + } +} + + +/***************************************************************************/ +/* + * unlinkFreeMem() unlinks chunk from free memory double-linked list + * makes the previous and next chunk in the list point to each other.. + * param allocHandle : Allocator handle + * param current : Pointer on chunk to remove + * + * Remove a chunk in the free memory list and update pointer + * + */ +/***************************************************************************/ +PUBLIC void unlinkFreeMem(t_cm_allocator_desc* alloc ,t_cm_chunk* current) +{ + int bin = bin_index(current->size); + + /* unlink previous */ + if (current->prev_free_mem != 0) + { + current->prev_free_mem->next_free_mem = current->next_free_mem; + } + + /* Unlink next */ + if (current->next_free_mem !=0 ) + { + current->next_free_mem->prev_free_mem = current->prev_free_mem; + } + + /* update first free pointer */ + if (alloc->free_mem_chunks[bin] == current) + { + alloc->free_mem_chunks[bin] = current->next_free_mem; + } + + current->prev_free_mem = 0; + current->next_free_mem = 0; +} + +/***************************************************************************/ +/* + * linkFreeMemBefore + * param add : Pointer on chunk to add + * param next : Pointer on next chunk where the chunk will be added before + * + * Add a chunk in the free memory list + * + */ +/***************************************************************************/ +PUBLIC void linkFreeMemBefore(t_cm_chunk* add, t_cm_chunk* next) +{ + /* Link next */ + add->prev_free_mem = next->prev_free_mem; + add->next_free_mem = next; + + /* Link previous */ + if (next->prev_free_mem != 0) + { + next->prev_free_mem->next_free_mem = add; + } + next->prev_free_mem = add; +} + +/***************************************************************************/ +/* + * linkFreeMemAfter + * param add : Pointer on chunk to add + * param prev : Pointer on previous chunk where the chunk will be added after + * + * Add a chunk in the free memory list + * + */ +/***************************************************************************/ +PUBLIC void linkFreeMemAfter(t_cm_chunk* prev,t_cm_chunk* add) +{ + /* Link previous */ + add->prev_free_mem = prev; + add->next_free_mem = prev->next_free_mem; + + /* Link next */ + if (prev->next_free_mem != 0) + { + prev->next_free_mem->prev_free_mem = add; + } + prev->next_free_mem = add; +} + + +/***************************************************************************/ +/* + * updateFreeList + * param allocHandle : Allocator handle + * param offset : Pointer on chunk + * + * Update free memory list, ordered by size + * + */ +/***************************************************************************/ +PUBLIC void updateFreeList(t_cm_allocator_desc* alloc , t_cm_chunk* chunk) +{ + t_cm_chunk* free_chunk; + int bin = bin_index(chunk->size); + + /* check case with no more free block */ + if (alloc->free_mem_chunks[bin] == 0) + { + alloc->free_mem_chunks[bin] = chunk; + return ; + } + + /* order list */ + free_chunk = alloc->free_mem_chunks[bin]; + while ((free_chunk->next_free_mem != 0) && (chunk->size > free_chunk->size)) + { + free_chunk = free_chunk->next_free_mem; + } + + /* Add after free chunk if smaller -> we are the last */ + if(free_chunk->size <= chunk->size) + { + linkFreeMemAfter(free_chunk,chunk); + } + else // This mean that we are smaller + { + linkFreeMemBefore(chunk,free_chunk); + + /* Update first free chunk */ + if (alloc->free_mem_chunks[bin] == free_chunk) + { + alloc->free_mem_chunks[bin] = chunk; + } + } +} + + +/***************************************************************************/ +/* + * splitChunk + * param allocHandle : Allocator handle + * param chunk : Current chunk (modified in place) + * param offset : Offset address of the start memory + * return : New chunk handle or 0 if an error occurs + * + * Create new chunk before/after the current chunk with the size + */ +/***************************************************************************/ +PUBLIC t_cm_chunk* splitChunk(t_cm_allocator_desc* alloc ,t_cm_chunk *chunk, + t_uint32 offset, t_mem_split_position position) +{ + t_cm_chunk *free; + t_cm_chunk *returned; + + t_cm_chunk* new_chunk = allocChunk(); + + if (position == FREE_CHUNK_AFTER) { + returned = chunk; + free = new_chunk; + } else { //FREE_CHUNK_BEFORE + returned = new_chunk; + free = chunk; + } + + new_chunk->offset = offset; + new_chunk->size = chunk->offset + chunk->size - offset; + new_chunk->alloc = alloc; + chunk->size = offset - chunk->offset; + + linkChunk(alloc, chunk, new_chunk); + unlinkFreeMem(alloc, free); + updateFreeList(alloc, free); + + return returned; +} diff --git a/drivers/staging/nmf-cm/cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h b/drivers/staging/nmf-cm/cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h new file mode 100644 index 00000000000..c9ec864795f --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h @@ -0,0 +1,498 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \brief OS Adaptation Layer API + * + * \defgroup CM_ENGINE_OSAL_API CM Engine OSAL (Operating System Abstraction Layer) API + * \ingroup CM_ENGINE_MODULE + */ +#ifndef __INC_CM_OSAL_H +#define __INC_CM_OSAL_H + +#include <cm/inc/cm_type.h> +#include <cm/engine/communication/inc/communication_type.h> +#include <cm/engine/component/inc/instance.h> + +/*! + * \brief Identifier of a trace channel (id in [0..255]) + * \ingroup CM_ENGINE_OSAL_API + */ +typedef t_uint8 t_nmf_trace_channel; + +/*! + * \brief Identifier of lock create by OSAL + * \ingroup CM_ENGINE_OSAL_API + */ +typedef t_uint32 t_nmf_osal_sync_handle; + +/*! + * \brief Identifier of semaphore create by OSAL + * \ingroup CM_ENGINE_OSAL_API + */ +typedef t_uint32 t_nmf_osal_sem_handle; + +/*! + * \brief Identifier of semaphore wait error return by semaphore OSAL API + * \ingroup CM_ENGINE_OSAL_API + */ +typedef t_uint8 t_nmf_osal_sync_error; +#define SYNC_ERROR_TIMEOUT ((t_nmf_osal_sync_error)-1) +#define SYNC_OK ((t_nmf_osal_sync_error)0) +#define SEM_TIMEOUT_NORMAL 3000 +#define SEM_TIMEOUT_DEBUG 300000 + +/*! + * \brief Operations used to support additionnal OS-specific debug feature + * \ingroup CM_ENGINE_OSAL_API + */ +struct osal_debug_operations { + void (*component_create)(t_component_instance *component); + void (*component_destroy)(t_component_instance *component); + void (*domain_create)(t_cm_domain_id id); + void (*domain_destroy)(t_cm_domain_id id); +}; + +extern struct osal_debug_operations osal_debug_ops; + +/*! + * \brief Description of the Scheduling part of the OS Adaptation Layer + * + * <B>Goal:</B> Support of uplink communication path (from Media Processors to Host (ARM)) + * + * Post a function call outside of Host CPU Interrupt mode in order to minimize ISR execution time + * \param[in] upLayerTHIS : this one provided by user when calling CM_ENGINE_BindComponentToCMCore() (first field of the interface context) \n + * \param[in] methodIndex : index method to be called \n + * \param[in] anyPtr : internal NMF marshaled parameters block (to be passed as second parameter when calling the previous pSkeleton method) \n + * \param[in] ptrSize : size of anyPtr in bytes \n + * + * Called by: + * - CM_ProcessMpcEvent() call (shall be bound by OS integrator to HSEM IRQ) + * + * \ingroup CM_ENGINE_OSAL_API + */ + +PUBLIC void OSAL_PostDfc( + t_nmf_mpc2host_handle upLayerTHIS, + t_uint32 methodIndex, + t_event_params_handle anyPtr, + t_uint32 ptrSize); + + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * <B>Goal:</B> Use by CM to protect global variable against multiple call. Interrupt and scheduler function are use when + * we take hardware/local semaphore. Scheduler lock functions can have empty implementation but this may + * impact performance (dsp waiting semaphore because host thread was preempted whereas it has already take semaphore + * but not yet release it). + * + * \return handle of the Mutex created + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC t_nmf_osal_sync_handle OSAL_CreateLock(void); + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * <B>Goal:</B> Use by CM to protect global variable against multiple call. Interrupt and scheduler function are use when + * we take hardware/local semaphore. Scheduler lock functions can have empty implementation but this may + * impact performance (dsp waiting semaphore because host thread was preempted whereas it has already take semaphore + * but not yet release it). + * + * \param[in] handle handle of the Mutex to be locked + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_Lock( + t_nmf_osal_sync_handle handle); + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * <B>Goal:</B> Use by CM to protect global variable against multiple call. Interrupt and scheduler function are use when + * we take hardware/local semaphore. Scheduler lock functions can have empty implementation but this may + * impact performance (dsp waiting semaphore because host thread was preempted whereas it has already take semaphore + * but not yet release it). + * + * \param[in] handle handle of the Mutex to be unlocked + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_Unlock( + t_nmf_osal_sync_handle handle); + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * <B>Goal:</B> Use by CM to protect global variable against multiple call. Interrupt and scheduler function are use when + * we take hardware/local semaphore. Scheduler lock functions can have empty implementation but this may + * impact performance (dsp waiting semaphore because host thread was preempted whereas it has already take semaphore + * but not yet release it). + * + * \param[in] handle handle of the Mutex to be destroyed + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_DestroyLock( + t_nmf_osal_sync_handle handle); + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * <B>Goal:</B> Use by CM to allow to synchronize with code running on mpc side. + * + * \param[in] value : Initial value of semaphore. + * + * \return handle of the Semaphore created + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC t_nmf_osal_sem_handle OSAL_CreateSemaphore( + t_uint32 value); + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * <B>Goal:</B> Use by CM to allow to synchronize with code running on mpc side. This function can be call under + * Irq context by CM. + * + * \param[in] handle handle of the Semaphore for which we increase value and so potentially wake up thread. + * + * \param[in] aCtx is a hint to indicate to os that we are in a none normal context (e.g under interruption). + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_SemaphorePost( + t_nmf_osal_sem_handle handle, + t_uint8 aCtx); + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * <B>Goal:</B> Use by CM to allow to synchronize with code running on mpc side. + * + * \param[in] handle of the Semaphore for which we decrease value and so potentially block current thread. + * + * \param[in] timeOutInMs maximun time in ms after which the block thread is wake up. In this case function return SYNC_ERROR_TIMEOUT value. + * + * \return error number: SYNC_ERROR_TIMEOUT in case semaphore is not release withing timeOutInMs. + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC t_nmf_osal_sync_error OSAL_SemaphoreWaitTimed( + t_nmf_osal_sem_handle handle, + t_uint32 timeOutInMs); + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * <B>Goal:</B> Use by CM to allow to synchronize with code running on mpc side. + * + * \param[in] handle handle of the Semaphore to be destroyed + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_DestroySemaphore( + t_nmf_osal_sem_handle handle); + +/*! + * \brief Description of the System Memory Allocator part of the OS Adaptation Layer + * + * <B>Goal:</B> Allocate CM some cacheable and bufferable memory (SDRAM) for internal usage \n + * This memory will be accessed only by Host CPU (ARM) + * + * This function provide a simple, general-purpose memory allocation. The + * OSAL_Alloc macro returns a pointer to a block of at least size bytes + * suitably aligned for any use. If there is no available memory, this + * function returns a null pointer. + * + * \param[in] size size in bytes, of memory to be allocated + * \return pointer on the beginning of the allocated memory + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void* OSAL_Alloc( + t_cm_size size); + +/*! + * \brief Description of the System Memory Allocator part of the OS Adaptation Layer with memory set to zero + * + * Compare to \see OSAL_Alloc, same allocation is done but memory is set with zero before returning. + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void* OSAL_Alloc_Zero( + t_cm_size size); + +/*! + * \brief Description of the System Memory Allocator part of the OS Adaptation Layer + * + * <B>Goal:</B> Free CM some cacheable and bufferable memory (SDRAM) for internal usage \n + * This memory will be accessed only by Host CPU (ARM) + * + * \param[in] pHandle pointer on the begining of the memory previously allocated + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_Free( + void *pHandle); + +/*! + * \brief Clean data cache in DDR in order to be accessible from peripheral. + * + * This method must be synchronized with MMDSP Code cache attribute. + * Strongly Ordered -> nothing + * Shared device -> dsb + L2 Write buffer drain + * Non cacheable, Bufferable -> dsb + L2 Write buffer drain + * WT or WB -> Flush cache range + dsb + L2 Write buffer drain + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_CleanDCache( + t_uint32 startAddr, //!< [in] Start data address of range to clean + t_uint32 Size //!< [in] Size of range to clean + ); + +/*! + * \brief Flush write buffer. + * + * This method must be synchronized with MMDSP Data cache attribute. + * Strongly Ordered -> nothing + * Shared device -> dsb + L2 Write buffer drain + * Non cacheable, Bufferable -> dsb + L2 Write buffer drain + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_mb(void); + +/*! + * \brief Description of the System Memory part of the OS Adaptation Layer + * + * <B>Goal:</B> Copy some cacheable and bufferable memory (SDRAM) provided by a client to\n + * internal memory. + * + * \param[in] dst : pointer on the begining of the internal memory previously allocated + * \param[in] src : pointer on the begining of the client's memory + * \param[in] size : The size of the data to copy + * + * Called by: + * - CM_ENGINE_PushComponent() + * + * \note This API is mainly provided for the OS were the client application does execute in the same + * address space as the CM. + * For example in Linux or Symbian, the client's address space is userland but the CM execute in + * kernel space. Thus, 'dst' is supposed to be a kernel address but src is supposed to be a user + * space address + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC t_cm_error OSAL_Copy( + void *dst, + const void *src, + t_cm_size size); + +/*! + * \brief Description of the internal log traces configuration of the Component Manager + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_Log( + const char *format, + int param1, + int param2, + int param3, + int param4, + int param5, + int param6); + +/*! + * \brief Generate an OS-Panic. Called in from CM_ASSERT(). + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_Panic(void); + +/*! + * \brief Description of the configuration of the trace features + * + * (trace output itself is provided by user through his custom implementation of the generic APIs) + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_Write64( + t_nmf_trace_channel channel, + t_uint8 isTimestamped, + t_uint64 value); + +/*! + * \brief Power enabling/disabling commands description. + * + * \ingroup CM_ENGINE_OSAL_API + */ +typedef enum +{ + CM_OSAL_POWER_SxA_CLOCK, //!< SxA Power & Clock, firstParam contains Core ID + CM_OSAL_POWER_SxA_AUTOIDLE, //!< SxA AutoIdle, firstParam contains Core ID + CM_OSAL_POWER_SxA_HARDWARE, //!< SxA Hardware Power, firstParam contains Core ID + CM_OSAL_POWER_HSEM, //!< HSEM Power + CM_OSAL_POWER_SDRAM, //!< SDRAM memory, firstParam contains physical resource address, secondParam contains size + CM_OSAL_POWER_ESRAM //!< ESRAM memory, firstParam contains physical resource address, secondParam contains size +} t_nmf_power_resource; + +/*! + * \brief Description of the Power Management part of the OS Adaptation Layer + * + * Use by CM engine to disable a logical power domain (see \ref t_nmf_power_resource) + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_DisablePwrRessource( + t_nmf_power_resource resource, //!< [in] Describe the domain which must be disabled + t_uint32 firstParam, //!< [in] Eventual first parameter to power to disable + t_uint32 secondParam //!< [in] Eventual second parameter to power to disable + ); + +/*! + * \brief Description of the Power Management part of the OS Adaptation Layer + * + * Use by CM engine to enable a logical power domain (see \ref t_nmf_power_resource) + * + * \return + * - \ref CM_OK + * - \ref CM_PWR_NOT_AVAILABLE A specified power domain is not managed (see returned value in aPowerMask) + * + * Called by: + * - any CM API call + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC t_cm_error OSAL_EnablePwrRessource( + t_nmf_power_resource resource, //!< [in] Describing the domains which must be enabled + t_uint32 firstParam, //!< [in] Eventual first parameter to power to disable + t_uint32 secondParam //!< [in] Eventual second parameter to power to disable + ); + + +/*! + * \brief return prcmu timer value. + * + * This is need for perfmeter api (see \ref t_nmf_power_resource) + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC t_uint64 OSAL_GetPrcmuTimer(void); + +/*! + * \brief Disable the service message handling (panic, etc) + * + * It must disable the handling of all service messages + * If a service message is currently handled, it must wait till the end + * of its managment before returning. + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_DisableServiceMessages(void); + +/*! + * \brief Enable the service message handling (panic, etc) + * + * It enables the handling of all service messages + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_EnableServiceMessages(void); + +/*! + * \brief Generate 'software' panic due to dsp crash + * + * We request that the os part generate a panic to notify cm users +* that a problem occur but not dsp panic has been sent (for example +* a dsp crash) + * + * \param[in] t_nmf_core_id : core_id is the id of dsp for which we need to generate a panic. + * \param[in] reason : additional information. Today only 0 is valid. + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_GeneratePanic(t_nmf_core_id coreId, t_uint32 reason); + +extern /*const*/ t_nmf_osal_sync_handle lockHandleApi; +extern /*const*/ t_nmf_osal_sync_handle lockHandleCom; +extern /*const*/ t_nmf_osal_sem_handle semHandle; + +/*! + * \brief Take a lock before entering critical section. Can suspend current thread if lock already taken. \n + * Use this macro in api function. For com function use OSAL_LOCK_COM. + * + * \ingroup CM_ENGINE_OSAL_API + */ +#define OSAL_LOCK_API() OSAL_Lock(lockHandleApi) + +/*! + * \brief Release lock before leaving critical section. + * + * \ingroup CM_ENGINE_OSAL_API + */ +#define OSAL_UNLOCK_API() OSAL_Unlock((lockHandleApi)) + +/*! + * \brief Take a lock before entering critical section. Can suspend current thread if lock already taken. \n + * Use this macro in com function. For com function use OSAL_LOCK_API. + * + * \ingroup CM_ENGINE_OSAL_API + */ +#define OSAL_LOCK_COM() OSAL_Lock(lockHandleCom) + +/*! + * \brief Release lock before leaving critical section. + * + * \ingroup CM_ENGINE_OSAL_API + */ +#define OSAL_UNLOCK_COM() OSAL_Unlock((lockHandleCom)) + +/*! + * \brief Go to sleep untill post done on semaphore or timeout expire. In that case SYNC_ERROR_TIMEOUT is return. + * + * \ingroup CM_ENGINE_OSAL_API + */ +#define OSAL_SEMAPHORE_WAIT_TIMEOUT(semHandle) OSAL_SemaphoreWaitTimed(semHandle, (cm_PWR_GetMode() == NORMAL_PWR_MODE)?SEM_TIMEOUT_NORMAL:SEM_TIMEOUT_DEBUG) + +/****************/ +/* Generic part */ +/****************/ +t_cm_error cm_OSAL_Init(void); +void cm_OSAL_Destroy(void); + +#endif /* __INC_CM_OSAL_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/os_adaptation_layer/src/os_adaptation_layer.c b/drivers/staging/nmf-cm/cm/engine/os_adaptation_layer/src/os_adaptation_layer.c new file mode 100644 index 00000000000..380692e3cd8 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/os_adaptation_layer/src/os_adaptation_layer.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> +#include <cm/engine/utils/inc/mem.h> + +t_nmf_osal_sync_handle lockHandleApi; +t_nmf_osal_sync_handle lockHandleCom; +t_nmf_osal_sem_handle semHandle; +struct osal_debug_operations osal_debug_ops; + +/****************/ +/* Generic part */ +/****************/ +PUBLIC t_cm_error cm_OSAL_Init(void) +{ + + /* create locks */ + lockHandleApi = OSAL_CreateLock(); + if (lockHandleApi == 0) {return CM_INVALID_PARAMETER;} + lockHandleCom = OSAL_CreateLock(); + if (lockHandleCom == 0) {return CM_INVALID_PARAMETER;} + + /* create semaphore */ + semHandle = OSAL_CreateSemaphore(0); + if (semHandle == 0) {return CM_INVALID_PARAMETER;} + + /* init to zero */ + cm_MemSet(&osal_debug_ops, 0, sizeof(osal_debug_ops)); + + return CM_OK; +} + +PUBLIC void cm_OSAL_Destroy(void) +{ + /* destroy locks */ + OSAL_DestroyLock(lockHandleApi); + OSAL_DestroyLock(lockHandleCom); + + /* destroy semaphore */ + OSAL_DestroySemaphore(semHandle); +} diff --git a/drivers/staging/nmf-cm/cm/engine/perfmeter/inc/mpcload.h b/drivers/staging/nmf-cm/cm/engine/perfmeter/inc/mpcload.h new file mode 100644 index 00000000000..0831f1940ca --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/perfmeter/inc/mpcload.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + */ +#ifndef MPCLOAD_H_ +#define MPCLOAD_H_ + +#include <cm/engine/component/inc/instance.h> + +/******************************************************************************/ +/************************ FUNCTIONS PROTOTYPES ********************************/ +/******************************************************************************/ + +PUBLIC t_cm_error cm_PFM_allocatePerfmeterDataMemory(t_nmf_core_id coreId, t_cm_domain_id domainId); +PUBLIC void cm_PFM_deallocatePerfmeterDataMemory(t_nmf_core_id coreId); + +#endif /* MPCLOAD_H_ */ diff --git a/drivers/staging/nmf-cm/cm/engine/perfmeter/inc/perfmeter_type.h b/drivers/staging/nmf-cm/cm/engine/perfmeter/inc/perfmeter_type.h new file mode 100644 index 00000000000..8733c20b21b --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/perfmeter/inc/perfmeter_type.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Public Component Manager Performance Meter API type. + * + * This file contains the Component Manager API type for performance meter. + * + * \defgroup PERFMETER CM Monitoring API + * \ingroup CM_USER_API + */ +#ifndef CM_COMMON_PERFMETER_TYPE_H_ +#define CM_COMMON_PERFMETER_TYPE_H_ + +#include <cm/inc/cm_type.h> +/*! + * \brief Description of mpc load structure. + * + * This contain mpc load value. + * + * \ingroup PERFMETER + */ +typedef struct { + t_uint64 totalCounter; + t_uint64 loadCounter; +} t_cm_mpc_load_counter; + + +#endif /* CM_COMMON_PERFMETER_TYPE_H_ */ diff --git a/drivers/staging/nmf-cm/cm/engine/perfmeter/src/mpcload.c b/drivers/staging/nmf-cm/cm/engine/perfmeter/src/mpcload.c new file mode 100644 index 00000000000..193d155b97b --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/perfmeter/src/mpcload.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/engine/perfmeter/inc/mpcload.h> +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> + +#include <cm/engine/api/perfmeter_engine.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> + +#include <cm/engine/trace/inc/trace.h> + +#define PERFMETER_MAX_RETRIES 32 +#define PERFMETER_DATA_WORD_NB 7 + +/* private type */ +typedef struct { + t_memory_handle perfmeterDataHandle; + t_cm_logical_address perfmeterDataAddr; +} t_mpcLoad; + +/* private globals */ +t_mpcLoad mpcLoad_i[NB_CORE_IDS]; + +/* engine api */ +PUBLIC EXPORT_SHARED t_cm_error CM_GetMpcLoadCounter( + t_nmf_core_id coreId, + t_cm_mpc_load_counter *pMpcLoadCounter +) +{ + t_uint24 data[PERFMETER_DATA_WORD_NB]; + t_uint32 i; + t_uint64 prcmuBeforeAttributes; + t_uint32 retryCounter = 0; + volatile t_uint32 *pData; + + pMpcLoadCounter->totalCounter = 0; + pMpcLoadCounter->loadCounter = 0; + /* check core id is an mpc */ + if (coreId < FIRST_MPC_ID || coreId > LAST_CORE_ID) {return CM_INVALID_PARAMETER;} + + /* check core has been booted */ + pData = (t_uint32 *) mpcLoad_i[coreId].perfmeterDataAddr; + if (pData == NULL) {return CM_OK;} + + do { + prcmuBeforeAttributes = OSAL_GetPrcmuTimer(); + /* get attributes */ + do + { + for(i = 0;i < PERFMETER_DATA_WORD_NB;i++) + data[i] = pData[i]; + } + while(((data[0] & 0xff0000) != (data[1] & 0xff0000) || (data[0] & 0xff0000) != (data[2] & 0xff0000) || + (data[0] & 0xff0000) != (data[3] & 0xff0000) || (data[0] & 0xff0000) != (data[4] & 0xff0000) || + (data[0] & 0xff0000) != (data[5] & 0xff0000) || (data[0] & 0xff0000) != (data[6] & 0xff0000) || + (data[0] & 0xff0000) != (data[6] & 0xff0000)) + && retryCounter-- < PERFMETER_MAX_RETRIES); // check data coherence + if (retryCounter >= PERFMETER_MAX_RETRIES) + return CM_MPC_NOT_RESPONDING; + + /* read forever counter for totalCounter */ + pMpcLoadCounter->totalCounter = OSAL_GetPrcmuTimer(); + } while(pMpcLoadCounter->totalCounter - prcmuBeforeAttributes >= 32); //we loop until it seems we have not be preempt too long (< 1ms) + + /* we got coherent data, use them */ + pMpcLoadCounter->loadCounter = ((data[0] & (t_uint64)0xffff) << 32) + ((data[1] & (t_uint64)0xffff) << 16) + ((data[2] & (t_uint64)0xffff) << 0); + //fix load counter if needed + if ((data[6] & 0xffff) == 1) { + t_uint64 lastEvent; + + lastEvent = ((data[3] & (t_uint64)0xffff) << 32) + ((data[4] & (t_uint64)0xffff) << 16) + ((data[5] & (t_uint64)0xffff) << 0); + pMpcLoadCounter->loadCounter += pMpcLoadCounter->totalCounter - lastEvent; + } + + return CM_OK; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_getMpcLoadCounter( + t_nmf_core_id coreId, + t_cm_mpc_load_counter *pMpcLoadCounter +) +{ + t_cm_error error; + + OSAL_LOCK_API(); + error = CM_GetMpcLoadCounter(coreId, pMpcLoadCounter); + OSAL_UNLOCK_API(); + return error; +} + +/* internal api */ +PUBLIC t_cm_error cm_PFM_allocatePerfmeterDataMemory(t_nmf_core_id coreId, t_cm_domain_id domainId) +{ + t_cm_error error = CM_OK; + t_mpcLoad *pMpcLoad = (t_mpcLoad *) &mpcLoad_i[coreId]; + + pMpcLoad->perfmeterDataHandle = cm_DM_Alloc(domainId, SDRAM_EXT24, PERFMETER_DATA_WORD_NB, CM_MM_ALIGN_WORD, TRUE); + if (pMpcLoad->perfmeterDataHandle == INVALID_MEMORY_HANDLE) { + error = CM_NO_MORE_MEMORY; + ERROR("CM_NO_MORE_MEMORY: Unable to allocate perfmeter\n", 0, 0, 0, 0, 0, 0); + } else { + t_uint32 mmdspAddr; + + pMpcLoad->perfmeterDataAddr = cm_DSP_GetHostLogicalAddress(pMpcLoad->perfmeterDataHandle); + cm_DSP_GetDspAddress(pMpcLoad->perfmeterDataHandle, &mmdspAddr); + cm_writeAttribute(cm_EEM_getExecutiveEngine(coreId)->instance, "rtos/perfmeter/perfmeterDataAddr", mmdspAddr); + } + + return error; +} + +PUBLIC void cm_PFM_deallocatePerfmeterDataMemory(t_nmf_core_id coreId) +{ + mpcLoad_i[coreId].perfmeterDataAddr = 0; + cm_DM_Free(mpcLoad_i[coreId].perfmeterDataHandle, TRUE); +} diff --git a/drivers/staging/nmf-cm/cm/engine/power_mgt/inc/power.h b/drivers/staging/nmf-cm/cm/engine/power_mgt/inc/power.h new file mode 100644 index 00000000000..942805df2f3 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/power_mgt/inc/power.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Enable a CM power domain by CoreID. + * + * \ingroup COMPONENT_INTERNAL + */ +#ifndef __INC_NMF_POWER +#define __INC_NMF_POWER + +#include <cm/inc/cm_type.h> +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/dsp/inc/dsp.h> + +typedef enum +{ + DISABLE_PWR_MODE = 0x0, //!< Disable mode - CM Power management is disabled. CM Power domain are always enabled and the EEs are loaded by default + NORMAL_PWR_MODE = 0x1 //!< Normal mode +} t_nmf_power_mode; + +extern t_nmf_power_mode powerMode; + +PUBLIC t_cm_error cm_PWR_Init(void); +void cm_PWR_SetMode(t_nmf_power_mode aMode); +t_nmf_power_mode cm_PWR_GetMode(void); +t_uint32 cm_PWR_GetMPCMemoryCount(t_nmf_core_id coreId); + +typedef enum +{ + MPC_PWR_CLOCK, + MPC_PWR_AUTOIDLE, + MPC_PWR_HWIP +} t_mpc_power_request; + +PUBLIC t_cm_error cm_PWR_EnableMPC( + t_mpc_power_request request, + t_nmf_core_id coreId); +PUBLIC void cm_PWR_DisableMPC( + t_mpc_power_request request, + t_nmf_core_id coreId); + +PUBLIC t_cm_error cm_PWR_EnableHSEM(void); +PUBLIC void cm_PWR_DisableHSEM(void); + +PUBLIC t_cm_error cm_PWR_EnableMemory( + t_nmf_core_id coreId, + t_dsp_memory_type_id dspMemType, + t_cm_physical_address address, + t_cm_size size); +PUBLIC void cm_PWR_DisableMemory( + t_nmf_core_id coreId, + t_dsp_memory_type_id dspMemType, + t_cm_physical_address address, + t_cm_size size); + + +#endif /* __INC_NMF_POWER */ diff --git a/drivers/staging/nmf-cm/cm/engine/power_mgt/src/cmpower.c b/drivers/staging/nmf-cm/cm/engine/power_mgt/src/cmpower.c new file mode 100644 index 00000000000..a104486db6c --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/power_mgt/src/cmpower.c @@ -0,0 +1,244 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include "../inc/power.h" + +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/utils/inc/convert.h> +#include <cm/engine/dsp/inc/dsp.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> + +// ------------------------------------------------------------------------------- +// Compilation flags +// ------------------------------------------------------------------------------- +#define __PWR_DEBUG_TRACE_LEVEL 2 // Debug trave level for CM power module + +// ------------------------------------------------------------------------------- +// Internal counter to store the TCM allocated chunk (by MPC) +// ------------------------------------------------------------------------------- +static t_uint32 _pwrMPCHWIPCountT[NB_CORE_IDS]; + +// ------------------------------------------------------------------------------- +// Internal counter to store the TCM allocated chunk (by MPC) +// ------------------------------------------------------------------------------- +static t_uint32 _pwrMPCMemoryCountT[NB_CORE_IDS]; + + +// ------------------------------------------------------------------------------- +// Internal data to store the global Power Manager mode (see cm_PWR_Init fct) +// ------------------------------------------------------------------------------- +t_nmf_power_mode powerMode = NORMAL_PWR_MODE; + +// ------------------------------------------------------------------------------- +// cm_PWR_Init +// ------------------------------------------------------------------------------- +PUBLIC t_cm_error cm_PWR_Init(void) +{ + int i; + + for (i=0; i<NB_CORE_IDS;i++) + { + _pwrMPCHWIPCountT[i] = 0; + _pwrMPCMemoryCountT[i] = 0; + } + + return CM_OK; +} + +// ------------------------------------------------------------------------------- +// cm_PWR_SetMode +// ------------------------------------------------------------------------------- +void cm_PWR_SetMode(t_nmf_power_mode aMode) +{ + powerMode = aMode; +} + +t_nmf_power_mode cm_PWR_GetMode() +{ + return powerMode; +} + +t_uint32 cm_PWR_GetMPCMemoryCount(t_nmf_core_id coreId) +{ + return _pwrMPCMemoryCountT[coreId]; +} + + +PUBLIC t_cm_error cm_PWR_EnableMPC( + t_mpc_power_request request, + t_nmf_core_id coreId) +{ + t_cm_error error; + + switch(request) + { + case MPC_PWR_CLOCK: + LOG_INTERNAL(__PWR_DEBUG_TRACE_LEVEL, "[Pwr] MPC %s enable clock\n", cm_getDspName(coreId), 0, 0, 0, 0, 0); + if((error = OSAL_EnablePwrRessource(CM_OSAL_POWER_SxA_CLOCK, coreId, 0)) != CM_OK) + { + ERROR("[Pwr] MPC %s clock can't be enabled\n", cm_getDspName(coreId), 0, 0, 0, 0, 0); + return error; + } + break; + case MPC_PWR_AUTOIDLE: + if((error = OSAL_EnablePwrRessource(CM_OSAL_POWER_SxA_AUTOIDLE, coreId, 0)) != CM_OK) + { + ERROR("[Pwr] MPC %s clock can't be auto-idle\n", cm_getDspName(coreId), 0, 0, 0, 0, 0); + return error; + } + break; + case MPC_PWR_HWIP: + if(_pwrMPCHWIPCountT[coreId]++ == 0) + { + LOG_INTERNAL(__PWR_DEBUG_TRACE_LEVEL, "[Pwr] MPC %s HW IP enable clock\n",cm_getDspName(coreId), 0, 0, 0, 0, 0); + + // The PRCMU seem not supporting the transition of asking HW IP on while DSP in retention + // -> Thus force wake up of the MMDSP before asking the transition + if ((error = cm_EEM_ForceWakeup(coreId)) != CM_OK) + return error; + + if((error = OSAL_EnablePwrRessource(CM_OSAL_POWER_SxA_HARDWARE, coreId, 0)) != CM_OK) + { + ERROR("[Pwr] MPC %s HW IP clock can't be enabled\n", cm_getDspName(coreId), 0, 0, 0, 0, 0); + cm_EEM_AllowSleep(coreId); + return error; + } + + cm_EEM_AllowSleep(coreId); + } + break; + } + + return CM_OK; +} + +PUBLIC void cm_PWR_DisableMPC( + t_mpc_power_request request, + t_nmf_core_id coreId) +{ + switch(request) + { + case MPC_PWR_CLOCK: + LOG_INTERNAL(__PWR_DEBUG_TRACE_LEVEL, "[Pwr] MPC %s disable clock\n",cm_getDspName(coreId), 0, 0, 0, 0, 0); + OSAL_DisablePwrRessource(CM_OSAL_POWER_SxA_CLOCK, coreId, 0); + break; + case MPC_PWR_AUTOIDLE: + OSAL_DisablePwrRessource(CM_OSAL_POWER_SxA_AUTOIDLE, coreId, 0); + break; + case MPC_PWR_HWIP: + if(--_pwrMPCHWIPCountT[coreId] == 0) + { + LOG_INTERNAL(__PWR_DEBUG_TRACE_LEVEL, "[Pwr] MPC %s HW IP disable clock\n",cm_getDspName(coreId), 0, 0, 0, 0, 0); + + // The PRCMU seem not supporting the transition of asking HW IP on while DSP in retention + // -> Thus force wake up of the MMDSP before asking the transition + if (cm_EEM_ForceWakeup(coreId) != CM_OK) + return; + + OSAL_DisablePwrRessource(CM_OSAL_POWER_SxA_HARDWARE, coreId, 0); + + cm_EEM_AllowSleep(coreId); + } + break; + } +} + +PUBLIC t_cm_error cm_PWR_EnableHSEM(void) +{ + t_cm_error error; + + LOG_INTERNAL(__PWR_DEBUG_TRACE_LEVEL, "[Pwr] HSEM enable clock\n",0 , 0, 0, 0, 0, 0); + if((error = OSAL_EnablePwrRessource(CM_OSAL_POWER_HSEM, 0, 0)) != CM_OK) + { + ERROR("[Pwr] HSEM clock can't be enabled\n", 0, 0, 0, 0, 0, 0); + return error; + } + + return CM_OK; +} + +PUBLIC void cm_PWR_DisableHSEM(void) +{ + LOG_INTERNAL(__PWR_DEBUG_TRACE_LEVEL, "[Pwr] HSEM disable clock\n",0 , 0, 0, 0, 0, 0); + OSAL_DisablePwrRessource(CM_OSAL_POWER_HSEM, 0, 0); +} + +PUBLIC t_cm_error cm_PWR_EnableMemory( + t_nmf_core_id coreId, + t_dsp_memory_type_id dspMemType, + t_cm_physical_address address, + t_cm_size size) +{ + switch(dspMemType) + { + case INTERNAL_XRAM24: + case INTERNAL_XRAM16: + case INTERNAL_YRAM24: + case INTERNAL_YRAM16: + _pwrMPCMemoryCountT[coreId]++; + break; + case SDRAM_EXT24: + case SDRAM_EXT16: + case SDRAM_CODE: + case LOCKED_CODE: + return OSAL_EnablePwrRessource( + CM_OSAL_POWER_SDRAM, + address, + size); + case ESRAM_EXT24: + case ESRAM_EXT16: + case ESRAM_CODE: + return OSAL_EnablePwrRessource( + CM_OSAL_POWER_ESRAM, + address, + size); + default: + CM_ASSERT(0); + } + + return CM_OK; +} + +PUBLIC void cm_PWR_DisableMemory( + t_nmf_core_id coreId, + t_dsp_memory_type_id dspMemType, + t_cm_physical_address address, + t_cm_size size) +{ + switch(dspMemType) + { + case INTERNAL_XRAM24: + case INTERNAL_XRAM16: + case INTERNAL_YRAM24: + case INTERNAL_YRAM16: + _pwrMPCMemoryCountT[coreId]--; + break; + case SDRAM_EXT24: + case SDRAM_EXT16: + case SDRAM_CODE: + case LOCKED_CODE: + OSAL_DisablePwrRessource( + CM_OSAL_POWER_SDRAM, + address, + size); + break; + case ESRAM_EXT24: + case ESRAM_EXT16: + case ESRAM_CODE: + OSAL_DisablePwrRessource( + CM_OSAL_POWER_ESRAM, + address, + size); + break; + default: + CM_ASSERT(0); + } +} + + + + + diff --git a/drivers/staging/nmf-cm/cm/engine/repository_mgt/inc/repository_mgt.h b/drivers/staging/nmf-cm/cm/engine/repository_mgt/inc/repository_mgt.h new file mode 100644 index 00000000000..d2c7185b24f --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/repository_mgt/inc/repository_mgt.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Component repository internal methods. + * + * \defgroup REPOSITORY_INTERNAL Component repository. + */ +#ifndef __INC_CM_REP_MGT_H +#define __INC_CM_REP_MGT_H + +#include <cm/inc/cm_type.h> +#include <inc/nmf-limits.h> + +/*! + * \brief Identification of a component entry. + * \ingroup REPOSITORY_INTERNAL + */ +typedef struct t_rep_component { + t_dup_char name; + struct t_rep_component *prev; + struct t_rep_component *next; + t_elfdescription *elfhandle; //!< Must be last as data will be stored here +} t_rep_component; + +/*! + * \brief Search a component entry by name. + * + * \param[in] name The name of the component to look for. + * \param[out] component The corresponding component entry in the repository + * + * \retval t_cm_error + * + * \ingroup REPOSITORY_INTERNAL + */ +PUBLIC t_cm_error cm_REP_lookupComponent(const char *name, t_rep_component **component); + +/*! + * \brief Helper method that return the dataFile found in parameter or in the cache + */ +t_elfdescription* cm_REP_getComponentFile(t_dup_char templateName, t_elfdescription* elfhandle); + +/*! + * \brief Destroy the full repository (remove and free all components) + * + * \retval none + * + * \ingroup REPOSITORY_INTERNAL + */ +PUBLIC void cm_REP_Destroy(void); + +#endif /* __INC_CM_REP_MGT_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/repository_mgt/inc/repository_type.h b/drivers/staging/nmf-cm/cm/engine/repository_mgt/inc/repository_type.h new file mode 100644 index 00000000000..30ef8004c48 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/repository_mgt/inc/repository_type.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Components Component Manager API type. + * + * \defgroup COMPONENT CM Components API + * \ingroup CM_USER_API + */ + +#ifndef REPOSITORY_TYPE_H_ +#define REPOSITORY_TYPE_H_ + +typedef enum +{ + BIND_ASYNC, + BIND_TRACE, + BIND_FROMUSER, + BIND_TOUSER +} t_action_to_do; + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/repository_mgt/src/repository_mgt.c b/drivers/staging/nmf-cm/cm/engine/repository_mgt/src/repository_mgt.c new file mode 100644 index 00000000000..f6ccb4a9992 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/repository_mgt/src/repository_mgt.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + */ +#include <cm/engine/utils/inc/string.h> + +#include <cm/engine/component/inc/component_type.h> +#include <cm/engine/component/inc/bind.h> +#include <cm/engine/configuration/inc/configuration.h> +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> +#include <cm/engine/repository_mgt/inc/repository_mgt.h> +#include <cm/engine/api/repository_mgt_engine.h> +#include <cm/engine/trace/inc/trace.h> + + +#undef NHASH +#define NHASH 157 //Use a prime number! +#define MULT 17 + +static t_rep_component *componentCaches[NHASH]; + +static unsigned int repcomponentHash(const char *str) +{ + unsigned int h = 0; + for(; *str; str++) + h = MULT * h + *str; + return h % NHASH; +} + +static void repcomponentAdd(t_rep_component *component) +{ + unsigned int h = repcomponentHash(component->name); + + if(componentCaches[h] != NULL) + componentCaches[h]->prev = component; + component->next = componentCaches[h]; + component->prev = NULL; + componentCaches[h] = component; +} + +static void repcomponentRemove(t_rep_component *component) +{ + unsigned int h = repcomponentHash(component->name); + + if(component->prev != NULL) + component->prev->next = component->next; + if(component->next != NULL) + component->next->prev = component->prev; + if(component == componentCaches[h]) + componentCaches[h] = component->next; +} + + +PUBLIC t_cm_error cm_REP_lookupComponent(const char *name, t_rep_component **component) +{ + t_rep_component *tmp; + + for(tmp = componentCaches[repcomponentHash(name)]; tmp != NULL; tmp = tmp->next) + { + if(cm_StringCompare(name, tmp->name, MAX_TEMPLATE_NAME_LENGTH) == 0) + { + if(component != NULL) + *component = tmp; + return CM_OK; + } + } + + return CM_COMPONENT_NOT_FOUND; +} + +t_elfdescription* cm_REP_getComponentFile(t_dup_char templateName, t_elfdescription* elfhandle) +{ + if(elfhandle == NULL) + { + t_rep_component *pRepComponent; + + for(pRepComponent = componentCaches[repcomponentHash(templateName)]; pRepComponent != NULL; pRepComponent = pRepComponent->next) + { + if(pRepComponent->name == templateName) + return pRepComponent->elfhandle; + } + + return NULL; + } + + return elfhandle; +} + + +PUBLIC void cm_REP_Destroy(void) +{ + t_rep_component *component, *next; + int i; + + for(i = 0; i < NHASH; i++) + { + for (component = componentCaches[i]; component != NULL; component = next) + { + next = component->next; + cm_ELF_CloseFile(FALSE, component->elfhandle); + cm_StringRelease(component->name); + OSAL_Free(component); + } + componentCaches[i] = NULL; + } +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_GetRequiredComponentFiles( + // IN + t_action_to_do action, + const t_cm_instance_handle client, + const char *requiredItfClientName, + const t_cm_instance_handle server, + const char *providedItfServerName, + // OUT component to be pushed + char fileList[][MAX_INTERFACE_TYPE_NAME_LENGTH], + t_uint32 listSize, + // OUT interface information + char type[MAX_INTERFACE_TYPE_NAME_LENGTH], + t_uint32 *methodNumber) +{ + t_cm_error error; + t_component_instance* compClient, *compServer; + int n; + + OSAL_LOCK_API(); + + // No component required + for(n = 0; n < listSize; n++) + fileList[n][0] = 0; + + compClient = cm_lookupComponent(client); + compServer = cm_lookupComponent(server); + switch(action) + { + case BIND_FROMUSER:{ + t_interface_provide_description itfProvide; + + // Check server validity + if((error = cm_checkValidServer(compServer, providedItfServerName, + &itfProvide)) == CM_OK) + { + cm_StringCopy(type, itfProvide.server->Template->provides[itfProvide.provideIndex].interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + + cm_StringCopy(fileList[0], "_sk.", MAX_INTERFACE_TYPE_NAME_LENGTH); + cm_StringConcatenate(fileList[0], itfProvide.server->Template->provides[itfProvide.provideIndex].interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + } + } break; + + case BIND_TOUSER: { + /* Get Components names for a BindComponentToCMCore */ + t_interface_require_description itfRequire; + t_bool bindable; + + // Check client validity + if((error = cm_checkValidClient(compClient, requiredItfClientName, + &itfRequire, &bindable)) == CM_OK) + { + cm_StringCopy(type, itfRequire.client->Template->requires[itfRequire.requireIndex].interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + *methodNumber = itfRequire.client->Template->requires[itfRequire.requireIndex].interface->methodNumber; + + cm_StringCopy(fileList[0], "_st.", MAX_INTERFACE_TYPE_NAME_LENGTH); + cm_StringConcatenate(fileList[0], itfRequire.client->Template->requires[itfRequire.requireIndex].interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + } + }; break; + + case BIND_ASYNC: { + /* Get Components names for an asynchronous binding */ + t_interface_require_description itfRequire; + t_interface_provide_description itfProvide; + t_bool bindable; + + // Check invalid binding + if((error = cm_checkValidBinding(compClient, requiredItfClientName, + compServer, providedItfServerName, + &itfRequire, &itfProvide, &bindable)) == CM_OK) + { + if(compClient->Template->dspId != compServer->Template->dspId) + { + cm_StringCopy(fileList[0], "_sk.", MAX_INTERFACE_TYPE_NAME_LENGTH); + cm_StringConcatenate(fileList[0], itfRequire.client->Template->requires[itfRequire.requireIndex].interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + + cm_StringCopy(fileList[1], "_st.", MAX_INTERFACE_TYPE_NAME_LENGTH); + cm_StringConcatenate(fileList[1], itfRequire.client->Template->requires[itfRequire.requireIndex].interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + } + else + { + cm_StringCopy(fileList[0], "_ev.", MAX_INTERFACE_TYPE_NAME_LENGTH); + cm_StringConcatenate(fileList[0], itfRequire.client->Template->requires[itfRequire.requireIndex].interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + } + } + }; break; + + case BIND_TRACE: { + /* Get Components names for an asynchronous binding */ + t_interface_require_description itfRequire; + t_interface_provide_description itfProvide; + t_bool bindable; + + // Check invalid binding + if((error = cm_checkValidBinding(compClient, requiredItfClientName, + compServer, providedItfServerName, + &itfRequire, &itfProvide, &bindable)) == CM_OK) + { + cm_StringCopy(fileList[0], "_tr.", MAX_INTERFACE_TYPE_NAME_LENGTH); + cm_StringConcatenate(fileList[0], itfRequire.client->Template->requires[itfRequire.requireIndex].interface->type, MAX_INTERFACE_TYPE_NAME_LENGTH); + } + }; break; + + default: + error = CM_OK; + break; + } + + if(error == CM_OK) + { + for(n = 0; n < listSize; n++) + { + t_rep_component *comp; + + // If already loaded, don't ask to load it and put the name to NULL + if (fileList[n][0] != 0 && + cm_REP_lookupComponent(fileList[n], &comp) == CM_OK) + fileList[n][0] = 0; + } + } + + + OSAL_UNLOCK_API(); + return error; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_PushComponent(const char *name, const void *data, t_cm_size size) +{ + t_rep_component *comp; + t_cm_error error; + + OSAL_LOCK_API(); + + if (cm_REP_lookupComponent(name, &comp) == CM_OK) { + /* Component is already there: silently ignore it */ + OSAL_UNLOCK_API(); + return CM_OK; + } + + comp = OSAL_Alloc(sizeof(*comp)); + if (comp == NULL) { + OSAL_UNLOCK_API(); + return CM_NO_MORE_MEMORY; + } + + comp->name = cm_StringDuplicate(name); + if(comp->name == NULL) + { + OSAL_Free(comp); + OSAL_UNLOCK_API(); + return CM_NO_MORE_MEMORY; + } + + if((error = cm_ELF_CheckFile( + data, + FALSE, + &comp->elfhandle)) != CM_OK) { + cm_StringRelease(comp->name); + OSAL_Free(comp); + OSAL_UNLOCK_API(); + return error; + } +/* + if (OSAL_Copy(comp->data, data, size)) { + OSAL_Free(comp); + OSAL_UNLOCK_API(); + return CM_UNKNOWN_MEMORY_HANDLE; + }*/ + + repcomponentAdd(comp); + + OSAL_UNLOCK_API(); + return CM_OK; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ENGINE_ReleaseComponent (const char *name) +{ + t_rep_component *component; + t_cm_error err; + + OSAL_LOCK_API(); + err = cm_REP_lookupComponent(name , &component); + + if (CM_OK == err) + { + repcomponentRemove(component); + + cm_ELF_CloseFile(FALSE, component->elfhandle); + cm_StringRelease(component->name); + OSAL_Free(component); + } + + OSAL_UNLOCK_API(); + + return err; +} + +PUBLIC EXPORT_SHARED t_bool CM_ENGINE_IsComponentCacheEmpty(void) +{ + int i; + + OSAL_LOCK_API(); + for(i = 0; i < NHASH; i++) { + if (componentCaches[i] != NULL) { + OSAL_UNLOCK_API(); + return FALSE; + } + } + OSAL_UNLOCK_API(); + return TRUE; +} diff --git a/drivers/staging/nmf-cm/cm/engine/semaphores/hw_semaphores/inc/hw_semaphores.h b/drivers/staging/nmf-cm/cm/engine/semaphores/hw_semaphores/inc/hw_semaphores.h new file mode 100644 index 00000000000..bd914195b6d --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/semaphores/hw_semaphores/inc/hw_semaphores.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef __INC_HW_SEMA_H_ +#define __INC_HW_SEMA_H_ + +#include <cm/inc/cm_type.h> +#include <cm/engine/semaphores/inc/semaphores.h> +#include <share/semaphores/inc/hwsem_hwp.h> + + +/******************************************************************************/ +/************************ FUNCTIONS PROTOTYPES ********************************/ +/******************************************************************************/ + +PUBLIC t_cm_error cm_HSEM_Init(const t_cm_system_address *pSystemAddr); +PUBLIC t_cm_error cm_HSEM_EnableSemIrq(t_semaphore_id semId, t_nmf_core_id toCoreId); +PUBLIC void cm_HSEM_Take(t_nmf_core_id coreId, t_semaphore_id semId); +PUBLIC void cm_HSEM_Give(t_nmf_core_id coreId, t_semaphore_id semId); +PUBLIC void cm_HSEM_GiveWithInterruptGeneration(t_nmf_core_id coreId, t_semaphore_id semId); +PUBLIC void cm_HSEM_GenerateIrq(t_nmf_core_id coreId, t_semaphore_id semId); +PUBLIC t_nmf_core_id cm_HSEM_GetCoreIdFromIrqSrc(void); + +PUBLIC t_cm_error cm_HSEM_PowerOn(t_nmf_core_id coreId); +PUBLIC void cm_HSEM_PowerOff(t_nmf_core_id coreId); + +#endif /* __INC_HW_SEMA_H_ */ diff --git a/drivers/staging/nmf-cm/cm/engine/semaphores/hw_semaphores/src/hw_semaphores.c b/drivers/staging/nmf-cm/cm/engine/semaphores/hw_semaphores/src/hw_semaphores.c new file mode 100644 index 00000000000..932058cd4f2 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/semaphores/hw_semaphores/src/hw_semaphores.c @@ -0,0 +1,171 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/******************************************************************* Includes + ****************************************************************************/ + +#include "../inc/hw_semaphores.h" +#include <share/semaphores/inc/hwsem_hwp.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> +#include <cm/engine/power_mgt/inc/power.h> +static t_hw_semaphore_regs *pHwSemRegs = (t_hw_semaphore_regs *)0; + +static t_uint32 semaphoreUseCounter = 0; +static t_uint32 imsc[HSEM_MAX_INTR]; +PRIVATE void restoreMask(void); + +/****************************************************************************/ +/* NAME: t_cm_error cm_HSEM_Init(const t_cm_system_address *pSystemAddr) */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: Initialize the HW Semaphores module */ +/* */ +/* PARAMETERS: */ +/* (in) pSystemAddr: system base address of the HW semaphores IP */ +/* */ +/* RETURN: CM_OK always */ +/* */ +/****************************************************************************/ +PUBLIC t_cm_error cm_HSEM_Init(const t_cm_system_address *pSystemAddr) +{ + t_uint8 i; + + pHwSemRegs = (t_hw_semaphore_regs *)pSystemAddr->logical; + + for (i=HSEM_FIRST_INTR; i < HSEM_MAX_INTR; i++) + { + imsc[i] = 0; // Mask all interrupt + } + + return CM_OK; +} + +static void cm_HSEM_ReInit(void) +{ + t_uint8 i; + + pHwSemRegs->icrall = MASK_ALL16; + + for (i=HSEM_FIRST_INTR; i < HSEM_MAX_INTR; i++) + { + pHwSemRegs->it[i].imsc = imsc[i]; + pHwSemRegs->it[i].icr = MASK_ALL16; + } + + for (i=0; i < NUM_HW_SEMAPHORES; i++) + { + pHwSemRegs->sem[i] = 0; + } +} + +/****************************************************************************/ +/* NAME: t_cm_error cm_HSEM_EnableSemIrq( */ +/* t_semaphore_id semId, */ +/* t_nmf_core_id toCoreId */ +/* ) */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: Enable Irq for a given coreId (communication receiver) */ +/* */ +/* PARAMETERS: */ +/* (in) semId: identifier of the semaphore */ +/* (in) toCoreId: identifier of coreId destination of the coms */ +/* */ +/* RETURN: CM_OK always */ +/* */ +/****************************************************************************/ +PUBLIC t_cm_error cm_HSEM_EnableSemIrq(t_semaphore_id semId, t_nmf_core_id toCoreId) +{ + static t_uint32 CoreIdToIntr[NB_CORE_IDS] = {0, 2, 3}; + int i = CoreIdToIntr[toCoreId]; + + imsc[i] |= (1UL << semId); + + // Allow cm_HSEM_EnableSemIrq to be called before real start in order to save power + if(semaphoreUseCounter > 0) + { + pHwSemRegs->it[i].imsc = imsc[i]; + } + + return CM_OK; +} + +/****************************************************************************/ +/* NAME: void cm_HSEM_GenerateIrq(t_semaphore_id semId) */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: Generate an irq toward correct core according to semId */ +/* */ +/* PARAMETERS: */ +/* (in) semId: identifier of the semaphore to handle */ +/* */ +/* RETURN: none */ +/* */ +/****************************************************************************/ +PUBLIC void cm_HSEM_GenerateIrq(t_nmf_core_id coreId, t_semaphore_id semId) +{ + // TODO Move restore in OS BSP or in PRCMU in order to to it only when wake-up, for now do it always !!!!!!!!!!!! + restoreMask(); + + pHwSemRegs->sem[semId] = CORE_ID_2_HW_CORE_ID(ARM_CORE_ID); + pHwSemRegs->sem[semId] = (HSEM_INTRA_MASK|HSEM_INTRB_MASK|HSEM_INTRC_MASK|HSEM_INTRD_MASK); +} + +/****************************************************************************/ +/* NAME: t_nmf_core_id cm_HSEM_GetCoreIdFromIrqSrc(void) */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: Check Masked Interrupt Status to know which semaphore(s) */ +/* have pending interrupt and return the identifier of the given dsp */ +/* */ +/* PARAMETERS: none */ +/* */ +/* RETURN: none */ +/* */ +/****************************************************************************/ +PUBLIC t_nmf_core_id cm_HSEM_GetCoreIdFromIrqSrc(void) +{ + t_uword misValue = pHwSemRegs->it[ARM_CORE_ID].mis; + t_uint32 mask = 1 << FIRST_NEIGHBOR_SEMID(ARM_CORE_ID) /* == 0 here */; + t_nmf_core_id coreId = FIRST_MPC_ID; + + while ((misValue & mask) == 0) + { + mask <<= 1; + + coreId++; + if(coreId > LAST_MPC_ID) + return coreId; + } + + /* Acknowledge Hsem interrupt */ + pHwSemRegs->it[ARM_CORE_ID].icr = mask; + + return coreId; +} + +PUBLIC t_cm_error cm_HSEM_PowerOn(t_nmf_core_id coreId) +{ + if(semaphoreUseCounter++ == 0) + { + cm_PWR_EnableHSEM(); + + cm_HSEM_ReInit(); // HSEM is called one time only when the HSEM is switched ON + } + + return CM_OK; +} + +PUBLIC void cm_HSEM_PowerOff(t_nmf_core_id coreId) +{ + if(--semaphoreUseCounter == 0) + { + cm_PWR_DisableHSEM(); + } +} + +PRIVATE void restoreMask() +{ + t_uint8 i; + + for (i=HSEM_FIRST_INTR; i < HSEM_MAX_INTR; i++) + pHwSemRegs->it[i].imsc = imsc[i]; +} diff --git a/drivers/staging/nmf-cm/cm/engine/semaphores/inc/semaphores.h b/drivers/staging/nmf-cm/cm/engine/semaphores/inc/semaphores.h new file mode 100644 index 00000000000..7636d8e7c9d --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/semaphores/inc/semaphores.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/** + * \internal + */ +#ifndef __INC_NMF_SEMAPHORE_H +#define __INC_NMF_SEMAPHORE_H + +#include <cm/engine/api/control/configuration_engine.h> +#include <share/semaphores/inc/semaphores.h> +#include <cm/engine/semaphores/hw_semaphores/inc/hw_semaphores.h> + +PUBLIC t_cm_error cm_SEM_Init(const t_cm_system_address *pSystemAddr); +PUBLIC t_cm_error cm_SEM_InitMpc(t_nmf_core_id coreId, t_nmf_semaphore_type_id semTypeId); +PUBLIC t_semaphore_id cm_SEM_Alloc(t_nmf_core_id fromCoreId, t_nmf_core_id toCoreId); + +/* Semaphores management virtualized functions */ +extern void (*cm_SEM_GenerateIrq[NB_CORE_IDS])(t_nmf_core_id coreId, t_semaphore_id semId); +extern t_cm_error (*cm_SEM_PowerOn[NB_CORE_IDS])(t_nmf_core_id coreId); +extern void (*cm_SEM_PowerOff[NB_CORE_IDS])(t_nmf_core_id coreId); + +#endif /* __INC_NMF_SEMAPHORE_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/semaphores/src/semaphores.c b/drivers/staging/nmf-cm/cm/engine/semaphores/src/semaphores.c new file mode 100644 index 00000000000..daf95355a56 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/semaphores/src/semaphores.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/inc/cm_type.h> +#include <cm/engine/semaphores/inc/semaphores.h> +#include <cm/engine/semaphores/hw_semaphores/inc/hw_semaphores.h> +#include <cm/engine/dsp/inc/semaphores_dsp.h> +#include <cm/engine/trace/inc/trace.h> +#include <share/inc/nmf.h> + +void (*cm_SEM_GenerateIrq[NB_CORE_IDS])(t_nmf_core_id coreId, t_semaphore_id semId); +t_cm_error (*cm_SEM_PowerOn[NB_CORE_IDS])(t_nmf_core_id coreId); +void (*cm_SEM_PowerOff[NB_CORE_IDS])(t_nmf_core_id coreId); + +#define SEM_TYPE_ID_DEFAULT_VALUE ((t_nmf_semaphore_type_id)MASK_ALL32) +static t_nmf_semaphore_type_id semaphoreTypePerCoreId[NB_CORE_IDS]; + +static t_cm_error cm_LSEM_PowerOn(t_nmf_core_id coreId) +{ + return CM_OK; +} + +static void cm_LSEM_PowerOff(t_nmf_core_id coreId) +{ +} + +PUBLIC t_cm_error cm_SEM_Init(const t_cm_system_address *pSystemAddr) +{ + t_nmf_core_id coreId; + + for (coreId = ARM_CORE_ID; coreId < NB_CORE_IDS; coreId++) + { + semaphoreTypePerCoreId[coreId] = SEM_TYPE_ID_DEFAULT_VALUE; + + /* By default, we suppose that we use a full feature NMF ;) */ + cm_SEM_GenerateIrq[coreId] = NULL; + cm_SEM_PowerOn[coreId] = NULL; + cm_SEM_PowerOff[coreId] = NULL; + } + + cm_HSEM_Init(pSystemAddr); + /* if needed local semaphore init will be done coreId per coreId */ + + return CM_OK; +} + +PUBLIC t_cm_error cm_SEM_InitMpc(t_nmf_core_id coreId, t_nmf_semaphore_type_id semTypeId) +{ + if (semaphoreTypePerCoreId[coreId] != SEM_TYPE_ID_DEFAULT_VALUE) + return CM_MPC_ALREADY_INITIALIZED; + + if(semTypeId == SYSTEM_SEMAPHORES) + { + cm_SEM_GenerateIrq[coreId] = cm_HSEM_GenerateIrq; + cm_SEM_PowerOn[coreId] = cm_HSEM_PowerOn; + cm_SEM_PowerOff[coreId] = cm_HSEM_PowerOff; + } + else if (semTypeId == LOCAL_SEMAPHORES) + { + cm_SEM_GenerateIrq[coreId] = cm_DSP_SEM_GenerateIrq; + cm_SEM_PowerOn[coreId] = cm_LSEM_PowerOn; + cm_SEM_PowerOff[coreId] = cm_LSEM_PowerOff; + } + + semaphoreTypePerCoreId[coreId] = semTypeId; + + return CM_OK; +} + +PUBLIC t_semaphore_id cm_SEM_Alloc(t_nmf_core_id fromCoreId, t_nmf_core_id toCoreId) +{ + t_semaphore_id semId; + t_nmf_core_id corex; + + semId = FIRST_NEIGHBOR_SEMID(toCoreId); + for (corex = FIRST_CORE_ID; corex < fromCoreId; corex++) + { + if (corex == toCoreId) + continue; + semId++; + } + + if ( + (toCoreId == ARM_CORE_ID && semaphoreTypePerCoreId[fromCoreId] == SYSTEM_SEMAPHORES) || + (semaphoreTypePerCoreId[toCoreId] == SYSTEM_SEMAPHORES) + ) + { + cm_HSEM_EnableSemIrq(semId, toCoreId); + } + + return semId; +} diff --git a/drivers/staging/nmf-cm/cm/engine/trace/inc/trace.h b/drivers/staging/nmf-cm/cm/engine/trace/inc/trace.h new file mode 100644 index 00000000000..111eaf1324e --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/trace/inc/trace.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Trace facilities management API + * + * \defgroup Trace Facilities + */ +#ifndef __INC_CM_TRACE_H +#define __INC_CM_TRACE_H + +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> +#include <cm/engine/configuration/inc/configuration_status.h> + +/*********************/ +/* Log related stuff */ +/*********************/ +#define ERROR(format, param1, param2, param3, param4, param5, param6) \ +do { \ + if (cm_debug_level != -1) \ + OSAL_Log("Error: " format, (int)(param1), (int)(param2), (int)(param3), (int)(param4), (int)(param5), (int)(param6)); \ + while(cm_error_break);\ +} while(0) + +#define WARNING(format, param1, param2, param3, param4, param5, param6) \ +do { \ + if (cm_debug_level != -1) \ + OSAL_Log("Warning: " format, (int)(param1), (int)(param2), (int)(param3), (int)(param4), (int)(param5), (int)(param6)); \ +} while(0) + +#define LOG_INTERNAL(level, format, param1, param2, param3, param4, param5, param6) \ +do { \ + if (level <= cm_debug_level) \ + OSAL_Log((const char *)format, (int)(param1), (int)(param2), (int)(param3), (int)(param4), (int)(param5), (int)(param6)); \ +} while(0) + +/*************************/ +/* Panic related stuff */ +/*************************/ +#define CM_ASSERT(cond) \ +do { \ + if(!(cond)) { OSAL_Log("CM_ASSERT at %s:%d\n", (int)__FILE__, (int)__LINE__, 0, 0, 0, 0); OSAL_Panic(); while(1); } \ +} while (0) + +#endif /* __INC_CM_TRACE_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/trace/inc/xtitrace.h b/drivers/staging/nmf-cm/cm/engine/trace/inc/xtitrace.h new file mode 100644 index 00000000000..1efd4f1e699 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/trace/inc/xtitrace.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#ifndef __INC_CM_XTITRACE_H +#define __INC_CM_XTITRACE_H + +#include <cm/engine/component/inc/instance.h> + +#include <inc/nmf-tracedescription.h> + +extern t_bool cm_trace_enabled; + +/*************************/ +/* Trace related stuff */ +/*************************/ +void cm_TRC_Dump(void); + +void cm_TRC_traceReset(void); + +void cm_TRC_traceLoadMap( + t_nmfTraceComponentCommandDescription cmd, + const t_component_instance* component); + +#define ARM_TRACE_COMPONENT ((const t_component_instance*)0xFFFFFFFF) + +void cm_TRC_traceBinding( + t_nmfTraceBindCommandDescription command, + const t_component_instance* clientComponent, const t_component_instance* serverComponent, + const char *requiredItfName, const char *providedItfName); + +void cm_TRC_traceCommunication( + t_nmfTraceCommunicationCommandDescription command, + t_nmf_core_id coreId, + t_nmf_core_id remoteCoreId); + +void cm_TRC_traceMemAlloc(t_nmfTraceAllocatorCommandDescription command, t_uint8 allocId, t_uint32 memorySize, const char *allocname); + +void cm_TRC_traceMem(t_nmfTraceAllocCommandDescription command, t_uint8 allocId, t_uint32 startAddress, t_uint32 memorySize); + +/*************************/ +/* MMDSP trace buffer */ +/*************************/ +PUBLIC t_cm_error cm_SRV_allocateTraceBufferMemory(t_nmf_core_id coreId, t_cm_domain_id domainId); +PUBLIC void cm_SRV_deallocateTraceBufferMemory(t_nmf_core_id coreId); + + + +#endif /* __INC_CM_TRACE_H */ diff --git a/drivers/staging/nmf-cm/cm/engine/trace/src/panic.c b/drivers/staging/nmf-cm/cm/engine/trace/src/panic.c new file mode 100644 index 00000000000..e59d9f8b1ba --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/trace/src/panic.c @@ -0,0 +1,331 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include <cm/inc/cm_type.h> +#include <cm/engine/component/inc/introspection.h> +#include <cm/engine/component/inc/bind.h> +#include <cm/engine/executive_engine_mgt/inc/executive_engine_mgt.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/api/control/irq_engine.h> + +#include <cm/engine/utils/inc/convert.h> +#include <share/communication/inc/nmf_service.h> + +PUBLIC t_cm_error cm_SRV_allocateTraceBufferMemory(t_nmf_core_id coreId, t_cm_domain_id domainId) +{ + t_ee_state *state = cm_EEM_getExecutiveEngine(coreId); + + state->traceDataHandle = cm_DM_Alloc(domainId, SDRAM_EXT16, + TRACE_BUFFER_SIZE * sizeof(struct t_nmf_trace) / 2, CM_MM_ALIGN_WORD, TRUE); + if (state->traceDataHandle == INVALID_MEMORY_HANDLE) + return CM_NO_MORE_MEMORY; + else + { + t_uint32 mmdspAddr; + int i; + + state->traceDataAddr = (struct t_nmf_trace*)cm_DSP_GetHostLogicalAddress(state->traceDataHandle); + cm_DSP_GetDspAddress(state->traceDataHandle, &mmdspAddr); + cm_writeAttribute(state->instance, "rtos/commonpart/traceDataAddr", mmdspAddr); + + eeState[coreId].readTracePointer = 0; + eeState[coreId].lastReadedTraceRevision = 0; + + for(i = 0; i < TRACE_BUFFER_SIZE; i++) + state->traceDataAddr[i].revision = 0; + + return CM_OK; + } +} + +PUBLIC void cm_SRV_deallocateTraceBufferMemory(t_nmf_core_id coreId) +{ + t_ee_state *state = cm_EEM_getExecutiveEngine(coreId); + + state->traceDataAddr = 0; + cm_DM_Free(state->traceDataHandle, TRUE); +} + +static t_uint32 swapHalfWord(t_uint32 word) +{ + return (word >> 16) | (word << 16); +} + +PUBLIC EXPORT_SHARED t_cm_trace_type CM_ENGINE_GetNextTrace( + t_nmf_core_id coreId, + struct t_nmf_trace *trace) +{ + t_ee_state *state = cm_EEM_getExecutiveEngine(coreId); + t_uint32 foundRevision; + t_cm_trace_type type; + + OSAL_LOCK_API(); + if (state->traceDataAddr == NULL) { + type = CM_MPC_TRACE_NONE; + goto out; + } + + foundRevision = swapHalfWord(state->traceDataAddr[state->readTracePointer].revision); + + if(foundRevision <= state->lastReadedTraceRevision) + { + // It's an old trace forgot it + type = CM_MPC_TRACE_NONE; + } + else + { + struct t_nmf_trace *traceRaw; + + if(foundRevision == state->lastReadedTraceRevision + 1) + { + type = CM_MPC_TRACE_READ; + } + else + { + type = CM_MPC_TRACE_READ_OVERRUN; + /* + * If we find that revision is bigger, thus we are in overrun, then we take the writePointer + 1 which + * correspond to the older one. + * => Here there is a window where the MMDSP could update writePointer just after + */ + state->readTracePointer = (cm_readAttributeNoError(state->instance, "rtos/commonpart/writePointer") + 1) % TRACE_BUFFER_SIZE; + } + + traceRaw = &state->traceDataAddr[state->readTracePointer]; + + trace->timeStamp = swapHalfWord(traceRaw->timeStamp); + trace->componentId = swapHalfWord(traceRaw->componentId); + trace->traceId = swapHalfWord(traceRaw->traceId); + trace->paramOpt = swapHalfWord(traceRaw->paramOpt); + trace->componentHandle = swapHalfWord(traceRaw->componentHandle); + trace->parentHandle = swapHalfWord(traceRaw->parentHandle); + + trace->params[0] = swapHalfWord(traceRaw->params[0]); + trace->params[1] = swapHalfWord(traceRaw->params[1]); + trace->params[2] = swapHalfWord(traceRaw->params[2]); + trace->params[3] = swapHalfWord(traceRaw->params[3]); + + state->readTracePointer = (state->readTracePointer + 1) % TRACE_BUFFER_SIZE; + state->lastReadedTraceRevision = swapHalfWord(traceRaw->revision); + trace->revision = state->lastReadedTraceRevision; + } + +out: + OSAL_UNLOCK_API(); + + return type; +} + + +/* + * Panic + */ +const struct { + char* name; + unsigned int info1:1; + unsigned int PC:1; + unsigned int SP:1; + unsigned int interface:1; +} reason_descrs[] = { + {"NONE_PANIC", 0, 0, 0, 0}, + {"INTERNAL_PANIC", 1, 0, 0, 0}, + {"MPC_NOT_RESPONDING_PANIC", 0, 0, 0, 0}, /* Should not be useful since in that case CM_getServiceDescription() not call */ + {"USER_STACK_OVERFLOW", 0, 1, 1, 0}, + {"SYSTEM_STACK_OVERFLOW", 0, 1, 1, 0}, + {"UNALIGNED_LONG_ACCESS", 0, 1, 0, 0}, + {"EVENT_FIFO_OVERFLOW", 0, 0, 0, 1}, + {"PARAM_FIFO_OVERFLOW", 0, 0, 0, 1}, + {"INTERFACE_NOT_BINDED", 0, 1, 0, 0}, + {"USER_PANIC", 1, 0, 0, 0} +}; + +static t_component_instance* getCorrespondingInstance( + t_panic_reason panicReason, + t_uint32 panicThis, + t_dup_char *itfName, + t_cm_instance_handle *instHandle) { + t_component_instance *instance; + t_uint32 k; + + for (k=0; k<ComponentTable.idxMax; k++) { + if ((instance = componentEntry(k)) == NULL) + continue; + if(panicReason == PARAM_FIFO_OVERFLOW || + panicReason == EVENT_FIFO_OVERFLOW) { + // Panic has been generated by binding component, search the client who has call it + // and return the client handle (not the BC one). + int i; + + if(instance->thisAddress == panicThis && panicThis == 0) { + *itfName = "Internal NMF service"; + *instHandle = ENTRY2HANDLE(instance, k); + return instance; + } + + for(i = 0; i < instance->Template->requireNumber; i++) { + int nb = instance->Template->requires[i].collectionSize, j; + for(j = 0; j < nb; j++) { + if(instance->interfaceReferences[i][j].instance != NULL && + instance->interfaceReferences[i][j].instance != (t_component_instance *)NMF_HOST_COMPONENT && + instance->interfaceReferences[i][j].instance != (t_component_instance *)NMF_VOID_COMPONENT && + instance->interfaceReferences[i][j].instance->thisAddress == panicThis) + { + *itfName = instance->Template->requires[i].name; + *instHandle = ENTRY2HANDLE(instance, k); + return instance; + } + } + } + } else { + // The component which has generated the panic is the good one. + + if(instance->thisAddress == panicThis) { + *itfName = "?"; + *instHandle = ENTRY2HANDLE(instance, k); + return instance; + } + } + } + + *itfName = "?"; + *instHandle = 0; + return 0; +} + +PUBLIC EXPORT_SHARED t_cm_error CM_ReadMPCString( + t_nmf_core_id coreId, + t_uint32 dspAddress, + char * buffer, + t_uint32 bufferSize) { + + while(--bufferSize > 0) + { + char ch = cm_DSP_ReadXRamWord(coreId, dspAddress++); + if(ch == 0) + break; + + *buffer++ = ch; + }; + + *buffer = 0; + + // Reset panicReason + cm_writeAttribute(cm_EEM_getExecutiveEngine(coreId)->instance, + "rtos/commonpart/serviceReason", MPC_SERVICE_NONE); + + return CM_OK; +} + +/****************/ +/* Generic part */ +/****************/ +PUBLIC EXPORT_SHARED t_cm_error CM_getServiceDescription( + t_nmf_core_id coreId, + t_cm_service_type *srcType, + t_cm_service_description *srcDescr) +{ + t_uint32 serviceReason; + t_component_instance *ee; + + // Acknowledge interrupt (do it before resetting panicReason) + cm_DSP_AcknowledgeDspIrq(coreId, DSP2ARM_IRQ_1); + + ee = cm_EEM_getExecutiveEngine(coreId)->instance; + + // Read panicReason + serviceReason = cm_readAttributeNoError(ee, "rtos/commonpart/serviceReason"); + if(serviceReason == MPC_SERVICE_PRINT) + { + *srcType = CM_MPC_SERVICE_PRINT; + + srcDescr->u.print.dspAddress = cm_readAttributeNoError(ee, "rtos/commonpart/serviceInfo0"); + srcDescr->u.print.value1 = cm_readAttributeNoError(ee, "rtos/commonpart/serviceInfo1"); + srcDescr->u.print.value2 = cm_readAttributeNoError(ee, "rtos/commonpart/serviceInfo2"); + } + else if(serviceReason == MPC_SERVICE_TRACE) + { + *srcType = CM_MPC_SERVICE_TRACE; + } + else if(serviceReason != MPC_SERVICE_NONE) + { + t_uint32 panicThis; + t_dup_char itfName; + t_component_instance *instance; + + *srcType = CM_MPC_SERVICE_PANIC; + srcDescr->u.panic.panicReason = (t_panic_reason)serviceReason; + srcDescr->u.panic.panicSource = MPC_EE; + srcDescr->u.panic.info.mpc.coreid = coreId; + + // Read panicThis + panicThis = cm_readAttributeNoError(ee, "rtos/commonpart/serviceInfo0"); + + instance = getCorrespondingInstance(srcDescr->u.panic.panicReason, panicThis, &itfName, &srcDescr->u.panic.info.mpc.faultingComponent); + + LOG_INTERNAL(0, "Error: Panic(%s, %s), This=%x", cm_getDspName(coreId), + reason_descrs[srcDescr->u.panic.panicReason].name, (void*)panicThis, 0, 0, 0); + + if(reason_descrs[srcDescr->u.panic.panicReason].interface != 0) + { + LOG_INTERNAL(0, ", interface=%s", itfName, 0, 0, 0, 0, 0); + } + + if(reason_descrs[srcDescr->u.panic.panicReason].info1 != 0) + { + // Info 1 + srcDescr->u.panic.info.mpc.panicInfo1 = cm_readAttributeNoError(ee, "rtos/commonpart/serviceInfo1"); + + LOG_INTERNAL(0, ", Info=%x", srcDescr->u.panic.info.mpc.panicInfo1, 0, 0, 0, 0, 0); + } + + if(reason_descrs[srcDescr->u.panic.panicReason].PC != 0) + { + t_uint32 DspAddress = 0xFFFFFFFF; + t_uint32 DspSize = 0x0; + + // PC need to be read in rtos/commonpart/serviceInfo1 + srcDescr->u.panic.info.mpc.panicInfo1 = cm_readAttributeNoError(ee, "rtos/commonpart/serviceInfo1"); + + if(instance != 0) + { + cm_DSP_GetDspAddress(instance->memories[instance->Template->codeMemory->id], &DspAddress); + cm_DSP_GetDspMemoryHandleSize(instance->memories[instance->Template->codeMemory->id], &DspSize); + } + + if(DspAddress <= srcDescr->u.panic.info.mpc.panicInfo1 && + srcDescr->u.panic.info.mpc.panicInfo1 < (DspAddress + DspSize)) + LOG_INTERNAL(0, ", PC:off=%x <abs=%x>", + srcDescr->u.panic.info.mpc.panicInfo1 - DspAddress, + srcDescr->u.panic.info.mpc.panicInfo1, 0, 0, 0, 0); + else + LOG_INTERNAL(0, ", PC:<abs=%x>", srcDescr->u.panic.info.mpc.panicInfo1, 0, 0, 0, 0, 0); + } + + if(reason_descrs[srcDescr->u.panic.panicReason].SP != 0) + { + srcDescr->u.panic.info.mpc.panicInfo2 = cm_readAttributeNoError(ee, "rtos/commonpart/serviceInfo2"); + + LOG_INTERNAL(0, ", SP=%x", srcDescr->u.panic.info.mpc.panicInfo2, 0, 0, 0, 0, 0); + } + + LOG_INTERNAL(0, "\n", 0, 0, 0, 0, 0, 0); + + if(instance != 0) + { + LOG_INTERNAL(0, "Error: Component=%s<%s>\n", + instance->pathname, instance->Template->name, 0, 0, 0, 0); + } + + // We don't set rtos/commonpart/serviceReason = MPC_SERVICE_NONE, since we don't want the + // MMDSP to continue execution, and we put in in Panic state + cm_DSP_SetStatePanic(coreId); + } + else + { + *srcType = CM_MPC_SERVICE_NONE; + } + + return CM_OK; +} diff --git a/drivers/staging/nmf-cm/cm/engine/trace/src/trace.c b/drivers/staging/nmf-cm/cm/engine/trace/src/trace.c new file mode 100644 index 00000000000..e27d3284ed2 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/trace/src/trace.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +#include "../inc/trace.h" +#include "../inc/xtitrace.h" +#include <inc/nmf-tracedescription.h> +#include <inc/nmf-limits.h> +#include <cm/engine/utils/inc/string.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> + +t_bool cm_trace_enabled = FALSE; + +/* + * STM message dump + */ +#define HEADER(t, s) ((t) | (s << 16)) + +static void writeN(struct t_nmfTraceChannelHeader* header) +{ + t_uint64* data = (t_uint64*)header; + t_uint64 *end = (t_uint64*)(((unsigned int)data) + header->traceSize - sizeof(t_uint64)); + + while(data < end) + { + OSAL_Write64(CM_CHANNEL, 0, *data++); + } + + OSAL_Write64(CM_CHANNEL, 1, *data); +} + +void cm_TRC_Dump(void) +{ + t_uint32 i; + + cm_TRC_traceReset(); + + for (i=0; i<ComponentTable.idxMax; i++) + { + if (componentEntry(i) != NULL) + cm_TRC_traceLoadMap(TRACE_COMPONENT_COMMAND_ADD, componentEntry(i)); + } +} + +void cm_TRC_traceReset(void) +{ + if(cm_trace_enabled) + { + struct t_nmfTraceReset trace; + + trace.header.v = HEADER(TRACE_TYPE_RESET, sizeof(trace)); + + trace.minorVersion = TRACE_MINOR_VERSION; + trace.majorVersion = TRACE_MAJOR_VERSION; + + writeN((struct t_nmfTraceChannelHeader*)&trace); + } +} + +void cm_TRC_traceLoadMap( + t_nmfTraceComponentCommandDescription command, + const t_component_instance* component) +{ + if(cm_trace_enabled) + { + struct t_nmfTraceComponent trace; + + /* + * Generate instantiate trace + */ + trace.header.v = HEADER(TRACE_TYPE_COMPONENT, sizeof(trace)); + + trace.command = (t_uint16)command; + trace.domainId = (t_uint16)component->Template->dspId + 1; + trace.componentContext = (t_uint32)component->thisAddress; + trace.componentUserContext = (t_uint32)component; + cm_StringCopy((char*)trace.componentLocalName, component->pathname, MAX_COMPONENT_NAME_LENGTH); + cm_StringCopy((char*)trace.componentTemplateName, component->Template->name, MAX_TEMPLATE_NAME_LENGTH); + + writeN((struct t_nmfTraceChannelHeader*)&trace); + + if(command == TRACE_COMPONENT_COMMAND_ADD) + { + struct t_nmfTraceMethod tracemethod; + int i, j, k; + + /* + * Generate method trace + */ + tracemethod.header.v = HEADER(TRACE_TYPE_METHOD, sizeof(tracemethod)); + + tracemethod.domainId = (t_uint16)component->Template->dspId + 1; + tracemethod.componentContext = (t_uint32)component->thisAddress; + + for(i = 0; i < component->Template->provideNumber; i++) + { + t_interface_provide* provide = &component->Template->provides[i]; + t_interface_provide_loaded* provideLoaded = &component->Template->providesLoaded[i]; + + for(j = 0; j < provide->collectionSize; j++) + { + for(k = 0; k < provide->interface->methodNumber; k++) + { + tracemethod.methodId = provideLoaded->indexesLoaded[j][k].methodAddresses; + + cm_StringCopy((char*)tracemethod.methodName, provide->interface->methodNames[k], MAX_INTERFACE_METHOD_NAME_LENGTH); + + writeN((struct t_nmfTraceChannelHeader*)&tracemethod); + } + } + } + } + } +} + +void cm_TRC_traceBinding( + t_nmfTraceBindCommandDescription command, + const t_component_instance* clientComponent, const t_component_instance* serverComponent, + const char *requiredItfName, const char *providedItfName) +{ + if(cm_trace_enabled) + { + struct t_nmfTraceBind trace; + + trace.header.v = HEADER(TRACE_TYPE_BIND, sizeof(trace)); + + trace.command = (t_uint16)command; + + if(clientComponent == ARM_TRACE_COMPONENT) // ARM + { + trace.clientDomainId = 0x1; + trace.clientComponentContext = 0x0; + } + else + { + trace.clientDomainId = (t_uint16)clientComponent->Template->dspId + 1; + trace.clientComponentContext = (t_uint32)clientComponent->thisAddress; + } + if(requiredItfName != NULL) + cm_StringCopy((char*)trace.requiredItfName, requiredItfName, MAX_INTERFACE_NAME_LENGTH); + else + trace.requiredItfName[0] = 0; + + if(serverComponent == NULL) + { // Unbind or VOID + trace.serverDomainId = 0; + trace.serverComponentContext = 0x0; + } + else if(serverComponent == ARM_TRACE_COMPONENT) + { // ARM + trace.serverDomainId = 0x1; + trace.serverComponentContext = 0x0; + } + else + { + trace.serverDomainId = (t_uint16)serverComponent->Template->dspId + 1; + trace.serverComponentContext = (t_uint32)serverComponent->thisAddress; + } + if(providedItfName != NULL) + cm_StringCopy((char*)trace.providedItfName, providedItfName, MAX_INTERFACE_NAME_LENGTH); + else + trace.providedItfName[0] = 0; + + writeN((struct t_nmfTraceChannelHeader*)&trace); + } +} + +void cm_TRC_traceCommunication( + t_nmfTraceCommunicationCommandDescription command, + t_nmf_core_id coreId, + t_nmf_core_id remoteCoreId) +{ + if(cm_trace_enabled) + { + struct t_nmfTraceCommunication trace; + + trace.header.v = HEADER(TRACE_TYPE_COMMUNICATION, sizeof(trace)); + + trace.command = (t_uint16)command; + trace.domainId = (t_uint16)coreId + 1; + trace.remoteDomainId = (t_uint16)remoteCoreId + 1; + + writeN((struct t_nmfTraceChannelHeader*)&trace); + } +} + +void cm_TRC_traceMemAlloc(t_nmfTraceAllocatorCommandDescription command, t_uint8 allocId, t_uint32 memorySize, const char *allocname) +{ + if(cm_trace_enabled) + { + struct t_nmfTraceAllocator trace; + + trace.header.v = HEADER(TRACE_TYPE_ALLOCATOR, sizeof(trace)); + + trace.command = (t_uint16)command; + trace.allocId = (t_uint16)allocId; + trace.size = memorySize; + cm_StringCopy((char*)trace.name, allocname, sizeof(trace.name)); + + writeN((struct t_nmfTraceChannelHeader*)&trace); + } +} + +void cm_TRC_traceMem(t_nmfTraceAllocCommandDescription command, t_uint8 allocId, t_uint32 startAddress, t_uint32 memorySize) +{ + if(cm_trace_enabled) + { + struct t_nmfTraceAlloc trace; + + trace.header.v = HEADER(TRACE_TYPE_ALLOC, sizeof(trace)); + + trace.command = (t_uint16)command; + trace.allocId = (t_uint16)allocId; + trace.offset = startAddress; + trace.size = memorySize; + + writeN((struct t_nmfTraceChannelHeader*)&trace); + } +} + diff --git a/drivers/staging/nmf-cm/cm/engine/utils/inc/convert.h b/drivers/staging/nmf-cm/cm/engine/utils/inc/convert.h new file mode 100644 index 00000000000..d6912e58687 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/utils/inc/convert.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Conversion utility methods. + */ +#ifndef H_CM_CONVERTS_MEM +#define H_CM_CONVERTS_MEM + +#include <share/inc/nmf.h> + +/* + * Utils convert methods + */ +const char* cm_getDspName(t_nmf_core_id dsp); + + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/utils/inc/mem.h b/drivers/staging/nmf-cm/cm/engine/utils/inc/mem.h new file mode 100644 index 00000000000..c950a94023d --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/utils/inc/mem.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Memory manipulation. + */ +#ifndef H_CM_UTILS_MEM +#define H_CM_UTILS_MEM + +/* + * Utils libc methods + */ +void cm_MemCopy(void* dest, const void *src, int count); +void cm_MemSet(void *str, int c, int count); + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/utils/inc/string.h b/drivers/staging/nmf-cm/cm/engine/utils/inc/string.h new file mode 100644 index 00000000000..d2b7c0b0823 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/utils/inc/string.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief String manipulation. + */ +#ifndef H_CM_UTILS_STRING +#define H_CM_UTILS_STRING + +#include <cm/engine/memory/inc/memory.h> + +#define MAX_INTERNAL_STRING_LENGTH 2048 + +typedef const char *t_dup_char; + +t_dup_char cm_StringGet(const char* str); +t_dup_char cm_StringReference(t_dup_char str); +t_dup_char cm_StringDuplicate(const char* orig); +void cm_StringRelease(t_dup_char orig); + +/* + * Utils libc methods + */ +void cm_StringCopy(char* dest, const char* src, int count); +int cm_StringCompare(const char* str1, const char* str2, int count); +int cm_StringLength(const char * str, int count); +void cm_StringConcatenate(char* dest, const char* src, int count); +char* cm_StringSearch(const char* str, int c); + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/utils/inc/swap.h b/drivers/staging/nmf-cm/cm/engine/utils/inc/swap.h new file mode 100644 index 00000000000..e4f5acb3010 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/utils/inc/swap.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Swap integer manipulation. + */ +#ifndef H_CM_UTILS_SWAP +#define H_CM_UTILS_SWAP + +#include <cm/inc/cm_type.h> + +/* + * Swap methods + */ +t_uint16 swap16(t_uint16 x); +t_uint32 swap32(t_uint32 x); +t_uint64 swap64(t_uint64 x); +t_uint32 noswap32(t_uint32 x); + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/utils/inc/table.h b/drivers/staging/nmf-cm/cm/engine/utils/inc/table.h new file mode 100644 index 00000000000..9d9828a81f6 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/utils/inc/table.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/*! + * \internal + * \brief Dynamic table manipulation. + */ +#ifndef H_CM_UTILS_TABLE +#define H_CM_UTILS_TABLE + +#include <cm/inc/cm_type.h> + +/* + This implement a (generic) dynamic table (the size is dynamic) + to register some pointers of a given kind of elements + + It also allows to compute/convert each kernel pointer registered in the + table to a user handler, that can be checked. + + The "user" handler is composed by the index in this table + (the low INDEX_SHIFT bits) and the low bits of the "local" pointer + shifted by INDEX_SHIFT are stored in the high bits: + + handle bits: 31 ................................ 12 11 ...... 0 + | lower bits of of the local pointer | index | + + This allows a straight translation from a user handle to a local pointer + + a strong check to validate the value of a user handle. + The reverse translation from pointer to a user handle is + slower as it requires an explicit search in the list. + */ + + +/* INDEX_SHIFT determines the index size and thus the max index */ +#define INDEX_SHIFT 12 +#define INDEX_MAX (1UL << INDEX_SHIFT) +#define INDEX_MASK (INDEX_MAX-1) +#define ENTRY2HANDLE(pointer, index) (((unsigned int)pointer << INDEX_SHIFT) | index) +#define TABLE_DEF_SIZE 0x1000 + +typedef struct { + t_uint32 idxNb; /**< number of entries used */ + t_uint32 idxCur; /**< current index: point to next supposed + free entry: used to look for the next + free entry */ + t_uint32 idxMax; /**< index max currently allowed */ + void **entries; /**< table itself */ +} t_nmf_table; + +t_cm_error cm_initTable(t_nmf_table* table); +void cm_destroyTable(t_nmf_table* table); +t_uint32 cm_addEntry(t_nmf_table *table, void *entry); +void cm_delEntry(t_nmf_table *table, t_uint32 idx); +void *cm_lookupEntry(const t_nmf_table *table, const t_uint32 hdl); +t_uint32 cm_lookupHandle(const t_nmf_table *table, const void *entry); + +#endif diff --git a/drivers/staging/nmf-cm/cm/engine/utils/src/convert.c b/drivers/staging/nmf-cm/cm/engine/utils/src/convert.c new file mode 100644 index 00000000000..ad6e097bfe6 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/utils/src/convert.c @@ -0,0 +1,20 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + */ +#include <cm/engine/utils/inc/convert.h> + +const char* dspNames[NB_CORE_IDS] = { + "ARM", + "SVA", + "SIA" +}; + + +const char* cm_getDspName(t_nmf_core_id dsp) { + return dspNames[dsp]; +} diff --git a/drivers/staging/nmf-cm/cm/engine/utils/src/mem.c b/drivers/staging/nmf-cm/cm/engine/utils/src/mem.c new file mode 100644 index 00000000000..130a044bbf8 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/utils/src/mem.c @@ -0,0 +1,27 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + */ +#include <cm/engine/utils/inc/mem.h> + + +/* + * Methods + */ +void cm_MemCopy(void* dest, const void *src, int count) { + char *tmp = (char *) dest, *s = (char *) src; + + while (count--) + *tmp++ = *s++; +} + +void cm_MemSet(void *str, int c, int count) { + char *tmp = (char *)str; + + while (count--) + *tmp++ = c; +} diff --git a/drivers/staging/nmf-cm/cm/engine/utils/src/string.c b/drivers/staging/nmf-cm/cm/engine/utils/src/string.c new file mode 100644 index 00000000000..89058d5825a --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/utils/src/string.c @@ -0,0 +1,231 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + * Shared string manipulation. + * TODO This is a list today, must be a hash later !!!!! + */ +#include <cm/engine/utils/inc/string.h> +#include <cm/engine/trace/inc/trace.h> + +#include <cm/engine/memory/inc/memory.h> +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> + +#undef NHASH +#define NHASH 257 //Use a prime number! +#define MULT 17 + +/* + * Data + */ +struct t_linkedstring +{ + struct t_linkedstring *next; + int referencer; + char string[1]; +}; + +static struct t_linkedstring *list[NHASH]; + +#undef myoffsetof +#define myoffsetof(st, m) \ + ((int) ( (char *)&((st *)(0))->m - (char *)0 )) + +unsigned int hash(const char *str) +{ + unsigned int h = 0; + for(; *str; str++) + h = MULT * h + *str; + return h % NHASH; +} +/* + * Methods + */ +PRIVATE struct t_linkedstring *lookupString( + const char* str, + struct t_linkedstring *first) +{ + while(first != 0) + { + if(cm_StringCompare(str, first->string, MAX_INTERNAL_STRING_LENGTH) == 0) + break; + first = first->next; + } + + return first; +} + +t_dup_char cm_StringGet(const char* str) +{ + struct t_linkedstring *entry; + + entry = lookupString(str, list[hash(str)]); + CM_ASSERT(entry != 0); + + return (t_dup_char)entry->string; +} + +t_dup_char cm_StringReference(t_dup_char str) +{ + struct t_linkedstring* entry = (struct t_linkedstring*)((t_uint32)str - myoffsetof(struct t_linkedstring, string)); + + // One more referencer + entry->referencer++; + + return (t_dup_char)entry->string; +} + +t_dup_char cm_StringDuplicate(const char* str) +{ + struct t_linkedstring *entry; + unsigned int h; + + h = hash(str); + entry = lookupString(str, list[h]); + if(entry != 0) + { + // One more referencer + entry->referencer++; + } + else + { + // Allocate new entry + entry = (struct t_linkedstring *)OSAL_Alloc(sizeof(struct t_linkedstring)-1 + cm_StringLength(str, MAX_INTERNAL_STRING_LENGTH)+1); + if(entry == NULL) + return NULL; + + entry->referencer = 1; + cm_StringCopy(entry->string, str, MAX_INTERNAL_STRING_LENGTH); + + // Link it in list + entry->next = list[h]; + list[h] = entry; + } + + return (t_dup_char)entry->string; +} + +void cm_StringRelease(t_dup_char str) +{ + if(str != NULL) + { + struct t_linkedstring* entry = (struct t_linkedstring*)((t_uint32)str - myoffsetof(struct t_linkedstring, string)); + + // One less referencer + entry->referencer--; + + if(entry->referencer == 0) + { + int h = hash(entry->string); + + if(list[h] == entry) // This first first one + { + list[h] = entry->next; + } + else + { + struct t_linkedstring *tmp = list[h]; + + // Here we assume that entry is in the list + while(/*tmp != NULL && */tmp->next != entry) + tmp = tmp->next; + + tmp->next = entry->next; + } + OSAL_Free(entry); + } + } +} + +#if 0 +void checkString() +{ + struct t_linkedstring *tmp = list; + + while(tmp != 0) + { + printf(" stay %s %d\n", tmp->string, tmp->referencer); + tmp = tmp->next; + } +} +#endif + +/* + * LibC method + */ +void cm_StringCopy(char* dest, const char *src, int count) +{ + while (count-- && (*dest++ = *src++) != '\0') + /* nothing */ + ; +} +#define DETECTNULL(X) (((X) - 0x01010101) & ~(X) & 0x80808080) + +int cm_StringCompare(const char* str1, const char* str2, int count) +{ + /* If s1 and s2 are word-aligned, compare them a word at a time. */ + if ((((int)str1 & 3) | ((int)str2 & 3)) == 0) + { + unsigned int *a1 = (unsigned int*)str1; + unsigned int *a2 = (unsigned int*)str2; + + while (count >= sizeof (unsigned int) && *a1 == *a2) + { + count -= sizeof (unsigned int); + + /* If we've run out of bytes or hit a null, return zero since we already know *a1 == *a2. */ + if (count == 0 || DETECTNULL (*a1)) + return 0; + + a1++; + a2++; + } + + /* A difference was detected in last few bytes of s1, so search bytewise */ + str1 = (char*)a1; + str2 = (char*)a2; + } + + while (count-- > 0 && *str1 == *str2) + { + /* If we've run out of bytes or hit a null, return zero + since we already know *s1 == *s2. */ + if (count == 0 || *str1 == '\0') + return 0; + str1++; + str2++; + } + + return (*(unsigned char *) str1) - (*(unsigned char *) str2); +} + +int cm_StringLength(const char * str, int count) +{ + const char *sc; + + for (sc = str; count-- && *sc != '\0'; ++sc) + /* nothing */ + ; + return sc - str; +} + +void cm_StringConcatenate(char* dest, const char* src, int count) +{ + while ((*dest) != '\0') + { + dest++; + count--; + } + cm_StringCopy(dest, src, count); +} + +char* cm_StringSearch(const char* str, int c) +{ + for(; *str != (char) c; ++str) + if (*str == '\0') + return 0; + return (char *) str; +} diff --git a/drivers/staging/nmf-cm/cm/engine/utils/src/swap.c b/drivers/staging/nmf-cm/cm/engine/utils/src/swap.c new file mode 100644 index 00000000000..e3e2d536144 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/utils/src/swap.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + */ +#include <cm/engine/utils/inc/swap.h> + + +/* + * Methods + */ +t_uint16 swap16(t_uint16 x) +{ + return ((x >> 8) | + ((x << 8) & 0xff00U)); +} + +#ifdef LINUX + +#if defined(__STN_8815) /* __STN_8815 -> ARMv5*/ +t_uint32 swap32(t_uint32 x) +{ + asm volatile ( + "EOR r1, r0, r0, ROR #16 \n\t" + "BIC r1, r1, #0xFF0000 \n\t" + "MOV r0, r0, ROR #8 \n\t" + "EOR r0, r0, r1, LSR #8" + : : : "r3" ); + + return x; +} + +t_uint64 swap64(t_uint64 x) +{ + asm volatile ( + "MOV r2, r1 \n\t" + " \n\t" + "EOR r3, r0, r0, ROR #16 \n\t" + "BIC r3, r3, #0xFF0000 \n\t" + "MOV r0, r0, ROR #8 \n\t" + "EOR r1, r0, r3, LSR #8 \n\t" + " \n\t" + "EOR r3, r2, r2, ROR #16 \n\t" + "BIC r3, r3, #0xFF0000 \n\t" + "MOV r2, r2, ROR #8 \n\t" + "EOR r0, r2, r3, LSR #8" + : : : "r3", "r2" ); + + return x; +} +#else /* -> ARMv6 or later */ + +t_uint32 swap32(t_uint32 x) +{ + asm volatile ( + "REV %0, %0" + : "+r"(x) : ); + + return x; +} + +t_uint64 swap64(t_uint64 x) +{ + asm volatile ( + "REV r2, %Q0 \n\t" + "REV %Q0, %R0 \n\t" + "MOV %R0, r2" + : "+&r" (x) : : "r2" ); + + return x; +} + +#endif + +#else /* Symbian, Think -> We assume ARMCC */ + +#if defined(__thumb__) + +t_uint32 swap32(t_uint32 x) +{ + return ((x >> 24) | + ((x >> 8) & 0xff00U) | + ((x << 8) & 0xff0000U) | + ((x << 24) & 0xff000000U)); +} + +t_uint64 swap64(t_uint64 x) +{ + return ((x >> 56) | + ((x >> 40) & 0xff00UL) | + ((x >> 24) & 0xff0000UL) | + ((x >> 8) & 0xff000000UL) | + ((x << 8) & 0xff00000000ULL) | + ((x << 24) & 0xff0000000000ULL) | + ((x << 40) & 0xff000000000000ULL) | + ((x << 56))); +} + +#elif (__TARGET_ARCH_ARM < 6) + +__asm t_uint32 swap32(t_uint32 x) +{ + EOR r1, r0, r0, ROR #16 + BIC r1, r1, #0xFF0000 + MOV r0, r0, ROR #8 + EOR r0, r0, r1, LSR #8 + + BX lr +} + +__asm t_uint64 swap64(t_uint64 x) +{ + MOV r2, r1 + + EOR r3, r0, r0, ROR #16 // Swap low (r0) and store it in high (r1) + BIC r3, r3, #0xFF0000 + MOV r0, r0, ROR #8 + EOR r1, r0, r3, LSR #8 + + EOR r3, r2, r2, ROR #16 // Swap high (r2 = ex r1) and store it in low (r0) + BIC r3, r3, #0xFF0000 + MOV r2, r2, ROR #8 + EOR r0, r2, r3, LSR #8 + + BX lr +} + +#else /* -> ARMv6 or later */ + +__asm t_uint32 swap32(t_uint32 x) +{ + REV r0, r0 + + BX lr +} + +__asm t_uint64 swap64(t_uint64 x) +{ + REV r2, r0 + REV r0, r1 + MOV r1, r2 + + BX lr +} + +#endif + +#endif + +t_uint32 noswap32(t_uint32 x) { + return x; +} + diff --git a/drivers/staging/nmf-cm/cm/engine/utils/src/table.c b/drivers/staging/nmf-cm/cm/engine/utils/src/table.c new file mode 100644 index 00000000000..708396a01b2 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/engine/utils/src/table.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ +/* + * + */ +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> +#include <cm/engine/trace/inc/trace.h> +#include <cm/engine/utils/inc/mem.h> +#include <cm/engine/utils/inc/table.h> + +/* + * Methods + */ +t_cm_error cm_initTable(t_nmf_table* table) +{ + table->idxMax = TABLE_DEF_SIZE / sizeof(table->entries); + + table->entries = OSAL_Alloc_Zero(table->idxMax*sizeof(table->entries)); + + if (table->entries == NULL) { + table->idxMax = 0; + return CM_NO_MORE_MEMORY; + } + + return CM_OK; +} + +void cm_destroyTable(t_nmf_table* table) +{ + if (table->idxNb) { + ERROR("Attempt to free non-empty table !!!\n", 0, 0, 0, 0, 0, 0); + return; + } + OSAL_Free(table->entries); + table->idxMax = 0; +} + +static t_cm_error cm_increaseTable(t_nmf_table* table) +{ + t_uint32 new_max; + void *mem; + + if (table->idxMax == INDEX_MASK) { + ERROR("CM_NO_MORE_MEMORY: Maximum table entries reached\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + new_max = table->idxMax + + TABLE_DEF_SIZE / sizeof(table->entries); + + if (new_max > INDEX_MAX) + new_max = INDEX_MAX; + + mem = OSAL_Alloc(new_max * sizeof(table->entries)); + + if (mem == NULL) { + ERROR("CM_NO_MORE_MEMORY: Unable to allocate memory for a table\n", 0, 0, 0, 0, 0, 0); + return CM_NO_MORE_MEMORY; + } + + cm_MemCopy(mem, table->entries, + table->idxMax*sizeof(table->entries)); + cm_MemSet((void *)((t_uint32) mem + table->idxMax*sizeof(*table->entries)), 0, + (new_max-table->idxMax) * sizeof(*table->entries)); + + OSAL_Free(table->entries); + table->entries = mem; + table->idxMax = new_max; + + return CM_OK; +} + +/** cm_addEntry - Add an local pointer to an element to the list + * + * 1. Increase the size of the list if it's full + * 2. Search an empty entry + * 3. Add the element to the list + * 4. Compute and return the "user handle" + */ +t_uint32 cm_addEntry(t_nmf_table *table, void *entry) +{ + unsigned int i; + t_uint32 hdl = 0; + + if (table->idxNb == table->idxMax) + cm_increaseTable(table); + + for (i = table->idxCur; + table->entries[i] != 0 && i != (table->idxCur-1); + i = (i+1)%table->idxMax); + + if (table->entries[i] == 0) { + table->entries[i] = entry; + table->idxCur = (i+1) % table->idxMax; + table->idxNb++; + hdl = ENTRY2HANDLE(entry, i); + } else + ERROR("No free entry found in table\n", 0, 0, 0, 0, 0, 0); + + return hdl; +} + +/** cm_delEntry - remove the given element from the list + * + * 1. Check if the handle is valid + * 2. Search the entry and free it + */ +void cm_delEntry(t_nmf_table *table, t_uint32 idx) +{ + table->entries[idx] = NULL; + table->idxNb--; +} + +/** cm_lookupEntry - search the entry corresponding to + * the user handle. + * + * 1. Check if the handle is valid + * 2. Return a pointer to the element + */ +void *cm_lookupEntry(const t_nmf_table *table, const t_uint32 hdl) +{ + unsigned int idx = hdl & INDEX_MASK; + + if ((idx >= table->idxMax) + || (((unsigned int)table->entries[idx] << INDEX_SHIFT) != (hdl & ~INDEX_MASK))) + return NULL; + else + return table->entries[idx]; +} + +/** cm_lookupHandle - search the handle corresponding + * to the given element + * + * 1. Check if the handler is valid or is a special handler + * 2. Loop in the table to retrieve the entry matching and return its value + */ +t_uint32 cm_lookupHandle(const t_nmf_table *table, const void *entry) +{ + t_uint32 i; + + /* NULL is an invalid value that must be handle separatly + as it'll match all used/free entries value */ + if (entry == NULL) + return 0; + + for (i=0; i < table->idxMax; i++) { + if (table->entries[i] == entry) + return ENTRY2HANDLE(table->entries[i], i); + } + + return 0; +} diff --git a/drivers/staging/nmf-cm/cm/inc/cm.h b/drivers/staging/nmf-cm/cm/inc/cm.h new file mode 100644 index 00000000000..37ccb36a5ee --- /dev/null +++ b/drivers/staging/nmf-cm/cm/inc/cm.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + +#ifndef __INC_CM_H +#define __INC_CM_H + +#include <cm/inc/cm_def.h> + +/********************************************************************************/ +/* Component Manager API prototypes */ +/********************************************************************************/ + +/* + * User level wrapper + */ +#include <cm/proxy/api/cm_proxy.h> + +#endif /* __INC_CM_H */ diff --git a/drivers/staging/nmf-cm/cm/inc/cm_def.h b/drivers/staging/nmf-cm/cm/inc/cm_def.h new file mode 100644 index 00000000000..dc7a1fdad66 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/inc/cm_def.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + +/*! + * \brief Component Manager API. + * + * This file contains the Component Manager API for manipulating components. + * + */ + +#ifndef __INC_CM_DEF_H +#define __INC_CM_DEF_H + +#include <cm/inc/cm_type.h> +#include <inc/nmf-def.h> + +/*! + * \brief Get the version of the NMF CM engine at runtime + * + * This method should be used to query the version number of the + * NMF Component Manager engine at runtime. This is useful when using + * to check if version of the engine linked with application correspond + * to engine used for development. + * + * Such code can be used to check compatibility: \code + t_uint32 nmfversion; + + // Print NMF version + CM_GetVersion(&nmfversion); + LOG("NMF Version %d-%d-%d\n", + VERSION_MAJOR(nmfversion), + VERSION_MINOR(nmfversion), + VERSION_PATCH(nmfversion)); + if(NMF_VERSION != nmfversion) { + LOG("Error: Incompatible API version %d != %d\n", NMF_VERSION, nmfversion); + EXIT(); + } + * \endcode + * + * \param[out] version Internal hardcoded version (use \ref VERSION_MAJOR, \ref VERSION_MINOR, \ref VERSION_PATCH macros to decode it). + * + * \ingroup CM + */ +PUBLIC IMPORT_SHARED void CM_GetVersion(t_uint32 *version); + +#endif /* __INC_CM_H */ diff --git a/drivers/staging/nmf-cm/cm/inc/cm_macros.h b/drivers/staging/nmf-cm/cm/inc/cm_macros.h new file mode 100644 index 00000000000..2279c204a20 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/inc/cm_macros.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + +/*! + * \brief Component Manager Macros. + * + * \defgroup CM_MACROS NMF Macros (ANSI C99) + * The Component Manager Macros are provided to ease FromHost interface call and ToHost callback definition. + * \attention <b>These macros are only ANSI C99 compliant</b> (ARM RVCT 2.x/3.x, GNU gcc 4.x, ...) + * \ingroup CM_USER_API + */ + +#ifndef __INC_CM_MACROS_H +#define __INC_CM_MACROS_H + +/* + * The next macros are supported only with C Ansi 99, so.... + */ + +/* + * The Symbian environment dependency, computation which uses an old gnu cpp, + * does not accept "..." parameters. + * However the actual compiler (armcc) does. + * So remove the macro definitions when computing dependencies. + */ +#if ( defined(__CC_ARM) && !defined(__STRICT_ANSI__) ) || !defined(__SYMBIAN32__) + +/* + * Only for skilled eyes ;) + * The following macros are used to implement NMFCALL[VOID] and NMFMETH[VOID] macros in an elegant way + */ +#define WITH_PARAM(...) __VA_ARGS__) +#define WITH_NOPARAM(...) ) + +/*! + * \brief Macro to ease Host to Dsp interface calling + * + * \attention <b>This macro is only ANSI C99 compliant</b> + * + * The <i>NMFCALL</i> macro can be used to call one method of any previously FromHost bounded interface.\n + * From Host side, today, we have no way to mask the multi-instance handling, so + * this macro is provided to ease FromHost interface calling and to avoid any mistake into the THIS parameter passing. + * + * So, any fromHost interface method call like: \code + * itf.method(itf.THIS, param1, param2, ...); + * \endcode + * can be replaced by: \code + * NMFCALL(itf, method)(param1, param2, ...); + * \endcode + * + * \warning Don't forget to use NMFCALLVOID macro when declaring a FromHost interface method having none application parameter, + * else it will lead to erroneous C code expansion + * \see NMFCALLVOID + * \hideinitializer + * \ingroup CM_MACROS + */ +#define NMFCALL(itfHandle, itfMethodName) \ + (itfHandle).itfMethodName((itfHandle).THIS, WITH_PARAM + +/*! + * \brief Macro to ease Host to Dsp interface calling (method without any user parameter) + * + * \attention <b>This macro is only ANSI C99 compliant</b> + * + * The <i>NMFCALLVOID</i> macro can be used to call one method (those without any user parameter) of any previously FromHost bounded interface.\n + * From Host side, today, we have no way to mask the multi-instance handling, so + * this macro is provided to ease FromHost interface calling and to avoid any mistake into the THIS parameter passing. + * + * So, any FromHost interface method call without any application parameter like:\code + * itf.method(itf.THIS); + * \endcode + * can be replaced by: \code + * NMFCALLVOID(itf, method)(); + * \endcode + * \see NMFCALL + * \hideinitializer + * \ingroup CM_MACROS + */ +#define NMFCALLVOID(itfHandle, itfMethodName) \ + (itfHandle).itfMethodName((itfHandle).THIS WITH_NOPARAM + +/*! + * \brief Macro to ease Dsp to Host interface method declaration + * + * \attention <b>This macro definition is only ANSI C99 compliant</b> + * + * The <i>NMFMETH</i> macro can be used to ease the ToHost interface method declaration.\n + * From Host side, today, we have no way to mask the multi-intance handling, so the user shall handle it by hand + * by passing the "component" context as first parameter of each ToHost interface method through the void *THIS parameter. + * This macro could avoid any mistake into the THIS parameter declaration when never used by the user code. + * + * So, any ToHost interface method declaration like:\code + * void mynotify(void *THIS, mytype1 myparam1, mytype2 myparam2, ...) { + * <body of the interface routine> + * } + * \endcode + * can be replaced by: \code + * void NMFMETH(mynotify)(mytype1 myparam1, mytype2 myparam2, ...) { + * <body of the interface routine> + * } + * \endcode + * + * \warning Don't forget to use NMFMETHVOID macro when declaring a ToHost interface method having none application parameter, + * else it will lead to erroneous C code expansion + * + * \see NMFMETHVOID + * \hideinitializer + * \ingroup CM_MACROS + */ +#define NMFMETH(itfMethodName) \ + itfMethodName(void *THIS, WITH_PARAM + +/*! + * \brief Macro to ease Dsp to Host interface method declaration (method without any user parameter) + * + * \attention <b>This macro is only ANSI C99 compliant</b> + * + * The <i>NMFMETHVOID</i> macro can be used to ease the ToHost interface method (those without any user parameter) declaration.\n + * From Host side, today, we have no way to mask the multi-intance handling, so the user shall handle it by hand + * by passing the "component" context as first parameter of each ToHost interface method through the void *THIS parameter. + * This macro could avoid any mistake into the THIS parameter declaration when never used by the user code. + * + * So, any ToHost interface method declaration having none application parameter like:\code + * void mynotify(void *THIS) { + * <body of the interface routine> + * } + * \endcode + * can be replaced by: \code + * void NMFMETHVOID(mynotify)(void) { + * <body of the interface routine> + * } + * \endcode + * + * \see NMFMETH + * \hideinitializer + * \ingroup CM_MACROS + */ +#define NMFMETHVOID(itfMethodName) \ + itfMethodName(void *THIS WITH_NOPARAM + +#endif /* not Symbian environment or compiling with ARMCC and not in strict ANSI */ + +#endif /* __INC_CM_MACROS_H */ + diff --git a/drivers/staging/nmf-cm/cm/inc/cm_type.h b/drivers/staging/nmf-cm/cm/inc/cm_type.h new file mode 100644 index 00000000000..780e27ca600 --- /dev/null +++ b/drivers/staging/nmf-cm/cm/inc/cm_type.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Component Manager types. + * + * This file contains the Component Manager types. + * + * \defgroup CM CM Type Definitions + * \ingroup CM_USER_API + */ +#ifndef _CM_TYPE_H_ +#define _CM_TYPE_H_ + +#include <share/inc/nmf.h> +#include <share/inc/macros.h> + +#include <nmf/inc/channel_type.h> + +/*! + * @defgroup t_cm_error t_cm_error + * \brief Description of the various errors returned by CM API routines + * @{ + * \ingroup CM + */ +typedef t_nmf_error t_cm_error; //!< Error type returned by CM API routines + +/*********************************************************************************/ +/* WARNING: UPDATE CM_StringError() func each time an error is added/removed !!! */ +/* CM_StringError() is defined twice in: */ +/* nmf_core/host/cm/proxy/common/wrapper/src/wrapper.c */ +/* tests/src/common/nte/src/nte.c */ +/*********************************************************************************/ +#define CM_LAST_ERROR_ID ((t_cm_error)-128) +#define CM_INTEGRATION_ERROR NMF_INTEGRATION_ERROR0 //!< \ref NMF_INTEGRATION_ERROR0 + + /* Communication */ +#define CM_FLUSH_MESSAGE NMF_FLUSH_MESSAGE //!< Message send after call to CM_FlushChannel() +#define CM_BUFFER_OVERFLOW ((t_cm_error)-105) //!< Buffer overflow (interface binding message bigger than buffer) +#define CM_USER_NOT_REGISTERED ((t_cm_error)-104) //!< User not registered +#define CM_NO_MESSAGE NMF_NO_MESSAGE //!< \ref NMF_NO_MESSAGE +#define CM_PARAM_FIFO_OVERFLOW ((t_cm_error)-102) //!< Param fifo overflow +#define CM_INTERNAL_FIFO_OVERFLOW ((t_cm_error)-101) //!< Internal services fifo overflow (not returned to user) +#define CM_MPC_NOT_RESPONDING ((t_cm_error)-100) //!< MPC not responding (either crash, interrupt handler too long, internal NMF fifo coms overflow, ...). + + /* ELF & File system */ +#define CM_FS_ERROR ((t_cm_error)-96) //!< FileSystem error +#define CM_NO_SUCH_FILE ((t_cm_error)-95) //!< No such file or directory +#define CM_INVALID_ELF_FILE ((t_cm_error)-94) //!< File isn't a valid MMDSP ELF file +#define CM_NO_SUCH_BASE ((t_cm_error)-93) //!< The memory base doesn't exist + + /* Introspection */ +#define CM_NO_SUCH_ATTRIBUTE NMF_NO_SUCH_ATTRIBUTE //!< \ref NMF_NO_SUCH_ATTRIBUTE +#define CM_NO_SUCH_PROPERTY NMF_NO_SUCH_PROPERTY //!< \ref NMF_NO_SUCH_PROPERTY + + /* Component Life Cycle */ +#define CM_COMPONENT_NOT_STOPPED NMF_COMPONENT_NOT_STOPPED //!< \ref NMF_COMPONENT_NOT_STOPPED +#define CM_COMPONENT_NOT_UNBINDED ((t_cm_error)-79) //!< Component must be fully unbinded before perform operation +#define CM_COMPONENT_NOT_STARTED ((t_cm_error)-78) //!< Component must be started to perform operation +#define CM_COMPONENT_WAIT_RUNNABLE ((t_cm_error)-76) //!< Component need acknowlegdment of life cycle start function before perform operation +#define CM_REQUIRE_INTERFACE_UNBINDED ((t_cm_error)-75) //!< Required component interfaces must be binded before perform operation +#define CM_INVALID_COMPONENT_HANDLE ((t_cm_error)-74) //!< Try to access a component already destroyed + + /* Binder */ +#define CM_NO_SUCH_PROVIDED_INTERFACE NMF_NO_SUCH_PROVIDED_INTERFACE //!< \ref NMF_NO_SUCH_PROVIDED_INTERFACE +#define CM_NO_SUCH_REQUIRED_INTERFACE NMF_NO_SUCH_REQUIRED_INTERFACE //!< \ref NMF_NO_SUCH_REQUIRED_INTERFACE +#define CM_ILLEGAL_BINDING ((t_cm_error)-62) //!< Client and server interface type mismatch +#define CM_ILLEGAL_UNBINDING ((t_cm_error)-61) //!< Try to unbind component with bad binding Factories +#define CM_INTERFACE_ALREADY_BINDED NMF_INTERFACE_ALREADY_BINDED//!< \ref NMF_INTERFACE_ALREADY_BINDED +#define CM_INTERFACE_NOT_BINDED NMF_INTERFACE_NOT_BINDED //!< \ref NMF_INTERFACE_NOT_BINDED + + /* Loader */ +#define CM_BINDING_COMPONENT_NOT_FOUND ((t_cm_error)-48) //!< Binding Component template name don't exist on components repository (should be generated thanks nkitf tool) +#define CM_COMPONENT_NOT_FOUND ((t_cm_error)-47) //!< Component template name doesn't exist on components repository +#define CM_NO_SUCH_SYMBOL ((t_cm_error)-46) //!< Symbol name doesn't exported by the underlying component +#define CM_COMPONENT_EXIST ((t_cm_error)-45) //!< Component name already exists in the component cache + + /* Fifo management related ones */ +#define CM_FIFO_FULL ((t_cm_error)-40) //!< Fifo is full +#define CM_FIFO_EMPTY ((t_cm_error)-39) //!< Fifo is empty +#define CM_UNKNOWN_FIFO_ID ((t_cm_error)-38) //!< Fifo handle doesn't exist + + /* Memory management related ones */ +#define CM_DOMAIN_VIOLATION ((t_cm_error)-33) //!< Domain violation +#define CM_CREATE_ALLOC_ERROR ((t_cm_error)-32) //!< Error during allocator creation +#define CM_UNKNOWN_MEMORY_HANDLE ((t_cm_error)-31) //!< Handle doesn't exists +#define CM_NO_MORE_MEMORY NMF_NO_MORE_MEMORY //!< \ref NMF_NO_MORE_MEMORY +#define CM_BAD_MEMORY_ALIGNMENT ((t_cm_error)-29) //!< Memory alignment wanted is not correct +#define CM_MEMORY_HANDLE_FREED ((t_cm_error)-28) //!< Handle was alread freed +#define CM_INVALID_DOMAIN_DEFINITION ((t_cm_error)-27) //!< Domain to be created is not correctly defined +#define CM_INTERNAL_DOMAIN_OVERFLOW ((t_cm_error)-26) //!< Internal domain descriptor overflow (too many domains) //TODO, juraj, remove this error +#define CM_INVALID_DOMAIN_HANDLE ((t_cm_error)-25) //!< Invalid domain handle +#define CM_ILLEGAL_DOMAIN_OPERATION ((t_cm_error)-21) //!< Operation on a domain is illegal (like destroy of a domain with referenced components) + + /* Media Processor related ones */ +#define CM_MPC_INVALID_CONFIGURATION ((t_cm_error)-24) //!< Media Processor Core invalid configuration +#define CM_MPC_NOT_INITIALIZED ((t_cm_error)-23) //!< Media Processor Core not yet initialized +#define CM_MPC_ALREADY_INITIALIZED ((t_cm_error)-22) //!< Media Processor Core already initialized +//ERROR 21 is defined above, with the domains + + /* Power Mgt related ones */ +#define CM_PWR_NOT_AVAILABLE ((t_cm_error)-16) //!< No modification of the state of the power input + + /* Common errors */ +#define CM_INVALID_DATA ((t_cm_error)-4) //!< Invalid internal data encountered +#define CM_OUT_OF_LIMITS ((t_cm_error)-3) //!< User reach an internal nmf limits of limits.h file +#define CM_INVALID_PARAMETER NMF_INVALID_PARAMETER //!< \ref NMF_INVALID_PARAMETER +#define CM_NOT_YET_IMPLEMENTED ((t_cm_error)-1) //!< CM API not yet implemented +#define CM_OK NMF_OK //!< \ref NMF_OK + +/** @} */ + +/*! + * \brief Definition of a physical memory address + * \ingroup MEMORY + */ +typedef t_uint32 t_cm_physical_address; + +/*! + * \brief Definition of a logical memory address + * \ingroup MEMORY + */ +typedef t_uint32 t_cm_logical_address; + +/*! + * \brief Definition of a system address into a system with MMU + * \ingroup MEMORY + */ +typedef struct { + t_cm_physical_address physical; //!< Physical memory address + t_cm_logical_address logical; //!< Logical memory address +} t_cm_system_address; +#define INVALID_SYSTEM_ADDRESS {(t_cm_physical_address)MASK_ALL32, (t_cm_logical_address)MASK_ALL32} + + +/*! + * \brief Define a type used to manipulate size of various buffers + * \ingroup MEMORY + */ +typedef t_uint32 t_cm_size; + +#endif /* _CM_TYPE_H_ */ + diff --git a/drivers/staging/nmf-cm/cm_debug.c b/drivers/staging/nmf-cm/cm_debug.c new file mode 100644 index 00000000000..ffb067c65d9 --- /dev/null +++ b/drivers/staging/nmf-cm/cm_debug.c @@ -0,0 +1,840 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#include <linux/proc_fs.h> + +#include "osal-kernel.h" +#include "cm_debug.h" + +#ifdef CONFIG_DEBUG_FS +#include <linux/sched.h> + +static struct dentry *cm_dir; /* nmf-cm/ */ +static struct dentry *proc_dir; /* nmf-cm/proc/ */ +static struct dentry *core_dir; /* nmf-cm/dsp/ */ +static struct dentry *domain_dir; /* nmf-cm/domains/ */ + +/* components data managment */ +struct cm_debug_component_cooky { + struct dentry *comp_file; /* entry in nmf-cm/dsp/sxa/components/ */ + struct dentry *proc_link; /* entry in nmf-cm/proc/ */ +}; + +static ssize_t component_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) { + t_component_instance *component = file->f_dentry->d_inode->i_private; + char buf[640]; + int ret=0; + + OSAL_LOCK_API(); + if ((component != NULL) && (component->dbgCooky != NULL)) { + char nb_i[16] = ""; + int i; + + if (component->Template->classe == SINGLETON) + snprintf(nb_i, sizeof(nb_i), " (%d)", + component->Template->numberOfInstance); + + ret = snprintf(buf, sizeof(buf), + "Name:\t\t%s <%s>\n" + "Class:\t\t%s%s\n" + "State:\t\t%s\n" + "Priority:\t%u\n" + "Domain:\t\t%u\n\n" + "Memory : Physical address Logical address" + " DSP address Size\n" + "---------------------------------------------" + "-----------------------------\n", + component->pathname, + component->Template->name, + component->Template->classe == COMPONENT ? + "Component" : + (component->Template->classe == SINGLETON ? + "Singleton" : + (component->Template->classe == FIRMWARE ? + "Firmware" : + "?")), + nb_i, + component->state == STATE_RUNNABLE ? "Runnable" : + (component->state == STATE_STOPPED ? "Sopped" : + "None"), + (unsigned)component->priority, + component->domainId + ); + + for (i=0; i<NUMBER_OF_MMDSP_MEMORY && ret<sizeof(buf); i++) { + if (component->memories[i]) { + t_cm_system_address addr; + t_uint32 dspAddr, dspSize; + cm_DSP_GetHostSystemAddress( + component->memories[i], &addr); + cm_DSP_GetDspAddress( + component->memories[i], &dspAddr); + cm_DSP_GetDspMemoryHandleSize( + component->memories[i], &dspSize); + ret += snprintf( + &buf[ret], sizeof(buf)-ret, + "%-10s: %p-%p %p-%p %p-%p %8lu\n", + MMDSP_getMappingById(i)->memoryName, + (void *)addr.physical, + (void *)addr.physical + + component->memories[i]->size-1, + (void *)addr.logical, + (void *)addr.logical + + component->memories[i]->size-1, + (void *)dspAddr, + (void *)dspAddr + dspSize - 1, + component->memories[i]->size); + } + } + } + + OSAL_UNLOCK_API(); + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations component_fops = { + .read = component_read, +}; + +static void cm_debug_component_create(t_component_instance *component) +{ + char tmp[12+MAX_COMPONENT_NAME_LENGTH]; + struct cm_debug_component_cooky *cooky; + struct mpcConfig *mpc; + mpc = &osalEnv.mpc[COREIDX(component->Template->dspId)]; + + cooky = OSAL_Alloc_Zero(sizeof(*cooky)); + if (cooky == NULL) + return; + + component->dbgCooky = cooky; + sprintf(tmp, "%s-%08x", component->pathname, + (unsigned int)component->instance); + cooky->comp_file = debugfs_create_file(tmp, S_IRUSR|S_IRGRP, + mpc->comp_dir, + component, &component_fops); + if (IS_ERR(cooky->comp_file)) { + if (PTR_ERR(cooky->comp_file) != -ENODEV) + pr_info("CM: Can't create dsp/%s/components/%s" + "debugfs file: %ld\n", + mpc->name, + tmp, + PTR_ERR(cooky->comp_file)); + cooky->comp_file = NULL; + } else { + char target_lnk[40+MAX_COMPONENT_NAME_LENGTH]; + sprintf(target_lnk, "../../../dsp/%s/components/%s-%08x", + mpc->name, + component->pathname, + (unsigned int)component->instance); + + /* Some firmware, like Executive Engine, do not belong + to any process */ + if (domainDesc[component->domainId].client == current->tgid) { + struct list_head* head; + struct cm_process_priv *entry = NULL; + /* Search the entry for the calling process */ + list_for_each(head, &process_list) { + entry = list_entry(head, + struct cm_process_priv, + entry); + if (entry->pid == current->tgid) + break; + } + + if (entry) { + cooky->proc_link = debugfs_create_symlink( + tmp, + entry->comp_dir, + target_lnk); + if (IS_ERR(cooky->proc_link)) { + long err = PTR_ERR(cooky->proc_link); + if (err != -ENODEV) + pr_info("CM: Can't create " + "proc/%d/%s " + "debugfs link: %ld\n", + entry->pid, tmp, err); + cooky->proc_link = NULL; + } + } + } + } +} + +static void cm_debug_component_destroy(t_component_instance *component) +{ + struct cm_debug_component_cooky *cooky = component->dbgCooky; + + if (cooky) { + component->dbgCooky = NULL; + debugfs_remove(cooky->proc_link); + debugfs_remove(cooky->comp_file); + OSAL_Free(cooky); + } +} + +/* domain data managment */ +struct cm_debug_domain_cooky { + struct dentry *domain_file; /* entry in nmf-cm/components/ */ + struct dentry *proc_link; /* entry in nmf-cm/proc/ */ + struct dentry *dsp_link; /* entry in nmf-cm/dsp/sxa/domains */ +}; + +static ssize_t domain_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + t_cm_domain_id id = + (t_cm_domain_id)(long)file->f_dentry->d_inode->i_private; + t_cm_domain_desc *domain = &domainDesc[id]; + + char buf[640]; + int ret=0; + + OSAL_LOCK_API(); + if ((domain->domain.coreId != MASK_ALL8) + && (domain->dbgCooky != NULL)) { + t_cm_allocator_status status; + t_uint32 dOffset; + t_uint32 dSize; + if (domain->domain.coreId != ARM_CORE_ID) { + t_cm_domain_info info; + + cm_DM_GetDomainAbsAdresses(id, &info); + cm_DSP_GetInternalMemoriesInfo(id, ESRAM_CODE, + &dOffset, &dSize); + cm_MM_GetAllocatorStatus( + cm_DSP_GetAllocator(domain->domain.coreId, + ESRAM_CODE), + dOffset, dSize, &status); + ret = snprintf( + buf, sizeof(buf), + "Core:\t%s\n\n" + "Memory : Physical address Logical address" + " Size Free Used\n" + "---------------------------------------------" + "-----------------------------\n" + "ESRAM Code: %08x-%08lx %08x-%08lx\t%8lu %8lu " + "%8lu\n", + osalEnv.mpc[COREIDX(domain->domain.coreId)].name, + (unsigned int)info.esramCode.physical, + domain->domain.esramCode.size ? + info.esramCode.physical + + domain->domain.esramCode.size - 1 : 0, + (unsigned int)info.esramCode.logical, + domain->domain.esramCode.size ? + info.esramCode.logical + + domain->domain.esramCode.size - 1 : 0, + domain->domain.esramCode.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + + cm_DSP_GetInternalMemoriesInfo(id, ESRAM_EXT24, + &dOffset, &dSize); + cm_MM_GetAllocatorStatus( + cm_DSP_GetAllocator(domain->domain.coreId, + ESRAM_EXT24), + dOffset, dSize, &status); + ret += snprintf( + &buf[ret], sizeof(buf)-ret, + "ESRAM Data: %08x-%08lx " + "%08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)info.esramData.physical, + domain->domain.esramData.size ? + info.esramData.physical + + domain->domain.esramData.size - 1 : 0, + (unsigned int)info.esramData.logical, + domain->domain.esramData.size ? + info.esramData.logical + + domain->domain.esramData.size - 1 : 0, + domain->domain.esramData.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + + cm_DSP_GetInternalMemoriesInfo(id, SDRAM_CODE, + &dOffset, &dSize); + cm_MM_GetAllocatorStatus( + cm_DSP_GetAllocator(domain->domain.coreId, + SDRAM_CODE), + dOffset, dSize, &status); + ret += snprintf( + &buf[ret], sizeof(buf)-ret, + "SDRAM Code: %08x-%08lx " + "%08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)info.sdramCode.physical, + domain->domain.sdramCode.size ? + info.sdramCode.physical + + domain->domain.sdramCode.size - 1 : 0, + (unsigned int)info.sdramCode.logical, + domain->domain.sdramCode.size ? + info.sdramCode.logical + + domain->domain.sdramCode.size - 1 : 0, + domain->domain.sdramCode.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + + cm_DSP_GetInternalMemoriesInfo(id, SDRAM_EXT24, + &dOffset, &dSize); + cm_MM_GetAllocatorStatus( + cm_DSP_GetAllocator(domain->domain.coreId, + SDRAM_EXT24), + dOffset, dSize, &status); + ret += snprintf( + &buf[ret], sizeof(buf)-ret, + "SDRAM Data: %08x-%08lx " + "%08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)info.sdramData.physical, + domain->domain.sdramData.size ? + info.sdramData.physical + + domain->domain.sdramData.size - 1 : 0, + (unsigned int)info.sdramData.logical, + domain->domain.sdramData.size ? + info.sdramData.logical + + domain->domain.sdramData.size - 1 : 0, + domain->domain.sdramData.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + } else { + t_cm_system_address addr; + ret = snprintf( + buf, sizeof(buf), + "Core:\tarm\n\n" + "Memory : Physical address Logical " + "address Size Free Used\n" + "---------------------------------------" + "-----------------------------------\n"); + if (domain->domain.esramCode.size && + cm_DSP_GetDspBaseAddress(ARM_CORE_ID, + ESRAM_CODE, + &addr) == CM_OK) { + cm_DSP_GetInternalMemoriesInfo(id, ESRAM_CODE, + &dOffset, + &dSize); + cm_MM_GetAllocatorStatus( + cm_DSP_GetAllocator(ARM_CORE_ID, + ESRAM_CODE), + dOffset, dSize, &status); + ret += snprintf( + &buf[ret], sizeof(buf)-ret, + "ESRAM Code: %08x-%08lx " + "%08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)addr.physical, + addr.physical + + domain->domain.esramCode.size - 1, + (unsigned int)addr.logical, + addr.logical + + domain->domain.esramCode.size - 1, + domain->domain.esramCode.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + } + if (domain->domain.esramData.size && + cm_DSP_GetDspBaseAddress(ARM_CORE_ID, + ESRAM_EXT24, + &addr) == CM_OK) { + cm_DSP_GetInternalMemoriesInfo(id, ESRAM_EXT24, + &dOffset, + &dSize); + cm_MM_GetAllocatorStatus( + cm_DSP_GetAllocator(ARM_CORE_ID, + ESRAM_EXT24), + dOffset, dSize, &status); + ret += snprintf( + &buf[ret], sizeof(buf)-ret, + "ESRAM Data: %08x-%08lx " + "%08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)addr.physical, + addr.physical + + domain->domain.esramData.size - 1, + (unsigned int)addr.logical, + addr.logical + + domain->domain.esramData.size - 1, + domain->domain.esramData.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + } + if (domain->domain.sdramCode.size && + cm_DSP_GetDspBaseAddress(ARM_CORE_ID, + SDRAM_CODE, + &addr) == CM_OK) { + cm_DSP_GetInternalMemoriesInfo(id, SDRAM_CODE, + &dOffset, + &dSize); + cm_MM_GetAllocatorStatus( + cm_DSP_GetAllocator(ARM_CORE_ID, + SDRAM_CODE), + dOffset, dSize, &status); + ret += snprintf( + &buf[ret], sizeof(buf)-ret, + "SDRAM Code: %08x-%08lx %08x-%08lx\t" + "%8lu %8lu %8lu\n", + (unsigned int)addr.physical, + addr.physical + + domain->domain.sdramCode.size - 1, + (unsigned int)addr.logical, + addr.logical + + domain->domain.sdramCode.size - 1, + domain->domain.sdramCode.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + } + if (domain->domain.sdramData.size && + cm_DSP_GetDspBaseAddress(ARM_CORE_ID, + SDRAM_EXT24, + &addr) == CM_OK) { + cm_DSP_GetInternalMemoriesInfo(id, SDRAM_EXT24, + &dOffset, + &dSize); + cm_MM_GetAllocatorStatus( + cm_DSP_GetAllocator(ARM_CORE_ID, + SDRAM_EXT24), + dOffset, dSize, &status); + ret += snprintf( + &buf[ret], sizeof(buf)-ret, + "SDRAM Data: %08x-%08lx %08x-%08lx\t" + "%8lu %8lu %8lu\n", + (unsigned int)addr.physical, + addr.physical + + domain->domain.sdramData.size - 1, + (unsigned int)addr.logical, + addr.logical + + domain->domain.sdramData.size - 1, + domain->domain.sdramData.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + } + } + } + OSAL_UNLOCK_API(); + return simple_read_from_buffer(userbuf, count, ppos, buf, ret);; +} + +static const struct file_operations domain_fops = { + .read = domain_read, +}; + +static void cm_debug_domain_create(t_cm_domain_id id) +{ + char tmp[12]; + struct cm_debug_domain_cooky *cooky; + + cooky = OSAL_Alloc_Zero(sizeof(*cooky)); + if (cooky == NULL) + return; + + domainDesc[id].dbgCooky = cooky; + sprintf(tmp, "%u", id); + cooky->domain_file = debugfs_create_file(tmp, S_IRUSR|S_IRGRP, + domain_dir, + (void *)(long)id, + &domain_fops); + if (IS_ERR(cooky->domain_file)) { + if (PTR_ERR(cooky->domain_file) != -ENODEV) + pr_err("CM: Can't create domains/%s debugfs " + "file: %ld\n", tmp, + PTR_ERR(cooky->domain_file)); + cooky->domain_file = NULL; + } else { + char target_lnk[40]; + sprintf(target_lnk, "../../../domains/%u", id); + + if (domainDesc[id].client != NMF_CORE_CLIENT) { + struct list_head* head; + struct cm_process_priv *entry = NULL; + + /* Search the entry for the target process */ + list_for_each(head, &process_list) { + entry = list_entry(head, + struct cm_process_priv, + entry); + if (entry->pid == domainDesc[id].client) + break; + } + + if (entry) { + cooky->proc_link = debugfs_create_symlink( + tmp, + entry->domain_dir, + target_lnk); + if (IS_ERR(cooky->proc_link)) { + long err = PTR_ERR(cooky->proc_link); + if (err != -ENODEV) + pr_err("CM: Can't create " + "proc/%d/domains/%s " + "debugfs link: %ld\n", + entry->pid, tmp, err); + cooky->proc_link = NULL; + } + } + } + if (domainDesc[id].domain.coreId != ARM_CORE_ID) { + cooky->dsp_link = + debugfs_create_symlink( + tmp, + osalEnv.mpc[COREIDX(domainDesc[id].domain.coreId)].domain_dir, + target_lnk); + if (IS_ERR(cooky->dsp_link)) { + if (PTR_ERR(cooky->dsp_link) != -ENODEV) + pr_err("CM: Can't create dsp/%s/domains/%s " + "debugfs link: %ld\n", + osalEnv.mpc[COREIDX(domainDesc[id].domain.coreId)].name, + tmp, + PTR_ERR(cooky->dsp_link)); + cooky->dsp_link = NULL; + } + } + } +} + +static void cm_debug_domain_destroy(t_cm_domain_id id) +{ + struct cm_debug_domain_cooky *cooky = domainDesc[id].dbgCooky; + if (cooky) { + domainDesc[id].dbgCooky = NULL; + debugfs_remove(cooky->proc_link); + debugfs_remove(cooky->dsp_link); + debugfs_remove(cooky->domain_file); + OSAL_Free(cooky); + } +} + +/* proc directory */ +void cm_debug_proc_init(struct cm_process_priv *entry) +{ + char tmp[PROC_NUMBUF]; + sprintf(tmp, "%d", entry->pid); + entry->dir = debugfs_create_dir(tmp, proc_dir); + if (IS_ERR(entry->dir)) { + if (PTR_ERR(entry->dir) != -ENODEV) + pr_info("CM: Can't create proc/%d debugfs directory: " + "%ld\n", entry->pid, PTR_ERR(entry->dir)); + entry->dir = NULL; + return; + } + entry->comp_dir = debugfs_create_dir("components", entry->dir); + if (IS_ERR(entry->comp_dir)) { + if (PTR_ERR(entry->comp_dir) != -ENODEV) + pr_info("CM: Can't create proc/%d/components debugfs " + "directory: %ld\n", entry->pid, + PTR_ERR(entry->comp_dir)); + entry->comp_dir = NULL; + } + entry->domain_dir = debugfs_create_dir("domains", entry->dir); + if (IS_ERR(entry->domain_dir)) { + if (PTR_ERR(entry->domain_dir) != -ENODEV) + pr_info("CM: Can't create proc/%d/domains debugfs " + "directory: %ld\n", entry->pid, + PTR_ERR(entry->domain_dir)); + entry->domain_dir = NULL; + } +} + +/* DSP meminfo */ +static ssize_t meminfo_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + const t_nmf_core_id id = + *(t_nmf_core_id *)file->f_dentry->d_inode->i_private; + char buf[640]; + int ret=0; + t_cm_allocator_status status; + t_cm_system_address addr; + + OSAL_LOCK_API(); + cm_MM_GetAllocatorStatus(cm_DSP_GetAllocator(id, ESRAM_CODE), + 0, 0, &status); + cm_DSP_GetDspBaseAddress(id, ESRAM_CODE, &addr); + ret = snprintf(buf, sizeof(buf), + "Memory : Physical address Logical address Size " + " Free Used\n" + "-------------------------------------------------------" + "-------------------\n" + "ESRAM Code: %08x-%08lx %08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)addr.physical, + addr.physical + status.global.size - 1, + (unsigned int)addr.logical, + addr.logical + status.global.size - 1, + status.global.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + + cm_MM_GetAllocatorStatus(cm_DSP_GetAllocator(id, ESRAM_EXT24), + 0, 0, &status); + cm_DSP_GetDspBaseAddress(id, ESRAM_EXT24, &addr); + ret += snprintf(&buf[ret], sizeof(buf)-ret, + "ESRAM Data: %08x-%08lx %08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)addr.physical, + addr.physical + status.global.size - 1, + (unsigned int)addr.logical, + addr.logical + status.global.size - 1, + status.global.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + + cm_MM_GetAllocatorStatus(cm_DSP_GetAllocator(id, SDRAM_CODE), + 0, 0, &status); + cm_DSP_GetDspBaseAddress(id, SDRAM_CODE, &addr); + ret += snprintf(&buf[ret], sizeof(buf)-ret, + "SDRAM Code: %08x-%08lx %08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)addr.physical, + addr.physical + status.global.size - 1, + (unsigned int)addr.logical, + addr.logical + status.global.size - 1, + status.global.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + + cm_MM_GetAllocatorStatus(cm_DSP_GetAllocator(id, SDRAM_EXT24), + 0, 0, &status); + cm_DSP_GetDspBaseAddress(id, SDRAM_EXT24, &addr); + ret += snprintf(&buf[ret], sizeof(buf)-ret, + "SDRAM Data: %08x-%08lx %08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)addr.physical, + addr.physical + status.global.size - 1, + (unsigned int)addr.logical, + addr.logical + status.global.size - 1, + status.global.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + + cm_MM_GetAllocatorStatus(cm_DSP_GetAllocator(id, INTERNAL_XRAM24), + 0, 0, &status); + cm_DSP_GetDspBaseAddress(id, INTERNAL_XRAM24, &addr); + ret += snprintf(&buf[ret], sizeof(buf)-ret, + "TCM XRAM : %08x-%08lx %08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)addr.physical, + addr.physical + status.global.size - 1, + (unsigned int)addr.logical, + addr.logical + status.global.size - 1, + status.global.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + + cm_MM_GetAllocatorStatus(cm_DSP_GetAllocator(id, INTERNAL_YRAM24), + 0, 0, &status); + cm_DSP_GetDspBaseAddress(id, INTERNAL_YRAM24, &addr); + ret += snprintf(&buf[ret], sizeof(buf)-ret, + "TCM YRAM : %08x-%08lx %08x-%08lx\t%8lu %8lu %8lu\n", + (unsigned int)addr.physical, + addr.physical + status.global.size - 1, + (unsigned int)addr.logical, + addr.logical + status.global.size - 1, + status.global.size, + status.global.accumulate_free_memory, + status.global.accumulate_used_memory); + + OSAL_UNLOCK_API(); + return simple_read_from_buffer(userbuf, count, ppos, buf, ret);; +} + +static const struct file_operations mem_fops = { + .read = meminfo_read, +}; + +/* ESRAM file operations */ +static int esram_open(struct inode *inode, struct file *file) +{ + int i, err=0; + for (i=0; i<NB_ESRAM; i++) { + if (regulator_enable(osalEnv.esram_regulator[i]) < 0) { + pr_err("CM (%s): can't enable regulator" + "for esram bank %s\n", __func__, + i ? "34" : "12"); + err = -EIO; + break; + } + } + + if (err) { + for (i--; i>=0; i--) + regulator_disable(osalEnv.esram_regulator[i]); + } + + return err; +} + +static int esram_release(struct inode *inode, struct file *file) +{ + int i; + for (i=0; i<NB_ESRAM; i++) + regulator_disable(osalEnv.esram_regulator[i]); + return 0; +} + +static ssize_t esram_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + return simple_read_from_buffer(user_buf, count, ppos, + osalEnv.esram_base, + cfgESRAMSize*ONE_KB); +} + +static const struct file_operations esram_fops = { + .read = esram_read, + .open = esram_open, + .release = esram_release, +}; + + +/* TCM file */ +void cm_debug_create_tcm_file(unsigned mpc_index) +{ + osalEnv.mpc[mpc_index].tcm_file = debugfs_create_blob( + "tcm24", S_IRUSR|S_IRGRP|S_IROTH, + osalEnv.mpc[mpc_index].snapshot_dir, + &osalEnv.mpc[mpc_index].base); + if (IS_ERR(osalEnv.mpc[mpc_index].tcm_file)) { + if (PTR_ERR(osalEnv.mpc[mpc_index].tcm_file) != -ENODEV) + pr_info("CM: Can't create dsp/%s/tcm24 debugfs " + "directory: %ld\n", osalEnv.mpc[mpc_index].name, + PTR_ERR(osalEnv.mpc[mpc_index].tcm_file)); + osalEnv.mpc[mpc_index].tcm_file = NULL; + } +} + +void cm_debug_destroy_tcm_file(unsigned mpc_index) +{ + debugfs_remove(osalEnv.mpc[mpc_index].tcm_file); +} + +/* Global init */ +void cm_debug_init(void) +{ + int i; + + cm_dir = debugfs_create_dir(DEBUGFS_ROOT, NULL); + if (IS_ERR(cm_dir)) { + if (PTR_ERR(cm_dir) != -ENODEV) + pr_info("CM: Can't create root debugfs directory: " + "%ld\n", PTR_ERR(cm_dir)); + cm_dir = NULL; + return; + } + + proc_dir = debugfs_create_dir("proc", cm_dir); + if (IS_ERR(proc_dir)) { + if (PTR_ERR(proc_dir) != -ENODEV) + pr_info("CM: Can't create 'proc' debugfs directory: " + "%ld\n", PTR_ERR(proc_dir)); + proc_dir = NULL; + } + + core_dir = debugfs_create_dir("dsp", cm_dir); + if (IS_ERR(core_dir)) { + if (PTR_ERR(core_dir) != -ENODEV) + pr_info("CM: Can't create 'dsp' debugfs directory: %ld\n", + PTR_ERR(core_dir)); + core_dir = NULL; + } + + domain_dir = debugfs_create_dir("domains", cm_dir); + if (IS_ERR(domain_dir)) { + if (PTR_ERR(domain_dir) != -ENODEV) + pr_info("CM: Can't create 'domains' debugfs directory: " + "%ld\n", + PTR_ERR(domain_dir)); + domain_dir = NULL; + } else { + osal_debug_ops.domain_create = cm_debug_domain_create; + osal_debug_ops.domain_destroy = cm_debug_domain_destroy; + } + + for (i=0; i<NB_MPC; i++) { + osalEnv.mpc[i].dir = debugfs_create_dir(osalEnv.mpc[i].name, + core_dir); + if (IS_ERR(osalEnv.mpc[i].dir)) { + if (PTR_ERR(osalEnv.mpc[i].dir) != -ENODEV) + pr_info("CM: Can't create %s debugfs directory: " + "%ld\n", + osalEnv.mpc[i].name, + PTR_ERR(osalEnv.mpc[i].dir)); + osalEnv.mpc[i].dir = NULL; + } else { + osalEnv.mpc[i].mem_file = + debugfs_create_file("meminfo", S_IRUSR|S_IRGRP, + osalEnv.mpc[i].dir, + (void*)&osalEnv.mpc[i].coreId, + &mem_fops); + if (IS_ERR(osalEnv.mpc[i].mem_file)) { + if (PTR_ERR(osalEnv.mpc[i].mem_file) != -ENODEV) + pr_err("CM: Can't create dsp/%s/meminfo " + "debugfs file: %ld\n", + osalEnv.mpc[i].name, + PTR_ERR(osalEnv.mpc[i].mem_file)); + osalEnv.mpc[i].mem_file = NULL; + } + + osalEnv.mpc[i].comp_dir = debugfs_create_dir( + "components", + osalEnv.mpc[i].dir); + if (IS_ERR(osalEnv.mpc[i].comp_dir)) { + if (PTR_ERR(osalEnv.mpc[i].comp_dir) != -ENODEV) + pr_info("CM: Can't create " + "'dsp/%s/components' debugfs " + "directory: %ld\n", + osalEnv.mpc[i].name, + PTR_ERR(osalEnv.mpc[i].comp_dir)); + osalEnv.mpc[i].comp_dir = NULL; + } + + osalEnv.mpc[i].domain_dir = + debugfs_create_dir("domains", + osalEnv.mpc[i].dir); + if (IS_ERR(osalEnv.mpc[i].domain_dir)) { + if (PTR_ERR(osalEnv.mpc[i].domain_dir) != -ENODEV) + pr_info("CM: Can't create " + "'dsp/%s/domains' " + "debugfs directory: %ld\n", + osalEnv.mpc[i].name, + PTR_ERR(osalEnv.mpc[i].domain_dir)); + osalEnv.mpc[i].domain_dir = NULL; + } + + osalEnv.mpc[i].snapshot_dir = debugfs_create_dir( + "snapshot", + osalEnv.mpc[i].dir); + if (IS_ERR(osalEnv.mpc[i].snapshot_dir)) { + if (PTR_ERR(osalEnv.mpc[i].snapshot_dir) != -ENODEV) + pr_info("CM: Can't create " + "'dsp/%s/snapshot' debugfs " + "directory: %ld\n", + osalEnv.mpc[i].name, + PTR_ERR(osalEnv.mpc[i].snapshot_dir)); + osalEnv.mpc[i].snapshot_dir = NULL; + } else { + debugfs_create_file("esram", S_IRUSR|S_IRGRP|S_IROTH, + osalEnv.mpc[i].snapshot_dir, + &osalEnv.esram_base, + &esram_fops); + debugfs_create_blob("sdram_data", S_IRUSR|S_IRGRP|S_IROTH, + osalEnv.mpc[i].snapshot_dir, + &osalEnv.mpc[i].sdram_data); + debugfs_create_blob("sdram_code", S_IRUSR|S_IRGRP|S_IROTH, + osalEnv.mpc[i].snapshot_dir, + &osalEnv.mpc[i].sdram_code); + } + + debugfs_create_bool("running", S_IRUSR|S_IRGRP, + osalEnv.mpc[i].dir, + (u32 *)&osalEnv.mpc[i].monitor_tsk); + debugfs_create_u8("load", S_IRUSR|S_IRGRP, + osalEnv.mpc[i].dir, + &osalEnv.mpc[i].load); + debugfs_create_u8("requested_opp", S_IRUSR|S_IRGRP, + osalEnv.mpc[i].dir, + &osalEnv.mpc[i].opp_request); + } + } + osal_debug_ops.component_create = cm_debug_component_create; + osal_debug_ops.component_destroy = cm_debug_component_destroy; +} + +void cm_debug_exit(void) +{ + debugfs_remove_recursive(cm_dir); +} + +#endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/staging/nmf-cm/cm_debug.h b/drivers/staging/nmf-cm/cm_debug.h new file mode 100644 index 00000000000..26c80682d11 --- /dev/null +++ b/drivers/staging/nmf-cm/cm_debug.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef CM_DEBUG_H +#define CM_DEBUG_H + +#ifdef CONFIG_DEBUG_FS +#include "cmld.h" + +void cm_debug_init(void); +void cm_debug_exit(void); +void cm_debug_proc_init(struct cm_process_priv *entry); +void cm_debug_create_tcm_file(unsigned mpc_index); +void cm_debug_destroy_tcm_file(unsigned mpc_index); + +#else + +#define cm_debug_init() +#define cm_debug_exit() +#define cm_debug_proc_init(entry) +#define cm_debug_create_tcm_file(mpc_index) +#define cm_debug_destroy_tcm_file(mpc_index) + +#endif /* CONFIG_DEBUG_FS */ +#endif /* CM_DEBUG_H */ diff --git a/drivers/staging/nmf-cm/cm_dma.c b/drivers/staging/nmf-cm/cm_dma.c new file mode 100644 index 00000000000..652b504324c --- /dev/null +++ b/drivers/staging/nmf-cm/cm_dma.c @@ -0,0 +1,226 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * License terms: GNU General Public License (GPL), version 2. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <asm/io.h> +#include <mach/db8500-regs.h> + +#include "cm_dma.h" + +#define CMDMA_LIDX (2) +#define CMDMA_REG_LCLA (0x024) + +static void __iomem *virtbase = NULL; + +static int cmdma_write_cyclic_list_mem2per( + unsigned int from_addr, + unsigned int to_addr, + unsigned int segments, + unsigned int segmentsize, + unsigned int LOS); + +static int cmdma_write_cyclic_list_per2mem( + unsigned int from_addr, + unsigned int to_addr, + unsigned int segments, + unsigned int segmentsize, + unsigned int LOS); + +static bool cmdma_setup_relink_area_called = false; + +int cmdma_setup_relink_area( unsigned int mem_addr, + unsigned int per_addr, + unsigned int segments, + unsigned int segmentsize, + unsigned int LOS, + enum cmdma_type type) +{ + if (!cmdma_setup_relink_area_called) + cmdma_setup_relink_area_called = true; + + switch (type) { + + case CMDMA_MEM_2_PER: + return cmdma_write_cyclic_list_mem2per( + mem_addr, + per_addr, + segments, + segmentsize, + LOS); + + case CMDMA_PER_2_MEM: + return cmdma_write_cyclic_list_per2mem( + per_addr, + mem_addr, + segments, + segmentsize, + LOS); + + default : + return -EINVAL; + } + } + + static unsigned int cmdma_getlcla( void) { + + if(!virtbase) + virtbase = ioremap(U8500_DMA_BASE, CMDMA_REG_LCLA + sizeof(int) ); + + return readl(virtbase + CMDMA_REG_LCLA); + } + + static void cmdma_write_relink_params_mem2per ( + int * relink, + unsigned int LOS, + unsigned int nb_element, + unsigned int src_addr, + unsigned int dst_addr, + unsigned int burst_size) { + + relink[0] = (((long)(nb_element & 0xFFFF)) << 16) | + (src_addr & 0xFFFF); + + relink[1] = (((src_addr >> 16) & 0xFFFFUL) << 16) | + (0x1200UL | (LOS << 1) | (burst_size<<10)); + + relink[2] = ((nb_element & 0xFFFF) << 16) | + (dst_addr & 0xFFFF); + + relink[3] = (((dst_addr >> 16) & 0xFFFFUL) << 16 ) | + 0x8201UL | ((LOS+1) << 1) | (burst_size<<10); +} + +static void cmdma_write_relink_params_per2mem ( + int * relink, + unsigned int LOS, + unsigned int nb_element, + unsigned int src_addr, + unsigned int dst_addr, + unsigned int burst_size) { + + relink[0] = (((long)(nb_element & 0xFFFF)) << 16) | + (src_addr & 0xFFFF); + + relink[1] = (((src_addr >> 16) & 0xFFFFUL) << 16) | + (0x8201UL | (LOS << 1) | (burst_size<<10)); + + relink[2] = ((nb_element & 0xFFFF) << 16) | + (dst_addr & 0xFFFF); + + relink[3] = (((dst_addr >> 16) & 0xFFFFUL) << 16 ) | + 0x1200UL | ((LOS+1) << 1) | (burst_size<<10); +} + +static int cmdma_write_cyclic_list_mem2per( + unsigned int from_addr, + unsigned int to_addr, + unsigned int segments, + unsigned int segmentsize, + unsigned int LOS) { + + unsigned int i,j; + int *relink; + + j = LOS; + + for ( i = 0; i < segments; i++) { + relink = ioremap_nocache (cmdma_getlcla() + 1024 * CMDMA_LIDX + 8 * j, 4 * sizeof(int)); + + if (i == (segments-1)) + j = LOS; + else + j += 2; + + cmdma_write_relink_params_mem2per ( + relink, + j, + segmentsize / 4, + from_addr, + to_addr, + 0x2); + + iounmap(relink); + + from_addr += segmentsize; + } + + return 0; +} + +static int cmdma_write_cyclic_list_per2mem( + unsigned int from_addr, + unsigned int to_addr, + unsigned int segments, + unsigned int segmentsize, + unsigned int LOS) { + + unsigned int i,j; + int *relink; + j = LOS; + + for ( i = 0; i < segments; i++) { + relink = ioremap_nocache (cmdma_getlcla() + 1024 * CMDMA_LIDX + 8 * j, 4 * sizeof(int)); + + if (i == (segments-1)) + j = LOS; + else + j += 2; + + cmdma_write_relink_params_per2mem ( + relink, + j, + segmentsize / 4, + from_addr, + to_addr, + 0x2); + + iounmap(relink); + + to_addr += segmentsize; + } + + return 0; +} + +static void __iomem *dmabase = 0; +int cmdma_init(void) +{ + dmabase = ioremap_nocache(U8500_DMA_BASE, PAGE_SIZE); + if (dmabase == NULL) + return -ENOMEM; + else + return 0; +} + +void cmdma_destroy(void) +{ + iounmap(dmabase); +} + +#define SSLNK_CHAN_2 (0x40C + 0x20 * 2) +#define SDLNK_CHAN_2 (0x41C + 0x20 * 2) + +void cmdma_stop_dma(void) +{ + if(cmdma_setup_relink_area_called) { + cmdma_setup_relink_area_called = false; + if (readl(dmabase + SSLNK_CHAN_2) & (0x3 << 28)) { + printk(KERN_ERR "CM: ERROR - RX DMA was running\n"); + } + if (readl(dmabase + SDLNK_CHAN_2) & (0x3 << 28)) { + printk(KERN_ERR "CM: ERROR - TX DMA was running\n"); + } + + writel(~(1 << 28), dmabase + SSLNK_CHAN_2); + while (readl(dmabase + SSLNK_CHAN_2) & (0x3 << 28)); + + writel(~(1 << 28), dmabase + SDLNK_CHAN_2); + while (readl(dmabase + SDLNK_CHAN_2) & (0x3 << 28)); + } +} diff --git a/drivers/staging/nmf-cm/cm_dma.h b/drivers/staging/nmf-cm/cm_dma.h new file mode 100644 index 00000000000..4fccef03830 --- /dev/null +++ b/drivers/staging/nmf-cm/cm_dma.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ + +#ifndef __CMDMA_H +#define __CMDMA_H +#include "cmioctl.h" + +int cmdma_setup_relink_area( + unsigned int mem_addr, + unsigned int per_addr, + unsigned int segments, + unsigned int segmentsize, + unsigned int LOS, + enum cmdma_type type); + +void cmdma_stop_dma(void); +int cmdma_init(void); +void cmdma_destroy(void); +#endif diff --git a/drivers/staging/nmf-cm/cm_service.c b/drivers/staging/nmf-cm/cm_service.c new file mode 100644 index 00000000000..a2a6ffa5b57 --- /dev/null +++ b/drivers/staging/nmf-cm/cm_service.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +/** \file cm_service.c + * + * Nomadik Multiprocessing Framework Linux Driver + * + */ + +#include <linux/module.h> +#include <linux/plist.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/spinlock_types.h> + +#include <cm/engine/api/control/irq_engine.h> + +#include "osal-kernel.h" +#include "cmld.h" +#include "cm_service.h" +#include "cm_dma.h" + +/* Panic managment */ +static void service_tasklet_func(unsigned long); +unsigned long service_tasklet_data = 0; +DECLARE_TASKLET(cmld_service_tasklet, service_tasklet_func, 0); + +void dispatch_service_msg(struct osal_msg *msg) +{ + struct list_head *head, *next; +#ifdef CONFIG_DEBUG_FS + bool dump_flag_to_set = true; +#endif + /* + * Note: no lock needed to protect the channel_list against list + * changes, as the current tasklet is disabled each time we modify + * the list + */ + list_for_each_safe(head, next, &channel_list) { + struct cm_channel_priv *channelPriv = list_entry(head, struct cm_channel_priv, entry); + struct osal_msg *new_msg; + size_t msg_size; + + if (channelPriv->state == CHANNEL_CLOSED) + continue; + msg_size = sizeof(new_msg->hdr) + sizeof(new_msg->d.srv); + new_msg = kmalloc(msg_size, GFP_ATOMIC); + if (new_msg == NULL) { + pr_err("[CM] %s: can't allocate memory, service" + " message not dispatched !!\n", __func__); + continue; + } + memcpy(new_msg, msg, msg_size); + plist_node_init(&new_msg->msg_entry, 0); +#ifdef CONFIG_DEBUG_FS + if (cmld_user_has_debugfs && dump_flag_to_set + && (new_msg->d.srv.srvType == NMF_SERVICE_PANIC)) { + /* + * The reciever of this message will do the DSP + * memory dump + */ + new_msg->d.srv.srvData.panic.panicSource + |= DEBUGFS_DUMP_FLAG; + dump_flag_to_set = false; + cmld_dump_ongoing = true; + } +#endif + spin_lock_bh(&channelPriv->bh_lock); + plist_add(&new_msg->msg_entry, &channelPriv->messageQueue); + spin_unlock_bh(&channelPriv->bh_lock); + wake_up(&channelPriv->waitq); + } +} + +static void service_tasklet_func(unsigned long unused) +{ + t_cm_service_type type; + t_cm_service_description desc; + int i=0; + + do { + if (test_and_clear_bit(i, &service_tasklet_data)) { + CM_getServiceDescription(osalEnv.mpc[i].coreId, &type, &desc); + + switch (type) { + case CM_MPC_SERVICE_PANIC: { + struct osal_msg msg; + + msg.msg_type = MSG_SERVICE; + msg.d.srv.srvType = NMF_SERVICE_PANIC; + msg.d.srv.srvData.panic = desc.u.panic; + + dispatch_service_msg(&msg); + /* + * Stop DMA directly before shutdown, to avoid + * bad sound. Should be called after DSP has + * stopped executing, to avoid the DSP + * re-starting DMA + */ + if (osalEnv.mpc[i].coreId == SIA_CORE_ID) + cmdma_stop_dma(); + break; + } + case CM_MPC_SERVICE_PRINT: { + char msg[256]; + if (CM_ReadMPCString(osalEnv.mpc[i].coreId, + desc.u.print.dspAddress, msg, + sizeof(msg)) == CM_OK) + printk(msg, desc.u.print.value1, + desc.u.print.value2); + break; + } + case CM_MPC_SERVICE_TRACE: + spin_lock_bh(&osalEnv.mpc[i].trace_reader_lock); + if (osalEnv.mpc[i].trace_reader) + wake_up_process(osalEnv.mpc[i].trace_reader); + spin_unlock_bh(&osalEnv.mpc[i].trace_reader_lock); + break; + default: + pr_err("[CM] %s: MPC Service Type %d not supported\n", __func__, type); + } + enable_irq(osalEnv.mpc[i].interrupt1); + } + i = (i+1) % NB_MPC; + } while (service_tasklet_data != 0); +} diff --git a/drivers/staging/nmf-cm/cm_service.h b/drivers/staging/nmf-cm/cm_service.h new file mode 100644 index 00000000000..39582eae573 --- /dev/null +++ b/drivers/staging/nmf-cm/cm_service.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +/** \file cm_service.c + * + * Nomadik Multiprocessing Framework Linux Driver + * + */ + +#ifndef CM_SERVICE_H +#define CM_SERVICE_H + +#include <linux/interrupt.h> + +extern unsigned long service_tasklet_data; +extern struct tasklet_struct cmld_service_tasklet; +void dispatch_service_msg(struct osal_msg *msg); + +#endif diff --git a/drivers/staging/nmf-cm/cm_syscall.c b/drivers/staging/nmf-cm/cm_syscall.c new file mode 100644 index 00000000000..ca8d664abb4 --- /dev/null +++ b/drivers/staging/nmf-cm/cm_syscall.c @@ -0,0 +1,1413 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/vmalloc.h> +#include <cm/engine/api/cm_engine.h> +#include "cmioctl.h" +#include "osal-kernel.h" +#include "cmld.h" +#include "cm_dma.h" + +/** Dequeue and free per-process messages for specific binding + * + * \note + * This is only safe if the per process mutex is held when called. + */ +static inline void freeMessages(struct cm_channel_priv* cPriv, t_skelwrapper* binding) +{ + struct osal_msg *this, *next; + + spin_lock_bh(&cPriv->bh_lock); + + /* free any pending messages */ + plist_for_each_entry_safe(this, next, &cPriv->messageQueue, msg_entry) { + if (this->msg_type == MSG_INTERFACE + && this->d.itf.skelwrap == binding) { + plist_del(&this->msg_entry, &cPriv->messageQueue); + kfree(this); + } + } + spin_unlock_bh(&cPriv->bh_lock); +} + +static t_cm_error copy_string_from_user(char *dst, const char __user *src, int len) +{ + int ret; + + ret = strncpy_from_user(dst, src, len); + if (ret < 0) /* -EFAULT */ + return CM_INVALID_PARAMETER; + + if (ret >= len) + return CM_OUT_OF_LIMITS; + + return 0; +} + +inline int cmld_InstantiateComponent(struct cm_process_priv* procPriv, + CM_InstantiateComponent_t __user *param) +{ + CM_InstantiateComponent_t data; + char templateName[MAX_TEMPLATE_NAME_LENGTH]; + char localName[MAX_COMPONENT_NAME_LENGTH]; + char *dataFile = NULL; + + /* Copy all user data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if (data.in.dataFile != NULL) { + dataFile = vmalloc(data.in.dataFileSize); + if (dataFile == NULL) { + data.out.error = CM_NO_MORE_MEMORY; + goto out; + } + /* coverity[tainted_data : FALSE] */ + if (copy_from_user(dataFile, data.in.dataFile, data.in.dataFileSize)) { + data.out.error = CM_INVALID_PARAMETER; + goto out; + } + } + + if ((data.out.error = copy_string_from_user(templateName, + data.in.templateName, + sizeof(templateName)))) + goto out; + + if ((data.in.localName != NULL) && + (data.out.error = copy_string_from_user(localName, + data.in.localName, + sizeof(localName)))) + goto out; + + /* Do appropriate CM Engine call */ + data.out.error = CM_ENGINE_InstantiateComponent(templateName, + data.in.domainId, + procPriv->pid, + data.in.priority, + data.in.localName ? localName : NULL, + dataFile, + &data.out.component); + +out: + if (dataFile) + vfree(dataFile); + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_BindComponentFromCMCore(struct cm_process_priv* procPriv, + CM_BindComponentFromCMCore_t __user *param) +{ + CM_BindComponentFromCMCore_t data; + char providedItfServerName[MAX_INTERFACE_NAME_LENGTH]; + char *dataFileSkeleton = NULL; + + /* Copy all user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(providedItfServerName, + data.in.providedItfServerName, + sizeof(providedItfServerName)))) + goto out; + + if (data.in.dataFileSkeleton != NULL) { + dataFileSkeleton = OSAL_Alloc(data.in.dataFileSkeletonSize); + if (dataFileSkeleton == NULL) { + data.out.error = CM_NO_MORE_MEMORY; + goto out; + } + /* coverity[tainted_data : FALSE] */ + if (copy_from_user(dataFileSkeleton, data.in.dataFileSkeleton, + data.in.dataFileSkeletonSize)) { + data.out.error = CM_INVALID_PARAMETER; + goto out; + } + } + + data.out.error = CM_ENGINE_BindComponentFromCMCore(data.in.server, + providedItfServerName, + data.in.fifosize, + data.in.eventMemType, + &data.out.host2mpcId, + procPriv->pid, + dataFileSkeleton); +out: + if (dataFileSkeleton) + OSAL_Free(dataFileSkeleton); + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + + return 0; +} + +inline int cmld_UnbindComponentFromCMCore(CM_UnbindComponentFromCMCore_t __user *param) +{ + CM_UnbindComponentFromCMCore_t data; + + /* Copy all user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_UnbindComponentFromCMCore(data.in.host2mpcId); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_BindComponentToCMCore(struct cm_channel_priv* channelPriv, + CM_BindComponentToCMCore_t __user *param) +{ + CM_BindComponentToCMCore_t data; + t_skelwrapper *skelwrapper; + struct cm_process_priv *procPriv = channelPriv->proc; + char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH]; + char *dataFileStub = NULL; + + /* Copy all user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(requiredItfClientName, + data.in.requiredItfClientName, + sizeof(requiredItfClientName)))) + goto out; + + /* Do appropriate CM Engine call */ + skelwrapper = (t_skelwrapper *)OSAL_Alloc(sizeof(*skelwrapper)); + if (skelwrapper == NULL) { + data.out.error = CM_NO_MORE_MEMORY; + goto out; + } + + if (data.in.dataFileStub != NULL) { + dataFileStub = OSAL_Alloc(data.in.dataFileStubSize); + if (dataFileStub == NULL) { + data.out.error = CM_NO_MORE_MEMORY; + goto out; + } + /* coverity[tainted_data : FALSE] */ + if (copy_from_user(dataFileStub, data.in.dataFileStub, data.in.dataFileStubSize)) { + data.out.error = CM_INVALID_PARAMETER; + goto out; + } + } + + if ((data.out.error = CM_ENGINE_BindComponentToCMCore( + data.in.client, + requiredItfClientName, + data.in.fifosize, + (t_nmf_mpc2host_handle)skelwrapper, + dataFileStub, + &data.out.mpc2hostId, + procPriv->pid)) != CM_OK) { + OSAL_Free(skelwrapper); + goto out; + } + + skelwrapper->upperLayerThis = data.in.upLayerThis; + skelwrapper->mpc2hostId = data.out.mpc2hostId; + skelwrapper->channelPriv = channelPriv; + mutex_lock(&channelPriv->skelListLock); + list_add(&skelwrapper->entry, &channelPriv->skelList); + mutex_unlock(&channelPriv->skelListLock); +out: + if (dataFileStub != NULL) + OSAL_Free(dataFileStub); + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_UnbindComponentToCMCore(struct cm_process_priv* procPriv, + CM_UnbindComponentToCMCore_t __user *param) +{ + CM_UnbindComponentToCMCore_t data; + t_skelwrapper *skelwrapper; + char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH]; + + /* Copy all user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(requiredItfClientName, + data.in.requiredItfClientName, + sizeof(requiredItfClientName)))) + goto out; + + data.out.error = CM_ENGINE_UnbindComponentToCMCore( + data.in.client, requiredItfClientName, + (t_nmf_mpc2host_handle*)&skelwrapper, + procPriv->pid); + + if (data.out.error != CM_OK && data.out.error != CM_MPC_NOT_RESPONDING) + goto out; + + data.out.upLayerThis = skelwrapper->upperLayerThis; + + mutex_lock(&skelwrapper->channelPriv->msgQueueLock); + freeMessages(skelwrapper->channelPriv, skelwrapper); + mutex_lock(&skelwrapper->channelPriv->skelListLock); + list_del(&skelwrapper->entry); + mutex_unlock(&skelwrapper->channelPriv->skelListLock); + mutex_unlock(&skelwrapper->channelPriv->msgQueueLock); + OSAL_Free(skelwrapper); +out: + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_BindComponentAsynchronous(struct cm_process_priv* procPriv, + CM_BindComponentAsynchronous_t __user *param) +{ + CM_BindComponentAsynchronous_t data; + char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH]; + char providedItfServerName[MAX_INTERFACE_NAME_LENGTH]; + char *dataFileSkeletonOrEvent = NULL; + char *dataFileStub = NULL; + + /* Copy all user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(requiredItfClientName, + data.in.requiredItfClientName, + sizeof(requiredItfClientName)))) + goto out; + + if ((data.out.error = copy_string_from_user(providedItfServerName, + data.in.providedItfServerName, + sizeof(providedItfServerName)))) + goto out; + + if (data.in.dataFileSkeletonOrEvent != NULL) { + dataFileSkeletonOrEvent = + OSAL_Alloc(data.in.dataFileSkeletonOrEventSize); + if (dataFileSkeletonOrEvent == NULL) { + data.out.error = CM_NO_MORE_MEMORY; + goto out; + } + /* coverity[tainted_data : FALSE] */ + if (copy_from_user(dataFileSkeletonOrEvent, data.in.dataFileSkeletonOrEvent, data.in.dataFileSkeletonOrEventSize)) { + data.out.error = CM_INVALID_PARAMETER; + goto out; + } + } + + if (data.in.dataFileStub != NULL) { + dataFileStub = OSAL_Alloc(data.in.dataFileStubSize); + if (dataFileStub == NULL) { + data.out.error = CM_NO_MORE_MEMORY; + goto out; + } + /* coverity[tainted_data : FALSE] */ + if (copy_from_user(dataFileStub, data.in.dataFileStub, data.in.dataFileStubSize)) { + data.out.error = CM_INVALID_PARAMETER; + goto out; + } + } + + /* Do appropriate CM Engine call */ + data.out.error = CM_ENGINE_BindComponentAsynchronous(data.in.client, + requiredItfClientName, + data.in.server, + providedItfServerName, + data.in.fifosize, + data.in.eventMemType, + procPriv->pid, + dataFileSkeletonOrEvent, + dataFileStub); + +out: + if (dataFileSkeletonOrEvent != NULL) + OSAL_Free(dataFileSkeletonOrEvent); + if (dataFileStub != NULL) + OSAL_Free(dataFileStub); + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_UnbindComponentAsynchronous(struct cm_process_priv* procPriv, + CM_UnbindComponentAsynchronous_t __user *param) +{ + CM_UnbindComponentAsynchronous_t data; + char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH]; + + /* Copy all user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(requiredItfClientName, + data.in.requiredItfClientName, + sizeof(requiredItfClientName)))) + goto out; + + /* Do appropriate CM Engine call */ + /* coverity[tainted_data : FALSE] */ + data.out.error = CM_ENGINE_UnbindComponentAsynchronous(data.in.client, + requiredItfClientName, + procPriv->pid); +out: + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_BindComponent(struct cm_process_priv* procPriv, + CM_BindComponent_t __user *param) +{ + CM_BindComponent_t data; + char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH]; + char providedItfServerName[MAX_INTERFACE_NAME_LENGTH]; + char *dataFileTrace = NULL; + + /* Copy all user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(requiredItfClientName, + data.in.requiredItfClientName, + sizeof(requiredItfClientName)))) + goto out; + + if ((data.out.error = copy_string_from_user(providedItfServerName, + data.in.providedItfServerName, + sizeof(providedItfServerName)))) + goto out; + + if (data.in.dataFileTrace != NULL) { + dataFileTrace = OSAL_Alloc(data.in.dataFileTraceSize); + if (dataFileTrace == NULL) { + data.out.error = CM_NO_MORE_MEMORY; + goto out; + } + /* coverity[tainted_data : FALSE] */ + if (copy_from_user(dataFileTrace, data.in.dataFileTrace, + data.in.dataFileTraceSize)) { + data.out.error = CM_INVALID_PARAMETER; + goto out; + } + } + + /* Do appropriate CM Engine call */ + data.out.error = CM_ENGINE_BindComponent(data.in.client, + requiredItfClientName, + data.in.server, + providedItfServerName, + data.in.traced, + procPriv->pid, + dataFileTrace); +out: + if (dataFileTrace != NULL) + OSAL_Free(dataFileTrace); + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_UnbindComponent(struct cm_process_priv* procPriv, + CM_UnbindComponent_t __user *param) +{ + CM_UnbindComponent_t data; + char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH]; + + /* Copy all user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(requiredItfClientName, + data.in.requiredItfClientName, + sizeof(requiredItfClientName)))) + goto out; + + /* Do appropriate CM Engine call */ + data.out.error = CM_ENGINE_UnbindComponent(data.in.client, + requiredItfClientName, + procPriv->pid); + +out: + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_BindComponentToVoid(struct cm_process_priv* procPriv, + CM_BindComponentToVoid_t __user *param) +{ + CM_BindComponentToVoid_t data; + char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH]; + + /* Copy all user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(requiredItfClientName, + data.in.requiredItfClientName, + sizeof(requiredItfClientName)))) + goto out; + + data.out.error = CM_ENGINE_BindComponentToVoid(data.in.client, + requiredItfClientName, + procPriv->pid); + +out: + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_DestroyComponent(struct cm_process_priv* procPriv, + CM_DestroyComponent_t __user *param) +{ + CM_DestroyComponent_t data; + + /* Copy all user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_DestroyComponent(data.in.component, + procPriv->pid); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_CreateMemoryDomain(struct cm_process_priv *procPriv, + CM_CreateMemoryDomain_t __user *param) +{ + CM_CreateMemoryDomain_t data; + t_cm_domain_memory domain; + + /* Copy all user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if (copy_from_user(&domain, data.in.domain, sizeof(domain))) + return -EFAULT; + + if (data.in.client == NMF_CURRENT_CLIENT) + data.out.error = CM_ENGINE_CreateMemoryDomain(procPriv->pid, + &domain, + &data.out.handle); + else { + /* Check if client is valid (ie already registered) */ + struct list_head* head; + struct cm_process_priv *entry; + + list_for_each(head, &process_list) { + entry = list_entry(head, struct cm_process_priv, + entry); + if (entry->pid == data.in.client) + break; + } + if (head == &process_list) + data.out.error = CM_INVALID_PARAMETER; + else + data.out.error = + CM_ENGINE_CreateMemoryDomain(data.in.client, + &domain, + &data.out.handle); + } + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_CreateMemoryDomainScratch(struct cm_process_priv *procPriv, + CM_CreateMemoryDomainScratch_t __user *param) +{ + CM_CreateMemoryDomainScratch_t data; + t_cm_domain_memory domain; + + /* Copy all user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if (copy_from_user(&domain, data.in.domain, sizeof(domain))) + return -EFAULT; + + data.out.error = CM_ENGINE_CreateMemoryDomainScratch(procPriv->pid, + data.in.parentId, + &domain, + &data.out.handle); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_DestroyMemoryDomain(CM_DestroyMemoryDomain_t __user *param) +{ + CM_DestroyMemoryDomain_t data; + + /* Copy all user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + /* coverity[tainted_data : FALSE] */ + data.out.error = CM_ENGINE_DestroyMemoryDomain(data.in.domainId); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetDomainCoreId(CM_GetDomainCoreId_t __user *param) +{ + CM_GetDomainCoreId_t data; + + /* Copy all user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + /* coverity[tainted_data : FALSE] */ + data.out.error = CM_ENGINE_GetDomainCoreId(data.in.domainId, + &data.out.coreId); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_AllocMpcMemory(struct cm_process_priv *procPriv, + CM_AllocMpcMemory_t __user *param) +{ + t_cm_error err; + CM_AllocMpcMemory_t data; + t_cm_memory_handle handle = 0; + struct memAreaDesc_t* memAreaDesc; + t_cm_system_address systemAddress; + t_uint32 mpcAddress; + + /* Copy all user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + /* Disregard alignment information and force 4kB memory alignment, + in any case (see devnotes.txt) */ + /* PP: Disable this 'force' for now, because of the low amount of + available MPC Memory */ + //data.in.memAlignment = CM_MM_MPC_ALIGN_1024WORDS; + + /* coverity[tainted_data : FALSE] */ + data.out.error = CM_ENGINE_AllocMpcMemory(data.in.domainId, + procPriv->pid, + data.in.memType, + data.in.size, + data.in.memAlignment, + &handle); + + data.out.pHandle = handle; + + if (data.out.error != CM_OK) + goto out; + + /* Get memory area decriptors in advance + so to fill in list elements right now */ + err = CM_ENGINE_GetMpcMemorySystemAddress(handle, &systemAddress); + if (err != CM_OK) { + pr_err("%s: failed CM_ENGINE_GetMpcMemorySystemAddress (%i)\n", __func__, err); + /* If we can't manage internally this allocated memory latter, it's + better to report the error now. + Free the handle to not let the driver in an inconsistent state */ + CM_ENGINE_FreeMpcMemory(handle); + return -EFAULT; + } + + /* Get MPC address in advance so to fill in list elements right now */ + err = CM_ENGINE_GetMpcMemoryMpcAddress(handle, &mpcAddress); + if (err != CM_OK) { + pr_err("%s: failed CM_ENGINE_GetMpcMemoryMpcAddress (%i)\n", __func__, err); + /* see comments above */ + CM_ENGINE_FreeMpcMemory(handle); + return -EFAULT; + } + + /* Allocate and fill a new memory area descriptor. Add it to the list */ + memAreaDesc = OSAL_Alloc(sizeof(struct memAreaDesc_t)); + if (memAreaDesc == NULL) { + pr_err("%s: failed allocating memAreaDesc\n", __func__); + /* see comments above */ + CM_ENGINE_FreeMpcMemory(handle); + return -ENOMEM; + } + + memAreaDesc->procPriv = procPriv; + memAreaDesc->handle = handle; + memAreaDesc->tid = 0; + memAreaDesc->physAddr = systemAddress.physical; + memAreaDesc->kernelLogicalAddr = systemAddress.logical; + memAreaDesc->userLogicalAddr = 0; + memAreaDesc->mpcPhysAddr = mpcAddress; + memAreaDesc->size = data.in.size * ((data.in.memType % 2) ? 4 : 2); // betzw: set size in bytes for host (ugly version) + atomic_set(&memAreaDesc->count, 0); + + if (lock_process(procPriv)) { + /* may be rather call lock_process_uninterruptible() */ + CM_ENGINE_FreeMpcMemory(handle); + OSAL_Free(memAreaDesc); + return -ERESTARTSYS; + } + list_add(&memAreaDesc->list, &procPriv->memAreaDescList); + unlock_process(procPriv); +out: + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_FreeMpcMemory(struct cm_process_priv *procPriv, + CM_FreeMpcMemory_t __user *param) +{ + CM_FreeMpcMemory_t data; + struct list_head *cursor, *next; + + /* Copy user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + /* check that it is actually owned by the process */ + data.out.error = CM_UNKNOWN_MEMORY_HANDLE; + + if (lock_process(procPriv)) + return -ERESTARTSYS; + list_for_each_safe(cursor, next, &procPriv->memAreaDescList){ + struct memAreaDesc_t* curr; + curr = list_entry(cursor, struct memAreaDesc_t, list); + if (curr->handle == data.in.handle){ + if (atomic_read(&curr->count) != 0) { + pr_err("%s: Memory area (phyAddr: %x, size: %d) " + "still in use (count=%d)!\n", __func__, + curr->physAddr, curr->size, + atomic_read(&curr->count)); + data.out.error = CM_INVALID_PARAMETER; + } else { + data.out.error = + CM_ENGINE_FreeMpcMemory(data.in.handle); + if (data.out.error == CM_OK) { + list_del(cursor); + OSAL_Free(curr); + } + } + break; + } + } + unlock_process(procPriv); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetMpcMemoryStatus(CM_GetMpcMemoryStatus_t __user *param) +{ + CM_GetMpcMemoryStatus_t data; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + /* coverity[tainted_data : FALSE] */ + data.out.error = CM_ENGINE_GetMpcMemoryStatus(data.in.coreId, + data.in.memType, + &data.out.pStatus); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_StartComponent(struct cm_process_priv *procPriv, + CM_StartComponent_t __user *param) +{ + CM_StartComponent_t data; + + /* Copy user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_StartComponent(data.in.client, + procPriv->pid); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_StopComponent(struct cm_process_priv *procPriv, + CM_StopComponent_t __user *param) +{ + CM_StopComponent_t data; + + /* Copy user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_StopComponent(data.in.client, + procPriv->pid); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetMpcLoadCounter(CM_GetMpcLoadCounter_t __user *param) +{ + CM_GetMpcLoadCounter_t data; + + /* Copy user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_getMpcLoadCounter(data.in.coreId, + &data.out.pMpcLoadCounter); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentDescription(struct cm_process_priv *procPriv, + CM_GetComponentDescription_t __user *param) +{ + CM_GetComponentDescription_t data; + char templateName[MAX_TEMPLATE_NAME_LENGTH]; + char localName[MAX_COMPONENT_NAME_LENGTH]; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_GetComponentDescription(data.in.component, + templateName, + data.in.templateNameLength, + &data.out.coreId, + localName, + data.in.localNameLength, + &data.out.priority); + + /* Copy results back to userspace */ + if (data.out.error == CM_OK) { + /* coverity[tainted_data : FALSE] */ + if (copy_to_user(data.in.templateName, templateName, data.in.templateNameLength)) + return -EFAULT; + /* coverity[tainted_data : FALSE] */ + if (copy_to_user(data.in.localName, localName, data.in.localNameLength)) + return -EFAULT; + } + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentListHeader(struct cm_process_priv *procPriv, + CM_GetComponentListHeader_t __user *param) +{ + CM_GetComponentListHeader_t data; + + data.out.error = CM_ENGINE_GetComponentListHeader(procPriv->pid, + &data.out.headerComponent); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentListNext(struct cm_process_priv *procPriv, + CM_GetComponentListNext_t __user *param) +{ + CM_GetComponentListNext_t data; + + /* Copy user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_GetComponentListNext(procPriv->pid, + data.in.prevComponent, + &data.out.nextComponent); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentRequiredInterfaceNumber(struct cm_process_priv *procPriv, + CM_GetComponentRequiredInterfaceNumber_t __user *param) +{ + CM_GetComponentRequiredInterfaceNumber_t data; + + /* Copy user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_GetComponentRequiredInterfaceNumber(data.in.component, + &data.out.numberRequiredInterfaces); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentRequiredInterface(struct cm_process_priv *procPriv, + CM_GetComponentRequiredInterface_t __user *param) +{ + CM_GetComponentRequiredInterface_t data; + char itfName[MAX_INTERFACE_NAME_LENGTH]; + char itfType[MAX_INTERFACE_TYPE_NAME_LENGTH]; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_GetComponentRequiredInterface(data.in.component, + data.in.index, + itfName, + data.in.itfNameLength, + itfType, + data.in.itfTypeLength, + &data.out.requireState, + &data.out.collectionSize); + + /* Copy results back to userspace */ + if (data.out.error == CM_OK) { + /* coverity[tainted_data : FALSE] */ + if (copy_to_user(data.in.itfName, itfName, data.in.itfNameLength)) + return -EFAULT; + /* coverity[tainted_data : FALSE] */ + if (copy_to_user(data.in.itfType, itfType, data.in.itfTypeLength)) + return -EFAULT; + } + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentRequiredInterfaceBinding(struct cm_process_priv *procPriv, + CM_GetComponentRequiredInterfaceBinding_t __user *param) +{ + CM_GetComponentRequiredInterfaceBinding_t data; + char itfName[MAX_INTERFACE_NAME_LENGTH]; + char serverItfName[MAX_INTERFACE_NAME_LENGTH]; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + if ((data.out.error = copy_string_from_user(itfName, + data.in.itfName, + sizeof(itfName)))) + goto out; + + data.out.error = CM_ENGINE_GetComponentRequiredInterfaceBinding(data.in.component, + itfName, + &data.out.server, + serverItfName, + data.in.serverItfNameLength); + + /* Copy results back to userspace */ + if (data.out.error != CM_OK) + goto out; + + /* coverity[tainted_data : FALSE] */ + if (copy_to_user(data.in.serverItfName, serverItfName, data.in.serverItfNameLength)) + return -EFAULT; +out: + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentProvidedInterfaceNumber(struct cm_process_priv *procPriv, + CM_GetComponentProvidedInterfaceNumber_t __user *param) +{ + CM_GetComponentProvidedInterfaceNumber_t data; + + /* Copy user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_GetComponentProvidedInterfaceNumber(data.in.component, + &data.out.numberProvidedInterfaces); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentProvidedInterface(struct cm_process_priv *procPriv, + CM_GetComponentProvidedInterface_t __user *param) +{ + CM_GetComponentProvidedInterface_t data; + char itfName[MAX_INTERFACE_NAME_LENGTH]; + char itfType[MAX_INTERFACE_TYPE_NAME_LENGTH]; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_GetComponentProvidedInterface(data.in.component, + data.in.index, + itfName, + data.in.itfNameLength, + itfType, + data.in.itfTypeLength, + &data.out.collectionSize); + + /* Copy results back to userspace */ + if (data.out.error == CM_OK) { + /* coverity[tainted_data : FALSE] */ + if (copy_to_user(data.in.itfName, itfName, data.in.itfNameLength)) + return -EFAULT; + /* coverity[tainted_data : FALSE] */ + if (copy_to_user(data.in.itfType, itfType, data.in.itfTypeLength)) + return -EFAULT; + } + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentPropertyNumber(struct cm_process_priv *procPriv, + CM_GetComponentPropertyNumber_t __user *param) +{ + CM_GetComponentPropertyNumber_t data; + + /* Copy user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_GetComponentPropertyNumber(data.in.component, + &data.out.numberProperties); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentPropertyName(struct cm_process_priv *procPriv, + CM_GetComponentPropertyName_t __user *param) +{ + CM_GetComponentPropertyName_t data; + char propertyName[MAX_PROPERTY_NAME_LENGTH]; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_GetComponentPropertyName(data.in.component, + data.in.index, + propertyName, + data.in.propertyNameLength); + + /* Copy results back to userspace */ + /* coverity[tainted_data : FALSE] */ + if ((data.out.error == CM_OK) && + copy_to_user(data.in.propertyName, propertyName, data.in.propertyNameLength)) + return -EFAULT; + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetComponentPropertyValue(struct cm_process_priv *procPriv, + CM_GetComponentPropertyValue_t __user *param) +{ + CM_GetComponentPropertyValue_t data; + char propertyName[MAX_PROPERTY_NAME_LENGTH]; + char propertyValue[MAX_PROPERTY_VALUE_LENGTH]; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(propertyName, + data.in.propertyName, + sizeof(propertyName)))) + goto out; + + data.out.error = CM_ENGINE_GetComponentPropertyValue(data.in.component, + propertyName, + propertyValue, + data.in.propertyValueLength); + /* Copy results back to userspace */ + /* coverity[tainted_data : FALSE] */ + if ((data.out.error == CM_OK) && + copy_to_user(data.in.propertyValue, propertyValue, data.in.propertyValueLength)) + return -EFAULT; +out: + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_ReadComponentAttribute(struct cm_process_priv *procPriv, + CM_ReadComponentAttribute_t __user *param) +{ + CM_ReadComponentAttribute_t data; + char attrName[MAX_ATTRIBUTE_NAME_LENGTH]; + + /* Copy user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(attrName, + data.in.attrName, + sizeof(attrName)))) + goto out; + + data.out.error = CM_ENGINE_ReadComponentAttribute(data.in.component, + attrName, + &data.out.value); +out: + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetExecutiveEngineHandle(struct cm_process_priv *procPriv, + CM_GetExecutiveEngineHandle_t __user *param) +{ + CM_GetExecutiveEngineHandle_t data; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + /* coverity[tainted_data : FALSE] */ + data.out.error = CM_ENGINE_GetExecutiveEngineHandle(data.in.domainId, + &data.out.executiveEngineHandle); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_SetMode(CM_SetMode_t __user *param) +{ + CM_SetMode_t data; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + /* coverity[tainted_data : FALSE] */ + data.out.error = CM_ENGINE_SetMode(data.in.aCmdID, data.in.aParam); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_GetRequiredComponentFiles(struct cm_process_priv *procPriv, + CM_GetRequiredComponentFiles_t __user *param) +{ + CM_GetRequiredComponentFiles_t data; + char components[4][MAX_INTERFACE_TYPE_NAME_LENGTH]; + char requiredItfClientName[MAX_INTERFACE_NAME_LENGTH]; + char providedItfServerName[MAX_INTERFACE_NAME_LENGTH]; + char type[MAX_INTERFACE_TYPE_NAME_LENGTH]; + unsigned int i; + int err; + + /* Copy user input data in kernel space */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if (data.in.requiredItfClientName && + (data.out.error = copy_string_from_user(requiredItfClientName, + data.in.requiredItfClientName, + sizeof(requiredItfClientName)))) + goto out; + + if (data.in.providedItfServerName && + (data.out.error = copy_string_from_user(providedItfServerName, + data.in.providedItfServerName, + sizeof(providedItfServerName)))) + goto out; + + data.out.error = CM_ENGINE_GetRequiredComponentFiles(data.in.action, + data.in.client, + requiredItfClientName, + data.in.server, + providedItfServerName, + components, + data.in.listSize, + data.in.type ? type : NULL, + &data.out.methodNumber); + + if (data.out.error) + goto out; + + if (data.in.fileList) { + /* Copy results back to userspace */ + for (i=0; i<data.in.listSize; i++) { + err = copy_to_user(&((char*)data.in.fileList)[i*MAX_INTERFACE_TYPE_NAME_LENGTH], components[i], MAX_INTERFACE_TYPE_NAME_LENGTH); + if (err) + return -EFAULT; + } + } + if (data.in.type + && copy_to_user(data.in.type, type, MAX_INTERFACE_TYPE_NAME_LENGTH)) + return -EFAULT; +out: + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_Migrate(CM_Migrate_t __user *param) +{ + CM_Migrate_t data; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + data.out.error = CM_ENGINE_Migrate(data.in.srcShared, data.in.src, data.in.dst); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_Unmigrate(CM_Unmigrate_t __user *param) +{ + CM_Unmigrate_t data; + + data.out.error = CM_ENGINE_Unmigrate(); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +int cmld_SetupRelinkArea(struct cm_process_priv *procPriv, + CM_SetupRelinkArea_t __user *param) +{ + CM_SetupRelinkArea_t data; + struct list_head *cursor, *next; + struct memAreaDesc_t *entry = NULL; + + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + + /* check that it is actually owned by the process */ + data.out.error = CM_UNKNOWN_MEMORY_HANDLE; + + if (lock_process(procPriv)) + return -ERESTARTSYS; + list_for_each_safe(cursor, next, &procPriv->memAreaDescList){ + entry = list_entry(cursor, struct memAreaDesc_t, list); + if (entry->handle == data.in.mem_handle) + break; + } + unlock_process(procPriv); + + if ((entry == NULL) || (entry->handle != data.in.mem_handle)) + goto out; + + if (entry->size < data.in.segments * data.in.segmentsize) + { + data.out.error = CM_INVALID_PARAMETER; + goto out; + } + + data.out.error = cmdma_setup_relink_area( + entry->physAddr, + data.in.peripheral_addr, + data.in.segments, + data.in.segmentsize, + data.in.LOS, + data.in.type); +out: + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + + return 0; +} + + +inline int cmld_PushComponent(CM_PushComponent_t __user *param) +{ + CM_PushComponent_t data; + char name[MAX_INTERFACE_TYPE_NAME_LENGTH]; + void *dataFile = NULL; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(name, + data.in.name, + sizeof(name)))) + goto out; + + if (data.in.data != NULL) { + dataFile = OSAL_Alloc(data.in.size); + if (dataFile == NULL) { + data.out.error = CM_NO_MORE_MEMORY; + goto out; + } + /* coverity[tainted_data : FALSE] */ + if (copy_from_user(dataFile, data.in.data, data.in.size)) + data.out.error = CM_INVALID_PARAMETER; + else + data.out.error = CM_ENGINE_PushComponent(name, dataFile, + data.in.size); + OSAL_Free(dataFile); + } + +out: + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_ReleaseComponent(CM_ReleaseComponent_t __user *param) +{ + CM_ReleaseComponent_t data; + char name[MAX_INTERFACE_TYPE_NAME_LENGTH]; + + /* Copy user input data in kernel space */ + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if ((data.out.error = copy_string_from_user(name, + data.in.name, + sizeof(name)))) + goto out; + + /* coverity[tainted_data : FALSE] */ + data.out.error = CM_ENGINE_ReleaseComponent(name); + +out: + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +inline int cmld_PrivGetMPCMemoryDesc(struct cm_process_priv *procPriv, CM_PrivGetMPCMemoryDesc_t __user *param) +{ + CM_PrivGetMPCMemoryDesc_t data; + struct list_head* cursor; + + if (copy_from_user(&data.in, ¶m->in, sizeof(data.in))) + return -EFAULT; + + if (lock_process(procPriv)) + return -ERESTARTSYS; + /* Scan the memory descriptors list looking for the requested handle */ + data.out.error = CM_UNKNOWN_MEMORY_HANDLE; + list_for_each(cursor, &procPriv->memAreaDescList) { + struct memAreaDesc_t* curr; + curr = list_entry(cursor, struct memAreaDesc_t, list); + if (curr->handle == data.in.handle) { + data.out.size = curr->size; + data.out.physAddr = curr->physAddr; + data.out.kernelLogicalAddr = curr->kernelLogicalAddr; + data.out.userLogicalAddr = curr->userLogicalAddr; + data.out.mpcPhysAddr = curr->mpcPhysAddr; + data.out.error = CM_OK; + break; + } + } + unlock_process(procPriv); + + /* Copy results back to userspace */ + if (copy_to_user(¶m->out, &data.out, sizeof(data.out))) + return -EFAULT; + return 0; +} + +int cmld_PrivReserveMemory(struct cm_process_priv *procPriv, unsigned int physAddr) +{ + struct list_head* cursor; + struct memAreaDesc_t* curr; + int err = -ENXIO; + + if (lock_process(procPriv)) + return -ERESTARTSYS; + list_for_each(cursor, &procPriv->memAreaDescList) { + curr = list_entry(cursor, struct memAreaDesc_t, list); + if (curr->physAddr == physAddr) { + /* Mark this memory area reserved for a mapping for this thread ID */ + /* It must not be already reserved but this should not happen */ + if (curr->tid) { + pr_err("%s: thread %d can't reseveved memory %x already " + "reserved for %d\n", + __func__, current->pid, physAddr, curr->tid); + err = -EBUSY; + } else { + curr->tid = current->pid; + err = 0; + } + break; + } + } + unlock_process(procPriv); + return err; +} diff --git a/drivers/staging/nmf-cm/cmioctl.h b/drivers/staging/nmf-cm/cmioctl.h new file mode 100644 index 00000000000..5f7d5b6a349 --- /dev/null +++ b/drivers/staging/nmf-cm/cmioctl.h @@ -0,0 +1,604 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ + +#ifndef __CMIOCTL_H +#define __CMIOCTL_H + +#ifndef __KERNEL__ +#define BITS_PER_BYTE 8 +#endif + +#include <cm/engine/component/inc/component_type.h> +#include <cm/engine/communication/inc/communication_type.h> +#include <cm/engine/configuration/inc/configuration_type.h> +#include <cm/engine/memory/inc/domain_type.h> +#include <cm/engine/memory/inc/memory_type.h> +#include <cm/engine/perfmeter/inc/perfmeter_type.h> +#include <cm/engine/repository_mgt/inc/repository_type.h> + +#define DEBUGFS_ROOT "nmf-cm" +#define DEBUGFS_DUMP_FLAG (1 << (sizeof(t_panic_source)*BITS_PER_BYTE - 1)) + +enum cmdma_type { + CMDMA_MEM_2_PER, + CMDMA_PER_2_MEM +}; + +#define CMLD_DEV_NAME \ + { "cm_control", \ + "cm_channel", \ + "cm_sia_trace", \ + "cm_sva_trace", \ + } + +/* + * The following structures are used to exchange CM_SYSCALL parameters with + * the driver. There is one structure per ioctl command, ie per CM_SYSCAL. + * Each of them contains: + * - One set of fields placed in a struture 'in' which are all input + * parameters of the syscall (parameters that kernel side must retrieve + * from user space) + * - One set of fields placed in a struture 'out' which contains all output + * parameters of the syscall plus the error code. + * + * NOTE: all pointers to (user) buffer are always placed in struct 'in', including + * buffers used as output parameters; because the pointer itself is considered as + * an input parameter, as it is directly accessed from kernel space. + */ +typedef struct{ + struct { + const char * templateName; + t_cm_domain_id domainId; + t_nmf_ee_priority priority; + const char * localName; + const char *dataFile; + t_uint32 dataFileSize; + } in; + struct { + t_cm_instance_handle component; /** < Output parameter */ + t_cm_error error; + } out; +} CM_InstantiateComponent_t; + +typedef struct { + struct { + t_cm_bf_host2mpc_handle host2mpcId; + t_event_params_handle h; + t_uint32 size; + t_uint32 methodIndex; + } in; + struct { + t_cm_error error; + } out; +} CM_PushEventWithSize_t; + +typedef struct { + struct { + t_cm_instance_handle server; + const char * providedItfServerName; + t_uint32 fifosize; + t_cm_mpc_memory_type eventMemType; + const char *dataFileSkeleton; + t_uint32 dataFileSkeletonSize; + } in; + struct { + t_cm_bf_host2mpc_handle host2mpcId; /** < Output parameter */ + t_cm_error error; + } out; +} CM_BindComponentFromCMCore_t; + +typedef struct { + struct { + t_cm_bf_host2mpc_handle host2mpcId; + } in; + struct { + t_cm_error error; + } out; +} CM_UnbindComponentFromCMCore_t; + +typedef struct { + struct { + t_cm_instance_handle client; + const char *requiredItfClientName; + t_uint32 fifosize; + t_nmf_mpc2host_handle upLayerThis; + const char *dataFileStub; + t_uint32 dataFileStubSize; + } in; + struct { + t_cm_bf_mpc2host_handle mpc2hostId; /** < Output parameter */ + t_cm_error error; + } out; +} CM_BindComponentToCMCore_t; + +typedef struct { + struct { + t_cm_instance_handle client; + const char *requiredItfClientName; + } in; + struct { + t_nmf_mpc2host_handle upLayerThis; /** < Output parameter */ + t_cm_error error; + } out; +} CM_UnbindComponentToCMCore_t; + +typedef struct { + struct { + t_cm_instance_handle component; + } in; + struct { + t_cm_error error; /** < Output parameter */ + } out; /** < Output parameter */ +} CM_DestroyComponent_t; + +typedef struct { + struct { + const t_cm_domain_memory *domain; + t_nmf_client_id client; + } in; + struct { + t_cm_domain_id handle; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; /** < Out parameter */ +} CM_CreateMemoryDomain_t; + +typedef struct { + struct { + t_cm_domain_id parentId; + const t_cm_domain_memory *domain; + } in; + struct { + t_cm_domain_id handle; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; /** < Out parameter */ +} CM_CreateMemoryDomainScratch_t; + +typedef struct { + struct { + t_cm_domain_id domainId; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; /** < Out parameter */ +} CM_DestroyMemoryDomain_t; + +typedef struct { + struct { + t_cm_domain_id domainId; /** < In parameter */ + } in; + struct { + t_nmf_core_id coreId; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; /** < Out parameter */ +} CM_GetDomainCoreId_t; + +typedef struct { + struct { + t_cm_domain_id domainId; + t_cm_mpc_memory_type memType; + t_cm_size size; + t_cm_memory_alignment memAlignment; + } in; + struct { + t_cm_memory_handle pHandle; /** < Output parameter */ + t_cm_error error; + } out; /** < Output parameter */ +} CM_AllocMpcMemory_t; + +typedef struct{ + struct { + t_cm_memory_handle handle; + } in; + struct { + t_cm_error error; + } out; /** < Output parameter */ +} CM_FreeMpcMemory_t; + +typedef struct { + struct { + t_cm_memory_handle handle; + } in; + struct { + t_uint32 size; /** < Out parameter */ + t_uint32 physAddr; /** < Out parameter */ + t_uint32 kernelLogicalAddr; /** < Out parameter */ + t_uint32 userLogicalAddr; /** < Out parameter */ + t_uint32 mpcPhysAddr; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; /** < Output parameter */ +} CM_PrivGetMPCMemoryDesc_t; + +typedef struct { + struct { + t_cm_instance_handle client; + const char *requiredItfClientName; + t_cm_instance_handle server; + const char *providedItfServerName; + t_uint32 fifosize; + t_cm_mpc_memory_type eventMemType; + const char *dataFileSkeletonOrEvent; + t_uint32 dataFileSkeletonOrEventSize; + const char *dataFileStub; + t_uint32 dataFileStubSize; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_BindComponentAsynchronous_t; + +typedef struct { + struct { + t_cm_instance_handle client; + const char* requiredItfClientName; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_UnbindComponentAsynchronous_t; + +typedef struct { + struct { + t_cm_instance_handle client; + const char *requiredItfClientName; + t_cm_instance_handle server; + const char *providedItfServerName; + t_bool traced; + const char *dataFileTrace; + t_uint32 dataFileTraceSize; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_BindComponent_t; + +typedef struct { + struct { + t_cm_instance_handle client; + const char* requiredItfClientName; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_UnbindComponent_t; + +typedef struct { + struct { + t_cm_instance_handle client; + const char* requiredItfClientName; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_BindComponentToVoid_t; + +typedef struct { + struct { + t_cm_instance_handle client; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_StartComponent_t; + +typedef struct { + struct { + t_cm_instance_handle client; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_StopComponent_t; + +typedef struct { + struct { + t_nmf_core_id coreId; + } in; + struct { + t_cm_mpc_load_counter pMpcLoadCounter; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetMpcLoadCounter_t; + +typedef struct { + struct { + t_nmf_core_id coreId; + t_cm_mpc_memory_type memType; + } in; + struct { + t_cm_allocator_status pStatus; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; /** < Out parameter */ +} CM_GetMpcMemoryStatus_t; + +typedef struct { + struct { + t_cm_instance_handle component; + t_uint32 templateNameLength; + t_uint32 localNameLength; + char *templateName; /** < Out parameter */ + char *localName; /** < Out parameter */ + } in; + struct { + t_nmf_core_id coreId; /** < Out parameter */ + t_nmf_ee_priority priority; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; /** < Out parameter */ +} CM_GetComponentDescription_t; + +typedef struct { + struct { + t_cm_instance_handle headerComponent; /** < Output parameter */ + t_cm_error error; /** < Out parameter */ + } out; /** < Out parameter */ +} CM_GetComponentListHeader_t; + +typedef struct { + struct { + t_cm_instance_handle prevComponent; + } in; + struct { + t_cm_instance_handle nextComponent; /** < Output parameter */ + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetComponentListNext_t; + +typedef struct { + struct { + t_cm_instance_handle component; + } in; + struct { + t_uint8 numberRequiredInterfaces; /** < Output parameter */ + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetComponentRequiredInterfaceNumber_t; + +typedef struct { + struct { + t_cm_instance_handle component; + t_uint8 index; + t_uint32 itfNameLength; + t_uint32 itfTypeLength; + char *itfName; /** < Out parameter */ + char *itfType; /** < Out parameter */ + } in; + struct { + t_cm_require_state requireState; /** < Out parameter */ + t_sint16 collectionSize; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetComponentRequiredInterface_t; + +typedef struct { + struct { + t_cm_instance_handle component; + const char *itfName; + t_uint32 serverItfNameLength; + char *serverItfName; /** < Out parameter */ + } in; + struct { + t_cm_instance_handle server; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetComponentRequiredInterfaceBinding_t; + +typedef struct { + struct { + t_cm_instance_handle component; + } in; + struct { + t_uint8 numberProvidedInterfaces; /** < Output parameter */ + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetComponentProvidedInterfaceNumber_t; + +typedef struct { + struct { + t_cm_instance_handle component; + t_uint8 index; + t_uint32 itfNameLength; + t_uint32 itfTypeLength; + char *itfName; /** < Out parameter */ + char *itfType; /** < Out parameter */ + } in; + struct { + t_sint16 collectionSize; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetComponentProvidedInterface_t; + +typedef struct { + struct { + t_cm_instance_handle component; + } in; + struct { + t_uint8 numberProperties; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetComponentPropertyNumber_t; + +typedef struct { + struct { + t_cm_instance_handle component; + const char *attrName; + t_uint8 index; + t_uint32 propertyNameLength; + char *propertyName; /** < Out parameter */ + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetComponentPropertyName_t; + +typedef struct { + struct { + t_cm_instance_handle component; + const char *propertyName; + t_uint32 propertyValueLength; + char *propertyValue; /** < Out parameter */ + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetComponentPropertyValue_t; + +typedef struct { + struct { + t_cm_instance_handle component; + const char *attrName; + } in; + struct { + t_uint32 value; /** < Out parameter */ + t_cm_error error; /** < Out parameter */ + } out; +} CM_ReadComponentAttribute_t; + +typedef struct { + struct { + t_cm_domain_id domainId; + } in; + struct { + t_cm_instance_handle executiveEngineHandle; + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetExecutiveEngineHandle_t; + +typedef struct { + struct { + t_cm_cmd_id aCmdID; + t_sint32 aParam; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_SetMode_t; + +typedef struct { + struct { + t_action_to_do action; + t_cm_instance_handle client; + const char *requiredItfClientName; + t_cm_instance_handle server; + const char *providedItfServerName; + char **fileList; + unsigned int listSize; + char *type; + } in; + struct { + t_uint32 methodNumber; /** < Output parameter */ + t_cm_error error; /** < Out parameter */ + } out; +} CM_GetRequiredComponentFiles_t; + +typedef struct { + struct { + const char *name; + const void *data; + t_cm_size size; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_PushComponent_t; + +typedef struct { + struct { + const char *name; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_ReleaseComponent_t; + +typedef struct { + struct { + t_cm_domain_id srcShared; + t_cm_domain_id src; + t_cm_domain_id dst; + } in; + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_Migrate_t; + +typedef struct { + struct { + t_cm_error error; /** < Out parameter */ + } out; +} CM_Unmigrate_t; + +typedef struct{ + struct { + t_cm_memory_handle mem_handle; + unsigned int peripheral_addr; + unsigned int segments; + unsigned int segmentsize; + unsigned int LOS; + enum cmdma_type type; + } in; + struct { + t_cm_error error; + } out; +} CM_SetupRelinkArea_t; + +#define CM_PUSHEVENTWITHSIZE _IOWR('c', 0, CM_PushEventWithSize_t) +#define CM_GETVERSION _IOR('c', 1, t_uint32) +#define CM_INSTANTIATECOMPONENT _IOWR('c', 2, CM_InstantiateComponent_t) +#define CM_BINDCOMPONENTFROMCMCORE _IOWR('c', 3, CM_BindComponentFromCMCore_t) +#define CM_UNBINDCOMPONENTFROMCMCORE _IOWR('c', 4, CM_UnbindComponentFromCMCore_t) +#define CM_BINDCOMPONENTTOCMCORE _IOWR('c', 5, CM_BindComponentToCMCore_t) +#define CM_UNBINDCOMPONENTTOCMCORE _IOWR('c', 6, CM_UnbindComponentToCMCore_t) +#define CM_DESTROYCOMPONENT _IOWR('c', 7, CM_DestroyComponent_t) +#define CM_CREATEMEMORYDOMAIN _IOWR('c', 8, CM_CreateMemoryDomain_t) +#define CM_CREATEMEMORYDOMAINSCRATCH _IOWR('c', 9, CM_CreateMemoryDomainScratch_t) +#define CM_DESTROYMEMORYDOMAIN _IOWR('c', 10, CM_DestroyMemoryDomain_t) +#define CM_GETDOMAINCOREID _IOWR('c', 11, CM_GetDomainCoreId_t) +#define CM_ALLOCMPCMEMORY _IOWR('c', 12, CM_AllocMpcMemory_t) +#define CM_FREEMPCMEMORY _IOWR('c', 13, CM_FreeMpcMemory_t) +#define CM_BINDCOMPONENTASYNCHRONOUS _IOWR('c', 14, CM_BindComponentAsynchronous_t) +#define CM_UNBINDCOMPONENTASYNCHRONOUS _IOWR('c', 15, CM_UnbindComponentAsynchronous_t) +#define CM_BINDCOMPONENT _IOWR('c', 16, CM_BindComponent_t) +#define CM_UNBINDCOMPONENT _IOWR('c', 17, CM_UnbindComponent_t) +#define CM_BINDCOMPONENTTOVOID _IOWR('c', 18, CM_BindComponentToVoid_t) +#define CM_STARTCOMPONENT _IOWR('c', 19, CM_StartComponent_t) +#define CM_STOPCOMPONENT _IOWR('c', 20, CM_StopComponent_t) +#define CM_GETMPCLOADCOUNTER _IOWR('c', 21, CM_GetMpcLoadCounter_t) +#define CM_GETMPCMEMORYSTATUS _IOWR('c', 22, CM_GetMpcMemoryStatus_t) +#define CM_GETCOMPONENTDESCRIPTION _IOWR('c', 23, CM_GetComponentDescription_t) +#define CM_GETCOMPONENTLISTHEADER _IOWR('c', 24, CM_GetComponentListHeader_t) +#define CM_GETCOMPONENTLISTNEXT _IOWR('c', 25, CM_GetComponentListNext_t) +#define CM_GETCOMPONENTREQUIREDINTERFACENUMBER _IOWR('c', 26, CM_GetComponentRequiredInterfaceNumber_t) +#define CM_GETCOMPONENTREQUIREDINTERFACE _IOWR('c', 27, CM_GetComponentRequiredInterface_t) +#define CM_GETCOMPONENTREQUIREDINTERFACEBINDING _IOWR('c', 28, CM_GetComponentRequiredInterfaceBinding_t) +#define CM_GETCOMPONENTPROVIDEDINTERFACENUMBER _IOWR('c', 29, CM_GetComponentProvidedInterfaceNumber_t) +#define CM_GETCOMPONENTPROVIDEDINTERFACE _IOWR('c', 30, CM_GetComponentProvidedInterface_t) +#define CM_GETCOMPONENTPROPERTYNUMBER _IOWR('c', 31, CM_GetComponentPropertyNumber_t) +#define CM_GETCOMPONENTPROPERTYNAME _IOWR('c', 32, CM_GetComponentPropertyName_t) +#define CM_GETCOMPONENTPROPERTYVALUE _IOWR('c', 33, CM_GetComponentPropertyValue_t) +#define CM_READCOMPONENTATTRIBUTE _IOWR('c', 34, CM_ReadComponentAttribute_t) +#define CM_GETEXECUTIVEENGINEHANDLE _IOWR('c', 35, CM_GetExecutiveEngineHandle_t) +#define CM_SETMODE _IOWR('c', 36, CM_SetMode_t) +#define CM_GETREQUIREDCOMPONENTFILES _IOWR('c', 37, CM_GetRequiredComponentFiles_t) +#define CM_PUSHCOMPONENT _IOWR('c', 38, CM_PushComponent_t) +#define CM_FLUSHCHANNEL _IO('c', 39) +#define CM_MIGRATE _IOWR('c', 40, CM_Migrate_t) +#define CM_UNMIGRATE _IOR('c', 41, CM_Unmigrate_t) +#define CM_RELEASECOMPONENT _IOWR('c', 42, CM_ReleaseComponent_t) +#define CM_SETUPRELINKAREA _IOWR('c', 43, CM_SetupRelinkArea_t) + +#define CM_PRIVGETMPCMEMORYDESC _IOWR('c', 100, CM_PrivGetMPCMemoryDesc_t) +#define CM_PRIVRESERVEMEMORY _IOW('c', 101, unsigned int) +#define CM_PRIV_GETBOARDVERSION _IOR('c', 102, unsigned int) +#define CM_PRIV_ISCOMPONENTCACHEEMPTY _IO('c', 103) +#define CM_PRIV_DEBUGFS_READY _IO('c', 104) +#define CM_PRIV_DEBUGFS_WAIT_DUMP _IO('c', 105) +#define CM_PRIV_DEBUGFS_DUMP_DONE _IO('c', 106) + +enum board_version { + U8500_V2 +}; +#endif diff --git a/drivers/staging/nmf-cm/cmld.c b/drivers/staging/nmf-cm/cmld.c new file mode 100644 index 00000000000..60c20cadaee --- /dev/null +++ b/drivers/staging/nmf-cm/cmld.c @@ -0,0 +1,1403 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +/** \file cmld.c + * + * Nomadik Multiprocessing Framework Linux Driver + * + */ + +#include <linux/module.h> +#include <linux/cdev.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <cm/inc/cm_def.h> +#include <cm/engine/api/cm_engine.h> +#include <cm/engine/api/control/irq_engine.h> + +#include "osal-kernel.h" +#include "cmld.h" +#include "cmioctl.h" +#include "cm_debug.h" +#include "cm_service.h" +#include "cm_dma.h" + +#define CMDRIVER_PATCH_VERSION 122 +#define O_FLUSH 0x1000000 + +static int cmld_major; +static struct cdev cmld_cdev; +static struct class cmld_class = { + .name = "cm", + .owner = THIS_MODULE, +}; +const char *cmld_devname[] = CMLD_DEV_NAME; +static struct device *cmld_dev[ARRAY_SIZE(cmld_devname)]; + +/* List of per process structure (struct cm_process_priv list) */ +LIST_HEAD(process_list); +static DEFINE_MUTEX(process_lock); /* lock used to protect previous list */ +/* List of per channel structure (struct cm_channel_priv list). + A channel == One file descriptor */ +LIST_HEAD(channel_list); +static DEFINE_MUTEX(channel_lock); /* lock used to protect previous list */ + +#ifdef CONFIG_DEBUG_FS +/* Debugfs support */ +bool cmld_user_has_debugfs = false; +bool cmld_dump_ongoing = false; +module_param(cmld_dump_ongoing, bool, S_IWUSR|S_IRUGO); +static DECLARE_WAIT_QUEUE_HEAD(dump_waitq); +#endif + +static inline struct cm_process_priv *getProcessPriv(void) +{ + struct list_head* head; + struct cm_process_priv *entry; + + mutex_lock(&process_lock); + + /* Look for an entry for the calling process */ + list_for_each(head, &process_list) { + entry = list_entry(head, struct cm_process_priv, entry); + if (entry->pid == current->tgid) { + kref_get(&entry->ref); + goto out; + } + } + mutex_unlock(&process_lock); + + /* Allocate, init and register a new one otherwise */ + entry = OSAL_Alloc(sizeof(*entry)); + if (entry == NULL) + return ERR_PTR(-ENOMEM); + + /* init host2mpcLock */ + mutex_init(&entry->host2mpcLock); + + INIT_LIST_HEAD(&entry->memAreaDescList); + kref_init(&entry->ref); + mutex_init(&entry->mutex); + + entry->pid = current->tgid; + mutex_lock(&process_lock); + list_add(&entry->entry, &process_list); + cm_debug_proc_init(entry); +out: + mutex_unlock(&process_lock); + return entry; +} + +/* Free all messages */ +static inline void freeMessages(struct cm_channel_priv* channelPriv) +{ + struct osal_msg *this, *next; + int warn = 0; + + spin_lock_bh(&channelPriv->bh_lock); + plist_for_each_entry_safe(this, next, &channelPriv->messageQueue, msg_entry) { + plist_del(&this->msg_entry, &channelPriv->messageQueue); + kfree(this); + warn = 1; + } + spin_unlock_bh(&channelPriv->bh_lock); + if (warn) + pr_err("[CM - PID=%d]: Some remaining" + " message(s) freed\n", current->tgid); +} + +/* Free all pending memory areas and relative descriptors */ +static inline void freeMemHandles(struct cm_process_priv* processPriv) +{ + struct list_head* head, *next; + int warn = 0; + + list_for_each_safe(head, next, &processPriv->memAreaDescList) { + struct memAreaDesc_t* curr; + int err; + curr = list_entry(head, struct memAreaDesc_t, list); + err=CM_ENGINE_FreeMpcMemory(curr->handle); + if (err) + pr_err("[CM - PID=%d]: Error (%d) freeing remaining memory area " + "handle\n", current->tgid, err); + list_del(head); + OSAL_Free(curr); + warn = 1; + } + if (warn) { + pr_err("[CM - PID=%d]: Some remaining memory area " + "handle(s) freed\n", current->tgid); + warn = 0; + } +} + +/* Free any skeleton, called when freeing the process entry */ +static inline void freeSkelList(struct list_head* skelList) +{ + struct list_head* head, *next; + int warn = 0; + + /* No lock held, we know that we are the only and the last user + of the list */ + list_for_each_safe(head, next, skelList) { + t_skelwrapper* curr; + curr = list_entry(head, t_skelwrapper, entry); + list_del(head); + OSAL_Free(curr); + warn = 1; + } + if (warn) + pr_err("[CM - PID=%d]: Some remaining skeleton " + "wrapper(s) freed\n", current->tgid); +} + +/* Free any remaining channels belonging to this process */ +/* Called _only_ when freeing the process entry, once the network constructed by + this process has been destroyed. + See cmld_release() to see why there can be some remaining non-freed channels */ +static inline void freeChannels(struct cm_process_priv* processPriv) +{ + struct list_head* head, *next; + int warn = 0; + + mutex_lock(&channel_lock); + list_for_each_safe(head, next, &channel_list) { + struct cm_channel_priv *channelPriv; + channelPriv = list_entry(head, struct cm_channel_priv, entry); + /* Only channels belonging to this process are concerned */ + if (channelPriv->proc == processPriv) { + tasklet_disable(&cmld_service_tasklet); + list_del(&channelPriv->entry); + tasklet_enable(&cmld_service_tasklet); + + /* Free all remaining messages if any + (normally none, but double check) */ + freeMessages(channelPriv); + + /* Free any pending skeleton wrapper */ + /* Here it's safe, we know that all bindings have been undone */ + freeSkelList(&channelPriv->skelList); + + /* Free the per-channel descriptor */ + OSAL_Free(channelPriv); + } + warn = 1; + } + mutex_unlock(&channel_lock); + + if (warn) + pr_err("[CM - PID=%d]: Some remaining channel entries " + "freed\n", current->tgid); +} + +/* Free the process priv structure and all related stuff */ +/* Called only when the last ref to this structure is released */ +static void freeProcessPriv(struct kref *ref) +{ + struct cm_process_priv *entry = container_of(ref, struct cm_process_priv, ref); + t_nmf_error err; + + mutex_lock(&process_lock); + list_del(&entry->entry); + mutex_unlock(&process_lock); + + /* Destroy all remaining components */ + err=CM_ENGINE_FlushComponents(entry->pid); + if (err != NMF_OK) + pr_err("[CM - PID=%d]: Error while flushing some remaining" + " components: error=%d\n", current->tgid, err); + + freeChannels(entry); + + /* Free any pending memory areas and relative descriptors */ + freeMemHandles(entry); + + /* Destroy all remaining domains */ + err=CM_ENGINE_FlushMemoryDomains(entry->pid); + if (err != NMF_OK) + pr_err("[CM - PID=%d]: Error while flushing some remaining" + " domains: error=%d\n", current->tgid, err); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(entry->dir); +#endif + + /* Free the per-process descriptor */ + OSAL_Free(entry); +} + +/** Reads Component Manager messages destinated to this process. + * The message is composed by three fields: + * 1) mpc2host handle (distinguishes interfaces) + * 2) methodIndex (distinguishes interface's methods) + * 3) Variable length parameters (method's parameters values) + * + * \note cfr GetEvent() + * \return POSIX error code + */ +static ssize_t cmld_channel_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + int err = 0; + struct cm_channel_priv* channelPriv = file->private_data; + int msgSize = 0; + struct plist_head* messageQueue; + struct osal_msg* msg; + t_os_message *os_msg = (t_os_message *)buf; + int block = !(file->f_flags & O_NONBLOCK); + + messageQueue = &channelPriv->messageQueue; + + if (mutex_lock_killable(&channelPriv->msgQueueLock)) + return -ERESTARTSYS; + +wait: + while (plist_head_empty(messageQueue)) { + mutex_unlock(&channelPriv->msgQueueLock); + if (block == 0) + return -EAGAIN; + /* Wait until there is a message to ferry up */ + if (wait_event_interruptible(channelPriv->waitq, ((!plist_head_empty(messageQueue)) || (file->f_flags & O_FLUSH)))) + return -ERESTARTSYS; + if (file->f_flags & O_FLUSH) { + file->f_flags &= ~O_FLUSH; + return 0; + } + if (mutex_lock_killable(&channelPriv->msgQueueLock)) + return -ERESTARTSYS; + } + + /* Pick up the first message from the queue, making sure that the + * hwsem tasklet does not wreak havoc the queue in the meantime + */ + spin_lock_bh(&channelPriv->bh_lock); + msg = plist_first_entry(messageQueue, struct osal_msg, msg_entry); + plist_del(&msg->msg_entry, messageQueue); + spin_unlock_bh(&channelPriv->bh_lock); + + switch (msg->msg_type) { + case MSG_INTERFACE: { + + /* Check if enough space is available */ + msgSize = sizeof(msg->msg_type) + msg->d.itf.ptrSize + sizeof(os_msg->data.itf) - sizeof(os_msg->data.itf.params) ; + if (msgSize > count) { + mutex_unlock(&channelPriv->msgQueueLock); + pr_err("CM: message size bigger than buffer size silently ignored!\n"); + err = -EMSGSIZE; + goto out; + } + + /* Copy to user message type */ + err = put_user(msg->msg_type, &os_msg->type); + if (err) goto ack_evt; + + /* Copy to user the t_nmf_mpc2host_handle */ + err = put_user(msg->d.itf.skelwrap->upperLayerThis, &os_msg->data.itf.THIS); + if (err) goto ack_evt; + + /* The methodIndex */ + err = put_user(msg->d.itf.methodIdx, &os_msg->data.itf.methodIndex); + if (err) goto ack_evt; + + /* And the parameters */ + err = copy_to_user(os_msg->data.itf.params, msg->d.itf.anyPtr, msg->d.itf.ptrSize); + + ack_evt: + /* This call is void */ + /* Note: that we cannot release the lock before having called this function + as acknowledgements MUST be executed in the same order as their + respective messages have arrived! */ + CM_ENGINE_AcknowledgeEvent(msg->d.itf.skelwrap->mpc2hostId); + + mutex_unlock(&channelPriv->msgQueueLock); + break; + } + case MSG_SERVICE: { + mutex_unlock(&channelPriv->msgQueueLock); + msgSize = sizeof(msg->msg_type) + sizeof(msg->d.srv.srvType) + + sizeof(msg->d.srv.srvData); + if (count < msgSize) { + pr_err("CM: service message size bigger than buffer size - silently ignored!\n"); + err = -EMSGSIZE; + } + + /* Copy to user message type */ + err = put_user(msg->msg_type, &os_msg->type); + if (err) goto out; + err = copy_to_user(&os_msg->data.srv, &msg->d.srv, + sizeof(msg->d.srv.srvType) + sizeof(msg->d.srv.srvData)); + break; + } + default: + mutex_unlock(&channelPriv->msgQueueLock); + pr_err("CM: invalid message type %d discarded\n", msg->msg_type); + goto wait; + } +out: + /* Destroy the message */ + kfree(msg); + + return err ? err : msgSize; +} + +/** Part of driver's release method. (ie userspace close()) + * It wakes up all waiter. + * + * \return POSIX error code + */ +static int cmld_channel_flush(struct file *file, fl_owner_t id) +{ + struct cm_channel_priv* channelPriv = file->private_data; + file->f_flags |= O_FLUSH; + wake_up(&channelPriv->waitq); + return 0; +} + +static long cmld_channel_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct cm_channel_priv *channelPriv = file->private_data; +#ifdef CONFIG_DEBUG_FS + if (wait_event_interruptible(dump_waitq, (!cmld_dump_ongoing))) + return -ERESTARTSYS; +#endif + + switch(cmd) { + /* + * All channel CM SYSCALL + */ + case CM_BINDCOMPONENTTOCMCORE: + return cmld_BindComponentToCMCore(channelPriv, (CM_BindComponentToCMCore_t *)arg); + case CM_FLUSHCHANNEL: + return cmld_channel_flush(file, 0); + default: + pr_err("CM(%s): unsupported command %i\n", __func__, cmd); + return -EINVAL; + } + return 0; +} + +static long cmld_control_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct cm_process_priv* procPriv = file->private_data; +#ifdef CONFIG_DEBUG_FS + if (cmd == CM_PRIV_DEBUGFS_DUMP_DONE) { + cmld_dump_ongoing = false; + wake_up(&dump_waitq); + return 0; + } else if (wait_event_interruptible(dump_waitq, (!cmld_dump_ongoing))) + return -ERESTARTSYS; +#endif + + switch(cmd) { + /* + * All wrapped CM SYSCALL + */ + case CM_INSTANTIATECOMPONENT: + return cmld_InstantiateComponent(procPriv, + (CM_InstantiateComponent_t *)arg); + + case CM_BINDCOMPONENTFROMCMCORE: + return cmld_BindComponentFromCMCore(procPriv, + (CM_BindComponentFromCMCore_t *)arg); + + case CM_UNBINDCOMPONENTFROMCMCORE: + return cmld_UnbindComponentFromCMCore((CM_UnbindComponentFromCMCore_t *)arg); + + case CM_UNBINDCOMPONENTTOCMCORE: + return cmld_UnbindComponentToCMCore(procPriv, (CM_UnbindComponentToCMCore_t *)arg); + + case CM_BINDCOMPONENTASYNCHRONOUS: + return cmld_BindComponentAsynchronous(procPriv, (CM_BindComponentAsynchronous_t *)arg); + + case CM_UNBINDCOMPONENTASYNCHRONOUS: + return cmld_UnbindComponentAsynchronous(procPriv, (CM_UnbindComponentAsynchronous_t *)arg); + + case CM_BINDCOMPONENT: + return cmld_BindComponent(procPriv, (CM_BindComponent_t *)arg); + + case CM_UNBINDCOMPONENT: + return cmld_UnbindComponent(procPriv, (CM_UnbindComponent_t *)arg); + + case CM_BINDCOMPONENTTOVOID: + return cmld_BindComponentToVoid(procPriv, (CM_BindComponentToVoid_t *)arg); + + case CM_DESTROYCOMPONENT: + return cmld_DestroyComponent(procPriv, (CM_DestroyComponent_t *)arg); + + case CM_CREATEMEMORYDOMAIN: + return cmld_CreateMemoryDomain(procPriv, (CM_CreateMemoryDomain_t *)arg); + + case CM_CREATEMEMORYDOMAINSCRATCH: + return cmld_CreateMemoryDomainScratch(procPriv, (CM_CreateMemoryDomainScratch_t *)arg); + + case CM_DESTROYMEMORYDOMAIN: + return cmld_DestroyMemoryDomain((CM_DestroyMemoryDomain_t *)arg); + + case CM_GETDOMAINCOREID: + return cmld_GetDomainCoreId((CM_GetDomainCoreId_t *)arg); + + case CM_ALLOCMPCMEMORY: + return cmld_AllocMpcMemory(procPriv, (CM_AllocMpcMemory_t *)arg); + + case CM_FREEMPCMEMORY: + return cmld_FreeMpcMemory(procPriv, (CM_FreeMpcMemory_t *)arg); + + case CM_GETMPCMEMORYSTATUS: + return cmld_GetMpcMemoryStatus((CM_GetMpcMemoryStatus_t *)arg); + + case CM_STARTCOMPONENT: + return cmld_StartComponent(procPriv, (CM_StartComponent_t *)arg); + + case CM_STOPCOMPONENT: + return cmld_StopComponent(procPriv, (CM_StopComponent_t *)arg); + + case CM_GETMPCLOADCOUNTER: + return cmld_GetMpcLoadCounter((CM_GetMpcLoadCounter_t *)arg); + + case CM_GETCOMPONENTDESCRIPTION: + return cmld_GetComponentDescription(procPriv, (CM_GetComponentDescription_t *)arg); + + case CM_GETCOMPONENTLISTHEADER: + return cmld_GetComponentListHeader(procPriv, (CM_GetComponentListHeader_t *)arg); + + case CM_GETCOMPONENTLISTNEXT: + return cmld_GetComponentListNext(procPriv, (CM_GetComponentListNext_t *)arg); + + case CM_GETCOMPONENTREQUIREDINTERFACENUMBER: + return cmld_GetComponentRequiredInterfaceNumber(procPriv, + (CM_GetComponentRequiredInterfaceNumber_t *)arg); + + case CM_GETCOMPONENTREQUIREDINTERFACE: + return cmld_GetComponentRequiredInterface(procPriv, + (CM_GetComponentRequiredInterface_t *)arg); + + case CM_GETCOMPONENTREQUIREDINTERFACEBINDING: + return cmld_GetComponentRequiredInterfaceBinding(procPriv, + (CM_GetComponentRequiredInterfaceBinding_t *)arg); + + case CM_GETCOMPONENTPROVIDEDINTERFACENUMBER: + return cmld_GetComponentProvidedInterfaceNumber(procPriv, + (CM_GetComponentProvidedInterfaceNumber_t *)arg); + + case CM_GETCOMPONENTPROVIDEDINTERFACE: + return cmld_GetComponentProvidedInterface(procPriv, + (CM_GetComponentProvidedInterface_t *)arg); + + case CM_GETCOMPONENTPROPERTYNUMBER: + return cmld_GetComponentPropertyNumber(procPriv, + (CM_GetComponentPropertyNumber_t *)arg); + + case CM_GETCOMPONENTPROPERTYNAME: + return cmld_GetComponentPropertyName(procPriv, + (CM_GetComponentPropertyName_t *)arg); + + case CM_GETCOMPONENTPROPERTYVALUE: + return cmld_GetComponentPropertyValue(procPriv, + (CM_GetComponentPropertyValue_t *)arg); + + case CM_READCOMPONENTATTRIBUTE: + return cmld_ReadComponentAttribute(procPriv, + (CM_ReadComponentAttribute_t *)arg); + + case CM_GETEXECUTIVEENGINEHANDLE: + return cmld_GetExecutiveEngineHandle(procPriv, + (CM_GetExecutiveEngineHandle_t *)arg); + + case CM_SETMODE: + return cmld_SetMode((CM_SetMode_t *)arg); + + case CM_GETREQUIREDCOMPONENTFILES: + return cmld_GetRequiredComponentFiles(procPriv, + (CM_GetRequiredComponentFiles_t *)arg); + + case CM_MIGRATE: + return cmld_Migrate((CM_Migrate_t *)arg); + + case CM_UNMIGRATE: + return cmld_Unmigrate((CM_Unmigrate_t *)arg); + + case CM_SETUPRELINKAREA: + return cmld_SetupRelinkArea(procPriv, + (CM_SetupRelinkArea_t *)arg); + + case CM_PUSHCOMPONENT: + return cmld_PushComponent((CM_PushComponent_t *)arg); + + case CM_RELEASECOMPONENT: + return cmld_ReleaseComponent((CM_ReleaseComponent_t *)arg); + + /* + * NMF CALLS (Host->MPC bindings) + */ + case CM_PUSHEVENTWITHSIZE: { + CM_PushEventWithSize_t data; + t_event_params_handle event; + + /* coverity[tainted_data_argument : FALSE] */ + if (copy_from_user(&data.in, (CM_PushEventWithSize_t*)arg, sizeof(data.in))) + return -EFAULT; + + /* Take the lock to synchronize CM_ENGINE_AllocEvent() + * and CM_ENGINE_PushEvent() + */ + if (mutex_lock_killable(&procPriv->host2mpcLock)) + return -ERESTARTSYS; + + event = CM_ENGINE_AllocEvent(data.in.host2mpcId); + if (event == NULL) { + mutex_unlock(&procPriv->host2mpcLock); + return put_user(CM_PARAM_FIFO_OVERFLOW, + &((CM_PushEventWithSize_t*)arg)->out.error); + } + if (data.in.size != 0) + /* coverity[tainted_data : FALSE] */ + if (copy_from_user(event, data.in.h, data.in.size)) { + mutex_unlock(&procPriv->host2mpcLock); + return -EFAULT; // TODO: what about the already allocated and acknowledged event!?! + } + + data.out.error = CM_ENGINE_PushEvent(data.in.host2mpcId, event, data.in.methodIndex); + mutex_unlock(&procPriv->host2mpcLock); + + /* copy error value back */ + return put_user(data.out.error, &((CM_PushEventWithSize_t*)arg)->out.error); + } + + /* + * All private (internal) commands + */ + case CM_PRIVGETMPCMEMORYDESC: + return cmld_PrivGetMPCMemoryDesc(procPriv, (CM_PrivGetMPCMemoryDesc_t *)arg); + + case CM_PRIVRESERVEMEMORY: + return cmld_PrivReserveMemory(procPriv, arg); + + case CM_GETVERSION: { + t_uint32 nmfversion = NMF_VERSION; + return copy_to_user((void*)arg, &nmfversion, sizeof(nmfversion)); + } + case CM_PRIV_GETBOARDVERSION: { + enum board_version v = U8500_V2; + return copy_to_user((void*)arg, &v, sizeof(v)); + } + case CM_PRIV_ISCOMPONENTCACHEEMPTY: + if (CM_ENGINE_IsComponentCacheEmpty()) + return 0; + else + return -ENOENT; + case CM_PRIV_DEBUGFS_READY: +#ifdef CONFIG_DEBUG_FS + cmld_user_has_debugfs = true; +#endif + return 0; + case CM_PRIV_DEBUGFS_WAIT_DUMP: + return 0; + default: + pr_err("CM(%s): unsupported command %i\n", __func__, cmd); + return -EINVAL; + } + + return 0; +} + +/** VMA open callback function + */ +static void cmld_vma_open(struct vm_area_struct* vma) { + struct memAreaDesc_t* curr = (struct memAreaDesc_t*)vma->vm_private_data; + + atomic_inc(&curr->count); +} + +/** VMA close callback function + */ +static void cmld_vma_close(struct vm_area_struct* vma) { + struct memAreaDesc_t* curr = (struct memAreaDesc_t*)vma->vm_private_data; + + atomic_dec(&curr->count); +} + +static struct vm_operations_struct cmld_remap_vm_ops = { + .open = cmld_vma_open, + .close = cmld_vma_close, +}; + +/** mmap implementation. + * Remaps just once. + * + * \return POSIX error code + */ +static int cmld_control_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct list_head* listHead; + struct list_head* cursor; + struct cm_process_priv* procPriv = file->private_data; + struct memAreaDesc_t* curr = NULL; + unsigned int vma_size = vma->vm_end-vma->vm_start; + + listHead = &procPriv->memAreaDescList; + + if (lock_process(procPriv)) return -ERESTARTSYS; + /* Make sure the memory area has not already been remapped */ + list_for_each(cursor, listHead) { + curr = list_entry(cursor, struct memAreaDesc_t, list); + /* For now, the user space aligns any requested physaddr to a page-size limit + This is not safe and must be fixed. But this is the only way to + minimize the allocated TCM memory, needed because of low amount of + TCM memory + Another way is to add some more check before doing this mmap() + to allow this mmap, for example. + NOTE: this memory must be first reserved via the CM_PRIVRESERVEMEMORY ioctl() + */ + if ((curr->physAddr&PAGE_MASK) == offset && + curr->tid == current->pid) { + if (curr->userLogicalAddr) { + unlock_process(procPriv); + return -EINVAL; // already mapped! + } + /* reset the thread id value, to not confuse any further mmap() */ + curr->tid = 0; + break; + } + } + + if (cursor == listHead) { + unlock_process(procPriv); + return -EINVAL; // no matching memory area descriptor found! + } + + /* Very, very important to have consistent buffer transition */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + vma->vm_flags |= VM_RESERVED | VM_IO | VM_DONTEXPAND | VM_DONTCOPY; + + if (remap_pfn_range(vma, vma->vm_start, offset>>PAGE_SHIFT, + vma_size, vma->vm_page_prot)) { + unlock_process(procPriv); + return -EAGAIN; + } + + /* Offset represents the physical address. + * Update the list entry filling in the logical address assigned to the user + */ + /* + * NOTE: here the useLogicalAddr is page-aligned, but not necessaly the + * phycical address. We mmap() more than originaly requested by the + * user, see in CM User Proxy (file cmsyscallwrapper.c) + */ + curr->userLogicalAddr = vma->vm_start; + + /* increment reference counter */ + atomic_inc(&curr->count); + + unlock_process(procPriv); + + /* set private data structure and callbacks */ + vma->vm_private_data = (void *)curr; + vma->vm_ops = &cmld_remap_vm_ops; + + return 0; +} + +/* Driver's release method for /dev/cm_channel */ +static int cmld_channel_release(struct inode *inode, struct file *file) +{ + struct cm_channel_priv* channelPriv = file->private_data; + struct cm_process_priv* procPriv = channelPriv->proc; + + /* + * The driver must guarantee that all related resources are released. + * Thus all these checks below are necessary to release all remaining + * resources still linked to this 'client', in case of abnormal process + * exit. + * => These are error cases ! + * In the usual case, nothing should be done except the free of + * the cmPriv itself + */ + + /* We don't need to synchronize here by using the skelListLock: + the list is only accessed during ioctl() and we can't be here + if an ioctl() is on-going */ + if (list_empty(&channelPriv->skelList)) { + /* There is no pending MPC->HOST binding + => we can quietly delete the channel */ + tasklet_disable(&cmld_service_tasklet); + mutex_lock(&channel_lock); + list_del(&channelPriv->entry); + mutex_unlock(&channel_lock); + tasklet_enable(&cmld_service_tasklet); + + /* Free all remaining messages if any */ + freeMessages(channelPriv); + + /* Free the per-channel descriptor */ + OSAL_Free(channelPriv); + } else { + /* + * Uh: there are still some MPC->HOST binding but we don't have + * the required info to unbind them. + * => we must keep all skel structures because possibly used in + * OSAL_PostDfc (incoming callback msg). We flag the channel as + * closed to discard any new msg that will never be read anyway + */ + channelPriv->state = CHANNEL_CLOSED; + + /* Already Free all remaining messages if any, + they will never be read anyway */ + freeMessages(channelPriv); + } + + kref_put(&procPriv->ref, freeProcessPriv); + file->private_data = NULL; + + return 0; +} + +/* Driver's release method for /dev/cm_control */ +static int cmld_control_release(struct inode *inode, struct file *file) +{ + struct cm_process_priv* procPriv = file->private_data; + + kref_put(&procPriv->ref, freeProcessPriv); + file->private_data = NULL; + + return 0; +} + +static struct file_operations cmld_control_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = cmld_control_ioctl, + .mmap = cmld_control_mmap, + .release = cmld_control_release, +}; + +static int cmld_control_open(struct file *file) +{ + struct cm_process_priv *procPriv = getProcessPriv(); + if (IS_ERR(procPriv)) + return PTR_ERR(procPriv); + file->private_data = procPriv; + file->f_op = &cmld_control_fops; + return 0; +} + +static struct file_operations cmld_channel_fops = { + .owner = THIS_MODULE, + .read = cmld_channel_read, + .unlocked_ioctl = cmld_channel_ioctl, + .flush = cmld_channel_flush, + .release = cmld_channel_release, +}; + +static int cmld_channel_open(struct file *file) +{ + struct cm_process_priv *procPriv = getProcessPriv(); + struct cm_channel_priv *channelPriv; + + if (IS_ERR(procPriv)) + return PTR_ERR(procPriv); + + channelPriv = (struct cm_channel_priv*)OSAL_Alloc(sizeof(*channelPriv)); + if (channelPriv == NULL) { + kref_put(&procPriv->ref, freeProcessPriv); + return -ENOMEM; + } + + channelPriv->proc = procPriv; + channelPriv->state = CHANNEL_OPEN; + + /* Initialize wait_queue, lists and mutexes */ + init_waitqueue_head(&channelPriv->waitq); + plist_head_init(&channelPriv->messageQueue); + INIT_LIST_HEAD(&channelPriv->skelList); + spin_lock_init(&channelPriv->bh_lock); + mutex_init(&channelPriv->msgQueueLock); + mutex_init(&channelPriv->skelListLock); + + tasklet_disable(&cmld_service_tasklet); + mutex_lock(&channel_lock); + list_add(&channelPriv->entry, &channel_list); + mutex_unlock(&channel_lock); + tasklet_enable(&cmld_service_tasklet); + + file->private_data = channelPriv; + file->f_op = &cmld_channel_fops; + return 0; +} + +static ssize_t cmld_sxa_trace_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct mpcConfig *mpc = file->private_data; + size_t written = 0; + struct t_nmf_trace trace; + t_cm_trace_type traceType; + struct mmdsp_trace mmdsp_tr = { + .media = TB_MEDIA_FILE, + .receiver_dev = TB_DEV_PC, + .sender_dev = TB_DEV_TRACEBOX, + .unused = TB_TRACEBOX, + .receiver_obj = DEFAULT_RECEIVERR_OBJ, + .sender_obj = DEFAULT_SENDER_OBJ, + .transaction_id = 0, + .message_id = TB_TRACE_MSG, + .master_id = mpc->coreId+1, + .channel_id = 0, + .ost_version = OST_VERSION, + .entity = ENTITY, + .protocol_id = PROTOCOL_ID, + .btrace_hdr_flag = 0, + .btrace_hdr_subcategory = 0, + }; + + while ((count - written) >= sizeof(mmdsp_tr)) { + traceType = CM_ENGINE_GetNextTrace(mpc->coreId, &trace); + + switch (traceType) { + case CM_MPC_TRACE_READ_OVERRUN: + mmdsp_tr.size = + cpu_to_be16(offsetof(struct mmdsp_trace, + ost_version) + -offsetof(struct mmdsp_trace, + receiver_obj)); + mmdsp_tr.message_id = TB_TRACE_EXCEPTION_MSG; + mmdsp_tr.ost_master_id = TB_EXCEPTION_LONG_OVRF_PACKET; + if (copy_to_user(&buf[written], &mmdsp_tr, + offsetof(struct mmdsp_trace, + ost_version))) + return -EFAULT; + written += offsetof(struct mmdsp_trace, ost_version); + if ((count - written) < sizeof(mmdsp_tr)) + break; + case CM_MPC_TRACE_READ: { + u16 param_nr = (u16)trace.paramOpt; + u16 handle_valid = (u16)(trace.paramOpt >> 16); + u32 to_write = offsetof(struct mmdsp_trace, + parent_handle); + mmdsp_tr.transaction_id = trace.revision%256; + mmdsp_tr.message_id = TB_TRACE_MSG; + mmdsp_tr.ost_master_id = OST_MASTERID; + mmdsp_tr.timestamp = cpu_to_be64(trace.timeStamp); + mmdsp_tr.timestamp2 = cpu_to_be64(trace.timeStamp); + mmdsp_tr.component_id = cpu_to_be32(trace.componentId); + mmdsp_tr.trace_id = cpu_to_be32(trace.traceId); + mmdsp_tr.btrace_hdr_category = (trace.traceId>>16)&0xFF; + mmdsp_tr.btrace_hdr_size = BTRACE_HEADER_SIZE + + sizeof(trace.params[0]) * param_nr; + if (handle_valid) { + mmdsp_tr.parent_handle = trace.parentHandle; + mmdsp_tr.component_handle = + trace.componentHandle; + to_write += sizeof(trace.parentHandle) + + sizeof(trace.componentHandle); + mmdsp_tr.btrace_hdr_size += + sizeof(trace.parentHandle) + + sizeof(trace.componentHandle); + } + mmdsp_tr.size = + cpu_to_be16(to_write + + (sizeof(trace.params[0])*param_nr) + - offsetof(struct mmdsp_trace, + receiver_obj)); + mmdsp_tr.length = to_write + + (sizeof(trace.params[0])*param_nr) + - offsetof(struct mmdsp_trace, + timestamp2); + if (copy_to_user(&buf[written], &mmdsp_tr, to_write)) + return -EFAULT; + written += to_write; + /* write param */ + to_write = sizeof(trace.params[0]) * param_nr; + if (copy_to_user(&buf[written], trace.params, to_write)) + return -EFAULT; + written += to_write; + break; + } + case CM_MPC_TRACE_NONE: + default: + if ((file->f_flags & O_NONBLOCK) || written) + return written; + spin_lock_bh(&mpc->trace_reader_lock); + mpc->trace_reader = current; + spin_unlock_bh(&mpc->trace_reader_lock); + schedule_timeout_killable(msecs_to_jiffies(200)); + spin_lock_bh(&mpc->trace_reader_lock); + mpc->trace_reader = NULL; + spin_unlock_bh(&mpc->trace_reader_lock); + if (signal_pending(current)) + return -ERESTARTSYS; + } + } + return written; +} + +/* Driver's release method for /dev/cm_sxa_trace */ +static int cmld_sxa_trace_release(struct inode *inode, struct file *file) +{ + struct mpcConfig *mpc = file->private_data; + atomic_dec(&mpc->trace_read_count); + return 0; +} + +static struct file_operations cmld_sxa_trace_fops = { + .owner = THIS_MODULE, + .read = cmld_sxa_trace_read, + .release = cmld_sxa_trace_release, +}; + +static int cmld_sxa_trace_open(struct file *file, struct mpcConfig *mpc) +{ + if (atomic_add_unless(&mpc->trace_read_count, 1, 1) == 0) + return -EBUSY; + + file->private_data = mpc; + file->f_op = &cmld_sxa_trace_fops; + return 0; +} + +/* driver open() call: specific */ +static int cmld_open(struct inode *inode, struct file *file) +{ + switch (iminor(inode)) { + case 0: + return cmld_control_open(file); + case 1: + return cmld_channel_open(file); + case 2: + return cmld_sxa_trace_open(file, &osalEnv.mpc[SIA]); + case 3: + return cmld_sxa_trace_open(file, &osalEnv.mpc[SVA]); + default: + return -ENOSYS; + } +} + +/** MPC Events tasklet + * The parameter is used to know from which interrupts we're comming + * and which core to pass to CM_ProcessMpcEvent(): + * 0 means HSEM => ARM_CORE_ID + * otherwise, it gives the index+1 of MPC within osalEnv.mpc table + */ +static void mpc_events_tasklet_handler(unsigned long core) +{ + /* This serves internal events directly. No propagation to user space. + * Calls OSAL_PostDfc implementation for user interface events */ + if (core == 0) { + CM_ProcessMpcEvent(ARM_CORE_ID); + enable_irq(IRQ_DB8500_HSEM); + } else { + --core; + CM_ProcessMpcEvent(osalEnv.mpc[core].coreId); + enable_irq(osalEnv.mpc[core].interrupt0); + } +} + +/** Hardware semaphore and MPC interrupt handler + * 'data' param is the one given when registering the IRQ hanlder, + * contains the source core (ARM or MPC), and follows the same logic + * as for mpc_events_tasklet_handler() + * This handler is used for all IRQ handling some com (ie HSEM or + * all MPC IRQ line0) + */ +static irqreturn_t mpc_events_irq_handler(int irq, void *data) +{ + unsigned core = (unsigned)data; + + if (core != 0) + --core; + disable_irq_nosync(irq); + tasklet_schedule(&osalEnv.mpc[core].tasklet); + + return IRQ_HANDLED; +} + +/** MPC panic handler + * 'idx' contains the index of the core within the osalEnv.mpc table. + * This handler is used for all MPC IRQ line1 + */ +static irqreturn_t panic_handler(int irq, void *idx) +{ + set_bit((int)idx, &service_tasklet_data); + disable_irq_nosync(irq); + tasklet_schedule(&cmld_service_tasklet); + return IRQ_HANDLED; +} + +/** Driver's operations + */ +static struct file_operations cmld_fops = { + .owner = THIS_MODULE, + .open = cmld_open, +}; + +/** + * Configure a MPC, called for each MPC to configure + * + * \param i index of the MPC to configure (refer to the index + * of the MPC within the osalEnvironment.mpc table) + * \param dataAllocId allocId of the data segment, passed through each call of + * this function, and initialized at the first call in case + * shared data segment + */ +static int configureMpc(unsigned i, t_cfg_allocator_id *dataAllocId) +{ + int err; + t_cm_system_address mpcSystemAddress; + t_nmf_memory_segment codeSegment, dataSegment; + t_cfg_allocator_id codeAllocId; + t_cm_domain_id eeDomainId; + t_cm_domain_memory eeDomain = INIT_DOMAIN; + char regulator_name[14]; + + getMpcSystemAddress(i, &mpcSystemAddress); + getMpcSdramSegments(i, &codeSegment, &dataSegment); + + /* Create code segment */ + err = CM_ENGINE_AddMpcSdramSegment(&codeSegment, &codeAllocId, "Code"); + if (err != CM_OK) { + pr_err("CM_ENGINE_AddMpcSdramSegment() error code: %d\n", err); + return -EAGAIN; + } + + /* Create data segment + * NOTE: in case of shared data segment, all MPC point to the same data segment + * (see in remapRegions()) and we need to create the segment only at first call. + * => we reuse the same allocId for the following MPCs + */ + if ((osalEnv.mpc[i].sdram_data.data != osalEnv.mpc[0].sdram_data.data) + || *dataAllocId == -1) { + err = CM_ENGINE_AddMpcSdramSegment(&dataSegment, dataAllocId, "Data"); + if (err != CM_OK) { + pr_err("CM_ENGINE_AddMpcSdramSegment() error code: %d\n", err); + return -EAGAIN; + } + } + + /* create default domain for the given coreId + * this serves for instanciating EE and the LoadMap, only sdram segment is present + * this domain will probably overlap with other user domains + */ + eeDomain.coreId = osalEnv.mpc[i].coreId; + eeDomain.sdramCode.offset = 0x0; + eeDomain.sdramData.offset = 0x0; + eeDomain.sdramCode.size = 0x8000; + eeDomain.sdramData.size = 0x40000; + eeDomain.esramCode.size = 0x4000; + eeDomain.esramData.size = 0x40000; + err = CM_ENGINE_CreateMemoryDomain(NMF_CORE_CLIENT, &eeDomain, &eeDomainId); + if (err != CM_OK) { + pr_err("Create EE domain on %s failed with error code: %d\n", osalEnv.mpc[i].name, err); + return -EAGAIN; + } + + err = CM_ENGINE_ConfigureMediaProcessorCore( + osalEnv.mpc[i].coreId, + osalEnv.mpc[i].eeId, + (cfgSemaphoreTypeHSEM ? SYSTEM_SEMAPHORES : LOCAL_SEMAPHORES), + osalEnv.mpc[i].nbYramBanks, + &mpcSystemAddress, + eeDomainId, + codeAllocId, + *dataAllocId); + + if (err != CM_OK) { + pr_err("CM_ConfigureMediaProcessorCore failed with error code: %d\n", err); + return -EAGAIN; + } + + // Communication channel + if (! cfgSemaphoreTypeHSEM) { + tasklet_init(&osalEnv.mpc[i].tasklet, mpc_events_tasklet_handler, i+1); + err = request_irq(osalEnv.mpc[i].interrupt0, mpc_events_irq_handler, IRQF_DISABLED, osalEnv.mpc[i].name, (void*)(i+1)); + if (err != 0) { + pr_err("CM: request_irq failed to register irq0 %i for %s (%i)\n", osalEnv.mpc[i].interrupt0, osalEnv.mpc[i].name, err); + return err; + } + } + + // Panic channel + err = request_irq(osalEnv.mpc[i].interrupt1, panic_handler, IRQF_DISABLED, osalEnv.mpc[i].name, (void*)i); + if (err != 0) { + pr_err("CM: request_irq failed to register irq1 %i for %s (%i)\n", osalEnv.mpc[i].interrupt1, osalEnv.mpc[i].name, err); + free_irq(osalEnv.mpc[i].interrupt0, (void*)(i+1)); + return err; + } + + // Retrieve the regulators used for this MPCs + sprintf(regulator_name, "%s-mmdsp", osalEnv.mpc[i].name); + osalEnv.mpc[i].mmdsp_regulator = regulator_get(cmld_dev[0], regulator_name); + if (IS_ERR(osalEnv.mpc[i].mmdsp_regulator)) { + long err = PTR_ERR(osalEnv.mpc[i].mmdsp_regulator); + pr_err("CM: Error while retrieving the regulator %s: %ld\n", regulator_name, err); + osalEnv.mpc[i].mmdsp_regulator = NULL; + return err; + } + sprintf(regulator_name, "%s-pipe", osalEnv.mpc[i].name); + osalEnv.mpc[i].pipe_regulator = regulator_get(cmld_dev[0], regulator_name); + if (IS_ERR(osalEnv.mpc[i].pipe_regulator)) { + long err = PTR_ERR(osalEnv.mpc[i].pipe_regulator); + pr_err("CM: Error while retrieving the regulator %s: %ld\n", regulator_name, err); + osalEnv.mpc[i].pipe_regulator = NULL; + return err; + } +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&osalEnv.mpc[i].wakelock, WAKE_LOCK_SUSPEND, osalEnv.mpc[i].name); +#endif + return 0; +} + +/* Free all used MPC irqs and clocks. + * max_mpc allows it to be called from init_module and free + * only the already configured irqs. + */ +static void free_mpc_irqs(int max_mpc) +{ + int i; + for (i=0; i<max_mpc; i++) { + if (! cfgSemaphoreTypeHSEM) + free_irq(osalEnv.mpc[i].interrupt0, (void*)(i+1)); + free_irq(osalEnv.mpc[i].interrupt1, (void*)i); + if (osalEnv.mpc[i].mmdsp_regulator) + regulator_put(osalEnv.mpc[i].mmdsp_regulator); + if (osalEnv.mpc[i].pipe_regulator) + regulator_put(osalEnv.mpc[i].pipe_regulator); +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_destroy(&osalEnv.mpc[i].wakelock); +#endif + } +} + +/** Module entry point + * Allocate memory chunks. Register hardware semaphore, SIA and SVA interrupts. + * Initialize Component Manager. Register hotplug for components download. + * + * \return POSIX error code + */ +static int __init cmld_init_module(void) +{ + int err; + unsigned i=0; + dev_t dev; + t_cfg_allocator_id dataAllocId = -1; + void *htim_base=NULL; + + /* Component manager initialization descriptors */ + t_nmf_hw_mapping_desc nmfHwMappingDesc; + t_nmf_config_desc nmfConfigDesc = { cfgCommunicationLocationInSDRAM ? COMS_IN_SDRAM : COMS_IN_ESRAM }; + + /* OSAL_*Resources() assumes the following, so check that it is correct */ + if (SVA != COREIDX((int)SVA_CORE_ID)) { + pr_err("SVA and (SVA_CORE_ID-1) differs : code must be fixed !\n"); + return -EIO; + } + if (SIA != COREIDX((int)SIA_CORE_ID)) { + pr_err("SIA and (SIA_CORE_ID-1) differs : code must be fixed !\n"); + return -EIO; + } + +#ifdef CM_DEBUG_ALLOC + init_debug_alloc(); +#endif + + err = -EIO; + prcmu_base = __io_address(U8500_PRCMU_BASE); + + /* power on a clock/timer 90KHz used on SVA */ + htim_base = ioremap_nocache(U8500_CR_BASE /*0xA03C8000*/, SZ_4K); + prcmu_tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE); + + /* Activate SVA 90 KHz timer */ + if (htim_base == NULL) + goto out; + iowrite32((1<<26) | ioread32(htim_base), htim_base); + iounmap(htim_base); + + /*i = ioread32(PRCM_SVAMMDSPCLK_MGT) & 0xFF; + if (i != 0x22) + pr_alert("CM: Looks like SVA is not clocked at 200MHz (PRCM_SVAMMDSPCLK_MGT=%x)\n", i); + i = ioread32(PRCM_SIAMMDSPCLK_MGT) & 0xFF; + if (i != 0x22) + pr_alert("CM: Looks like SIA is not clocked at 200MHz (PRCM_SIAMMDSPCLK_MGT=%x)\n", i); + + i = 0;*/ + err = init_config(); + if (err) + goto out; + + /* Remap all needed regions and store in osalEnv base addresses */ + err = remapRegions(); + if (err != 0) + goto out; + + /* Initialize linux devices */ + err = class_register(&cmld_class); + if (err) { + pr_err("CM: class_register failed (%d)\n", err); + goto out; + } + + /* Register char device */ + err = alloc_chrdev_region(&dev, 0, ARRAY_SIZE(cmld_devname), "cm"); + if (err) { + pr_err("CM: alloc_chrdev_region failed (%d)\n", err); + goto out_destroy_class; + } + cmld_major = MAJOR(dev); + + cdev_init(&cmld_cdev, &cmld_fops); + cmld_cdev.owner = THIS_MODULE; + err = cdev_add (&cmld_cdev, dev, ARRAY_SIZE(cmld_devname)); + if (err) { + pr_err("CM: cdev_add failed (%d)\n", err); + goto out_destroy_chrdev; + } + + for (i=0; i<ARRAY_SIZE(cmld_devname); i++) { + cmld_dev[i] = device_create(&cmld_class, NULL, MKDEV(cmld_major, i), NULL, + "%s", cmld_devname[i]); + if (IS_ERR(cmld_dev[i])) { + err = PTR_ERR(cmld_dev[i]); + pr_err("CM: device_create failed (%d)\n", err); + goto out_destroy_device; + } + } + + osalEnv.esram_regulator[ESRAM_12] = regulator_get(cmld_dev[0], "esram12"); + if (IS_ERR(osalEnv.esram_regulator[ESRAM_12])) { + err = PTR_ERR(osalEnv.esram_regulator[ESRAM_12]); + pr_err("CM: Error while retrieving the regulator for esram12: %d\n", err); + osalEnv.esram_regulator[ESRAM_12] = NULL; + goto out_destroy_device; + } + osalEnv.esram_regulator[ESRAM_34] = regulator_get(cmld_dev[0], "esram34"); + if (IS_ERR(osalEnv.esram_regulator[ESRAM_34])) { + err = PTR_ERR(osalEnv.esram_regulator[ESRAM_34]); + pr_err("CM: Error while retrieving the regulator for esram34: %d\n", err); + osalEnv.esram_regulator[ESRAM_34] = NULL; + goto out_destroy_device; + } + + /* Fill in the descriptors needed by CM_ENGINE_Init() */ + getNmfHwMappingDesc(&nmfHwMappingDesc); + + /* Initialize Component Manager */ + err = CM_ENGINE_Init(&nmfHwMappingDesc, &nmfConfigDesc); + if (err != CM_OK) { + pr_err("CM: CM_Init failed with error code: %d\n", err); + err = -EAGAIN; + goto out_destroy_device; + } else { + pr_info("Initialize NMF %d.%d.%d Component Manager......\n", + VERSION_MAJOR(NMF_VERSION), + VERSION_MINOR(NMF_VERSION), + VERSION_PATCH(NMF_VERSION)); + pr_info("[ CM Linux Driver %d.%d.%d ]\n", + VERSION_MAJOR(NMF_VERSION), + VERSION_MINOR(NMF_VERSION), + CMDRIVER_PATCH_VERSION); + } + + cm_debug_init(); + if (osal_debug_ops.domain_create) { + osal_debug_ops.domain_create(DEFAULT_SVA_DOMAIN); + osal_debug_ops.domain_create(DEFAULT_SIA_DOMAIN); + } + + /* Configure MPC Cores */ + for (i=0; i<NB_MPC; i++) { + err = configureMpc(i, &dataAllocId); + if (err) + goto out_all; + } + /* End of Component Manager initialization phase */ + + + if (cfgSemaphoreTypeHSEM) { + /* We use tasklet of mpc[0]. See comments above osalEnvironnent struct */ + tasklet_init(&osalEnv.mpc[0].tasklet, mpc_events_tasklet_handler, 0); + err = request_irq(IRQ_DB8500_HSEM, mpc_events_irq_handler, IRQF_DISABLED, + "hwsem", 0); + if (err) { + pr_err("CM: request_irq failed to register hwsem irq %i (%i)\n", + IRQ_DB8500_HSEM, err); + goto out_all; + } + } + + err = cmdma_init(); + if (err == 0) + return 0; + +out_all: + cm_debug_exit(); + free_mpc_irqs(i); + CM_ENGINE_Destroy(); + i=ARRAY_SIZE(cmld_devname); +out_destroy_device: + if (osalEnv.esram_regulator[ESRAM_12]) + regulator_put(osalEnv.esram_regulator[ESRAM_12]); + if (osalEnv.esram_regulator[ESRAM_34]) + regulator_put(osalEnv.esram_regulator[ESRAM_34]); + while (i--) + device_destroy(&cmld_class, MKDEV(cmld_major, i)); + cdev_del(&cmld_cdev); +out_destroy_chrdev: + unregister_chrdev_region(dev, ARRAY_SIZE(cmld_devname)); +out_destroy_class: + class_unregister(&cmld_class); +out: + unmapRegions(); +#ifdef CM_DEBUG_ALLOC + cleanup_debug_alloc(); +#endif + return err; +} + +/** Module exit point + * Unregister the driver. This will lead to a 'remove' call. + */ +static void __exit cmld_cleanup_module(void) +{ + unsigned i; + + if (!list_empty(&channel_list)) + pr_err("CM Driver ending with non empty channel list\n"); + if (!list_empty(&process_list)) + pr_err("CM Driver ending with non empty process list\n"); + + if (cfgSemaphoreTypeHSEM) + free_irq(IRQ_DB8500_HSEM, NULL); + free_mpc_irqs(NB_MPC); + tasklet_kill(&cmld_service_tasklet); + + if (osalEnv.esram_regulator[ESRAM_12]) + regulator_put(osalEnv.esram_regulator[ESRAM_12]); + if (osalEnv.esram_regulator[ESRAM_34]) + regulator_put(osalEnv.esram_regulator[ESRAM_34]); + for (i=0; i<ARRAY_SIZE(cmld_devname); i++) + device_destroy(&cmld_class, MKDEV(cmld_major, i)); + cdev_del(&cmld_cdev); + unregister_chrdev_region(MKDEV(cmld_major, 0), ARRAY_SIZE(cmld_devname)); + class_unregister(&cmld_class); + + CM_ENGINE_Destroy(); + + cmdma_destroy(); + unmapRegions(); +#ifdef CM_DEBUG_ALLOC + cleanup_debug_alloc(); +#endif + cm_debug_exit(); +} +module_init(cmld_init_module); +module_exit(cmld_cleanup_module); + +MODULE_AUTHOR("David Siorpaes"); +MODULE_AUTHOR("Wolfgang Betz"); +MODULE_AUTHOR("Pierre Peiffer"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Nomadik Multiprocessing Framework Component Manager Linux driver"); diff --git a/drivers/staging/nmf-cm/cmld.h b/drivers/staging/nmf-cm/cmld.h new file mode 100644 index 00000000000..17e6c55ff61 --- /dev/null +++ b/drivers/staging/nmf-cm/cmld.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef CMLD_H +#define CMLD_H + +#include <linux/kref.h> +#include <linux/mutex.h> +#include <linux/version.h> +#include <linux/wait.h> +#include <inc/nmf-limits.h> +#include "cmioctl.h" + +/** Channel state used within the per-channel private structure 'cm_channel_priv' + */ +enum channel_state { + CHANNEL_CLOSED = 0, /**< Channel already closed */ + CHANNEL_OPEN, /**< Channel still open */ +}; + +/** Component Manager per-process private structure + * It is created the first time a process opens /dev/cm0 or /dev/cm1 + */ +struct cm_process_priv +{ + struct kref ref; /**< ref count */ + struct list_head entry; /**< This entry */ + pid_t pid; /**< pid of process owner */ + struct mutex mutex; /**< per process mutex: protect memAreaDescList */ + struct list_head memAreaDescList; /**< memAreaDesc_t list */ + struct mutex host2mpcLock; /**< used to synchronize each AllocEvent + PushEvent */ +#ifdef CONFIG_DEBUG_FS + struct dentry *dir; /**< debugfs dir entry under nmf-cm/proc */ + struct dentry *comp_dir; /**< debugfs dir entry under nmf-cm/proc/..%components */ + struct dentry *domain_dir; /**< debugfs dir entry under nmf-cm/proc/..%domains */ +#endif +}; + +/** Component Manager per-channel private structure + * It is created when a user opens /dev/cm1 + */ +struct cm_channel_priv +{ + enum channel_state state; /**< Channel state */ + struct list_head entry; /**< This entry */ + struct cm_process_priv *proc; /**< Pointer to the owner process structure */ + struct list_head skelList; /**< t_skelwrapper list */ + struct mutex skelListLock; /**< skelList mutex */ + struct plist_head messageQueue; /**< queueelem_t list */ + struct mutex msgQueueLock; /**< lock used to synchronize MPC to HOST bindings + in case of multiple read (see cmld_read comments) */ + spinlock_t bh_lock; /**< lock used to synchronize add/removal of element in/from + the message queue in both user context and tasklet */ + wait_queue_head_t waitq; /**< wait queue used to block read() call */ +}; + +/** Memory area descriptor. + */ +struct memAreaDesc_t { + struct list_head list; /**< Doubly linked list descriptor */ + atomic_t count; /**< Reference counter */ + pid_t tid; /**< tid of the process this area is reserved for */ + t_cm_memory_handle handle; /**< Component Manager handle */ + unsigned int size; /**< Size */ + unsigned int physAddr; /**< Physical address */ + unsigned int kernelLogicalAddr; /**< Logical address as seen by kernel */ + unsigned int userLogicalAddr; /**< Logical address as seen by user */ + unsigned int mpcPhysAddr; /**< Physicaladdress as seen by MPC */ + struct cm_process_priv* procPriv; /**< link to per process private structure */ +}; + +extern struct list_head channel_list; /**< List of all allocated channel structures */ +extern struct list_head process_list; /**< List of all allocated process private structure */ +#ifdef CONFIG_DEBUG_FS +extern bool cmld_user_has_debugfs; /**< Whether user side has proper support of debugfs to take a dump */ +extern bool cmld_dump_ongoing; /**< Whether a dump is on-going */ +#endif + +/* Structure used to embed DSP traces */ +#define TB_MEDIA_FILE 0x1C +#define TB_DEV_PC 0x10 +#define TB_DEV_TRACEBOX 0x4C +#define TB_TRACEBOX 0x7C +#define DEFAULT_RECEIVERR_OBJ 0x0 +#define DEFAULT_SENDER_OBJ 0x0A +#define TB_TRACE_MSG 0x94 +#define TB_TRACE_EXCEPTION_MSG 0x95 +#define TB_EXCEPTION_LONG_OVRF_PACKET 0x07 +#define OST_MASTERID 0x08 +#define OST_VERSION 0x05 +#define ENTITY 0xAA +#define PROTOCOL_ID 0x03 +#define BTRACE_HEADER_SIZE 4 + +struct __attribute__ ((__packed__)) mmdsp_trace { + u8 media; + u8 receiver_dev; + u8 sender_dev; + u8 unused; + u16 size; + u8 receiver_obj; + u8 sender_obj; + u8 transaction_id; + u8 message_id; + u8 master_id; + u8 channel_id; + u64 timestamp; + u8 ost_master_id; + u8 ost_version; + u8 entity; + u8 protocol_id; + u8 length; + u64 timestamp2; + u32 component_id; + u32 trace_id; + u8 btrace_hdr_size; + u8 btrace_hdr_flag; + u8 btrace_hdr_category; + u8 btrace_hdr_subcategory; + u32 parent_handle; + u32 component_handle; + u32 params[4]; +}; + +/** Lock/unlock per process mutex + * + * \note Must be taken before tasklet_disable (if necessary)! + */ +#define lock_process_uninterruptible(proc) (mutex_lock(&proc->mutex)) +#define lock_process(proc) (mutex_lock_killable(&proc->mutex)) +#define unlock_process(proc) (mutex_unlock(&proc->mutex)) + + + +int cmld_InstantiateComponent(struct cm_process_priv *, CM_InstantiateComponent_t __user *); +int cmld_BindComponentFromCMCore(struct cm_process_priv *, + CM_BindComponentFromCMCore_t __user *); +int cmld_UnbindComponentFromCMCore(CM_UnbindComponentFromCMCore_t __user *); +int cmld_BindComponentToCMCore(struct cm_channel_priv *, CM_BindComponentToCMCore_t __user *); +int cmld_UnbindComponentToCMCore(struct cm_process_priv*, CM_UnbindComponentToCMCore_t __user *); +int cmld_BindComponentAsynchronous(struct cm_process_priv*, CM_BindComponentAsynchronous_t __user *); +int cmld_UnbindComponentAsynchronous(struct cm_process_priv*, CM_UnbindComponentAsynchronous_t __user *); +int cmld_BindComponent(struct cm_process_priv*, CM_BindComponent_t __user *); +int cmld_UnbindComponent(struct cm_process_priv*, CM_UnbindComponent_t __user *); +int cmld_BindComponentToVoid(struct cm_process_priv*, CM_BindComponentToVoid_t __user *); +int cmld_DestroyComponent(struct cm_process_priv*, CM_DestroyComponent_t __user *); +int cmld_CreateMemoryDomain(struct cm_process_priv*, CM_CreateMemoryDomain_t __user *); +int cmld_CreateMemoryDomainScratch(struct cm_process_priv*, CM_CreateMemoryDomainScratch_t __user *); +int cmld_DestroyMemoryDomain(CM_DestroyMemoryDomain_t __user *); +int cmld_GetDomainCoreId(CM_GetDomainCoreId_t __user *); +int cmld_AllocMpcMemory(struct cm_process_priv *, CM_AllocMpcMemory_t __user *); +int cmld_FreeMpcMemory(struct cm_process_priv *, CM_FreeMpcMemory_t __user *); +int cmld_GetMpcMemoryStatus(CM_GetMpcMemoryStatus_t __user *); +int cmld_StartComponent(struct cm_process_priv *, CM_StartComponent_t __user *); +int cmld_StopComponent(struct cm_process_priv *, CM_StopComponent_t __user *); +int cmld_GetMpcLoadCounter(CM_GetMpcLoadCounter_t __user *); +int cmld_GetComponentDescription(struct cm_process_priv *, CM_GetComponentDescription_t __user *); +int cmld_GetComponentListHeader(struct cm_process_priv *, CM_GetComponentListHeader_t __user *); +int cmld_GetComponentListNext(struct cm_process_priv *, CM_GetComponentListNext_t __user *); +int cmld_GetComponentRequiredInterfaceNumber(struct cm_process_priv *, + CM_GetComponentRequiredInterfaceNumber_t __user *); +int cmld_GetComponentRequiredInterface(struct cm_process_priv *, + CM_GetComponentRequiredInterface_t __user *); +int cmld_GetComponentRequiredInterfaceBinding(struct cm_process_priv *, + CM_GetComponentRequiredInterfaceBinding_t __user *); +int cmld_GetComponentProvidedInterfaceNumber(struct cm_process_priv *, + CM_GetComponentProvidedInterfaceNumber_t __user *); +int cmld_GetComponentProvidedInterface(struct cm_process_priv *, + CM_GetComponentProvidedInterface_t __user *); +int cmld_GetComponentPropertyNumber(struct cm_process_priv *, + CM_GetComponentPropertyNumber_t __user *); +int cmld_GetComponentPropertyName(struct cm_process_priv *, CM_GetComponentPropertyName_t __user *); +int cmld_GetComponentPropertyValue(struct cm_process_priv *, CM_GetComponentPropertyValue_t __user *); +int cmld_ReadComponentAttribute(struct cm_process_priv *, CM_ReadComponentAttribute_t __user *); +int cmld_GetExecutiveEngineHandle(struct cm_process_priv *, CM_GetExecutiveEngineHandle_t __user *); +int cmld_SetMode(CM_SetMode_t __user *); +int cmld_GetRequiredComponentFiles(struct cm_process_priv *cmPriv, + CM_GetRequiredComponentFiles_t __user *); +int cmld_Migrate(CM_Migrate_t __user *); +int cmld_Unmigrate(CM_Unmigrate_t __user *); +int cmld_SetupRelinkArea(struct cm_process_priv *, CM_SetupRelinkArea_t __user *); +int cmld_PushComponent(CM_PushComponent_t __user *); +int cmld_ReleaseComponent(CM_ReleaseComponent_t __user *); +int cmld_PrivGetMPCMemoryDesc(struct cm_process_priv *, CM_PrivGetMPCMemoryDesc_t __user *); +int cmld_PrivReserveMemory(struct cm_process_priv *, unsigned int); +#endif diff --git a/drivers/staging/nmf-cm/configuration.c b/drivers/staging/nmf-cm/configuration.c new file mode 100644 index 00000000000..523874fc586 --- /dev/null +++ b/drivers/staging/nmf-cm/configuration.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +/** \file configuration.c + * + * Nomadik Multiprocessing Framework Linux Driver + * + */ + +#include <linux/module.h> +#include <cm/engine/api/configuration_engine.h> +#include <cm/engine/configuration/inc/configuration_status.h> +#include <cm/engine/power_mgt/inc/power.h> +#include "osal-kernel.h" + +/* Per-driver environment */ +struct OsalEnvironment osalEnv = +{ + .mpc = { + { + .coreId = SVA_CORE_ID, + .name = "sva", + .base_phys = (void*)U8500_SVA_BASE, + .interrupt0 = IRQ_DB8500_SVA, + .interrupt1 = IRQ_DB8500_SVA2, + .mmdsp_regulator = NULL, + .pipe_regulator = NULL, + .monitor_tsk = NULL, + .hwmem_code = NULL, + .hwmem_data = NULL, + .trace_read_count = ATOMIC_INIT(0), + }, + { + .coreId = SIA_CORE_ID, + .name = "sia", + .base_phys = (void*)U8500_SIA_BASE, + .interrupt0 = IRQ_DB8500_SIA, + .interrupt1 = IRQ_DB8500_SIA2, + .mmdsp_regulator = NULL, + .pipe_regulator = NULL, + .monitor_tsk = NULL, + .hwmem_code = NULL, + .hwmem_data = NULL, + .trace_read_count = ATOMIC_INIT(0), + } + }, + .esram_regulator = { NULL, NULL}, + .dsp_sleep = { + .sia_auto_pm_enable = PRCMU_AUTO_PM_OFF, + .sia_power_on = 0, + .sia_policy = PRCMU_AUTO_PM_POLICY_DSP_OFF_HWP_OFF, + .sva_auto_pm_enable = PRCMU_AUTO_PM_OFF, + .sva_power_on = 0, + .sva_policy = PRCMU_AUTO_PM_POLICY_DSP_OFF_HWP_OFF, + }, + .dsp_idle = { + .sia_auto_pm_enable = PRCMU_AUTO_PM_OFF, + .sia_power_on = 0, + .sia_policy = PRCMU_AUTO_PM_POLICY_DSP_OFF_HWP_OFF, + .sva_auto_pm_enable = PRCMU_AUTO_PM_OFF, + .sva_power_on = 0, + .sva_policy = PRCMU_AUTO_PM_POLICY_DSP_OFF_HWP_OFF, + }, +}; + +module_param_call(cm_debug_level, param_set_int, param_get_int, + &cm_debug_level, S_IWUSR|S_IRUGO); +MODULE_PARM_DESC(cm_debug_level, "Debug level of NMF Core"); + +module_param_call(cm_error_break, param_set_bool, param_get_bool, + &cm_error_break, S_IWUSR|S_IRUGO); +MODULE_PARM_DESC(cm_error_break, "Stop on error (in an infinite loop, for debugging purpose)"); + +module_param_call(cmIntensiveCheckState, param_set_bool, param_get_bool, + &cmIntensiveCheckState, S_IWUSR|S_IRUGO); +MODULE_PARM_DESC(cmIntensiveCheckState, "Add additional intensive checks"); + +DECLARE_MPC_PARAM(SVA, SDRAM_DATA_SIZE, "", 1); + +DECLARE_MPC_PARAM(SIA, 0, "\n\t\t\t(0 means shared with SVA)", 2); + +bool cfgCommunicationLocationInSDRAM = true; +module_param(cfgCommunicationLocationInSDRAM, bool, S_IRUGO); +MODULE_PARM_DESC(cfgCommunicationLocationInSDRAM, "Location of communications (SDRAM or ESRAM)"); + +bool cfgSemaphoreTypeHSEM = true; +module_param(cfgSemaphoreTypeHSEM, bool, S_IRUGO); +MODULE_PARM_DESC(cfgSemaphoreTypeHSEM, "Semaphore used (HSEM or LSEM)"); + +int cfgESRAMSize = ESRAM_SIZE; +module_param(cfgESRAMSize, uint, S_IRUGO); +MODULE_PARM_DESC(cfgESRAMSize, "Size of ESRAM used in the CM (in Kb)"); + +static int set_param_powerMode(const char *val, const struct kernel_param *kp) +{ + /* No equals means "set"... */ + if (!val) val = "1"; + + /* One of =[yYnN01] */ + switch (val[0]) { + case 'y': case 'Y': case '1': + CM_ENGINE_SetMode(CM_CMD_DBG_MODE, 0); + break; + case 'n': case 'N': case '0': + CM_ENGINE_SetMode(CM_CMD_DBG_MODE, 1); + break; + default: + return -EINVAL; + } + return 0; +} + +module_param_call(powerMode, set_param_powerMode, param_get_bool, &powerMode, S_IWUSR|S_IRUGO); +MODULE_PARM_DESC(powerMode, "DSP power mode enable"); + +int init_config(void) +{ + if (cfgMpcSDRAMCodeSize_SVA == 0 || cfgMpcSDRAMCodeSize_SIA == 0) { + pr_err("SDRAM code size must be greater than 0\n"); + return -EINVAL; + } + + if (cfgMpcSDRAMDataSize_SVA == 0) { + pr_err("SDRAM data size for SVA must be greater than 0\n"); + return -EINVAL; + } + + osalEnv.mpc[SVA].nbYramBanks = cfgMpcYBanks_SVA; + osalEnv.mpc[SVA].eeId = cfgSchedulerTypeHybrid_SVA ? HYBRID_EXECUTIVE_ENGINE : SYNCHRONOUS_EXECUTIVE_ENGINE; + osalEnv.mpc[SVA].sdram_code.size = cfgMpcSDRAMCodeSize_SVA * ONE_KB; + osalEnv.mpc[SVA].sdram_data.size = cfgMpcSDRAMDataSize_SVA * ONE_KB; + osalEnv.mpc[SVA].base.size = 128*ONE_KB; //we expose only TCM24 + spin_lock_init(&osalEnv.mpc[SVA].trace_reader_lock); + + osalEnv.mpc[SIA].nbYramBanks = cfgMpcYBanks_SIA; + osalEnv.mpc[SIA].eeId = cfgSchedulerTypeHybrid_SIA ? HYBRID_EXECUTIVE_ENGINE : SYNCHRONOUS_EXECUTIVE_ENGINE; + osalEnv.mpc[SIA].sdram_code.size = cfgMpcSDRAMCodeSize_SIA * ONE_KB; + osalEnv.mpc[SIA].sdram_data.size = cfgMpcSDRAMDataSize_SIA * ONE_KB; + osalEnv.mpc[SIA].base.size = 128*ONE_KB; //we expose only TCM24 + spin_lock_init(&osalEnv.mpc[SIA].trace_reader_lock); + + return 0; +} diff --git a/drivers/staging/nmf-cm/configuration.h b/drivers/staging/nmf-cm/configuration.h new file mode 100644 index 00000000000..39416cde686 --- /dev/null +++ b/drivers/staging/nmf-cm/configuration.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef CONFIGURATION_H +#define CONFIGURATION_H + +/** Peripherals description. + * Some of these values are taken from kernel header description (which should be the + * right place of these definition); the missing ones are defined here. + */ + +#include <linux/version.h> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) +#include <generated/autoconf.h> +#else +#include <linux/autoconf.h> +#endif + +/* Embedded Static RAM base address */ +/* config: 0-64k: secure */ +#define ESRAM_BASE (U8500_ESRAM_BASE + U8500_ESRAM_DMA_LCPA_OFFSET) + +/* + * Embedded ram size for CM (in Kb) + * 5 banks of 128k: skip the first half bank (secure) and the last + * one (used for MCDE/B2R2), but include DMA part (4k after the secure part) + * to give access from DSP side + */ +#define ESRAM_SIZE 448 +enum { + ESRAM_12, + ESRAM_34, + NB_ESRAM, +}; + +/** MPCs */ +enum { + SVA, + SIA, + NB_MPC, +}; +#define COREIDX(id) (id-1) + +/** Base address of shared SDRAM: use upper SDRAM. We reserve a rather */ +#define SDRAM_CODE_SIZE_SVA (2*ONE_KB) +#define SDRAM_CODE_SIZE_SIA (2*ONE_KB) +#define SDRAM_DATA_SIZE (8*ONE_KB) + +extern bool cfgCommunicationLocationInSDRAM; +extern bool cfgSemaphoreTypeHSEM; +extern int cfgESRAMSize; + +int init_config(void); + +#define DECLARE_MPC_PARAM(mpc, sdramDataSize, extension, ybank) \ + static unsigned int cfgMpcYBanks_##mpc = ybank; \ + module_param(cfgMpcYBanks_##mpc, uint, S_IRUGO); \ + MODULE_PARM_DESC(cfgMpcYBanks_##mpc, "Nb of Y-Ram banks used on " #mpc); \ + \ + static bool cfgSchedulerTypeHybrid_##mpc = 1; \ + module_param(cfgSchedulerTypeHybrid_##mpc, bool, S_IRUGO); \ + MODULE_PARM_DESC(cfgSchedulerTypeHybrid_##mpc, "Scheduler used on " #mpc " (Hybrid or Synchronous)"); \ + \ + static unsigned int cfgMpcSDRAMCodeSize_##mpc = SDRAM_CODE_SIZE_##mpc; \ + module_param(cfgMpcSDRAMCodeSize_##mpc, uint, S_IRUGO); \ + MODULE_PARM_DESC(cfgMpcSDRAMCodeSize_##mpc, "Size of code segment on " #mpc " (in Kb)"); \ + \ + static unsigned int cfgMpcSDRAMDataSize_##mpc = sdramDataSize; \ + module_param(cfgMpcSDRAMDataSize_##mpc, uint, S_IRUGO); \ + MODULE_PARM_DESC(cfgMpcSDRAMDataSize_##mpc, "Size of data segment on " #mpc " (in Kb)" extension) + +#endif diff --git a/drivers/staging/nmf-cm/ee/api/panic.idt b/drivers/staging/nmf-cm/ee/api/panic.idt new file mode 100644 index 00000000000..71996b8a55e --- /dev/null +++ b/drivers/staging/nmf-cm/ee/api/panic.idt @@ -0,0 +1,74 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \defgroup NMF_EE_TYPE Execution Engine Common Type Definitions + * \ingroup COMMON + */ + +#ifndef __INC_PANIC_IDT +#define __INC_PANIC_IDT + +/*! + * \brief Panic reason type + * + * For values, see \ref t_panic_reasonDescription. + * + * \ingroup NMF_EE_TYPE + */ +typedef t_uint8 t_panic_reason; + +/*! + * \brief The different panic reasons + * + * \verbatim + * Reason | Information | Behavior + * ------------------------------------------------------------------- + * INTERNAL_PANIC | Not interpreted | Fatal panic, stop MPC + * MPC_NOT_RESPONDING_PANIC | Not interpreted | Fatal panic, stop MPC + * USER_STACK_OVERFLOW | Faulting address & SPu | Fatal panic, stop MPC + * SYSTEM_STACK_OVERFLOW | Faulting address & SPu | Fatal panic, stop MPC + * UNALIGNED_LONG_ACCESS | Indicative Faulting address & SPu | Fatal panic, stop MPC + * EVENT_FIFO_OVERFLOW | 0 | Abort current task, stop MPC + * PARAM_FIFO_OVERFLOW | 0 | idem + * INTERFACE_NOT_BINDED | 0 | idem + * USER_PANIC | Not interpreted | idem + * UNBIND_INTERRUPT | Interrupt number | Do nothing, just return from interrupt. + * EVENT_FIFO_IN_USE | Destroy event Fifo while event already schedule (only for HostEE) + * \endverbatim + * + * \ingroup NMF_EE_TYPE + */ +typedef enum { + INTERNAL_PANIC = 1, + MPC_NOT_RESPONDING_PANIC = 2, + USER_STACK_OVERFLOW = 3, + SYSTEM_STACK_OVERFLOW = 4, + UNALIGNED_LONG_ACCESS = 5, + EVENT_FIFO_OVERFLOW = 6, + PARAM_FIFO_OVERFLOW = 7, + INTERFACE_NOT_BINDED = 8, + USER_PANIC = 9, + UNBIND_INTERRUPT = 10, + EVENT_FIFO_IN_USE = 11, + RESERVED_PANIC = 2 //for COMPATIBILITY with previous versions of NMF, to be deprecated +} t_panic_reasonDescription; + +/*! + * \brief Define the source of the panic + * + * It indicates the source core of the panic message.\n + * It gives the member to use within \ref t_nmf_panic_data (which is a member of the t_nmf_service_data service data structure). + + * \ingroup NMF_EE_TYPE + */ +typedef enum { + HOST_EE, //!< If the source is the Executive Engine running on the ARM Core + MPC_EE //!< If the source is the Executive Engine running on one of the MPC Core +} t_panic_source; + +#endif diff --git a/drivers/staging/nmf-cm/ee/api/trace.idt b/drivers/staging/nmf-cm/ee/api/trace.idt new file mode 100644 index 00000000000..f4d4c8615e2 --- /dev/null +++ b/drivers/staging/nmf-cm/ee/api/trace.idt @@ -0,0 +1,30 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \defgroup NMF_EE_TYPE Execution Engine Common Type Definitions + * \ingroup COMMON + */ + +#ifndef __INC_TRACE_IDT +#define __INC_TRACE_IDT + +struct t_nmf_trace +{ + t_uint32 revision; + t_uint32 timeStamp; + t_uint32 componentId; + t_uint32 traceId; + t_uint32 paramOpt; + t_uint32 componentHandle; + t_uint32 parentHandle; + t_uint32 params[4]; +}; + +#define TRACE_BUFFER_SIZE 128 + +#endif diff --git a/drivers/staging/nmf-cm/inc/nmf-def.h b/drivers/staging/nmf-cm/inc/nmf-def.h new file mode 100644 index 00000000000..7cdea18996b --- /dev/null +++ b/drivers/staging/nmf-cm/inc/nmf-def.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + /*! + * \brief NMF Version. + * + * This file contains the NMF Version. + * + * \defgroup NMF_VERSION NMF Version + * \ingroup COMMON + */ + +#ifndef __INC_NMF_DEF_H +#define __INC_NMF_DEF_H + +/*! + * \brief Current NMF version number + * + * \ingroup NMF_VERSION + */ +#define NMF_VERSION ((2 << 16) | (10 << 8) | (122)) + +/*! + * \brief Get NMF major version corresponding to NMF version number + * \ingroup NMF_VERSION + */ +#define VERSION_MAJOR(version) (((version) >> 16) & 0xFF) +/*! + * \brief Get NMF minor version corresponding to NMF version number + * \ingroup NMF_VERSION + */ +#define VERSION_MINOR(version) (((version) >> 8) & 0xFF) +/*! + * \brief Get NMF patch version corresponding to NMF version number + * \ingroup NMF_VERSION + */ +#define VERSION_PATCH(version) (((version) >> 0) & 0xFF) + +#endif /* __INC_NMF_DEF_H */ diff --git a/drivers/staging/nmf-cm/inc/nmf-limits.h b/drivers/staging/nmf-cm/inc/nmf-limits.h new file mode 100644 index 00000000000..374795f91e0 --- /dev/null +++ b/drivers/staging/nmf-cm/inc/nmf-limits.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Common Nomadik Multiprocessing Framework limits definition + * + * This file contains the limit definitions used into NMF. + * + * \warning Don't modify it since it is also hardcoded in tools + * + * \defgroup NMF_LIMITS NMF limits definition + * \ingroup COMMON + */ +#ifndef __INC_NMF_LIMITS_H +#define __INC_NMF_LIMITS_H + +/*! + * \brief Maximum interface name length + * + * Define the maximum interface name length allowed by NMF. + * + * \ingroup NMF_LIMITS + */ +#define MAX_INTERFACE_NAME_LENGTH 32 + +/*! + * \brief Maximum interface method name length + * + * Define the maximum interface method name length allowed by NMF. + * + * \ingroup NMF_LIMITS + */ +#define MAX_INTERFACE_METHOD_NAME_LENGTH 64 + +/*! + * \brief Maximum interface type name length + * + * Define the maximum interface type name length allowed by NMF. + * + * \ingroup NMF_LIMITS + */ +#define MAX_INTERFACE_TYPE_NAME_LENGTH 128 + + +/*! + * \brief Maximum template name length + * + * Define the maximum template name length allowed by NMF. + * + * \ingroup NMF_LIMITS + */ +#define MAX_TEMPLATE_NAME_LENGTH 128 + +/*! + * \brief Maximum component local name length + * + * Define the maximum component local name length inside a composite component allowed by NMF. + * + * \ingroup NMF_LIMITS + */ +#define MAX_COMPONENT_NAME_LENGTH 32 + +/*! + * \brief Maximum property name length + * + * Define the maximum property name length allowed by NMF. + * + * \ingroup NMF_LIMITS + */ +#define MAX_PROPERTY_NAME_LENGTH 32 + +/*! + * \brief Maximum property value length + * + * Define the maximum property value length allowed by NMF. + * + * \ingroup NMF_LIMITS + */ +#define MAX_PROPERTY_VALUE_LENGTH 128 + +/*! + * \brief Maximum attribute name length + * + * Define the maximum attribute name length allowed by NMF. + * + * \ingroup NMF_LIMITS + */ +#define MAX_ATTRIBUTE_NAME_LENGTH 32 + +/*! + * \brief Maximum fifo size allowed for binding component + * + * Define the maximum fifo size allowed for binding component allowed by NMF when calling + * CM_BindComponentFromHost and CM_BindComponentAsynchronous. + * + * \ingroup NMF_LIMITS + */ +#define MAX_COMMUNICATION_FIFO_SIZE 256 + +#define MAX_COMPONENT_FILE_PATH_LENGTH 1024 + +#endif /* __INC_NMF_LIMITS_H */ diff --git a/drivers/staging/nmf-cm/inc/nmf-tracedescription.h b/drivers/staging/nmf-cm/inc/nmf-tracedescription.h new file mode 100644 index 00000000000..bce589079b9 --- /dev/null +++ b/drivers/staging/nmf-cm/inc/nmf-tracedescription.h @@ -0,0 +1,323 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief NMF xti/stm trace format description + * + * \defgroup NMF_TRACE_FORMAT NMF xti/stm trace format description + * + * The NMF trace is output by either xti ip on 8815 or stm ip on 8820 and 8500. + * Each type of trace is output on a dedicated channel. Following is a description + * of each of this traces. + * + * Traces have generally a timestamp added by hardware but is not described here. + * \ingroup NMF_ABI + */ +#ifndef TRACE_FORMAT_H_ +#define TRACE_FORMAT_H_ + +#include <inc/nmf-limits.h> + +/*! + * \brief XTI/STM Channel where trace are dumped + * + * \note This type is only for defining constants, please not reference it. + * + * \note Ever if this format is able to be generated on same channel, Host EE & CM channel are separated + * in order to avoir concurrency and access STM IP without require mutual exclusion. + * + * \ingroup NMF_TRACE_FORMAT + */ +typedef enum { + MPC_EE_CHANNEL = 100, //!< MPC EE channel (MPC activity) in 32bits bundle + CM_CHANNEL = 101, //!< CM channel (MPC deployment) in 64bits bundle + HOST_EE_CHANNEL = 151 //!< Host EE channel (deployment & activity) in 64bits bundle +} t_nmfTraceChannelDescription; + +/*! + * \brief Message trace type + * + * \note This type is only for defining constants, please not reference it. + * + * \ingroup NMF_TRACE_FORMAT + */ +typedef enum { + TRACE_TYPE_RESET = 1, //!< Reset trace type + TRACE_TYPE_COMPONENT = 2, //!< Component instantiate trace type + TRACE_TYPE_BIND = 3, //!< Component bind trace type + TRACE_TYPE_METHOD = 4, //!< Component method trace type + TRACE_TYPE_ACTIVITY = 5, //!< Activity trace type + TRACE_TYPE_PANIC = 6, //!< Panic trace type + TRACE_TYPE_COMMUNICATION = 7, //!< Communication trace type + TRACE_TYPE_ALLOCATOR = 8, //!< Allocator trace type + TRACE_TYPE_ALLOC = 9, //!< Alloc trace type + TRACE_TYPE_USER = 10 //!< User trace type +} t_nmfTraceTypeDescription; + +#define TRACE_MAJOR_VERSION 1 //!< Current major trace version number \ingroup NMF_TRACE_FORMAT +#define TRACE_MINOR_VERSION 2 //!< Current minor trace version number \ingroup NMF_TRACE_FORMAT + +/*! + * \brief Trace header description. + * + * \note XTI will add 64bits time-stamp in first field of this structure, but not generated by us ! + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTraceChannelHeader { + // t_uint64 timeStamp; + t_uint8 traceType; //!< Trace type + t_uint8 reserved; + t_uint16 traceSize; //!< Trace size (depending on trace type description) +}; + +/*! + * \brief Trace header union + * + * The purpose of this is to optimize header setting in one instruction. + * + * \ingroup NMF_TRACE_FORMAT + */ +typedef union { + struct t_nmfTraceChannelHeader s; + t_uint32 v; +} t_nmfTraceChannelHeaderUnion; + + +/*! + * \brief Trace reset description + * + * Inform tools to reset their internal state because network will be dumped new time. + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTraceReset { + t_nmfTraceChannelHeaderUnion header; //!< Trace header + + t_uint16 minorVersion; //!< NMF trace format minor version + t_uint16 majorVersion; //!< NMF trace format major version +}; + +/** + * \brief Component instantiation trace command description + * + * \ingroup NMF_TRACE_FORMAT + */ +typedef enum { + TRACE_COMPONENT_COMMAND_ADD = 0x1, + TRACE_COMPONENT_COMMAND_REMOVE = 0x2 +} t_nmfTraceComponentCommandDescription; + + +/*! + * \brief Component instantiation trace description + * + * Component instantiation trace is generated each time an instance of a component is added or removed. + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTraceComponent { + t_nmfTraceChannelHeaderUnion header; //!< Trace header + + t_uint16 command; //!< See \ref t_nmfTraceComponentCommandDescription + t_uint16 domainId; //!< In CM: 0x01:Arm | 0x02:SAA | 0x03:SVA | 0x04:SIA, in SMPEE: 0x1 + t_uint32 componentContext; //!< Component context belonging domain (DSP this or ARM class this) + t_uint32 componentUserContext; //!< User friendly component Id belonging the channel (CM handle or ARM class this) + t_uint8 componentLocalName[MAX_COMPONENT_NAME_LENGTH]; //!< local name of component as given by user (null terminated) + t_uint8 componentTemplateName[MAX_TEMPLATE_NAME_LENGTH];//!< template name of component (null terminated) +}; + +/** + * \brief Component binding trace command description + * + * \ingroup NMF_TRACE_FORMAT + */ +typedef enum { + TRACE_BIND_COMMAND_BIND_SYNCHRONOUS = 0x1, + TRACE_BIND_COMMAND_UNBIND_SYNCHRONOUS = 0x2, + TRACE_BIND_COMMAND_BIND_ASYNCHRONOUS = 0x3, + TRACE_BIND_COMMAND_UNBIND_ASYNCHRONOUS = 0x4 +} t_nmfTraceBindCommandDescription; + +/** + * \brief Component binding trace description + * + * \note clientComponentContext & serverComponentContext take value 0xffffffff when client or server are Component Manager. + * \note serverComponentContext take value 0x00000000 when binding to void. + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTraceBind { + t_nmfTraceChannelHeaderUnion header; //!< Trace header + + t_uint16 command; //!< See \ref t_nmfTraceBindCommandDescription + t_uint16 reserved; + t_uint16 clientDomainId; //!< In CM: 0x01:Arm | 0x02:SAA | 0x03:SVA | 0x04:SIA, in SMPEE: 0x1 + t_uint16 serverDomainId; //!< In CM: 0x01:Arm | 0x02:SAA | 0x03:SVA | 0x04:SIA, in SMPEE: 0x1 + t_uint32 clientComponentContext; //!< Component context belonging domain (DSP this or ARM class this) + t_uint32 serverComponentContext; //!< Component context belonging domain (DSP this or ARM class this) + t_uint8 requiredItfName[MAX_INTERFACE_NAME_LENGTH]; //!< Required interface name + t_uint8 providedItfName[MAX_INTERFACE_NAME_LENGTH]; //!< Provided interface name +}; + +/*! + * \brief Component interface method name trace description + * + * For each methods of each interfaces provided by a component, one such trace is dumped. + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTraceMethod { + t_nmfTraceChannelHeaderUnion header; //!< Trace header + + t_uint16 domainId; //!< In CM: 0x01:Arm | 0x02:SAA | 0x03:SVA | 0x04:SIA, in SMPEE: 0x1 + t_uint16 reserved; + t_uint32 methodId; //!< Unique Method Id belonging the component + t_uint32 componentContext; //!< Component context belonging domain (DSP this or ARM class this) + t_uint8 methodName[MAX_INTERFACE_METHOD_NAME_LENGTH]; //!< Symbolic method name +}; + +/** + * \brief Activity trace trace command description + * + * \ingroup NMF_TRACE_FORMAT + */ +typedef enum { + TRACE_ACTIVITY_START = 0x1, //!< Start method + TRACE_ACTIVITY_END = 0x2, //!< End method + TRACE_ACTIVITY_POST = 0x3, //!< Post method + TRACE_ACTIVITY_CALL = 0x4, //!< Synchronous call method + TRACE_ACTIVITY_RETURN = 0x5 //!< Synchronous return method +} t_nmfTraceActivityCommandDescription; + +/*! + * \brief Execution Engine scheduling activity trace description + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTraceActivity { + t_nmfTraceChannelHeaderUnion header; //!< Trace header + + t_uint16 command; //!< See \ref t_nmfTraceActivityCommandDescription + t_uint16 domainId; //!< In CM: 0x01:Arm | 0x02:SAA | 0x03:SVA | 0x04:SIA + t_uint32 componentContext; //!< Unique component Id (Component Handle for CM, Component this for EE) + t_uint32 methodId; //!< Unique Method Id belonging the component +}; + +/** + * \brief Component instantiation trace command description + * + * \ingroup NMF_TRACE_FORMAT + */ +typedef enum { + TRACE_COMMUNICATION_COMMAND_SEND = 0x1, + TRACE_COMMUNICATION_COMMAND_RECEIVE = 0x2 +} t_nmfTraceCommunicationCommandDescription; + +/** + * \brief Inter-processor communication signaling trace description + * + * Use when trigging interrupt through core. + * + * \note Not used on SMP EE + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTraceCommunication { + t_nmfTraceChannelHeaderUnion header; //!< Trace header + + t_uint16 command; //!< See \ref t_nmfTraceCommunicationCommandDescription + t_uint16 reserved_0; + t_uint16 domainId; //!< In CM: 0x01:Arm | 0x02:SAA | 0x03:SVA | 0x04:SIA + t_uint16 remoteDomainId; //!< In CM: 0x01:Arm | 0x02:SAA | 0x03:SVA | 0x04:SIA +}; + +/** + * \brief Component instantiation trace command description + * + * \ingroup NMF_TRACE_FORMAT + */ +typedef enum { + TRACE_ALLOCATOR_COMMAND_CREATE = 0x1, + TRACE_ALLOCATOR_COMMAND_DESTROY = 0x2 +} t_nmfTraceAllocatorCommandDescription; + +/*! + * \brief Panic trace description + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTraceAllocator { + t_nmfTraceChannelHeaderUnion header; //!< Trace header + + t_uint16 command; //!< See \ref t_nmfTraceAllocatorCommandDescription + t_uint16 allocId; //!< Memory allocator ID + t_uint32 size; //!< Memory allocator size + t_uint8 name[32]; //!< Memory allocator name +}; + +/** + * \brief Component instantiation trace command description + * + * \ingroup NMF_TRACE_FORMAT + */ +typedef enum { + TRACE_ALLOC_COMMAND_ALLOC = 0x1, + TRACE_ALLOC_COMMAND_FREE = 0x2 +} t_nmfTraceAllocCommandDescription; + +/*! + * \brief Panic trace description + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTraceAlloc { + t_nmfTraceChannelHeaderUnion header; //!< Trace header + + t_uint16 command; //!< See \ref t_nmfTraceAllocatorCommandDescription + t_uint16 allocId; //!< Memory allocator ID + t_uint32 offset; //!< Memory chunk offet + t_uint32 size; //!< Memory chunk size +}; + +/*! + * \brief Panic trace description + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTracePanic { + t_nmfTraceChannelHeaderUnion header; //!< Trace header + + t_uint16 reason; //!< See \ref t_panic_reason for description + t_uint16 domainId; //!< In CM: 0x01:Arm | 0x02:SAA | 0x03:SVA | 0x04:SIA + t_uint32 componentContext; //!< Unique component Id (Component Handle for CM, Component this for EE) + t_uint32 information1; //!< Reason dependent information 1st + t_uint32 information2; //!< Reason dependent information 2nd +}; + +/*! + * \brief User trace description + * + * \ingroup NMF_TRACE_FORMAT + */ +struct t_nmfTraceUser { + t_nmfTraceChannelHeaderUnion header; //!< Trace header + + t_uint32 key; //!< User key + t_uint16 domainId; //!< In CM: 0x01:Arm | 0x02:SAA | 0x03:SVA | 0x04:SIA + t_uint16 reserved; + t_uint32 componentContext; //!< Unique component Id (Component Handle for CM, Component this for EE) + t_uint32 callerAddress; //!< Unique code address belonging the component +}; + +/* +struct t_nmfTracePower{ + struct t_nmfTraceChannelHeader header; +}; +*/ + +#endif /* TRACE_FORMAT_H_ */ diff --git a/drivers/staging/nmf-cm/inc/nmf_type.idt b/drivers/staging/nmf-cm/inc/nmf_type.idt new file mode 100644 index 00000000000..dda547a463e --- /dev/null +++ b/drivers/staging/nmf-cm/inc/nmf_type.idt @@ -0,0 +1,63 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +#ifndef NMF_TYPE_H_ +#define NMF_TYPE_H_ + +/*! + * \defgroup NMF_COMMON_TYPE NMF Common Type + * \ingroup COMMON + */ + +/*! + * \brief Error type returned by NMF API routines + * + * Possible value describe by \ref t_nmf_errorDescription + * + * \ingroup NMF_COMMON_TYPE + */ +typedef t_sint8 t_nmf_error; + +/*! + * \brief Error type values + * + * \ingroup NMF_COMMON_TYPE + */ +typedef enum { + NMF_OK = 0, //!< No error + NMF_INVALID_PARAMETER = -2, //!< Invalid parameter + NMF_NO_MORE_MEMORY = -30, //!< Out of memory + NMF_INTERFACE_NOT_BINDED = -59, //!< Try to unbind not binded interface + NMF_INTERFACE_ALREADY_BINDED = -60, //!< Try to bind already binded interface + NMF_NO_SUCH_REQUIRED_INTERFACE = -63, //!< Interface name not required by a component + NMF_NO_SUCH_PROVIDED_INTERFACE = -64, //!< Interface name not provided by a component + NMF_COMPONENT_NOT_STOPPED = -80, //!< Component must be stopped to perform operation + NMF_INVALID_COMPONENT_STATE_TRANSITION = -81, //!< Invalid component state transition caused by user action + NMF_NO_SUCH_PROPERTY = -87, //!< Property name doesn't exported by the underlying component + NMF_NO_SUCH_ATTRIBUTE = -88, //!< Attribute name not shared (exported) by a component + NMF_NO_MESSAGE = -103, //!< No message available + NMF_FLUSH_MESSAGE = -106, //!< Message send after call to EE_FlushChannel() + NMF_INTEGRATION_ERROR0 = -112, //!< OS dependent integration Error [-112 -> -121] + NMF_INTEGRATION_ERROR1 = -113, //!< OS dependent integration Error [-112 -> -121] + NMF_INTEGRATION_ERROR2 = -114, //!< OS dependent integration Error [-112 -> -121] + NMF_INTEGRATION_ERROR3 = -115, //!< OS dependent integration Error [-112 -> -121] + NMF_INTEGRATION_ERROR4 = -116, //!< OS dependent integration Error [-112 -> -121] + NMF_INTEGRATION_ERROR5 = -117, //!< OS dependent integration Error [-112 -> -121] + NMF_INTEGRATION_ERROR6 = -118, //!< OS dependent integration Error [-112 -> -121] + NMF_INTEGRATION_ERROR7 = -119, //!< OS dependent integration Error [-112 -> -121] + NMF_INTEGRATION_ERROR8 = -120, //!< OS dependent integration Error [-112 -> -121] + NMF_INTEGRATION_ERROR9 = -121 //!< OS dependent integration Error [-112 -> -121] +} t_nmf_errorDescription; + +/*! + * \brief Define t_nmf_channel type that identify a communication channel between nmf and user. + * + * \ingroup NMF_COMMON_TYPE + */ +typedef t_uint32 t_nmf_channel; + +#endif /* NMF_TYPE_H_ */ diff --git a/drivers/staging/nmf-cm/inc/type.h b/drivers/staging/nmf-cm/inc/type.h new file mode 100644 index 00000000000..3075505aee5 --- /dev/null +++ b/drivers/staging/nmf-cm/inc/type.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/* inc/type.h - Programming Model. + * + * Copyright (c) 2006, 2007, 2008 STMicroelectronics. + * + * Reproduction and Communication of this document is strictly prohibited + * unless specifically authorized in writing by STMicroelectronics. + * + * Written by NMF team. + */ +#ifndef _NMF_TYPE_H_ +#define _NMF_TYPE_H_ + +#include <inc/typedef.h> + +PUBLIC IMPORT_SHARED void NMF_LOG(const char* fmt, ...); +PUBLIC IMPORT_SHARED void NMF_PANIC(const char* fmt, ...); + +#define NMF_ASSERT(cond) do { if(!(cond)) NMF_PANIC("NMF_ASSERT at %s:%d\n", (int)__FILE__, (int)__LINE__); } while(0) + +#ifndef EXPORT_NMF_COMPONENT + #define EXPORT_NMF_COMPONENT EXPORT_SHARED +#endif + +#ifndef IMPORT_NMF_COMPONENT + #define IMPORT_NMF_COMPONENT IMPORT_SHARED +#endif + +#endif /* _NMF_TYPE_H_ */ diff --git a/drivers/staging/nmf-cm/inc/typedef.h b/drivers/staging/nmf-cm/inc/typedef.h new file mode 100644 index 00000000000..a29e6b88fde --- /dev/null +++ b/drivers/staging/nmf-cm/inc/typedef.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \defgroup COMMON Common types and definitions + * + * \defgroup NMF_COMMON NMF common definition + * \ingroup COMMON + * + * \defgroup NMF_ABI NMF ABI specification + * \warning This page is not for multimedia developers ! + */ +/*! + * \brief Primitive Type Definition + * + * \defgroup NMF_PRIMITIVE_TYPE Primitive type definition + * \ingroup COMMON + */ + +#ifndef NMF_TYPEDEF_H_ +#define NMF_TYPEDEF_H_ + +#undef PRIVATE +#define PRIVATE static //!< Private macro declaration \ingroup NMF_PRIMITIVE_TYPE + +#undef PUBLIC +#ifdef __cplusplus +#define PUBLIC extern "C" //!< Public macro declaration \ingroup NMF_PRIMITIVE_TYPE +#else +#define PUBLIC extern //!< Public macro declaration \ingroup NMF_PRIMITIVE_TYPE +#endif + +#if defined(__SYMBIAN32__) +/*! + * \brief Declared IMPORT_SHARED to allow dll/shared library creation + * + * \note Value depend on OS. + * + * \ingroup NMF_PRIMITIVE_TYPE + */ + #ifndef IMPORT_SHARED + #define IMPORT_SHARED IMPORT_C + #endif +/*! + * \brief Declared EXPORT_SHARED to allow dll/shared library creation + * + * \note Value depend on OS. + * + * \ingroup NMF_PRIMITIVE_TYPE + */ + #ifndef EXPORT_SHARED + #define EXPORT_SHARED EXPORT_C + #endif +#elif defined(LINUX) + #ifndef IMPORT_SHARED + #define IMPORT_SHARED + #endif + #ifndef EXPORT_SHARED + #define EXPORT_SHARED __attribute__ ((visibility ("default"))) + #endif +#else + #ifndef IMPORT_SHARED + #define IMPORT_SHARED + #endif + + #ifndef EXPORT_SHARED + #define EXPORT_SHARED + #endif +#endif + +/* + * Definition of type that are used by interface. + */ + +typedef unsigned int t_uword; +typedef signed int t_sword; + +#ifdef __flexcc2__ + +typedef unsigned char t_bool; + +#ifdef __mode16__ + +typedef signed char t_sint8; +typedef signed int t_sint16; +typedef signed long t_sint24; +typedef signed long t_sint32; +typedef signed long long t_sint40; +// bigger type are not handle on this mode + +typedef unsigned char t_uint8; +typedef unsigned int t_uint16; +typedef unsigned long t_uint24; +typedef unsigned long t_uint32; +typedef unsigned long long t_uint40; +// bigger type are not handle on this mode + +// shared addr type definition +//typedef __SHARED16 t_uint16 * t_shared_addr; +typedef void * t_shared_field; + +#else /* __mode16__ -> __mode24__ */ + +typedef signed char t_sint8; +typedef signed short t_sint16; +typedef signed int t_sint24; +typedef signed long t_sint32; +typedef signed long t_sint40; +typedef signed long t_sint48; +typedef signed long long t_sint56; + +typedef unsigned char t_uint8; +typedef unsigned short t_uint16; +typedef unsigned int t_uint24; +typedef unsigned long t_uint32; +typedef unsigned long t_uint40; +typedef unsigned long t_uint48; +typedef unsigned long long t_uint56; + +// shared addr type definition +//typedef __SHARED16 t_uint16 * t_shared_addr; +typedef t_uint24 t_shared_field; + +#endif /* MMDSP mode24 */ + +// shared register (ARM world) type definition +#if 0 +typedef struct { + t_uint16 lsb; + t_uint16 msb; +} t_shared_reg; +#endif +typedef t_uint32 t_shared_reg; + +typedef t_uint32 t_physical_address; + +#include <stwdsp.h> + +#else /* __flexcc2__ -> RISC 32 Bits */ + +#ifndef _HCL_DEFS_H +typedef unsigned char t_bool; //!< Boolean primitive type \ingroup NMF_PRIMITIVE_TYPE + +typedef unsigned char t_uint8; //!< Unsigned 8 bits primitive type \ingroup NMF_PRIMITIVE_TYPE +typedef signed char t_sint8; //!< Signed 8 bits primitive type \ingroup NMF_PRIMITIVE_TYPE +typedef unsigned short t_uint16; //!< Unsigned 16 bits primitive type \ingroup NMF_PRIMITIVE_TYPE +typedef signed short t_sint16; //!< Signed 16 bits primitive type \ingroup NMF_PRIMITIVE_TYPE +typedef unsigned long t_uint32; //!< Unsigned 32 bits primitive type \ingroup NMF_PRIMITIVE_TYPE +typedef signed long t_sint32; //!< Signed 32 bits primitive type \ingroup NMF_PRIMITIVE_TYPE +typedef unsigned long long t_uint64; //!< Unsigned 64 bits primitive type \ingroup NMF_PRIMITIVE_TYPE +typedef signed long long t_sint64; //!< Signed 64 bits primitive type \ingroup NMF_PRIMITIVE_TYPE + +typedef t_uint32 t_physical_address; +#endif /* _HCL_DEFS_H */ + +typedef unsigned long t_uint24; +typedef signed long t_sint24; +typedef unsigned long long t_uint48; +typedef signed long long t_sint48; + +// shared addr type definition +typedef t_uint32 t_shared_addr; + +// shared register (ARM world) type definition +typedef t_uint32 t_shared_reg; +typedef t_uint32 t_shared_field; + +#endif /* RISC 32 Bits */ + +/* + * Define boolean type + */ +#undef FALSE +#define FALSE 0 //!< Boolean FALSE value +#undef TRUE +#define TRUE 1 //!< Boolean TRUE value + +#ifndef NULL + #if defined __flexcc2__ || defined __SYMBIAN32__ + #define NULL (0x0) //!< Null type \ingroup NMF_PRIMITIVE_TYPE + #else + #define NULL ((void*)0x0) //!< Null type \ingroup NMF_PRIMITIVE_TYPE + #endif +#endif + +typedef t_uint32 t_nmf_component_handle; + +#endif /* NMF_TYPEDEF_H_ */ diff --git a/drivers/staging/nmf-cm/nmf/inc/channel_type.h b/drivers/staging/nmf-cm/nmf/inc/channel_type.h new file mode 100644 index 00000000000..91a733dbbbf --- /dev/null +++ b/drivers/staging/nmf-cm/nmf/inc/channel_type.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Common Nomadik Multiprocessing Framework type definition + * + * This file contains the shared between cm and ee type definitions used into NMF for callback. + */ +/*! + * \defgroup _t_nmf_channel_flag t_nmf_channel_flag + * \ingroup NMF_COMMON + */ + +#ifndef __INC_CHANNEL_TYPE_H +#define __INC_CHANNEL_TYPE_H + +#include <inc/typedef.h> +#include <inc/nmf_type.idt> + +/*! + * \brief Define t_nmf_channel_flag type that allow to control if/how a new communication channel is created. + * \ingroup _t_nmf_channel_flag + */ +typedef t_uint32 t_nmf_channel_flag; + +#define NMF_CHANNEL_SHARED ((t_nmf_channel_flag)0) //!< \ingroup _t_nmf_channel_flag +#define NMF_CHANNEL_PRIVATE ((t_nmf_channel_flag)1) //!< \ingroup _t_nmf_channel_flag + +/*! + * \brief Define t_nmf_virtualInterruptHandler function type to allow to dispatch virtual interrupt + * \ingroup VIRTUAL_INTERRUPT + */ +typedef void (*t_nmf_virtualInterruptHandler)(void *interruptContext); + +#endif + diff --git a/drivers/staging/nmf-cm/nmf/inc/component_type.h b/drivers/staging/nmf-cm/nmf/inc/component_type.h new file mode 100644 index 00000000000..26217554158 --- /dev/null +++ b/drivers/staging/nmf-cm/nmf/inc/component_type.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Common Nomadik Multiprocessing Framework type definition + * + * This file contains the shared between cm and ee type definitions used into NMF for callback. + */ +#ifndef __INC_COMPONENT_TYPE_H +#define __INC_COMPONENT_TYPE_H + +#include <inc/typedef.h> + +/*! + * \brief Identifier of a component instance handle + * + * \ingroup NMF_COMMON + */ +typedef t_nmf_component_handle t_cm_instance_handle; + +#endif + diff --git a/drivers/staging/nmf-cm/nmf/inc/service_type.h b/drivers/staging/nmf-cm/nmf/inc/service_type.h new file mode 100644 index 00000000000..06d5c72dce9 --- /dev/null +++ b/drivers/staging/nmf-cm/nmf/inc/service_type.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Service type and data used through service callback. + * \defgroup NMF_SERVICE NMF Service Callback types and data definition + * \ingroup NMF_COMMON + */ +#ifndef SERVICE_TYPE_H +#define SERVICE_TYPE_H + +#include <ee/api/panic.idt> +#include <nmf/inc/component_type.h> +#include <share/inc/nmf.h> + +/*! + * \brief Define t_nmf_service_type type + * + * It gives the type of service message passed to service callback. + * \ingroup NMF_SERVICE + */ +typedef t_uint32 t_nmf_service_type; +#define NMF_SERVICE_PANIC ((t_nmf_service_type)0) //!< \ingroup NMF_SERVICE +#define NMF_SERVICE_SHUTDOWN ((t_nmf_service_type)1) //!< \ingroup NMF_SERVICE + +/* + * The following structured define each data structure used for each service type + * and given to each serviceCallback + */ + +/*! + * \brief Define t_nmf_panic_data type + * + * This is the data structure passed to the service callback (inside \ref t_nmf_service_data) + * when t_nmf_service_type == NMF_SERVICE_PANIC + * \ingroup NMF_SERVICE + */ +typedef struct { + t_panic_reason panicReason; //!< The reason of the panic + t_panic_source panicSource; //!< THe source of the panic (One of the MPC or the ARM-EE) + /*! + * union of structures containing specific info, depending on the panicSource + */ + union { + struct { + t_nmf_core_id coreid; //!< The coreId of the MPC on which the panic occured + t_cm_instance_handle faultingComponent; //!< The faulting component handle + t_uint32 panicInfo1; //!< First info (depend on \ref panicReason) + t_uint32 panicInfo2; //!< Second info (depend on \ref panicReason) + } mpc; //!< member to use if panicSource == MPC_EE + struct { + void * faultingComponent; //!< The faulting component handle + t_uint32 panicInfo1; //!< First info (depend on \ref panicReason) + t_uint32 panicInfo2; //!< Second info (depend on \ref panicReason) + } host; //!< member to use if panicSource == HOST_EE + } info; //!< union of structures containing specific info, depending on the panicSource +} t_nmf_panic_data; + +/*! + * \brief Define t_nmf_shutdown_data type + * + * This is the data structure passed to the service callback (inside \ref t_nmf_service_data) + * when t_nmf_service_type == NMF_SERVICE_SHUTDOWN + * \ingroup NMF_SERVICE + */ +typedef struct { + t_nmf_core_id coreid; //!< The coreId of the MPC on which has been shutdown +} t_nmf_shutdown_data; + +/*! + * \brief Define t_nmf_service_data type + * + * It gives the data passed to the service callbacks for each service type + * This is an union whose member to use is defined by the given \ref t_nmf_service_type + * + * \ingroup NMF_SERVICE + */ +typedef union { + t_nmf_panic_data panic; //!< if service_type == NMF_SERVICE_PANIC + t_nmf_shutdown_data shutdown; //!< if service_type == NMF_SERVICE_SHUTDOWN +} t_nmf_service_data; + +/*! + * \brief Define t_nmf_serviceCallback function type to allow to dispatch service message to user. + * \ingroup NMF_SERVICE + */ +typedef void (*t_nmf_serviceCallback)(void *contextHandler, t_nmf_service_type serviceType, t_nmf_service_data *serviceData); + +#endif //SERVICE_TYPE_H diff --git a/drivers/staging/nmf-cm/osal-kernel.c b/drivers/staging/nmf-cm/osal-kernel.c new file mode 100644 index 00000000000..0dc8328dfc0 --- /dev/null +++ b/drivers/staging/nmf-cm/osal-kernel.c @@ -0,0 +1,1223 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +/** \file osal-kernel.c + * + * Implements NMF OSAL for Linux kernel-space environment + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kthread.h> +#include <linux/mm.h> +#include <linux/semaphore.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/uaccess.h> +#include <linux/vmalloc.h> + +#ifdef CONFIG_STM_TRACE +#include <trace/stm.h> +#endif + +#include <cm/engine/configuration/inc/configuration_status.h> + +#include "cmioctl.h" +#include "osal-kernel.h" +#include "cm_service.h" +#include "cmld.h" +#include "cm_debug.h" +#include "cm_dma.h" + +__iomem void *prcmu_base = NULL; +__iomem void *prcmu_tcdm_base = NULL; + +/* DSP Load Monitoring */ +#define FULL_OPP 100 +#define HALF_OPP 50 +static unsigned long running_dsp = 0; +static unsigned int dspLoadMonitorPeriod = 1000; +module_param(dspLoadMonitorPeriod, uint, S_IWUSR|S_IRUGO); +MODULE_PARM_DESC(dspLoadMonitorPeriod, "Period of the DSP-Load monitoring in ms"); +static unsigned int dspLoadHighThreshold = 85; +module_param(dspLoadHighThreshold, uint, S_IWUSR|S_IRUGO); +MODULE_PARM_DESC(dspLoadHighThreshold, "Threshold above which 100 APE OPP is requested"); +static unsigned int dspLoadLowThreshold = 35; +module_param(dspLoadLowThreshold, uint, S_IWUSR|S_IRUGO); +MODULE_PARM_DESC(dspLoadLowThreshold, "Threshold below which 100 APE OPP request is removed"); +static bool cm_use_ftrace; +module_param(cm_use_ftrace, bool, S_IWUSR|S_IRUGO); +MODULE_PARM_DESC(cm_use_ftrace, "Whether all CM debug traces goes through ftrace or normal kernel output"); + +/** \defgroup ENVIRONMENT_INITIALIZATION Environment initialization + * Includes functions that initialize the Linux OSAL itself plus functions that + * are responsible to factor configuration objects needed to initialize Component Manager library + */ + +/** \defgroup OSAL_IMPLEMENTATION OSAL implementation + * Linux-specific implementation of the Component Manager OSAL interface. + */ + + +/** \ingroup ENVIRONMENT_INITIALIZATION + * Remaps IO, SDRAM and ESRAM regions + * + * \osalEnvironment NMF-Osal descriptor + * \return POSIX error code + */ +int remapRegions(void) +{ + unsigned i; + + /* Remap DSP base areas */ + for (i=0; i<NB_MPC; i++) { + osalEnv.mpc[i].base.data = ioremap_nocache((int)osalEnv.mpc[i].base_phys, ONE_MB); + if(osalEnv.mpc[i].base.data == NULL){ + pr_err("%s: could not remap base address for %s\n", __func__, osalEnv.mpc[i].name); + return -ENOMEM; + } + } + + /* Remap hardware semaphores */ + osalEnv.hwsem_base = ioremap_nocache(U8500_HSEM_BASE, (4*ONE_KB)); + if(osalEnv.hwsem_base == NULL){ + pr_err("%s: could not remap HWSEM Base\n", __func__); + return -ENOMEM; + } + + /* Remap _all_ ESRAM banks */ + osalEnv.esram_base = ioremap_nocache(ESRAM_BASE, cfgESRAMSize*ONE_KB); + if(osalEnv.esram_base == NULL){ + pr_err("%s: could not remap ESRAM Base\n", __func__); + return -ENOMEM; + } + + /* Allocate code and data sections for MPC (SVA, SIA) */ + for (i=0; i<NB_MPC; i++) { + /* Allocate MPC SDRAM code area */ + struct hwmem_mem_chunk mem_chunk; + size_t mem_chunk_length; + osalEnv.mpc[i].hwmem_code = hwmem_alloc(osalEnv.mpc[i].sdram_code.size, + //HWMEM_ALLOC_HINT_CACHE_WB, + HWMEM_ALLOC_HINT_WRITE_COMBINE | HWMEM_ALLOC_HINT_UNCACHED, + HWMEM_ACCESS_READ | HWMEM_ACCESS_WRITE, + HWMEM_MEM_CONTIGUOUS_SYS); + if (IS_ERR(osalEnv.mpc[i].hwmem_code)) { + int err = PTR_ERR(osalEnv.mpc[i].hwmem_code); + osalEnv.mpc[i].hwmem_code = NULL; + pr_err("%s: could not allocate SDRAM Code for %s\n", + __func__, osalEnv.mpc[i].name); + return err; + } + osalEnv.mpc[i].sdram_code.data = hwmem_kmap(osalEnv.mpc[i].hwmem_code); + if (IS_ERR(osalEnv.mpc[i].sdram_code.data)) { + int err = PTR_ERR(osalEnv.mpc[i].sdram_code.data); + osalEnv.mpc[i].sdram_code.data = NULL; + pr_err("%s: could not map SDRAM Code for %s\n", __func__, osalEnv.mpc[i].name); + return err; + } + mem_chunk_length = 1; + (void)hwmem_pin(osalEnv.mpc[i].hwmem_code, &mem_chunk, &mem_chunk_length); + osalEnv.mpc[i].sdram_code_phys = mem_chunk.paddr; + /* Allocate MPC SDRAM data area by taking care wether the data are shared or not */ + if (osalEnv.mpc[i].sdram_data.size == 0) { + /* size of 0 means shared data segment, reuse the same param as for first MPC */ + osalEnv.mpc[i].sdram_data_phys = osalEnv.mpc[0].sdram_data_phys; + osalEnv.mpc[i].sdram_data.data = osalEnv.mpc[0].sdram_data.data; + osalEnv.mpc[i].sdram_data.size = osalEnv.mpc[0].sdram_data.size; + } else { + /* If we do not share the data segment or if this is the first MPC */ + osalEnv.mpc[i].hwmem_data = hwmem_alloc(osalEnv.mpc[i].sdram_data.size, + HWMEM_ALLOC_HINT_WRITE_COMBINE | HWMEM_ALLOC_HINT_UNCACHED, + HWMEM_ACCESS_READ | HWMEM_ACCESS_WRITE, + HWMEM_MEM_CONTIGUOUS_SYS); + if (IS_ERR(osalEnv.mpc[i].hwmem_data)) { + int err = PTR_ERR(osalEnv.mpc[i].hwmem_data); + osalEnv.mpc[i].hwmem_data = NULL; + pr_err("%s: could not allocate SDRAM Data for %s\n", + __func__, osalEnv.mpc[i].name); + return err; + } + mem_chunk_length = 1; + (void)hwmem_pin(osalEnv.mpc[i].hwmem_data, + &mem_chunk, &mem_chunk_length); + osalEnv.mpc[i].sdram_data_phys = mem_chunk.paddr; + osalEnv.mpc[i].sdram_data.data = hwmem_kmap(osalEnv.mpc[i].hwmem_data); + if (IS_ERR(osalEnv.mpc[i].sdram_data.data)) { + int err = PTR_ERR(osalEnv.mpc[i].sdram_data.data); + osalEnv.mpc[i].sdram_data.data = NULL; + pr_err("%s: could not map SDRAM Data for %s\n", + __func__, osalEnv.mpc[i].name); + return err; + } + } + } + + return 0; +} + +/** \ingroup ENVIRONMENT_INITIALIZATION + * Unmaps IO, SDRAM and ESRAM regions + * + * \return POSIX error code + */ +void unmapRegions(void) +{ + unsigned i; + + /* Release SVA, SIA, Hardware sempahores and embedded SRAM mappings */ + for (i=0; i<NB_MPC; i++) { + if(osalEnv.mpc[i].base.data != NULL) + iounmap(osalEnv.mpc[i].base.data); + } + + if(osalEnv.hwsem_base != NULL) + iounmap(osalEnv.hwsem_base); + + if(osalEnv.esram_base != NULL) + iounmap(osalEnv.esram_base); + + /* + * Free SVA and SIA code and data sections or release their mappings + * according on how memory allocations has been achieved + */ + for (i=0; i<NB_MPC; i++) { + if (osalEnv.mpc[i].sdram_code.data != NULL) { + hwmem_unpin(osalEnv.mpc[i].hwmem_code); + hwmem_kunmap(osalEnv.mpc[i].hwmem_code); + if (osalEnv.mpc[i].hwmem_code != NULL) + hwmem_release(osalEnv.mpc[i].hwmem_code); + } + + /* If data segment is shared, we must free only the first data segment */ + if (((i == 0) || (osalEnv.mpc[i].sdram_data.data != osalEnv.mpc[0].sdram_data.data)) + && (osalEnv.mpc[i].sdram_data.data != NULL)) { + hwmem_unpin(osalEnv.mpc[i].hwmem_data); + hwmem_kunmap(osalEnv.mpc[i].hwmem_data); + if (osalEnv.mpc[i].hwmem_data != NULL) + hwmem_release(osalEnv.mpc[i].hwmem_data); + } + } +} + + +/** \ingroup ENVIRONMENT_INITIALIZATION + * Fills a t_nmf_hw_mapping_desc object + * + * \param nmfHwMappingDesc Pointer to a t_nmf_hw_mapping_desc object + * \return POSIX error code + */ +int getNmfHwMappingDesc(t_nmf_hw_mapping_desc* nmfHwMappingDesc) +{ + + if (nmfHwMappingDesc == NULL) + return -ENXIO; + + nmfHwMappingDesc->esramDesc.systemAddr.physical = ESRAM_BASE; + nmfHwMappingDesc->esramDesc.systemAddr.logical = (t_cm_logical_address)osalEnv.esram_base; + nmfHwMappingDesc->esramDesc.size = cfgESRAMSize*ONE_KB; + + nmfHwMappingDesc->hwSemaphoresMappingBaseAddr.physical = U8500_HSEM_BASE; + nmfHwMappingDesc->hwSemaphoresMappingBaseAddr.logical = (t_cm_logical_address)osalEnv.hwsem_base; + + return 0; +} + +/** \ingroup ENVIRONMENT_INITIALIZATION + * Fills a t_cm_system_address object + * + * \param mpcSystemAddress Pointer to a t_cm_system_address object + * \return POSIX error code + */ +void getMpcSystemAddress(unsigned i, t_cm_system_address* mpcSystemAddress) +{ + mpcSystemAddress->physical = (t_cm_physical_address)osalEnv.mpc[i].base_phys; + mpcSystemAddress->logical = (t_cm_logical_address)osalEnv.mpc[i].base.data; +} + + +/** \ingroup ENVIRONMENT_INITIALIZATION + * Fills t_nmf_memory_segment objects for MPC code and data segments + * + * \param i Index of the MPC to initialize + * \param codeSegment Pointer to a t_nmf_memory_segment (code segment) + * \param dataSegment Pointer to a t_nmf_memory_segment (data segment) + * \return Always 0 + */ +void getMpcSdramSegments(unsigned i, t_nmf_memory_segment* codeSegment, t_nmf_memory_segment* dataSegment) +{ + codeSegment->systemAddr.logical = (t_cm_logical_address)osalEnv.mpc[i].sdram_code.data; + codeSegment->systemAddr.physical = osalEnv.mpc[i].sdram_code_phys; + codeSegment->size = osalEnv.mpc[i].sdram_code.size; + + dataSegment->systemAddr.logical = (t_cm_logical_address)osalEnv.mpc[i].sdram_data.data; + dataSegment->systemAddr.physical = osalEnv.mpc[i].sdram_data_phys; + dataSegment->size = osalEnv.mpc[i].sdram_data.size; +} + +#ifdef CM_DEBUG_ALLOC +#include <linux/kallsyms.h> +struct cm_alloc cm_alloc; + +/** + * These routines initializes the structures used to trace all alloc/free. + * These are used in debug mode to track all memory leak about the allocations + * done through the OSAL. + */ +void init_debug_alloc(void) +{ + INIT_LIST_HEAD(&cm_alloc.chain); + spin_lock_init(&cm_alloc.lock); +} + +void cleanup_debug_alloc(void) +{ + struct cm_alloc_elem *entry, *next; + char buffer[128]; + + list_for_each_entry_safe(entry, next, &cm_alloc.chain, elem) { + sprint_symbol(buffer, (int)entry->caller); + pr_err("/!\\ ALLOC(size=%d) not freed from: 0x%p (%s)\n", + entry->size, entry->caller, buffer); + list_del(&entry->elem); + if ((void*)entry >= (void*)VMALLOC_START + && (void*)entry < (void*)VMALLOC_END) + vfree(entry); + else + kfree(entry); + } +} + +void dump_debug_alloc(void) +{ + struct cm_alloc_elem *entry, *next; + char buffer[128]; + + pr_err("Current allocated memory:\n"); + list_for_each_entry_safe(entry, next, &cm_alloc.chain, elem) { + sprint_symbol(buffer, (int)entry->caller); + pr_err("=> Alloc of size=%d from: 0x%p (%s)\n", + entry->size, entry->caller, buffer); + } +} +#endif + + +/** \ingroup OSAL_IMPLEMENTATION + * Called by CM_ProcessMpcEvent in interrupt/tasklet context. Schedules the DFC. + * Enqueues the new event in the process' message queue. + * + * \note This is _not_ called in response to internal events such as in + * response to a CM_InstantiateComponent. It is called when user-defined + * functions need to be called in skeletons. This behavior is different + * from 0.8.1 version. + */ +void OSAL_PostDfc(t_nmf_mpc2host_handle upLayerTHIS, t_uint32 methodIndex, t_event_params_handle ptr, t_uint32 size) +{ + /* skelwrapper has been created in CM_SYSCALL_BindComponentToCMCore and conveys per-process private data */ + t_skelwrapper* skelwrapper = (t_skelwrapper*)upLayerTHIS; + struct osal_msg* message; + + /* If the clannel has been closed, no more reader exists + => discard the message */ + if (skelwrapper->channelPriv->state == CHANNEL_CLOSED) { + pr_warning("%s: message discarded (channel closed)\n", + __func__ ); + return; + } + + /* Create a new message */ + message = kmalloc(sizeof(*message), GFP_ATOMIC); + if (!message) { + pr_err("%s: message discarded (alloc failed)\n", __func__ ); + return; + } + + /* Stuff it */ + plist_node_init(&message->msg_entry, 0); + message->msg_type = MSG_INTERFACE; + message->d.itf.skelwrap = skelwrapper; + message->d.itf.methodIdx = methodIndex; + message->d.itf.anyPtr = ptr; + message->d.itf.ptrSize = size; + + /* Enqueue it */ + /* Should be protected with the cmPriv->msgQueueLock held + But we know by design that we are safe here. (Alone here in + tasklet (soft-interrupt) context. + When accessed in process context, soft-irq are disable) + */ + spin_lock_bh(&skelwrapper->channelPriv->bh_lock); + plist_add(&message->msg_entry, &skelwrapper->channelPriv->messageQueue); + spin_unlock_bh(&skelwrapper->channelPriv->bh_lock); + + /* Wake up process' wait queue */ + wake_up(&skelwrapper->channelPriv->waitq); +} + + +#define MAX_LOCKS 8 // max number of locks/semaphores creatable +static unsigned long semused = 0; // bit field for used semaphores +static unsigned long lockused = 0; // bit field for used mutexes +static struct mutex cmld_locks[MAX_LOCKS]; + +/** \ingroup OSAL_IMPLEMENTATION + */ +t_nmf_osal_sync_handle OSAL_CreateLock(void) +{ + int i; + + for (i=0; i<MAX_LOCKS; i++) + if (!test_and_set_bit(i, &lockused)) { + struct mutex* mutex = &cmld_locks[i]; + mutex_init(mutex); + return (t_nmf_osal_sync_handle)mutex; + } + + return (t_nmf_osal_sync_handle)NULL; +} + + +/** \ingroup OSAL_IMPLEMENTATION + */ +void OSAL_Lock(t_nmf_osal_sync_handle handle) +{ + // unfortunately there is no return value to this function + // so we cannot use 'mutex_lock_killable()' + mutex_lock((struct mutex*)handle); +} + + +/** \ingroup OSAL_IMPLEMENTATION + */ +void OSAL_Unlock(t_nmf_osal_sync_handle handle) +{ + mutex_unlock((struct mutex*)handle); +} + + +/** \ingroup OSAL_IMPLEMENTATION + */ +void OSAL_DestroyLock(t_nmf_osal_sync_handle handle) +{ + int i; + + // clear the bit in the bits field about used locks + i = ((struct mutex*)handle - cmld_locks); + + clear_bit(i, &lockused); +} + +static struct semaphore cmld_semaphores[MAX_LOCKS]; +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * Goal: Use by CM to allow to synchronize with code running on mpc side. + * + * \param[in] value : Initial value of semaphore. + * + * \return handle of the Semaphore created + * + * Called by: + * - any CM API call + * + * \ingroup OSAL + */ +t_nmf_osal_sem_handle OSAL_CreateSemaphore(t_uint32 value) +{ + int i; + + for (i=0; i<MAX_LOCKS; i++) + if (!test_and_set_bit(i, &semused)) { + struct semaphore* sem = &cmld_semaphores[i]; + sema_init(sem, value); + return (t_nmf_osal_sem_handle)sem; + } + + return (t_nmf_osal_sem_handle)NULL; +} + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * Goal: Use by CM to allow to synchronize with code running on mpc side. This function can be called under + * Irq context by CM. + * + * param[in] : handle of the Semaphore for which we increase value and so potentially wake up thread. + * + * param[in] : aCtx is a hint to indicate to os that we are in a none normal context (e.g under interruption). + * + * Called by: + * - any CM API call + * + * \ingroup OSAL + */ +void OSAL_SemaphorePost(t_nmf_osal_sem_handle handle, t_uint8 aCtx) +{ + up((struct semaphore*)handle); +} + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * Goal: Use by CM to allow to synchronize with code running on mpc side. + * + * param[in] : handle of the Semaphore for which we decrease value and so potentially block current thread. + * + * param[in] : maximun time in ms after which the block thread is wake up. In this case function return SYNC_ERROR_TIMEOUT value. + * + * \return error number: SYNC_ERROR_TIMEOUT in case semaphore is not release withing timeOutInMs. + * + * Called by: + * - any CM API call + * + * \ingroup OSAL + */ +t_nmf_osal_sync_error OSAL_SemaphoreWaitTimed(t_nmf_osal_sem_handle handle, + t_uint32 timeOutInMs) +{ + if (down_timeout((struct semaphore*)handle, msecs_to_jiffies(timeOutInMs))) + return SYNC_ERROR_TIMEOUT; + else + return SYNC_OK; +} + +/*! + * \brief Description of the Synchronization part of the OS Adaptation Layer + * + * Goal: Use by CM to allow to synchronize with code running on mpc side. + * + * param[in] : handle of the Semaphore to be destroyed + * + * Called by: + * - any CM API call + * + * \ingroup OSAL + */ +void OSAL_DestroySemaphore(t_nmf_osal_sem_handle handle) +{ + int i; + + // clear the bit in the bits field about used locks + i = ((struct semaphore*)handle - cmld_semaphores); + + clear_bit(i, &semused); +} + +/** \ingroup OSAL_IMPLEMENTATION + * OSAL alloc implementation + * + * In both OSAL_Alloc() and OSAL_Alloc_Zero() function, the strategy is to use + * kmalloc() as it is the more efficient and most common way to allocate memory. + * For big allocation, kmalloc may fail because memory is very fragmented + * (kmalloc() allocates contiguous memory). In that case, we fall to vmalloc() + * instead. + * In OSAL_Free(), we rely on the virtual address to know which of kfree() or + * vfree() to use (vmalloc() use its own range of virtual addresses) + */ +void* OSAL_Alloc(t_cm_size size) +{ +#ifdef CM_DEBUG_ALLOC + struct cm_alloc_elem *entry; + + if (size == 0) + return NULL; + + entry = kmalloc(size + sizeof(*entry), GFP_KERNEL); + + if (entry == NULL) { + entry = vmalloc(size + sizeof(*entry)); + + if (entry == NULL) { + pr_alert("%s: kmalloc(%d) and vmalloc(%d) failed\n", + __func__, (int)size, (int)size); + dump_debug_alloc(); + return NULL; + } + } + /* return address of the caller */ + entry->caller = __builtin_return_address(0); + entry->size = size; + + spin_lock(&cm_alloc.lock); + list_add_tail(&entry->elem, &cm_alloc.chain); + spin_unlock(&cm_alloc.lock); + + return entry->addr; +#else + void* mem; + + if (size == 0) + return NULL; + mem = kmalloc(size, GFP_KERNEL); + if (mem == NULL) { + mem = vmalloc(size); + if (mem == NULL) + pr_alert("CM (%s): No more memory (requested " + "size=%d) !!!\n", __func__, (int)size); + } + return mem; +#endif +} + + +/** \ingroup OSAL_IMPLEMENTATION + * OSAL alloc implementation + */ +void* OSAL_Alloc_Zero(t_cm_size size) +{ +#ifdef CM_DEBUG_ALLOC + struct cm_alloc_elem *entry; + + if (size == 0) + return NULL; + + entry = kzalloc(size + sizeof(*entry), GFP_KERNEL); + if (entry == NULL) { + entry = vmalloc(size + sizeof(*entry)); + if (entry == NULL) { + pr_alert("%s: kmalloc(%d) and vmalloc(%d) failed\n", + __func__, (int)size, (int)size); + dump_debug_alloc(); + return NULL; + } else { + memset(entry, 0, size + sizeof(*entry)); + } + } + + /* return address of the caller */ + entry->caller = __builtin_return_address(0); + entry->size = size; + + spin_lock(&cm_alloc.lock); + list_add_tail(&entry->elem, &cm_alloc.chain); + spin_unlock(&cm_alloc.lock); + + return entry->addr; +#else + void* mem; + + if (size == 0) + return NULL; + mem = kzalloc(size, GFP_KERNEL); + if (mem == NULL) { + mem = vmalloc(size); + if (mem == NULL) + pr_alert("CM (%s): No more memory (requested " + "size=%d) !!!\n", __func__, (int)size); + else + memset(mem, 0, size); + } + + return mem; +#endif +} + + +/** \ingroup OSAL_IMPLEMENTATION + * OSAL free implementation + */ +void OSAL_Free(void* mem) +{ +#ifdef CM_DEBUG_ALLOC + struct cm_alloc_elem *entry = container_of(mem, struct cm_alloc_elem, addr); + unsigned int i; + char pattern[4] = { 0xEF, 0xBE, 0xAD, 0xDE }; + + if (mem == NULL) + return; + + /* fill with a pattern to detect bad re-use of this area */ + for (i=0; i<entry->size; i++) + entry->addr[i] = pattern[i%4]; + + spin_lock(&cm_alloc.lock); + list_del(&entry->elem); + spin_unlock(&cm_alloc.lock); + + if ((void*)entry >= (void*)VMALLOC_START + && (void*)entry < (void*)VMALLOC_END) + vfree(entry); + else + kfree(entry); +#else + if (mem >= (void*)VMALLOC_START && mem < (void*)VMALLOC_END) + vfree(mem); + else + kfree(mem); +#endif +} + +/** \ingroup OSAL_IMPLEMENTATION + * OSAL Copy implementation + * This copy some data from userspace (address to kernel space. + * This implementation differs on Symbian. + */ +t_cm_error OSAL_Copy(void *dst, const void *src, t_cm_size size) +{ + if (copy_from_user(dst, src, size)) + return CM_UNKNOWN_MEMORY_HANDLE; + return CM_OK; +} + +/** \ingroup OSAL_IMPLEMENTATION + * OSAL write64 function implementation + */ +void OSAL_Write64(t_nmf_trace_channel channel, t_uint8 isTimestamped, t_uint64 value) +{ +#ifdef CONFIG_STM_TRACE + if (isTimestamped) + stm_tracet_64(channel, value); + else + stm_trace_64(channel, value); +#endif +} + + +/** \ingroup OSAL_IMPLEMENTATION + * OSAL log function implementation + */ +void OSAL_Log(const char *format, int param1, int param2, int param3, int param4, int param5, int param6) +{ + if (cm_use_ftrace) + trace_printk(format, + param1, param2, param3, param4, param5, param6); + else + printk(format, param1, param2, param3, param4, param5, param6); +} + +/** + * compute the dsp load + * + * return -1 if in case of failure, a value between 0 and 100 otherwise + */ +static s8 computeDspLoad(t_cm_mpc_load_counter *oldCounter, t_cm_mpc_load_counter *counter) +{ + u32 t, l; + + if ((oldCounter->totalCounter == 0) && (oldCounter->loadCounter == 0)) + return -1; // Failure or not started ? + if ((counter->totalCounter == 0) && (counter->loadCounter == 0)) + return -1; // Failure or already stopped ? + + if (counter->totalCounter < oldCounter->totalCounter) + t = (u32)((((u64)-1) - oldCounter->totalCounter) + + counter->totalCounter + 1); + else + t = (u32)(counter->totalCounter - oldCounter->totalCounter); + + if (counter->loadCounter < oldCounter->loadCounter) + l = (u32)((((u64)-1) - oldCounter->loadCounter) + + counter->loadCounter + 1); + else + l = (u32)(counter->loadCounter - oldCounter->loadCounter); + + if (t == 0) // not significant + return -1; + + if (l > t) // not significant + return -1; + + return (l*100) / t; +} + +static void wakeup_process(unsigned long data) +{ + wake_up_process((struct task_struct *)data); +} + +/** + * Thread function entry for monitorin the CPU load + */ +static int dspload_monitor(void *idx) +{ + int i = (int)idx; + unsigned char current_opp_request = FULL_OPP; + struct mpcConfig *mpc = &osalEnv.mpc[i]; + struct timer_list timer; + + timer.function = wakeup_process; + timer.data = (unsigned long)current; + init_timer_deferrable(&timer); + +#ifdef CONFIG_DEBUG_FS + mpc->opp_request = current_opp_request; +#endif + if (prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, + (char*)mpc->name, + current_opp_request)) + pr_err("CM Driver: Add QoS failed\n"); + + /* + * Wait for 500ms before initializing the counter, + * to let the DSP boot (init of counter will failed if + * DSP is not booted). + */ + schedule_timeout_uninterruptible(msecs_to_jiffies(500)); + + /* init counter */ + if (CM_GetMpcLoadCounter(mpc->coreId, + &mpc->oldLoadCounter) != CM_OK) + pr_warn("CM Driver: Failed to init load counter for %s\n", + mpc->name); + + while (!kthread_should_stop()) { + t_cm_mpc_load_counter loadCounter; + s8 load = -1; + unsigned long expire; + + __set_current_state(TASK_UNINTERRUPTIBLE); + + expire = msecs_to_jiffies(dspLoadMonitorPeriod) + jiffies; + + mod_timer(&timer, expire); + schedule(); + /* We can be woken up before the expiration of the timer + but we don't need to handle that case as the + computation of the DSP load takes that into account */ + + if (!test_bit(i, &running_dsp)) + continue; + + if (CM_GetMpcLoadCounter(mpc->coreId, + &loadCounter) != CM_OK) + loadCounter = mpc->oldLoadCounter; + +#ifdef CONFIG_DEBUG_FS + mpc->load = +#endif + load = computeDspLoad(&mpc->oldLoadCounter, &loadCounter); + mpc->oldLoadCounter = loadCounter; + + if (load == -1) + continue; + /* check if we must request more opp */ + if ((current_opp_request == HALF_OPP) + && (load > dspLoadHighThreshold)) { +#ifdef CONFIG_DEBUG_FS + mpc->opp_request = +#endif + current_opp_request = FULL_OPP; + if (cm_debug_level) + pr_info("CM Driver: Request QoS OPP %d for %s\n", + current_opp_request, mpc->name); + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, + (char*)mpc->name, + current_opp_request); + } + /* check if we can request less opp */ + else if ((current_opp_request == FULL_OPP) + && (load < dspLoadLowThreshold)) { +#ifdef CONFIG_DEBUG_FS + mpc->opp_request = +#endif + current_opp_request = HALF_OPP; + if (cm_debug_level) + pr_info("CM Driver: Request QoS OPP %d for %s\n", + current_opp_request, mpc->name); + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, + (char*)mpc->name, + current_opp_request); + } + } + +#ifdef CONFIG_DEBUG_FS + mpc->opp_request = mpc->load = 0; +#endif + del_singleshot_timer_sync(&timer); + if (cm_debug_level) + pr_info("CM Driver: Remove QoS OPP for %s\n", mpc->name); + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, + (char*)mpc->name); + return 0; +} + +static bool enable_auto_pm = 1; +module_param(enable_auto_pm, bool, S_IWUSR|S_IRUGO); + +/** \ingroup OSAL_IMPLEMENTATION + * Used by CM to disable a power resource + */ +void OSAL_DisablePwrRessource(t_nmf_power_resource resource, t_uint32 firstParam, t_uint32 secondParam) +{ + switch (resource) { + case CM_OSAL_POWER_SxA_CLOCK: { + unsigned idx = COREIDX(firstParam); + struct osal_msg msg; + + if (idx >= NB_MPC) { + pr_err("CM Driver(%s(res=%d)): core %u unknown\n", + __func__, (int)resource, (unsigned)firstParam); + return; + } + + cm_debug_destroy_tcm_file(idx); + + /* Stop the DSP load monitoring */ + clear_bit(idx, &running_dsp); + if (osalEnv.mpc[idx].monitor_tsk) { + kthread_stop(osalEnv.mpc[idx].monitor_tsk); + osalEnv.mpc[idx].monitor_tsk = NULL; + } + + /* Stop the DMA (normally done on DSP side, but be safe) */ + if (firstParam == SIA_CORE_ID) + cmdma_stop_dma(); + + /* Stop the DSP */ + if (regulator_disable(osalEnv.mpc[idx].mmdsp_regulator) < 0) + pr_err("CM Driver(%s): can't disable regulator %s-mmsdp\n", + __func__, osalEnv.mpc[idx].name); +#ifdef CONFIG_HAS_WAKELOCK + wake_unlock(&osalEnv.mpc[idx].wakelock); +#endif + + /* Create and dispatch a shutdown service message */ + msg.msg_type = MSG_SERVICE; + msg.d.srv.srvType = NMF_SERVICE_SHUTDOWN; + msg.d.srv.srvData.shutdown.coreid = firstParam; + dispatch_service_msg(&msg); + break; + } + case CM_OSAL_POWER_SxA_AUTOIDLE: + switch (firstParam) { + case SVA_CORE_ID: + osalEnv.dsp_sleep.sva_auto_pm_enable = PRCMU_AUTO_PM_OFF; + osalEnv.dsp_sleep.sva_power_on = 0; + osalEnv.dsp_sleep.sva_policy = PRCMU_AUTO_PM_POLICY_DSP_OFF_HWP_OFF; + break; + case SIA_CORE_ID: + osalEnv.dsp_sleep.sia_auto_pm_enable = PRCMU_AUTO_PM_OFF; + osalEnv.dsp_sleep.sia_power_on = 0; + osalEnv.dsp_sleep.sia_policy = PRCMU_AUTO_PM_POLICY_DSP_OFF_HWP_OFF; + break; + default: + pr_err("CM Driver(%s(res=%d)): core %u unknown\n", __func__, (int)resource, (unsigned)firstParam); + return; + } + if (enable_auto_pm) + prcmu_configure_auto_pm(&osalEnv.dsp_sleep, &osalEnv.dsp_idle); + break; + case CM_OSAL_POWER_SxA_HARDWARE: { + unsigned idx = COREIDX(firstParam); + if (idx >= NB_MPC) { + pr_err("CM Driver(%s(res=%d)): core %u unknown\n", + __func__, (int)resource, (unsigned)firstParam); + return; + } + if (regulator_disable(osalEnv.mpc[idx].pipe_regulator) < 0) + pr_err("CM Driver(%s): can't disable regulator %s-pipe\n", + __func__, osalEnv.mpc[idx].name); + break; + } + case CM_OSAL_POWER_HSEM: + break; + case CM_OSAL_POWER_SDRAM: + break; + case CM_OSAL_POWER_ESRAM: { + int i; + /* firstParam: base address; secondParam: size + U8500_ESRAM_BASE is the start address of BANK 0, + BANK size=0x20000 */ + + /* Compute the relative end address of the range, + relative to base address of BANK1 */ + secondParam = (firstParam+secondParam-U8500_ESRAM_BANK1-1); + + /* if end is below base address of BANK1, it means that full + range of addresses is on Bank0 */ + if (((int)secondParam) < 0) + break; + /* Compute the index of the last bank accessed among + esram 1+2 and esram 3+4 banks */ + secondParam /= (2*U8500_ESRAM_BANK_SIZE); + WARN_ON(secondParam > 1); + + /* Compute the index of the first bank accessed among esram 1+2 + and esram 3+4 banks + Do not manage Bank 0 (secured, must be always ON) */ + if (firstParam < U8500_ESRAM_BANK1) + firstParam = 0; + else + firstParam = (firstParam-U8500_ESRAM_BANK1)/(2*U8500_ESRAM_BANK_SIZE); + + /* power off the banks 1+2 and 3+4 if accessed. */ + for (i=firstParam; i<=secondParam; i++) { + if (regulator_disable(osalEnv.esram_regulator[i]) < 0) + pr_err("CM Driver(%s): can't disable regulator" + "for esram bank %s\n", __func__, + i ? "34" : "12"); + } + break; + } + default: + pr_err("CM Driver(%s): resource %d unknown/not supported\n", + __func__, (int)resource); + } +} + +/** \ingroup OSAL_IMPLEMENTATION + * Used by CM to enable a power resource + */ +t_cm_error OSAL_EnablePwrRessource(t_nmf_power_resource resource, t_uint32 firstParam, t_uint32 secondParam) +{ + switch (resource) { + case CM_OSAL_POWER_SxA_CLOCK: { + unsigned idx = COREIDX(firstParam); + + if (idx > NB_MPC) { + pr_err("CM Driver(%s(res=%d)): core %u unknown\n", __func__, (int)resource, (unsigned)firstParam); + return CM_INVALID_PARAMETER; + } + + /* Start the DSP */ +#ifdef CONFIG_HAS_WAKELOCK + wake_lock(&osalEnv.mpc[idx].wakelock); +#endif + if (regulator_enable(osalEnv.mpc[idx].mmdsp_regulator) < 0) + pr_err("CM Driver(%s): can't enable regulator %s-mmsdp\n", __func__, osalEnv.mpc[idx].name); + + /* Start the DSP load monitoring for this dsp */ + set_bit(idx, &running_dsp); + osalEnv.mpc[idx].monitor_tsk = kthread_run(&dspload_monitor, + (void*)idx, + "%s-loadd", + osalEnv.mpc[idx].name); + if (IS_ERR(osalEnv.mpc[idx].monitor_tsk)) { + pr_err("CM Driver: failed to start dspmonitord " + "thread: %ld\n", PTR_ERR(osalEnv.mpc[idx].monitor_tsk)); + osalEnv.mpc[idx].monitor_tsk = NULL; + } + + cm_debug_create_tcm_file(idx); + break; + } + case CM_OSAL_POWER_SxA_AUTOIDLE: + switch (firstParam) { + case SVA_CORE_ID: + osalEnv.dsp_sleep.sva_auto_pm_enable = PRCMU_AUTO_PM_ON; + osalEnv.dsp_sleep.sva_power_on = PRCMU_AUTO_PM_POWER_ON_HSEM | PRCMU_AUTO_PM_POWER_ON_ABB_FIFO_IT; + osalEnv.dsp_sleep.sva_policy = PRCMU_AUTO_PM_POLICY_DSP_OFF_RAMRET_HWP_OFF; + break; + case SIA_CORE_ID: + osalEnv.dsp_sleep.sia_auto_pm_enable = PRCMU_AUTO_PM_ON; + osalEnv.dsp_sleep.sia_power_on = PRCMU_AUTO_PM_POWER_ON_HSEM | PRCMU_AUTO_PM_POWER_ON_ABB_FIFO_IT; + osalEnv.dsp_sleep.sia_policy = PRCMU_AUTO_PM_POLICY_DSP_OFF_RAMRET_HWP_OFF; + break; + default: + pr_err("CM Driver(%s(res=%d)): core %u unknown\n", __func__, (int)resource, (unsigned)firstParam); + return CM_INVALID_PARAMETER; + } + if (enable_auto_pm) + prcmu_configure_auto_pm(&osalEnv.dsp_sleep, &osalEnv.dsp_idle); + break; + case CM_OSAL_POWER_SxA_HARDWARE: { + unsigned idx = COREIDX(firstParam); + + if (idx > NB_MPC) { + pr_err("CM Driver(%s(res=%d)): core %u unknown\n", __func__, (int)resource, (unsigned)firstParam); + return CM_INVALID_PARAMETER; + } + if (regulator_enable(osalEnv.mpc[idx].pipe_regulator) < 0) + pr_err("CM Driver(%s): can't enable regulator %s-pipe\n", __func__, osalEnv.mpc[idx].name); + break; + } + case CM_OSAL_POWER_HSEM: + return CM_OK; + case CM_OSAL_POWER_SDRAM: + break; + case CM_OSAL_POWER_ESRAM: + { + int i; + /* firstParam: base address; secondParam: size + U8500_ESRAM_BASE is the start address of BANK 0, + BANK size=0x20000 */ + + /* Compute the relative end address of the range, relative + to base address of BANK1 */ + secondParam = (firstParam+secondParam-U8500_ESRAM_BANK1-1); + + /* if end is below base address of BANK1, it means that full + range of addresses is on Bank0 */ + if (((int)secondParam) < 0) + break; + /* Compute the index of the last bank accessed among esram 1+2 + and esram 3+4 banks */ + secondParam /= (2*U8500_ESRAM_BANK_SIZE); + WARN_ON(secondParam > 1); + + /* Compute the index of the first bank accessed among esram 1+2 + and esram 3+4 banks + Do not manage Bank 0 (secured, must be always ON) */ + if (firstParam < U8500_ESRAM_BANK1) + firstParam = 0; + else + firstParam = (firstParam-U8500_ESRAM_BANK1)/(2*U8500_ESRAM_BANK_SIZE); + + /* power on the banks 1+2 and 3+4 if accessed. */ + for (i=firstParam; i<=secondParam; i++) { + if (regulator_enable(osalEnv.esram_regulator[i]) < 0) + pr_err("CM Driver(%s): can't enable regulator " + "for esram bank %s\n", __func__, + i ? "34" : "12"); + } + break; + } + default: + pr_err("CM Driver(%s): resource %x unknown/not supported\n", + __func__, (int)resource); + return CM_INVALID_PARAMETER; + } + + return CM_OK; +} + +/*! + * \brief Generate 'software' panic to notify cm users + * that a problem occurs but no dsp panic has been sent yet + * (for example a dsp crash) + * \ingroup CM_ENGINE_OSAL_API + */ +void OSAL_GeneratePanic(t_nmf_core_id coreId, t_uint32 reason) +{ + struct osal_msg msg; + + /* Create and dispatch a shutdown service message */ + msg.msg_type = MSG_SERVICE; + msg.d.srv.srvType = NMF_SERVICE_PANIC; + msg.d.srv.srvData.panic.panicReason = MPC_NOT_RESPONDING_PANIC; + msg.d.srv.srvData.panic.panicSource = MPC_EE; + msg.d.srv.srvData.panic.info.mpc.coreid = coreId; + msg.d.srv.srvData.panic.info.mpc.faultingComponent = 0; + msg.d.srv.srvData.panic.info.mpc.panicInfo1 = reason; + msg.d.srv.srvData.panic.info.mpc.panicInfo2 = 0; + dispatch_service_msg(&msg); +} + +/*! + * \brief Generate an OS-Panic. Called in from CM_ASSERT(). + * \ingroup CM_ENGINE_OSAL_API + */ +void OSAL_Panic(void) +{ + panic("FATAL ISSUE IN THE CM DRIVER !!"); +} +#include <mach/dcache.h> +/*! + * \brief Clean data cache in DDR in order to be accessible from peripheral. + * + * \ingroup CM_ENGINE_OSAL_API + */ +void OSAL_CleanDCache(t_uint32 startAddr, t_uint32 size) +{ +#if 0 + /* + * Currently, the code sections are non-cached/buffered, + * which normally doesn't required the maintenance done below. + * As the cost is low (doesn't do much thing), I keep it in case + * of the memory settings are changed later. + */ + + struct hwmem_region region; + struct mpcConfig *mpc; + t_uint32 endAddr = startAddr + size; + + if (startAddr >= (u32)osalEnv.mpc[0].sdram_code.data + && endAddr <= (u32)(osalEnv.mpc[0].sdram_code.data + + osalEnv.mpc[0].sdram_code.size)) { + mpc = &osalEnv.mpc[0]; + } else if (startAddr >= (u32)osalEnv.mpc[1].sdram_code.data + && endAddr <= (u32)(osalEnv.mpc[1].sdram_code.data + + osalEnv.mpc[1].sdram_code.size)) { + mpc = &osalEnv.mpc[1]; + } else { + /* The code may be in esram, in that case, nothing to do */ + return; + } + + region.offset = startAddr - (u32)mpc->sdram_code.data; + region.count = 1; + region.start = 0; + region.end = size; + region.size = size; + hwmem_set_domain(mpc->hwmem_code, HWMEM_ACCESS_READ, + HWMEM_DOMAIN_SYNC, ®ion); + /* + * The hwmem keep track of region being sync or not. + * Mark the region as being write-accessed here right now + * to let following clean being done as expected. Today, + * there is no other place to do that in CM Core right now + */ + hwmem_set_domain(mpc->hwmem_code, HWMEM_ACCESS_WRITE, + HWMEM_DOMAIN_CPU, ®ion); +#else + dsb(); + outer_cache.sync(); +#endif +} + +/*! + * \brief Flush write-buffer of L2 cache + * + * \ingroup CM_ENGINE_OSAL_API + */ +void OSAL_mb(void) +{ + mb(); +} + +/*! + * \brief return prcmu timer value. + * + * This is need for perfmeter api (see \ref t_nmf_power_resource) + * + * \ingroup CM_ENGINE_OSAL_API + */ +t_uint64 OSAL_GetPrcmuTimer() +{ + t_uint64 msbBefore; + t_uint32 lsb; + t_uint64 msbAfter; + + /* read prcmu timers */ + msbBefore = ~ioread32(prcmu_tcdm_base+0xDE4); + lsb = ~ioread32(prcmu_base+0x454); + msbAfter = ~ioread32(prcmu_tcdm_base+0xDE4); + + /* handle rollover test case */ + // NOTE : there is still a window in prcmu side between counter rollover + // and prcmu interrupt handling + // to update msb register => this can lead to erroneous value return here + if (msbBefore == msbAfter || lsb >= 0x80000000UL) + return (((msbBefore & 0xffffffUL) << 32) + lsb); + else + return (((msbAfter & 0xffffffUL) << 32) + lsb); +} + +/*! + * \brief Disable the service message handling (panic, etc) + * + * It must disable the handling of all service messages + * If a service message is currently handled, it must wait till the end + * of its managment before returning. + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_DisableServiceMessages(void) { + tasklet_disable(&cmld_service_tasklet); +} + +/*! + * \brief Enable the service message handling (panic, etc) + * + * It enables the handling of all service messages + * + * \ingroup CM_ENGINE_OSAL_API + */ +PUBLIC void OSAL_EnableServiceMessages(void) { + tasklet_enable(&cmld_service_tasklet); +} diff --git a/drivers/staging/nmf-cm/osal-kernel.h b/drivers/staging/nmf-cm/osal-kernel.h new file mode 100644 index 00000000000..29b82368d8d --- /dev/null +++ b/drivers/staging/nmf-cm/osal-kernel.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef OSAL_KERNEL_H +#define OSAL_KERNEL_H + +#include <linux/debugfs.h> +#include <linux/interrupt.h> +#include <linux/hwmem.h> +#include <linux/regulator/consumer.h> +#include <linux/plist.h> +#include <linux/version.h> +#ifdef CONFIG_HAS_WAKELOCK +#include <linux/wakelock.h> +#endif +#include <linux/mfd/dbx500-prcmu.h> +#include <cm/engine/api/channel_engine.h> +#include <cm/engine/api/control/configuration_engine.h> +#include <cm/engine/api/perfmeter_engine.h> +/* + * Do not include ELF definition from cm/engine/elf/inc/elfapi.h file + * because it conflicts with definition from linux/elf.h file + */ +#define _CM_ELF_H +#include <cm/engine/os_adaptation_layer/inc/os_adaptation_layer.h> + +#include "configuration.h" + +/* + * Per-MPC configuration structure + * Use struct debugfs_blob_wrapper to store pointer and size of section + * to allow easy re-use of data through debugfs + */ +struct mpcConfig { + const t_nmf_core_id coreId; /**< MPC coreId */ + const char *name; /**< MPC name */ + t_uint8 nbYramBanks; /**< number of TCM ram banks to reserve for y memory */ + t_nmf_executive_engine_id eeId; /**< Type of Executive Engine */ + const void *base_phys; /**< Physical base address of the MPC */ + struct debugfs_blob_wrapper base;/**< Remapped base address of the MPC and size of TCM24 */ + struct hwmem_alloc *hwmem_code; /**< hwmem code segment */ + u32 sdram_code_phys; /**< Physical base address for MPC SDRAM Code region */ + struct debugfs_blob_wrapper sdram_code; /**< Remapped base address and size for MPC SDRAM Code */ + struct hwmem_alloc *hwmem_data; /**< hwmem data segment */ + u32 sdram_data_phys; /**< Physical base address for MPC SDRAM Data region */ + struct debugfs_blob_wrapper sdram_data; /**< Remapped base address and size for MPC SDRAM Data */ + const unsigned int interrupt0; /**< interrupt line triggered by the MPC, for MPC events (if HSEM not used) */ + const unsigned int interrupt1; /**< interrupt line triggered by the MPC, for PANIC events */ + struct tasklet_struct tasklet; /**< taskket used to process MPC events */ + struct regulator *mmdsp_regulator; /**< mmdsp regulator linked to this MPC */ + struct regulator *pipe_regulator; /**< hardware pipe linked to this MPC */ +#ifdef CONFIG_HAS_WAKELOCK + struct wake_lock wakelock; /**< wakelock for this MPC to prevent ARM to go in APSLEEP state */ +#endif + struct task_struct *monitor_tsk; /**< task to monitor the dsp load; */ + t_cm_mpc_load_counter oldLoadCounter; /**< previous load counter of the DSP */ + atomic_t trace_read_count; /**< number of trace reader */ + spinlock_t trace_reader_lock; + struct task_struct *trace_reader;/**< current reader task */ +#ifdef CONFIG_DEBUG_FS + struct dentry *dir; /**< debugfs dir entry */ + struct dentry *comp_dir; /**< debugfs component dir entry */ + struct dentry *domain_dir; /**< debugfs domain dir entry */ + struct dentry *snapshot_dir; /**< debugfs snapshot dir entry */ + struct dentry *mem_file; /**< debugfs meminfo file entry */ + struct dentry *tcm_file; /**< debugfs meminfo file entry */ + struct dentry *esram_file; /**< debugfs meminfo file entry */ + s8 load; /**< current load of the DSP */ + s8 opp_request; /**< current requested opp of the DSP */ +#endif +}; + +/** Describes current Kernel OSAL environment + * + * Note about mpc.tasklet : we declare one tasklet per MPC but their usage depends + * on cfgSemaphoreTypeHSEM. + * + * This tasklet is scheduled by the interrupt handler to process MPC Events. + * - If we use Hardware Semaphore, there is only one interrupt handler used + * and thus only one tasklet, tasklet of MPC 0 (ie osalEnv.mpc[0].tasklet) + * - If we use local semaphore, there is one interrupt handler and tasklet per mpc + */ +struct OsalEnvironment +{ + struct mpcConfig mpc[NB_MPC]; + void* hwsem_base; /** < Remapped base address of the hardware semaphores */ + void* esram_base; /**< Remapped base address embedded RAM used within the CM */ + struct regulator *esram_regulator[NB_ESRAM]; /**< regulator for ESRAM bank 1+2 and 3+4 */ + struct prcmu_auto_pm_config dsp_sleep; + struct prcmu_auto_pm_config dsp_idle; +}; + + +/** Structure used to store the skeleton related data. + * It is used for communicattion from a MPC to a user process (=host) + */ +typedef struct { + struct list_head entry; /**< Doubly linked list descriptor */ + t_cm_bf_mpc2host_handle mpc2hostId; /**< mpc2host ID */ + t_nmf_mpc2host_handle upperLayerThis;/**< upper-layer handle */ + struct cm_channel_priv* channelPriv; /**< Per-channel private data. The actual message queue is hold here */ +} t_skelwrapper; + +/** Message description for MPC to HOST communication + */ +struct osal_msg { + struct { + struct plist_node entry; /**< Doubly linked list descriptor */ + t_message_type type; /**< Type of message (callback, service or interrupt for now) */ + } hdr; /**< Header of the message */ +#define msg_entry hdr.entry +#define msg_type hdr.type + union { + struct { + t_skelwrapper *skelwrap; /**< Link to the skelwrapper, to retrieve the channel on which this message has to be forwarded */ + t_uint32 methodIdx; /**< callback data: method index*/ + t_event_params_handle anyPtr; /**< callback data: method parameters */ + t_uint32 ptrSize; /**< size of the parameters */ + } itf; /**< structure holding callback data */ + struct { + t_nmf_service_type srvType; /**< Type of the service */ + t_nmf_service_data srvData; /**< Data of the service */ + } srv; /**< structure holding service data */ + } d; /**< data */ +}; + +extern struct OsalEnvironment osalEnv; + +/** Environment initialization/deinitialization */ +int remapRegions(void); +void unmapRegions(void); + +/** Component manager configuration getters for CM_ENGINE_Init() */ +int getNmfHwMappingDesc(t_nmf_hw_mapping_desc* nmfHwMappingDesc); + +/** Component manager configuration getters for CM_ConfigureMediaProcessorCore (SVA and SIA) */ +void getMpcSystemAddress(unsigned i, t_cm_system_address* mpcSystemAddress); +void getMpcSdramSegments(unsigned i, t_nmf_memory_segment* codeSegment, t_nmf_memory_segment* dataSegment); + +#ifdef CM_DEBUG_ALLOC +struct cm_alloc { + spinlock_t lock; + struct list_head chain; +}; + +struct cm_alloc_elem { + struct list_head elem; + void *caller; + size_t size; + char addr[0]; +}; + +void init_debug_alloc(void); +void cleanup_debug_alloc(void); +#endif /* CM_DEBUG_ALLOC */ + +/* TODO: To remove later */ +extern __iomem void *prcmu_base; +extern __iomem void *prcmu_tcdm_base; +extern const char *cmld_devname[]; + +#define PRCM_SVAMMDSPCLK_MGT (prcmu_base + 0x008) +#define PRCM_SIAMMDSPCLK_MGT (prcmu_base + 0x00c) + +#endif /* OSAL_KERNEL_H */ diff --git a/drivers/staging/nmf-cm/share/communication/inc/communication_fifo.h b/drivers/staging/nmf-cm/share/communication/inc/communication_fifo.h new file mode 100644 index 00000000000..ea24e82ceae --- /dev/null +++ b/drivers/staging/nmf-cm/share/communication/inc/communication_fifo.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + +#ifndef __INC_NMF_COM_FIFO +#define __INC_NMF_COM_FIFO + +#include <inc/typedef.h> + +#define EVENT_ELEM_METHOD_IDX 0 +#define EVENT_ELEM_PARAM_IDX 1 +#define EVENT_ELEM_EXTFIELD_IDX 2 + +#define EVENT_ELEM_SIZE_IN_BYTE (3 * sizeof(t_shared_field)) + +#endif /* __INC_NMF_COM_FIFO */ diff --git a/drivers/staging/nmf-cm/share/communication/inc/initializer.h b/drivers/staging/nmf-cm/share/communication/inc/initializer.h new file mode 100644 index 00000000000..10985c3981a --- /dev/null +++ b/drivers/staging/nmf-cm/share/communication/inc/initializer.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + +#ifndef __INC_SHARE_INITIALIZER +#define __INC_SHARE_INITIALIZER + +#define NMF_CONSTRUCT_INDEX 0 +#define NMF_START_INDEX 1 +#define NMF_STOP_INDEX 2 +#define NMF_DESTROY_INDEX 3 +#define NMF_UPDATE_STACK 4 +#define NMF_LOCK_CACHE 5 +#define NMF_UNLOCK_CACHE 6 +#define NMF_ULP_FORCEWAKEUP 7 +#define NMF_ULP_ALLOWSLEEP 8 +#define NMF_CONSTRUCT_SYNC_INDEX 9 +#define NMF_START_SYNC_INDEX 10 +#define NMF_STOP_SYNC_INDEX 11 + +/* + * Index of datas in command parameter format + */ +#define INIT_COMPONENT_CMD_HANDLE_INDEX 0 +#define INIT_COMPONENT_CMD_THIS_INDEX 2 +#define INIT_COMPONENT_CMD_METHOD_INDEX 4 +#define INIT_COMPONENT_CMD_SIZE 6 + +/* + * Index of datas in acknowledge parameter format + */ +#define INIT_COMPONENT_ACK_HANDLE_INDEX 0 +#define INIT_COMPONENT_ACK_SIZE 2 + +#endif /* __INC_SHARE_INITIALIZER */ diff --git a/drivers/staging/nmf-cm/share/communication/inc/nmf_fifo_desc.h b/drivers/staging/nmf-cm/share/communication/inc/nmf_fifo_desc.h new file mode 100644 index 00000000000..99caa48b05c --- /dev/null +++ b/drivers/staging/nmf-cm/share/communication/inc/nmf_fifo_desc.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + +#ifndef __INC_NMF_FIFO_DESC +#define __INC_NMF_FIFO_DESC + +#include <inc/typedef.h> +#include <share/semaphores/inc/semaphores.h> + +/* + * SHOULD be mapped onto a AHB burst (16 bytes=8x16-bit) + */ +typedef struct { + t_semaphore_id semId; + + t_uint16 elemSize; + t_uint16 fifoFullValue; + t_uint16 readIndex; + t_uint16 writeIndex; + t_uint16 wrappingValue; + + t_uint32 extendedField; /* in DSP 24 memory when to MPC in Logical Host when to ARM */ +} t_nmf_fifo_desc; + +#define EXTENDED_FIELD_BCTHIS_OR_TOP 0 //<! This field will be used: + //<! - as hostBCThis for DSP->HOST binding + //<! - as TOP else +#define EXTENDED_FIELD_BCDESC 1 //<! This field will be used for: + //<! - interface method address for ->MPC binding + //<! - for params size for ->Host binding (today only [0] is used as max size) + +#endif /* __INC_NMF_FIFO */ diff --git a/drivers/staging/nmf-cm/share/communication/inc/nmf_service.h b/drivers/staging/nmf-cm/share/communication/inc/nmf_service.h new file mode 100644 index 00000000000..71dfc534f97 --- /dev/null +++ b/drivers/staging/nmf-cm/share/communication/inc/nmf_service.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + +#ifndef __INC_NMF_SERVICE_H +#define __INC_NMF_SERVICE_H + +/* 1 - 0xff Reserved for Panic Reason */ +#define MPC_SERVICE_NONE 0 +#define MPC_SERVICE_BOOT 0xB001 +#define MPC_SERVICE_PRINT 0x1234 +#define MPC_SERVICE_TRACE 0x789 + +#endif diff --git a/drivers/staging/nmf-cm/share/inc/macros.h b/drivers/staging/nmf-cm/share/inc/macros.h new file mode 100644 index 00000000000..7d2c2289cd3 --- /dev/null +++ b/drivers/staging/nmf-cm/share/inc/macros.h @@ -0,0 +1,213 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief NMF Macro API. + */ + +#ifndef _COMMON_MACROS_H_ +#define _COMMON_MACROS_H_ + +#undef ALIGN_VALUE +#define ALIGN_VALUE(value, alignment) (((value) + (alignment - 1)) & ~(alignment - 1)) + +#undef MIN +#define MIN(a,b) (((a)>(b))?(b):(a)) + +#undef MAX +#define MAX(a,b) (((a)<(b))?(b):(a)) + +/*----------------------------------------------------------------------------- + * endianess switch macros (32 bits and 16 bits) + *---------------------------------------------------------------------------*/ +#define ENDIANESS_32_SWITCH(value) ( \ + (((value) & MASK_BYTE3) >> SHIFT_BYTE3) | \ + (((value) & MASK_BYTE2) >> SHIFT_BYTE1) | \ + (((value) & MASK_BYTE1) << SHIFT_BYTE1) | \ + (((value) & MASK_BYTE0) << SHIFT_BYTE3) \ + ) + +#define ENDIANESS_16_SWITCH(value) ( \ + (((value) & MASK_BYTE0) << SHIFT_BYTE1) | \ + (((value) & MASK_BYTE1) >> SHIFT_BYTE1) \ + ) + +/*----------------------------------------------------------------------------- + * field offset extraction from a structure + *---------------------------------------------------------------------------*/ +#undef FIELD_OFFSET +#define FIELD_OFFSET(typeName, fieldName) ((t_uint32)(&(((typeName *)0)->fieldName))) + +#undef MASK_BIT +#define MASK_BIT(n) (1UL << ((n) - 1)) + +/*----------------------------------------------------------------------------- + * Misc definition + *---------------------------------------------------------------------------*/ + +#undef ONE_KB +#define ONE_KB (1024) +#undef ONE_MB +#define ONE_MB (ONE_KB * ONE_KB) + +/*----------------------------------------------------------------------------- + * Bit mask definition + *---------------------------------------------------------------------------*/ +#undef MASK_NULL8 +#define MASK_NULL8 0x00U +#undef MASK_NULL16 +#define MASK_NULL16 0x0000U +#undef MASK_NULL32 +#define MASK_NULL32 0x00000000UL +#undef MASK_ALL8 +#define MASK_ALL8 0xFFU +#undef MASK_ALL16 +#define MASK_ALL16 0xFFFFU +#undef MASK_ALL32 +#define MASK_ALL32 0xFFFFFFFFUL + +#undef MASK_BIT0 +#define MASK_BIT0 (1UL<<0) +#undef MASK_BIT1 +#define MASK_BIT1 (1UL<<1) +#undef MASK_BIT2 +#define MASK_BIT2 (1UL<<2) +#undef MASK_BIT3 +#define MASK_BIT3 (1UL<<3) +#undef MASK_BIT4 +#define MASK_BIT4 (1UL<<4) +#undef MASK_BIT5 +#define MASK_BIT5 (1UL<<5) +#undef MASK_BIT6 +#define MASK_BIT6 (1UL<<6) +#undef MASK_BIT7 +#define MASK_BIT7 (1UL<<7) +#undef MASK_BIT8 +#define MASK_BIT8 (1UL<<8) +#undef MASK_BIT9 +#define MASK_BIT9 (1UL<<9) +#undef MASK_BIT10 +#define MASK_BIT10 (1UL<<10) +#undef MASK_BIT11 +#define MASK_BIT11 (1UL<<11) +#undef MASK_BIT12 +#define MASK_BIT12 (1UL<<12) +#undef MASK_BIT13 +#define MASK_BIT13 (1UL<<13) +#undef MASK_BIT14 +#define MASK_BIT14 (1UL<<14) +#undef MASK_BIT15 +#define MASK_BIT15 (1UL<<15) +#undef MASK_BIT16 +#define MASK_BIT16 (1UL<<16) +#undef MASK_BIT17 +#define MASK_BIT17 (1UL<<17) +#undef MASK_BIT18 +#define MASK_BIT18 (1UL<<18) +#undef MASK_BIT19 +#define MASK_BIT19 (1UL<<19) +#undef MASK_BIT20 +#define MASK_BIT20 (1UL<<20) +#undef MASK_BIT21 +#define MASK_BIT21 (1UL<<21) +#undef MASK_BIT22 +#define MASK_BIT22 (1UL<<22) +#undef MASK_BIT23 +#define MASK_BIT23 (1UL<<23) +#undef MASK_BIT24 +#define MASK_BIT24 (1UL<<24) +#undef MASK_BIT25 +#define MASK_BIT25 (1UL<<25) +#undef MASK_BIT26 +#define MASK_BIT26 (1UL<<26) +#undef MASK_BIT27 +#define MASK_BIT27 (1UL<<27) +#undef MASK_BIT28 +#define MASK_BIT28 (1UL<<28) +#undef MASK_BIT29 +#define MASK_BIT29 (1UL<<29) +#undef MASK_BIT30 +#define MASK_BIT30 (1UL<<30) +#undef MASK_BIT31 +#define MASK_BIT31 (1UL<<31) + +/*----------------------------------------------------------------------------- + * quartet shift definition + *---------------------------------------------------------------------------*/ +#undef MASK_QUARTET +#define MASK_QUARTET (0xFUL) +#undef SHIFT_QUARTET0 +#define SHIFT_QUARTET0 0 +#undef SHIFT_QUARTET1 +#define SHIFT_QUARTET1 4 +#undef SHIFT_QUARTET2 +#define SHIFT_QUARTET2 8 +#undef SHIFT_QUARTET3 +#define SHIFT_QUARTET3 12 +#undef SHIFT_QUARTET4 +#define SHIFT_QUARTET4 16 +#undef SHIFT_QUARTET5 +#define SHIFT_QUARTET5 20 +#undef SHIFT_QUARTET6 +#define SHIFT_QUARTET6 24 +#undef SHIFT_QUARTET7 +#define SHIFT_QUARTET7 28 +#undef MASK_QUARTET0 +#define MASK_QUARTET0 (MASK_QUARTET << SHIFT_QUARTET0) +#undef MASK_QUARTET1 +#define MASK_QUARTET1 (MASK_QUARTET << SHIFT_QUARTET1) +#undef MASK_QUARTET2 +#define MASK_QUARTET2 (MASK_QUARTET << SHIFT_QUARTET2) +#undef MASK_QUARTET3 +#define MASK_QUARTET3 (MASK_QUARTET << SHIFT_QUARTET3) +#undef MASK_QUARTET4 +#define MASK_QUARTET4 (MASK_QUARTET << SHIFT_QUARTET4) +#undef MASK_QUARTET5 +#define MASK_QUARTET5 (MASK_QUARTET << SHIFT_QUARTET5) +#undef MASK_QUARTET6 +#define MASK_QUARTET6 (MASK_QUARTET << SHIFT_QUARTET6) +#undef MASK_QUARTET7 +#define MASK_QUARTET7 (MASK_QUARTET << SHIFT_QUARTET7) + +/*----------------------------------------------------------------------------- + * Byte shift definition + *---------------------------------------------------------------------------*/ +#undef MASK_BYTE +#define MASK_BYTE (0xFFUL) +#undef SHIFT_BYTE0 +#define SHIFT_BYTE0 0U +#undef SHIFT_BYTE1 +#define SHIFT_BYTE1 8U +#undef SHIFT_BYTE2 +#define SHIFT_BYTE2 16U +#undef SHIFT_BYTE3 +#define SHIFT_BYTE3 24U +#undef MASK_BYTE0 +#define MASK_BYTE0 (MASK_BYTE << SHIFT_BYTE0) +#undef MASK_BYTE1 +#define MASK_BYTE1 (MASK_BYTE << SHIFT_BYTE1) +#undef MASK_BYTE2 +#define MASK_BYTE2 (MASK_BYTE << SHIFT_BYTE2) +#undef MASK_BYTE3 +#define MASK_BYTE3 (MASK_BYTE << SHIFT_BYTE3) + +/*----------------------------------------------------------------------------- + * Halfword shift definition + *---------------------------------------------------------------------------*/ +#undef MASK_HALFWORD +#define MASK_HALFWORD (0xFFFFUL) +#undef SHIFT_HALFWORD0 +#define SHIFT_HALFWORD0 0U +#undef SHIFT_HALFWORD1 +#define SHIFT_HALFWORD1 16U +#undef MASK_HALFWORD0 +#define MASK_HALFWORD0 (MASK_HALFWORD << SHIFT_HALFWORD0) +#undef MASK_HALFWORD1 +#define MASK_HALFWORD1 (MASK_HALFWORD << SHIFT_HALFWORD1) + +#endif /* _COMMON_MACROS_H_ */ + diff --git a/drivers/staging/nmf-cm/share/inc/nmf.h b/drivers/staging/nmf-cm/share/inc/nmf.h new file mode 100644 index 00000000000..2f73311c2f3 --- /dev/null +++ b/drivers/staging/nmf-cm/share/inc/nmf.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Jean-Philippe FASSINO <jean-philippe.fassino@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2, with + * user space exemption described in the top-level COPYING file in + * the Linux kernel source tree. + */ +/*! + * \brief Common Nomadik Multiprocessing Framework type definition + * + * This file contains the shared type definitions used into NMF. + */ + +#ifndef __INC_NMF_H +#define __INC_NMF_H + +#include <inc/typedef.h> + +/*! + * \brief Identification of the various cores (host cpu and Media Processors) into Nomadik Platform + * In order to improve performance, these ids are those used to interconnect HW Semaphores IP with Cores (Interrupt lines) + * \ingroup NMF_COMMON + */ +#if defined(__STN_8500) + //#warning "TODO : mapping below is not correct, need to think how to change it" +#endif +typedef t_uint8 t_nmf_core_id; +#define ARM_CORE_ID ((t_nmf_core_id)0) //!< HOST CPU Id +#define SVA_CORE_ID ((t_nmf_core_id)1) //!< Smart Video Accelerator Media Processor Code Id +#define SIA_CORE_ID ((t_nmf_core_id)2) //!< Smart Imaging Accelerator Media Processor Code Id +#define NB_CORE_IDS ((t_nmf_core_id)3) + +#define FIRST_CORE_ID ((t_nmf_core_id)ARM_CORE_ID) +#define FIRST_MPC_ID ((t_nmf_core_id)SVA_CORE_ID) +#define LAST_CORE_ID ((t_nmf_core_id)SIA_CORE_ID) +#define LAST_MPC_ID ((t_nmf_core_id)SIA_CORE_ID) + + +/*! + * \brief Define minimal stack size use by execution engine + */ +#define MIN_STACK_SIZE 128 + + + +#endif /* __INC_NMF_H */ diff --git a/drivers/staging/nmf-cm/share/inc/nomadik_mapping.h b/drivers/staging/nmf-cm/share/inc/nomadik_mapping.h new file mode 100644 index 00000000000..bec221aa111 --- /dev/null +++ b/drivers/staging/nmf-cm/share/inc/nomadik_mapping.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + +#ifndef __INC_NOMADIK_MAPPING_H +#define __INC_NOMADIK_MAPPING_H + +/*--------------------------------------------------------------------------*/ +#if defined(__STN_8810) + +/* XTI (CPU OSMO/OSMOT address space) */ +#define XTI_CPU_BASE_ADDR 0x10000000 +#define XTI_CPU_END_ADDR 0x100FFFFF + +/* XTI configuration registers */ +#define XTI_CFG_REG_BASE_ADDR 0x101A0000 +#define XTI_CFG_REG_END_ADDR 0x101AFFFF + +/* Core APB Peripherals */ +#define CORE_APB_BASE_ADDR 0x101E0000 +#define CORE_APB_END_ADDR 0x101EFFFF + +/* DMA APB Peripherals */ +#define DMA_APB_BASE_ADDR 0x101F0000 +#define DMA_APB_END_ADDR 0x101FFFFF + +/* XTI (DSP OSMO/OSMOT address space) */ +#define XTI_DSP_BASE_ADDR 0x10200000 +#define XTI_DSP_END_ADDR 0x1020FFFF + +#endif /* defined(__STN_8810) */ + +/*--------------------------------------------------------------------------*/ +#if defined(__STN_8815) + +/* XTI (CPU OSMO/OSMOT address space) */ +#define XTI_CPU_BASE_ADDR 0x10000000 +#define XTI_CPU_END_ADDR 0x100FFFFF + +/* XTI configuration registers */ +#define XTI_CFG_REG_BASE_ADDR 0x101A0000 +#define XTI_CFG_REG_END_ADDR 0x101AFFFF + +/* Core APB Peripherals */ +#define CORE_APB_BASE_ADDR 0x101E0000 +#define CORE_APB_END_ADDR 0x101EFFFF + +/* DMA APB Peripherals */ +#define DMA_APB_BASE_ADDR 0x101F0000 +#define DMA_APB_END_ADDR 0x101FFFFF + +/* XTI (DSP OSMO/OSMOT address space) */ +#define XTI_DSP_BASE_ADDR 0x10220000 +#define XTI_DSP_END_ADDR 0x1022FFFF + +#endif /* defined(__STN_8815) */ + + +/*--------------------------------------------------------------------------*/ +#if defined(__STN_8820) + +/* STM (System Trace Module address space) */ +#define STM_BASE_ADDR 0x700F0000 +#define STM_END_ADDR 0x700FFFFF + +/* AHB2 Peripherals */ +#define AHB2_PERIPH_BASE_ADDR 0x70100000 +#define AHB2_PERIPH_END_ADDR 0x7010FFFF + +/* APB2 Peripherals */ +#define APB2_PERIPH_BASE_ADDR 0x70110000 +#define APB2_PERIPH_END_ADDR 0x7011FFFF + +/* APB1 Peripherals */ +#define APB1_PERIPH_BASE_ADDR 0x70120000 +#define APB1_PERIPH_END_ADDR 0x7012FFFF + +#endif /* defined(__STN_8820) */ + +/*--------------------------------------------------------------------------*/ +#if defined(__STN_8500) +/* STM (System Trace Module address space) */ +#define STM_BASE_ADDR 0x80100000 +#define STM_END_ADDR 0x8010FFFF + +#define HSEM_BASE_ADDR 0x80140000 +#define HSEM_END_ADDR 0x8014FFFF + +#define DMA_CTRL_BASE_ADDR 0x801C0000 +#define DMA_CTRL_END_ADDR 0x801C0FFF + + +#endif /* defined(__STN_8500) */ + +#endif /*__INC_NOMADIK_MAPPING_H */ diff --git a/drivers/staging/nmf-cm/share/semaphores/inc/hwsem_hwp.h b/drivers/staging/nmf-cm/share/semaphores/inc/hwsem_hwp.h new file mode 100644 index 00000000000..b573627beae --- /dev/null +++ b/drivers/staging/nmf-cm/share/semaphores/inc/hwsem_hwp.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + +#ifndef __INC_HWSEM_HWP_H +#define __INC_HWSEM_HWP_H + +#include <share/semaphores/inc/semaphores.h> + +#define CORE_ID_2_HW_CORE_ID(coreId) (1U << (coreId)) + +/* + * Definition of the number of hw semaphores into the Nomadik IP + */ +#define NUM_HW_SEMAPHORES 32 + + +/* + * Definition of how HSEM IP interrupts are interconnected with cores + */ +typedef enum { + HSEM_FIRST_INTR = 0, + HSEM_INTRA = HSEM_FIRST_INTR, + HSEM_INTRB = 1, + HSEM_INTRC = 2, + HSEM_INTRD = 3, + HSEM_INTRE = 4, + HSEM_MAX_INTR +} t_hw_semaphore_irq_id; + +/* + * Description of the registers of the HW Sem IP + */ +#define HSEM_INTRA_MASK (1<<(4+HSEM_INTRA)) +#define HSEM_INTRB_MASK (1<<(4+HSEM_INTRB)) +#define HSEM_INTRC_MASK (1<<(4+HSEM_INTRC)) +#define HSEM_INTRD_MASK (1<<(4+HSEM_INTRD)) +#define HSEM_INTRE_MASK (1<<(4+HSEM_INTRE)) + +typedef struct { + t_shared_reg imsc; + t_shared_reg ris; + t_shared_reg mis; + t_shared_reg icr; +} t_hsem_it_regs; + +typedef volatile struct { +#if defined(__STN_8500) + t_shared_reg cr; + t_shared_reg dummy; +#endif + t_shared_reg sem[NUM_HW_SEMAPHORES]; +#if defined(__STN_8820) + t_shared_reg RESERVED1[(0x90 - 0x80)>>2]; +#elif defined(__STN_8500) + t_shared_reg RESERVED1[(0x90 - 0x88)>>2]; +#else /* __STN_8820 or __STN_8500 -> _STN_8815 */ + t_shared_reg RESERVED1[(0x90 - 0x40)>>2]; +#endif /* __STN_8820 or __STN_8500 -> _STN_8815 */ + t_shared_reg icrall; + t_shared_reg RESERVED2[(0xa0 - 0x94)>>2]; + t_hsem_it_regs it[HSEM_MAX_INTR]; +#if defined(__STN_8820) || defined(__STN_8500) + t_shared_reg RESERVED3[(0x100 - 0xf0)>>2]; +#else /* __STN_8820 or __STN_8500 -> _STN_8815 */ + t_shared_reg RESERVED3[(0x100 - 0xe0)>>2]; +#endif /* __STN_8820 or __STN_8500 -> _STN_8815 */ + t_shared_reg itcr; + t_shared_reg RESERVED4; + t_shared_reg itop; + t_shared_reg RESERVED5[(0xfe0 - 0x10c)>>2]; + t_shared_reg pid0; + t_shared_reg pid1; + t_shared_reg pid2; + t_shared_reg pid3; + t_shared_reg pcid0; + t_shared_reg pcid1; + t_shared_reg pcid2; + t_shared_reg pcid3; +} t_hw_semaphore_regs, *tp_hw_semaphore_regs; + +#endif /* __INC_HWSEM_HWP_H */ diff --git a/drivers/staging/nmf-cm/share/semaphores/inc/semaphores.h b/drivers/staging/nmf-cm/share/semaphores/inc/semaphores.h new file mode 100644 index 00000000000..c72b64cd709 --- /dev/null +++ b/drivers/staging/nmf-cm/share/semaphores/inc/semaphores.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + */ + +#ifndef __INC_SHARED_SEMAPHORE_H +#define __INC_SHARED_SEMAPHORE_H + +#include <share/inc/nmf.h> + +typedef t_uint16 t_semaphore_id; + +/* + * HW semaphore allocation + * ----------------------- + * We want to optimize interrupt demultiplexing at dsp interrupt handler level + * so a good solution would be to have sequentially the semaphores for each neighbors + * + * STn8500 : + * --------- + * ARM <- SVA COMS => 0 + * ARM <- SIA COMS => 1 + * SVA <- ARM COMS => 2 + * SVA <- SIA COMS => 3 + * SIA <- ARM COMS => 4 + * SIA <- SVA COMS => 5 + + * The first neighbor is always the ARM, then the other ones (SVA,SIA) + */ + +/* + * Local semaphore allocation + * ----------------------- + * 0 : ARM <- DSP + * 1 : DSP <- ARM + */ + +#define NB_USED_HSEM_PER_CORE (NB_CORE_IDS - 1) +#define FIRST_NEIGHBOR_SEMID(coreId) ((coreId)*NB_USED_HSEM_PER_CORE) + +#endif /* __INC_SHARED_SEMAPHORE_H */ diff --git a/drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c b/drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c index a272e488e5b..6f9029d81ab 100644 --- a/drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c +++ b/drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c @@ -22,6 +22,7 @@ static struct synaptics_rmi4_platform_data rmi4_i2c_dev_platformdata = { .irq_type = (IRQF_TRIGGER_FALLING | IRQF_SHARED), .x_flip = false, .y_flip = true, + .regulator_en = true, }; struct i2c_board_info __initdata mop500_i2c3_devices_u8500[] = { diff --git a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c index 11728a03f8a..fd7fed743f7 100644 --- a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c +++ b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c @@ -1,11 +1,10 @@ -/** - * +/* * Synaptics Register Mapped Interface (RMI4) I2C Physical Layer Driver. * Copyright (c) 2007-2010, Synaptics Incorporated * * Author: Js HA <js.ha@stericsson.com> for ST-Ericsson * Author: Naveen Kumar G <naveen.gaddipati@stericsson.com> for ST-Ericsson - * Copyright 2010 (c) ST-Ericsson AB + * Copyright 2010 (c) ST-Ericsson SA */ /* * This file is licensed under the GPL2 license. @@ -27,6 +26,7 @@ #include <linux/input.h> #include <linux/slab.h> +#include <linux/delay.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/regulator/consumer.h> @@ -36,8 +36,10 @@ /* TODO: for multiple device support will need a per-device mutex */ #define DRIVER_NAME "synaptics_rmi4_i2c" +#define DELTA 8 #define MAX_ERROR_REPORT 6 -#define MAX_TOUCH_MAJOR 15 +#define TIMEOUT_PERIOD 1 +#define MAX_WIDTH_MAJOR 255 #define MAX_RETRY_COUNT 5 #define STD_QUERY_LEN 21 #define PAGE_LEN 2 @@ -45,6 +47,7 @@ #define BUF_LEN 37 #define QUERY_LEN 9 #define DATA_LEN 12 +#define RESUME_DELAY 100 /* msecs */ #define HAS_TAP 0x01 #define HAS_PALMDETECT 0x01 #define HAS_ROTATE 0x02 @@ -164,6 +167,8 @@ struct synaptics_rmi4_device_info { * @regulator: pointer to the regulator structure * @wait: wait queue structure variable * @touch_stopped: flag to stop the thread function + * @enable: flag to enable/disable the driver event. + * @resume_wq_handler: work queue for resume the device * * This structure gives the device data information. */ @@ -184,6 +189,8 @@ struct synaptics_rmi4_data { struct regulator *regulator; wait_queue_head_t wait; bool touch_stopped; + bool enable; + struct work_struct resume_wq_handler; }; /** @@ -291,6 +298,133 @@ exit: } /** + * synaptics_rmi4_enable() - enable the touchpad driver event + * @pdata: pointer to synaptics_rmi4_data structure + * + * This function is to enable the touchpad driver event and returns integer. + */ +static int synaptics_rmi4_enable(struct synaptics_rmi4_data *pdata) +{ + int retval; + unsigned char intr_status; + + if (pdata->board->regulator_en) + regulator_enable(pdata->regulator); + enable_irq(pdata->board->irq_number); + pdata->touch_stopped = false; + + msleep(RESUME_DELAY); + retval = synaptics_rmi4_i2c_block_read(pdata, + pdata->fn01_data_base_addr + 1, + &intr_status, + pdata->number_of_interrupt_register); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_i2c_byte_write(pdata, + pdata->fn01_ctrl_base_addr + 1, + (intr_status | TOUCHPAD_CTRL_INTR)); + if (retval < 0) + return retval; + + return 0; +} + +/** + * synaptics_rmi4_disable() - disable the touchpad driver event + * @pdata: pointer to synaptics_rmi4_data structure + * + * This function is to disable the driver event and returns integer. + */ + +static int synaptics_rmi4_disable(struct synaptics_rmi4_data *pdata) +{ + int retval; + unsigned char intr_status; + + pdata->touch_stopped = true; + disable_irq(pdata->board->irq_number); + + retval = synaptics_rmi4_i2c_block_read(pdata, + pdata->fn01_data_base_addr + 1, + &intr_status, + pdata->number_of_interrupt_register); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_i2c_byte_write(pdata, + pdata->fn01_ctrl_base_addr + 1, + (intr_status & ~TOUCHPAD_CTRL_INTR)); + if (retval < 0) + return retval; + if (pdata->board->regulator_en) + regulator_disable(pdata->regulator); + + return 0; +} + +/** + * synaptics_rmi4_show_attr_enable() - show the touchpad enable value + * @dev: pointer to device data structure + * @attr: pointer to attribute structure + * @buf: pointer to character buffer + * + * This function is to show the touchpad enable value and returns ssize_t. + */ +static ssize_t synaptics_rmi4_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *pdata = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", pdata->enable); +} + +/** + * synaptics_rmi4_store_attr_enable() - store the touchpad enable value + * @dev: pointer to device data structure + * @attr: pointer to attribute structure + * @buf: pointer to character buffer + * @count: number fo arguments + * + * This function is to store the touchpad enable value and returns ssize_t. + */ +static ssize_t synaptics_rmi4_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct synaptics_rmi4_data *pdata = dev_get_drvdata(dev); + unsigned long val; + int retval = 0; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (pdata->enable != val) { + pdata->enable = val ? true : false; + if (pdata->enable) + retval = synaptics_rmi4_enable(pdata); + else + retval = synaptics_rmi4_disable(pdata); + + } + return ((retval < 0) ? retval : count); +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + synaptics_rmi4_show_attr_enable, synaptics_rmi4_store_attr_enable); + +static struct attribute *synaptics_rmi4_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group synaptics_rmi4_attr_group = { + .attrs = synaptics_rmi4_attrs, +}; + +/** * synpatics_rmi4_touchpad_report() - reports for the rmi4 touchpad device * @pdata: pointer to synaptics_rmi4_data structure * @rfi: pointer to synaptics_rmi4_fn structure @@ -316,8 +450,9 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, unsigned char data[DATA_LEN]; int x[RMI4_NUMBER_OF_MAX_FINGERS]; int y[RMI4_NUMBER_OF_MAX_FINGERS]; - int wx[RMI4_NUMBER_OF_MAX_FINGERS]; - int wy[RMI4_NUMBER_OF_MAX_FINGERS]; + int w[RMI4_NUMBER_OF_MAX_FINGERS]; + static int prv_x[RMI4_NUMBER_OF_MAX_FINGERS]; + static int prv_y[RMI4_NUMBER_OF_MAX_FINGERS]; struct i2c_client *client = pdata->i2c_client; /* get 2D sensor finger data */ @@ -376,11 +511,7 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, y[touch_count] = (data[1] << 4) | ((data[2] >> 4) & MASK_4BIT); - wy[touch_count] = - (data[3] >> 4) & MASK_4BIT; - wx[touch_count] = - (data[3] & MASK_4BIT); - + w[touch_count] = data[3]; if (pdata->board->x_flip) x[touch_count] = pdata->sensor_max_x - @@ -389,6 +520,25 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, y[touch_count] = pdata->sensor_max_y - y[touch_count]; + if (x[touch_count] < 0) + x[touch_count] = 0; + else if (x[touch_count] >= pdata->sensor_max_x) + x[touch_count] = + pdata->sensor_max_x - 1; + + if (y[touch_count] < 0) + y[touch_count] = 0; + else if (y[touch_count] >= pdata->sensor_max_y) + y[touch_count] = + pdata->sensor_max_y - 1; + } + if ((abs(x[finger] - prv_x[finger]) < DELTA) && + (abs(y[finger] - prv_y[finger]) < DELTA)) { + x[finger] = prv_x[finger]; + y[finger] = prv_y[finger]; + } else { + prv_x[finger] = x[finger]; + prv_y[finger] = y[finger]; } /* number of active touch points */ touch_count++; @@ -399,7 +549,9 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, if (touch_count) { for (finger = 0; finger < touch_count; finger++) { input_report_abs(pdata->input_dev, ABS_MT_TOUCH_MAJOR, - max(wx[finger] , wy[finger])); + max(x[finger] , y[finger])); + input_report_abs(pdata->input_dev, ABS_MT_WIDTH_MAJOR, + w[finger]); input_report_abs(pdata->input_dev, ABS_MT_POSITION_X, x[finger]); input_report_abs(pdata->input_dev, ABS_MT_POSITION_Y, @@ -502,7 +654,7 @@ static irqreturn_t synaptics_rmi4_irq(int irq, void *data) touch_count = synaptics_rmi4_sensor_report(pdata); if (touch_count) wait_event_timeout(pdata->wait, pdata->touch_stopped, - msecs_to_jiffies(1)); + msecs_to_jiffies(TIMEOUT_PERIOD)); else break; } while (!pdata->touch_stopped); @@ -881,9 +1033,27 @@ static int synaptics_rmi4_i2c_query_device(struct synaptics_rmi4_data *pdata) } /** + * synaptics_rmi4_resume_handler() - work queue for resume handler + * @work:work_struct structure pointer + * + * This work queue handler used to resume the device and returns none + */ +static void synaptics_rmi4_resume_handler(struct work_struct *work) +{ + struct synaptics_rmi4_data *prmi4_data = container_of(work, + struct synaptics_rmi4_data, resume_wq_handler); + struct i2c_client *client = prmi4_data->i2c_client; + int retval; + + retval = synaptics_rmi4_enable(prmi4_data); + if (retval < 0) + dev_err(&client->dev, "%s: resume failed\n", __func__); +} + +/** * synaptics_rmi4_probe() - Initialze the i2c-client touchscreen driver - * @i2c: i2c client structure pointer - * @id:i2c device id pointer + * @client: i2c client structure pointer + * @dev_id:i2c device id pointer * * This function will allocate and initialize the instance * data and request the irq and set the instance data as the clients @@ -927,19 +1097,17 @@ static int __devinit synaptics_rmi4_probe goto err_input; } - rmi4_data->regulator = regulator_get(&client->dev, "vdd"); - if (IS_ERR(rmi4_data->regulator)) { - dev_err(&client->dev, "%s:get regulator failed\n", - __func__); - retval = PTR_ERR(rmi4_data->regulator); - goto err_get_regulator; - } - retval = regulator_enable(rmi4_data->regulator); - if (retval < 0) { - dev_err(&client->dev, "%s:regulator enable failed\n", - __func__); - goto err_regulator_enable; + if (platformdata->regulator_en) { + rmi4_data->regulator = regulator_get(&client->dev, "vdd"); + if (IS_ERR(rmi4_data->regulator)) { + dev_err(&client->dev, "%s:get regulator failed\n", + __func__); + retval = PTR_ERR(rmi4_data->regulator); + goto err_regulator; + } + regulator_enable(rmi4_data->regulator); } + init_waitqueue_head(&rmi4_data->wait); /* * Copy i2c_client pointer into RTID's i2c_client pointer for @@ -987,7 +1155,16 @@ static int __devinit synaptics_rmi4_probe input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_Y, 0, rmi4_data->sensor_max_y, 0, 0); input_set_abs_params(rmi4_data->input_dev, ABS_MT_TOUCH_MAJOR, 0, - MAX_TOUCH_MAJOR, 0, 0); + max(rmi4_data->sensor_max_y, rmi4_data->sensor_max_y), + 0, 0); + input_set_abs_params(rmi4_data->input_dev, ABS_MT_WIDTH_MAJOR, 0, + MAX_WIDTH_MAJOR, 0, 0); + + retval = input_register_device(rmi4_data->input_dev); + if (retval) { + dev_err(&client->dev, "%s:input register failed\n", __func__); + goto err_input_register; + } /* Clear interrupts */ synaptics_rmi4_i2c_block_read(rmi4_data, @@ -1000,24 +1177,34 @@ static int __devinit synaptics_rmi4_probe if (retval) { dev_err(&client->dev, "%s:Unable to get attn irq %d\n", __func__, platformdata->irq_number); - goto err_query_dev; + goto err_request_irq; } - retval = input_register_device(rmi4_data->input_dev); + INIT_WORK(&rmi4_data->resume_wq_handler, synaptics_rmi4_resume_handler); + + /* sysfs implementation for dynamic enable/disable the input event */ + retval = sysfs_create_group(&client->dev.kobj, + &synaptics_rmi4_attr_group); if (retval) { - dev_err(&client->dev, "%s:input register failed\n", __func__); - goto err_free_irq; + dev_err(&client->dev, "failed to create sysfs entries\n"); + goto err_sysfs; } - + rmi4_data->enable = true; return retval; -err_free_irq: +err_sysfs: + cancel_work_sync(&rmi4_data->resume_wq_handler); +err_request_irq: free_irq(platformdata->irq_number, rmi4_data); + input_unregister_device(rmi4_data->input_dev); +err_input_register: + i2c_set_clientdata(client, NULL); err_query_dev: - regulator_disable(rmi4_data->regulator); -err_regulator_enable: - regulator_put(rmi4_data->regulator); -err_get_regulator: + if (platformdata->regulator_en) { + regulator_disable(rmi4_data->regulator); + regulator_put(rmi4_data->regulator); + } +err_regulator: input_free_device(rmi4_data->input_dev); rmi4_data->input_dev = NULL; err_input: @@ -1037,12 +1224,16 @@ static int __devexit synaptics_rmi4_remove(struct i2c_client *client) struct synaptics_rmi4_data *rmi4_data = i2c_get_clientdata(client); const struct synaptics_rmi4_platform_data *pdata = rmi4_data->board; + sysfs_remove_group(&client->dev.kobj, &synaptics_rmi4_attr_group); rmi4_data->touch_stopped = true; wake_up(&rmi4_data->wait); + cancel_work_sync(&rmi4_data->resume_wq_handler); free_irq(pdata->irq_number, rmi4_data); input_unregister_device(rmi4_data->input_dev); - regulator_disable(rmi4_data->regulator); - regulator_put(rmi4_data->regulator); + if (pdata->regulator_en) { + regulator_disable(rmi4_data->regulator); + regulator_put(rmi4_data->regulator); + } kfree(rmi4_data); return 0; @@ -1059,31 +1250,11 @@ static int __devexit synaptics_rmi4_remove(struct i2c_client *client) static int synaptics_rmi4_suspend(struct device *dev) { /* Touch sleep mode */ - int retval; - unsigned char intr_status; struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); - const struct synaptics_rmi4_platform_data *pdata = rmi4_data->board; - rmi4_data->touch_stopped = true; - disable_irq(pdata->irq_number); - - retval = synaptics_rmi4_i2c_block_read(rmi4_data, - rmi4_data->fn01_data_base_addr + 1, - &intr_status, - rmi4_data->number_of_interrupt_register); - if (retval < 0) - return retval; - - retval = synaptics_rmi4_i2c_byte_write(rmi4_data, - rmi4_data->fn01_ctrl_base_addr + 1, - (intr_status & ~TOUCHPAD_CTRL_INTR)); - if (retval < 0) - return retval; - - regulator_disable(rmi4_data->regulator); - - return 0; + return synaptics_rmi4_disable(rmi4_data); } + /** * synaptics_rmi4_resume() - resume the touch screen controller * @dev: pointer to device structure @@ -1093,28 +1264,9 @@ static int synaptics_rmi4_suspend(struct device *dev) */ static int synaptics_rmi4_resume(struct device *dev) { - int retval; - unsigned char intr_status; struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); - const struct synaptics_rmi4_platform_data *pdata = rmi4_data->board; - - regulator_enable(rmi4_data->regulator); - enable_irq(pdata->irq_number); - rmi4_data->touch_stopped = false; - - retval = synaptics_rmi4_i2c_block_read(rmi4_data, - rmi4_data->fn01_data_base_addr + 1, - &intr_status, - rmi4_data->number_of_interrupt_register); - if (retval < 0) - return retval; - - retval = synaptics_rmi4_i2c_byte_write(rmi4_data, - rmi4_data->fn01_ctrl_base_addr + 1, - (intr_status | TOUCHPAD_CTRL_INTR)); - if (retval < 0) - return retval; + schedule_work(&rmi4_data->resume_wq_handler); return 0; } diff --git a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h index 384436ef806..973abc97374 100644 --- a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h +++ b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h @@ -42,6 +42,7 @@ struct synaptics_rmi4_platform_data { int irq_type; bool x_flip; bool y_flip; + bool regulator_en; }; #endif |