From 04df25d123945a2261a7755673962257241b1394 Mon Sep 17 00:00:00 2001 From: Oded Gabbay Date: Mon, 5 Jan 2015 18:15:45 +0200 Subject: MAINTAINERS: Update amdkfd files Add two files under amdkfd section. Signed-off-by: Oded Gabbay Reviewed-by: Alex Deucher --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..23fa31dc9390 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -624,6 +624,8 @@ L: dri-devel@lists.freedesktop.org T: git git://people.freedesktop.org/~gabbayo/linux.git S: Supported F: drivers/gpu/drm/amd/amdkfd/ +F: drivers/gpu/drm/amd/include/cik_structs.h +F: drivers/gpu/drm/amd/include/kgd_kfd_interface.h F: drivers/gpu/drm/radeon/radeon_kfd.c F: drivers/gpu/drm/radeon/radeon_kfd.h F: include/uapi/linux/kfd_ioctl.h -- cgit v1.2.3 From a483dcbfa21f919c7666cb897e293eff785e3bee Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Wed, 29 Oct 2014 16:47:07 +0900 Subject: ARM: shmobile: lager: Remove legacy board support Lager legacy support level is same as the DT case so remove the legacy code and force people to move over to using Multiplatform and DT. Signed-off-by: Magnus Damm [Remove lager_defconfig and don't build the dtb for legacy kernels] Signed-off-by: Laurent Pinchart Acked-by: Geert Uytterhoeven Signed-off-by: Simon Horman --- MAINTAINERS | 1 - arch/arm/boot/dts/Makefile | 1 - arch/arm/configs/lager_defconfig | 150 ------- arch/arm/mach-shmobile/Kconfig | 7 - arch/arm/mach-shmobile/Makefile | 1 - arch/arm/mach-shmobile/Makefile.boot | 1 - arch/arm/mach-shmobile/board-lager.c | 827 ----------------------------------- 7 files changed, 988 deletions(-) delete mode 100644 arch/arm/configs/lager_defconfig delete mode 100644 arch/arm/mach-shmobile/board-lager.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..f1f41fc48bb8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1395,7 +1395,6 @@ F: arch/arm/configs/ape6evm_defconfig F: arch/arm/configs/armadillo800eva_defconfig F: arch/arm/configs/bockw_defconfig F: arch/arm/configs/kzm9g_defconfig -F: arch/arm/configs/lager_defconfig F: arch/arm/configs/mackerel_defconfig F: arch/arm/configs/marzen_defconfig F: arch/arm/configs/shmobile_defconfig diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index 91bd5bd62857..19cb6fcecf89 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -410,7 +410,6 @@ dtb-$(CONFIG_ARCH_SHMOBILE_LEGACY) += \ r8a7778-bockw.dtb \ r8a7778-bockw-reference.dtb \ r8a7779-marzen.dtb \ - r8a7790-lager.dtb \ sh7372-mackerel.dtb \ sh73a0-kzm9g.dtb \ sh73a0-kzm9g-reference.dtb diff --git a/arch/arm/configs/lager_defconfig b/arch/arm/configs/lager_defconfig deleted file mode 100644 index a82afc916a89..000000000000 --- a/arch/arm/configs/lager_defconfig +++ /dev/null @@ -1,150 +0,0 @@ -CONFIG_SYSVIPC=y -CONFIG_NO_HZ=y -CONFIG_IKCONFIG=y -CONFIG_IKCONFIG_PROC=y -CONFIG_LOG_BUF_SHIFT=16 -CONFIG_CC_OPTIMIZE_FOR_SIZE=y -CONFIG_SYSCTL_SYSCALL=y -CONFIG_EMBEDDED=y -CONFIG_PERF_EVENTS=y -CONFIG_SLAB=y -# CONFIG_LBDAF is not set -# CONFIG_BLK_DEV_BSG is not set -# CONFIG_IOSCHED_DEADLINE is not set -# CONFIG_IOSCHED_CFQ is not set -CONFIG_ARCH_SHMOBILE_LEGACY=y -CONFIG_ARCH_R8A7790=y -CONFIG_MACH_LAGER=y -# CONFIG_SH_TIMER_TMU is not set -# CONFIG_EM_TIMER_STI is not set -CONFIG_ARM_ERRATA_430973=y -CONFIG_ARM_ERRATA_458693=y -CONFIG_ARM_ERRATA_460075=y -CONFIG_ARM_ERRATA_743622=y -CONFIG_ARM_ERRATA_754322=y -CONFIG_PCI=y -CONFIG_PCI_RCAR_GEN2=y -CONFIG_PCI_RCAR_GEN2_PCIE=y -CONFIG_HAVE_ARM_ARCH_TIMER=y -CONFIG_AEABI=y -# CONFIG_OABI_COMPAT is not set -CONFIG_FORCE_MAX_ZONEORDER=13 -CONFIG_ZBOOT_ROM_TEXT=0x0 -CONFIG_ZBOOT_ROM_BSS=0x0 -CONFIG_ARM_APPENDED_DTB=y -CONFIG_KEXEC=y -CONFIG_AUTO_ZRELADDR=y -CONFIG_VFP=y -CONFIG_NEON=y -# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set -CONFIG_PM=y -CONFIG_NET=y -CONFIG_PACKET=y -CONFIG_UNIX=y -CONFIG_INET=y -CONFIG_IP_PNP=y -CONFIG_IP_PNP_DHCP=y -# CONFIG_INET_XFRM_MODE_TRANSPORT is not set -# CONFIG_INET_XFRM_MODE_TUNNEL is not set -# CONFIG_INET_XFRM_MODE_BEET is not set -# CONFIG_INET_LRO is not set -# CONFIG_INET_DIAG is not set -# CONFIG_IPV6 is not set -# CONFIG_WIRELESS is not set -CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" -CONFIG_DEVTMPFS=y -CONFIG_DEVTMPFS_MOUNT=y -CONFIG_MTD=y -CONFIG_MTD_M25P80=y -CONFIG_MTD_SPI_NOR=y -CONFIG_BLK_DEV_SD=y -CONFIG_ATA=y -CONFIG_SATA_RCAR=y -CONFIG_NETDEVICES=y -# CONFIG_NET_CORE is not set -# CONFIG_NET_VENDOR_ARC is not set -# CONFIG_NET_CADENCE is not set -# CONFIG_NET_VENDOR_BROADCOM is not set -# CONFIG_NET_VENDOR_CIRRUS is not set -# CONFIG_NET_VENDOR_FARADAY is not set -# CONFIG_NET_VENDOR_INTEL is not set -# CONFIG_NET_VENDOR_MARVELL is not set -# CONFIG_NET_VENDOR_MICREL is not set -# CONFIG_NET_VENDOR_NATSEMI is not set -CONFIG_SH_ETH=y -# CONFIG_NET_VENDOR_SEEQ is not set -# CONFIG_NET_VENDOR_SMSC is not set -# CONFIG_NET_VENDOR_STMICRO is not set -# CONFIG_NET_VENDOR_VIA is not set -# CONFIG_NET_VENDOR_WIZNET is not set -# CONFIG_WLAN is not set -# CONFIG_INPUT_MOUSEDEV_PSAUX is not set -CONFIG_INPUT_EVDEV=y -# CONFIG_KEYBOARD_ATKBD is not set -CONFIG_KEYBOARD_GPIO=y -# CONFIG_INPUT_MOUSE is not set -# CONFIG_SERIO is not set -# CONFIG_LEGACY_PTYS is not set -CONFIG_SERIAL_SH_SCI=y -CONFIG_SERIAL_SH_SCI_NR_UARTS=10 -CONFIG_SERIAL_SH_SCI_CONSOLE=y -# CONFIG_HW_RANDOM is not set -CONFIG_I2C_GPIO=y -CONFIG_I2C_SH_MOBILE=y -CONFIG_I2C_RCAR=y -CONFIG_SPI=y -CONFIG_SPI_RSPI=y -CONFIG_SPI_SH_MSIOF=y -CONFIG_GPIO_SH_PFC=y -CONFIG_GPIOLIB=y -CONFIG_GPIO_RCAR=y -# CONFIG_HWMON is not set -CONFIG_THERMAL=y -CONFIG_RCAR_THERMAL=y -CONFIG_REGULATOR=y -CONFIG_REGULATOR_FIXED_VOLTAGE=y -CONFIG_REGULATOR_DA9210=y -CONFIG_REGULATOR_GPIO=y -CONFIG_MEDIA_SUPPORT=y -CONFIG_MEDIA_CAMERA_SUPPORT=y -CONFIG_V4L_PLATFORM_DRIVERS=y -CONFIG_SOC_CAMERA=y -CONFIG_SOC_CAMERA_PLATFORM=y -CONFIG_VIDEO_RCAR_VIN=y -# CONFIG_MEDIA_SUBDRV_AUTOSELECT is not set -CONFIG_VIDEO_ADV7180=y -CONFIG_DRM=y -CONFIG_DRM_RCAR_DU=y -CONFIG_SOUND=y -CONFIG_SND=y -CONFIG_SND_SOC=y -CONFIG_SND_SOC_RCAR=y -# CONFIG_USB_SUPPORT is not set -CONFIG_MMC=y -CONFIG_MMC_SDHI=y -CONFIG_MMC_SH_MMCIF=y -CONFIG_NEW_LEDS=y -CONFIG_LEDS_CLASS=y -CONFIG_LEDS_GPIO=y -CONFIG_RTC_CLASS=y -CONFIG_DMADEVICES=y -CONFIG_SH_DMAE=y -# CONFIG_IOMMU_SUPPORT is not set -# CONFIG_DNOTIFY is not set -CONFIG_MSDOS_FS=y -CONFIG_VFAT_FS=y -CONFIG_TMPFS=y -CONFIG_CONFIGFS_FS=y -# CONFIG_MISC_FILESYSTEMS is not set -CONFIG_NFS_FS=y -CONFIG_NFS_V3_ACL=y -CONFIG_NFS_V4=y -CONFIG_NFS_V4_1=y -CONFIG_ROOT_NFS=y -CONFIG_NLS_CODEPAGE_437=y -CONFIG_NLS_ISO8859_1=y -# CONFIG_ENABLE_WARN_DEPRECATED is not set -# CONFIG_ENABLE_MUST_CHECK is not set -# CONFIG_ARM_UNWIND is not set -# CONFIG_CRYPTO_ANSI_CPRNG is not set -# CONFIG_CRYPTO_HW is not set diff --git a/arch/arm/mach-shmobile/Kconfig b/arch/arm/mach-shmobile/Kconfig index 859f391aa6e1..d4211cba3513 100644 --- a/arch/arm/mach-shmobile/Kconfig +++ b/arch/arm/mach-shmobile/Kconfig @@ -203,13 +203,6 @@ config MACH_MARZEN select REGULATOR_FIXED_VOLTAGE if REGULATOR select USE_OF -config MACH_LAGER - bool "Lager board" - depends on ARCH_R8A7790 - select USE_OF - select MICREL_PHY if SH_ETH - select SND_SOC_AK4642 if SND_SIMPLE_CARD - config MACH_KZM9G bool "KZM-A9-GT board" depends on ARCH_SH73A0 diff --git a/arch/arm/mach-shmobile/Makefile b/arch/arm/mach-shmobile/Makefile index 85692c9a1cef..3eefe7dc74b6 100644 --- a/arch/arm/mach-shmobile/Makefile +++ b/arch/arm/mach-shmobile/Makefile @@ -65,7 +65,6 @@ obj-$(CONFIG_MACH_MACKEREL) += board-mackerel.o obj-$(CONFIG_MACH_BOCKW) += board-bockw.o obj-$(CONFIG_MACH_BOCKW_REFERENCE) += board-bockw-reference.o obj-$(CONFIG_MACH_MARZEN) += board-marzen.o -obj-$(CONFIG_MACH_LAGER) += board-lager.o obj-$(CONFIG_MACH_ARMADILLO800EVA) += board-armadillo800eva.o obj-$(CONFIG_MACH_KZM9G) += board-kzm9g.o obj-$(CONFIG_MACH_KZM9G_REFERENCE) += board-kzm9g-reference.o diff --git a/arch/arm/mach-shmobile/Makefile.boot b/arch/arm/mach-shmobile/Makefile.boot index 57d00ed6ec0c..02532bea5300 100644 --- a/arch/arm/mach-shmobile/Makefile.boot +++ b/arch/arm/mach-shmobile/Makefile.boot @@ -7,7 +7,6 @@ loadaddr-$(CONFIG_MACH_BOCKW) += 0x60008000 loadaddr-$(CONFIG_MACH_BOCKW_REFERENCE) += 0x60008000 loadaddr-$(CONFIG_MACH_KZM9G) += 0x41008000 loadaddr-$(CONFIG_MACH_KZM9G_REFERENCE) += 0x41008000 -loadaddr-$(CONFIG_MACH_LAGER) += 0x40008000 loadaddr-$(CONFIG_MACH_MACKEREL) += 0x40008000 loadaddr-$(CONFIG_MACH_MARZEN) += 0x60008000 diff --git a/arch/arm/mach-shmobile/board-lager.c b/arch/arm/mach-shmobile/board-lager.c deleted file mode 100644 index f8197eb6e566..000000000000 --- a/arch/arm/mach-shmobile/board-lager.c +++ /dev/null @@ -1,827 +0,0 @@ -/* - * Lager board support - * - * Copyright (C) 2013-2014 Renesas Solutions Corp. - * Copyright (C) 2013 Magnus Damm - * Copyright (C) 2014 Cogent Embedded, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "common.h" -#include "irqs.h" -#include "r8a7790.h" -#include "rcar-gen2.h" - -/* - * SSI-AK4643 - * - * SW1: 1: AK4643 - * 2: CN22 - * 3: ADV7511 - * - * this command is required when playback. - * - * # amixer set "LINEOUT Mixer DACL" on - */ - -/* - * SDHI0 (CN8) - * - * JP3: pin1 - * SW20: pin1 - - * GP5_24: 1: VDD 3.3V (defult) - * 0: VDD 0.0V - * GP5_29: 1: VccQ 3.3V (defult) - * 0: VccQ 1.8V - * - */ - -/* LEDS */ -static struct gpio_led lager_leds[] = { - { - .name = "led8", - .gpio = RCAR_GP_PIN(5, 17), - .default_state = LEDS_GPIO_DEFSTATE_ON, - }, { - .name = "led7", - .gpio = RCAR_GP_PIN(4, 23), - .default_state = LEDS_GPIO_DEFSTATE_ON, - }, { - .name = "led6", - .gpio = RCAR_GP_PIN(4, 22), - .default_state = LEDS_GPIO_DEFSTATE_ON, - }, -}; - -static const struct gpio_led_platform_data lager_leds_pdata __initconst = { - .leds = lager_leds, - .num_leds = ARRAY_SIZE(lager_leds), -}; - -/* GPIO KEY */ -#define GPIO_KEY(c, g, d, ...) \ - { .code = c, .gpio = g, .desc = d, .active_low = 1, \ - .wakeup = 1, .debounce_interval = 20 } - -static struct gpio_keys_button gpio_buttons[] = { - GPIO_KEY(KEY_4, RCAR_GP_PIN(1, 28), "SW2-pin4"), - GPIO_KEY(KEY_3, RCAR_GP_PIN(1, 26), "SW2-pin3"), - GPIO_KEY(KEY_2, RCAR_GP_PIN(1, 24), "SW2-pin2"), - GPIO_KEY(KEY_1, RCAR_GP_PIN(1, 14), "SW2-pin1"), -}; - -static const struct gpio_keys_platform_data lager_keys_pdata __initconst = { - .buttons = gpio_buttons, - .nbuttons = ARRAY_SIZE(gpio_buttons), -}; - -/* Fixed 3.3V regulator to be used by MMCIF */ -static struct regulator_consumer_supply fixed3v3_power_consumers[] = -{ - REGULATOR_SUPPLY("vmmc", "sh_mmcif.1"), -}; - -/* - * SDHI regulator macro - * - ** FIXME** - * Lager board vqmmc is provided via DA9063 PMIC chip, - * and we should use ${LINK}/drivers/mfd/da9063-* driver for it. - * but, it doesn't have regulator support at this point. - * It uses gpio-regulator for vqmmc as quick-hack. - */ -#define SDHI_REGULATOR(idx, vdd_pin, vccq_pin) \ -static struct regulator_consumer_supply vcc_sdhi##idx##_consumer = \ - REGULATOR_SUPPLY("vmmc", "sh_mobile_sdhi." #idx); \ - \ -static struct regulator_init_data vcc_sdhi##idx##_init_data = { \ - .constraints = { \ - .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ - }, \ - .consumer_supplies = &vcc_sdhi##idx##_consumer, \ - .num_consumer_supplies = 1, \ -}; \ - \ -static const struct fixed_voltage_config vcc_sdhi##idx##_info __initconst = {\ - .supply_name = "SDHI" #idx "Vcc", \ - .microvolts = 3300000, \ - .gpio = vdd_pin, \ - .enable_high = 1, \ - .init_data = &vcc_sdhi##idx##_init_data, \ -}; \ - \ -static struct regulator_consumer_supply vccq_sdhi##idx##_consumer = \ - REGULATOR_SUPPLY("vqmmc", "sh_mobile_sdhi." #idx); \ - \ -static struct regulator_init_data vccq_sdhi##idx##_init_data = { \ - .constraints = { \ - .input_uV = 3300000, \ - .min_uV = 1800000, \ - .max_uV = 3300000, \ - .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | \ - REGULATOR_CHANGE_STATUS, \ - }, \ - .consumer_supplies = &vccq_sdhi##idx##_consumer, \ - .num_consumer_supplies = 1, \ -}; \ - \ -static struct gpio vccq_sdhi##idx##_gpio = \ - { vccq_pin, GPIOF_OUT_INIT_HIGH, "vccq-sdhi" #idx }; \ - \ -static struct gpio_regulator_state vccq_sdhi##idx##_states[] = { \ - { .value = 1800000, .gpios = 0 }, \ - { .value = 3300000, .gpios = 1 }, \ -}; \ - \ -static const struct gpio_regulator_config vccq_sdhi##idx##_info __initconst = {\ - .supply_name = "vqmmc", \ - .gpios = &vccq_sdhi##idx##_gpio, \ - .nr_gpios = 1, \ - .states = vccq_sdhi##idx##_states, \ - .nr_states = ARRAY_SIZE(vccq_sdhi##idx##_states), \ - .type = REGULATOR_VOLTAGE, \ - .init_data = &vccq_sdhi##idx##_init_data, \ -}; - -SDHI_REGULATOR(0, RCAR_GP_PIN(5, 24), RCAR_GP_PIN(5, 29)); -SDHI_REGULATOR(2, RCAR_GP_PIN(5, 25), RCAR_GP_PIN(5, 30)); - -/* MMCIF */ -static const struct sh_mmcif_plat_data mmcif1_pdata __initconst = { - .caps = MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE, - .clk_ctrl2_present = true, - .ccs_unsupported = true, -}; - -static const struct resource mmcif1_resources[] __initconst = { - DEFINE_RES_MEM(0xee220000, 0x80), - DEFINE_RES_IRQ(gic_spi(170)), -}; - -/* Ether */ -static const struct sh_eth_plat_data ether_pdata __initconst = { - .phy = 0x1, - .phy_irq = irq_pin(0), - .edmac_endian = EDMAC_LITTLE_ENDIAN, - .phy_interface = PHY_INTERFACE_MODE_RMII, - .ether_link_active_low = 1, -}; - -static const struct resource ether_resources[] __initconst = { - DEFINE_RES_MEM(0xee700000, 0x400), - DEFINE_RES_IRQ(gic_spi(162)), -}; - -static const struct platform_device_info ether_info __initconst = { - .name = "r8a7790-ether", - .id = -1, - .res = ether_resources, - .num_res = ARRAY_SIZE(ether_resources), - .data = ðer_pdata, - .size_data = sizeof(ether_pdata), - .dma_mask = DMA_BIT_MASK(32), -}; - -/* SPI Flash memory (Spansion S25FL512SAGMFIG11 64Mb) */ -static struct mtd_partition spi_flash_part[] = { - /* Reserved for user loader program, read-only */ - { - .name = "loader", - .offset = 0, - .size = SZ_256K, - .mask_flags = MTD_WRITEABLE, - }, - /* Reserved for user program, read-only */ - { - .name = "user", - .offset = MTDPART_OFS_APPEND, - .size = SZ_4M, - .mask_flags = MTD_WRITEABLE, - }, - /* All else is writable (e.g. JFFS2) */ - { - .name = "flash", - .offset = MTDPART_OFS_APPEND, - .size = MTDPART_SIZ_FULL, - .mask_flags = 0, - }, -}; - -static const struct flash_platform_data spi_flash_data = { - .name = "m25p80", - .parts = spi_flash_part, - .nr_parts = ARRAY_SIZE(spi_flash_part), - .type = "s25fl512s", -}; - -static const struct rspi_plat_data qspi_pdata __initconst = { - .num_chipselect = 1, -}; - -static const struct spi_board_info spi_info[] __initconst = { - { - .modalias = "m25p80", - .platform_data = &spi_flash_data, - .mode = SPI_MODE_0 | SPI_TX_QUAD | SPI_RX_QUAD, - .max_speed_hz = 30000000, - .bus_num = 0, - .chip_select = 0, - }, -}; - -/* QSPI resource */ -static const struct resource qspi_resources[] __initconst = { - DEFINE_RES_MEM(0xe6b10000, 0x1000), - DEFINE_RES_IRQ_NAMED(gic_spi(184), "mux"), -}; - -/* VIN */ -static const struct resource vin_resources[] __initconst = { - /* VIN0 */ - DEFINE_RES_MEM(0xe6ef0000, 0x1000), - DEFINE_RES_IRQ(gic_spi(188)), - /* VIN1 */ - DEFINE_RES_MEM(0xe6ef1000, 0x1000), - DEFINE_RES_IRQ(gic_spi(189)), -}; - -static void __init lager_add_vin_device(unsigned idx, - struct rcar_vin_platform_data *pdata) -{ - struct platform_device_info vin_info = { - .name = "r8a7790-vin", - .id = idx, - .res = &vin_resources[idx * 2], - .num_res = 2, - .dma_mask = DMA_BIT_MASK(32), - .data = pdata, - .size_data = sizeof(*pdata), - }; - - BUG_ON(idx > 1); - - platform_device_register_full(&vin_info); -} - -#define LAGER_CAMERA(idx, name, addr, pdata, flag) \ -static struct i2c_board_info i2c_cam##idx##_device = { \ - I2C_BOARD_INFO(name, addr), \ -}; \ - \ -static struct rcar_vin_platform_data vin##idx##_pdata = { \ - .flags = flag, \ -}; \ - \ -static struct soc_camera_link cam##idx##_link = { \ - .bus_id = idx, \ - .board_info = &i2c_cam##idx##_device, \ - .i2c_adapter_id = 2, \ - .module_name = name, \ - .priv = pdata, \ -} - -/* Camera 0 is not currently supported due to adv7612 support missing */ -LAGER_CAMERA(1, "adv7180", 0x20, NULL, RCAR_VIN_BT656); - -static void __init lager_add_camera1_device(void) -{ - platform_device_register_data(NULL, "soc-camera-pdrv", 1, - &cam1_link, sizeof(cam1_link)); - lager_add_vin_device(1, &vin1_pdata); -} - -/* SATA1 */ -static const struct resource sata1_resources[] __initconst = { - DEFINE_RES_MEM(0xee500000, 0x2000), - DEFINE_RES_IRQ(gic_spi(106)), -}; - -static const struct platform_device_info sata1_info __initconst = { - .name = "sata-r8a7790", - .id = 1, - .res = sata1_resources, - .num_res = ARRAY_SIZE(sata1_resources), - .dma_mask = DMA_BIT_MASK(32), -}; - -/* USBHS */ -static const struct resource usbhs_resources[] __initconst = { - DEFINE_RES_MEM(0xe6590000, 0x100), - DEFINE_RES_IRQ(gic_spi(107)), -}; - -struct usbhs_private { - struct renesas_usbhs_platform_info info; - struct usb_phy *phy; -}; - -#define usbhs_get_priv(pdev) \ - container_of(renesas_usbhs_get_info(pdev), struct usbhs_private, info) - -static int usbhs_power_ctrl(struct platform_device *pdev, - void __iomem *base, int enable) -{ - struct usbhs_private *priv = usbhs_get_priv(pdev); - - if (!priv->phy) - return -ENODEV; - - if (enable) { - int retval = usb_phy_init(priv->phy); - - if (!retval) - retval = usb_phy_set_suspend(priv->phy, 0); - return retval; - } - - usb_phy_set_suspend(priv->phy, 1); - usb_phy_shutdown(priv->phy); - return 0; -} - -static int usbhs_hardware_init(struct platform_device *pdev) -{ - struct usbhs_private *priv = usbhs_get_priv(pdev); - struct usb_phy *phy; - int ret; - - /* USB0 Function - use PWEN as GPIO input to detect DIP Switch SW5 - * setting to avoid VBUS short circuit due to wrong cable. - * PWEN should be pulled up high if USB Function is selected by SW5 - */ - gpio_request_one(RCAR_GP_PIN(5, 18), GPIOF_IN, NULL); /* USB0_PWEN */ - if (!gpio_get_value(RCAR_GP_PIN(5, 18))) { - pr_warn("Error: USB Function not selected - check SW5 + SW6\n"); - ret = -ENOTSUPP; - goto error; - } - - phy = usb_get_phy_dev(&pdev->dev, 0); - if (IS_ERR(phy)) { - ret = PTR_ERR(phy); - goto error; - } - - priv->phy = phy; - return 0; - error: - gpio_free(RCAR_GP_PIN(5, 18)); - return ret; -} - -static int usbhs_hardware_exit(struct platform_device *pdev) -{ - struct usbhs_private *priv = usbhs_get_priv(pdev); - - if (!priv->phy) - return 0; - - usb_put_phy(priv->phy); - priv->phy = NULL; - - gpio_free(RCAR_GP_PIN(5, 18)); - return 0; -} - -static int usbhs_get_id(struct platform_device *pdev) -{ - return USBHS_GADGET; -} - -static u32 lager_usbhs_pipe_type[] = { - USB_ENDPOINT_XFER_CONTROL, - USB_ENDPOINT_XFER_ISOC, - USB_ENDPOINT_XFER_ISOC, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, -}; - -static struct usbhs_private usbhs_priv __initdata = { - .info = { - .platform_callback = { - .power_ctrl = usbhs_power_ctrl, - .hardware_init = usbhs_hardware_init, - .hardware_exit = usbhs_hardware_exit, - .get_id = usbhs_get_id, - }, - .driver_param = { - .buswait_bwait = 4, - .pipe_type = lager_usbhs_pipe_type, - .pipe_size = ARRAY_SIZE(lager_usbhs_pipe_type), - }, - } -}; - -static void __init lager_register_usbhs(void) -{ - usb_bind_phy("renesas_usbhs", 0, "usb_phy_rcar_gen2"); - platform_device_register_resndata(NULL, - "renesas_usbhs", -1, - usbhs_resources, - ARRAY_SIZE(usbhs_resources), - &usbhs_priv.info, - sizeof(usbhs_priv.info)); -} - -/* USBHS PHY */ -static const struct rcar_gen2_phy_platform_data usbhs_phy_pdata __initconst = { - .chan0_pci = 0, /* Channel 0 is USBHS */ - .chan2_pci = 1, /* Channel 2 is PCI USB */ -}; - -static const struct resource usbhs_phy_resources[] __initconst = { - DEFINE_RES_MEM(0xe6590100, 0x100), -}; - -/* I2C */ -static struct i2c_board_info i2c2_devices[] = { - { - I2C_BOARD_INFO("ak4643", 0x12), - } -}; - -/* Sound */ -static struct resource rsnd_resources[] __initdata = { - [RSND_GEN2_SCU] = DEFINE_RES_MEM(0xec500000, 0x1000), - [RSND_GEN2_ADG] = DEFINE_RES_MEM(0xec5a0000, 0x100), - [RSND_GEN2_SSIU] = DEFINE_RES_MEM(0xec540000, 0x1000), - [RSND_GEN2_SSI] = DEFINE_RES_MEM(0xec541000, 0x1280), -}; - -static struct rsnd_ssi_platform_info rsnd_ssi[] = { - RSND_SSI(0, gic_spi(370), 0), - RSND_SSI(0, gic_spi(371), RSND_SSI_CLK_PIN_SHARE), -}; - -static struct rsnd_src_platform_info rsnd_src[2] = { - /* no member at this point */ -}; - -static struct rsnd_dai_platform_info rsnd_dai = { - .playback = { .ssi = &rsnd_ssi[0], }, - .capture = { .ssi = &rsnd_ssi[1], }, -}; - -static struct rcar_snd_info rsnd_info = { - .flags = RSND_GEN2, - .ssi_info = rsnd_ssi, - .ssi_info_nr = ARRAY_SIZE(rsnd_ssi), - .src_info = rsnd_src, - .src_info_nr = ARRAY_SIZE(rsnd_src), - .dai_info = &rsnd_dai, - .dai_info_nr = 1, -}; - -static struct asoc_simple_card_info rsnd_card_info = { - .name = "AK4643", - .card = "SSI01-AK4643", - .codec = "ak4642-codec.2-0012", - .platform = "rcar_sound", - .daifmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM, - .cpu_dai = { - .name = "rcar_sound", - }, - .codec_dai = { - .name = "ak4642-hifi", - .sysclk = 11289600, - }, -}; - -static void __init lager_add_rsnd_device(void) -{ - struct platform_device_info cardinfo = { - .name = "asoc-simple-card", - .id = -1, - .data = &rsnd_card_info, - .size_data = sizeof(struct asoc_simple_card_info), - .dma_mask = DMA_BIT_MASK(32), - }; - - i2c_register_board_info(2, i2c2_devices, - ARRAY_SIZE(i2c2_devices)); - - platform_device_register_resndata( - NULL, "rcar_sound", -1, - rsnd_resources, ARRAY_SIZE(rsnd_resources), - &rsnd_info, sizeof(rsnd_info)); - - platform_device_register_full(&cardinfo); -} - -/* SDHI0 */ -static struct sh_mobile_sdhi_info sdhi0_info __initdata = { - .tmio_caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ | - MMC_CAP_POWER_OFF_CARD, - .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | - TMIO_MMC_WRPROTECT_DISABLE, -}; - -static struct resource sdhi0_resources[] __initdata = { - DEFINE_RES_MEM(0xee100000, 0x200), - DEFINE_RES_IRQ(gic_spi(165)), -}; - -/* SDHI2 */ -static struct sh_mobile_sdhi_info sdhi2_info __initdata = { - .tmio_caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ | - MMC_CAP_POWER_OFF_CARD, - .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | - TMIO_MMC_WRPROTECT_DISABLE, -}; - -static struct resource sdhi2_resources[] __initdata = { - DEFINE_RES_MEM(0xee140000, 0x100), - DEFINE_RES_IRQ(gic_spi(167)), -}; - -/* Internal PCI1 */ -static const struct resource pci1_resources[] __initconst = { - DEFINE_RES_MEM(0xee0b0000, 0x10000), /* CFG */ - DEFINE_RES_MEM(0xee0a0000, 0x10000), /* MEM */ - DEFINE_RES_IRQ(gic_spi(112)), -}; - -static const struct platform_device_info pci1_info __initconst = { - .name = "pci-rcar-gen2", - .id = 1, - .res = pci1_resources, - .num_res = ARRAY_SIZE(pci1_resources), - .dma_mask = DMA_BIT_MASK(32), -}; - -static void __init lager_add_usb1_device(void) -{ - platform_device_register_full(&pci1_info); -} - -/* Internal PCI2 */ -static const struct resource pci2_resources[] __initconst = { - DEFINE_RES_MEM(0xee0d0000, 0x10000), /* CFG */ - DEFINE_RES_MEM(0xee0c0000, 0x10000), /* MEM */ - DEFINE_RES_IRQ(gic_spi(113)), -}; - -static const struct platform_device_info pci2_info __initconst = { - .name = "pci-rcar-gen2", - .id = 2, - .res = pci2_resources, - .num_res = ARRAY_SIZE(pci2_resources), - .dma_mask = DMA_BIT_MASK(32), -}; - -static void __init lager_add_usb2_device(void) -{ - platform_device_register_full(&pci2_info); -} - -static const struct pinctrl_map lager_pinctrl_map[] = { - /* DU (CN10: ARGB0, CN13: LVDS) */ - PIN_MAP_MUX_GROUP_DEFAULT("rcar-du-r8a7790", "pfc-r8a7790", - "du_rgb666", "du"), - PIN_MAP_MUX_GROUP_DEFAULT("rcar-du-r8a7790", "pfc-r8a7790", - "du_sync_1", "du"), - PIN_MAP_MUX_GROUP_DEFAULT("rcar-du-r8a7790", "pfc-r8a7790", - "du_clk_out_0", "du"), - /* I2C2 */ - PIN_MAP_MUX_GROUP_DEFAULT("i2c-rcar.2", "pfc-r8a7790", - "i2c2", "i2c2"), - /* QSPI */ - PIN_MAP_MUX_GROUP_DEFAULT("qspi.0", "pfc-r8a7790", - "qspi_ctrl", "qspi"), - PIN_MAP_MUX_GROUP_DEFAULT("qspi.0", "pfc-r8a7790", - "qspi_data4", "qspi"), - /* SCIF0 (CN19: DEBUG SERIAL0) */ - PIN_MAP_MUX_GROUP_DEFAULT("sh-sci.6", "pfc-r8a7790", - "scif0_data", "scif0"), - /* SCIF1 (CN20: DEBUG SERIAL1) */ - PIN_MAP_MUX_GROUP_DEFAULT("sh-sci.7", "pfc-r8a7790", - "scif1_data", "scif1"), - /* SDHI0 */ - PIN_MAP_MUX_GROUP_DEFAULT("sh_mobile_sdhi.0", "pfc-r8a7790", - "sdhi0_data4", "sdhi0"), - PIN_MAP_MUX_GROUP_DEFAULT("sh_mobile_sdhi.0", "pfc-r8a7790", - "sdhi0_ctrl", "sdhi0"), - PIN_MAP_MUX_GROUP_DEFAULT("sh_mobile_sdhi.0", "pfc-r8a7790", - "sdhi0_cd", "sdhi0"), - /* SDHI2 */ - PIN_MAP_MUX_GROUP_DEFAULT("sh_mobile_sdhi.2", "pfc-r8a7790", - "sdhi2_data4", "sdhi2"), - PIN_MAP_MUX_GROUP_DEFAULT("sh_mobile_sdhi.2", "pfc-r8a7790", - "sdhi2_ctrl", "sdhi2"), - PIN_MAP_MUX_GROUP_DEFAULT("sh_mobile_sdhi.2", "pfc-r8a7790", - "sdhi2_cd", "sdhi2"), - /* SSI (CN17: sound) */ - PIN_MAP_MUX_GROUP_DEFAULT("rcar_sound", "pfc-r8a7790", - "ssi0129_ctrl", "ssi"), - PIN_MAP_MUX_GROUP_DEFAULT("rcar_sound", "pfc-r8a7790", - "ssi0_data", "ssi"), - PIN_MAP_MUX_GROUP_DEFAULT("rcar_sound", "pfc-r8a7790", - "ssi1_data", "ssi"), - PIN_MAP_MUX_GROUP_DEFAULT("rcar_sound", "pfc-r8a7790", - "audio_clk_a", "audio_clk"), - /* MMCIF1 */ - PIN_MAP_MUX_GROUP_DEFAULT("sh_mmcif.1", "pfc-r8a7790", - "mmc1_data8", "mmc1"), - PIN_MAP_MUX_GROUP_DEFAULT("sh_mmcif.1", "pfc-r8a7790", - "mmc1_ctrl", "mmc1"), - /* Ether */ - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-ether", "pfc-r8a7790", - "eth_link", "eth"), - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-ether", "pfc-r8a7790", - "eth_mdio", "eth"), - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-ether", "pfc-r8a7790", - "eth_rmii", "eth"), - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-ether", "pfc-r8a7790", - "intc_irq0", "intc"), - /* VIN0 */ - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-vin.0", "pfc-r8a7790", - "vin0_data24", "vin0"), - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-vin.0", "pfc-r8a7790", - "vin0_sync", "vin0"), - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-vin.0", "pfc-r8a7790", - "vin0_field", "vin0"), - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-vin.0", "pfc-r8a7790", - "vin0_clkenb", "vin0"), - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-vin.0", "pfc-r8a7790", - "vin0_clk", "vin0"), - /* VIN1 */ - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-vin.1", "pfc-r8a7790", - "vin1_data8", "vin1"), - PIN_MAP_MUX_GROUP_DEFAULT("r8a7790-vin.1", "pfc-r8a7790", - "vin1_clk", "vin1"), - /* USB0 */ - PIN_MAP_MUX_GROUP_DEFAULT("renesas_usbhs", "pfc-r8a7790", - "usb0_ovc_vbus", "usb0"), - /* USB1 */ - PIN_MAP_MUX_GROUP_DEFAULT("pci-rcar-gen2.1", "pfc-r8a7790", - "usb1", "usb1"), - /* USB2 */ - PIN_MAP_MUX_GROUP_DEFAULT("pci-rcar-gen2.2", "pfc-r8a7790", - "usb2", "usb2"), -}; - -static void __init lager_add_standard_devices(void) -{ - int fixed_regulator_idx = 0; - int gpio_regulator_idx = 0; - - r8a7790_clock_init(); - - pinctrl_register_mappings(lager_pinctrl_map, - ARRAY_SIZE(lager_pinctrl_map)); - r8a7790_pinmux_init(); - - r8a7790_add_standard_devices(); - platform_device_register_data(NULL, "leds-gpio", -1, - &lager_leds_pdata, - sizeof(lager_leds_pdata)); - platform_device_register_data(NULL, "gpio-keys", -1, - &lager_keys_pdata, - sizeof(lager_keys_pdata)); - regulator_register_always_on(fixed_regulator_idx++, - "fixed-3.3V", fixed3v3_power_consumers, - ARRAY_SIZE(fixed3v3_power_consumers), 3300000); - platform_device_register_resndata(NULL, "sh_mmcif", 1, - mmcif1_resources, ARRAY_SIZE(mmcif1_resources), - &mmcif1_pdata, sizeof(mmcif1_pdata)); - - platform_device_register_full(ðer_info); - - platform_device_register_resndata(NULL, "qspi", 0, - qspi_resources, - ARRAY_SIZE(qspi_resources), - &qspi_pdata, sizeof(qspi_pdata)); - spi_register_board_info(spi_info, ARRAY_SIZE(spi_info)); - - platform_device_register_data(NULL, "reg-fixed-voltage", fixed_regulator_idx++, - &vcc_sdhi0_info, sizeof(struct fixed_voltage_config)); - platform_device_register_data(NULL, "reg-fixed-voltage", fixed_regulator_idx++, - &vcc_sdhi2_info, sizeof(struct fixed_voltage_config)); - - platform_device_register_data(NULL, "gpio-regulator", gpio_regulator_idx++, - &vccq_sdhi0_info, sizeof(struct gpio_regulator_config)); - platform_device_register_data(NULL, "gpio-regulator", gpio_regulator_idx++, - &vccq_sdhi2_info, sizeof(struct gpio_regulator_config)); - - lager_add_camera1_device(); - - platform_device_register_full(&sata1_info); - - platform_device_register_resndata(NULL, "usb_phy_rcar_gen2", - -1, usbhs_phy_resources, - ARRAY_SIZE(usbhs_phy_resources), - &usbhs_phy_pdata, - sizeof(usbhs_phy_pdata)); - lager_register_usbhs(); - lager_add_usb1_device(); - lager_add_usb2_device(); - - lager_add_rsnd_device(); - - platform_device_register_resndata(NULL, "sh_mobile_sdhi", 0, - sdhi0_resources, ARRAY_SIZE(sdhi0_resources), - &sdhi0_info, sizeof(struct sh_mobile_sdhi_info)); - platform_device_register_resndata(NULL, "sh_mobile_sdhi", 2, - sdhi2_resources, ARRAY_SIZE(sdhi2_resources), - &sdhi2_info, sizeof(struct sh_mobile_sdhi_info)); -} - -/* - * Ether LEDs on the Lager board are named LINK and ACTIVE which corresponds - * to non-default 01 setting of the Micrel KSZ8041 PHY control register 1 bits - * 14-15. We have to set them back to 01 from the default 00 value each time - * the PHY is reset. It's also important because the PHY's LED0 signal is - * connected to SoC's ETH_LINK signal and in the PHY's default mode it will - * bounce on and off after each packet, which we apparently want to avoid. - */ -static int lager_ksz8041_fixup(struct phy_device *phydev) -{ - u16 phyctrl1 = phy_read(phydev, 0x1e); - - phyctrl1 &= ~0xc000; - phyctrl1 |= 0x4000; - return phy_write(phydev, 0x1e, phyctrl1); -} - -static void __init lager_init(void) -{ - lager_add_standard_devices(); - - irq_set_irq_type(irq_pin(0), IRQ_TYPE_LEVEL_LOW); - - if (IS_ENABLED(CONFIG_PHYLIB)) - phy_register_fixup_for_id("r8a7790-ether-ff:01", - lager_ksz8041_fixup); -} - -static const char * const lager_boards_compat_dt[] __initconst = { - "renesas,lager", - NULL, -}; - -DT_MACHINE_START(LAGER_DT, "lager") - .smp = smp_ops(r8a7790_smp_ops), - .init_early = shmobile_init_delay, - .init_time = rcar_gen2_timer_init, - .init_machine = lager_init, - .init_late = shmobile_init_late, - .reserve = rcar_gen2_reserve, - .dt_compat = lager_boards_compat_dt, -MACHINE_END -- cgit v1.2.3 From af6a5af8e8cc1566fc06636de02347825808650e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 18 Dec 2014 09:24:50 -0800 Subject: Input: add new sun4i-lradc-keys driver Allwinnner sunxi SoCs have a low resolution adc (called lradc) which is specifically designed to have various (tablet) keys (ie home, back, search, etc). attached to it using a resistor network. This adds a driver for this. There are 2 channels, currently this driver only supports chan0 since there are no boards known to use chan1. This has been tested on an olimex a10s-olinuxino-micro, a13-olinuxino, and a20-olinuxino-micro. Signed-off-by: Hans de Goede Signed-off-by: Dmitry Torokhov --- .../devicetree/bindings/input/sun4i-lradc-keys.txt | 62 +++++ MAINTAINERS | 7 + drivers/input/keyboard/Kconfig | 10 + drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/sun4i-lradc-keys.c | 286 +++++++++++++++++++++ 5 files changed, 366 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt create mode 100644 drivers/input/keyboard/sun4i-lradc-keys.c (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt b/Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt new file mode 100644 index 000000000000..b9c32f6fd687 --- /dev/null +++ b/Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt @@ -0,0 +1,62 @@ +Allwinner sun4i low res adc attached tablet keys +------------------------------------------------ + +Required properties: + - compatible: "allwinner,sun4i-a10-lradc-keys" + - reg: mmio address range of the chip + - interrupts: interrupt to which the chip is connected + - vref-supply: powersupply for the lradc reference voltage + +Each key is represented as a sub-node of "allwinner,sun4i-a10-lradc-keys": + +Required subnode-properties: + - label: Descriptive name of the key. + - linux,code: Keycode to emit. + - channel: Channel this key is attached to, mut be 0 or 1. + - voltage: Voltage in µV at lradc input when this key is pressed. + +Example: + +#include + + lradc: lradc@01c22800 { + compatible = "allwinner,sun4i-a10-lradc-keys"; + reg = <0x01c22800 0x100>; + interrupts = <31>; + vref-supply = <®_vcc3v0>; + + button@191 { + label = "Volume Up"; + linux,code = ; + channel = <0>; + voltage = <191274>; + }; + + button@392 { + label = "Volume Down"; + linux,code = ; + channel = <0>; + voltage = <392644>; + }; + + button@601 { + label = "Menu"; + linux,code = ; + channel = <0>; + voltage = <601151>; + }; + + button@795 { + label = "Enter"; + linux,code = ; + channel = <0>; + voltage = <795090>; + }; + + button@987 { + label = "Home"; + linux,code = ; + channel = <0>; + voltage = <987387>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index f73bb4179832..21b834b191d0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8806,6 +8806,13 @@ F: arch/m68k/sun3*/ F: arch/m68k/include/asm/sun3* F: drivers/net/ethernet/i825xx/sun3* +SUN4I LOW RES ADC ATTACHED TABLET KEYS DRIVER +M: Hans de Goede +L: linux-input@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt +F: drivers/input/keyboard/sun4i-lradc-keys.c + SUNDANCE NETWORK DRIVER M: Denis Kirjanov L: netdev@vger.kernel.org diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index a5d9b3f3c871..a89ba7cb96f1 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -568,6 +568,16 @@ config KEYBOARD_STMPE To compile this driver as a module, choose M here: the module will be called stmpe-keypad. +config KEYBOARD_SUN4I_LRADC + tristate "Allwinner sun4i low res adc attached tablet keys support" + depends on ARCH_SUNXI + help + This selects support for the Allwinner low res adc attached tablet + keys found on Allwinner sunxi SoCs. + + To compile this driver as a module, choose M here: the + module will be called sun4i-lradc-keys. + config KEYBOARD_DAVINCI tristate "TI DaVinci Key Scan" depends on ARCH_DAVINCI_DM365 diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index febafa527eb6..470767884bd8 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_KEYBOARD_SPEAR) += spear-keyboard.o obj-$(CONFIG_KEYBOARD_STMPE) += stmpe-keypad.o obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o obj-$(CONFIG_KEYBOARD_ST_KEYSCAN) += st-keyscan.o +obj-$(CONFIG_KEYBOARD_SUN4I_LRADC) += sun4i-lradc-keys.o obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o obj-$(CONFIG_KEYBOARD_TC3589X) += tc3589x-keypad.o obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o diff --git a/drivers/input/keyboard/sun4i-lradc-keys.c b/drivers/input/keyboard/sun4i-lradc-keys.c new file mode 100644 index 000000000000..cc8f7ddcee53 --- /dev/null +++ b/drivers/input/keyboard/sun4i-lradc-keys.c @@ -0,0 +1,286 @@ +/* + * Allwinner sun4i low res adc attached tablet keys driver + * + * Copyright (C) 2014 Hans de Goede + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Allwinnner sunxi SoCs have a lradc which is specifically designed to have + * various (tablet) keys (ie home, back, search, etc). attached to it using + * a resistor network. This driver is for the keys on such boards. + * + * There are 2 channels, currently this driver only supports channel 0 since + * there are no boards known to use channel 1. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LRADC_CTRL 0x00 +#define LRADC_INTC 0x04 +#define LRADC_INTS 0x08 +#define LRADC_DATA0 0x0c +#define LRADC_DATA1 0x10 + +/* LRADC_CTRL bits */ +#define FIRST_CONVERT_DLY(x) ((x) << 24) /* 8 bits */ +#define CHAN_SELECT(x) ((x) << 22) /* 2 bits */ +#define CONTINUE_TIME_SEL(x) ((x) << 16) /* 4 bits */ +#define KEY_MODE_SEL(x) ((x) << 12) /* 2 bits */ +#define LEVELA_B_CNT(x) ((x) << 8) /* 4 bits */ +#define HOLD_EN(x) ((x) << 6) +#define LEVELB_VOL(x) ((x) << 4) /* 2 bits */ +#define SAMPLE_RATE(x) ((x) << 2) /* 2 bits */ +#define ENABLE(x) ((x) << 0) + +/* LRADC_INTC and LRADC_INTS bits */ +#define CHAN1_KEYUP_IRQ BIT(12) +#define CHAN1_ALRDY_HOLD_IRQ BIT(11) +#define CHAN1_HOLD_IRQ BIT(10) +#define CHAN1_KEYDOWN_IRQ BIT(9) +#define CHAN1_DATA_IRQ BIT(8) +#define CHAN0_KEYUP_IRQ BIT(4) +#define CHAN0_ALRDY_HOLD_IRQ BIT(3) +#define CHAN0_HOLD_IRQ BIT(2) +#define CHAN0_KEYDOWN_IRQ BIT(1) +#define CHAN0_DATA_IRQ BIT(0) + +struct sun4i_lradc_keymap { + u32 voltage; + u32 keycode; +}; + +struct sun4i_lradc_data { + struct device *dev; + struct input_dev *input; + void __iomem *base; + struct regulator *vref_supply; + struct sun4i_lradc_keymap *chan0_map; + u32 chan0_map_count; + u32 chan0_keycode; + u32 vref; +}; + +static irqreturn_t sun4i_lradc_irq(int irq, void *dev_id) +{ + struct sun4i_lradc_data *lradc = dev_id; + u32 i, ints, val, voltage, diff, keycode = 0, closest = 0xffffffff; + + ints = readl(lradc->base + LRADC_INTS); + + /* + * lradc supports only one keypress at a time, release does not give + * any info as to which key was released, so we cache the keycode. + */ + + if (ints & CHAN0_KEYUP_IRQ) { + input_report_key(lradc->input, lradc->chan0_keycode, 0); + lradc->chan0_keycode = 0; + } + + if ((ints & CHAN0_KEYDOWN_IRQ) && lradc->chan0_keycode == 0) { + val = readl(lradc->base + LRADC_DATA0) & 0x3f; + voltage = val * lradc->vref / 63; + + for (i = 0; i < lradc->chan0_map_count; i++) { + diff = abs(lradc->chan0_map[i].voltage - voltage); + if (diff < closest) { + closest = diff; + keycode = lradc->chan0_map[i].keycode; + } + } + + lradc->chan0_keycode = keycode; + input_report_key(lradc->input, lradc->chan0_keycode, 1); + } + + input_sync(lradc->input); + + writel(ints, lradc->base + LRADC_INTS); + + return IRQ_HANDLED; +} + +static int sun4i_lradc_open(struct input_dev *dev) +{ + struct sun4i_lradc_data *lradc = input_get_drvdata(dev); + int error; + + error = regulator_enable(lradc->vref_supply); + if (error) + return error; + + /* lradc Vref internally is divided by 2/3 */ + lradc->vref = regulator_get_voltage(lradc->vref_supply) * 2 / 3; + + /* + * Set sample time to 4 ms / 250 Hz. Wait 2 * 4 ms for key to + * stabilize on press, wait (1 + 1) * 4 ms for key release + */ + writel(FIRST_CONVERT_DLY(2) | LEVELA_B_CNT(1) | HOLD_EN(1) | + SAMPLE_RATE(0) | ENABLE(1), lradc->base + LRADC_CTRL); + + writel(CHAN0_KEYUP_IRQ | CHAN0_KEYDOWN_IRQ, lradc->base + LRADC_INTC); + + return 0; +} + +static void sun4i_lradc_close(struct input_dev *dev) +{ + struct sun4i_lradc_data *lradc = input_get_drvdata(dev); + + /* Disable lradc, leave other settings unchanged */ + writel(FIRST_CONVERT_DLY(2) | LEVELA_B_CNT(1) | HOLD_EN(1) | + SAMPLE_RATE(2), lradc->base + LRADC_CTRL); + writel(0, lradc->base + LRADC_INTC); + + regulator_disable(lradc->vref_supply); +} + +static int sun4i_lradc_load_dt_keymap(struct device *dev, + struct sun4i_lradc_data *lradc) +{ + struct device_node *np, *pp; + int i; + int error; + + np = dev->of_node; + if (!np) + return -EINVAL; + + lradc->chan0_map_count = of_get_child_count(np); + if (lradc->chan0_map_count == 0) { + dev_err(dev, "keymap is missing in device tree\n"); + return -EINVAL; + } + + lradc->chan0_map = devm_kmalloc_array(dev, lradc->chan0_map_count, + sizeof(struct sun4i_lradc_keymap), + GFP_KERNEL); + if (!lradc->chan0_map) + return -ENOMEM; + + i = 0; + for_each_child_of_node(np, pp) { + struct sun4i_lradc_keymap *map = &lradc->chan0_map[i]; + u32 channel; + + error = of_property_read_u32(pp, "channel", &channel); + if (error || channel != 0) { + dev_err(dev, "%s: Inval channel prop\n", pp->name); + return -EINVAL; + } + + error = of_property_read_u32(pp, "voltage", &map->voltage); + if (error) { + dev_err(dev, "%s: Inval voltage prop\n", pp->name); + return -EINVAL; + } + + error = of_property_read_u32(pp, "linux,code", &map->keycode); + if (error) { + dev_err(dev, "%s: Inval linux,code prop\n", pp->name); + return -EINVAL; + } + + i++; + } + + return 0; +} + +static int sun4i_lradc_probe(struct platform_device *pdev) +{ + struct sun4i_lradc_data *lradc; + struct device *dev = &pdev->dev; + int i; + int error; + + lradc = devm_kzalloc(dev, sizeof(struct sun4i_lradc_data), GFP_KERNEL); + if (!lradc) + return -ENOMEM; + + error = sun4i_lradc_load_dt_keymap(dev, lradc); + if (error) + return error; + + lradc->vref_supply = devm_regulator_get(dev, "vref"); + if (IS_ERR(lradc->vref_supply)) + return PTR_ERR(lradc->vref_supply); + + lradc->dev = dev; + lradc->input = devm_input_allocate_device(dev); + if (!lradc->input) + return -ENOMEM; + + lradc->input->name = pdev->name; + lradc->input->phys = "sun4i_lradc/input0"; + lradc->input->open = sun4i_lradc_open; + lradc->input->close = sun4i_lradc_close; + lradc->input->id.bustype = BUS_HOST; + lradc->input->id.vendor = 0x0001; + lradc->input->id.product = 0x0001; + lradc->input->id.version = 0x0100; + + __set_bit(EV_KEY, lradc->input->evbit); + for (i = 0; i < lradc->chan0_map_count; i++) + __set_bit(lradc->chan0_map[i].keycode, lradc->input->keybit); + + input_set_drvdata(lradc->input, lradc); + + lradc->base = devm_ioremap_resource(dev, + platform_get_resource(pdev, IORESOURCE_MEM, 0)); + if (IS_ERR(lradc->base)) + return PTR_ERR(lradc->base); + + error = devm_request_irq(dev, platform_get_irq(pdev, 0), + sun4i_lradc_irq, 0, + "sun4i-a10-lradc-keys", lradc); + if (error) + return error; + + error = input_register_device(lradc->input); + if (error) + return error; + + platform_set_drvdata(pdev, lradc); + return 0; +} + +static const struct of_device_id sun4i_lradc_of_match[] = { + { .compatible = "allwinner,sun4i-a10-lradc-keys", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sun4i_lradc_of_match); + +static struct platform_driver sun4i_lradc_driver = { + .driver = { + .name = "sun4i-a10-lradc-keys", + .of_match_table = of_match_ptr(sun4i_lradc_of_match), + }, + .probe = sun4i_lradc_probe, +}; + +module_platform_driver(sun4i_lradc_driver); + +MODULE_DESCRIPTION("Allwinner sun4i low res adc attached tablet keys driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From b700e7f03df5d92f85fa5247fe1f557528d3363d Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Tue, 16 Dec 2014 11:58:19 -0600 Subject: livepatch: kernel: add support for live patching This commit introduces code for the live patching core. It implements an ftrace-based mechanism and kernel interface for doing live patching of kernel and kernel module functions. It represents the greatest common functionality set between kpatch and kgraft and can accept patches built using either method. This first version does not implement any consistency mechanism that ensures that old and new code do not run together. In practice, ~90% of CVEs are safe to apply in this way, since they simply add a conditional check. However, any function change that can not execute safely with the old version of the function can _not_ be safely applied in this version. [ jkosina@suse.cz: due to the number of contributions that got folded into this original patch from Seth Jennings, add SUSE's copyright as well, as discussed via e-mail ] Signed-off-by: Seth Jennings Signed-off-by: Josh Poimboeuf Reviewed-by: Miroslav Benes Reviewed-by: Petr Mladek Reviewed-by: Masami Hiramatsu Signed-off-by: Miroslav Benes Signed-off-by: Petr Mladek Signed-off-by: Jiri Kosina --- Documentation/ABI/testing/sysfs-kernel-livepatch | 44 ++ MAINTAINERS | 13 + arch/x86/Kconfig | 3 + arch/x86/include/asm/livepatch.h | 37 + arch/x86/kernel/Makefile | 1 + arch/x86/kernel/livepatch.c | 90 +++ include/linux/livepatch.h | 133 ++++ kernel/Makefile | 1 + kernel/livepatch/Kconfig | 18 + kernel/livepatch/Makefile | 3 + kernel/livepatch/core.c | 930 +++++++++++++++++++++++ 11 files changed, 1273 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-kernel-livepatch create mode 100644 arch/x86/include/asm/livepatch.h create mode 100644 arch/x86/kernel/livepatch.c create mode 100644 include/linux/livepatch.h create mode 100644 kernel/livepatch/Kconfig create mode 100644 kernel/livepatch/Makefile create mode 100644 kernel/livepatch/core.c (limited to 'MAINTAINERS') diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch new file mode 100644 index 000000000000..5bf42a840b22 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-kernel-livepatch @@ -0,0 +1,44 @@ +What: /sys/kernel/livepatch +Date: Nov 2014 +KernelVersion: 3.19.0 +Contact: live-patching@vger.kernel.org +Description: + Interface for kernel live patching + + The /sys/kernel/livepatch directory contains subdirectories for + each loaded live patch module. + +What: /sys/kernel/livepatch/ +Date: Nov 2014 +KernelVersion: 3.19.0 +Contact: live-patching@vger.kernel.org +Description: + The patch directory contains subdirectories for each kernel + object (vmlinux or a module) in which it patched functions. + +What: /sys/kernel/livepatch//enabled +Date: Nov 2014 +KernelVersion: 3.19.0 +Contact: live-patching@vger.kernel.org +Description: + A writable attribute that indicates whether the patched + code is currently applied. Writing 0 will disable the patch + while writing 1 will re-enable the patch. + +What: /sys/kernel/livepatch// +Date: Nov 2014 +KernelVersion: 3.19.0 +Contact: live-patching@vger.kernel.org +Description: + The object directory contains subdirectories for each function + that is patched within the object. + +What: /sys/kernel/livepatch/// +Date: Nov 2014 +KernelVersion: 3.19.0 +Contact: live-patching@vger.kernel.org +Description: + The function directory contains attributes regarding the + properties and state of the patched function. + + There are currently no such attributes. diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..df6a0784b466 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5784,6 +5784,19 @@ F: Documentation/misc-devices/lis3lv02d F: drivers/misc/lis3lv02d/ F: drivers/platform/x86/hp_accel.c +LIVE PATCHING +M: Josh Poimboeuf +M: Seth Jennings +M: Jiri Kosina +M: Vojtech Pavlik +S: Maintained +F: kernel/livepatch/ +F: include/linux/livepatch.h +F: arch/x86/include/asm/livepatch.h +F: arch/x86/kernel/livepatch.c +F: Documentation/ABI/testing/sysfs-kernel-livepatch +L: live-patching@vger.kernel.org + LLC (802.2) M: Arnaldo Carvalho de Melo S: Maintained diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index ba397bde7948..460b31b79938 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -17,6 +17,7 @@ config X86_64 depends on 64BIT select X86_DEV_DMA_OPS select ARCH_USE_CMPXCHG_LOCKREF + select ARCH_HAVE_LIVE_PATCHING ### Arch settings config X86 @@ -2008,6 +2009,8 @@ config CMDLINE_OVERRIDE This is used to work around broken boot loaders. This should be set to 'N' under normal conditions. +source "kernel/livepatch/Kconfig" + endmenu config ARCH_ENABLE_MEMORY_HOTPLUG diff --git a/arch/x86/include/asm/livepatch.h b/arch/x86/include/asm/livepatch.h new file mode 100644 index 000000000000..d529db1b1edf --- /dev/null +++ b/arch/x86/include/asm/livepatch.h @@ -0,0 +1,37 @@ +/* + * livepatch.h - x86-specific Kernel Live Patching Core + * + * Copyright (C) 2014 Seth Jennings + * Copyright (C) 2014 SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _ASM_X86_LIVEPATCH_H +#define _ASM_X86_LIVEPATCH_H + +#include + +#ifdef CONFIG_LIVE_PATCHING +#ifndef CC_USING_FENTRY +#error Your compiler must support -mfentry for live patching to work +#endif +extern int klp_write_module_reloc(struct module *mod, unsigned long type, + unsigned long loc, unsigned long value); + +#else +#error Live patching support is disabled; check CONFIG_LIVE_PATCHING +#endif + +#endif /* _ASM_X86_LIVEPATCH_H */ diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 5d4502c8b983..316b34e74c15 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_X86_MPPARSE) += mpparse.o obj-y += apic/ obj-$(CONFIG_X86_REBOOTFIXUPS) += reboot_fixups_32.o obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o +obj-$(CONFIG_LIVE_PATCHING) += livepatch.o obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o obj-$(CONFIG_FTRACE_SYSCALLS) += ftrace.o obj-$(CONFIG_X86_TSC) += trace_clock.o diff --git a/arch/x86/kernel/livepatch.c b/arch/x86/kernel/livepatch.c new file mode 100644 index 000000000000..ff3c3101d003 --- /dev/null +++ b/arch/x86/kernel/livepatch.c @@ -0,0 +1,90 @@ +/* + * livepatch.c - x86-specific Kernel Live Patching Core + * + * Copyright (C) 2014 Seth Jennings + * Copyright (C) 2014 SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +/** + * klp_write_module_reloc() - write a relocation in a module + * @mod: module in which the section to be modified is found + * @type: ELF relocation type (see asm/elf.h) + * @loc: address that the relocation should be written to + * @value: relocation value (sym address + addend) + * + * This function writes a relocation to the specified location for + * a particular module. + */ +int klp_write_module_reloc(struct module *mod, unsigned long type, + unsigned long loc, unsigned long value) +{ + int ret, numpages, size = 4; + bool readonly; + unsigned long val; + unsigned long core = (unsigned long)mod->module_core; + unsigned long core_ro_size = mod->core_ro_size; + unsigned long core_size = mod->core_size; + + switch (type) { + case R_X86_64_NONE: + return 0; + case R_X86_64_64: + val = value; + size = 8; + break; + case R_X86_64_32: + val = (u32)value; + break; + case R_X86_64_32S: + val = (s32)value; + break; + case R_X86_64_PC32: + val = (u32)(value - loc); + break; + default: + /* unsupported relocation type */ + return -EINVAL; + } + + if (loc < core || loc >= core + core_size) + /* loc does not point to any symbol inside the module */ + return -EINVAL; + + if (loc < core + core_ro_size) + readonly = true; + else + readonly = false; + + /* determine if the relocation spans a page boundary */ + numpages = ((loc & PAGE_MASK) == ((loc + size) & PAGE_MASK)) ? 1 : 2; + + if (readonly) + set_memory_rw(loc & PAGE_MASK, numpages); + + ret = probe_kernel_write((void *)loc, &val, size); + + if (readonly) + set_memory_ro(loc & PAGE_MASK, numpages); + + return ret; +} diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h new file mode 100644 index 000000000000..950bc615842f --- /dev/null +++ b/include/linux/livepatch.h @@ -0,0 +1,133 @@ +/* + * livepatch.h - Kernel Live Patching Core + * + * Copyright (C) 2014 Seth Jennings + * Copyright (C) 2014 SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _LINUX_LIVEPATCH_H_ +#define _LINUX_LIVEPATCH_H_ + +#include +#include + +#if IS_ENABLED(CONFIG_LIVE_PATCHING) + +#include + +enum klp_state { + KLP_DISABLED, + KLP_ENABLED +}; + +/** + * struct klp_func - function structure for live patching + * @old_name: name of the function to be patched + * @new_func: pointer to the patched function code + * @old_addr: a hint conveying at what address the old function + * can be found (optional, vmlinux patches only) + * @kobj: kobject for sysfs resources + * @fops: ftrace operations structure + * @state: tracks function-level patch application state + */ +struct klp_func { + /* external */ + const char *old_name; + void *new_func; + /* + * The old_addr field is optional and can be used to resolve + * duplicate symbol names in the vmlinux object. If this + * information is not present, the symbol is located by name + * with kallsyms. If the name is not unique and old_addr is + * not provided, the patch application fails as there is no + * way to resolve the ambiguity. + */ + unsigned long old_addr; + + /* internal */ + struct kobject kobj; + struct ftrace_ops *fops; + enum klp_state state; +}; + +/** + * struct klp_reloc - relocation structure for live patching + * @loc: address where the relocation will be written + * @val: address of the referenced symbol (optional, + * vmlinux patches only) + * @type: ELF relocation type + * @name: name of the referenced symbol (for lookup/verification) + * @addend: offset from the referenced symbol + * @external: symbol is either exported or within the live patch module itself + */ +struct klp_reloc { + unsigned long loc; + unsigned long val; + unsigned long type; + const char *name; + int addend; + int external; +}; + +/** + * struct klp_object - kernel object structure for live patching + * @name: module name (or NULL for vmlinux) + * @relocs: relocation entries to be applied at load time + * @funcs: function entries for functions to be patched in the object + * @kobj: kobject for sysfs resources + * @mod: kernel module associated with the patched object + * (NULL for vmlinux) + * @state: tracks object-level patch application state + */ +struct klp_object { + /* external */ + const char *name; + struct klp_reloc *relocs; + struct klp_func *funcs; + + /* internal */ + struct kobject *kobj; + struct module *mod; + enum klp_state state; +}; + +/** + * struct klp_patch - patch structure for live patching + * @mod: reference to the live patch module + * @objs: object entries for kernel objects to be patched + * @list: list node for global list of registered patches + * @kobj: kobject for sysfs resources + * @state: tracks patch-level application state + */ +struct klp_patch { + /* external */ + struct module *mod; + struct klp_object *objs; + + /* internal */ + struct list_head list; + struct kobject kobj; + enum klp_state state; +}; + +extern int klp_register_patch(struct klp_patch *); +extern int klp_unregister_patch(struct klp_patch *); +extern int klp_enable_patch(struct klp_patch *); +extern int klp_disable_patch(struct klp_patch *); + +#endif /* CONFIG_LIVE_PATCHING */ + +#endif /* _LINUX_LIVEPATCH_H_ */ diff --git a/kernel/Makefile b/kernel/Makefile index a59481a3fa6c..616994f0a76f 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -26,6 +26,7 @@ obj-y += power/ obj-y += printk/ obj-y += irq/ obj-y += rcu/ +obj-y += livepatch/ obj-$(CONFIG_CHECKPOINT_RESTORE) += kcmp.o obj-$(CONFIG_FREEZER) += freezer.o diff --git a/kernel/livepatch/Kconfig b/kernel/livepatch/Kconfig new file mode 100644 index 000000000000..96da00fbc120 --- /dev/null +++ b/kernel/livepatch/Kconfig @@ -0,0 +1,18 @@ +config ARCH_HAVE_LIVE_PATCHING + boolean + help + Arch supports kernel live patching + +config LIVE_PATCHING + boolean "Kernel Live Patching" + depends on DYNAMIC_FTRACE_WITH_REGS + depends on MODULES + depends on SYSFS + depends on KALLSYMS_ALL + depends on ARCH_HAVE_LIVE_PATCHING + help + Say Y here if you want to support kernel live patching. + This option has no runtime impact until a kernel "patch" + module uses the interface provided by this option to register + a patch, causing calls to patched functions to be redirected + to new function code contained in the patch module. diff --git a/kernel/livepatch/Makefile b/kernel/livepatch/Makefile new file mode 100644 index 000000000000..7c1f00861428 --- /dev/null +++ b/kernel/livepatch/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_LIVE_PATCHING) += livepatch.o + +livepatch-objs := core.o diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c new file mode 100644 index 000000000000..f99fe189d596 --- /dev/null +++ b/kernel/livepatch/core.c @@ -0,0 +1,930 @@ +/* + * core.c - Kernel Live Patching Core + * + * Copyright (C) 2014 Seth Jennings + * Copyright (C) 2014 SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The klp_mutex protects the klp_patches list and state transitions of any + * structure reachable from the patches list. References to any structure must + * be obtained under mutex protection. + */ + +static DEFINE_MUTEX(klp_mutex); +static LIST_HEAD(klp_patches); + +static struct kobject *klp_root_kobj; + +static bool klp_is_module(struct klp_object *obj) +{ + return obj->name; +} + +static bool klp_is_object_loaded(struct klp_object *obj) +{ + return !obj->name || obj->mod; +} + +/* sets obj->mod if object is not vmlinux and module is found */ +static void klp_find_object_module(struct klp_object *obj) +{ + if (!klp_is_module(obj)) + return; + + mutex_lock(&module_mutex); + /* + * We don't need to take a reference on the module here because we have + * the klp_mutex, which is also taken by the module notifier. This + * prevents any module from unloading until we release the klp_mutex. + */ + obj->mod = find_module(obj->name); + mutex_unlock(&module_mutex); +} + +/* klp_mutex must be held by caller */ +static bool klp_is_patch_registered(struct klp_patch *patch) +{ + struct klp_patch *mypatch; + + list_for_each_entry(mypatch, &klp_patches, list) + if (mypatch == patch) + return true; + + return false; +} + +static bool klp_initialized(void) +{ + return klp_root_kobj; +} + +struct klp_find_arg { + const char *objname; + const char *name; + unsigned long addr; + /* + * If count == 0, the symbol was not found. If count == 1, a unique + * match was found and addr is set. If count > 1, there is + * unresolvable ambiguity among "count" number of symbols with the same + * name in the same object. + */ + unsigned long count; +}; + +static int klp_find_callback(void *data, const char *name, + struct module *mod, unsigned long addr) +{ + struct klp_find_arg *args = data; + + if ((mod && !args->objname) || (!mod && args->objname)) + return 0; + + if (strcmp(args->name, name)) + return 0; + + if (args->objname && strcmp(args->objname, mod->name)) + return 0; + + /* + * args->addr might be overwritten if another match is found + * but klp_find_object_symbol() handles this and only returns the + * addr if count == 1. + */ + args->addr = addr; + args->count++; + + return 0; +} + +static int klp_find_object_symbol(const char *objname, const char *name, + unsigned long *addr) +{ + struct klp_find_arg args = { + .objname = objname, + .name = name, + .addr = 0, + .count = 0 + }; + + kallsyms_on_each_symbol(klp_find_callback, &args); + + if (args.count == 0) + pr_err("symbol '%s' not found in symbol table\n", name); + else if (args.count > 1) + pr_err("unresolvable ambiguity (%lu matches) on symbol '%s' in object '%s'\n", + args.count, name, objname); + else { + *addr = args.addr; + return 0; + } + + *addr = 0; + return -EINVAL; +} + +struct klp_verify_args { + const char *name; + const unsigned long addr; +}; + +static int klp_verify_callback(void *data, const char *name, + struct module *mod, unsigned long addr) +{ + struct klp_verify_args *args = data; + + if (!mod && + !strcmp(args->name, name) && + args->addr == addr) + return 1; + + return 0; +} + +static int klp_verify_vmlinux_symbol(const char *name, unsigned long addr) +{ + struct klp_verify_args args = { + .name = name, + .addr = addr, + }; + + if (kallsyms_on_each_symbol(klp_verify_callback, &args)) + return 0; + + pr_err("symbol '%s' not found at specified address 0x%016lx, kernel mismatch?", + name, addr); + return -EINVAL; +} + +static int klp_find_verify_func_addr(struct klp_object *obj, + struct klp_func *func) +{ + int ret; + +#if defined(CONFIG_RANDOMIZE_BASE) + /* KASLR is enabled, disregard old_addr from user */ + func->old_addr = 0; +#endif + + if (!func->old_addr || klp_is_module(obj)) + ret = klp_find_object_symbol(obj->name, func->old_name, + &func->old_addr); + else + ret = klp_verify_vmlinux_symbol(func->old_name, + func->old_addr); + + return ret; +} + +/* + * external symbols are located outside the parent object (where the parent + * object is either vmlinux or the kmod being patched). + */ +static int klp_find_external_symbol(struct module *pmod, const char *name, + unsigned long *addr) +{ + const struct kernel_symbol *sym; + + /* first, check if it's an exported symbol */ + preempt_disable(); + sym = find_symbol(name, NULL, NULL, true, true); + preempt_enable(); + if (sym) { + *addr = sym->value; + return 0; + } + + /* otherwise check if it's in another .o within the patch module */ + return klp_find_object_symbol(pmod->name, name, addr); +} + +static int klp_write_object_relocations(struct module *pmod, + struct klp_object *obj) +{ + int ret; + struct klp_reloc *reloc; + + if (WARN_ON(!klp_is_object_loaded(obj))) + return -EINVAL; + + if (WARN_ON(!obj->relocs)) + return -EINVAL; + + for (reloc = obj->relocs; reloc->name; reloc++) { + if (!klp_is_module(obj)) { + ret = klp_verify_vmlinux_symbol(reloc->name, + reloc->val); + if (ret) + return ret; + } else { + /* module, reloc->val needs to be discovered */ + if (reloc->external) + ret = klp_find_external_symbol(pmod, + reloc->name, + &reloc->val); + else + ret = klp_find_object_symbol(obj->mod->name, + reloc->name, + &reloc->val); + if (ret) + return ret; + } + ret = klp_write_module_reloc(pmod, reloc->type, reloc->loc, + reloc->val + reloc->addend); + if (ret) { + pr_err("relocation failed for symbol '%s' at 0x%016lx (%d)\n", + reloc->name, reloc->val, ret); + return ret; + } + } + + return 0; +} + +static void notrace klp_ftrace_handler(unsigned long ip, + unsigned long parent_ip, + struct ftrace_ops *ops, + struct pt_regs *regs) +{ + struct klp_func *func = ops->private; + + regs->ip = (unsigned long)func->new_func; +} + +static int klp_disable_func(struct klp_func *func) +{ + int ret; + + if (WARN_ON(func->state != KLP_ENABLED)) + return -EINVAL; + + if (WARN_ON(!func->old_addr)) + return -EINVAL; + + ret = unregister_ftrace_function(func->fops); + if (ret) { + pr_err("failed to unregister ftrace handler for function '%s' (%d)\n", + func->old_name, ret); + return ret; + } + + ret = ftrace_set_filter_ip(func->fops, func->old_addr, 1, 0); + if (ret) + pr_warn("function unregister succeeded but failed to clear the filter\n"); + + func->state = KLP_DISABLED; + + return 0; +} + +static int klp_enable_func(struct klp_func *func) +{ + int ret; + + if (WARN_ON(!func->old_addr)) + return -EINVAL; + + if (WARN_ON(func->state != KLP_DISABLED)) + return -EINVAL; + + ret = ftrace_set_filter_ip(func->fops, func->old_addr, 0, 0); + if (ret) { + pr_err("failed to set ftrace filter for function '%s' (%d)\n", + func->old_name, ret); + return ret; + } + + ret = register_ftrace_function(func->fops); + if (ret) { + pr_err("failed to register ftrace handler for function '%s' (%d)\n", + func->old_name, ret); + ftrace_set_filter_ip(func->fops, func->old_addr, 1, 0); + } else { + func->state = KLP_ENABLED; + } + + return ret; +} + +static int klp_disable_object(struct klp_object *obj) +{ + struct klp_func *func; + int ret; + + for (func = obj->funcs; func->old_name; func++) { + if (func->state != KLP_ENABLED) + continue; + + ret = klp_disable_func(func); + if (ret) + return ret; + } + + obj->state = KLP_DISABLED; + + return 0; +} + +static int klp_enable_object(struct klp_object *obj) +{ + struct klp_func *func; + int ret; + + if (WARN_ON(obj->state != KLP_DISABLED)) + return -EINVAL; + + if (WARN_ON(!klp_is_object_loaded(obj))) + return -EINVAL; + + for (func = obj->funcs; func->old_name; func++) { + ret = klp_enable_func(func); + if (ret) + goto unregister; + } + obj->state = KLP_ENABLED; + + return 0; + +unregister: + WARN_ON(klp_disable_object(obj)); + return ret; +} + +static int __klp_disable_patch(struct klp_patch *patch) +{ + struct klp_object *obj; + int ret; + + pr_notice("disabling patch '%s'\n", patch->mod->name); + + for (obj = patch->objs; obj->funcs; obj++) { + if (obj->state != KLP_ENABLED) + continue; + + ret = klp_disable_object(obj); + if (ret) + return ret; + } + + patch->state = KLP_DISABLED; + + return 0; +} + +/** + * klp_disable_patch() - disables a registered patch + * @patch: The registered, enabled patch to be disabled + * + * Unregisters the patched functions from ftrace. + * + * Return: 0 on success, otherwise error + */ +int klp_disable_patch(struct klp_patch *patch) +{ + int ret; + + mutex_lock(&klp_mutex); + + if (!klp_is_patch_registered(patch)) { + ret = -EINVAL; + goto err; + } + + if (patch->state == KLP_DISABLED) { + ret = -EINVAL; + goto err; + } + + ret = __klp_disable_patch(patch); + +err: + mutex_unlock(&klp_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(klp_disable_patch); + +static int __klp_enable_patch(struct klp_patch *patch) +{ + struct klp_object *obj; + int ret; + + if (WARN_ON(patch->state != KLP_DISABLED)) + return -EINVAL; + + pr_notice_once("tainting kernel with TAINT_LIVEPATCH\n"); + add_taint(TAINT_LIVEPATCH, LOCKDEP_STILL_OK); + + pr_notice("enabling patch '%s'\n", patch->mod->name); + + for (obj = patch->objs; obj->funcs; obj++) { + klp_find_object_module(obj); + + if (!klp_is_object_loaded(obj)) + continue; + + ret = klp_enable_object(obj); + if (ret) + goto unregister; + } + + patch->state = KLP_ENABLED; + + return 0; + +unregister: + WARN_ON(__klp_disable_patch(patch)); + return ret; +} + +/** + * klp_enable_patch() - enables a registered patch + * @patch: The registered, disabled patch to be enabled + * + * Performs the needed symbol lookups and code relocations, + * then registers the patched functions with ftrace. + * + * Return: 0 on success, otherwise error + */ +int klp_enable_patch(struct klp_patch *patch) +{ + int ret; + + mutex_lock(&klp_mutex); + + if (!klp_is_patch_registered(patch)) { + ret = -EINVAL; + goto err; + } + + ret = __klp_enable_patch(patch); + +err: + mutex_unlock(&klp_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(klp_enable_patch); + +/* + * Sysfs Interface + * + * /sys/kernel/livepatch + * /sys/kernel/livepatch/ + * /sys/kernel/livepatch//enabled + * /sys/kernel/livepatch// + * /sys/kernel/livepatch/// + */ + +static ssize_t enabled_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct klp_patch *patch; + int ret; + unsigned long val; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return -EINVAL; + + if (val != KLP_DISABLED && val != KLP_ENABLED) + return -EINVAL; + + patch = container_of(kobj, struct klp_patch, kobj); + + mutex_lock(&klp_mutex); + + if (val == patch->state) { + /* already in requested state */ + ret = -EINVAL; + goto err; + } + + if (val == KLP_ENABLED) { + ret = __klp_enable_patch(patch); + if (ret) + goto err; + } else { + ret = __klp_disable_patch(patch); + if (ret) + goto err; + } + + mutex_unlock(&klp_mutex); + + return count; + +err: + mutex_unlock(&klp_mutex); + return ret; +} + +static ssize_t enabled_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct klp_patch *patch; + + patch = container_of(kobj, struct klp_patch, kobj); + return snprintf(buf, PAGE_SIZE-1, "%d\n", patch->state); +} + +static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled); +static struct attribute *klp_patch_attrs[] = { + &enabled_kobj_attr.attr, + NULL +}; + +static void klp_kobj_release_patch(struct kobject *kobj) +{ + /* + * Once we have a consistency model we'll need to module_put() the + * patch module here. See klp_register_patch() for more details. + */ +} + +static struct kobj_type klp_ktype_patch = { + .release = klp_kobj_release_patch, + .sysfs_ops = &kobj_sysfs_ops, + .default_attrs = klp_patch_attrs, +}; + +static void klp_kobj_release_func(struct kobject *kobj) +{ + struct klp_func *func; + + func = container_of(kobj, struct klp_func, kobj); + kfree(func->fops); +} + +static struct kobj_type klp_ktype_func = { + .release = klp_kobj_release_func, + .sysfs_ops = &kobj_sysfs_ops, +}; + +/* + * Free all functions' kobjects in the array up to some limit. When limit is + * NULL, all kobjects are freed. + */ +static void klp_free_funcs_limited(struct klp_object *obj, + struct klp_func *limit) +{ + struct klp_func *func; + + for (func = obj->funcs; func->old_name && func != limit; func++) + kobject_put(&func->kobj); +} + +/* Clean up when a patched object is unloaded */ +static void klp_free_object_loaded(struct klp_object *obj) +{ + struct klp_func *func; + + obj->mod = NULL; + + for (func = obj->funcs; func->old_name; func++) + func->old_addr = 0; +} + +/* + * Free all objects' kobjects in the array up to some limit. When limit is + * NULL, all kobjects are freed. + */ +static void klp_free_objects_limited(struct klp_patch *patch, + struct klp_object *limit) +{ + struct klp_object *obj; + + for (obj = patch->objs; obj->funcs && obj != limit; obj++) { + klp_free_funcs_limited(obj, NULL); + kobject_put(obj->kobj); + } +} + +static void klp_free_patch(struct klp_patch *patch) +{ + klp_free_objects_limited(patch, NULL); + if (!list_empty(&patch->list)) + list_del(&patch->list); + kobject_put(&patch->kobj); +} + +static int klp_init_func(struct klp_object *obj, struct klp_func *func) +{ + struct ftrace_ops *ops; + int ret; + + ops = kzalloc(sizeof(*ops), GFP_KERNEL); + if (!ops) + return -ENOMEM; + + ops->private = func; + ops->func = klp_ftrace_handler; + ops->flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_DYNAMIC; + func->fops = ops; + func->state = KLP_DISABLED; + + ret = kobject_init_and_add(&func->kobj, &klp_ktype_func, + obj->kobj, func->old_name); + if (ret) { + kfree(func->fops); + return ret; + } + + return 0; +} + +/* parts of the initialization that is done only when the object is loaded */ +static int klp_init_object_loaded(struct klp_patch *patch, + struct klp_object *obj) +{ + struct klp_func *func; + int ret; + + if (obj->relocs) { + ret = klp_write_object_relocations(patch->mod, obj); + if (ret) + return ret; + } + + for (func = obj->funcs; func->old_name; func++) { + ret = klp_find_verify_func_addr(obj, func); + if (ret) + return ret; + } + + return 0; +} + +static int klp_init_object(struct klp_patch *patch, struct klp_object *obj) +{ + struct klp_func *func; + int ret; + const char *name; + + if (!obj->funcs) + return -EINVAL; + + obj->state = KLP_DISABLED; + + klp_find_object_module(obj); + + name = klp_is_module(obj) ? obj->name : "vmlinux"; + obj->kobj = kobject_create_and_add(name, &patch->kobj); + if (!obj->kobj) + return -ENOMEM; + + for (func = obj->funcs; func->old_name; func++) { + ret = klp_init_func(obj, func); + if (ret) + goto free; + } + + if (klp_is_object_loaded(obj)) { + ret = klp_init_object_loaded(patch, obj); + if (ret) + goto free; + } + + return 0; + +free: + klp_free_funcs_limited(obj, func); + kobject_put(obj->kobj); + return ret; +} + +static int klp_init_patch(struct klp_patch *patch) +{ + struct klp_object *obj; + int ret; + + if (!patch->objs) + return -EINVAL; + + mutex_lock(&klp_mutex); + + patch->state = KLP_DISABLED; + + ret = kobject_init_and_add(&patch->kobj, &klp_ktype_patch, + klp_root_kobj, patch->mod->name); + if (ret) + goto unlock; + + for (obj = patch->objs; obj->funcs; obj++) { + ret = klp_init_object(patch, obj); + if (ret) + goto free; + } + + list_add(&patch->list, &klp_patches); + + mutex_unlock(&klp_mutex); + + return 0; + +free: + klp_free_objects_limited(patch, obj); + kobject_put(&patch->kobj); +unlock: + mutex_unlock(&klp_mutex); + return ret; +} + +/** + * klp_unregister_patch() - unregisters a patch + * @patch: Disabled patch to be unregistered + * + * Frees the data structures and removes the sysfs interface. + * + * Return: 0 on success, otherwise error + */ +int klp_unregister_patch(struct klp_patch *patch) +{ + int ret = 0; + + mutex_lock(&klp_mutex); + + if (!klp_is_patch_registered(patch)) { + ret = -EINVAL; + goto out; + } + + if (patch->state == KLP_ENABLED) { + ret = -EBUSY; + goto out; + } + + klp_free_patch(patch); + +out: + mutex_unlock(&klp_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(klp_unregister_patch); + +/** + * klp_register_patch() - registers a patch + * @patch: Patch to be registered + * + * Initializes the data structure associated with the patch and + * creates the sysfs interface. + * + * Return: 0 on success, otherwise error + */ +int klp_register_patch(struct klp_patch *patch) +{ + int ret; + + if (!klp_initialized()) + return -ENODEV; + + if (!patch || !patch->mod) + return -EINVAL; + + /* + * A reference is taken on the patch module to prevent it from being + * unloaded. Right now, we don't allow patch modules to unload since + * there is currently no method to determine if a thread is still + * running in the patched code contained in the patch module once + * the ftrace registration is successful. + */ + if (!try_module_get(patch->mod)) + return -ENODEV; + + ret = klp_init_patch(patch); + if (ret) + module_put(patch->mod); + + return ret; +} +EXPORT_SYMBOL_GPL(klp_register_patch); + +static void klp_module_notify_coming(struct klp_patch *patch, + struct klp_object *obj) +{ + struct module *pmod = patch->mod; + struct module *mod = obj->mod; + int ret; + + ret = klp_init_object_loaded(patch, obj); + if (ret) + goto err; + + if (patch->state == KLP_DISABLED) + return; + + pr_notice("applying patch '%s' to loading module '%s'\n", + pmod->name, mod->name); + + ret = klp_enable_object(obj); + if (!ret) + return; + +err: + pr_warn("failed to apply patch '%s' to module '%s' (%d)\n", + pmod->name, mod->name, ret); +} + +static void klp_module_notify_going(struct klp_patch *patch, + struct klp_object *obj) +{ + struct module *pmod = patch->mod; + struct module *mod = obj->mod; + int ret; + + if (patch->state == KLP_DISABLED) + goto disabled; + + pr_notice("reverting patch '%s' on unloading module '%s'\n", + pmod->name, mod->name); + + ret = klp_disable_object(obj); + if (ret) + pr_warn("failed to revert patch '%s' on module '%s' (%d)\n", + pmod->name, mod->name, ret); + +disabled: + klp_free_object_loaded(obj); +} + +static int klp_module_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct module *mod = data; + struct klp_patch *patch; + struct klp_object *obj; + + if (action != MODULE_STATE_COMING && action != MODULE_STATE_GOING) + return 0; + + mutex_lock(&klp_mutex); + + list_for_each_entry(patch, &klp_patches, list) { + for (obj = patch->objs; obj->funcs; obj++) { + if (!klp_is_module(obj) || strcmp(obj->name, mod->name)) + continue; + + if (action == MODULE_STATE_COMING) { + obj->mod = mod; + klp_module_notify_coming(patch, obj); + } else /* MODULE_STATE_GOING */ + klp_module_notify_going(patch, obj); + + break; + } + } + + mutex_unlock(&klp_mutex); + + return 0; +} + +static struct notifier_block klp_module_nb = { + .notifier_call = klp_module_notify, + .priority = INT_MIN+1, /* called late but before ftrace notifier */ +}; + +static int klp_init(void) +{ + int ret; + + ret = register_module_notifier(&klp_module_nb); + if (ret) + return ret; + + klp_root_kobj = kobject_create_and_add("livepatch", kernel_kobj); + if (!klp_root_kobj) { + ret = -ENOMEM; + goto unregister; + } + + return 0; + +unregister: + unregister_module_notifier(&klp_module_nb); + return ret; +} + +module_init(klp_init); -- cgit v1.2.3 From 13d1cf7e702596e0cd8ec62afa6bd49c431f2d0c Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Tue, 16 Dec 2014 11:58:20 -0600 Subject: livepatch: samples: add sample live patching module Add a sample live patching module. Signed-off-by: Seth Jennings Reviewed-by: Miroslav Benes Reviewed-by: Petr Mladek Reviewed-by: Masami Hiramatsu Signed-off-by: Jiri Kosina --- MAINTAINERS | 1 + samples/Kconfig | 7 +++ samples/Makefile | 2 +- samples/livepatch/Makefile | 1 + samples/livepatch/livepatch-sample.c | 87 ++++++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 samples/livepatch/Makefile create mode 100644 samples/livepatch/livepatch-sample.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index df6a0784b466..afe93ead73f7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5795,6 +5795,7 @@ F: include/linux/livepatch.h F: arch/x86/include/asm/livepatch.h F: arch/x86/kernel/livepatch.c F: Documentation/ABI/testing/sysfs-kernel-livepatch +F: samples/livepatch/ L: live-patching@vger.kernel.org LLC (802.2) diff --git a/samples/Kconfig b/samples/Kconfig index 6181c2cc9ca0..0aed20df5f0b 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -63,4 +63,11 @@ config SAMPLE_RPMSG_CLIENT to communicate with an AMP-configured remote processor over the rpmsg bus. +config SAMPLE_LIVE_PATCHING + tristate "Build live patching sample -- loadable modules only" + depends on LIVE_PATCHING && m + help + Builds a sample live patch that replaces the procfs handler + for /proc/cmdline to print "this has been live patched". + endif # SAMPLES diff --git a/samples/Makefile b/samples/Makefile index 1a60c62e2045..f00257bcc5a7 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -1,4 +1,4 @@ # Makefile for Linux samples code -obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ \ +obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \ hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ diff --git a/samples/livepatch/Makefile b/samples/livepatch/Makefile new file mode 100644 index 000000000000..7f1cdc131a02 --- /dev/null +++ b/samples/livepatch/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SAMPLE_LIVE_PATCHING) += livepatch-sample.o diff --git a/samples/livepatch/livepatch-sample.c b/samples/livepatch/livepatch-sample.c new file mode 100644 index 000000000000..21f159d51074 --- /dev/null +++ b/samples/livepatch/livepatch-sample.c @@ -0,0 +1,87 @@ +/* + * livepatch-sample.c - Kernel Live Patching Sample Module + * + * Copyright (C) 2014 Seth Jennings + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include + +/* + * This (dumb) live patch overrides the function that prints the + * kernel boot cmdline when /proc/cmdline is read. + * + * Example: + * $ cat /proc/cmdline + * + * $ insmod livepatch-sample.ko + * $ cat /proc/cmdline + * this has been live patched + * $ echo 0 > /sys/kernel/livepatch/klp_sample/enabled + * + */ + +#include +static int livepatch_cmdline_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "%s\n", "this has been live patched"); + return 0; +} + +static struct klp_func funcs[] = { + { + .old_name = "cmdline_proc_show", + .new_func = livepatch_cmdline_proc_show, + }, { } +}; + +static struct klp_object objs[] = { + { + /* name being NULL means vmlinux */ + .funcs = funcs, + }, { } +}; + +static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, +}; + +static int livepatch_init(void) +{ + int ret; + + ret = klp_register_patch(&patch); + if (ret) + return ret; + ret = klp_enable_patch(&patch); + if (ret) { + WARN_ON(klp_unregister_patch(&patch)); + return ret; + } + return 0; +} + +static void livepatch_exit(void) +{ + WARN_ON(klp_disable_patch(&patch)); + WARN_ON(klp_unregister_patch(&patch)); +} + +module_init(livepatch_init); +module_exit(livepatch_exit); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 74d50da3e47f9e0b9eba2be4372556aa82d0f05d Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Mon, 22 Dec 2014 13:40:20 +0100 Subject: livepatch: MAINTAINERS: add git tree location Update MAINTAINERS entry for live patching infrastructure so that it points to git tree hosted at kernel.org. Signed-off-by: Jiri Kosina --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index afe93ead73f7..cff1f33b3965 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5797,6 +5797,7 @@ F: arch/x86/kernel/livepatch.c F: Documentation/ABI/testing/sysfs-kernel-livepatch F: samples/livepatch/ L: live-patching@vger.kernel.org +T: git git://git.kernel.org/pub/scm/linux/kernel/git/jikos/livepatching.git LLC (802.2) M: Arnaldo Carvalho de Melo -- cgit v1.2.3 From fd2bfdc863c7912e4865f9d993b2ab1108645fd5 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Sun, 16 Nov 2014 12:08:44 -0300 Subject: [media] smiapp: List include/uapi/linux/smiapp.h in MAINTAINERS This is part of the smiapp driver. Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..f7d04bac206b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8826,6 +8826,7 @@ F: drivers/media/i2c/smiapp/ F: include/media/smiapp.h F: drivers/media/i2c/smiapp-pll.c F: drivers/media/i2c/smiapp-pll.h +F: include/uapi/linux/smiapp.h SMM665 HARDWARE MONITOR DRIVER M: Guenter Roeck -- cgit v1.2.3 From a2cec3c0199ab4d7c0d83dee7fc69bd22eef7e12 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 6 Nov 2014 18:16:13 -0300 Subject: [media] of: smiapp: Add documentation Document the smiapp device tree properties. Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/i2c/nokia,smia.txt | 63 ++++++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 64 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/nokia,smia.txt (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/media/i2c/nokia,smia.txt b/Documentation/devicetree/bindings/media/i2c/nokia,smia.txt new file mode 100644 index 000000000000..855e1faf73e2 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/nokia,smia.txt @@ -0,0 +1,63 @@ +SMIA/SMIA++ sensor + +SMIA (Standard Mobile Imaging Architecture) is an image sensor standard +defined jointly by Nokia and ST. SMIA++, defined by Nokia, is an extension +of that. These definitions are valid for both types of sensors. + +More detailed documentation can be found in +Documentation/devicetree/bindings/media/video-interfaces.txt . + + +Mandatory properties +-------------------- + +- compatible: "nokia,smia" +- reg: I2C address (0x10, or an alternative address) +- vana-supply: Analogue voltage supply (VANA), typically 2,8 volts (sensor + dependent). +- clocks: External clock to the sensor +- clock-frequency: Frequency of the external clock to the sensor +- link-frequencies: List of allowed data link frequencies. An array of + 64-bit elements. + + +Optional properties +------------------- + +- nokia,nvm-size: The size of the NVM, in bytes. If the size is not given, + the NVM contents will not be read. +- reset-gpios: XSHUTDOWN GPIO + + +Endpoint node mandatory properties +---------------------------------- + +- clock-lanes: <0> +- data-lanes: <1..n> +- remote-endpoint: A phandle to the bus receiver's endpoint node. + + +Example +------- + +&i2c2 { + clock-frequency = <400000>; + + smiapp_1: camera@10 { + compatible = "nokia,smia"; + reg = <0x10>; + reset-gpios = <&gpio3 20 0>; + vana-supply = <&vaux3>; + clocks = <&omap3_isp 0>; + clock-frequency = <9600000>; + nokia,nvm-size = <512>; /* 8 * 64 */ + link-frequencies = /bits/ 64 <199200000 210000000 499200000>; + port { + smiapp_1_1: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + remote-endpoint = <&csi2a_ep>; + }; + }; + }; +}; diff --git a/MAINTAINERS b/MAINTAINERS index f7d04bac206b..dc2d91252d8b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8827,6 +8827,7 @@ F: include/media/smiapp.h F: drivers/media/i2c/smiapp-pll.c F: drivers/media/i2c/smiapp-pll.h F: include/uapi/linux/smiapp.h +F: Documentation/devicetree/bindings/media/i2c/nokia,smia.txt SMM665 HARDWARE MONITOR DRIVER M: Guenter Roeck -- cgit v1.2.3 From 417d2e507edcb5cf15eb344f86bd3dd28737f24e Mon Sep 17 00:00:00 2001 From: Benoit Parrot Date: Tue, 9 Dec 2014 16:43:44 -0300 Subject: [media] media: platform: add VPFE capture driver support for AM437X This patch adds Video Processing Front End (VPFE) driver for AM437X family of devices Driver supports the following: - V4L2 API using MMAP buffer access based on videobuf2 api - Asynchronous sensor/decoder sub device registration - DT support Signed-off-by: Benoit Parrot Signed-off-by: Darren Etheridge Signed-off-by: Lad, Prabhakar [hans.verkuil@cisco.com: swapped two lines to fix vpfe_release() & add pinctrl include] Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/ti-am437x-vpfe.txt | 61 + MAINTAINERS | 9 + drivers/media/platform/Kconfig | 1 + drivers/media/platform/Makefile | 2 + drivers/media/platform/am437x/Kconfig | 11 + drivers/media/platform/am437x/Makefile | 3 + drivers/media/platform/am437x/am437x-vpfe.c | 2778 ++++++++++++++++++++ drivers/media/platform/am437x/am437x-vpfe.h | 283 ++ drivers/media/platform/am437x/am437x-vpfe_regs.h | 140 + include/uapi/linux/Kbuild | 1 + include/uapi/linux/am437x-vpfe.h | 122 + 11 files changed, 3411 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/ti-am437x-vpfe.txt create mode 100644 drivers/media/platform/am437x/Kconfig create mode 100644 drivers/media/platform/am437x/Makefile create mode 100644 drivers/media/platform/am437x/am437x-vpfe.c create mode 100644 drivers/media/platform/am437x/am437x-vpfe.h create mode 100644 drivers/media/platform/am437x/am437x-vpfe_regs.h create mode 100644 include/uapi/linux/am437x-vpfe.h (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/media/ti-am437x-vpfe.txt b/Documentation/devicetree/bindings/media/ti-am437x-vpfe.txt new file mode 100644 index 000000000000..3932e766553a --- /dev/null +++ b/Documentation/devicetree/bindings/media/ti-am437x-vpfe.txt @@ -0,0 +1,61 @@ +Texas Instruments AM437x CAMERA (VPFE) +-------------------------------------- + +The Video Processing Front End (VPFE) is a key component for image capture +applications. The capture module provides the system interface and the +processing capability to connect RAW image-sensor modules and video decoders +to the AM437x device. + +Required properties: +- compatible: must be "ti,am437x-vpfe" +- reg: physical base address and length of the registers set for the device; +- interrupts: should contain IRQ line for the VPFE; +- ti,am437x-vpfe-interface: can be one of the following, + 0 - Raw Bayer Interface. + 1 - 8 Bit BT656 Interface. + 2 - 10 Bit BT656 Interface. + 3 - YCbCr 8 Bit Interface. + 4 - YCbCr 16 Bit Interface. + +VPFE supports a single port node with parallel bus. It should contain one +'port' child node with child 'endpoint' node. Please refer to the bindings +defined in Documentation/devicetree/bindings/media/video-interfaces.txt. + +Example: + vpfe: vpfe@f0034000 { + compatible = "ti,am437x-vpfe"; + reg = <0x48328000 0x2000>; + interrupts = ; + + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&vpfe_pins_default>; + pinctrl-1 = <&vpfe_pins_sleep>; + + port { + #address-cells = <1>; + #size-cells = <0>; + + vpfe0_ep: endpoint { + remote-endpoint = <&ov2659_1>; + ti,am437x-vpfe-interface = <0>; + bus-width = <8>; + hsync-active = <0>; + vsync-active = <0>; + }; + }; + }; + + i2c1: i2c@4802a000 { + + ov2659@30 { + compatible = "ti,ov2659"; + reg = <0x30>; + + port { + ov2659_1: endpoint { + remote-endpoint = <&vpfe0_ep>; + bus-width = <8>; + mclk-frequency = <12000000>; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index dc2d91252d8b..4318f348dbd8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8745,6 +8745,15 @@ S: Maintained F: drivers/media/platform/davinci/ F: include/media/davinci/ +TI AM437X VPFE DRIVER +M: Lad, Prabhakar +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/mhadli/v4l-dvb-davinci_devices.git +S: Maintained +F: drivers/media/platform/am437x/ + SIS 190 ETHERNET DRIVER M: Francois Romieu L: netdev@vger.kernel.org diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 480a174832a6..71e8873ceb94 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -118,6 +118,7 @@ config VIDEO_S3C_CAMIF source "drivers/media/platform/soc_camera/Kconfig" source "drivers/media/platform/exynos4-is/Kconfig" source "drivers/media/platform/s5p-tv/Kconfig" +source "drivers/media/platform/am437x/Kconfig" endif # V4L_PLATFORM_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index a49936b8ce8a..3ec154742083 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -46,4 +46,6 @@ obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ obj-y += omap/ +obj-$(CONFIG_VIDEO_AM437X_VPFE) += am437x/ + ccflags-y += -I$(srctree)/drivers/media/i2c diff --git a/drivers/media/platform/am437x/Kconfig b/drivers/media/platform/am437x/Kconfig new file mode 100644 index 000000000000..7b023a76e32e --- /dev/null +++ b/drivers/media/platform/am437x/Kconfig @@ -0,0 +1,11 @@ +config VIDEO_AM437X_VPFE + tristate "TI AM437x VPFE video capture driver" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on SOC_AM43XX || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + help + Support for AM437x Video Processing Front End based Video + Capture Driver. + + To compile this driver as a module, choose M here. The module + will be called am437x-vpfe. diff --git a/drivers/media/platform/am437x/Makefile b/drivers/media/platform/am437x/Makefile new file mode 100644 index 000000000000..d11fff16f260 --- /dev/null +++ b/drivers/media/platform/am437x/Makefile @@ -0,0 +1,3 @@ +# Makefile for AM437x VPFE driver + +obj-$(CONFIG_VIDEO_AM437X_VPFE) += am437x-vpfe.o diff --git a/drivers/media/platform/am437x/am437x-vpfe.c b/drivers/media/platform/am437x/am437x-vpfe.c new file mode 100644 index 000000000000..e01ac22d6244 --- /dev/null +++ b/drivers/media/platform/am437x/am437x-vpfe.c @@ -0,0 +1,2778 @@ +/* + * TI VPFE capture Driver + * + * Copyright (C) 2013 - 2014 Texas Instruments, Inc. + * + * Benoit Parrot + * Lad, Prabhakar + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "am437x-vpfe.h" + +#define VPFE_MODULE_NAME "vpfe" +#define VPFE_VERSION "0.1.0" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level 0-8"); + +#define vpfe_dbg(level, dev, fmt, arg...) \ + v4l2_dbg(level, debug, &dev->v4l2_dev, fmt, ##arg) +#define vpfe_info(dev, fmt, arg...) \ + v4l2_info(&dev->v4l2_dev, fmt, ##arg) +#define vpfe_err(dev, fmt, arg...) \ + v4l2_err(&dev->v4l2_dev, fmt, ##arg) + +/* standard information */ +struct vpfe_standard { + v4l2_std_id std_id; + unsigned int width; + unsigned int height; + struct v4l2_fract pixelaspect; + int frame_format; +}; + +const struct vpfe_standard vpfe_standards[] = { + {V4L2_STD_525_60, 720, 480, {11, 10}, 1}, + {V4L2_STD_625_50, 720, 576, {54, 59}, 1}, +}; + +struct bus_format { + unsigned int width; + unsigned int bpp; +}; + +/* + * struct vpfe_fmt - VPFE media bus format information + * @name: V4L2 format description + * @code: V4L2 media bus format code + * @shifted: V4L2 media bus format code for the same pixel layout but + * shifted to be 8 bits per pixel. =0 if format is not shiftable. + * @pixelformat: V4L2 pixel format FCC identifier + * @width: Bits per pixel (when transferred over a bus) + * @bpp: Bytes per pixel (when stored in memory) + * @supported: Indicates format supported by subdev + */ +struct vpfe_fmt { + const char *name; + u32 fourcc; + u32 code; + struct bus_format l; + struct bus_format s; + bool supported; + u32 index; +}; + +static struct vpfe_fmt formats[] = { + { + .name = "YUV 4:2:2 packed, YCbYCr", + .fourcc = V4L2_PIX_FMT_YUYV, + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .l.width = 10, + .l.bpp = 4, + .s.width = 8, + .s.bpp = 2, + .supported = false, + }, { + .name = "YUV 4:2:2 packed, CbYCrY", + .fourcc = V4L2_PIX_FMT_UYVY, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .l.width = 10, + .l.bpp = 4, + .s.width = 8, + .s.bpp = 2, + .supported = false, + }, { + .name = "YUV 4:2:2 packed, YCrYCb", + .fourcc = V4L2_PIX_FMT_YVYU, + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .l.width = 10, + .l.bpp = 4, + .s.width = 8, + .s.bpp = 2, + .supported = false, + }, { + .name = "YUV 4:2:2 packed, CrYCbY", + .fourcc = V4L2_PIX_FMT_VYUY, + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .l.width = 10, + .l.bpp = 4, + .s.width = 8, + .s.bpp = 2, + .supported = false, + }, { + .name = "RAW8 BGGR", + .fourcc = V4L2_PIX_FMT_SBGGR8, + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .l.width = 10, + .l.bpp = 2, + .s.width = 8, + .s.bpp = 1, + .supported = false, + }, { + .name = "RAW8 GBRG", + .fourcc = V4L2_PIX_FMT_SGBRG8, + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .l.width = 10, + .l.bpp = 2, + .s.width = 8, + .s.bpp = 1, + .supported = false, + }, { + .name = "RAW8 GRBG", + .fourcc = V4L2_PIX_FMT_SGRBG8, + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .l.width = 10, + .l.bpp = 2, + .s.width = 8, + .s.bpp = 1, + .supported = false, + }, { + .name = "RAW8 RGGB", + .fourcc = V4L2_PIX_FMT_SRGGB8, + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .l.width = 10, + .l.bpp = 2, + .s.width = 8, + .s.bpp = 1, + .supported = false, + }, { + .name = "RGB565 (LE)", + .fourcc = V4L2_PIX_FMT_RGB565, + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .l.width = 10, + .l.bpp = 4, + .s.width = 8, + .s.bpp = 2, + .supported = false, + }, { + .name = "RGB565 (BE)", + .fourcc = V4L2_PIX_FMT_RGB565X, + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .l.width = 10, + .l.bpp = 4, + .s.width = 8, + .s.bpp = 2, + .supported = false, + }, +}; + +static int +__vpfe_get_format(struct vpfe_device *vpfe, + struct v4l2_format *format, unsigned int *bpp); + +static struct vpfe_fmt *find_format_by_code(unsigned int code) +{ + struct vpfe_fmt *fmt; + unsigned int k; + + for (k = 0; k < ARRAY_SIZE(formats); k++) { + fmt = &formats[k]; + if (fmt->code == code) + return fmt; + } + + return NULL; +} + +static struct vpfe_fmt *find_format_by_pix(unsigned int pixelformat) +{ + struct vpfe_fmt *fmt; + unsigned int k; + + for (k = 0; k < ARRAY_SIZE(formats); k++) { + fmt = &formats[k]; + if (fmt->fourcc == pixelformat) + return fmt; + } + + return NULL; +} + +static void +mbus_to_pix(struct vpfe_device *vpfe, + const struct v4l2_mbus_framefmt *mbus, + struct v4l2_pix_format *pix, unsigned int *bpp) +{ + struct vpfe_subdev_info *sdinfo = vpfe->current_subdev; + unsigned int bus_width = sdinfo->vpfe_param.bus_width; + struct vpfe_fmt *fmt; + + fmt = find_format_by_code(mbus->code); + if (WARN_ON(fmt == NULL)) { + pr_err("Invalid mbus code set\n"); + *bpp = 1; + return; + } + + memset(pix, 0, sizeof(*pix)); + v4l2_fill_pix_format(pix, mbus); + pix->pixelformat = fmt->fourcc; + *bpp = (bus_width == 10) ? fmt->l.bpp : fmt->s.bpp; + + /* pitch should be 32 bytes aligned */ + pix->bytesperline = ALIGN(pix->width * *bpp, 32); + pix->sizeimage = pix->bytesperline * pix->height; +} + +static void pix_to_mbus(struct vpfe_device *vpfe, + struct v4l2_pix_format *pix_fmt, + struct v4l2_mbus_framefmt *mbus_fmt) +{ + struct vpfe_fmt *fmt; + + fmt = find_format_by_pix(pix_fmt->pixelformat); + if (!fmt) { + /* default to first entry */ + vpfe_dbg(3, vpfe, "Invalid pixel code: %x, default used instead\n", + pix_fmt->pixelformat); + fmt = &formats[0]; + } + + memset(mbus_fmt, 0, sizeof(*mbus_fmt)); + v4l2_fill_mbus_format(mbus_fmt, pix_fmt, fmt->code); +} + +/* Print Four-character-code (FOURCC) */ +static char *print_fourcc(u32 fmt) +{ + static char code[5]; + + code[0] = (unsigned char)(fmt & 0xff); + code[1] = (unsigned char)((fmt >> 8) & 0xff); + code[2] = (unsigned char)((fmt >> 16) & 0xff); + code[3] = (unsigned char)((fmt >> 24) & 0xff); + code[4] = '\0'; + + return code; +} + +static int +cmp_v4l2_format(const struct v4l2_format *lhs, const struct v4l2_format *rhs) +{ + return lhs->type == rhs->type && + lhs->fmt.pix.width == rhs->fmt.pix.width && + lhs->fmt.pix.height == rhs->fmt.pix.height && + lhs->fmt.pix.pixelformat == rhs->fmt.pix.pixelformat && + lhs->fmt.pix.field == rhs->fmt.pix.field && + lhs->fmt.pix.colorspace == rhs->fmt.pix.colorspace && + lhs->fmt.pix.ycbcr_enc == rhs->fmt.pix.ycbcr_enc && + lhs->fmt.pix.quantization == rhs->fmt.pix.quantization; +} + +static inline u32 vpfe_reg_read(struct vpfe_ccdc *ccdc, u32 offset) +{ + return ioread32(ccdc->ccdc_cfg.base_addr + offset); +} + +static inline void vpfe_reg_write(struct vpfe_ccdc *ccdc, u32 val, u32 offset) +{ + iowrite32(val, ccdc->ccdc_cfg.base_addr + offset); +} + +static inline struct vpfe_device *to_vpfe(struct vpfe_ccdc *ccdc) +{ + return container_of(ccdc, struct vpfe_device, ccdc); +} + +static inline struct vpfe_cap_buffer *to_vpfe_buffer(struct vb2_buffer *vb) +{ + return container_of(vb, struct vpfe_cap_buffer, vb); +} + +static inline void vpfe_pcr_enable(struct vpfe_ccdc *ccdc, int flag) +{ + vpfe_reg_write(ccdc, !!flag, VPFE_PCR); +} + +static void vpfe_config_enable(struct vpfe_ccdc *ccdc, int flag) +{ + unsigned int cfg; + + if (!flag) { + cfg = vpfe_reg_read(ccdc, VPFE_CONFIG); + cfg &= ~(VPFE_CONFIG_EN_ENABLE << VPFE_CONFIG_EN_SHIFT); + } else { + cfg = VPFE_CONFIG_EN_ENABLE << VPFE_CONFIG_EN_SHIFT; + } + + vpfe_reg_write(ccdc, cfg, VPFE_CONFIG); +} + +static void vpfe_ccdc_setwin(struct vpfe_ccdc *ccdc, + struct v4l2_rect *image_win, + enum ccdc_frmfmt frm_fmt, + int bpp) +{ + int horz_start, horz_nr_pixels; + int vert_start, vert_nr_lines; + int val, mid_img; + + /* + * ppc - per pixel count. indicates how many pixels per cell + * output to SDRAM. example, for ycbcr, it is one y and one c, so 2. + * raw capture this is 1 + */ + horz_start = image_win->left * bpp; + horz_nr_pixels = (image_win->width * bpp) - 1; + vpfe_reg_write(ccdc, (horz_start << VPFE_HORZ_INFO_SPH_SHIFT) | + horz_nr_pixels, VPFE_HORZ_INFO); + + vert_start = image_win->top; + + if (frm_fmt == CCDC_FRMFMT_INTERLACED) { + vert_nr_lines = (image_win->height >> 1) - 1; + vert_start >>= 1; + /* Since first line doesn't have any data */ + vert_start += 1; + /* configure VDINT0 */ + val = (vert_start << VPFE_VDINT_VDINT0_SHIFT); + } else { + /* Since first line doesn't have any data */ + vert_start += 1; + vert_nr_lines = image_win->height - 1; + /* + * configure VDINT0 and VDINT1. VDINT1 will be at half + * of image height + */ + mid_img = vert_start + (image_win->height / 2); + val = (vert_start << VPFE_VDINT_VDINT0_SHIFT) | + (mid_img & VPFE_VDINT_VDINT1_MASK); + } + + vpfe_reg_write(ccdc, val, VPFE_VDINT); + + vpfe_reg_write(ccdc, (vert_start << VPFE_VERT_START_SLV0_SHIFT) | + vert_start, VPFE_VERT_START); + vpfe_reg_write(ccdc, vert_nr_lines, VPFE_VERT_LINES); +} + +static void vpfe_reg_dump(struct vpfe_ccdc *ccdc) +{ + struct vpfe_device *vpfe = to_vpfe(ccdc); + + vpfe_dbg(3, vpfe, "ALAW: 0x%x\n", vpfe_reg_read(ccdc, VPFE_ALAW)); + vpfe_dbg(3, vpfe, "CLAMP: 0x%x\n", vpfe_reg_read(ccdc, VPFE_CLAMP)); + vpfe_dbg(3, vpfe, "DCSUB: 0x%x\n", vpfe_reg_read(ccdc, VPFE_DCSUB)); + vpfe_dbg(3, vpfe, "BLKCMP: 0x%x\n", vpfe_reg_read(ccdc, VPFE_BLKCMP)); + vpfe_dbg(3, vpfe, "COLPTN: 0x%x\n", vpfe_reg_read(ccdc, VPFE_COLPTN)); + vpfe_dbg(3, vpfe, "SDOFST: 0x%x\n", vpfe_reg_read(ccdc, VPFE_SDOFST)); + vpfe_dbg(3, vpfe, "SYN_MODE: 0x%x\n", + vpfe_reg_read(ccdc, VPFE_SYNMODE)); + vpfe_dbg(3, vpfe, "HSIZE_OFF: 0x%x\n", + vpfe_reg_read(ccdc, VPFE_HSIZE_OFF)); + vpfe_dbg(3, vpfe, "HORZ_INFO: 0x%x\n", + vpfe_reg_read(ccdc, VPFE_HORZ_INFO)); + vpfe_dbg(3, vpfe, "VERT_START: 0x%x\n", + vpfe_reg_read(ccdc, VPFE_VERT_START)); + vpfe_dbg(3, vpfe, "VERT_LINES: 0x%x\n", + vpfe_reg_read(ccdc, VPFE_VERT_LINES)); +} + +static int +vpfe_ccdc_validate_param(struct vpfe_ccdc *ccdc, + struct vpfe_ccdc_config_params_raw *ccdcparam) +{ + struct vpfe_device *vpfe = to_vpfe(ccdc); + u8 max_gamma, max_data; + + if (!ccdcparam->alaw.enable) + return 0; + + max_gamma = ccdc_gamma_width_max_bit(ccdcparam->alaw.gamma_wd); + max_data = ccdc_data_size_max_bit(ccdcparam->data_sz); + + if (ccdcparam->alaw.gamma_wd > VPFE_CCDC_GAMMA_BITS_09_0 || + ccdcparam->alaw.gamma_wd < VPFE_CCDC_GAMMA_BITS_15_6 || + max_gamma > max_data) { + vpfe_dbg(1, vpfe, "Invalid data line select\n"); + return -EINVAL; + } + + return 0; +} + +static void +vpfe_ccdc_update_raw_params(struct vpfe_ccdc *ccdc, + struct vpfe_ccdc_config_params_raw *raw_params) +{ + struct vpfe_ccdc_config_params_raw *config_params = + &ccdc->ccdc_cfg.bayer.config_params; + + config_params = raw_params; +} + +/* + * vpfe_ccdc_restore_defaults() + * This function will write defaults to all CCDC registers + */ +static void vpfe_ccdc_restore_defaults(struct vpfe_ccdc *ccdc) +{ + int i; + + /* Disable CCDC */ + vpfe_pcr_enable(ccdc, 0); + + /* set all registers to default value */ + for (i = 4; i <= 0x94; i += 4) + vpfe_reg_write(ccdc, 0, i); + + vpfe_reg_write(ccdc, VPFE_NO_CULLING, VPFE_CULLING); + vpfe_reg_write(ccdc, VPFE_CCDC_GAMMA_BITS_11_2, VPFE_ALAW); +} + +static int vpfe_ccdc_close(struct vpfe_ccdc *ccdc, struct device *dev) +{ + int dma_cntl, i, pcr; + + /* If the CCDC module is still busy wait for it to be done */ + for (i = 0; i < 10; i++) { + usleep_range(5000, 6000); + pcr = vpfe_reg_read(ccdc, VPFE_PCR); + if (!pcr) + break; + + /* make sure it it is disabled */ + vpfe_pcr_enable(ccdc, 0); + } + + /* Disable CCDC by resetting all register to default POR values */ + vpfe_ccdc_restore_defaults(ccdc); + + /* if DMA_CNTL overflow bit is set. Clear it + * It appears to take a while for this to become quiescent ~20ms + */ + for (i = 0; i < 10; i++) { + dma_cntl = vpfe_reg_read(ccdc, VPFE_DMA_CNTL); + if (!(dma_cntl & VPFE_DMA_CNTL_OVERFLOW)) + break; + + /* Clear the overflow bit */ + vpfe_reg_write(ccdc, dma_cntl, VPFE_DMA_CNTL); + usleep_range(5000, 6000); + } + + /* Disabled the module at the CONFIG level */ + vpfe_config_enable(ccdc, 0); + + pm_runtime_put_sync(dev); + + return 0; +} + +static int vpfe_ccdc_set_params(struct vpfe_ccdc *ccdc, void __user *params) +{ + struct vpfe_device *vpfe = container_of(ccdc, struct vpfe_device, ccdc); + struct vpfe_ccdc_config_params_raw raw_params; + int x; + + if (ccdc->ccdc_cfg.if_type != VPFE_RAW_BAYER) + return -EINVAL; + + x = copy_from_user(&raw_params, params, sizeof(raw_params)); + if (x) { + vpfe_dbg(1, vpfe, + "vpfe_ccdc_set_params: error in copying ccdc params, %d\n", + x); + return -EFAULT; + } + + if (!vpfe_ccdc_validate_param(ccdc, &raw_params)) { + vpfe_ccdc_update_raw_params(ccdc, &raw_params); + return 0; + } + + return -EINVAL; +} + +/* + * vpfe_ccdc_config_ycbcr() + * This function will configure CCDC for YCbCr video capture + */ +static void vpfe_ccdc_config_ycbcr(struct vpfe_ccdc *ccdc) +{ + struct vpfe_device *vpfe = container_of(ccdc, struct vpfe_device, ccdc); + struct ccdc_params_ycbcr *params = &ccdc->ccdc_cfg.ycbcr; + u32 syn_mode; + + vpfe_dbg(3, vpfe, "vpfe_ccdc_config_ycbcr:\n"); + /* + * first restore the CCDC registers to default values + * This is important since we assume default values to be set in + * a lot of registers that we didn't touch + */ + vpfe_ccdc_restore_defaults(ccdc); + + /* + * configure pixel format, frame format, configure video frame + * format, enable output to SDRAM, enable internal timing generator + * and 8bit pack mode + */ + syn_mode = (((params->pix_fmt & VPFE_SYN_MODE_INPMOD_MASK) << + VPFE_SYN_MODE_INPMOD_SHIFT) | + ((params->frm_fmt & VPFE_SYN_FLDMODE_MASK) << + VPFE_SYN_FLDMODE_SHIFT) | VPFE_VDHDEN_ENABLE | + VPFE_WEN_ENABLE | VPFE_DATA_PACK_ENABLE); + + /* setup BT.656 sync mode */ + if (params->bt656_enable) { + vpfe_reg_write(ccdc, VPFE_REC656IF_BT656_EN, VPFE_REC656IF); + + /* + * configure the FID, VD, HD pin polarity, + * fld,hd pol positive, vd negative, 8-bit data + */ + syn_mode |= VPFE_SYN_MODE_VD_POL_NEGATIVE; + if (ccdc->ccdc_cfg.if_type == VPFE_BT656_10BIT) + syn_mode |= VPFE_SYN_MODE_10BITS; + else + syn_mode |= VPFE_SYN_MODE_8BITS; + } else { + /* y/c external sync mode */ + syn_mode |= (((params->fid_pol & VPFE_FID_POL_MASK) << + VPFE_FID_POL_SHIFT) | + ((params->hd_pol & VPFE_HD_POL_MASK) << + VPFE_HD_POL_SHIFT) | + ((params->vd_pol & VPFE_VD_POL_MASK) << + VPFE_VD_POL_SHIFT)); + } + vpfe_reg_write(ccdc, syn_mode, VPFE_SYNMODE); + + /* configure video window */ + vpfe_ccdc_setwin(ccdc, ¶ms->win, + params->frm_fmt, params->bytesperpixel); + + /* + * configure the order of y cb cr in SDRAM, and disable latch + * internal register on vsync + */ + if (ccdc->ccdc_cfg.if_type == VPFE_BT656_10BIT) + vpfe_reg_write(ccdc, + (params->pix_order << VPFE_CCDCFG_Y8POS_SHIFT) | + VPFE_LATCH_ON_VSYNC_DISABLE | + VPFE_CCDCFG_BW656_10BIT, VPFE_CCDCFG); + else + vpfe_reg_write(ccdc, + (params->pix_order << VPFE_CCDCFG_Y8POS_SHIFT) | + VPFE_LATCH_ON_VSYNC_DISABLE, VPFE_CCDCFG); + + /* + * configure the horizontal line offset. This should be a + * on 32 byte boundary. So clear LSB 5 bits + */ + vpfe_reg_write(ccdc, params->bytesperline, VPFE_HSIZE_OFF); + + /* configure the memory line offset */ + if (params->buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) + /* two fields are interleaved in memory */ + vpfe_reg_write(ccdc, VPFE_SDOFST_FIELD_INTERLEAVED, + VPFE_SDOFST); +} + +static void +vpfe_ccdc_config_black_clamp(struct vpfe_ccdc *ccdc, + struct vpfe_ccdc_black_clamp *bclamp) +{ + u32 val; + + if (!bclamp->enable) { + /* configure DCSub */ + val = (bclamp->dc_sub) & VPFE_BLK_DC_SUB_MASK; + vpfe_reg_write(ccdc, val, VPFE_DCSUB); + vpfe_reg_write(ccdc, VPFE_CLAMP_DEFAULT_VAL, VPFE_CLAMP); + return; + } + /* + * Configure gain, Start pixel, No of line to be avg, + * No of pixel/line to be avg, & Enable the Black clamping + */ + val = ((bclamp->sgain & VPFE_BLK_SGAIN_MASK) | + ((bclamp->start_pixel & VPFE_BLK_ST_PXL_MASK) << + VPFE_BLK_ST_PXL_SHIFT) | + ((bclamp->sample_ln & VPFE_BLK_SAMPLE_LINE_MASK) << + VPFE_BLK_SAMPLE_LINE_SHIFT) | + ((bclamp->sample_pixel & VPFE_BLK_SAMPLE_LN_MASK) << + VPFE_BLK_SAMPLE_LN_SHIFT) | VPFE_BLK_CLAMP_ENABLE); + vpfe_reg_write(ccdc, val, VPFE_CLAMP); + /* If Black clamping is enable then make dcsub 0 */ + vpfe_reg_write(ccdc, VPFE_DCSUB_DEFAULT_VAL, VPFE_DCSUB); +} + +static void +vpfe_ccdc_config_black_compense(struct vpfe_ccdc *ccdc, + struct vpfe_ccdc_black_compensation *bcomp) +{ + u32 val; + + val = ((bcomp->b & VPFE_BLK_COMP_MASK) | + ((bcomp->gb & VPFE_BLK_COMP_MASK) << + VPFE_BLK_COMP_GB_COMP_SHIFT) | + ((bcomp->gr & VPFE_BLK_COMP_MASK) << + VPFE_BLK_COMP_GR_COMP_SHIFT) | + ((bcomp->r & VPFE_BLK_COMP_MASK) << + VPFE_BLK_COMP_R_COMP_SHIFT)); + vpfe_reg_write(ccdc, val, VPFE_BLKCMP); +} + +/* + * vpfe_ccdc_config_raw() + * This function will configure CCDC for Raw capture mode + */ +static void vpfe_ccdc_config_raw(struct vpfe_ccdc *ccdc) +{ + struct vpfe_device *vpfe = container_of(ccdc, struct vpfe_device, ccdc); + struct vpfe_ccdc_config_params_raw *config_params = + &ccdc->ccdc_cfg.bayer.config_params; + struct ccdc_params_raw *params = &ccdc->ccdc_cfg.bayer; + unsigned int syn_mode; + unsigned int val; + + vpfe_dbg(3, vpfe, "vpfe_ccdc_config_raw:\n"); + + /* Reset CCDC */ + vpfe_ccdc_restore_defaults(ccdc); + + /* Disable latching function registers on VSYNC */ + vpfe_reg_write(ccdc, VPFE_LATCH_ON_VSYNC_DISABLE, VPFE_CCDCFG); + + /* + * Configure the vertical sync polarity(SYN_MODE.VDPOL), + * horizontal sync polarity (SYN_MODE.HDPOL), frame id polarity + * (SYN_MODE.FLDPOL), frame format(progressive or interlace), + * data size(SYNMODE.DATSIZ), &pixel format (Input mode), output + * SDRAM, enable internal timing generator + */ + syn_mode = (((params->vd_pol & VPFE_VD_POL_MASK) << VPFE_VD_POL_SHIFT) | + ((params->hd_pol & VPFE_HD_POL_MASK) << VPFE_HD_POL_SHIFT) | + ((params->fid_pol & VPFE_FID_POL_MASK) << + VPFE_FID_POL_SHIFT) | ((params->frm_fmt & + VPFE_FRM_FMT_MASK) << VPFE_FRM_FMT_SHIFT) | + ((config_params->data_sz & VPFE_DATA_SZ_MASK) << + VPFE_DATA_SZ_SHIFT) | ((params->pix_fmt & + VPFE_PIX_FMT_MASK) << VPFE_PIX_FMT_SHIFT) | + VPFE_WEN_ENABLE | VPFE_VDHDEN_ENABLE); + + /* Enable and configure aLaw register if needed */ + if (config_params->alaw.enable) { + val = ((config_params->alaw.gamma_wd & + VPFE_ALAW_GAMMA_WD_MASK) | VPFE_ALAW_ENABLE); + vpfe_reg_write(ccdc, val, VPFE_ALAW); + vpfe_dbg(3, vpfe, "\nWriting 0x%x to ALAW...\n", val); + } + + /* Configure video window */ + vpfe_ccdc_setwin(ccdc, ¶ms->win, params->frm_fmt, + params->bytesperpixel); + + /* Configure Black Clamp */ + vpfe_ccdc_config_black_clamp(ccdc, &config_params->blk_clamp); + + /* Configure Black level compensation */ + vpfe_ccdc_config_black_compense(ccdc, &config_params->blk_comp); + + /* If data size is 8 bit then pack the data */ + if ((config_params->data_sz == VPFE_CCDC_DATA_8BITS) || + config_params->alaw.enable) + syn_mode |= VPFE_DATA_PACK_ENABLE; + + /* + * Configure Horizontal offset register. If pack 8 is enabled then + * 1 pixel will take 1 byte + */ + vpfe_reg_write(ccdc, params->bytesperline, VPFE_HSIZE_OFF); + + vpfe_dbg(3, vpfe, "Writing %d (%x) to HSIZE_OFF\n", + params->bytesperline, params->bytesperline); + + /* Set value for SDOFST */ + if (params->frm_fmt == CCDC_FRMFMT_INTERLACED) { + if (params->image_invert_enable) { + /* For interlace inverse mode */ + vpfe_reg_write(ccdc, VPFE_INTERLACED_IMAGE_INVERT, + VPFE_SDOFST); + } else { + /* For interlace non inverse mode */ + vpfe_reg_write(ccdc, VPFE_INTERLACED_NO_IMAGE_INVERT, + VPFE_SDOFST); + } + } else if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) { + vpfe_reg_write(ccdc, VPFE_PROGRESSIVE_NO_IMAGE_INVERT, + VPFE_SDOFST); + } + + vpfe_reg_write(ccdc, syn_mode, VPFE_SYNMODE); + + vpfe_reg_dump(ccdc); +} + +static inline int +vpfe_ccdc_set_buftype(struct vpfe_ccdc *ccdc, + enum ccdc_buftype buf_type) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc->ccdc_cfg.bayer.buf_type = buf_type; + else + ccdc->ccdc_cfg.ycbcr.buf_type = buf_type; + + return 0; +} + +static inline enum ccdc_buftype vpfe_ccdc_get_buftype(struct vpfe_ccdc *ccdc) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc->ccdc_cfg.bayer.buf_type; + + return ccdc->ccdc_cfg.ycbcr.buf_type; +} + +static int vpfe_ccdc_set_pixel_format(struct vpfe_ccdc *ccdc, u32 pixfmt) +{ + struct vpfe_device *vpfe = container_of(ccdc, struct vpfe_device, ccdc); + + vpfe_dbg(1, vpfe, "vpfe_ccdc_set_pixel_format: if_type: %d, pixfmt:%s\n", + ccdc->ccdc_cfg.if_type, print_fourcc(pixfmt)); + + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) { + ccdc->ccdc_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; + /* + * Need to clear it in case it was left on + * after the last capture. + */ + ccdc->ccdc_cfg.bayer.config_params.alaw.enable = 0; + + switch (pixfmt) { + case V4L2_PIX_FMT_SBGGR8: + ccdc->ccdc_cfg.bayer.config_params.alaw.enable = 1; + break; + + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_RGB565X: + break; + + case V4L2_PIX_FMT_SBGGR16: + default: + return -EINVAL; + } + } else { + switch (pixfmt) { + case V4L2_PIX_FMT_YUYV: + ccdc->ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_YCBYCR; + break; + + case V4L2_PIX_FMT_UYVY: + ccdc->ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; + break; + + default: + return -EINVAL; + } + } + + return 0; +} + +static u32 vpfe_ccdc_get_pixel_format(struct vpfe_ccdc *ccdc) +{ + u32 pixfmt; + + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) { + pixfmt = V4L2_PIX_FMT_YUYV; + } else { + if (ccdc->ccdc_cfg.ycbcr.pix_order == CCDC_PIXORDER_YCBYCR) + pixfmt = V4L2_PIX_FMT_YUYV; + else + pixfmt = V4L2_PIX_FMT_UYVY; + } + + return pixfmt; +} + +static int +vpfe_ccdc_set_image_window(struct vpfe_ccdc *ccdc, + struct v4l2_rect *win, unsigned int bpp) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) { + ccdc->ccdc_cfg.bayer.win = *win; + ccdc->ccdc_cfg.bayer.bytesperpixel = bpp; + ccdc->ccdc_cfg.bayer.bytesperline = ALIGN(win->width * bpp, 32); + } else { + ccdc->ccdc_cfg.ycbcr.win = *win; + ccdc->ccdc_cfg.ycbcr.bytesperpixel = bpp; + ccdc->ccdc_cfg.ycbcr.bytesperline = ALIGN(win->width * bpp, 32); + } + + return 0; +} + +static inline void +vpfe_ccdc_get_image_window(struct vpfe_ccdc *ccdc, + struct v4l2_rect *win) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + *win = ccdc->ccdc_cfg.bayer.win; + else + *win = ccdc->ccdc_cfg.ycbcr.win; +} + +static inline unsigned int vpfe_ccdc_get_line_length(struct vpfe_ccdc *ccdc) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc->ccdc_cfg.bayer.bytesperline; + + return ccdc->ccdc_cfg.ycbcr.bytesperline; +} + +static inline int +vpfe_ccdc_set_frame_format(struct vpfe_ccdc *ccdc, + enum ccdc_frmfmt frm_fmt) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc->ccdc_cfg.bayer.frm_fmt = frm_fmt; + else + ccdc->ccdc_cfg.ycbcr.frm_fmt = frm_fmt; + + return 0; +} + +static inline enum ccdc_frmfmt +vpfe_ccdc_get_frame_format(struct vpfe_ccdc *ccdc) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc->ccdc_cfg.bayer.frm_fmt; + + return ccdc->ccdc_cfg.ycbcr.frm_fmt; +} + +static inline int vpfe_ccdc_getfid(struct vpfe_ccdc *ccdc) +{ + return (vpfe_reg_read(ccdc, VPFE_SYNMODE) >> 15) & 1; +} + +static inline void vpfe_set_sdr_addr(struct vpfe_ccdc *ccdc, unsigned long addr) +{ + vpfe_reg_write(ccdc, addr & 0xffffffe0, VPFE_SDR_ADDR); +} + +static int vpfe_ccdc_set_hw_if_params(struct vpfe_ccdc *ccdc, + struct vpfe_hw_if_param *params) +{ + struct vpfe_device *vpfe = container_of(ccdc, struct vpfe_device, ccdc); + + ccdc->ccdc_cfg.if_type = params->if_type; + + switch (params->if_type) { + case VPFE_BT656: + case VPFE_YCBCR_SYNC_16: + case VPFE_YCBCR_SYNC_8: + case VPFE_BT656_10BIT: + ccdc->ccdc_cfg.ycbcr.vd_pol = params->vdpol; + ccdc->ccdc_cfg.ycbcr.hd_pol = params->hdpol; + break; + + case VPFE_RAW_BAYER: + ccdc->ccdc_cfg.bayer.vd_pol = params->vdpol; + ccdc->ccdc_cfg.bayer.hd_pol = params->hdpol; + if (params->bus_width == 10) + ccdc->ccdc_cfg.bayer.config_params.data_sz = + VPFE_CCDC_DATA_10BITS; + else + ccdc->ccdc_cfg.bayer.config_params.data_sz = + VPFE_CCDC_DATA_8BITS; + vpfe_dbg(1, vpfe, "params.bus_width: %d\n", + params->bus_width); + vpfe_dbg(1, vpfe, "config_params.data_sz: %d\n", + ccdc->ccdc_cfg.bayer.config_params.data_sz); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static void vpfe_clear_intr(struct vpfe_ccdc *ccdc, int vdint) +{ + unsigned int vpfe_int_status; + + vpfe_int_status = vpfe_reg_read(ccdc, VPFE_IRQ_STS); + + switch (vdint) { + /* VD0 interrupt */ + case VPFE_VDINT0: + vpfe_int_status &= ~VPFE_VDINT0; + vpfe_int_status |= VPFE_VDINT0; + break; + + /* VD1 interrupt */ + case VPFE_VDINT1: + vpfe_int_status &= ~VPFE_VDINT1; + vpfe_int_status |= VPFE_VDINT1; + break; + + /* VD2 interrupt */ + case VPFE_VDINT2: + vpfe_int_status &= ~VPFE_VDINT2; + vpfe_int_status |= VPFE_VDINT2; + break; + + /* Clear all interrupts */ + default: + vpfe_int_status &= ~(VPFE_VDINT0 | + VPFE_VDINT1 | + VPFE_VDINT2); + vpfe_int_status |= (VPFE_VDINT0 | + VPFE_VDINT1 | + VPFE_VDINT2); + break; + } + /* Clear specific VDINT from the status register */ + vpfe_reg_write(ccdc, vpfe_int_status, VPFE_IRQ_STS); + + vpfe_int_status = vpfe_reg_read(ccdc, VPFE_IRQ_STS); + + /* Acknowledge that we are done with all interrupts */ + vpfe_reg_write(ccdc, 1, VPFE_IRQ_EOI); +} + +static void vpfe_ccdc_config_defaults(struct vpfe_ccdc *ccdc) +{ + ccdc->ccdc_cfg.if_type = VPFE_RAW_BAYER; + + ccdc->ccdc_cfg.ycbcr.pix_fmt = CCDC_PIXFMT_YCBCR_8BIT; + ccdc->ccdc_cfg.ycbcr.frm_fmt = CCDC_FRMFMT_INTERLACED; + ccdc->ccdc_cfg.ycbcr.fid_pol = VPFE_PINPOL_POSITIVE; + ccdc->ccdc_cfg.ycbcr.vd_pol = VPFE_PINPOL_POSITIVE; + ccdc->ccdc_cfg.ycbcr.hd_pol = VPFE_PINPOL_POSITIVE; + ccdc->ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; + ccdc->ccdc_cfg.ycbcr.buf_type = CCDC_BUFTYPE_FLD_INTERLEAVED; + + ccdc->ccdc_cfg.ycbcr.win.left = 0; + ccdc->ccdc_cfg.ycbcr.win.top = 0; + ccdc->ccdc_cfg.ycbcr.win.width = 720; + ccdc->ccdc_cfg.ycbcr.win.height = 576; + ccdc->ccdc_cfg.ycbcr.bt656_enable = 1; + + ccdc->ccdc_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; + ccdc->ccdc_cfg.bayer.frm_fmt = CCDC_FRMFMT_PROGRESSIVE; + ccdc->ccdc_cfg.bayer.fid_pol = VPFE_PINPOL_POSITIVE; + ccdc->ccdc_cfg.bayer.vd_pol = VPFE_PINPOL_POSITIVE; + ccdc->ccdc_cfg.bayer.hd_pol = VPFE_PINPOL_POSITIVE; + + ccdc->ccdc_cfg.bayer.win.left = 0; + ccdc->ccdc_cfg.bayer.win.top = 0; + ccdc->ccdc_cfg.bayer.win.width = 800; + ccdc->ccdc_cfg.bayer.win.height = 600; + ccdc->ccdc_cfg.bayer.config_params.data_sz = VPFE_CCDC_DATA_8BITS; + ccdc->ccdc_cfg.bayer.config_params.alaw.gamma_wd = + VPFE_CCDC_GAMMA_BITS_09_0; +} + +/* + * vpfe_get_ccdc_image_format - Get image parameters based on CCDC settings + */ +static int vpfe_get_ccdc_image_format(struct vpfe_device *vpfe, + struct v4l2_format *f) +{ + struct v4l2_rect image_win; + enum ccdc_buftype buf_type; + enum ccdc_frmfmt frm_fmt; + + memset(f, 0, sizeof(*f)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vpfe_ccdc_get_image_window(&vpfe->ccdc, &image_win); + f->fmt.pix.width = image_win.width; + f->fmt.pix.height = image_win.height; + f->fmt.pix.bytesperline = vpfe_ccdc_get_line_length(&vpfe->ccdc); + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height; + buf_type = vpfe_ccdc_get_buftype(&vpfe->ccdc); + f->fmt.pix.pixelformat = vpfe_ccdc_get_pixel_format(&vpfe->ccdc); + frm_fmt = vpfe_ccdc_get_frame_format(&vpfe->ccdc); + + if (frm_fmt == CCDC_FRMFMT_PROGRESSIVE) { + f->fmt.pix.field = V4L2_FIELD_NONE; + } else if (frm_fmt == CCDC_FRMFMT_INTERLACED) { + if (buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) { + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + } else if (buf_type == CCDC_BUFTYPE_FLD_SEPARATED) { + f->fmt.pix.field = V4L2_FIELD_SEQ_TB; + } else { + vpfe_err(vpfe, "Invalid buf_type\n"); + return -EINVAL; + } + } else { + vpfe_err(vpfe, "Invalid frm_fmt\n"); + return -EINVAL; + } + return 0; +} + +static int vpfe_config_ccdc_image_format(struct vpfe_device *vpfe) +{ + enum ccdc_frmfmt frm_fmt = CCDC_FRMFMT_INTERLACED; + int ret; + + vpfe_dbg(2, vpfe, "vpfe_config_ccdc_image_format\n"); + + vpfe_dbg(1, vpfe, "pixelformat: %s\n", + print_fourcc(vpfe->fmt.fmt.pix.pixelformat)); + + if (vpfe_ccdc_set_pixel_format(&vpfe->ccdc, + vpfe->fmt.fmt.pix.pixelformat) < 0) { + vpfe_err(vpfe, "couldn't set pix format in ccdc\n"); + return -EINVAL; + } + + /* configure the image window */ + vpfe_ccdc_set_image_window(&vpfe->ccdc, &vpfe->crop, vpfe->bpp); + + switch (vpfe->fmt.fmt.pix.field) { + case V4L2_FIELD_INTERLACED: + /* do nothing, since it is default */ + ret = vpfe_ccdc_set_buftype( + &vpfe->ccdc, + CCDC_BUFTYPE_FLD_INTERLEAVED); + break; + + case V4L2_FIELD_NONE: + frm_fmt = CCDC_FRMFMT_PROGRESSIVE; + /* buffer type only applicable for interlaced scan */ + break; + + case V4L2_FIELD_SEQ_TB: + ret = vpfe_ccdc_set_buftype( + &vpfe->ccdc, + CCDC_BUFTYPE_FLD_SEPARATED); + break; + + default: + return -EINVAL; + } + + if (ret) + return ret; + + return vpfe_ccdc_set_frame_format(&vpfe->ccdc, frm_fmt); +} + +/* + * vpfe_config_image_format() + * For a given standard, this functions sets up the default + * pix format & crop values in the vpfe device and ccdc. It first + * starts with defaults based values from the standard table. + * It then checks if sub device support g_mbus_fmt and then override the + * values based on that.Sets crop values to match with scan resolution + * starting at 0,0. It calls vpfe_config_ccdc_image_format() set the + * values in ccdc + */ +static int vpfe_config_image_format(struct vpfe_device *vpfe, + v4l2_std_id std_id) +{ + struct v4l2_pix_format *pix = &vpfe->fmt.fmt.pix; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(vpfe_standards); i++) { + if (vpfe_standards[i].std_id & std_id) { + vpfe->std_info.active_pixels = + vpfe_standards[i].width; + vpfe->std_info.active_lines = + vpfe_standards[i].height; + vpfe->std_info.frame_format = + vpfe_standards[i].frame_format; + vpfe->std_index = i; + + break; + } + } + + if (i == ARRAY_SIZE(vpfe_standards)) { + vpfe_err(vpfe, "standard not supported\n"); + return -EINVAL; + } + + vpfe->crop.top = vpfe->crop.left = 0; + vpfe->crop.width = vpfe->std_info.active_pixels; + vpfe->crop.height = vpfe->std_info.active_lines; + pix->width = vpfe->crop.width; + pix->height = vpfe->crop.height; + pix->pixelformat = V4L2_PIX_FMT_YUYV; + + /* first field and frame format based on standard frame format */ + if (vpfe->std_info.frame_format) + pix->field = V4L2_FIELD_INTERLACED; + else + pix->field = V4L2_FIELD_NONE; + + ret = __vpfe_get_format(vpfe, &vpfe->fmt, &vpfe->bpp); + if (ret) + return ret; + + /* Update the crop window based on found values */ + vpfe->crop.width = pix->width; + vpfe->crop.height = pix->height; + + return vpfe_config_ccdc_image_format(vpfe); +} + +static int vpfe_initialize_device(struct vpfe_device *vpfe) +{ + struct vpfe_subdev_info *sdinfo; + int ret; + + sdinfo = &vpfe->cfg->sub_devs[0]; + sdinfo->sd = vpfe->sd[0]; + vpfe->current_input = 0; + vpfe->std_index = 0; + /* Configure the default format information */ + ret = vpfe_config_image_format(vpfe, + vpfe_standards[vpfe->std_index].std_id); + if (ret) + return ret; + + pm_runtime_get_sync(vpfe->pdev); + + vpfe_config_enable(&vpfe->ccdc, 1); + + vpfe_ccdc_restore_defaults(&vpfe->ccdc); + + /* Clear all VPFE interrupts */ + vpfe_clear_intr(&vpfe->ccdc, -1); + + return ret; +} + +/* + * vpfe_release : This function is based on the vb2_fop_release + * helper function. + * It has been augmented to handle module power management, + * by disabling/enabling h/w module fcntl clock when necessary. + */ +static int vpfe_release(struct file *file) +{ + struct vpfe_device *vpfe = video_drvdata(file); + int ret; + + mutex_lock(&vpfe->lock); + + if (v4l2_fh_is_singular_file(file)) + vpfe_ccdc_close(&vpfe->ccdc, vpfe->pdev); + ret = _vb2_fop_release(file, NULL); + + mutex_unlock(&vpfe->lock); + + return ret; +} + +/* + * vpfe_open : This function is based on the v4l2_fh_open helper function. + * It has been augmented to handle module power management, + * by disabling/enabling h/w module fcntl clock when necessary. + */ +static int vpfe_open(struct file *file) +{ + struct vpfe_device *vpfe = video_drvdata(file); + int ret; + + mutex_lock(&vpfe->lock); + + ret = v4l2_fh_open(file); + if (ret) { + vpfe_err(vpfe, "v4l2_fh_open failed\n"); + goto unlock; + } + + if (!v4l2_fh_is_singular_file(file)) + goto unlock; + + if (vpfe_initialize_device(vpfe)) { + v4l2_fh_release(file); + ret = -ENODEV; + } + +unlock: + mutex_unlock(&vpfe->lock); + return ret; +} + +/** + * vpfe_schedule_next_buffer: set next buffer address for capture + * @vpfe : ptr to vpfe device + * + * This function will get next buffer from the dma queue and + * set the buffer address in the vpfe register for capture. + * the buffer is marked active + * + * Assumes caller is holding vpfe->dma_queue_lock already + */ +static inline void vpfe_schedule_next_buffer(struct vpfe_device *vpfe) +{ + vpfe->next_frm = list_entry(vpfe->dma_queue.next, + struct vpfe_cap_buffer, list); + list_del(&vpfe->next_frm->list); + + vpfe_set_sdr_addr(&vpfe->ccdc, + vb2_dma_contig_plane_dma_addr(&vpfe->next_frm->vb, 0)); +} + +static inline void vpfe_schedule_bottom_field(struct vpfe_device *vpfe) +{ + unsigned long addr; + + addr = vb2_dma_contig_plane_dma_addr(&vpfe->next_frm->vb, 0) + + vpfe->field_off; + + vpfe_set_sdr_addr(&vpfe->ccdc, addr); +} + +/* + * vpfe_process_buffer_complete: process a completed buffer + * @vpfe : ptr to vpfe device + * + * This function time stamp the buffer and mark it as DONE. It also + * wake up any process waiting on the QUEUE and set the next buffer + * as current + */ +static inline void vpfe_process_buffer_complete(struct vpfe_device *vpfe) +{ + v4l2_get_timestamp(&vpfe->cur_frm->vb.v4l2_buf.timestamp); + vpfe->cur_frm->vb.v4l2_buf.field = vpfe->fmt.fmt.pix.field; + vpfe->cur_frm->vb.v4l2_buf.sequence = vpfe->sequence++; + vb2_buffer_done(&vpfe->cur_frm->vb, VB2_BUF_STATE_DONE); + vpfe->cur_frm = vpfe->next_frm; +} + +/* + * vpfe_isr : ISR handler for vpfe capture (VINT0) + * @irq: irq number + * @dev_id: dev_id ptr + * + * It changes status of the captured buffer, takes next buffer from the queue + * and sets its address in VPFE registers + */ +static irqreturn_t vpfe_isr(int irq, void *dev) +{ + struct vpfe_device *vpfe = (struct vpfe_device *)dev; + enum v4l2_field field; + int intr_status; + int fid; + + intr_status = vpfe_reg_read(&vpfe->ccdc, VPFE_IRQ_STS); + + if (intr_status & VPFE_VDINT0) { + field = vpfe->fmt.fmt.pix.field; + + if (field == V4L2_FIELD_NONE) { + /* handle progressive frame capture */ + if (vpfe->cur_frm != vpfe->next_frm) + vpfe_process_buffer_complete(vpfe); + goto next_intr; + } + + /* interlaced or TB capture check which field + we are in hardware */ + fid = vpfe_ccdc_getfid(&vpfe->ccdc); + + /* switch the software maintained field id */ + vpfe->field ^= 1; + if (fid == vpfe->field) { + /* we are in-sync here,continue */ + if (fid == 0) { + /* + * One frame is just being captured. If the + * next frame is available, release the + * current frame and move on + */ + if (vpfe->cur_frm != vpfe->next_frm) + vpfe_process_buffer_complete(vpfe); + /* + * based on whether the two fields are stored + * interleave or separately in memory, + * reconfigure the CCDC memory address + */ + if (field == V4L2_FIELD_SEQ_TB) + vpfe_schedule_bottom_field(vpfe); + + goto next_intr; + } + /* + * if one field is just being captured configure + * the next frame get the next frame from the empty + * queue if no frame is available hold on to the + * current buffer + */ + spin_lock(&vpfe->dma_queue_lock); + if (!list_empty(&vpfe->dma_queue) && + vpfe->cur_frm == vpfe->next_frm) + vpfe_schedule_next_buffer(vpfe); + spin_unlock(&vpfe->dma_queue_lock); + } else if (fid == 0) { + /* + * out of sync. Recover from any hardware out-of-sync. + * May loose one frame + */ + vpfe->field = fid; + } + } + +next_intr: + if (intr_status & VPFE_VDINT1) { + spin_lock(&vpfe->dma_queue_lock); + if (vpfe->fmt.fmt.pix.field == V4L2_FIELD_NONE && + !list_empty(&vpfe->dma_queue) && + vpfe->cur_frm == vpfe->next_frm) + vpfe_schedule_next_buffer(vpfe); + spin_unlock(&vpfe->dma_queue_lock); + } + + vpfe_clear_intr(&vpfe->ccdc, intr_status); + + return IRQ_HANDLED; +} + +static inline void vpfe_detach_irq(struct vpfe_device *vpfe) +{ + unsigned int intr = VPFE_VDINT0; + enum ccdc_frmfmt frame_format; + + frame_format = vpfe_ccdc_get_frame_format(&vpfe->ccdc); + if (frame_format == CCDC_FRMFMT_PROGRESSIVE) + intr |= VPFE_VDINT1; + + vpfe_reg_write(&vpfe->ccdc, intr, VPFE_IRQ_EN_CLR); +} + +static inline void vpfe_attach_irq(struct vpfe_device *vpfe) +{ + unsigned int intr = VPFE_VDINT0; + enum ccdc_frmfmt frame_format; + + frame_format = vpfe_ccdc_get_frame_format(&vpfe->ccdc); + if (frame_format == CCDC_FRMFMT_PROGRESSIVE) + intr |= VPFE_VDINT1; + + vpfe_reg_write(&vpfe->ccdc, intr, VPFE_IRQ_EN_SET); +} + +static int vpfe_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + vpfe_dbg(2, vpfe, "vpfe_querycap\n"); + + strlcpy(cap->driver, VPFE_MODULE_NAME, sizeof(cap->driver)); + strlcpy(cap->card, "TI AM437x VPFE", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", vpfe->v4l2_dev.name); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +/* get the format set at output pad of the adjacent subdev */ +static int __vpfe_get_format(struct vpfe_device *vpfe, + struct v4l2_format *format, unsigned int *bpp) +{ + struct v4l2_mbus_framefmt mbus_fmt; + struct vpfe_subdev_info *sdinfo; + struct v4l2_subdev_format fmt; + int ret; + + sdinfo = vpfe->current_subdev; + if (!sdinfo->sd) + return -EINVAL; + + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.pad = 0; + + ret = v4l2_subdev_call(sdinfo->sd, pad, get_fmt, NULL, &fmt); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + if (!ret) { + v4l2_fill_pix_format(&format->fmt.pix, &fmt.format); + mbus_to_pix(vpfe, &fmt.format, &format->fmt.pix, bpp); + } else { + ret = v4l2_device_call_until_err(&vpfe->v4l2_dev, + sdinfo->grp_id, + video, g_mbus_fmt, + &mbus_fmt); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + v4l2_fill_pix_format(&format->fmt.pix, &mbus_fmt); + mbus_to_pix(vpfe, &mbus_fmt, &format->fmt.pix, bpp); + } + + format->type = vpfe->fmt.type; + + vpfe_dbg(1, vpfe, + "%s size %dx%d (%s) bytesperline = %d, size = %d, bpp = %d\n", + __func__, format->fmt.pix.width, format->fmt.pix.height, + print_fourcc(format->fmt.pix.pixelformat), + format->fmt.pix.bytesperline, format->fmt.pix.sizeimage, *bpp); + + return 0; +} + +/* set the format at output pad of the adjacent subdev */ +static int __vpfe_set_format(struct vpfe_device *vpfe, + struct v4l2_format *format, unsigned int *bpp) +{ + struct v4l2_mbus_framefmt mbus_fmt; + struct vpfe_subdev_info *sdinfo; + struct v4l2_subdev_format fmt; + int ret; + + vpfe_dbg(2, vpfe, "__vpfe_set_format\n"); + + sdinfo = vpfe->current_subdev; + if (!sdinfo->sd) + return -EINVAL; + + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.pad = 0; + + pix_to_mbus(vpfe, &format->fmt.pix, &fmt.format); + + ret = v4l2_subdev_call(sdinfo->sd, pad, set_fmt, NULL, &fmt); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + if (!ret) { + v4l2_fill_pix_format(&format->fmt.pix, &fmt.format); + mbus_to_pix(vpfe, &fmt.format, &format->fmt.pix, bpp); + } else { + ret = v4l2_device_call_until_err(&vpfe->v4l2_dev, + sdinfo->grp_id, + video, s_mbus_fmt, + &mbus_fmt); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + v4l2_fill_pix_format(&format->fmt.pix, &mbus_fmt); + mbus_to_pix(vpfe, &mbus_fmt, &format->fmt.pix, bpp); + } + + format->type = vpfe->fmt.type; + + vpfe_dbg(1, vpfe, + "%s size %dx%d (%s) bytesperline = %d, size = %d, bpp = %d\n", + __func__, format->fmt.pix.width, format->fmt.pix.height, + print_fourcc(format->fmt.pix.pixelformat), + format->fmt.pix.bytesperline, format->fmt.pix.sizeimage, *bpp); + + return 0; +} + +static int vpfe_g_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + vpfe_dbg(2, vpfe, "vpfe_g_fmt\n"); + + *fmt = vpfe->fmt; + + return 0; +} + +static int vpfe_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + struct vpfe_fmt *fmt = NULL; + unsigned int k; + + vpfe_dbg(2, vpfe, "vpfe_enum_format index:%d\n", + f->index); + + sdinfo = vpfe->current_subdev; + if (!sdinfo->sd) + return -EINVAL; + + if (f->index > ARRAY_SIZE(formats)) + return -EINVAL; + + for (k = 0; k < ARRAY_SIZE(formats); k++) { + if (formats[k].index == f->index) { + fmt = &formats[k]; + break; + } + } + if (!fmt) + return -EINVAL; + + strncpy(f->description, fmt->name, sizeof(f->description) - 1); + f->pixelformat = fmt->fourcc; + f->type = vpfe->fmt.type; + + vpfe_dbg(1, vpfe, "vpfe_enum_format: mbus index: %d code: %x pixelformat: %s [%s]\n", + f->index, fmt->code, print_fourcc(fmt->fourcc), fmt->name); + + return 0; +} + +static int vpfe_try_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_device *vpfe = video_drvdata(file); + unsigned int bpp; + + vpfe_dbg(2, vpfe, "vpfe_try_fmt\n"); + + return __vpfe_get_format(vpfe, fmt, &bpp); +} + +static int vpfe_s_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct v4l2_format format; + unsigned int bpp; + int ret; + + vpfe_dbg(2, vpfe, "vpfe_s_fmt\n"); + + /* If streaming is started, return error */ + if (vb2_is_busy(&vpfe->buffer_queue)) { + vpfe_err(vpfe, "%s device busy\n", __func__); + return -EBUSY; + } + + ret = vpfe_try_fmt(file, priv, fmt); + if (ret) + return ret; + + + if (!cmp_v4l2_format(fmt, &format)) { + /* Sensor format is different from the requested format + * so we need to change it + */ + ret = __vpfe_set_format(vpfe, fmt, &bpp); + if (ret) + return ret; + } else /* Just make sure all of the fields are consistent */ + *fmt = format; + + /* First detach any IRQ if currently attached */ + vpfe_detach_irq(vpfe); + vpfe->fmt = *fmt; + vpfe->bpp = bpp; + + /* Update the crop window based on found values */ + vpfe->crop.width = fmt->fmt.pix.width; + vpfe->crop.height = fmt->fmt.pix.height; + + /* set image capture parameters in the ccdc */ + return vpfe_config_ccdc_image_format(vpfe); +} + +static int vpfe_enum_size(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct v4l2_subdev_frame_size_enum fse; + struct vpfe_subdev_info *sdinfo; + struct v4l2_mbus_framefmt mbus; + struct v4l2_pix_format pix; + struct vpfe_fmt *fmt; + int ret; + + vpfe_dbg(2, vpfe, "vpfe_enum_size\n"); + + /* check for valid format */ + fmt = find_format_by_pix(fsize->pixel_format); + if (!fmt) { + vpfe_dbg(3, vpfe, "Invalid pixel code: %x, default used instead\n", + fsize->pixel_format); + return -EINVAL; + } + + memset(fsize->reserved, 0x0, sizeof(fsize->reserved)); + + sdinfo = vpfe->current_subdev; + if (!sdinfo->sd) + return -EINVAL; + + memset(&pix, 0x0, sizeof(pix)); + /* Construct pix from parameter and use default for the rest */ + pix.pixelformat = fsize->pixel_format; + pix.width = 640; + pix.height = 480; + pix.colorspace = V4L2_COLORSPACE_SRGB; + pix.field = V4L2_FIELD_NONE; + pix_to_mbus(vpfe, &pix, &mbus); + + memset(&fse, 0x0, sizeof(fse)); + fse.index = fsize->index; + fse.pad = 0; + fse.code = mbus.code; + ret = v4l2_subdev_call(sdinfo->sd, pad, enum_frame_size, NULL, &fse); + if (ret) + return -EINVAL; + + vpfe_dbg(1, vpfe, "vpfe_enum_size: index: %d code: %x W:[%d,%d] H:[%d,%d]\n", + fse.index, fse.code, fse.min_width, fse.max_width, + fse.min_height, fse.max_height); + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fse.max_width; + fsize->discrete.height = fse.max_height; + + vpfe_dbg(1, vpfe, "vpfe_enum_size: index: %d pixformat: %s size: %dx%d\n", + fsize->index, print_fourcc(fsize->pixel_format), + fsize->discrete.width, fsize->discrete.height); + + return 0; +} + +/* + * vpfe_get_subdev_input_index - Get subdev index and subdev input index for a + * given app input index + */ +static int +vpfe_get_subdev_input_index(struct vpfe_device *vpfe, + int *subdev_index, + int *subdev_input_index, + int app_input_index) +{ + struct vpfe_config *cfg = vpfe->cfg; + struct vpfe_subdev_info *sdinfo; + int i, j = 0; + + for (i = 0; i < ARRAY_SIZE(vpfe->cfg->asd); i++) { + sdinfo = &cfg->sub_devs[i]; + if (app_input_index < (j + 1)) { + *subdev_index = i; + *subdev_input_index = app_input_index - j; + return 0; + } + j++; + } + return -EINVAL; +} + +/* + * vpfe_get_app_input - Get app input index for a given subdev input index + * driver stores the input index of the current sub device and translate it + * when application request the current input + */ +static int vpfe_get_app_input_index(struct vpfe_device *vpfe, + int *app_input_index) +{ + struct vpfe_config *cfg = vpfe->cfg; + struct vpfe_subdev_info *sdinfo; + int i, j = 0; + + for (i = 0; i < ARRAY_SIZE(vpfe->cfg->asd); i++) { + sdinfo = &cfg->sub_devs[i]; + if (!strcmp(sdinfo->name, vpfe->current_subdev->name)) { + if (vpfe->current_input >= 1) + return -1; + *app_input_index = j + vpfe->current_input; + return 0; + } + j++; + } + return -EINVAL; +} + +static int vpfe_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + int subdev, index; + + vpfe_dbg(2, vpfe, "vpfe_enum_input\n"); + + if (vpfe_get_subdev_input_index(vpfe, &subdev, &index, + inp->index) < 0) { + vpfe_dbg(1, vpfe, + "input information not found for the subdev\n"); + return -EINVAL; + } + sdinfo = &vpfe->cfg->sub_devs[subdev]; + *inp = sdinfo->inputs[index]; + + return 0; +} + +static int vpfe_g_input(struct file *file, void *priv, unsigned int *index) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + vpfe_dbg(2, vpfe, "vpfe_g_input\n"); + + return vpfe_get_app_input_index(vpfe, index); +} + +/* Assumes caller is holding vpfe_dev->lock */ +static int vpfe_set_input(struct vpfe_device *vpfe, unsigned int index) +{ + int subdev_index = 0, inp_index = 0; + struct vpfe_subdev_info *sdinfo; + struct vpfe_route *route; + u32 input, output; + int ret; + + vpfe_dbg(2, vpfe, "vpfe_set_input: index: %d\n", index); + + /* If streaming is started, return error */ + if (vb2_is_busy(&vpfe->buffer_queue)) { + vpfe_err(vpfe, "%s device busy\n", __func__); + return -EBUSY; + } + ret = vpfe_get_subdev_input_index(vpfe, + &subdev_index, + &inp_index, + index); + if (ret < 0) { + vpfe_err(vpfe, "invalid input index: %d\n", index); + goto get_out; + } + + sdinfo = &vpfe->cfg->sub_devs[subdev_index]; + sdinfo->sd = vpfe->sd[subdev_index]; + route = &sdinfo->routes[inp_index]; + if (route && sdinfo->can_route) { + input = route->input; + output = route->output; + if (sdinfo->sd) { + ret = v4l2_subdev_call(sdinfo->sd, video, + s_routing, input, output, 0); + if (ret) { + vpfe_err(vpfe, "s_routing failed\n"); + ret = -EINVAL; + goto get_out; + } + } + + } + + vpfe->current_subdev = sdinfo; + if (sdinfo->sd) + vpfe->v4l2_dev.ctrl_handler = sdinfo->sd->ctrl_handler; + vpfe->current_input = index; + vpfe->std_index = 0; + + /* set the bus/interface parameter for the sub device in ccdc */ + ret = vpfe_ccdc_set_hw_if_params(&vpfe->ccdc, &sdinfo->vpfe_param); + if (ret) + return ret; + + /* set the default image parameters in the device */ + return vpfe_config_image_format(vpfe, + vpfe_standards[vpfe->std_index].std_id); + +get_out: + return ret; +} + +static int vpfe_s_input(struct file *file, void *priv, unsigned int index) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + vpfe_dbg(2, vpfe, + "vpfe_s_input: index: %d\n", index); + + return vpfe_set_input(vpfe, index); +} + +static int vpfe_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + + vpfe_dbg(2, vpfe, "vpfe_querystd\n"); + + sdinfo = vpfe->current_subdev; + if (!(sdinfo->inputs[0].capabilities & V4L2_IN_CAP_STD)) + return -ENODATA; + + /* Call querystd function of decoder device */ + return v4l2_device_call_until_err(&vpfe->v4l2_dev, sdinfo->grp_id, + video, querystd, std_id); +} + +static int vpfe_s_std(struct file *file, void *priv, v4l2_std_id std_id) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + int ret; + + vpfe_dbg(2, vpfe, "vpfe_s_std\n"); + + sdinfo = vpfe->current_subdev; + if (!(sdinfo->inputs[0].capabilities & V4L2_IN_CAP_STD)) + return -ENODATA; + + /* If streaming is started, return error */ + if (vb2_is_busy(&vpfe->buffer_queue)) { + vpfe_err(vpfe, "%s device busy\n", __func__); + ret = -EBUSY; + return ret; + } + + ret = v4l2_device_call_until_err(&vpfe->v4l2_dev, sdinfo->grp_id, + video, s_std, std_id); + if (ret < 0) { + vpfe_err(vpfe, "Failed to set standard\n"); + return ret; + } + ret = vpfe_config_image_format(vpfe, std_id); + + return ret; +} + +static int vpfe_g_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + + vpfe_dbg(2, vpfe, "vpfe_g_std\n"); + + sdinfo = vpfe->current_subdev; + if (sdinfo->inputs[0].capabilities != V4L2_IN_CAP_STD) + return -ENODATA; + + *std_id = vpfe_standards[vpfe->std_index].std_id; + + return 0; +} + +/* + * vpfe_calculate_offsets : This function calculates buffers offset + * for top and bottom field + */ +static void vpfe_calculate_offsets(struct vpfe_device *vpfe) +{ + struct v4l2_rect image_win; + + vpfe_dbg(2, vpfe, "vpfe_calculate_offsets\n"); + + vpfe_ccdc_get_image_window(&vpfe->ccdc, &image_win); + vpfe->field_off = image_win.height * image_win.width; +} + +/* + * vpfe_queue_setup - Callback function for buffer setup. + * @vq: vb2_queue ptr + * @fmt: v4l2 format + * @nbuffers: ptr to number of buffers requested by application + * @nplanes:: contains number of distinct video planes needed to hold a frame + * @sizes[]: contains the size (in bytes) of each plane. + * @alloc_ctxs: ptr to allocation context + * + * This callback function is called when reqbuf() is called to adjust + * the buffer count and buffer size + */ +static int vpfe_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct vpfe_device *vpfe = vb2_get_drv_priv(vq); + + if (fmt && fmt->fmt.pix.sizeimage < vpfe->fmt.fmt.pix.sizeimage) + return -EINVAL; + + if (vq->num_buffers + *nbuffers < 3) + *nbuffers = 3 - vq->num_buffers; + + *nplanes = 1; + sizes[0] = fmt ? fmt->fmt.pix.sizeimage : vpfe->fmt.fmt.pix.sizeimage; + alloc_ctxs[0] = vpfe->alloc_ctx; + + vpfe_dbg(1, vpfe, + "nbuffers=%d, size=%u\n", *nbuffers, sizes[0]); + + /* Calculate field offset */ + vpfe_calculate_offsets(vpfe); + + return 0; +} + +/* + * vpfe_buffer_prepare : callback function for buffer prepare + * @vb: ptr to vb2_buffer + * + * This is the callback function for buffer prepare when vb2_qbuf() + * function is called. The buffer is prepared and user space virtual address + * or user address is converted into physical address + */ +static int vpfe_buffer_prepare(struct vb2_buffer *vb) +{ + struct vpfe_device *vpfe = vb2_get_drv_priv(vb->vb2_queue); + + vb2_set_plane_payload(vb, 0, vpfe->fmt.fmt.pix.sizeimage); + + if (vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) + return -EINVAL; + + vb->v4l2_buf.field = vpfe->fmt.fmt.pix.field; + + return 0; +} + +/* + * vpfe_buffer_queue : Callback function to add buffer to DMA queue + * @vb: ptr to vb2_buffer + */ +static void vpfe_buffer_queue(struct vb2_buffer *vb) +{ + struct vpfe_device *vpfe = vb2_get_drv_priv(vb->vb2_queue); + struct vpfe_cap_buffer *buf = to_vpfe_buffer(vb); + unsigned long flags = 0; + + /* add the buffer to the DMA queue */ + spin_lock_irqsave(&vpfe->dma_queue_lock, flags); + list_add_tail(&buf->list, &vpfe->dma_queue); + spin_unlock_irqrestore(&vpfe->dma_queue_lock, flags); +} + +/* + * vpfe_start_streaming : Starts the DMA engine for streaming + * @vb: ptr to vb2_buffer + * @count: number of buffers + */ +static int vpfe_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vpfe_device *vpfe = vb2_get_drv_priv(vq); + struct vpfe_cap_buffer *buf, *tmp; + struct vpfe_subdev_info *sdinfo; + unsigned long flags; + unsigned long addr; + int ret; + + spin_lock_irqsave(&vpfe->dma_queue_lock, flags); + + vpfe->field = 0; + vpfe->sequence = 0; + + sdinfo = vpfe->current_subdev; + + vpfe_attach_irq(vpfe); + + if (vpfe->ccdc.ccdc_cfg.if_type == VPFE_RAW_BAYER) + vpfe_ccdc_config_raw(&vpfe->ccdc); + else + vpfe_ccdc_config_ycbcr(&vpfe->ccdc); + + /* Get the next frame from the buffer queue */ + vpfe->next_frm = list_entry(vpfe->dma_queue.next, + struct vpfe_cap_buffer, list); + vpfe->cur_frm = vpfe->next_frm; + /* Remove buffer from the buffer queue */ + list_del(&vpfe->cur_frm->list); + spin_unlock_irqrestore(&vpfe->dma_queue_lock, flags); + + addr = vb2_dma_contig_plane_dma_addr(&vpfe->cur_frm->vb, 0); + + vpfe_set_sdr_addr(&vpfe->ccdc, (unsigned long)(addr)); + + vpfe_pcr_enable(&vpfe->ccdc, 1); + + ret = v4l2_subdev_call(sdinfo->sd, video, s_stream, 1); + if (ret < 0) { + vpfe_err(vpfe, "Error in attaching interrupt handle\n"); + goto err; + } + + return 0; + +err: + list_for_each_entry_safe(buf, tmp, &vpfe->dma_queue, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&vpfe->dma_queue_lock, flags); + + return ret; +} + +/* + * vpfe_stop_streaming : Stop the DMA engine + * @vq: ptr to vb2_queue + * + * This callback stops the DMA engine and any remaining buffers + * in the DMA queue are released. + */ +static void vpfe_stop_streaming(struct vb2_queue *vq) +{ + struct vpfe_device *vpfe = vb2_get_drv_priv(vq); + struct vpfe_subdev_info *sdinfo; + unsigned long flags; + int ret; + + vpfe_pcr_enable(&vpfe->ccdc, 0); + + vpfe_detach_irq(vpfe); + + sdinfo = vpfe->current_subdev; + ret = v4l2_subdev_call(sdinfo->sd, video, s_stream, 0); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) + vpfe_dbg(1, vpfe, "stream off failed in subdev\n"); + + /* release all active buffers */ + spin_lock_irqsave(&vpfe->dma_queue_lock, flags); + if (vpfe->cur_frm == vpfe->next_frm) { + vb2_buffer_done(&vpfe->cur_frm->vb, VB2_BUF_STATE_ERROR); + } else { + if (vpfe->cur_frm != NULL) + vb2_buffer_done(&vpfe->cur_frm->vb, + VB2_BUF_STATE_ERROR); + if (vpfe->next_frm != NULL) + vb2_buffer_done(&vpfe->next_frm->vb, + VB2_BUF_STATE_ERROR); + } + + while (!list_empty(&vpfe->dma_queue)) { + vpfe->next_frm = list_entry(vpfe->dma_queue.next, + struct vpfe_cap_buffer, list); + list_del(&vpfe->next_frm->list); + vb2_buffer_done(&vpfe->next_frm->vb, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&vpfe->dma_queue_lock, flags); +} + +static int vpfe_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *crop) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + vpfe_dbg(2, vpfe, "vpfe_cropcap\n"); + + if (vpfe->std_index >= ARRAY_SIZE(vpfe_standards)) + return -EINVAL; + + memset(crop, 0, sizeof(struct v4l2_cropcap)); + + crop->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop->defrect.width = vpfe_standards[vpfe->std_index].width; + crop->bounds.width = crop->defrect.width; + crop->defrect.height = vpfe_standards[vpfe->std_index].height; + crop->bounds.height = crop->defrect.height; + crop->pixelaspect = vpfe_standards[vpfe->std_index].pixelaspect; + + return 0; +} + +static int +vpfe_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + switch (s->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = s->r.top = 0; + s->r.width = vpfe->crop.width; + s->r.height = vpfe->crop.height; + break; + + case V4L2_SEL_TGT_CROP: + s->r = vpfe->crop; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int enclosed_rectangle(struct v4l2_rect *a, struct v4l2_rect *b) +{ + if (a->left < b->left || a->top < b->top) + return 0; + + if (a->left + a->width > b->left + b->width) + return 0; + + if (a->top + a->height > b->top + b->height) + return 0; + + return 1; +} + +static int +vpfe_s_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct v4l2_rect cr = vpfe->crop; + struct v4l2_rect r = s->r; + + /* If streaming is started, return error */ + if (vb2_is_busy(&vpfe->buffer_queue)) { + vpfe_err(vpfe, "%s device busy\n", __func__); + return -EBUSY; + } + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + s->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + v4l_bound_align_image(&r.width, 0, cr.width, 0, + &r.height, 0, cr.height, 0, 0); + + r.left = clamp_t(unsigned int, r.left, 0, cr.width - r.width); + r.top = clamp_t(unsigned int, r.top, 0, cr.height - r.height); + + if (s->flags & V4L2_SEL_FLAG_LE && !enclosed_rectangle(&r, &s->r)) + return -ERANGE; + + if (s->flags & V4L2_SEL_FLAG_GE && !enclosed_rectangle(&s->r, &r)) + return -ERANGE; + + s->r = vpfe->crop = r; + + vpfe_ccdc_set_image_window(&vpfe->ccdc, &r, vpfe->bpp); + vpfe->fmt.fmt.pix.width = r.width; + vpfe->fmt.fmt.pix.height = r.height; + vpfe->fmt.fmt.pix.bytesperline = vpfe_ccdc_get_line_length(&vpfe->ccdc); + vpfe->fmt.fmt.pix.sizeimage = vpfe->fmt.fmt.pix.bytesperline * + vpfe->fmt.fmt.pix.height; + + vpfe_dbg(1, vpfe, "cropped (%d,%d)/%dx%d of %dx%d\n", + r.left, r.top, r.width, r.height, cr.width, cr.height); + + return 0; +} + +static long vpfe_ioctl_default(struct file *file, void *priv, + bool valid_prio, unsigned int cmd, void *param) +{ + struct vpfe_device *vpfe = video_drvdata(file); + int ret; + + vpfe_dbg(2, vpfe, "vpfe_ioctl_default\n"); + + if (!valid_prio) { + vpfe_err(vpfe, "%s device busy\n", __func__); + return -EBUSY; + } + + /* If streaming is started, return error */ + if (vb2_is_busy(&vpfe->buffer_queue)) { + vpfe_err(vpfe, "%s device busy\n", __func__); + return -EBUSY; + } + + switch (cmd) { + case VIDIOC_AM437X_CCDC_CFG: + ret = vpfe_ccdc_set_params(&vpfe->ccdc, param); + if (ret) { + vpfe_dbg(2, vpfe, + "Error setting parameters in CCDC\n"); + return ret; + } + ret = vpfe_get_ccdc_image_format(vpfe, + &vpfe->fmt); + if (ret < 0) { + vpfe_dbg(2, vpfe, + "Invalid image format at CCDC\n"); + return ret; + } + break; + + default: + ret = -ENOTTY; + break; + } + + return ret; +} + +static const struct vb2_ops vpfe_video_qops = { + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .queue_setup = vpfe_queue_setup, + .buf_prepare = vpfe_buffer_prepare, + .buf_queue = vpfe_buffer_queue, + .start_streaming = vpfe_start_streaming, + .stop_streaming = vpfe_stop_streaming, +}; + +/* vpfe capture driver file operations */ +static const struct v4l2_file_operations vpfe_fops = { + .owner = THIS_MODULE, + .open = vpfe_open, + .release = vpfe_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +/* vpfe capture ioctl operations */ +static const struct v4l2_ioctl_ops vpfe_ioctl_ops = { + .vidioc_querycap = vpfe_querycap, + .vidioc_enum_fmt_vid_cap = vpfe_enum_fmt, + .vidioc_g_fmt_vid_cap = vpfe_g_fmt, + .vidioc_s_fmt_vid_cap = vpfe_s_fmt, + .vidioc_try_fmt_vid_cap = vpfe_try_fmt, + + .vidioc_enum_framesizes = vpfe_enum_size, + + .vidioc_enum_input = vpfe_enum_input, + .vidioc_g_input = vpfe_g_input, + .vidioc_s_input = vpfe_s_input, + + .vidioc_querystd = vpfe_querystd, + .vidioc_s_std = vpfe_s_std, + .vidioc_g_std = vpfe_g_std, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_cropcap = vpfe_cropcap, + .vidioc_g_selection = vpfe_g_selection, + .vidioc_s_selection = vpfe_s_selection, + + .vidioc_default = vpfe_ioctl_default, +}; + +static int +vpfe_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct vpfe_device *vpfe = container_of(notifier->v4l2_dev, + struct vpfe_device, v4l2_dev); + struct v4l2_subdev_mbus_code_enum mbus_code; + struct vpfe_subdev_info *sdinfo; + bool found = false; + int i, j; + + vpfe_dbg(1, vpfe, "vpfe_async_bound\n"); + + for (i = 0; i < ARRAY_SIZE(vpfe->cfg->asd); i++) { + sdinfo = &vpfe->cfg->sub_devs[i]; + + if (!strcmp(sdinfo->name, subdev->name)) { + vpfe->sd[i] = subdev; + vpfe_info(vpfe, + "v4l2 sub device %s registered\n", + subdev->name); + vpfe->sd[i]->grp_id = + sdinfo->grp_id; + /* update tvnorms from the sub devices */ + for (j = 0; j < 1; j++) + vpfe->video_dev->tvnorms |= + sdinfo->inputs[j].std; + + found = true; + break; + } + } + + if (!found) { + vpfe_info(vpfe, "sub device (%s) not matched\n", subdev->name); + return -EINVAL; + } + + /* setup the supported formats & indexes */ + for (j = 0, i = 0; ; ++j) { + struct vpfe_fmt *fmt; + int ret; + + memset(&mbus_code, 0, sizeof(mbus_code)); + mbus_code.index = j; + ret = v4l2_subdev_call(subdev, pad, enum_mbus_code, + NULL, &mbus_code); + if (ret) + break; + + fmt = find_format_by_code(mbus_code.code); + if (!fmt) + continue; + + fmt->supported = true; + fmt->index = i++; + } + + return 0; +} + +static int vpfe_probe_complete(struct vpfe_device *vpfe) +{ + struct video_device *vdev; + struct vb2_queue *q; + int err; + + spin_lock_init(&vpfe->dma_queue_lock); + mutex_init(&vpfe->lock); + + vpfe->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* set first sub device as current one */ + vpfe->current_subdev = &vpfe->cfg->sub_devs[0]; + vpfe->v4l2_dev.ctrl_handler = vpfe->sd[0]->ctrl_handler; + + err = vpfe_set_input(vpfe, 0); + if (err) + goto probe_out; + + /* Initialize videobuf2 queue as per the buffer type */ + vpfe->alloc_ctx = vb2_dma_contig_init_ctx(vpfe->pdev); + if (IS_ERR(vpfe->alloc_ctx)) { + vpfe_err(vpfe, "Failed to get the context\n"); + err = PTR_ERR(vpfe->alloc_ctx); + goto probe_out; + } + + q = &vpfe->buffer_queue; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + q->drv_priv = vpfe; + q->ops = &vpfe_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct vpfe_cap_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &vpfe->lock; + q->min_buffers_needed = 1; + + err = vb2_queue_init(q); + if (err) { + vpfe_err(vpfe, "vb2_queue_init() failed\n"); + vb2_dma_contig_cleanup_ctx(vpfe->alloc_ctx); + goto probe_out; + } + + INIT_LIST_HEAD(&vpfe->dma_queue); + + vdev = vpfe->video_dev; + strlcpy(vdev->name, VPFE_MODULE_NAME, sizeof(vdev->name)); + vdev->release = video_device_release; + vdev->fops = &vpfe_fops; + vdev->ioctl_ops = &vpfe_ioctl_ops; + vdev->v4l2_dev = &vpfe->v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + vdev->queue = q; + vdev->lock = &vpfe->lock; + video_set_drvdata(vdev, vpfe); + err = video_register_device(vpfe->video_dev, VFL_TYPE_GRABBER, -1); + if (err) { + vpfe_err(vpfe, + "Unable to register video device.\n"); + goto probe_out; + } + + return 0; + +probe_out: + v4l2_device_unregister(&vpfe->v4l2_dev); + return err; +} + +static int vpfe_async_complete(struct v4l2_async_notifier *notifier) +{ + struct vpfe_device *vpfe = container_of(notifier->v4l2_dev, + struct vpfe_device, v4l2_dev); + + return vpfe_probe_complete(vpfe); +} + +static struct vpfe_config * +vpfe_get_pdata(struct platform_device *pdev) +{ + struct device_node *endpoint = NULL, *rem = NULL; + struct v4l2_of_endpoint bus_cfg; + struct vpfe_subdev_info *sdinfo; + struct vpfe_config *pdata; + unsigned int flags; + unsigned int i; + int err; + + dev_dbg(&pdev->dev, "vpfe_get_pdata\n"); + + if (!IS_ENABLED(CONFIG_OF) || !pdev->dev.of_node) + return pdev->dev.platform_data; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + for (i = 0; ; i++) { + endpoint = of_graph_get_next_endpoint(pdev->dev.of_node, + endpoint); + if (!endpoint) + break; + + sdinfo = &pdata->sub_devs[i]; + sdinfo->grp_id = 0; + + /* we only support camera */ + sdinfo->inputs[0].index = i; + strcpy(sdinfo->inputs[0].name, "Camera"); + sdinfo->inputs[0].type = V4L2_INPUT_TYPE_CAMERA; + sdinfo->inputs[0].std = V4L2_STD_ALL; + sdinfo->inputs[0].capabilities = V4L2_IN_CAP_STD; + + sdinfo->can_route = 0; + sdinfo->routes = NULL; + + of_property_read_u32(endpoint, "ti,am437x-vpfe-interface", + &sdinfo->vpfe_param.if_type); + if (sdinfo->vpfe_param.if_type < 0 || + sdinfo->vpfe_param.if_type > 4) { + sdinfo->vpfe_param.if_type = VPFE_RAW_BAYER; + } + + err = v4l2_of_parse_endpoint(endpoint, &bus_cfg); + if (err) { + dev_err(&pdev->dev, "Could not parse the endpoint\n"); + goto done; + } + + sdinfo->vpfe_param.bus_width = bus_cfg.bus.parallel.bus_width; + + if (sdinfo->vpfe_param.bus_width < 8 || + sdinfo->vpfe_param.bus_width > 16) { + dev_err(&pdev->dev, "Invalid bus width.\n"); + goto done; + } + + flags = bus_cfg.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + sdinfo->vpfe_param.hdpol = 1; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + sdinfo->vpfe_param.vdpol = 1; + + rem = of_graph_get_remote_port_parent(endpoint); + if (!rem) { + dev_err(&pdev->dev, "Remote device at %s not found\n", + endpoint->full_name); + goto done; + } + + strncpy(sdinfo->name, rem->name, sizeof(sdinfo->name)); + + pdata->asd[i] = devm_kzalloc(&pdev->dev, + sizeof(struct v4l2_async_subdev), + GFP_KERNEL); + pdata->asd[i]->match_type = V4L2_ASYNC_MATCH_OF; + pdata->asd[i]->match.of.node = rem; + of_node_put(endpoint); + of_node_put(rem); + } + + of_node_put(endpoint); + return pdata; + +done: + of_node_put(endpoint); + of_node_put(rem); + return NULL; +} + +/* + * vpfe_probe : This function creates device entries by register + * itself to the V4L2 driver and initializes fields of each + * device objects + */ +static int vpfe_probe(struct platform_device *pdev) +{ + struct vpfe_config *vpfe_cfg = vpfe_get_pdata(pdev); + struct vpfe_device *vpfe; + struct vpfe_ccdc *ccdc; + struct resource *res; + int ret; + + if (!vpfe_cfg) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } + + vpfe = devm_kzalloc(&pdev->dev, sizeof(*vpfe), GFP_KERNEL); + if (!vpfe) + return -ENOMEM; + + vpfe->pdev = &pdev->dev; + vpfe->cfg = vpfe_cfg; + ccdc = &vpfe->ccdc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ccdc->ccdc_cfg.base_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ccdc->ccdc_cfg.base_addr)) + return PTR_ERR(ccdc->ccdc_cfg.base_addr); + + vpfe->irq = platform_get_irq(pdev, 0); + if (vpfe->irq <= 0) { + dev_err(&pdev->dev, "No IRQ resource\n"); + return -ENODEV; + } + + ret = devm_request_irq(vpfe->pdev, vpfe->irq, vpfe_isr, 0, + "vpfe_capture0", vpfe); + if (ret) { + dev_err(&pdev->dev, "Unable to request interrupt\n"); + return -EINVAL; + } + + vpfe->video_dev = video_device_alloc(); + if (!vpfe->video_dev) { + dev_err(&pdev->dev, "Unable to allocate video device\n"); + return -ENOMEM; + } + + ret = v4l2_device_register(&pdev->dev, &vpfe->v4l2_dev); + if (ret) { + vpfe_err(vpfe, + "Unable to register v4l2 device.\n"); + goto probe_out_video_release; + } + + /* set the driver data in platform device */ + platform_set_drvdata(pdev, vpfe); + /* Enabling module functional clock */ + pm_runtime_enable(&pdev->dev); + + /* for now just enable it here instead of waiting for the open */ + pm_runtime_get_sync(&pdev->dev); + + vpfe_ccdc_config_defaults(ccdc); + + pm_runtime_put_sync(&pdev->dev); + + vpfe->sd = devm_kzalloc(&pdev->dev, sizeof(struct v4l2_subdev *) * + ARRAY_SIZE(vpfe->cfg->asd), GFP_KERNEL); + if (!vpfe->sd) { + ret = -ENOMEM; + goto probe_out_v4l2_unregister; + } + + vpfe->notifier.subdevs = vpfe->cfg->asd; + vpfe->notifier.num_subdevs = ARRAY_SIZE(vpfe->cfg->asd); + vpfe->notifier.bound = vpfe_async_bound; + vpfe->notifier.complete = vpfe_async_complete; + ret = v4l2_async_notifier_register(&vpfe->v4l2_dev, + &vpfe->notifier); + if (ret) { + vpfe_err(vpfe, "Error registering async notifier\n"); + ret = -EINVAL; + goto probe_out_v4l2_unregister; + } + + return 0; + +probe_out_v4l2_unregister: + v4l2_device_unregister(&vpfe->v4l2_dev); +probe_out_video_release: + if (!video_is_registered(vpfe->video_dev)) + video_device_release(vpfe->video_dev); + return ret; +} + +/* + * vpfe_remove : It un-register device from V4L2 driver + */ +static int vpfe_remove(struct platform_device *pdev) +{ + struct vpfe_device *vpfe = platform_get_drvdata(pdev); + + vpfe_dbg(2, vpfe, "vpfe_remove\n"); + + pm_runtime_disable(&pdev->dev); + + v4l2_async_notifier_unregister(&vpfe->notifier); + v4l2_device_unregister(&vpfe->v4l2_dev); + video_unregister_device(vpfe->video_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static void vpfe_save_context(struct vpfe_ccdc *ccdc) +{ + ccdc->ccdc_ctx[VPFE_PCR >> 2] = vpfe_reg_read(ccdc, VPFE_PCR); + ccdc->ccdc_ctx[VPFE_SYNMODE >> 2] = vpfe_reg_read(ccdc, VPFE_SYNMODE); + ccdc->ccdc_ctx[VPFE_SDOFST >> 2] = vpfe_reg_read(ccdc, VPFE_SDOFST); + ccdc->ccdc_ctx[VPFE_SDR_ADDR >> 2] = vpfe_reg_read(ccdc, VPFE_SDR_ADDR); + ccdc->ccdc_ctx[VPFE_CLAMP >> 2] = vpfe_reg_read(ccdc, VPFE_CLAMP); + ccdc->ccdc_ctx[VPFE_DCSUB >> 2] = vpfe_reg_read(ccdc, VPFE_DCSUB); + ccdc->ccdc_ctx[VPFE_COLPTN >> 2] = vpfe_reg_read(ccdc, VPFE_COLPTN); + ccdc->ccdc_ctx[VPFE_BLKCMP >> 2] = vpfe_reg_read(ccdc, VPFE_BLKCMP); + ccdc->ccdc_ctx[VPFE_VDINT >> 2] = vpfe_reg_read(ccdc, VPFE_VDINT); + ccdc->ccdc_ctx[VPFE_ALAW >> 2] = vpfe_reg_read(ccdc, VPFE_ALAW); + ccdc->ccdc_ctx[VPFE_REC656IF >> 2] = vpfe_reg_read(ccdc, VPFE_REC656IF); + ccdc->ccdc_ctx[VPFE_CCDCFG >> 2] = vpfe_reg_read(ccdc, VPFE_CCDCFG); + ccdc->ccdc_ctx[VPFE_CULLING >> 2] = vpfe_reg_read(ccdc, VPFE_CULLING); + ccdc->ccdc_ctx[VPFE_HD_VD_WID >> 2] = vpfe_reg_read(ccdc, + VPFE_HD_VD_WID); + ccdc->ccdc_ctx[VPFE_PIX_LINES >> 2] = vpfe_reg_read(ccdc, + VPFE_PIX_LINES); + ccdc->ccdc_ctx[VPFE_HORZ_INFO >> 2] = vpfe_reg_read(ccdc, + VPFE_HORZ_INFO); + ccdc->ccdc_ctx[VPFE_VERT_START >> 2] = vpfe_reg_read(ccdc, + VPFE_VERT_START); + ccdc->ccdc_ctx[VPFE_VERT_LINES >> 2] = vpfe_reg_read(ccdc, + VPFE_VERT_LINES); + ccdc->ccdc_ctx[VPFE_HSIZE_OFF >> 2] = vpfe_reg_read(ccdc, + VPFE_HSIZE_OFF); +} + +static int vpfe_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vpfe_device *vpfe = platform_get_drvdata(pdev); + struct vpfe_ccdc *ccdc = &vpfe->ccdc; + + /* if streaming has not started we don't care */ + if (!vb2_start_streaming_called(&vpfe->buffer_queue)) + return 0; + + pm_runtime_get_sync(dev); + vpfe_config_enable(ccdc, 1); + + /* Save VPFE context */ + vpfe_save_context(ccdc); + + /* Disable CCDC */ + vpfe_pcr_enable(ccdc, 0); + vpfe_config_enable(ccdc, 0); + + /* Disable both master and slave clock */ + pm_runtime_put_sync(dev); + + /* Select sleep pin state */ + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static void vpfe_restore_context(struct vpfe_ccdc *ccdc) +{ + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_SYNMODE >> 2], VPFE_SYNMODE); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_CULLING >> 2], VPFE_CULLING); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_SDOFST >> 2], VPFE_SDOFST); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_SDR_ADDR >> 2], VPFE_SDR_ADDR); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_CLAMP >> 2], VPFE_CLAMP); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_DCSUB >> 2], VPFE_DCSUB); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_COLPTN >> 2], VPFE_COLPTN); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_BLKCMP >> 2], VPFE_BLKCMP); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_VDINT >> 2], VPFE_VDINT); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_ALAW >> 2], VPFE_ALAW); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_REC656IF >> 2], VPFE_REC656IF); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_CCDCFG >> 2], VPFE_CCDCFG); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_PCR >> 2], VPFE_PCR); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_HD_VD_WID >> 2], + VPFE_HD_VD_WID); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_PIX_LINES >> 2], + VPFE_PIX_LINES); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_HORZ_INFO >> 2], + VPFE_HORZ_INFO); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_VERT_START >> 2], + VPFE_VERT_START); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_VERT_LINES >> 2], + VPFE_VERT_LINES); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_HSIZE_OFF >> 2], + VPFE_HSIZE_OFF); +} + +static int vpfe_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vpfe_device *vpfe = platform_get_drvdata(pdev); + struct vpfe_ccdc *ccdc = &vpfe->ccdc; + + /* if streaming has not started we don't care */ + if (!vb2_start_streaming_called(&vpfe->buffer_queue)) + return 0; + + /* Enable both master and slave clock */ + pm_runtime_get_sync(dev); + vpfe_config_enable(ccdc, 1); + + /* Restore VPFE context */ + vpfe_restore_context(ccdc); + + vpfe_config_enable(ccdc, 0); + pm_runtime_put_sync(dev); + + /* Select default pin state */ + pinctrl_pm_select_default_state(dev); + + return 0; +} + +#endif + +static SIMPLE_DEV_PM_OPS(vpfe_pm_ops, vpfe_suspend, vpfe_resume); + +static const struct of_device_id vpfe_of_match[] = { + { .compatible = "ti,am437x-vpfe", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, vpfe_of_match); + +static struct platform_driver vpfe_driver = { + .probe = vpfe_probe, + .remove = vpfe_remove, + .driver = { + .name = VPFE_MODULE_NAME, + .owner = THIS_MODULE, + .pm = &vpfe_pm_ops, + .of_match_table = of_match_ptr(vpfe_of_match), + }, +}; + +module_platform_driver(vpfe_driver); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("TI AM437x VPFE driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(VPFE_VERSION); diff --git a/drivers/media/platform/am437x/am437x-vpfe.h b/drivers/media/platform/am437x/am437x-vpfe.h new file mode 100644 index 000000000000..0f557352313d --- /dev/null +++ b/drivers/media/platform/am437x/am437x-vpfe.h @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2013 - 2014 Texas Instruments, Inc. + * + * Benoit Parrot + * Lad, Prabhakar + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef AM437X_VPFE_H +#define AM437X_VPFE_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "am437x-vpfe_regs.h" + +enum vpfe_pin_pol { + VPFE_PINPOL_POSITIVE = 0, + VPFE_PINPOL_NEGATIVE, +}; + +enum vpfe_hw_if_type { + /* Raw Bayer */ + VPFE_RAW_BAYER = 0, + /* BT656 - 8 bit */ + VPFE_BT656, + /* BT656 - 10 bit */ + VPFE_BT656_10BIT, + /* YCbCr - 8 bit with external sync */ + VPFE_YCBCR_SYNC_8, + /* YCbCr - 16 bit with external sync */ + VPFE_YCBCR_SYNC_16, +}; + +/* interface description */ +struct vpfe_hw_if_param { + enum vpfe_hw_if_type if_type; + enum vpfe_pin_pol hdpol; + enum vpfe_pin_pol vdpol; + unsigned int bus_width; +}; + +#define VPFE_MAX_SUBDEV 1 +#define VPFE_MAX_INPUTS 1 + +struct vpfe_pixel_format { + struct v4l2_fmtdesc fmtdesc; + /* bytes per pixel */ + int bpp; +}; + +struct vpfe_std_info { + int active_pixels; + int active_lines; + /* current frame format */ + int frame_format; +}; + +struct vpfe_route { + u32 input; + u32 output; +}; + +struct vpfe_subdev_info { + char name[32]; + /* Sub device group id */ + int grp_id; + /* inputs available at the sub device */ + struct v4l2_input inputs[VPFE_MAX_INPUTS]; + /* Sub dev routing information for each input */ + struct vpfe_route *routes; + /* check if sub dev supports routing */ + int can_route; + /* ccdc bus/interface configuration */ + struct vpfe_hw_if_param vpfe_param; + struct v4l2_subdev *sd; +}; + +struct vpfe_config { + /* information about each subdev */ + struct vpfe_subdev_info sub_devs[VPFE_MAX_SUBDEV]; + /* Flat array, arranged in groups */ + struct v4l2_async_subdev *asd[VPFE_MAX_SUBDEV]; +}; + +struct vpfe_cap_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +enum ccdc_pixfmt { + CCDC_PIXFMT_RAW = 0, + CCDC_PIXFMT_YCBCR_16BIT, + CCDC_PIXFMT_YCBCR_8BIT, +}; + +enum ccdc_frmfmt { + CCDC_FRMFMT_PROGRESSIVE = 0, + CCDC_FRMFMT_INTERLACED, +}; + +/* PIXEL ORDER IN MEMORY from LSB to MSB */ +/* only applicable for 8-bit input mode */ +enum ccdc_pixorder { + CCDC_PIXORDER_YCBYCR, + CCDC_PIXORDER_CBYCRY, +}; + +enum ccdc_buftype { + CCDC_BUFTYPE_FLD_INTERLEAVED, + CCDC_BUFTYPE_FLD_SEPARATED +}; + + +/* returns the highest bit used for the gamma */ +static inline u8 ccdc_gamma_width_max_bit(enum vpfe_ccdc_gamma_width width) +{ + return 15 - width; +} + +/* returns the highest bit used for this data size */ +static inline u8 ccdc_data_size_max_bit(enum vpfe_ccdc_data_size sz) +{ + return sz == VPFE_CCDC_DATA_8BITS ? 7 : 15 - sz; +} + +/* Structure for CCDC configuration parameters for raw capture mode */ +struct ccdc_params_raw { + /* pixel format */ + enum ccdc_pixfmt pix_fmt; + /* progressive or interlaced frame */ + enum ccdc_frmfmt frm_fmt; + struct v4l2_rect win; + /* Current Format Bytes Per Pixels */ + unsigned int bytesperpixel; + /* Current Format Bytes per Lines + * (Aligned to 32 bytes) used for HORZ_INFO + */ + unsigned int bytesperline; + /* field id polarity */ + enum vpfe_pin_pol fid_pol; + /* vertical sync polarity */ + enum vpfe_pin_pol vd_pol; + /* horizontal sync polarity */ + enum vpfe_pin_pol hd_pol; + /* interleaved or separated fields */ + enum ccdc_buftype buf_type; + /* + * enable to store the image in inverse + * order in memory(bottom to top) + */ + unsigned char image_invert_enable; + /* configurable parameters */ + struct vpfe_ccdc_config_params_raw config_params; +}; + +struct ccdc_params_ycbcr { + /* pixel format */ + enum ccdc_pixfmt pix_fmt; + /* progressive or interlaced frame */ + enum ccdc_frmfmt frm_fmt; + struct v4l2_rect win; + /* Current Format Bytes Per Pixels */ + unsigned int bytesperpixel; + /* Current Format Bytes per Lines + * (Aligned to 32 bytes) used for HORZ_INFO + */ + unsigned int bytesperline; + /* field id polarity */ + enum vpfe_pin_pol fid_pol; + /* vertical sync polarity */ + enum vpfe_pin_pol vd_pol; + /* horizontal sync polarity */ + enum vpfe_pin_pol hd_pol; + /* enable BT.656 embedded sync mode */ + int bt656_enable; + /* cb:y:cr:y or y:cb:y:cr in memory */ + enum ccdc_pixorder pix_order; + /* interleaved or separated fields */ + enum ccdc_buftype buf_type; +}; + +/* + * CCDC operational configuration + */ +struct ccdc_config { + /* CCDC interface type */ + enum vpfe_hw_if_type if_type; + /* Raw Bayer configuration */ + struct ccdc_params_raw bayer; + /* YCbCr configuration */ + struct ccdc_params_ycbcr ycbcr; + /* ccdc base address */ + void __iomem *base_addr; +}; + +struct vpfe_ccdc { + struct ccdc_config ccdc_cfg; + u32 ccdc_ctx[VPFE_REG_END / sizeof(u32)]; +}; + +struct vpfe_device { + /* V4l2 specific parameters */ + /* Identifies video device for this channel */ + struct video_device *video_dev; + /* sub devices */ + struct v4l2_subdev **sd; + /* vpfe cfg */ + struct vpfe_config *cfg; + /* V4l2 device */ + struct v4l2_device v4l2_dev; + /* parent device */ + struct device *pdev; + /* subdevice async Notifier */ + struct v4l2_async_notifier notifier; + /* Indicates id of the field which is being displayed */ + unsigned field; + unsigned sequence; + /* current interface type */ + struct vpfe_hw_if_param vpfe_if_params; + /* ptr to currently selected sub device */ + struct vpfe_subdev_info *current_subdev; + /* current input at the sub device */ + int current_input; + /* Keeps track of the information about the standard */ + struct vpfe_std_info std_info; + /* std index into std table */ + int std_index; + /* IRQs used when CCDC output to SDRAM */ + unsigned int irq; + /* Pointer pointing to current v4l2_buffer */ + struct vpfe_cap_buffer *cur_frm; + /* Pointer pointing to next v4l2_buffer */ + struct vpfe_cap_buffer *next_frm; + /* Used to store pixel format */ + struct v4l2_format fmt; + /* Used to store current bytes per pixel based on current format */ + unsigned int bpp; + /* + * used when IMP is chained to store the crop window which + * is different from the image window + */ + struct v4l2_rect crop; + /* Buffer queue used in video-buf */ + struct vb2_queue buffer_queue; + /* Allocator-specific contexts for each plane */ + struct vb2_alloc_ctx *alloc_ctx; + /* Queue of filled frames */ + struct list_head dma_queue; + /* IRQ lock for DMA queue */ + spinlock_t dma_queue_lock; + /* lock used to access this structure */ + struct mutex lock; + /* + * offset where second field starts from the starting of the + * buffer for field separated YCbCr formats + */ + u32 field_off; + struct vpfe_ccdc ccdc; +}; + +#endif /* AM437X_VPFE_H */ diff --git a/drivers/media/platform/am437x/am437x-vpfe_regs.h b/drivers/media/platform/am437x/am437x-vpfe_regs.h new file mode 100644 index 000000000000..4a0ed29723e8 --- /dev/null +++ b/drivers/media/platform/am437x/am437x-vpfe_regs.h @@ -0,0 +1,140 @@ +/* + * TI AM437x Image Sensor Interface Registers + * + * Copyright (C) 2013 - 2014 Texas Instruments, Inc. + * + * Benoit Parrot + * Lad, Prabhakar + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef AM437X_VPFE_REGS_H +#define AM437X_VPFE_REGS_H + +/* VPFE module register offset */ +#define VPFE_REVISION 0x0 +#define VPFE_PCR 0x4 +#define VPFE_SYNMODE 0x8 +#define VPFE_HD_VD_WID 0xc +#define VPFE_PIX_LINES 0x10 +#define VPFE_HORZ_INFO 0x14 +#define VPFE_VERT_START 0x18 +#define VPFE_VERT_LINES 0x1c +#define VPFE_CULLING 0x20 +#define VPFE_HSIZE_OFF 0x24 +#define VPFE_SDOFST 0x28 +#define VPFE_SDR_ADDR 0x2c +#define VPFE_CLAMP 0x30 +#define VPFE_DCSUB 0x34 +#define VPFE_COLPTN 0x38 +#define VPFE_BLKCMP 0x3c +#define VPFE_VDINT 0x48 +#define VPFE_ALAW 0x4c +#define VPFE_REC656IF 0x50 +#define VPFE_CCDCFG 0x54 +#define VPFE_DMA_CNTL 0x98 +#define VPFE_SYSCONFIG 0x104 +#define VPFE_CONFIG 0x108 +#define VPFE_IRQ_EOI 0x110 +#define VPFE_IRQ_STS_RAW 0x114 +#define VPFE_IRQ_STS 0x118 +#define VPFE_IRQ_EN_SET 0x11c +#define VPFE_IRQ_EN_CLR 0x120 +#define VPFE_REG_END 0x124 + +/* Define bit fields within selected registers */ +#define VPFE_FID_POL_MASK 1 +#define VPFE_FID_POL_SHIFT 4 +#define VPFE_HD_POL_MASK 1 +#define VPFE_HD_POL_SHIFT 3 +#define VPFE_VD_POL_MASK 1 +#define VPFE_VD_POL_SHIFT 2 +#define VPFE_HSIZE_OFF_MASK 0xffffffe0 +#define VPFE_32BYTE_ALIGN_VAL 31 +#define VPFE_FRM_FMT_MASK 0x1 +#define VPFE_FRM_FMT_SHIFT 7 +#define VPFE_DATA_SZ_MASK 7 +#define VPFE_DATA_SZ_SHIFT 8 +#define VPFE_PIX_FMT_MASK 3 +#define VPFE_PIX_FMT_SHIFT 12 +#define VPFE_VP2SDR_DISABLE 0xfffbffff +#define VPFE_WEN_ENABLE (1 << 17) +#define VPFE_SDR2RSZ_DISABLE 0xfff7ffff +#define VPFE_VDHDEN_ENABLE (1 << 16) +#define VPFE_LPF_ENABLE (1 << 14) +#define VPFE_ALAW_ENABLE (1 << 3) +#define VPFE_ALAW_GAMMA_WD_MASK 7 +#define VPFE_BLK_CLAMP_ENABLE (1 << 31) +#define VPFE_BLK_SGAIN_MASK 0x1f +#define VPFE_BLK_ST_PXL_MASK 0x7fff +#define VPFE_BLK_ST_PXL_SHIFT 10 +#define VPFE_BLK_SAMPLE_LN_MASK 7 +#define VPFE_BLK_SAMPLE_LN_SHIFT 28 +#define VPFE_BLK_SAMPLE_LINE_MASK 7 +#define VPFE_BLK_SAMPLE_LINE_SHIFT 25 +#define VPFE_BLK_DC_SUB_MASK 0x03fff +#define VPFE_BLK_COMP_MASK 0xff +#define VPFE_BLK_COMP_GB_COMP_SHIFT 8 +#define VPFE_BLK_COMP_GR_COMP_SHIFT 16 +#define VPFE_BLK_COMP_R_COMP_SHIFT 24 +#define VPFE_LATCH_ON_VSYNC_DISABLE (1 << 15) +#define VPFE_DATA_PACK_ENABLE (1 << 11) +#define VPFE_HORZ_INFO_SPH_SHIFT 16 +#define VPFE_VERT_START_SLV0_SHIFT 16 +#define VPFE_VDINT_VDINT0_SHIFT 16 +#define VPFE_VDINT_VDINT1_MASK 0xffff +#define VPFE_PPC_RAW 1 +#define VPFE_DCSUB_DEFAULT_VAL 0 +#define VPFE_CLAMP_DEFAULT_VAL 0 +#define VPFE_COLPTN_VAL 0xbb11bb11 +#define VPFE_TWO_BYTES_PER_PIXEL 2 +#define VPFE_INTERLACED_IMAGE_INVERT 0x4b6d +#define VPFE_INTERLACED_NO_IMAGE_INVERT 0x0249 +#define VPFE_PROGRESSIVE_IMAGE_INVERT 0x4000 +#define VPFE_PROGRESSIVE_NO_IMAGE_INVERT 0 +#define VPFE_INTERLACED_HEIGHT_SHIFT 1 +#define VPFE_SYN_MODE_INPMOD_SHIFT 12 +#define VPFE_SYN_MODE_INPMOD_MASK 3 +#define VPFE_SYN_MODE_8BITS (7 << 8) +#define VPFE_SYN_MODE_10BITS (6 << 8) +#define VPFE_SYN_MODE_11BITS (5 << 8) +#define VPFE_SYN_MODE_12BITS (4 << 8) +#define VPFE_SYN_MODE_13BITS (3 << 8) +#define VPFE_SYN_MODE_14BITS (2 << 8) +#define VPFE_SYN_MODE_15BITS (1 << 8) +#define VPFE_SYN_MODE_16BITS (0 << 8) +#define VPFE_SYN_FLDMODE_MASK 1 +#define VPFE_SYN_FLDMODE_SHIFT 7 +#define VPFE_REC656IF_BT656_EN 3 +#define VPFE_SYN_MODE_VD_POL_NEGATIVE (1 << 2) +#define VPFE_CCDCFG_Y8POS_SHIFT 11 +#define VPFE_CCDCFG_BW656_10BIT (1 << 5) +#define VPFE_SDOFST_FIELD_INTERLEAVED 0x249 +#define VPFE_NO_CULLING 0xffff00ff +#define VPFE_VDINT0 (1 << 0) +#define VPFE_VDINT1 (1 << 1) +#define VPFE_VDINT2 (1 << 2) +#define VPFE_DMA_CNTL_OVERFLOW (1 << 31) + +#define VPFE_CONFIG_PCLK_INV_SHIFT 0 +#define VPFE_CONFIG_PCLK_INV_MASK 1 +#define VPFE_CONFIG_PCLK_INV_NOT_INV 0 +#define VPFE_CONFIG_PCLK_INV_INV 1 +#define VPFE_CONFIG_EN_SHIFT 1 +#define VPFE_CONFIG_EN_MASK 2 +#define VPFE_CONFIG_EN_DISABLE 0 +#define VPFE_CONFIG_EN_ENABLE 1 +#define VPFE_CONFIG_ST_SHIFT 2 +#define VPFE_CONFIG_ST_MASK 4 +#define VPFE_CONFIG_ST_OCP_ACTIVE 0 +#define VPFE_CONFIG_ST_OCP_STANDBY 1 + +#endif /* AM437X_VPFE_REGS_H */ diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index 00b100023c47..9312d5806541 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -35,6 +35,7 @@ header-y += adfs_fs.h header-y += affs_hardblocks.h header-y += agpgart.h header-y += aio_abi.h +header-y += am437x-vpfe.h header-y += apm_bios.h header-y += arcfb.h header-y += atalk.h diff --git a/include/uapi/linux/am437x-vpfe.h b/include/uapi/linux/am437x-vpfe.h new file mode 100644 index 000000000000..9b03033f9cd6 --- /dev/null +++ b/include/uapi/linux/am437x-vpfe.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2013 - 2014 Texas Instruments, Inc. + * + * Benoit Parrot + * Lad, Prabhakar + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef AM437X_VPFE_USER_H +#define AM437X_VPFE_USER_H + +enum vpfe_ccdc_data_size { + VPFE_CCDC_DATA_16BITS = 0, + VPFE_CCDC_DATA_15BITS, + VPFE_CCDC_DATA_14BITS, + VPFE_CCDC_DATA_13BITS, + VPFE_CCDC_DATA_12BITS, + VPFE_CCDC_DATA_11BITS, + VPFE_CCDC_DATA_10BITS, + VPFE_CCDC_DATA_8BITS, +}; + +/* enum for No of pixel per line to be avg. in Black Clamping*/ +enum vpfe_ccdc_sample_length { + VPFE_CCDC_SAMPLE_1PIXELS = 0, + VPFE_CCDC_SAMPLE_2PIXELS, + VPFE_CCDC_SAMPLE_4PIXELS, + VPFE_CCDC_SAMPLE_8PIXELS, + VPFE_CCDC_SAMPLE_16PIXELS, +}; + +/* enum for No of lines in Black Clamping */ +enum vpfe_ccdc_sample_line { + VPFE_CCDC_SAMPLE_1LINES = 0, + VPFE_CCDC_SAMPLE_2LINES, + VPFE_CCDC_SAMPLE_4LINES, + VPFE_CCDC_SAMPLE_8LINES, + VPFE_CCDC_SAMPLE_16LINES, +}; + +/* enum for Alaw gamma width */ +enum vpfe_ccdc_gamma_width { + VPFE_CCDC_GAMMA_BITS_15_6 = 0, /* use bits 15-6 for gamma */ + VPFE_CCDC_GAMMA_BITS_14_5, + VPFE_CCDC_GAMMA_BITS_13_4, + VPFE_CCDC_GAMMA_BITS_12_3, + VPFE_CCDC_GAMMA_BITS_11_2, + VPFE_CCDC_GAMMA_BITS_10_1, + VPFE_CCDC_GAMMA_BITS_09_0, /* use bits 9-0 for gamma */ +}; + +/* structure for ALaw */ +struct vpfe_ccdc_a_law { + /* Enable/disable A-Law */ + unsigned char enable; + /* Gamma Width Input */ + enum vpfe_ccdc_gamma_width gamma_wd; +}; + +/* structure for Black Clamping */ +struct vpfe_ccdc_black_clamp { + unsigned char enable; + /* only if bClampEnable is TRUE */ + enum vpfe_ccdc_sample_length sample_pixel; + /* only if bClampEnable is TRUE */ + enum vpfe_ccdc_sample_line sample_ln; + /* only if bClampEnable is TRUE */ + unsigned short start_pixel; + /* only if bClampEnable is TRUE */ + unsigned short sgain; + /* only if bClampEnable is FALSE */ + unsigned short dc_sub; +}; + +/* structure for Black Level Compensation */ +struct vpfe_ccdc_black_compensation { + /* Constant value to subtract from Red component */ + char r; + /* Constant value to subtract from Gr component */ + char gr; + /* Constant value to subtract from Blue component */ + char b; + /* Constant value to subtract from Gb component */ + char gb; +}; + +/* Structure for CCDC configuration parameters for raw capture mode passed + * by application + */ +struct vpfe_ccdc_config_params_raw { + /* data size value from 8 to 16 bits */ + enum vpfe_ccdc_data_size data_sz; + /* Structure for Optional A-Law */ + struct vpfe_ccdc_a_law alaw; + /* Structure for Optical Black Clamp */ + struct vpfe_ccdc_black_clamp blk_clamp; + /* Structure for Black Compensation */ + struct vpfe_ccdc_black_compensation blk_comp; +}; + +/* + * Private IOCTL + * VIDIOC_AM437X_CCDC_CFG - Set CCDC configuration for raw capture + * This is an experimental ioctl that will change in future kernels. So use + * this ioctl with care ! + **/ +#define VIDIOC_AM437X_CCDC_CFG \ + _IOW('V', BASE_VIDIOC_PRIVATE + 1, void *) + +#endif /* AM437X_VPFE_USER_H */ -- cgit v1.2.3 From e71e2c6fbbac673f3e222c7a6b6b62bcf6f40fb1 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Mon, 29 Dec 2014 16:17:36 -0700 Subject: MAINTAINERS: Add the docs-next git tree to the maintainer entry Signed-off-by: Jonathan Corbet --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..76f9c0df3e73 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3207,6 +3207,7 @@ F: Documentation/ X: Documentation/ABI/ X: Documentation/devicetree/ X: Documentation/[a-z][a-z]_[A-Z][A-Z]/ +T: git git://git.lwn.net/linux-2.6.git docs-next DOUBLETALK DRIVER M: "James R. Van Zandt" -- cgit v1.2.3 From b422da7c366fbf0899db1fdc808fc5fd6700dec5 Mon Sep 17 00:00:00 2001 From: Pravin B Shelar Date: Fri, 2 Jan 2015 11:18:21 -0800 Subject: MAINTAINERS: Update Open vSwitch entry. OVS development is moved to netdev mailing list. Update tree and list in MAINTAINERS file. Signed-off-by: Pravin B Shelar Signed-off-by: David S. Miller --- MAINTAINERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..33098956d930 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7008,11 +7008,12 @@ F: arch/openrisc/ OPENVSWITCH M: Pravin Shelar +L: netdev@vger.kernel.org L: dev@openvswitch.org W: http://openvswitch.org -T: git git://git.kernel.org/pub/scm/linux/kernel/git/pshelar/openvswitch.git S: Maintained F: net/openvswitch/ +F: include/uapi/linux/openvswitch.h OPL4 DRIVER M: Clemens Ladisch -- cgit v1.2.3 From ae9b56e3996dadbb59c727018f45486c06844261 Mon Sep 17 00:00:00 2001 From: Punnaiah Choudary Kalluri Date: Tue, 6 Jan 2015 23:13:47 +0530 Subject: EDAC, synps: Add EDAC support for zynq ddr ecc controller Add EDAC support for ecc errors reporting on the synopsys ddr controller. The ddr ecc controller corrects single bit errors and detects double bit errors. Selected important-ish notes from the changelog: - I have not taken care of spliting synps_edac_geterror_info function as it adds additional indentation levels and moreover the existing changes were made as part of the v2 review comments - Removed dt binding info as already there is a binding info available under memorycontroller. so, updated ecc info there. - Shortened the prefix "sysnopsys" to "synps" Signed-off-by: Punnaiah Choudary Kalluri Link: http://lkml.kernel.org/r/a728a8d4678f4dbf9de189a480297c3d@BY2FFO11FD034.protection.gbl [ Boris: massage commit message. ] Signed-off-by: Borislav Petkov --- MAINTAINERS | 1 + drivers/edac/Kconfig | 7 + drivers/edac/Makefile | 1 + drivers/edac/synopsys_edac.c | 535 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 544 insertions(+) create mode 100644 drivers/edac/synopsys_edac.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..375e2488d7ca 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1583,6 +1583,7 @@ N: xilinx F: drivers/clocksource/cadence_ttc_timer.c F: drivers/i2c/busses/i2c-cadence.c F: drivers/mmc/host/sdhci-of-arasan.c +F: drivers/edac/synopsys_edac.c ARM SMMU DRIVER M: Will Deacon diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index 49c265255a07..cb59619df23f 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -385,4 +385,11 @@ config EDAC_ALTERA_MC preloader must initialize the SDRAM before loading the kernel. +config EDAC_SYNOPSYS + tristate "Synopsys DDR Memory Controller" + depends on EDAC_MM_EDAC && ARCH_ZYNQ + help + Support for error detection and correction on the Synopsys DDR + memory controller. + endif # EDAC diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index d40c69a04df7..b255f362b1db 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -67,3 +67,4 @@ obj-$(CONFIG_EDAC_OCTEON_LMC) += octeon_edac-lmc.o obj-$(CONFIG_EDAC_OCTEON_PCI) += octeon_edac-pci.o obj-$(CONFIG_EDAC_ALTERA_MC) += altera_edac.o +obj-$(CONFIG_EDAC_SYNOPSYS) += synopsys_edac.o diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c new file mode 100644 index 000000000000..1c9691535e13 --- /dev/null +++ b/drivers/edac/synopsys_edac.c @@ -0,0 +1,535 @@ +/* + * Synopsys DDR ECC Driver + * This driver is based on ppc4xx_edac.c drivers + * + * Copyright (C) 2012 - 2014 Xilinx, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details + */ + +#include +#include +#include + +#include "edac_core.h" + +/* Number of cs_rows needed per memory controller */ +#define SYNPS_EDAC_NR_CSROWS 1 + +/* Number of channels per memory controller */ +#define SYNPS_EDAC_NR_CHANS 1 + +/* Granularity of reported error in bytes */ +#define SYNPS_EDAC_ERR_GRAIN 1 + +#define SYNPS_EDAC_MSG_SIZE 256 + +#define SYNPS_EDAC_MOD_STRING "synps_edac" +#define SYNPS_EDAC_MOD_VER "1" + +/* Synopsys DDR memory controller registers that are relevant to ECC */ +#define CTRL_OFST 0x0 +#define T_ZQ_OFST 0xA4 + +/* ECC control register */ +#define ECC_CTRL_OFST 0xC4 +/* ECC log register */ +#define CE_LOG_OFST 0xC8 +/* ECC address register */ +#define CE_ADDR_OFST 0xCC +/* ECC data[31:0] register */ +#define CE_DATA_31_0_OFST 0xD0 + +/* Uncorrectable error info registers */ +#define UE_LOG_OFST 0xDC +#define UE_ADDR_OFST 0xE0 +#define UE_DATA_31_0_OFST 0xE4 + +#define STAT_OFST 0xF0 +#define SCRUB_OFST 0xF4 + +/* Control register bit field definitions */ +#define CTRL_BW_MASK 0xC +#define CTRL_BW_SHIFT 2 + +#define DDRCTL_WDTH_16 1 +#define DDRCTL_WDTH_32 0 + +/* ZQ register bit field definitions */ +#define T_ZQ_DDRMODE_MASK 0x2 + +/* ECC control register bit field definitions */ +#define ECC_CTRL_CLR_CE_ERR 0x2 +#define ECC_CTRL_CLR_UE_ERR 0x1 + +/* ECC correctable/uncorrectable error log register definitions */ +#define LOG_VALID 0x1 +#define CE_LOG_BITPOS_MASK 0xFE +#define CE_LOG_BITPOS_SHIFT 1 + +/* ECC correctable/uncorrectable error address register definitions */ +#define ADDR_COL_MASK 0xFFF +#define ADDR_ROW_MASK 0xFFFF000 +#define ADDR_ROW_SHIFT 12 +#define ADDR_BANK_MASK 0x70000000 +#define ADDR_BANK_SHIFT 28 + +/* ECC statistic register definitions */ +#define STAT_UECNT_MASK 0xFF +#define STAT_CECNT_MASK 0xFF00 +#define STAT_CECNT_SHIFT 8 + +/* ECC scrub register definitions */ +#define SCRUB_MODE_MASK 0x7 +#define SCRUB_MODE_SECDED 0x4 + +/** + * struct ecc_error_info - ECC error log information + * @row: Row number + * @col: Column number + * @bank: Bank number + * @bitpos: Bit position + * @data: Data causing the error + */ +struct ecc_error_info { + u32 row; + u32 col; + u32 bank; + u32 bitpos; + u32 data; +}; + +/** + * struct synps_ecc_status - ECC status information to report + * @ce_cnt: Correctable error count + * @ue_cnt: Uncorrectable error count + * @ceinfo: Correctable error log information + * @ueinfo: Uncorrectable error log information + */ +struct synps_ecc_status { + u32 ce_cnt; + u32 ue_cnt; + struct ecc_error_info ceinfo; + struct ecc_error_info ueinfo; +}; + +/** + * struct synps_edac_priv - DDR memory controller private instance data + * @baseaddr: Base address of the DDR controller + * @message: Buffer for framing the event specific info + * @stat: ECC status information + * @ce_cnt: Correctable Error count + * @ue_cnt: Uncorrectable Error count + */ +struct synps_edac_priv { + void __iomem *baseaddr; + char message[SYNPS_EDAC_MSG_SIZE]; + struct synps_ecc_status stat; + u32 ce_cnt; + u32 ue_cnt; +}; + +/** + * synps_edac_geterror_info - Get the current ecc error info + * @base: Pointer to the base address of the ddr memory controller + * @p: Pointer to the synopsys ecc status structure + * + * Determines there is any ecc error or not + * + * Return: one if there is no error otherwise returns zero + */ +static int synps_edac_geterror_info(void __iomem *base, + struct synps_ecc_status *p) +{ + u32 regval, clearval = 0; + + regval = readl(base + STAT_OFST); + if (!regval) + return 1; + + p->ce_cnt = (regval & STAT_CECNT_MASK) >> STAT_CECNT_SHIFT; + p->ue_cnt = regval & STAT_UECNT_MASK; + + regval = readl(base + CE_LOG_OFST); + if (!(p->ce_cnt && (regval & LOG_VALID))) + goto ue_err; + + p->ceinfo.bitpos = (regval & CE_LOG_BITPOS_MASK) >> CE_LOG_BITPOS_SHIFT; + regval = readl(base + CE_ADDR_OFST); + p->ceinfo.row = (regval & ADDR_ROW_MASK) >> ADDR_ROW_SHIFT; + p->ceinfo.col = regval & ADDR_COL_MASK; + p->ceinfo.bank = (regval & ADDR_BANK_MASK) >> ADDR_BANK_SHIFT; + p->ceinfo.data = readl(base + CE_DATA_31_0_OFST); + edac_dbg(3, "ce bit position: %d data: %d\n", p->ceinfo.bitpos, + p->ceinfo.data); + clearval = ECC_CTRL_CLR_CE_ERR; + +ue_err: + regval = readl(base + UE_LOG_OFST); + if (!(p->ue_cnt && (regval & LOG_VALID))) + goto out; + + regval = readl(base + UE_ADDR_OFST); + p->ueinfo.row = (regval & ADDR_ROW_MASK) >> ADDR_ROW_SHIFT; + p->ueinfo.col = regval & ADDR_COL_MASK; + p->ueinfo.bank = (regval & ADDR_BANK_MASK) >> ADDR_BANK_SHIFT; + p->ueinfo.data = readl(base + UE_DATA_31_0_OFST); + clearval |= ECC_CTRL_CLR_UE_ERR; + +out: + writel(clearval, base + ECC_CTRL_OFST); + writel(0x0, base + ECC_CTRL_OFST); + + return 0; +} + +/** + * synps_edac_handle_error - Handle controller error types CE and UE + * @mci: Pointer to the edac memory controller instance + * @p: Pointer to the synopsys ecc status structure + * + * Handles the controller ECC correctable and un correctable error. + */ +static void synps_edac_handle_error(struct mem_ctl_info *mci, + struct synps_ecc_status *p) +{ + struct synps_edac_priv *priv = mci->pvt_info; + struct ecc_error_info *pinf; + + if (p->ce_cnt) { + pinf = &p->ceinfo; + snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, + "DDR ECC error type :%s Row %d Bank %d Col %d ", + "CE", pinf->row, pinf->bank, pinf->col); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + p->ce_cnt, 0, 0, 0, 0, 0, -1, + priv->message, ""); + } + + if (p->ue_cnt) { + pinf = &p->ueinfo; + snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, + "DDR ECC error type :%s Row %d Bank %d Col %d ", + "UE", pinf->row, pinf->bank, pinf->col); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + p->ue_cnt, 0, 0, 0, 0, 0, -1, + priv->message, ""); + } + + memset(p, 0, sizeof(*p)); +} + +/** + * synps_edac_check - Check controller for ECC errors + * @mci: Pointer to the edac memory controller instance + * + * Used to check and post ECC errors. Called by the polling thread + */ +static void synps_edac_check(struct mem_ctl_info *mci) +{ + struct synps_edac_priv *priv = mci->pvt_info; + int status; + + status = synps_edac_geterror_info(priv->baseaddr, &priv->stat); + if (status) + return; + + priv->ce_cnt += priv->stat.ce_cnt; + priv->ue_cnt += priv->stat.ue_cnt; + synps_edac_handle_error(mci, &priv->stat); + + edac_dbg(3, "Total error count ce %d ue %d\n", + priv->ce_cnt, priv->ue_cnt); +} + +/** + * synps_edac_get_dtype - Return the controller memory width + * @base: Pointer to the ddr memory controller base address + * + * Get the EDAC device type width appropriate for the current controller + * configuration. + * + * Return: a device type width enumeration. + */ +static enum dev_type synps_edac_get_dtype(const void __iomem *base) +{ + enum dev_type dt; + u32 width; + + width = readl(base + CTRL_OFST); + width = (width & CTRL_BW_MASK) >> CTRL_BW_SHIFT; + + switch (width) { + case DDRCTL_WDTH_16: + dt = DEV_X2; + break; + case DDRCTL_WDTH_32: + dt = DEV_X4; + break; + default: + dt = DEV_UNKNOWN; + } + + return dt; +} + +/** + * synps_edac_get_eccstate - Return the controller ecc enable/disable status + * @base: Pointer to the ddr memory controller base address + * + * Get the ECC enable/disable status for the controller + * + * Return: a ecc status boolean i.e true/false - enabled/disabled. + */ +static bool synps_edac_get_eccstate(void __iomem *base) +{ + enum dev_type dt; + u32 ecctype; + bool state = false; + + dt = synps_edac_get_dtype(base); + if (dt == DEV_UNKNOWN) + return state; + + ecctype = readl(base + SCRUB_OFST) & SCRUB_MODE_MASK; + if ((ecctype == SCRUB_MODE_SECDED) && (dt == DEV_X2)) + state = true; + + return state; +} + +/** + * synps_edac_get_memsize - reads the size of the attached memory device + * + * Return: the memory size in bytes + */ +static u32 synps_edac_get_memsize(void) +{ + struct sysinfo inf; + + si_meminfo(&inf); + + return inf.totalram * inf.mem_unit; +} + +/** + * synps_edac_get_mtype - Returns controller memory type + * @base: pointer to the synopsys ecc status structure + * + * Get the EDAC memory type appropriate for the current controller + * configuration. + * + * Return: a memory type enumeration. + */ +static enum mem_type synps_edac_get_mtype(const void __iomem *base) +{ + enum mem_type mt; + u32 memtype; + + memtype = readl(base + T_ZQ_OFST); + + if (memtype & T_ZQ_DDRMODE_MASK) + mt = MEM_DDR3; + else + mt = MEM_DDR2; + + return mt; +} + +/** + * synps_edac_init_csrows - Initialize the cs row data + * @mci: Pointer to the edac memory controller instance + * + * Initializes the chip select rows associated with the EDAC memory + * controller instance + * + * Return: Unconditionally 0. + */ +static int synps_edac_init_csrows(struct mem_ctl_info *mci) +{ + struct csrow_info *csi; + struct dimm_info *dimm; + struct synps_edac_priv *priv = mci->pvt_info; + u32 size; + int row, j; + + for (row = 0; row < mci->nr_csrows; row++) { + csi = mci->csrows[row]; + size = synps_edac_get_memsize(); + + for (j = 0; j < csi->nr_channels; j++) { + dimm = csi->channels[j]->dimm; + dimm->edac_mode = EDAC_FLAG_SECDED; + dimm->mtype = synps_edac_get_mtype(priv->baseaddr); + dimm->nr_pages = (size >> PAGE_SHIFT) / csi->nr_channels; + dimm->grain = SYNPS_EDAC_ERR_GRAIN; + dimm->dtype = synps_edac_get_dtype(priv->baseaddr); + } + } + + return 0; +} + +/** + * synps_edac_mc_init - Initialize driver instance + * @mci: Pointer to the edac memory controller instance + * @pdev: Pointer to the platform_device struct + * + * Performs initialization of the EDAC memory controller instance and + * related driver-private data associated with the memory controller the + * instance is bound to. + * + * Return: Always zero. + */ +static int synps_edac_mc_init(struct mem_ctl_info *mci, + struct platform_device *pdev) +{ + int status; + struct synps_edac_priv *priv; + + mci->pdev = &pdev->dev; + priv = mci->pvt_info; + platform_set_drvdata(pdev, mci); + + /* Initialize controller capabilities and configuration */ + mci->mtype_cap = MEM_FLAG_DDR3 | MEM_FLAG_DDR2; + mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; + mci->scrub_cap = SCRUB_HW_SRC; + mci->scrub_mode = SCRUB_NONE; + + mci->edac_cap = EDAC_FLAG_SECDED; + mci->ctl_name = "synps_ddr_controller"; + mci->dev_name = SYNPS_EDAC_MOD_STRING; + mci->mod_name = SYNPS_EDAC_MOD_VER; + mci->mod_ver = "1"; + + edac_op_state = EDAC_OPSTATE_POLL; + mci->edac_check = synps_edac_check; + mci->ctl_page_to_phys = NULL; + + status = synps_edac_init_csrows(mci); + + return status; +} + +/** + * synps_edac_mc_probe - Check controller and bind driver + * @pdev: Pointer to the platform_device struct + * + * Probes a specific controller instance for binding with the driver. + * + * Return: 0 if the controller instance was successfully bound to the + * driver; otherwise, < 0 on error. + */ +static int synps_edac_mc_probe(struct platform_device *pdev) +{ + struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; + struct synps_edac_priv *priv; + int rc; + struct resource *res; + void __iomem *baseaddr; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + baseaddr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(baseaddr)) + return PTR_ERR(baseaddr); + + if (!synps_edac_get_eccstate(baseaddr)) { + edac_printk(KERN_INFO, EDAC_MC, "ECC not enabled\n"); + return -ENXIO; + } + + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = SYNPS_EDAC_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = SYNPS_EDAC_NR_CHANS; + layers[1].is_virt_csrow = false; + + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, + sizeof(struct synps_edac_priv)); + if (!mci) { + edac_printk(KERN_ERR, EDAC_MC, + "Failed memory allocation for mc instance\n"); + return -ENOMEM; + } + + priv = mci->pvt_info; + priv->baseaddr = baseaddr; + rc = synps_edac_mc_init(mci, pdev); + if (rc) { + edac_printk(KERN_ERR, EDAC_MC, + "Failed to initialize instance\n"); + goto free_edac_mc; + } + + rc = edac_mc_add_mc(mci); + if (rc) { + edac_printk(KERN_ERR, EDAC_MC, + "Failed to register with EDAC core\n"); + goto free_edac_mc; + } + + /* + * Start capturing the correctable and uncorrectable errors. A write of + * 0 starts the counters. + */ + writel(0x0, baseaddr + ECC_CTRL_OFST); + return rc; + +free_edac_mc: + edac_mc_free(mci); + + return rc; +} + +/** + * synps_edac_mc_remove - Unbind driver from controller + * @pdev: Pointer to the platform_device struct + * + * Return: Unconditionally 0 + */ +static int synps_edac_mc_remove(struct platform_device *pdev) +{ + struct mem_ctl_info *mci = platform_get_drvdata(pdev); + + edac_mc_del_mc(&pdev->dev); + edac_mc_free(mci); + + return 0; +} + +static struct of_device_id synps_edac_match[] = { + { .compatible = "xlnx,zynq-ddrc-a05", }, + { /* end of table */ } +}; + +MODULE_DEVICE_TABLE(of, synps_edac_match); + +static struct platform_driver synps_edac_mc_driver = { + .driver = { + .name = "synopsys-edac", + .of_match_table = synps_edac_match, + }, + .probe = synps_edac_mc_probe, + .remove = synps_edac_mc_remove, +}; + +module_platform_driver(synps_edac_mc_driver); + +MODULE_AUTHOR("Xilinx Inc"); +MODULE_DESCRIPTION("Synopsys DDR ECC driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 57b7068de5d0cca8ac6e21085b843c1bbd49d3f4 Mon Sep 17 00:00:00 2001 From: Max Filippov Date: Fri, 26 Dec 2014 20:19:38 +0300 Subject: ASoC: add xtensa xtfpga I2S interface and platform XTFPGA boards provides an audio subsystem that consists of TI CDCE706 clock synthesizer, I2S transmitter and TLV320AIC23 audio codec. I2S transmitter has MMIO-based interface that resembles that of the OpenCores I2S transmitter. I2S transmitter is always a master on I2S bus. There's no specialized audio DMA, sample data are transferred to I2S transmitter FIFO by CPU through memory-mapped queue interface. Signed-off-by: Max Filippov Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/cdns,xtfpga-i2s.txt | 18 + MAINTAINERS | 1 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/xtensa/Kconfig | 7 + sound/soc/xtensa/Makefile | 3 + sound/soc/xtensa/xtfpga-i2s.c | 675 +++++++++++++++++++++ 7 files changed, 706 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cdns,xtfpga-i2s.txt create mode 100644 sound/soc/xtensa/Kconfig create mode 100644 sound/soc/xtensa/Makefile create mode 100644 sound/soc/xtensa/xtfpga-i2s.c (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/sound/cdns,xtfpga-i2s.txt b/Documentation/devicetree/bindings/sound/cdns,xtfpga-i2s.txt new file mode 100644 index 000000000000..befd125d18bb --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cdns,xtfpga-i2s.txt @@ -0,0 +1,18 @@ +Bindings for I2S controller built into xtfpga Xtensa bitstreams. + +Required properties: +- compatible: shall be "cdns,xtfpga-i2s". +- reg: memory region (address and length) with device registers. +- interrupts: interrupt for the device. +- clocks: phandle to the clk used as master clock. I2S bus clock + is derived from it. + +Examples: + + i2s0: xtfpga-i2s@0d080000 { + #sound-dai-cells = <0>; + compatible = "cdns,xtfpga-i2s"; + reg = <0x0d080000 0x40>; + interrupts = <2 1>; + clocks = <&cdce706 4>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..f1eb40f8926c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10628,6 +10628,7 @@ M: Max Filippov L: linux-xtensa@linux-xtensa.org S: Maintained F: drivers/spi/spi-xtensa-xtfpga.c +F: sound/soc/xtensa/xtfpga-i2s.c YAM DRIVER FOR AX.25 M: Jean-Paul Roubelat diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 7d5d6444a837..dcc79aa0236b 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -55,6 +55,7 @@ source "sound/soc/spear/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" source "sound/soc/ux500/Kconfig" +source "sound/soc/xtensa/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 865e090c8061..5b3c8f67c8db 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -36,3 +36,4 @@ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ obj-$(CONFIG_SND_SOC) += ux500/ +obj-$(CONFIG_SND_SOC) += xtensa/ diff --git a/sound/soc/xtensa/Kconfig b/sound/soc/xtensa/Kconfig new file mode 100644 index 000000000000..c201beb36de6 --- /dev/null +++ b/sound/soc/xtensa/Kconfig @@ -0,0 +1,7 @@ +config SND_SOC_XTFPGA_I2S + tristate "XTFPGA I2S master" + select REGMAP_MMIO + help + Say Y or M if you want to add support for codecs attached to the + I2S interface on XTFPGA daughter board. You will also need to select + the drivers for the rest of XTFPGA audio subsystem. diff --git a/sound/soc/xtensa/Makefile b/sound/soc/xtensa/Makefile new file mode 100644 index 000000000000..15efbf914226 --- /dev/null +++ b/sound/soc/xtensa/Makefile @@ -0,0 +1,3 @@ +snd-soc-xtfpga-i2s-objs := xtfpga-i2s.o + +obj-$(CONFIG_SND_SOC_XTFPGA_I2S) += snd-soc-xtfpga-i2s.o diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c new file mode 100644 index 000000000000..1cfb19e12949 --- /dev/null +++ b/sound/soc/xtensa/xtfpga-i2s.c @@ -0,0 +1,675 @@ +/* + * Xtfpga I2S controller driver + * + * Copyright (c) 2014 Cadence Design Systems Inc. + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "xtfpga-i2s" + +#define XTFPGA_I2S_VERSION 0x00 +#define XTFPGA_I2S_CONFIG 0x04 +#define XTFPGA_I2S_INT_MASK 0x08 +#define XTFPGA_I2S_INT_STATUS 0x0c +#define XTFPGA_I2S_CHAN0_DATA 0x10 +#define XTFPGA_I2S_CHAN1_DATA 0x14 +#define XTFPGA_I2S_CHAN2_DATA 0x18 +#define XTFPGA_I2S_CHAN3_DATA 0x1c + +#define XTFPGA_I2S_CONFIG_TX_ENABLE 0x1 +#define XTFPGA_I2S_CONFIG_INT_ENABLE 0x2 +#define XTFPGA_I2S_CONFIG_LEFT 0x4 +#define XTFPGA_I2S_CONFIG_RATIO_BASE 8 +#define XTFPGA_I2S_CONFIG_RATIO_MASK 0x0000ff00 +#define XTFPGA_I2S_CONFIG_RES_BASE 16 +#define XTFPGA_I2S_CONFIG_RES_MASK 0x003f0000 +#define XTFPGA_I2S_CONFIG_LEVEL_BASE 24 +#define XTFPGA_I2S_CONFIG_LEVEL_MASK 0x0f000000 +#define XTFPGA_I2S_CONFIG_CHANNEL_BASE 28 + +#define XTFPGA_I2S_INT_UNDERRUN 0x1 +#define XTFPGA_I2S_INT_LEVEL 0x2 +#define XTFPGA_I2S_INT_VALID 0x3 + +#define XTFPGA_I2S_FIFO_SIZE 8192 + +/* + * I2S controller operation: + * + * Enabling TX: output 1 period of zeros (starting with left channel) + * and then queued data. + * + * Level status and interrupt: whenever FIFO level is below FIFO trigger, + * level status is 1 and an IRQ is asserted (if enabled). + * + * Underrun status and interrupt: whenever FIFO is empty, underrun status + * is 1 and an IRQ is asserted (if enabled). + */ +struct xtfpga_i2s { + struct device *dev; + struct clk *clk; + struct regmap *regmap; + void __iomem *regs; + + /* current playback substream. NULL if not playing. + * + * Access to that field is synchronized between the interrupt handler + * and userspace through RCU. + * + * Interrupt handler (threaded part) does PIO on substream data in RCU + * read-side critical section. Trigger callback sets and clears the + * pointer when the playback is started and stopped with + * rcu_assign_pointer. When userspace is about to free the playback + * stream in the pcm_close callback it synchronizes with the interrupt + * handler by means of synchronize_rcu call. + */ + struct snd_pcm_substream *tx_substream; + unsigned (*tx_fn)(struct xtfpga_i2s *i2s, + struct snd_pcm_runtime *runtime, + unsigned tx_ptr); + unsigned tx_ptr; /* next frame index in the sample buffer */ + + /* current fifo level estimate. + * Doesn't have to be perfectly accurate, but must be not less than + * the actual FIFO level in order to avoid stall on push attempt. + */ + unsigned tx_fifo_level; + + /* FIFO level at which level interrupt occurs */ + unsigned tx_fifo_low; + + /* maximal FIFO level */ + unsigned tx_fifo_high; +}; + +static bool xtfpga_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + return reg >= XTFPGA_I2S_CONFIG; +} + +static bool xtfpga_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + return reg < XTFPGA_I2S_CHAN0_DATA; +} + +static bool xtfpga_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == XTFPGA_I2S_INT_STATUS; +} + +static const struct regmap_config xtfpga_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = XTFPGA_I2S_CHAN3_DATA, + .writeable_reg = xtfpga_i2s_wr_reg, + .readable_reg = xtfpga_i2s_rd_reg, + .volatile_reg = xtfpga_i2s_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +/* Generate functions that do PIO from TX DMA area to FIFO for all supported + * stream formats. + * Functions will be called xtfpga_pcm_tx_x, e.g. + * xtfpga_pcm_tx_2x16 for 16-bit stereo. + * + * FIFO consists of 32-bit words, one word per channel, always 2 channels. + * If I2S interface is configured with smaller sample resolution, only + * the LSB of each word is used. + */ +#define xtfpga_pcm_tx_fn(channels, sample_bits) \ +static unsigned xtfpga_pcm_tx_##channels##x##sample_bits( \ + struct xtfpga_i2s *i2s, struct snd_pcm_runtime *runtime, \ + unsigned tx_ptr) \ +{ \ + const u##sample_bits (*p)[channels] = \ + (void *)runtime->dma_area; \ +\ + for (; i2s->tx_fifo_level < i2s->tx_fifo_high; \ + i2s->tx_fifo_level += 2) { \ + iowrite32(p[tx_ptr][0], \ + i2s->regs + XTFPGA_I2S_CHAN0_DATA); \ + iowrite32(p[tx_ptr][channels - 1], \ + i2s->regs + XTFPGA_I2S_CHAN0_DATA); \ + if (++tx_ptr >= runtime->buffer_size) \ + tx_ptr = 0; \ + } \ + return tx_ptr; \ +} + +xtfpga_pcm_tx_fn(1, 16) +xtfpga_pcm_tx_fn(2, 16) +xtfpga_pcm_tx_fn(1, 32) +xtfpga_pcm_tx_fn(2, 32) + +#undef xtfpga_pcm_tx_fn + +static bool xtfpga_pcm_push_tx(struct xtfpga_i2s *i2s) +{ + struct snd_pcm_substream *tx_substream; + bool tx_active; + + rcu_read_lock(); + tx_substream = rcu_dereference(i2s->tx_substream); + tx_active = tx_substream && snd_pcm_running(tx_substream); + if (tx_active) { + unsigned tx_ptr = ACCESS_ONCE(i2s->tx_ptr); + unsigned new_tx_ptr = i2s->tx_fn(i2s, tx_substream->runtime, + tx_ptr); + + cmpxchg(&i2s->tx_ptr, tx_ptr, new_tx_ptr); + } + rcu_read_unlock(); + + return tx_active; +} + +static void xtfpga_pcm_refill_fifo(struct xtfpga_i2s *i2s) +{ + unsigned int_status; + unsigned i; + + regmap_read(i2s->regmap, XTFPGA_I2S_INT_STATUS, + &int_status); + + for (i = 0; i < 2; ++i) { + bool tx_active = xtfpga_pcm_push_tx(i2s); + + regmap_write(i2s->regmap, XTFPGA_I2S_INT_STATUS, + XTFPGA_I2S_INT_VALID); + if (tx_active) + regmap_read(i2s->regmap, XTFPGA_I2S_INT_STATUS, + &int_status); + + if (!tx_active || + !(int_status & XTFPGA_I2S_INT_LEVEL)) + break; + + /* After the push the level IRQ is still asserted, + * means FIFO level is below tx_fifo_low. Estimate + * it as tx_fifo_low. + */ + i2s->tx_fifo_level = i2s->tx_fifo_low; + } + + if (!(int_status & XTFPGA_I2S_INT_LEVEL)) + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, + XTFPGA_I2S_INT_VALID); + else if (!(int_status & XTFPGA_I2S_INT_UNDERRUN)) + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, + XTFPGA_I2S_INT_UNDERRUN); + + if (!(int_status & XTFPGA_I2S_INT_UNDERRUN)) + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_INT_ENABLE | + XTFPGA_I2S_CONFIG_TX_ENABLE, + XTFPGA_I2S_CONFIG_INT_ENABLE | + XTFPGA_I2S_CONFIG_TX_ENABLE); + else + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_INT_ENABLE | + XTFPGA_I2S_CONFIG_TX_ENABLE, 0); +} + +static irqreturn_t xtfpga_i2s_threaded_irq_handler(int irq, void *dev_id) +{ + struct xtfpga_i2s *i2s = dev_id; + struct snd_pcm_substream *tx_substream; + unsigned config, int_status, int_mask; + + regmap_read(i2s->regmap, XTFPGA_I2S_CONFIG, &config); + regmap_read(i2s->regmap, XTFPGA_I2S_INT_MASK, &int_mask); + regmap_read(i2s->regmap, XTFPGA_I2S_INT_STATUS, &int_status); + + if (!(config & XTFPGA_I2S_CONFIG_INT_ENABLE) || + !(int_status & int_mask & XTFPGA_I2S_INT_VALID)) + return IRQ_NONE; + + /* Update FIFO level estimate in accordance with interrupt status + * register. + */ + if (int_status & XTFPGA_I2S_INT_UNDERRUN) { + i2s->tx_fifo_level = 0; + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_TX_ENABLE, 0); + } else { + /* The FIFO isn't empty, but is below tx_fifo_low. Estimate + * it as tx_fifo_low. + */ + i2s->tx_fifo_level = i2s->tx_fifo_low; + } + + rcu_read_lock(); + tx_substream = rcu_dereference(i2s->tx_substream); + + if (tx_substream && snd_pcm_running(tx_substream)) { + snd_pcm_period_elapsed(tx_substream); + if (int_status & XTFPGA_I2S_INT_UNDERRUN) + dev_dbg_ratelimited(i2s->dev, "%s: underrun\n", + __func__); + } + rcu_read_unlock(); + + /* Refill FIFO, update allowed IRQ reasons, enable IRQ if FIFO is + * not empty. + */ + xtfpga_pcm_refill_fifo(i2s); + + return IRQ_HANDLED; +} + +static int xtfpga_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct xtfpga_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_set_dma_data(dai, substream, i2s); + return 0; +} + +static int xtfpga_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct xtfpga_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned srate = params_rate(params); + unsigned channels = params_channels(params); + unsigned period_size = params_period_size(params); + unsigned sample_size = snd_pcm_format_width(params_format(params)); + unsigned freq, ratio, level; + int err; + + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_RES_MASK, + sample_size << XTFPGA_I2S_CONFIG_RES_BASE); + + freq = 256 * srate; + err = clk_set_rate(i2s->clk, freq); + if (err < 0) + return err; + + /* ratio field of the config register controls MCLK->I2S clock + * derivation: I2S clock = MCLK / (2 * (ratio + 2)). + * + * So with MCLK = 256 * sample rate ratio is 0 for 32 bit stereo + * and 2 for 16 bit stereo. + */ + ratio = (freq - (srate * sample_size * 8)) / + (srate * sample_size * 4); + + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_RATIO_MASK, + ratio << XTFPGA_I2S_CONFIG_RATIO_BASE); + + i2s->tx_fifo_low = XTFPGA_I2S_FIFO_SIZE / 2; + + /* period_size * 2: FIFO always gets 2 samples per frame */ + for (level = 1; + i2s->tx_fifo_low / 2 >= period_size * 2 && + level < (XTFPGA_I2S_CONFIG_LEVEL_MASK >> + XTFPGA_I2S_CONFIG_LEVEL_BASE); ++level) + i2s->tx_fifo_low /= 2; + + i2s->tx_fifo_high = 2 * i2s->tx_fifo_low; + + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_LEVEL_MASK, + level << XTFPGA_I2S_CONFIG_LEVEL_BASE); + + dev_dbg(i2s->dev, + "%s srate: %u, channels: %u, sample_size: %u, period_size: %u\n", + __func__, srate, channels, sample_size, period_size); + dev_dbg(i2s->dev, "%s freq: %u, ratio: %u, level: %u\n", + __func__, freq, ratio, level); + + return 0; +} + +static int xtfpga_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) + return -EINVAL; + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) + return -EINVAL; + + return 0; +} + +/* PCM */ + +static const struct snd_pcm_hardware xtfpga_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 2, + .period_bytes_min = 2, + .period_bytes_max = XTFPGA_I2S_FIFO_SIZE / 2 * 8, + .periods_min = 2, + .periods_max = XTFPGA_I2S_FIFO_SIZE * 8 / 2, + .buffer_bytes_max = XTFPGA_I2S_FIFO_SIZE * 8, + .fifo_size = 16, +}; + +static int xtfpga_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + void *p; + + snd_soc_set_runtime_hwparams(substream, &xtfpga_pcm_hardware); + p = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + runtime->private_data = p; + + return 0; +} + +static int xtfpga_pcm_close(struct snd_pcm_substream *substream) +{ + synchronize_rcu(); + return 0; +} + +static int xtfpga_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + int ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct xtfpga_i2s *i2s = runtime->private_data; + unsigned channels = params_channels(hw_params); + + switch (channels) { + case 1: + case 2: + break; + + default: + return -EINVAL; + + } + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16_LE: + i2s->tx_fn = (channels == 1) ? + xtfpga_pcm_tx_1x16 : + xtfpga_pcm_tx_2x16; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + i2s->tx_fn = (channels == 1) ? + xtfpga_pcm_tx_1x32 : + xtfpga_pcm_tx_2x32; + break; + + default: + return -EINVAL; + } + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + return ret; +} + +static int xtfpga_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct xtfpga_i2s *i2s = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ACCESS_ONCE(i2s->tx_ptr) = 0; + rcu_assign_pointer(i2s->tx_substream, substream); + xtfpga_pcm_refill_fifo(i2s); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + rcu_assign_pointer(i2s->tx_substream, NULL); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} + +static snd_pcm_uframes_t xtfpga_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct xtfpga_i2s *i2s = runtime->private_data; + snd_pcm_uframes_t pos = ACCESS_ONCE(i2s->tx_ptr); + + return pos < runtime->buffer_size ? pos : 0; +} + +static int xtfpga_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + size_t size = xtfpga_pcm_hardware.buffer_bytes_max; + + return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, + SNDRV_DMA_TYPE_DEV, + card->dev, size, size); +} + +static void xtfpga_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static const struct snd_pcm_ops xtfpga_pcm_ops = { + .open = xtfpga_pcm_open, + .close = xtfpga_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = xtfpga_pcm_hw_params, + .trigger = xtfpga_pcm_trigger, + .pointer = xtfpga_pcm_pointer, +}; + +static const struct snd_soc_platform_driver xtfpga_soc_platform = { + .pcm_new = xtfpga_pcm_new, + .pcm_free = xtfpga_pcm_free, + .ops = &xtfpga_pcm_ops, +}; + +static const struct snd_soc_component_driver xtfpga_i2s_component = { + .name = DRV_NAME, +}; + +static const struct snd_soc_dai_ops xtfpga_i2s_dai_ops = { + .startup = xtfpga_i2s_startup, + .hw_params = xtfpga_i2s_hw_params, + .set_fmt = xtfpga_i2s_set_fmt, +}; + +static struct snd_soc_dai_driver xtfpga_i2s_dai[] = { + { + .name = "xtfpga-i2s", + .id = 0, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &xtfpga_i2s_dai_ops, + }, +}; + +static int xtfpga_i2s_runtime_suspend(struct device *dev) +{ + struct xtfpga_i2s *i2s = dev_get_drvdata(dev); + + clk_disable_unprepare(i2s->clk); + return 0; +} + +static int xtfpga_i2s_runtime_resume(struct device *dev) +{ + struct xtfpga_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->clk); + if (ret) { + dev_err(dev, "clk_prepare_enable failed: %d\n", ret); + return ret; + } + return 0; +} + +static int xtfpga_i2s_probe(struct platform_device *pdev) +{ + struct xtfpga_i2s *i2s; + struct resource *mem; + int err, irq; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) { + err = -ENOMEM; + goto err; + } + platform_set_drvdata(pdev, i2s); + i2s->dev = &pdev->dev; + dev_dbg(&pdev->dev, "dev: %p, i2s: %p\n", &pdev->dev, i2s); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(i2s->regs)) { + err = PTR_ERR(i2s->regs); + goto err; + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->regs, + &xtfpga_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + err = PTR_ERR(i2s->regmap); + goto err; + } + + i2s->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(i2s->clk)) { + dev_err(&pdev->dev, "couldn't get clock\n"); + err = PTR_ERR(i2s->clk); + goto err; + } + + regmap_write(i2s->regmap, XTFPGA_I2S_CONFIG, + (0x1 << XTFPGA_I2S_CONFIG_CHANNEL_BASE)); + regmap_write(i2s->regmap, XTFPGA_I2S_INT_STATUS, XTFPGA_I2S_INT_VALID); + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, XTFPGA_I2S_INT_UNDERRUN); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "No IRQ resource\n"); + err = irq; + goto err; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + xtfpga_i2s_threaded_irq_handler, + IRQF_SHARED | IRQF_ONESHOT, + pdev->name, i2s); + if (err < 0) { + dev_err(&pdev->dev, "request_irq failed\n"); + goto err; + } + + err = snd_soc_register_platform(&pdev->dev, &xtfpga_soc_platform); + if (err < 0) { + dev_err(&pdev->dev, "couldn't register platform\n"); + goto err; + } + err = devm_snd_soc_register_component(&pdev->dev, + &xtfpga_i2s_component, + xtfpga_i2s_dai, + ARRAY_SIZE(xtfpga_i2s_dai)); + if (err < 0) { + dev_err(&pdev->dev, "couldn't register component\n"); + goto err_unregister_platform; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + err = xtfpga_i2s_runtime_resume(&pdev->dev); + if (err) + goto err_pm_disable; + } + return 0; + +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_unregister_platform: + snd_soc_unregister_platform(&pdev->dev); +err: + dev_err(&pdev->dev, "%s: err = %d\n", __func__, err); + return err; +} + +static int xtfpga_i2s_remove(struct platform_device *pdev) +{ + struct xtfpga_i2s *i2s = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_platform(&pdev->dev); + if (i2s->regmap && !IS_ERR(i2s->regmap)) { + regmap_write(i2s->regmap, XTFPGA_I2S_CONFIG, 0); + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, 0); + regmap_write(i2s->regmap, XTFPGA_I2S_INT_STATUS, + XTFPGA_I2S_INT_VALID); + } + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + xtfpga_i2s_runtime_suspend(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id xtfpga_i2s_of_match[] = { + { .compatible = "cdns,xtfpga-i2s", }, + {}, +}; +MODULE_DEVICE_TABLE(of, xtfpga_i2s_of_match); +#endif + +static const struct dev_pm_ops xtfpga_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(xtfpga_i2s_runtime_suspend, + xtfpga_i2s_runtime_resume, NULL) +}; + +static struct platform_driver xtfpga_i2s_driver = { + .probe = xtfpga_i2s_probe, + .remove = xtfpga_i2s_remove, + .driver = { + .name = "xtfpga-i2s", + .of_match_table = of_match_ptr(xtfpga_i2s_of_match), + .pm = &xtfpga_i2s_pm_ops, + }, +}; + +module_platform_driver(xtfpga_i2s_driver); + +MODULE_AUTHOR("Max Filippov "); +MODULE_DESCRIPTION("xtfpga I2S controller driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From f17effbe891624fe1ec2dcfa68625ed1c98ebe3a Mon Sep 17 00:00:00 2001 From: Moritz Fischer Date: Sat, 10 Jan 2015 14:10:59 -0800 Subject: MAINTAINERS: add info for e3x0-button driver Signed-off-by: Moritz Fischer Signed-off-by: Dmitry Torokhov --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 21b834b191d0..d7269172981d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3277,6 +3277,14 @@ M: "Maciej W. Rozycki" S: Maintained F: drivers/tty/serial/dz.* +E3X0 POWER BUTTON DRIVER +M: Moritz Fischer +L: usrp-users@lists.ettus.com +W: http://www.ettus.com +S: Supported +F: drivers/input/misc/e3x0-button.c +F: Documentation/devicetree/bindings/input/e3x0-button.txt + E4000 MEDIA DRIVER M: Antti Palosaari L: linux-media@vger.kernel.org -- cgit v1.2.3 From ef3522f78cf0d068e5d0bb3cdc3568d9c7aee1f0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 21 Dec 2014 20:54:37 +0100 Subject: MAINTAINERS: Fix up entry for Dell laptop SMM driver Mark driver as maintained. Signed-off-by: Guenter Roeck Reviewed-by: Jean Delvare Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..2b41cd183b4f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3012,6 +3012,7 @@ F: drivers/platform/x86/dell-laptop.c DELL LAPTOP SMM DRIVER M: Guenter Roeck +S: Maintained F: drivers/char/i8k.c F: include/uapi/linux/i8k.h -- cgit v1.2.3 From 95df09e6237836855031d6bebcad4a9782ea84a3 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Fri, 9 Jan 2015 12:58:16 -0800 Subject: MAINTAINERS: move BCM63xx ARM-based SoCs git tree Update the Broadcom BCM63xx ARM-based SoCs git tree from github.com/brcm/linux.git to github.com/broadcom/arm-bcm63xx.git where it now belongs. Signed-off-by: Florian Fainelli --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..ff73215cd165 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2124,7 +2124,7 @@ F: arch/arm/boot/dts/bcm470* BROADCOM BCM63XX ARM ARCHITECTURE M: Florian Fainelli L: linux-arm-kernel@lists.infradead.org -T: git git://git.github.com/brcm/linux.git +T: git git://git.github.com/broadcom/arm-bcm63xx.git S: Maintained F: arch/arm/mach-bcm/bcm63xx.c F: arch/arm/include/debug/bcm63xx.S -- cgit v1.2.3 From afdaee18ea7df2b918fa02e14e84197ec8b18d50 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Fri, 9 Jan 2015 12:59:51 -0800 Subject: MAINTAINERS: update Broadcom Cygnus SoC git tree The Cygnus SoC git tree is moved from github.com/brcm/linux.git to its own git tree at github.com/broadcom/cygnus-linux.git. Signed-off-by: Florian Fainelli --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ff73215cd165..676125dda5a6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2192,7 +2192,7 @@ M: Ray Jui M: Scott Branden L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) L: bcm-kernel-feedback-list@broadcom.com -T: git git://git.github.com/brcm/linux.git +T: git git://git.github.com/broadcom/cygnus-linux.git S: Maintained N: iproc N: cygnus -- cgit v1.2.3 From bc5e45960836c45b1a69ffd09fd93bacd0b2b32b Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Fri, 9 Jan 2015 13:01:26 -0800 Subject: MAINTAINERS: add a git entry for BCM7xxx ARM-based SoCs Use github.com/broadcom/stblinux.git as our default development tree for Broadcom BCM7xxx ARM-based SoCs. Signed-off-by: Florian Fainelli --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 676125dda5a6..2789744cace4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2141,6 +2141,7 @@ M: Brian Norris M: Gregory Fong M: Florian Fainelli L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) +T: git git://git.github.com/broadcom/stblinux.git S: Maintained F: arch/arm/mach-bcm/*brcmstb* F: arch/arm/boot/dts/bcm7*.dts* -- cgit v1.2.3 From 0048f5f3790dc4aa0d22c2612fadf39b3696b439 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Fri, 9 Jan 2015 13:02:54 -0800 Subject: MAINTAINERS: add a git entry for BMIPS-based BCM7xxx SoCs Add a git tree entry for the BMIPS-based BCM7xxx SoCs located at github.com/broadcom/stblinux.git. Signed-off-by: Florian Fainelli --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2789744cace4..1e382ed55000 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2151,6 +2151,7 @@ BROADCOM BMIPS MIPS ARCHITECTURE M: Kevin Cernekee M: Florian Fainelli L: linux-mips@linux-mips.org +T: git git://git.github.com/broadcom/stblinux.git S: Maintained F: arch/mips/bmips/* F: arch/mips/include/asm/mach-bmips/* -- cgit v1.2.3 From 933685cacb0483d43345273c9e83f7ff0c8dc4bd Mon Sep 17 00:00:00 2001 From: Thomas Graf Date: Tue, 13 Jan 2015 01:01:24 +0100 Subject: rhashtable: Add MAINTAINERS entry Signed-off-by: Thomas Graf Signed-off-by: David S. Miller --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 33098956d930..0c2983a62852 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8014,6 +8014,13 @@ S: Maintained F: Documentation/rfkill.txt F: net/rfkill/ +RHASHTABLE +M: Thomas Graf +L: netdev@vger.kernel.org +S: Maintained +F: lib/rhashtable.c +F: include/linux/rhashtable.h + RICOH SMARTMEDIA/XD DRIVER M: Maxim Levitsky S: Maintained -- cgit v1.2.3 From 5f58c97099f20cb12b463d3bb8bf0cbe522deb22 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 12 Jan 2015 19:42:14 +0100 Subject: ARM: at91: move debug-macro.S into the common space Move debug-macro.S from include/mach/ to include/debug where all other common debug macros are. Signed-off-by: Alexandre Belloni Acked-by: Boris Brezillon Signed-off-by: Nicolas Ferre --- MAINTAINERS | 1 + arch/arm/Kconfig.debug | 9 +++-- arch/arm/include/debug/at91.S | 49 +++++++++++++++++++++++++++ arch/arm/mach-at91/include/mach/debug-macro.S | 46 ------------------------- 4 files changed, 57 insertions(+), 48 deletions(-) create mode 100644 arch/arm/include/debug/at91.S delete mode 100644 arch/arm/mach-at91/include/mach/debug-macro.S (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3589d67437f8..8c25979a3c43 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -877,6 +877,7 @@ F: arch/arm/boot/dts/at91*.dts F: arch/arm/boot/dts/at91*.dtsi F: arch/arm/boot/dts/sama*.dts F: arch/arm/boot/dts/sama*.dtsi +F: arch/arm/include/debug/at91.S ARM/ATMEL AT91 Clock Support M: Boris Brezillon diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 5ddd4906f7a7..e34d24949c6a 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -115,15 +115,18 @@ choice 0x80024000 | 0xf0024000 | UART9 config AT91_DEBUG_LL_DBGU0 - bool "Kernel low-level debugging on rm9200, 9260/9g20, 9261/9g10 and 9rl" + bool "Kernel low-level debugging on rm9200, 9260/9g20, 9261/9g10, 9rl, 9x5, 9n12" + select DEBUG_AT91_UART depends on HAVE_AT91_DBGU0 config AT91_DEBUG_LL_DBGU1 - bool "Kernel low-level debugging on 9263 and 9g45" + bool "Kernel low-level debugging on 9263, 9g45 and sama5d3" + select DEBUG_AT91_UART depends on HAVE_AT91_DBGU1 config AT91_DEBUG_LL_DBGU2 bool "Kernel low-level debugging on sama5d4" + select DEBUG_AT91_UART depends on HAVE_AT91_DBGU2 config DEBUG_BCM2835 @@ -1165,6 +1168,8 @@ config DEBUG_LL_INCLUDE string default "debug/sa1100.S" if DEBUG_SA1100 default "debug/8250.S" if DEBUG_LL_UART_8250 || DEBUG_UART_8250 + default "debug/at91.S" if AT91_DEBUG_LL_DBGU0 || AT91_DEBUG_LL_DBGU1 || \ + AT91_DEBUG_LL_DBGU2 default "debug/asm9260.S" if DEBUG_ASM9260_UART default "debug/clps711x.S" if DEBUG_CLPS711X_UART1 || DEBUG_CLPS711X_UART2 default "debug/meson.S" if DEBUG_MESON_UARTAO diff --git a/arch/arm/include/debug/at91.S b/arch/arm/include/debug/at91.S new file mode 100644 index 000000000000..80a6501b4d50 --- /dev/null +++ b/arch/arm/include/debug/at91.S @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2005 SAN People + * + * Debugging macro include header + * + * 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. + * +*/ + +#if defined(CONFIG_AT91_DEBUG_LL_DBGU0) +#define AT91_DBGU 0xfffff200 /* AT91_BASE_DBGU0 */ +#elif defined(CONFIG_AT91_DEBUG_LL_DBGU1) +#define AT91_DBGU 0xffffee00 /* AT91_BASE_DBGU1 */ +#else +/* On sama5d4, use USART3 as low level serial console */ +#define AT91_DBGU 0xfc00c000 /* SAMA5D4_BASE_USART3 */ +#endif + +/* Keep in sync with mach-at91/include/mach/hardware.h */ +#define AT91_IO_P2V(x) ((x) - 0x01000000) + +#define AT91_DBGU_SR (0x14) /* Status Register */ +#define AT91_DBGU_THR (0x1c) /* Transmitter Holding Register */ +#define AT91_DBGU_TXRDY (1 << 1) /* Transmitter Ready */ +#define AT91_DBGU_TXEMPTY (1 << 9) /* Transmitter Empty */ + + .macro addruart, rp, rv, tmp + ldr \rp, =AT91_DBGU @ System peripherals (phys address) + ldr \rv, =AT91_IO_P2V(AT91_DBGU) @ System peripherals (virt address) + .endm + + .macro senduart,rd,rx + strb \rd, [\rx, #(AT91_DBGU_THR)] @ Write to Transmitter Holding Register + .endm + + .macro waituart,rd,rx +1001: ldr \rd, [\rx, #(AT91_DBGU_SR)] @ Read Status Register + tst \rd, #AT91_DBGU_TXRDY @ DBGU_TXRDY = 1 when ready to transmit + beq 1001b + .endm + + .macro busyuart,rd,rx +1001: ldr \rd, [\rx, #(AT91_DBGU_SR)] @ Read Status Register + tst \rd, #AT91_DBGU_TXEMPTY @ DBGU_TXEMPTY = 1 when transmission complete + beq 1001b + .endm + diff --git a/arch/arm/mach-at91/include/mach/debug-macro.S b/arch/arm/mach-at91/include/mach/debug-macro.S deleted file mode 100644 index 2103a90f2261..000000000000 --- a/arch/arm/mach-at91/include/mach/debug-macro.S +++ /dev/null @@ -1,46 +0,0 @@ -/* - * arch/arm/mach-at91/include/mach/debug-macro.S - * - * Copyright (C) 2003-2005 SAN People - * - * Debugging macro include header - * - * 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 -#include - -#if defined(CONFIG_AT91_DEBUG_LL_DBGU0) -#define AT91_DBGU AT91_BASE_DBGU0 -#elif defined(CONFIG_AT91_DEBUG_LL_DBGU1) -#define AT91_DBGU AT91_BASE_DBGU1 -#else -/* On sama5d4, use USART3 as low level serial console */ -#define AT91_DBGU SAMA5D4_BASE_USART3 -#endif - - .macro addruart, rp, rv, tmp - ldr \rp, =AT91_DBGU @ System peripherals (phys address) - ldr \rv, =AT91_IO_P2V(AT91_DBGU) @ System peripherals (virt address) - .endm - - .macro senduart,rd,rx - strb \rd, [\rx, #(AT91_DBGU_THR)] @ Write to Transmitter Holding Register - .endm - - .macro waituart,rd,rx -1001: ldr \rd, [\rx, #(AT91_DBGU_SR)] @ Read Status Register - tst \rd, #AT91_DBGU_TXRDY @ DBGU_TXRDY = 1 when ready to transmit - beq 1001b - .endm - - .macro busyuart,rd,rx -1001: ldr \rd, [\rx, #(AT91_DBGU_SR)] @ Read Status Register - tst \rd, #AT91_DBGU_TXEMPTY @ DBGU_TXEMPTY = 1 when transmission complete - beq 1001b - .endm - -- cgit v1.2.3 From 1a0f1b279c2c4e5747a682b055d27d88e35bb632 Mon Sep 17 00:00:00 2001 From: Ashley Lai Date: Thu, 4 Dec 2014 21:01:51 -0600 Subject: tpm_ibmvtpm: Update email address in maintainers list and ibmvtpm driver Added myself as a maintainer for the IBM vtpm driver and removed myself from the tpm maintainer list. Also, updated the tpm_ibmvtpm driver with my current email address. Signed-off-by: Ashley Lai Signed-off-by: Peter Huewe --- MAINTAINERS | 8 +++++++- drivers/char/tpm/tpm_ibmvtpm.c | 2 +- drivers/char/tpm/tpm_ibmvtpm.h | 2 +- drivers/char/tpm/tpm_of.c | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 600d2aad8276..e18aef54b4b2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9720,13 +9720,19 @@ F: drivers/media/pci/tw68/ TPM DEVICE DRIVER M: Peter Huewe -M: Ashley Lai M: Marcel Selhorst W: http://tpmdd.sourceforge.net L: tpmdd-devel@lists.sourceforge.net (moderated for non-subscribers) S: Maintained F: drivers/char/tpm/ +TPM IBM_VTPM DEVICE DRIVER +M: Ashley Lai +W: http://tpmdd.sourceforge.net +L: tpmdd-devel@lists.sourceforge.net (moderated for non-subscribers) +S: Maintained +F: drivers/char/tpm/tpm_ibmvtpm* + TRACING M: Steven Rostedt M: Ingo Molnar diff --git a/drivers/char/tpm/tpm_ibmvtpm.c b/drivers/char/tpm/tpm_ibmvtpm.c index 4109222f2878..96f5d448b84c 100644 --- a/drivers/char/tpm/tpm_ibmvtpm.c +++ b/drivers/char/tpm/tpm_ibmvtpm.c @@ -1,7 +1,7 @@ /* * Copyright (C) 2012 IBM Corporation * - * Author: Ashley Lai + * Author: Ashley Lai * * Maintained by: * diff --git a/drivers/char/tpm/tpm_ibmvtpm.h b/drivers/char/tpm/tpm_ibmvtpm.h index bd82a791f995..f595f14426bf 100644 --- a/drivers/char/tpm/tpm_ibmvtpm.h +++ b/drivers/char/tpm/tpm_ibmvtpm.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2012 IBM Corporation * - * Author: Ashley Lai + * Author: Ashley Lai * * Maintained by: * diff --git a/drivers/char/tpm/tpm_of.c b/drivers/char/tpm/tpm_of.c index 98ba2bd1a355..c002d1bd9caf 100644 --- a/drivers/char/tpm/tpm_of.c +++ b/drivers/char/tpm/tpm_of.c @@ -1,7 +1,7 @@ /* * Copyright 2012 IBM Corporation * - * Author: Ashley Lai + * Author: Ashley Lai * * Maintained by: * -- cgit v1.2.3 From f78c81b429ccb0a0574a1f169d208ce159007cb1 Mon Sep 17 00:00:00 2001 From: Peter Huewe Date: Sat, 17 Jan 2015 15:17:51 +0100 Subject: MAINTAINERS: Add Patchwork and Git URL for TPMDD Maybe it helps people finding the right tree and also simplifies tracking of their patches Signed-off-by: Peter Huewe --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index e18aef54b4b2..ddf762511cb3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9723,6 +9723,8 @@ M: Peter Huewe M: Marcel Selhorst W: http://tpmdd.sourceforge.net L: tpmdd-devel@lists.sourceforge.net (moderated for non-subscribers) +Q: git git://github.com/PeterHuewe/linux-tpmdd.git +T: https://github.com/PeterHuewe/linux-tpmdd S: Maintained F: drivers/char/tpm/ -- cgit v1.2.3 From 0c7665c356021c10c3f45a620f3f12ad599850d5 Mon Sep 17 00:00:00 2001 From: Max Filippov Date: Mon, 12 Jan 2015 10:20:46 +0300 Subject: clk: TI CDCE706 clock synthesizer driver The driver allows using CDCE706 in its default configuration recorded in EEPROM and adjusting of synthesized clocks by consumers. Signed-off-by: Max Filippov Signed-off-by: Michael Turquette --- .../devicetree/bindings/clock/ti,cdce706.txt | 42 ++ MAINTAINERS | 5 + drivers/clk/Kconfig | 8 + drivers/clk/Makefile | 1 + drivers/clk/clk-cdce706.c | 700 +++++++++++++++++++++ 5 files changed, 756 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/ti,cdce706.txt create mode 100644 drivers/clk/clk-cdce706.c (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/clock/ti,cdce706.txt b/Documentation/devicetree/bindings/clock/ti,cdce706.txt new file mode 100644 index 000000000000..616836e7e1e2 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/ti,cdce706.txt @@ -0,0 +1,42 @@ +Bindings for Texas Instruments CDCE706 programmable 3-PLL clock +synthesizer/multiplier/divider. + +Reference: http://www.ti.com/lit/ds/symlink/cdce706.pdf + +I2C device node required properties: +- compatible: shall be "ti,cdce706". +- reg: i2c device address, shall be in range [0x68...0x6b]. +- #clock-cells: from common clock binding; shall be set to 1. +- clocks: from common clock binding; list of parent clock + handles, shall be reference clock(s) connected to CLK_IN0 + and CLK_IN1 pins. +- clock-names: shall be clk_in0 and/or clk_in1. Use clk_in0 + in case of crystal oscillator or differential signal input + configuration. Use clk_in0 and clk_in1 in case of independent + single-ended LVCMOS inputs configuration. + +Example: + + clocks { + clk54: clk54 { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <54000000>; + }; + }; + ... + i2c0: i2c-master@0d090000 { + ... + cdce706: clock-synth@69 { + compatible = "ti,cdce706"; + #clock-cells = <1>; + reg = <0x69>; + clocks = <&clk54>; + clock-names = "clk_in0"; + }; + }; + ... + simple-audio-card,codec { + ... + clocks = <&cdce706 4>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..2164026b65ee 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9536,6 +9536,11 @@ L: linux-pm@vger.kernel.org S: Supported F: drivers/thermal/ti-soc-thermal/ +TI CDCE706 CLOCK DRIVER +M: Max Filippov +S: Maintained +F: drivers/clk/clk-cdce706.c + TI CLOCK DRIVER M: Tero Kristo L: linux-omap@vger.kernel.org diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 3f44f292d066..975af6a3c20d 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -134,6 +134,14 @@ config COMMON_CLK_PXA ---help--- Sypport for the Marvell PXA SoC. +config COMMON_CLK_CDCE706 + tristate "Clock driver for TI CDCE706 clock synthesizer" + depends on I2C + select REGMAP_I2C + select RATIONAL + ---help--- + This driver supports TI CDCE706 programmable 3-PLL clock synthesizer. + source "drivers/clk/qcom/Kconfig" endmenu diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index d5fba5bc6e1b..929e11a3546e 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -19,6 +19,7 @@ endif obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o obj-$(CONFIG_ARCH_AXXIA) += clk-axm5516.o obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o +obj-$(CONFIG_COMMON_CLK_CDCE706) += clk-cdce706.o obj-$(CONFIG_ARCH_CLPS711X) += clk-clps711x.o obj-$(CONFIG_ARCH_EFM32) += clk-efm32gg.o obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o diff --git a/drivers/clk/clk-cdce706.c b/drivers/clk/clk-cdce706.c new file mode 100644 index 000000000000..c386ad25beb4 --- /dev/null +++ b/drivers/clk/clk-cdce706.c @@ -0,0 +1,700 @@ +/* + * TI CDCE706 programmable 3-PLL clock synthesizer driver + * + * Copyright (c) 2014 Cadence Design Systems Inc. + * + * Reference: http://www.ti.com/lit/ds/symlink/cdce706.pdf + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CDCE706_CLKIN_CLOCK 10 +#define CDCE706_CLKIN_SOURCE 11 +#define CDCE706_PLL_M_LOW(pll) (1 + 3 * (pll)) +#define CDCE706_PLL_N_LOW(pll) (2 + 3 * (pll)) +#define CDCE706_PLL_HI(pll) (3 + 3 * (pll)) +#define CDCE706_PLL_MUX 3 +#define CDCE706_PLL_FVCO 6 +#define CDCE706_DIVIDER(div) (13 + (div)) +#define CDCE706_CLKOUT(out) (19 + (out)) + +#define CDCE706_CLKIN_CLOCK_MASK 0x10 +#define CDCE706_CLKIN_SOURCE_SHIFT 6 +#define CDCE706_CLKIN_SOURCE_MASK 0xc0 +#define CDCE706_CLKIN_SOURCE_LVCMOS 0x40 + +#define CDCE706_PLL_MUX_MASK(pll) (0x80 >> (pll)) +#define CDCE706_PLL_LOW_M_MASK 0xff +#define CDCE706_PLL_LOW_N_MASK 0xff +#define CDCE706_PLL_HI_M_MASK 0x1 +#define CDCE706_PLL_HI_N_MASK 0x1e +#define CDCE706_PLL_HI_N_SHIFT 1 +#define CDCE706_PLL_M_MAX 0x1ff +#define CDCE706_PLL_N_MAX 0xfff +#define CDCE706_PLL_FVCO_MASK(pll) (0x80 >> (pll)) +#define CDCE706_PLL_FREQ_MIN 80000000 +#define CDCE706_PLL_FREQ_MAX 300000000 +#define CDCE706_PLL_FREQ_HI 180000000 + +#define CDCE706_DIVIDER_PLL(div) (9 + (div) - ((div) > 2) - ((div) > 4)) +#define CDCE706_DIVIDER_PLL_SHIFT(div) ((div) < 2 ? 5 : 3 * ((div) & 1)) +#define CDCE706_DIVIDER_PLL_MASK(div) (0x7 << CDCE706_DIVIDER_PLL_SHIFT(div)) +#define CDCE706_DIVIDER_DIVIDER_MASK 0x7f +#define CDCE706_DIVIDER_DIVIDER_MAX 0x7f + +#define CDCE706_CLKOUT_DIVIDER_MASK 0x7 +#define CDCE706_CLKOUT_ENABLE_MASK 0x8 + +static struct regmap_config cdce706_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +#define to_hw_data(phw) (container_of((phw), struct cdce706_hw_data, hw)) + +struct cdce706_hw_data { + struct cdce706_dev_data *dev_data; + unsigned idx; + unsigned parent; + struct clk *clk; + struct clk_hw hw; + unsigned div; + unsigned mul; + unsigned mux; +}; + +struct cdce706_dev_data { + struct i2c_client *client; + struct regmap *regmap; + struct clk_onecell_data onecell; + struct clk *clks[6]; + struct clk *clkin_clk[2]; + const char *clkin_name[2]; + struct cdce706_hw_data clkin[1]; + struct cdce706_hw_data pll[3]; + struct cdce706_hw_data divider[6]; + struct cdce706_hw_data clkout[6]; +}; + +static const char * const cdce706_source_name[] = { + "clk_in0", "clk_in1", +}; + +static const char *cdce706_clkin_name[] = { + "clk_in", +}; + +static const char * const cdce706_pll_name[] = { + "pll1", "pll2", "pll3", +}; + +static const char *cdce706_divider_parent_name[] = { + "clk_in", "pll1", "pll2", "pll2", "pll3", +}; + +static const char *cdce706_divider_name[] = { + "p0", "p1", "p2", "p3", "p4", "p5", +}; + +static const char * const cdce706_clkout_name[] = { + "clk_out0", "clk_out1", "clk_out2", "clk_out3", "clk_out4", "clk_out5", +}; + +static int cdce706_reg_read(struct cdce706_dev_data *dev_data, unsigned reg, + unsigned *val) +{ + int rc = regmap_read(dev_data->regmap, reg | 0x80, val); + + if (rc < 0) + dev_err(&dev_data->client->dev, "error reading reg %u", reg); + return rc; +} + +static int cdce706_reg_write(struct cdce706_dev_data *dev_data, unsigned reg, + unsigned val) +{ + int rc = regmap_write(dev_data->regmap, reg | 0x80, val); + + if (rc < 0) + dev_err(&dev_data->client->dev, "error writing reg %u", reg); + return rc; +} + +static int cdce706_reg_update(struct cdce706_dev_data *dev_data, unsigned reg, + unsigned mask, unsigned val) +{ + int rc = regmap_update_bits(dev_data->regmap, reg | 0x80, mask, val); + + if (rc < 0) + dev_err(&dev_data->client->dev, "error updating reg %u", reg); + return rc; +} + +static int cdce706_clkin_set_parent(struct clk_hw *hw, u8 index) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + hwd->parent = index; + return 0; +} + +static u8 cdce706_clkin_get_parent(struct clk_hw *hw) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + return hwd->parent; +} + +static const struct clk_ops cdce706_clkin_ops = { + .set_parent = cdce706_clkin_set_parent, + .get_parent = cdce706_clkin_get_parent, +}; + +static unsigned long cdce706_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + dev_dbg(&hwd->dev_data->client->dev, + "%s, pll: %d, mux: %d, mul: %u, div: %u\n", + __func__, hwd->idx, hwd->mux, hwd->mul, hwd->div); + + if (!hwd->mux) { + if (hwd->div && hwd->mul) { + u64 res = (u64)parent_rate * hwd->mul; + + do_div(res, hwd->div); + return res; + } + } else { + if (hwd->div) + return parent_rate / hwd->div; + } + return 0; +} + +static long cdce706_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + unsigned long mul, div; + u64 res; + + dev_dbg(&hwd->dev_data->client->dev, + "%s, rate: %lu, parent_rate: %lu\n", + __func__, rate, *parent_rate); + + rational_best_approximation(rate, *parent_rate, + CDCE706_PLL_N_MAX, CDCE706_PLL_M_MAX, + &mul, &div); + hwd->mul = mul; + hwd->div = div; + + dev_dbg(&hwd->dev_data->client->dev, + "%s, pll: %d, mul: %lu, div: %lu\n", + __func__, hwd->idx, mul, div); + + res = (u64)*parent_rate * hwd->mul; + do_div(res, hwd->div); + return res; +} + +static int cdce706_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + unsigned long mul = hwd->mul, div = hwd->div; + int err; + + dev_dbg(&hwd->dev_data->client->dev, + "%s, pll: %d, mul: %lu, div: %lu\n", + __func__, hwd->idx, mul, div); + + err = cdce706_reg_update(hwd->dev_data, + CDCE706_PLL_HI(hwd->idx), + CDCE706_PLL_HI_M_MASK | CDCE706_PLL_HI_N_MASK, + ((div >> 8) & CDCE706_PLL_HI_M_MASK) | + ((mul >> (8 - CDCE706_PLL_HI_N_SHIFT)) & + CDCE706_PLL_HI_N_MASK)); + if (err < 0) + return err; + + err = cdce706_reg_write(hwd->dev_data, + CDCE706_PLL_M_LOW(hwd->idx), + div & CDCE706_PLL_LOW_M_MASK); + if (err < 0) + return err; + + err = cdce706_reg_write(hwd->dev_data, + CDCE706_PLL_N_LOW(hwd->idx), + mul & CDCE706_PLL_LOW_N_MASK); + if (err < 0) + return err; + + err = cdce706_reg_update(hwd->dev_data, + CDCE706_PLL_FVCO, + CDCE706_PLL_FVCO_MASK(hwd->idx), + rate > CDCE706_PLL_FREQ_HI ? + CDCE706_PLL_FVCO_MASK(hwd->idx) : 0); + return err; +} + +static const struct clk_ops cdce706_pll_ops = { + .recalc_rate = cdce706_pll_recalc_rate, + .round_rate = cdce706_pll_round_rate, + .set_rate = cdce706_pll_set_rate, +}; + +static int cdce706_divider_set_parent(struct clk_hw *hw, u8 index) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + if (hwd->parent == index) + return 0; + hwd->parent = index; + return cdce706_reg_update(hwd->dev_data, + CDCE706_DIVIDER_PLL(hwd->idx), + CDCE706_DIVIDER_PLL_MASK(hwd->idx), + index << CDCE706_DIVIDER_PLL_SHIFT(hwd->idx)); +} + +static u8 cdce706_divider_get_parent(struct clk_hw *hw) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + return hwd->parent; +} + +static unsigned long cdce706_divider_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + dev_dbg(&hwd->dev_data->client->dev, + "%s, divider: %d, div: %u\n", + __func__, hwd->idx, hwd->div); + if (hwd->div) + return parent_rate / hwd->div; + return 0; +} + +static long cdce706_divider_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + struct cdce706_dev_data *cdce = hwd->dev_data; + unsigned long mul, div; + + dev_dbg(&hwd->dev_data->client->dev, + "%s, rate: %lu, parent_rate: %lu\n", + __func__, rate, *parent_rate); + + rational_best_approximation(rate, *parent_rate, + 1, CDCE706_DIVIDER_DIVIDER_MAX, + &mul, &div); + if (!mul) + div = CDCE706_DIVIDER_DIVIDER_MAX; + + if (__clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT) { + unsigned long best_diff = rate; + unsigned long best_div = 0; + struct clk *gp_clk = cdce->clkin_clk[cdce->clkin[0].parent]; + unsigned long gp_rate = gp_clk ? clk_get_rate(gp_clk) : 0; + + for (div = CDCE706_PLL_FREQ_MIN / rate; best_diff && + div <= CDCE706_PLL_FREQ_MAX / rate; ++div) { + unsigned long n, m; + unsigned long diff; + unsigned long div_rate; + u64 div_rate64; + + if (rate * div < CDCE706_PLL_FREQ_MIN) + continue; + + rational_best_approximation(rate * div, gp_rate, + CDCE706_PLL_N_MAX, + CDCE706_PLL_M_MAX, + &n, &m); + div_rate64 = (u64)gp_rate * n; + do_div(div_rate64, m); + do_div(div_rate64, div); + div_rate = div_rate64; + diff = max(div_rate, rate) - min(div_rate, rate); + + if (diff < best_diff) { + best_diff = diff; + best_div = div; + dev_dbg(&hwd->dev_data->client->dev, + "%s, %lu * %lu / %lu / %lu = %lu\n", + __func__, gp_rate, n, m, div, div_rate); + } + } + + div = best_div; + + dev_dbg(&hwd->dev_data->client->dev, + "%s, altering parent rate: %lu -> %lu\n", + __func__, *parent_rate, rate * div); + *parent_rate = rate * div; + } + hwd->div = div; + + dev_dbg(&hwd->dev_data->client->dev, + "%s, divider: %d, div: %lu\n", + __func__, hwd->idx, div); + + return *parent_rate / div; +} + +static int cdce706_divider_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + dev_dbg(&hwd->dev_data->client->dev, + "%s, divider: %d, div: %u\n", + __func__, hwd->idx, hwd->div); + + return cdce706_reg_update(hwd->dev_data, + CDCE706_DIVIDER(hwd->idx), + CDCE706_DIVIDER_DIVIDER_MASK, + hwd->div); +} + +static const struct clk_ops cdce706_divider_ops = { + .set_parent = cdce706_divider_set_parent, + .get_parent = cdce706_divider_get_parent, + .recalc_rate = cdce706_divider_recalc_rate, + .round_rate = cdce706_divider_round_rate, + .set_rate = cdce706_divider_set_rate, +}; + +static int cdce706_clkout_prepare(struct clk_hw *hw) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + return cdce706_reg_update(hwd->dev_data, CDCE706_CLKOUT(hwd->idx), + CDCE706_CLKOUT_ENABLE_MASK, + CDCE706_CLKOUT_ENABLE_MASK); +} + +static void cdce706_clkout_unprepare(struct clk_hw *hw) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + cdce706_reg_update(hwd->dev_data, CDCE706_CLKOUT(hwd->idx), + CDCE706_CLKOUT_ENABLE_MASK, 0); +} + +static int cdce706_clkout_set_parent(struct clk_hw *hw, u8 index) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + if (hwd->parent == index) + return 0; + hwd->parent = index; + return cdce706_reg_update(hwd->dev_data, + CDCE706_CLKOUT(hwd->idx), + CDCE706_CLKOUT_ENABLE_MASK, index); +} + +static u8 cdce706_clkout_get_parent(struct clk_hw *hw) +{ + struct cdce706_hw_data *hwd = to_hw_data(hw); + + return hwd->parent; +} + +static unsigned long cdce706_clkout_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return parent_rate; +} + +static long cdce706_clkout_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + *parent_rate = rate; + return rate; +} + +static int cdce706_clkout_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return 0; +} + +static const struct clk_ops cdce706_clkout_ops = { + .prepare = cdce706_clkout_prepare, + .unprepare = cdce706_clkout_unprepare, + .set_parent = cdce706_clkout_set_parent, + .get_parent = cdce706_clkout_get_parent, + .recalc_rate = cdce706_clkout_recalc_rate, + .round_rate = cdce706_clkout_round_rate, + .set_rate = cdce706_clkout_set_rate, +}; + +static int cdce706_register_hw(struct cdce706_dev_data *cdce, + struct cdce706_hw_data *hw, unsigned num_hw, + const char * const *clk_names, + struct clk_init_data *init) +{ + unsigned i; + + for (i = 0; i < num_hw; ++i, ++hw) { + init->name = clk_names[i]; + hw->dev_data = cdce; + hw->idx = i; + hw->hw.init = init; + hw->clk = devm_clk_register(&cdce->client->dev, + &hw->hw); + if (IS_ERR(hw->clk)) { + dev_err(&cdce->client->dev, "Failed to register %s\n", + clk_names[i]); + return PTR_ERR(hw->clk); + } + } + return 0; +} + +static int cdce706_register_clkin(struct cdce706_dev_data *cdce) +{ + struct clk_init_data init = { + .ops = &cdce706_clkin_ops, + .parent_names = cdce->clkin_name, + .num_parents = ARRAY_SIZE(cdce->clkin_name), + }; + unsigned i; + int ret; + unsigned clock, source; + + for (i = 0; i < ARRAY_SIZE(cdce->clkin_name); ++i) { + struct clk *parent = devm_clk_get(&cdce->client->dev, + cdce706_source_name[i]); + + if (IS_ERR(parent)) { + cdce->clkin_name[i] = cdce706_source_name[i]; + } else { + cdce->clkin_name[i] = __clk_get_name(parent); + cdce->clkin_clk[i] = parent; + } + } + + ret = cdce706_reg_read(cdce, CDCE706_CLKIN_SOURCE, &source); + if (ret < 0) + return ret; + if ((source & CDCE706_CLKIN_SOURCE_MASK) == + CDCE706_CLKIN_SOURCE_LVCMOS) { + ret = cdce706_reg_read(cdce, CDCE706_CLKIN_CLOCK, &clock); + if (ret < 0) + return ret; + cdce->clkin[0].parent = !!(clock & CDCE706_CLKIN_CLOCK_MASK); + } + + ret = cdce706_register_hw(cdce, cdce->clkin, + ARRAY_SIZE(cdce->clkin), + cdce706_clkin_name, &init); + return ret; +} + +static int cdce706_register_plls(struct cdce706_dev_data *cdce) +{ + struct clk_init_data init = { + .ops = &cdce706_pll_ops, + .parent_names = cdce706_clkin_name, + .num_parents = ARRAY_SIZE(cdce706_clkin_name), + }; + unsigned i; + int ret; + unsigned mux; + + ret = cdce706_reg_read(cdce, CDCE706_PLL_MUX, &mux); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(cdce->pll); ++i) { + unsigned m, n, v; + + ret = cdce706_reg_read(cdce, CDCE706_PLL_M_LOW(i), &m); + if (ret < 0) + return ret; + ret = cdce706_reg_read(cdce, CDCE706_PLL_N_LOW(i), &n); + if (ret < 0) + return ret; + ret = cdce706_reg_read(cdce, CDCE706_PLL_HI(i), &v); + if (ret < 0) + return ret; + cdce->pll[i].div = m | ((v & CDCE706_PLL_HI_M_MASK) << 8); + cdce->pll[i].mul = n | ((v & CDCE706_PLL_HI_N_MASK) << + (8 - CDCE706_PLL_HI_N_SHIFT)); + cdce->pll[i].mux = mux & CDCE706_PLL_MUX_MASK(i); + dev_dbg(&cdce->client->dev, + "%s: i: %u, div: %u, mul: %u, mux: %d\n", __func__, i, + cdce->pll[i].div, cdce->pll[i].mul, cdce->pll[i].mux); + } + + ret = cdce706_register_hw(cdce, cdce->pll, + ARRAY_SIZE(cdce->pll), + cdce706_pll_name, &init); + return ret; +} + +static int cdce706_register_dividers(struct cdce706_dev_data *cdce) +{ + struct clk_init_data init = { + .ops = &cdce706_divider_ops, + .parent_names = cdce706_divider_parent_name, + .num_parents = ARRAY_SIZE(cdce706_divider_parent_name), + .flags = CLK_SET_RATE_PARENT, + }; + unsigned i; + int ret; + + for (i = 0; i < ARRAY_SIZE(cdce->divider); ++i) { + unsigned val; + + ret = cdce706_reg_read(cdce, CDCE706_DIVIDER_PLL(i), &val); + if (ret < 0) + return ret; + cdce->divider[i].parent = + (val & CDCE706_DIVIDER_PLL_MASK(i)) >> + CDCE706_DIVIDER_PLL_SHIFT(i); + + ret = cdce706_reg_read(cdce, CDCE706_DIVIDER(i), &val); + if (ret < 0) + return ret; + cdce->divider[i].div = val & CDCE706_DIVIDER_DIVIDER_MASK; + dev_dbg(&cdce->client->dev, + "%s: i: %u, parent: %u, div: %u\n", __func__, i, + cdce->divider[i].parent, cdce->divider[i].div); + } + + ret = cdce706_register_hw(cdce, cdce->divider, + ARRAY_SIZE(cdce->divider), + cdce706_divider_name, &init); + return ret; +} + +static int cdce706_register_clkouts(struct cdce706_dev_data *cdce) +{ + struct clk_init_data init = { + .ops = &cdce706_clkout_ops, + .parent_names = cdce706_divider_name, + .num_parents = ARRAY_SIZE(cdce706_divider_name), + .flags = CLK_SET_RATE_PARENT, + }; + unsigned i; + int ret; + + for (i = 0; i < ARRAY_SIZE(cdce->clkout); ++i) { + unsigned val; + + ret = cdce706_reg_read(cdce, CDCE706_CLKOUT(i), &val); + if (ret < 0) + return ret; + cdce->clkout[i].parent = val & CDCE706_CLKOUT_DIVIDER_MASK; + dev_dbg(&cdce->client->dev, + "%s: i: %u, parent: %u\n", __func__, i, + cdce->clkout[i].parent); + } + + ret = cdce706_register_hw(cdce, cdce->clkout, + ARRAY_SIZE(cdce->clkout), + cdce706_clkout_name, &init); + for (i = 0; i < ARRAY_SIZE(cdce->clkout); ++i) + cdce->clks[i] = cdce->clkout[i].clk; + + return ret; +} + +static int cdce706_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct cdce706_dev_data *cdce; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + cdce = devm_kzalloc(&client->dev, sizeof(*cdce), GFP_KERNEL); + if (!cdce) + return -ENOMEM; + + cdce->client = client; + cdce->regmap = devm_regmap_init_i2c(client, &cdce706_regmap_config); + if (IS_ERR(cdce->regmap)) { + dev_err(&client->dev, "Failed to initialize regmap\n"); + return -EINVAL; + } + + i2c_set_clientdata(client, cdce); + + ret = cdce706_register_clkin(cdce); + if (ret < 0) + return ret; + ret = cdce706_register_plls(cdce); + if (ret < 0) + return ret; + ret = cdce706_register_dividers(cdce); + if (ret < 0) + return ret; + ret = cdce706_register_clkouts(cdce); + if (ret < 0) + return ret; + cdce->onecell.clks = cdce->clks; + cdce->onecell.clk_num = ARRAY_SIZE(cdce->clks); + ret = of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get, + &cdce->onecell); + + return ret; +} + +static int cdce706_remove(struct i2c_client *client) +{ + return 0; +} + + +#ifdef CONFIG_OF +static const struct of_device_id cdce706_dt_match[] = { + { .compatible = "ti,cdce706" }, + { }, +}; +MODULE_DEVICE_TABLE(of, cdce706_dt_match); +#endif + +static const struct i2c_device_id cdce706_id[] = { + { "cdce706", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cdce706_id); + +static struct i2c_driver cdce706_i2c_driver = { + .driver = { + .name = "cdce706", + .of_match_table = of_match_ptr(cdce706_dt_match), + }, + .probe = cdce706_probe, + .remove = cdce706_remove, + .id_table = cdce706_id, +}; +module_i2c_driver(cdce706_i2c_driver); + +MODULE_AUTHOR("Max Filippov "); +MODULE_DESCRIPTION("TI CDCE 706 clock synthesizer driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From e1d3c0fd701df831169b116cd5c5d6203ac07f70 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 14 Nov 2014 17:18:23 +0000 Subject: iommu: add ARM LPAE page table allocator A number of IOMMUs found in ARM SoCs can walk architecture-compatible page tables. This patch adds a generic allocator for Stage-1 and Stage-2 v7/v8 long-descriptor page tables. 4k, 16k and 64k pages are supported, with up to 4-levels of walk to cover a 48-bit address space. Tested-by: Laurent Pinchart Signed-off-by: Will Deacon --- MAINTAINERS | 1 + drivers/iommu/Kconfig | 9 + drivers/iommu/Makefile | 1 + drivers/iommu/io-pgtable-arm.c | 781 +++++++++++++++++++++++++++++++++++++++++ drivers/iommu/io-pgtable.c | 11 + drivers/iommu/io-pgtable.h | 14 + 6 files changed, 817 insertions(+) create mode 100644 drivers/iommu/io-pgtable-arm.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3589d67437f8..00b27863384e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1589,6 +1589,7 @@ M: Will Deacon L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: drivers/iommu/arm-smmu.c +F: drivers/iommu/io-pgtable-arm.c ARM64 PORT (AARCH64 ARCHITECTURE) M: Catalin Marinas diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index 3faaa41db8ff..306454fbc52d 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -19,6 +19,15 @@ menu "Generic IOMMU Pagetable Support" config IOMMU_IO_PGTABLE bool +config IOMMU_IO_PGTABLE_LPAE + bool "ARMv7/v8 Long Descriptor Format" + select IOMMU_IO_PGTABLE + help + Enable support for the ARM long descriptor pagetable format. + This allocator supports 4K/2M/1G, 16K/32M and 64K/512M page + sizes at both stage-1 and stage-2, as well as address spaces + up to 48-bits in size. + endmenu config OF_IOMMU diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 701c9516e2ae..d6889b487d55 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_IOMMU_API) += iommu.o obj-$(CONFIG_IOMMU_API) += iommu-traces.o obj-$(CONFIG_IOMMU_API) += iommu-sysfs.o obj-$(CONFIG_IOMMU_IO_PGTABLE) += io-pgtable.o +obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o obj-$(CONFIG_OF_IOMMU) += of_iommu.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c new file mode 100644 index 000000000000..dbe6178a53e9 --- /dev/null +++ b/drivers/iommu/io-pgtable-arm.c @@ -0,0 +1,781 @@ +/* + * CPU-agnostic ARM page table allocator. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Copyright (C) 2014 ARM Limited + * + * Author: Will Deacon + */ + +#define pr_fmt(fmt) "arm-lpae io-pgtable: " fmt + +#include +#include +#include +#include +#include + +#include "io-pgtable.h" + +#define ARM_LPAE_MAX_ADDR_BITS 48 +#define ARM_LPAE_S2_MAX_CONCAT_PAGES 16 +#define ARM_LPAE_MAX_LEVELS 4 + +/* Struct accessors */ +#define io_pgtable_to_data(x) \ + container_of((x), struct arm_lpae_io_pgtable, iop) + +#define io_pgtable_ops_to_pgtable(x) \ + container_of((x), struct io_pgtable, ops) + +#define io_pgtable_ops_to_data(x) \ + io_pgtable_to_data(io_pgtable_ops_to_pgtable(x)) + +/* + * For consistency with the architecture, we always consider + * ARM_LPAE_MAX_LEVELS levels, with the walk starting at level n >=0 + */ +#define ARM_LPAE_START_LVL(d) (ARM_LPAE_MAX_LEVELS - (d)->levels) + +/* + * Calculate the right shift amount to get to the portion describing level l + * in a virtual address mapped by the pagetable in d. + */ +#define ARM_LPAE_LVL_SHIFT(l,d) \ + ((((d)->levels - ((l) - ARM_LPAE_START_LVL(d) + 1)) \ + * (d)->bits_per_level) + (d)->pg_shift) + +#define ARM_LPAE_PAGES_PER_PGD(d) ((d)->pgd_size >> (d)->pg_shift) + +/* + * Calculate the index at level l used to map virtual address a using the + * pagetable in d. + */ +#define ARM_LPAE_PGD_IDX(l,d) \ + ((l) == ARM_LPAE_START_LVL(d) ? ilog2(ARM_LPAE_PAGES_PER_PGD(d)) : 0) + +#define ARM_LPAE_LVL_IDX(a,l,d) \ + (((a) >> ARM_LPAE_LVL_SHIFT(l,d)) & \ + ((1 << ((d)->bits_per_level + ARM_LPAE_PGD_IDX(l,d))) - 1)) + +/* Calculate the block/page mapping size at level l for pagetable in d. */ +#define ARM_LPAE_BLOCK_SIZE(l,d) \ + (1 << (ilog2(sizeof(arm_lpae_iopte)) + \ + ((ARM_LPAE_MAX_LEVELS - (l)) * (d)->bits_per_level))) + +/* Page table bits */ +#define ARM_LPAE_PTE_TYPE_SHIFT 0 +#define ARM_LPAE_PTE_TYPE_MASK 0x3 + +#define ARM_LPAE_PTE_TYPE_BLOCK 1 +#define ARM_LPAE_PTE_TYPE_TABLE 3 +#define ARM_LPAE_PTE_TYPE_PAGE 3 + +#define ARM_LPAE_PTE_XN (((arm_lpae_iopte)3) << 53) +#define ARM_LPAE_PTE_AF (((arm_lpae_iopte)1) << 10) +#define ARM_LPAE_PTE_SH_NS (((arm_lpae_iopte)0) << 8) +#define ARM_LPAE_PTE_SH_OS (((arm_lpae_iopte)2) << 8) +#define ARM_LPAE_PTE_SH_IS (((arm_lpae_iopte)3) << 8) +#define ARM_LPAE_PTE_VALID (((arm_lpae_iopte)1) << 0) + +#define ARM_LPAE_PTE_ATTR_LO_MASK (((arm_lpae_iopte)0x3ff) << 2) +/* Ignore the contiguous bit for block splitting */ +#define ARM_LPAE_PTE_ATTR_HI_MASK (((arm_lpae_iopte)6) << 52) +#define ARM_LPAE_PTE_ATTR_MASK (ARM_LPAE_PTE_ATTR_LO_MASK | \ + ARM_LPAE_PTE_ATTR_HI_MASK) + +/* Stage-1 PTE */ +#define ARM_LPAE_PTE_AP_UNPRIV (((arm_lpae_iopte)1) << 6) +#define ARM_LPAE_PTE_AP_RDONLY (((arm_lpae_iopte)2) << 6) +#define ARM_LPAE_PTE_ATTRINDX_SHIFT 2 +#define ARM_LPAE_PTE_nG (((arm_lpae_iopte)1) << 11) + +/* Stage-2 PTE */ +#define ARM_LPAE_PTE_HAP_FAULT (((arm_lpae_iopte)0) << 6) +#define ARM_LPAE_PTE_HAP_READ (((arm_lpae_iopte)1) << 6) +#define ARM_LPAE_PTE_HAP_WRITE (((arm_lpae_iopte)2) << 6) +#define ARM_LPAE_PTE_MEMATTR_OIWB (((arm_lpae_iopte)0xf) << 2) +#define ARM_LPAE_PTE_MEMATTR_NC (((arm_lpae_iopte)0x5) << 2) +#define ARM_LPAE_PTE_MEMATTR_DEV (((arm_lpae_iopte)0x1) << 2) + +/* Register bits */ +#define ARM_32_LPAE_TCR_EAE (1 << 31) +#define ARM_64_LPAE_S2_TCR_RES1 (1 << 31) + +#define ARM_LPAE_TCR_TG0_4K (0 << 14) +#define ARM_LPAE_TCR_TG0_64K (1 << 14) +#define ARM_LPAE_TCR_TG0_16K (2 << 14) + +#define ARM_LPAE_TCR_SH0_SHIFT 12 +#define ARM_LPAE_TCR_SH0_MASK 0x3 +#define ARM_LPAE_TCR_SH_NS 0 +#define ARM_LPAE_TCR_SH_OS 2 +#define ARM_LPAE_TCR_SH_IS 3 + +#define ARM_LPAE_TCR_ORGN0_SHIFT 10 +#define ARM_LPAE_TCR_IRGN0_SHIFT 8 +#define ARM_LPAE_TCR_RGN_MASK 0x3 +#define ARM_LPAE_TCR_RGN_NC 0 +#define ARM_LPAE_TCR_RGN_WBWA 1 +#define ARM_LPAE_TCR_RGN_WT 2 +#define ARM_LPAE_TCR_RGN_WB 3 + +#define ARM_LPAE_TCR_SL0_SHIFT 6 +#define ARM_LPAE_TCR_SL0_MASK 0x3 + +#define ARM_LPAE_TCR_T0SZ_SHIFT 0 +#define ARM_LPAE_TCR_SZ_MASK 0xf + +#define ARM_LPAE_TCR_PS_SHIFT 16 +#define ARM_LPAE_TCR_PS_MASK 0x7 + +#define ARM_LPAE_TCR_IPS_SHIFT 32 +#define ARM_LPAE_TCR_IPS_MASK 0x7 + +#define ARM_LPAE_TCR_PS_32_BIT 0x0ULL +#define ARM_LPAE_TCR_PS_36_BIT 0x1ULL +#define ARM_LPAE_TCR_PS_40_BIT 0x2ULL +#define ARM_LPAE_TCR_PS_42_BIT 0x3ULL +#define ARM_LPAE_TCR_PS_44_BIT 0x4ULL +#define ARM_LPAE_TCR_PS_48_BIT 0x5ULL + +#define ARM_LPAE_MAIR_ATTR_SHIFT(n) ((n) << 3) +#define ARM_LPAE_MAIR_ATTR_MASK 0xff +#define ARM_LPAE_MAIR_ATTR_DEVICE 0x04 +#define ARM_LPAE_MAIR_ATTR_NC 0x44 +#define ARM_LPAE_MAIR_ATTR_WBRWA 0xff +#define ARM_LPAE_MAIR_ATTR_IDX_NC 0 +#define ARM_LPAE_MAIR_ATTR_IDX_CACHE 1 +#define ARM_LPAE_MAIR_ATTR_IDX_DEV 2 + +/* IOPTE accessors */ +#define iopte_deref(pte,d) \ + (__va((pte) & ((1ULL << ARM_LPAE_MAX_ADDR_BITS) - 1) \ + & ~((1ULL << (d)->pg_shift) - 1))) + +#define iopte_type(pte,l) \ + (((pte) >> ARM_LPAE_PTE_TYPE_SHIFT) & ARM_LPAE_PTE_TYPE_MASK) + +#define iopte_prot(pte) ((pte) & ARM_LPAE_PTE_ATTR_MASK) + +#define iopte_leaf(pte,l) \ + (l == (ARM_LPAE_MAX_LEVELS - 1) ? \ + (iopte_type(pte,l) == ARM_LPAE_PTE_TYPE_PAGE) : \ + (iopte_type(pte,l) == ARM_LPAE_PTE_TYPE_BLOCK)) + +#define iopte_to_pfn(pte,d) \ + (((pte) & ((1ULL << ARM_LPAE_MAX_ADDR_BITS) - 1)) >> (d)->pg_shift) + +#define pfn_to_iopte(pfn,d) \ + (((pfn) << (d)->pg_shift) & ((1ULL << ARM_LPAE_MAX_ADDR_BITS) - 1)) + +struct arm_lpae_io_pgtable { + struct io_pgtable iop; + + int levels; + size_t pgd_size; + unsigned long pg_shift; + unsigned long bits_per_level; + + void *pgd; +}; + +typedef u64 arm_lpae_iopte; + +static int arm_lpae_init_pte(struct arm_lpae_io_pgtable *data, + unsigned long iova, phys_addr_t paddr, + arm_lpae_iopte prot, int lvl, + arm_lpae_iopte *ptep) +{ + arm_lpae_iopte pte = prot; + + /* We require an unmap first */ + if (WARN_ON(iopte_leaf(*ptep, lvl))) + return -EEXIST; + + if (lvl == ARM_LPAE_MAX_LEVELS - 1) + pte |= ARM_LPAE_PTE_TYPE_PAGE; + else + pte |= ARM_LPAE_PTE_TYPE_BLOCK; + + pte |= ARM_LPAE_PTE_AF | ARM_LPAE_PTE_SH_IS; + pte |= pfn_to_iopte(paddr >> data->pg_shift, data); + + *ptep = pte; + data->iop.cfg.tlb->flush_pgtable(ptep, sizeof(*ptep), data->iop.cookie); + return 0; +} + +static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova, + phys_addr_t paddr, size_t size, arm_lpae_iopte prot, + int lvl, arm_lpae_iopte *ptep) +{ + arm_lpae_iopte *cptep, pte; + void *cookie = data->iop.cookie; + size_t block_size = ARM_LPAE_BLOCK_SIZE(lvl, data); + + /* Find our entry at the current level */ + ptep += ARM_LPAE_LVL_IDX(iova, lvl, data); + + /* If we can install a leaf entry at this level, then do so */ + if (size == block_size && (size & data->iop.cfg.pgsize_bitmap)) + return arm_lpae_init_pte(data, iova, paddr, prot, lvl, ptep); + + /* We can't allocate tables at the final level */ + if (WARN_ON(lvl >= ARM_LPAE_MAX_LEVELS - 1)) + return -EINVAL; + + /* Grab a pointer to the next level */ + pte = *ptep; + if (!pte) { + cptep = alloc_pages_exact(1UL << data->pg_shift, + GFP_ATOMIC | __GFP_ZERO); + if (!cptep) + return -ENOMEM; + + data->iop.cfg.tlb->flush_pgtable(cptep, 1UL << data->pg_shift, + cookie); + pte = __pa(cptep) | ARM_LPAE_PTE_TYPE_TABLE; + *ptep = pte; + data->iop.cfg.tlb->flush_pgtable(ptep, sizeof(*ptep), cookie); + } else { + cptep = iopte_deref(pte, data); + } + + /* Rinse, repeat */ + return __arm_lpae_map(data, iova, paddr, size, prot, lvl + 1, cptep); +} + +static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data, + int prot) +{ + arm_lpae_iopte pte; + + if (data->iop.fmt == ARM_64_LPAE_S1 || + data->iop.fmt == ARM_32_LPAE_S1) { + pte = ARM_LPAE_PTE_AP_UNPRIV | ARM_LPAE_PTE_nG; + + if (!(prot & IOMMU_WRITE) && (prot & IOMMU_READ)) + pte |= ARM_LPAE_PTE_AP_RDONLY; + + if (prot & IOMMU_CACHE) + pte |= (ARM_LPAE_MAIR_ATTR_IDX_CACHE + << ARM_LPAE_PTE_ATTRINDX_SHIFT); + } else { + pte = ARM_LPAE_PTE_HAP_FAULT; + if (prot & IOMMU_READ) + pte |= ARM_LPAE_PTE_HAP_READ; + if (prot & IOMMU_WRITE) + pte |= ARM_LPAE_PTE_HAP_WRITE; + if (prot & IOMMU_CACHE) + pte |= ARM_LPAE_PTE_MEMATTR_OIWB; + else + pte |= ARM_LPAE_PTE_MEMATTR_NC; + } + + if (prot & IOMMU_NOEXEC) + pte |= ARM_LPAE_PTE_XN; + + return pte; +} + +static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova, + phys_addr_t paddr, size_t size, int iommu_prot) +{ + struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); + arm_lpae_iopte *ptep = data->pgd; + int lvl = ARM_LPAE_START_LVL(data); + arm_lpae_iopte prot; + + /* If no access, then nothing to do */ + if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE))) + return 0; + + prot = arm_lpae_prot_to_pte(data, iommu_prot); + return __arm_lpae_map(data, iova, paddr, size, prot, lvl, ptep); +} + +static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, + arm_lpae_iopte *ptep) +{ + arm_lpae_iopte *start, *end; + unsigned long table_size; + + /* Only leaf entries at the last level */ + if (lvl == ARM_LPAE_MAX_LEVELS - 1) + return; + + if (lvl == ARM_LPAE_START_LVL(data)) + table_size = data->pgd_size; + else + table_size = 1UL << data->pg_shift; + + start = ptep; + end = (void *)ptep + table_size; + + while (ptep != end) { + arm_lpae_iopte pte = *ptep++; + + if (!pte || iopte_leaf(pte, lvl)) + continue; + + __arm_lpae_free_pgtable(data, lvl + 1, iopte_deref(pte, data)); + } + + free_pages_exact(start, table_size); +} + +static void arm_lpae_free_pgtable(struct io_pgtable *iop) +{ + struct arm_lpae_io_pgtable *data = io_pgtable_to_data(iop); + + __arm_lpae_free_pgtable(data, ARM_LPAE_START_LVL(data), data->pgd); + kfree(data); +} + +static int arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data, + unsigned long iova, size_t size, + arm_lpae_iopte prot, int lvl, + arm_lpae_iopte *ptep, size_t blk_size) +{ + unsigned long blk_start, blk_end; + phys_addr_t blk_paddr; + arm_lpae_iopte table = 0; + void *cookie = data->iop.cookie; + const struct iommu_gather_ops *tlb = data->iop.cfg.tlb; + + blk_start = iova & ~(blk_size - 1); + blk_end = blk_start + blk_size; + blk_paddr = iopte_to_pfn(*ptep, data) << data->pg_shift; + + for (; blk_start < blk_end; blk_start += size, blk_paddr += size) { + arm_lpae_iopte *tablep; + + /* Unmap! */ + if (blk_start == iova) + continue; + + /* __arm_lpae_map expects a pointer to the start of the table */ + tablep = &table - ARM_LPAE_LVL_IDX(blk_start, lvl, data); + if (__arm_lpae_map(data, blk_start, blk_paddr, size, prot, lvl, + tablep) < 0) { + if (table) { + /* Free the table we allocated */ + tablep = iopte_deref(table, data); + __arm_lpae_free_pgtable(data, lvl + 1, tablep); + } + return 0; /* Bytes unmapped */ + } + } + + *ptep = table; + tlb->flush_pgtable(ptep, sizeof(*ptep), cookie); + iova &= ~(blk_size - 1); + tlb->tlb_add_flush(iova, blk_size, true, cookie); + return size; +} + +static int __arm_lpae_unmap(struct arm_lpae_io_pgtable *data, + unsigned long iova, size_t size, int lvl, + arm_lpae_iopte *ptep) +{ + arm_lpae_iopte pte; + const struct iommu_gather_ops *tlb = data->iop.cfg.tlb; + void *cookie = data->iop.cookie; + size_t blk_size = ARM_LPAE_BLOCK_SIZE(lvl, data); + + ptep += ARM_LPAE_LVL_IDX(iova, lvl, data); + pte = *ptep; + + /* Something went horribly wrong and we ran out of page table */ + if (WARN_ON(!pte || (lvl == ARM_LPAE_MAX_LEVELS))) + return 0; + + /* If the size matches this level, we're in the right place */ + if (size == blk_size) { + *ptep = 0; + tlb->flush_pgtable(ptep, sizeof(*ptep), cookie); + + if (!iopte_leaf(pte, lvl)) { + /* Also flush any partial walks */ + tlb->tlb_add_flush(iova, size, false, cookie); + tlb->tlb_sync(data->iop.cookie); + ptep = iopte_deref(pte, data); + __arm_lpae_free_pgtable(data, lvl + 1, ptep); + } else { + tlb->tlb_add_flush(iova, size, true, cookie); + } + + return size; + } else if (iopte_leaf(pte, lvl)) { + /* + * Insert a table at the next level to map the old region, + * minus the part we want to unmap + */ + return arm_lpae_split_blk_unmap(data, iova, size, + iopte_prot(pte), lvl, ptep, + blk_size); + } + + /* Keep on walkin' */ + ptep = iopte_deref(pte, data); + return __arm_lpae_unmap(data, iova, size, lvl + 1, ptep); +} + +static int arm_lpae_unmap(struct io_pgtable_ops *ops, unsigned long iova, + size_t size) +{ + size_t unmapped; + struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); + struct io_pgtable *iop = &data->iop; + arm_lpae_iopte *ptep = data->pgd; + int lvl = ARM_LPAE_START_LVL(data); + + unmapped = __arm_lpae_unmap(data, iova, size, lvl, ptep); + if (unmapped) + iop->cfg.tlb->tlb_sync(iop->cookie); + + return unmapped; +} + +static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops, + unsigned long iova) +{ + struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops); + arm_lpae_iopte pte, *ptep = data->pgd; + int lvl = ARM_LPAE_START_LVL(data); + + do { + /* Valid IOPTE pointer? */ + if (!ptep) + return 0; + + /* Grab the IOPTE we're interested in */ + pte = *(ptep + ARM_LPAE_LVL_IDX(iova, lvl, data)); + + /* Valid entry? */ + if (!pte) + return 0; + + /* Leaf entry? */ + if (iopte_leaf(pte,lvl)) + goto found_translation; + + /* Take it to the next level */ + ptep = iopte_deref(pte, data); + } while (++lvl < ARM_LPAE_MAX_LEVELS); + + /* Ran out of page tables to walk */ + return 0; + +found_translation: + iova &= ((1 << data->pg_shift) - 1); + return ((phys_addr_t)iopte_to_pfn(pte,data) << data->pg_shift) | iova; +} + +static void arm_lpae_restrict_pgsizes(struct io_pgtable_cfg *cfg) +{ + unsigned long granule; + + /* + * We need to restrict the supported page sizes to match the + * translation regime for a particular granule. Aim to match + * the CPU page size if possible, otherwise prefer smaller sizes. + * While we're at it, restrict the block sizes to match the + * chosen granule. + */ + if (cfg->pgsize_bitmap & PAGE_SIZE) + granule = PAGE_SIZE; + else if (cfg->pgsize_bitmap & ~PAGE_MASK) + granule = 1UL << __fls(cfg->pgsize_bitmap & ~PAGE_MASK); + else if (cfg->pgsize_bitmap & PAGE_MASK) + granule = 1UL << __ffs(cfg->pgsize_bitmap & PAGE_MASK); + else + granule = 0; + + switch (granule) { + case SZ_4K: + cfg->pgsize_bitmap &= (SZ_4K | SZ_2M | SZ_1G); + break; + case SZ_16K: + cfg->pgsize_bitmap &= (SZ_16K | SZ_32M); + break; + case SZ_64K: + cfg->pgsize_bitmap &= (SZ_64K | SZ_512M); + break; + default: + cfg->pgsize_bitmap = 0; + } +} + +static struct arm_lpae_io_pgtable * +arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg) +{ + unsigned long va_bits, pgd_bits; + struct arm_lpae_io_pgtable *data; + + arm_lpae_restrict_pgsizes(cfg); + + if (!(cfg->pgsize_bitmap & (SZ_4K | SZ_16K | SZ_64K))) + return NULL; + + if (cfg->ias > ARM_LPAE_MAX_ADDR_BITS) + return NULL; + + if (cfg->oas > ARM_LPAE_MAX_ADDR_BITS) + return NULL; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->pg_shift = __ffs(cfg->pgsize_bitmap); + data->bits_per_level = data->pg_shift - ilog2(sizeof(arm_lpae_iopte)); + + va_bits = cfg->ias - data->pg_shift; + data->levels = DIV_ROUND_UP(va_bits, data->bits_per_level); + + /* Calculate the actual size of our pgd (without concatenation) */ + pgd_bits = va_bits - (data->bits_per_level * (data->levels - 1)); + data->pgd_size = 1UL << (pgd_bits + ilog2(sizeof(arm_lpae_iopte))); + + data->iop.ops = (struct io_pgtable_ops) { + .map = arm_lpae_map, + .unmap = arm_lpae_unmap, + .iova_to_phys = arm_lpae_iova_to_phys, + }; + + return data; +} + +static struct io_pgtable * +arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie) +{ + u64 reg; + struct arm_lpae_io_pgtable *data = arm_lpae_alloc_pgtable(cfg); + + if (!data) + return NULL; + + /* TCR */ + reg = (ARM_LPAE_TCR_SH_IS << ARM_LPAE_TCR_SH0_SHIFT) | + (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_IRGN0_SHIFT) | + (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_ORGN0_SHIFT); + + switch (1 << data->pg_shift) { + case SZ_4K: + reg |= ARM_LPAE_TCR_TG0_4K; + break; + case SZ_16K: + reg |= ARM_LPAE_TCR_TG0_16K; + break; + case SZ_64K: + reg |= ARM_LPAE_TCR_TG0_64K; + break; + } + + switch (cfg->oas) { + case 32: + reg |= (ARM_LPAE_TCR_PS_32_BIT << ARM_LPAE_TCR_IPS_SHIFT); + break; + case 36: + reg |= (ARM_LPAE_TCR_PS_36_BIT << ARM_LPAE_TCR_IPS_SHIFT); + break; + case 40: + reg |= (ARM_LPAE_TCR_PS_40_BIT << ARM_LPAE_TCR_IPS_SHIFT); + break; + case 42: + reg |= (ARM_LPAE_TCR_PS_42_BIT << ARM_LPAE_TCR_IPS_SHIFT); + break; + case 44: + reg |= (ARM_LPAE_TCR_PS_44_BIT << ARM_LPAE_TCR_IPS_SHIFT); + break; + case 48: + reg |= (ARM_LPAE_TCR_PS_48_BIT << ARM_LPAE_TCR_IPS_SHIFT); + break; + default: + goto out_free_data; + } + + reg |= (64ULL - cfg->ias) << ARM_LPAE_TCR_T0SZ_SHIFT; + cfg->arm_lpae_s1_cfg.tcr = reg; + + /* MAIRs */ + reg = (ARM_LPAE_MAIR_ATTR_NC + << ARM_LPAE_MAIR_ATTR_SHIFT(ARM_LPAE_MAIR_ATTR_IDX_NC)) | + (ARM_LPAE_MAIR_ATTR_WBRWA + << ARM_LPAE_MAIR_ATTR_SHIFT(ARM_LPAE_MAIR_ATTR_IDX_CACHE)) | + (ARM_LPAE_MAIR_ATTR_DEVICE + << ARM_LPAE_MAIR_ATTR_SHIFT(ARM_LPAE_MAIR_ATTR_IDX_DEV)); + + cfg->arm_lpae_s1_cfg.mair[0] = reg; + cfg->arm_lpae_s1_cfg.mair[1] = 0; + + /* Looking good; allocate a pgd */ + data->pgd = alloc_pages_exact(data->pgd_size, GFP_KERNEL | __GFP_ZERO); + if (!data->pgd) + goto out_free_data; + + cfg->tlb->flush_pgtable(data->pgd, data->pgd_size, cookie); + + /* TTBRs */ + cfg->arm_lpae_s1_cfg.ttbr[0] = virt_to_phys(data->pgd); + cfg->arm_lpae_s1_cfg.ttbr[1] = 0; + return &data->iop; + +out_free_data: + kfree(data); + return NULL; +} + +static struct io_pgtable * +arm_64_lpae_alloc_pgtable_s2(struct io_pgtable_cfg *cfg, void *cookie) +{ + u64 reg, sl; + struct arm_lpae_io_pgtable *data = arm_lpae_alloc_pgtable(cfg); + + if (!data) + return NULL; + + /* + * Concatenate PGDs at level 1 if possible in order to reduce + * the depth of the stage-2 walk. + */ + if (data->levels == ARM_LPAE_MAX_LEVELS) { + unsigned long pgd_pages; + + pgd_pages = data->pgd_size >> ilog2(sizeof(arm_lpae_iopte)); + if (pgd_pages <= ARM_LPAE_S2_MAX_CONCAT_PAGES) { + data->pgd_size = pgd_pages << data->pg_shift; + data->levels--; + } + } + + /* VTCR */ + reg = ARM_64_LPAE_S2_TCR_RES1 | + (ARM_LPAE_TCR_SH_IS << ARM_LPAE_TCR_SH0_SHIFT) | + (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_IRGN0_SHIFT) | + (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_ORGN0_SHIFT); + + sl = ARM_LPAE_START_LVL(data); + + switch (1 << data->pg_shift) { + case SZ_4K: + reg |= ARM_LPAE_TCR_TG0_4K; + sl++; /* SL0 format is different for 4K granule size */ + break; + case SZ_16K: + reg |= ARM_LPAE_TCR_TG0_16K; + break; + case SZ_64K: + reg |= ARM_LPAE_TCR_TG0_64K; + break; + } + + switch (cfg->oas) { + case 32: + reg |= (ARM_LPAE_TCR_PS_32_BIT << ARM_LPAE_TCR_PS_SHIFT); + break; + case 36: + reg |= (ARM_LPAE_TCR_PS_36_BIT << ARM_LPAE_TCR_PS_SHIFT); + break; + case 40: + reg |= (ARM_LPAE_TCR_PS_40_BIT << ARM_LPAE_TCR_PS_SHIFT); + break; + case 42: + reg |= (ARM_LPAE_TCR_PS_42_BIT << ARM_LPAE_TCR_PS_SHIFT); + break; + case 44: + reg |= (ARM_LPAE_TCR_PS_44_BIT << ARM_LPAE_TCR_PS_SHIFT); + break; + case 48: + reg |= (ARM_LPAE_TCR_PS_48_BIT << ARM_LPAE_TCR_PS_SHIFT); + break; + default: + goto out_free_data; + } + + reg |= (64ULL - cfg->ias) << ARM_LPAE_TCR_T0SZ_SHIFT; + reg |= (~sl & ARM_LPAE_TCR_SL0_MASK) << ARM_LPAE_TCR_SL0_SHIFT; + cfg->arm_lpae_s2_cfg.vtcr = reg; + + /* Allocate pgd pages */ + data->pgd = alloc_pages_exact(data->pgd_size, GFP_KERNEL | __GFP_ZERO); + if (!data->pgd) + goto out_free_data; + + cfg->tlb->flush_pgtable(data->pgd, data->pgd_size, cookie); + + /* VTTBR */ + cfg->arm_lpae_s2_cfg.vttbr = virt_to_phys(data->pgd); + return &data->iop; + +out_free_data: + kfree(data); + return NULL; +} + +static struct io_pgtable * +arm_32_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie) +{ + struct io_pgtable *iop; + + if (cfg->ias > 32 || cfg->oas > 40) + return NULL; + + cfg->pgsize_bitmap &= (SZ_4K | SZ_2M | SZ_1G); + iop = arm_64_lpae_alloc_pgtable_s1(cfg, cookie); + if (iop) { + cfg->arm_lpae_s1_cfg.tcr |= ARM_32_LPAE_TCR_EAE; + cfg->arm_lpae_s1_cfg.tcr &= 0xffffffff; + } + + return iop; +} + +static struct io_pgtable * +arm_32_lpae_alloc_pgtable_s2(struct io_pgtable_cfg *cfg, void *cookie) +{ + struct io_pgtable *iop; + + if (cfg->ias > 40 || cfg->oas > 40) + return NULL; + + cfg->pgsize_bitmap &= (SZ_4K | SZ_2M | SZ_1G); + iop = arm_64_lpae_alloc_pgtable_s2(cfg, cookie); + if (iop) + cfg->arm_lpae_s2_cfg.vtcr &= 0xffffffff; + + return iop; +} + +struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns = { + .alloc = arm_64_lpae_alloc_pgtable_s1, + .free = arm_lpae_free_pgtable, +}; + +struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s2_init_fns = { + .alloc = arm_64_lpae_alloc_pgtable_s2, + .free = arm_lpae_free_pgtable, +}; + +struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s1_init_fns = { + .alloc = arm_32_lpae_alloc_pgtable_s1, + .free = arm_lpae_free_pgtable, +}; + +struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s2_init_fns = { + .alloc = arm_32_lpae_alloc_pgtable_s2, + .free = arm_lpae_free_pgtable, +}; diff --git a/drivers/iommu/io-pgtable.c b/drivers/iommu/io-pgtable.c index f664a1ca49cf..6436fe24bc2f 100644 --- a/drivers/iommu/io-pgtable.c +++ b/drivers/iommu/io-pgtable.c @@ -24,9 +24,20 @@ #include "io-pgtable.h" +extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s1_init_fns; +extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s2_init_fns; +extern struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns; +extern struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s2_init_fns; + static const struct io_pgtable_init_fns * io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] = { +#ifdef CONFIG_IOMMU_IO_PGTABLE_LPAE + [ARM_32_LPAE_S1] = &io_pgtable_arm_32_lpae_s1_init_fns, + [ARM_32_LPAE_S2] = &io_pgtable_arm_32_lpae_s2_init_fns, + [ARM_64_LPAE_S1] = &io_pgtable_arm_64_lpae_s1_init_fns, + [ARM_64_LPAE_S2] = &io_pgtable_arm_64_lpae_s2_init_fns, +#endif }; struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt, diff --git a/drivers/iommu/io-pgtable.h b/drivers/iommu/io-pgtable.h index fdd792c37698..05c4e593a25f 100644 --- a/drivers/iommu/io-pgtable.h +++ b/drivers/iommu/io-pgtable.h @@ -5,6 +5,10 @@ * Public API for use by IOMMU drivers */ enum io_pgtable_fmt { + ARM_32_LPAE_S1, + ARM_32_LPAE_S2, + ARM_64_LPAE_S1, + ARM_64_LPAE_S2, IO_PGTABLE_NUM_FMTS, }; @@ -47,6 +51,16 @@ struct io_pgtable_cfg { /* Low-level data specific to the table format */ union { + struct { + u64 ttbr[2]; + u64 tcr; + u64 mair[2]; + } arm_lpae_s1_cfg; + + struct { + u64 vttbr; + u64 vtcr; + } arm_lpae_s2_cfg; }; }; -- cgit v1.2.3 From 16272ae77e0f167af70eabd476059137d64569a8 Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Thu, 15 Jan 2015 20:14:10 +0000 Subject: MAINTAINERS: update maintainer entry for dwc2 driver Update the MAINTAINERS entry for the dwc2 driver to show John Youn as the new maintainer Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2fa385321245..97de006f38f0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3028,7 +3028,7 @@ S: Maintained F: drivers/platform/x86/dell-wmi.c DESIGNWARE USB2 DRD IP DRIVER -M: Paul Zimmerman +M: John Youn L: linux-usb@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb.git S: Maintained -- cgit v1.2.3 From 0c7aecd4bde4b7302cd41986d3a29e4f0b0ed218 Mon Sep 17 00:00:00 2001 From: Nicolas Dichtel Date: Thu, 15 Jan 2015 15:11:15 +0100 Subject: netns: add rtnl cmd to add and get peer netns ids With this patch, a user can define an id for a peer netns by providing a FD or a PID. These ids are local to the netns where it is added (ie valid only into this netns). The main function (ie the one exported to other module), peernet2id(), allows to get the id of a peer netns. If no id has been assigned by the user, this function allocates one. These ids will be used in netlink messages to point to a peer netns, for example in case of a x-netns interface. Signed-off-by: Nicolas Dichtel Signed-off-by: David S. Miller --- MAINTAINERS | 1 + include/net/net_namespace.h | 4 + include/uapi/linux/Kbuild | 1 + include/uapi/linux/net_namespace.h | 23 ++++ include/uapi/linux/rtnetlink.h | 5 + net/core/net_namespace.c | 211 +++++++++++++++++++++++++++++++++++++ 6 files changed, 245 insertions(+) create mode 100644 include/uapi/linux/net_namespace.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 9de900572633..9b91d9f0257e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6578,6 +6578,7 @@ F: include/linux/netdevice.h F: include/uapi/linux/in.h F: include/uapi/linux/net.h F: include/uapi/linux/netdevice.h +F: include/uapi/linux/net_namespace.h F: tools/net/ F: tools/testing/selftests/net/ F: lib/random32.c diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index 2e8756b8c775..36faf4990c4b 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -60,6 +60,7 @@ struct net { struct list_head exit_list; /* Use only net_mutex */ struct user_namespace *user_ns; /* Owning user namespace */ + struct idr netns_ids; struct ns_common ns; @@ -290,6 +291,9 @@ static inline struct net *read_pnet(struct net * const *pnet) #define __net_initconst __initconst #endif +int peernet2id(struct net *net, struct net *peer); +struct net *get_net_ns_by_id(struct net *net, int id); + struct pernet_operations { struct list_head list; int (*init)(struct net *net); diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index 00b100023c47..14b7b6e44c77 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -283,6 +283,7 @@ header-y += net.h header-y += netlink_diag.h header-y += netlink.h header-y += netrom.h +header-y += net_namespace.h header-y += net_tstamp.h header-y += nfc.h header-y += nfs2.h diff --git a/include/uapi/linux/net_namespace.h b/include/uapi/linux/net_namespace.h new file mode 100644 index 000000000000..778cd2c3ebf4 --- /dev/null +++ b/include/uapi/linux/net_namespace.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2015 6WIND S.A. + * Author: Nicolas Dichtel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ +#ifndef _UAPI_LINUX_NET_NAMESPACE_H_ +#define _UAPI_LINUX_NET_NAMESPACE_H_ + +/* Attributes of RTM_NEWNSID/RTM_GETNSID messages */ +enum { + NETNSA_NONE, +#define NETNSA_NSID_NOT_ASSIGNED -1 + NETNSA_NSID, + NETNSA_PID, + NETNSA_FD, + __NETNSA_MAX, +}; + +#define NETNSA_MAX (__NETNSA_MAX - 1) + +#endif /* _UAPI_LINUX_NET_NAMESPACE_H_ */ diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h index a1d18593f41e..5cc5d66bf519 100644 --- a/include/uapi/linux/rtnetlink.h +++ b/include/uapi/linux/rtnetlink.h @@ -132,6 +132,11 @@ enum { RTM_GETMDB = 86, #define RTM_GETMDB RTM_GETMDB + RTM_NEWNSID = 88, +#define RTM_NEWNSID RTM_NEWNSID + RTM_GETNSID = 90, +#define RTM_GETNSID RTM_GETNSID + __RTM_MAX, #define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) }; diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index ce780c722e48..9d1a4cac83b6 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -15,6 +15,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -144,6 +148,77 @@ static void ops_free_list(const struct pernet_operations *ops, } } +static int alloc_netid(struct net *net, struct net *peer, int reqid) +{ + int min = 0, max = 0; + + ASSERT_RTNL(); + + if (reqid >= 0) { + min = reqid; + max = reqid + 1; + } + + return idr_alloc(&net->netns_ids, peer, min, max, GFP_KERNEL); +} + +/* This function is used by idr_for_each(). If net is equal to peer, the + * function returns the id so that idr_for_each() stops. Because we cannot + * returns the id 0 (idr_for_each() will not stop), we return the magic value + * NET_ID_ZERO (-1) for it. + */ +#define NET_ID_ZERO -1 +static int net_eq_idr(int id, void *net, void *peer) +{ + if (net_eq(net, peer)) + return id ? : NET_ID_ZERO; + return 0; +} + +static int __peernet2id(struct net *net, struct net *peer, bool alloc) +{ + int id = idr_for_each(&net->netns_ids, net_eq_idr, peer); + + ASSERT_RTNL(); + + /* Magic value for id 0. */ + if (id == NET_ID_ZERO) + return 0; + if (id > 0) + return id; + + if (alloc) + return alloc_netid(net, peer, -1); + + return -ENOENT; +} + +/* This function returns the id of a peer netns. If no id is assigned, one will + * be allocated and returned. + */ +int peernet2id(struct net *net, struct net *peer) +{ + int id = __peernet2id(net, peer, true); + + return id >= 0 ? id : NETNSA_NSID_NOT_ASSIGNED; +} + +struct net *get_net_ns_by_id(struct net *net, int id) +{ + struct net *peer; + + if (id < 0) + return NULL; + + rcu_read_lock(); + peer = idr_find(&net->netns_ids, id); + if (peer) + get_net(peer); + rcu_read_unlock(); + + return peer; +} + /* * setup_net runs the initializers for the network namespace object. */ @@ -158,6 +233,7 @@ static __net_init int setup_net(struct net *net, struct user_namespace *user_ns) atomic_set(&net->passive, 1); net->dev_base_seq = 1; net->user_ns = user_ns; + idr_init(&net->netns_ids); #ifdef NETNS_REFCNT_DEBUG atomic_set(&net->use_count, 0); @@ -288,6 +364,14 @@ static void cleanup_net(struct work_struct *work) list_for_each_entry(net, &net_kill_list, cleanup_list) { list_del_rcu(&net->list); list_add_tail(&net->exit_list, &net_exit_list); + for_each_net(tmp) { + int id = __peernet2id(tmp, net, false); + + if (id >= 0) + idr_remove(&tmp->netns_ids, id); + } + idr_destroy(&net->netns_ids); + } rtnl_unlock(); @@ -402,6 +486,130 @@ static struct pernet_operations __net_initdata net_ns_ops = { .exit = net_ns_net_exit, }; +static struct nla_policy rtnl_net_policy[NETNSA_MAX + 1] = { + [NETNSA_NONE] = { .type = NLA_UNSPEC }, + [NETNSA_NSID] = { .type = NLA_S32 }, + [NETNSA_PID] = { .type = NLA_U32 }, + [NETNSA_FD] = { .type = NLA_U32 }, +}; + +static int rtnl_net_newid(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + struct net *net = sock_net(skb->sk); + struct nlattr *tb[NETNSA_MAX + 1]; + struct net *peer; + int nsid, err; + + err = nlmsg_parse(nlh, sizeof(struct rtgenmsg), tb, NETNSA_MAX, + rtnl_net_policy); + if (err < 0) + return err; + if (!tb[NETNSA_NSID]) + return -EINVAL; + nsid = nla_get_s32(tb[NETNSA_NSID]); + + if (tb[NETNSA_PID]) + peer = get_net_ns_by_pid(nla_get_u32(tb[NETNSA_PID])); + else if (tb[NETNSA_FD]) + peer = get_net_ns_by_fd(nla_get_u32(tb[NETNSA_FD])); + else + return -EINVAL; + if (IS_ERR(peer)) + return PTR_ERR(peer); + + if (__peernet2id(net, peer, false) >= 0) { + err = -EEXIST; + goto out; + } + + err = alloc_netid(net, peer, nsid); + if (err > 0) + err = 0; +out: + put_net(peer); + return err; +} + +static int rtnl_net_get_size(void) +{ + return NLMSG_ALIGN(sizeof(struct rtgenmsg)) + + nla_total_size(sizeof(s32)) /* NETNSA_NSID */ + ; +} + +static int rtnl_net_fill(struct sk_buff *skb, u32 portid, u32 seq, int flags, + int cmd, struct net *net, struct net *peer) +{ + struct nlmsghdr *nlh; + struct rtgenmsg *rth; + int id; + + ASSERT_RTNL(); + + nlh = nlmsg_put(skb, portid, seq, cmd, sizeof(*rth), flags); + if (!nlh) + return -EMSGSIZE; + + rth = nlmsg_data(nlh); + rth->rtgen_family = AF_UNSPEC; + + id = __peernet2id(net, peer, false); + if (id < 0) + id = NETNSA_NSID_NOT_ASSIGNED; + if (nla_put_s32(skb, NETNSA_NSID, id)) + goto nla_put_failure; + + nlmsg_end(skb, nlh); + return 0; + +nla_put_failure: + nlmsg_cancel(skb, nlh); + return -EMSGSIZE; +} + +static int rtnl_net_getid(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + struct net *net = sock_net(skb->sk); + struct nlattr *tb[NETNSA_MAX + 1]; + struct sk_buff *msg; + int err = -ENOBUFS; + struct net *peer; + + err = nlmsg_parse(nlh, sizeof(struct rtgenmsg), tb, NETNSA_MAX, + rtnl_net_policy); + if (err < 0) + return err; + if (tb[NETNSA_PID]) + peer = get_net_ns_by_pid(nla_get_u32(tb[NETNSA_PID])); + else if (tb[NETNSA_FD]) + peer = get_net_ns_by_fd(nla_get_u32(tb[NETNSA_FD])); + else + return -EINVAL; + + if (IS_ERR(peer)) + return PTR_ERR(peer); + + msg = nlmsg_new(rtnl_net_get_size(), GFP_KERNEL); + if (!msg) { + err = -ENOMEM; + goto out; + } + + err = rtnl_net_fill(msg, NETLINK_CB(skb).portid, nlh->nlmsg_seq, 0, + RTM_GETNSID, net, peer); + if (err < 0) + goto err_out; + + err = rtnl_unicast(msg, net, NETLINK_CB(skb).portid); + goto out; + +err_out: + nlmsg_free(msg); +out: + put_net(peer); + return err; +} + static int __init net_ns_init(void) { struct net_generic *ng; @@ -435,6 +643,9 @@ static int __init net_ns_init(void) register_pernet_subsys(&net_ns_ops); + rtnl_register(PF_UNSPEC, RTM_NEWNSID, rtnl_net_newid, NULL, NULL); + rtnl_register(PF_UNSPEC, RTM_GETNSID, rtnl_net_getid, NULL, NULL); + return 0; } -- cgit v1.2.3 From 84640e27f23041d474c31d3362c3e2185ad68ec2 Mon Sep 17 00:00:00 2001 From: Karicheri, Muralidharan Date: Thu, 15 Jan 2015 19:12:50 -0500 Subject: net: netcp: Add Keystone NetCP core ethernet driver The network coprocessor (NetCP) is a hardware accelerator available in Keystone SoCs that processes Ethernet packets. NetCP consists of following hardware components 1 Gigabit Ethernet (GbE) subsystem with a Ethernet switch sub-module to send and receive packets. 2 Packet Accelerator (PA) module to perform packet classification operations such as header matching, and packet modification operations such as checksum generation. 3 Security Accelerator(SA) capable of performing IPSec operations on ingress/egress packets. 4 An optional 10 Gigabit Ethernet Subsystem (XGbE) which includes a 3-port Ethernet switch sub-module capable of 10Gb/s and 1Gb/s rates per Ethernet port. 5 Packet DMA and Queue Management Subsystem (QMSS) to enqueue and dequeue packets and DMA the packets between memory and NetCP hardware components described above. NetCP core driver make use of the Keystone Navigator driver API to allocate DMA channel for the Ethenet device and to handle packet queue/de-queue, Please refer API's in include/linux/soc/ti/knav_dma.h and drivers/soc/ti/knav_qmss.h for details. NetCP driver consists of NetCP core driver and at a minimum Gigabit Ethernet (GBE) module (1) driver to implement the Network device function. Other modules (2,3) can be optionally added to achieve supported hardware acceleration function. The initial version of the driver include NetCP core driver and GBE driver modules. Please refer Documentation/devicetree/bindings/net/keystone-netcp.txt for design of the driver. Cc: David Miller Cc: Rob Herring Cc: Grant Likely Cc: Santosh Shilimkar Cc: Pawel Moll Cc: Mark Rutland Cc: Ian Campbell Cc: Kumar Gala Signed-off-by: Murali Karicheri Signed-off-by: Wingman Kwok Signed-off-by: David S. Miller --- MAINTAINERS | 7 + drivers/net/ethernet/ti/Kconfig | 11 + drivers/net/ethernet/ti/Makefile | 3 + drivers/net/ethernet/ti/netcp.h | 229 ++++ drivers/net/ethernet/ti/netcp_core.c | 2141 ++++++++++++++++++++++++++++++++++ 5 files changed, 2391 insertions(+) create mode 100644 drivers/net/ethernet/ti/netcp.h create mode 100644 drivers/net/ethernet/ti/netcp_core.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 9b91d9f0257e..e1ff4ce5bcab 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9609,6 +9609,13 @@ F: drivers/power/lp8788-charger.c F: drivers/regulator/lp8788-*.c F: include/linux/mfd/lp8788*.h +TI NETCP ETHERNET DRIVER +M: Wingman Kwok +M: Murali Karicheri +L: netdev@vger.kernel.org +S: Maintained +F: drivers/net/ethernet/ti/netcp* + TI TWL4030 SERIES SOC CODEC DRIVER M: Peter Ujfalusi L: alsa-devel@alsa-project.org (moderated for non-subscribers) diff --git a/drivers/net/ethernet/ti/Kconfig b/drivers/net/ethernet/ti/Kconfig index 605dd909bcc3..e11bcfa69f52 100644 --- a/drivers/net/ethernet/ti/Kconfig +++ b/drivers/net/ethernet/ti/Kconfig @@ -73,12 +73,23 @@ config TI_CPSW config TI_CPTS boolean "TI Common Platform Time Sync (CPTS) Support" depends on TI_CPSW + depends on TI_CPSW || TI_KEYSTONE_NET select PTP_1588_CLOCK ---help--- This driver supports the Common Platform Time Sync unit of the CPSW Ethernet Switch. The unit can time stamp PTP UDP/IPv4 and Layer 2 packets, and the driver offers a PTP Hardware Clock. +config TI_KEYSTONE_NETCP + tristate "TI Keystone NETCP Ethernet subsystem Support" + depends on OF + depends on KEYSTONE_NAVIGATOR_DMA && KEYSTONE_NAVIGATOR_QMSS + ---help--- + This driver supports TI's Keystone NETCP Ethernet subsystem. + + To compile this driver as a module, choose M here: the module + will be called keystone_netcp. + config TLAN tristate "TI ThunderLAN support" depends on (PCI || EISA) diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Makefile index 9cfaab8152be..4e8a8e41f69f 100644 --- a/drivers/net/ethernet/ti/Makefile +++ b/drivers/net/ethernet/ti/Makefile @@ -10,3 +10,6 @@ obj-$(CONFIG_TI_DAVINCI_CPDMA) += davinci_cpdma.o obj-$(CONFIG_TI_CPSW_PHY_SEL) += cpsw-phy-sel.o obj-$(CONFIG_TI_CPSW) += ti_cpsw.o ti_cpsw-y := cpsw_ale.o cpsw.o cpts.o + +obj-$(CONFIG_TI_KEYSTONE_NETCP) += keystone_netcp.o +keystone_netcp-y := netcp_core.o diff --git a/drivers/net/ethernet/ti/netcp.h b/drivers/net/ethernet/ti/netcp.h new file mode 100644 index 000000000000..906e9bc412f5 --- /dev/null +++ b/drivers/net/ethernet/ti/netcp.h @@ -0,0 +1,229 @@ +/* + * NetCP driver local header + * + * Copyright (C) 2014 Texas Instruments Incorporated + * Authors: Sandeep Nair + * Sandeep Paulraj + * Cyril Chemparathy + * Santosh Shilimkar + * Wingman Kwok + * Murali Karicheri + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETCP_H__ +#define __NETCP_H__ + +#include +#include + +/* Maximum Ethernet frame size supported by Keystone switch */ +#define NETCP_MAX_FRAME_SIZE 9504 + +#define SGMII_LINK_MAC_MAC_AUTONEG 0 +#define SGMII_LINK_MAC_PHY 1 +#define SGMII_LINK_MAC_MAC_FORCED 2 +#define SGMII_LINK_MAC_FIBER 3 +#define SGMII_LINK_MAC_PHY_NO_MDIO 4 +#define XGMII_LINK_MAC_PHY 10 +#define XGMII_LINK_MAC_MAC_FORCED 11 + +struct netcp_device; + +struct netcp_tx_pipe { + struct netcp_device *netcp_device; + void *dma_queue; + unsigned int dma_queue_id; + u8 dma_psflags; + void *dma_channel; + const char *dma_chan_name; +}; + +#define ADDR_NEW BIT(0) +#define ADDR_VALID BIT(1) + +enum netcp_addr_type { + ADDR_ANY, + ADDR_DEV, + ADDR_UCAST, + ADDR_MCAST, + ADDR_BCAST +}; + +struct netcp_addr { + struct netcp_intf *netcp; + unsigned char addr[ETH_ALEN]; + enum netcp_addr_type type; + unsigned int flags; + struct list_head node; +}; + +struct netcp_intf { + struct device *dev; + struct device *ndev_dev; + struct net_device *ndev; + bool big_endian; + unsigned int tx_compl_qid; + void *tx_pool; + struct list_head txhook_list_head; + unsigned int tx_pause_threshold; + void *tx_compl_q; + + unsigned int tx_resume_threshold; + void *rx_queue; + void *rx_pool; + struct list_head rxhook_list_head; + unsigned int rx_queue_id; + void *rx_fdq[KNAV_DMA_FDQ_PER_CHAN]; + u32 rx_buffer_sizes[KNAV_DMA_FDQ_PER_CHAN]; + struct napi_struct rx_napi; + struct napi_struct tx_napi; + + void *rx_channel; + const char *dma_chan_name; + u32 rx_pool_size; + u32 rx_pool_region_id; + u32 tx_pool_size; + u32 tx_pool_region_id; + struct list_head module_head; + struct list_head interface_list; + struct list_head addr_list; + bool netdev_registered; + bool primary_module_attached; + + /* Lock used for protecting Rx/Tx hook list management */ + spinlock_t lock; + struct netcp_device *netcp_device; + struct device_node *node_interface; + + /* DMA configuration data */ + u32 msg_enable; + u32 rx_queue_depths[KNAV_DMA_FDQ_PER_CHAN]; +}; + +#define NETCP_PSDATA_LEN KNAV_DMA_NUM_PS_WORDS +struct netcp_packet { + struct sk_buff *skb; + u32 *epib; + u32 *psdata; + unsigned int psdata_len; + struct netcp_intf *netcp; + struct netcp_tx_pipe *tx_pipe; + bool rxtstamp_complete; + void *ts_context; + + int (*txtstamp_complete)(void *ctx, struct netcp_packet *pkt); +}; + +static inline u32 *netcp_push_psdata(struct netcp_packet *p_info, + unsigned int bytes) +{ + u32 *buf; + unsigned int words; + + if ((bytes & 0x03) != 0) + return NULL; + words = bytes >> 2; + + if ((p_info->psdata_len + words) > NETCP_PSDATA_LEN) + return NULL; + + p_info->psdata_len += words; + buf = &p_info->psdata[NETCP_PSDATA_LEN - p_info->psdata_len]; + return buf; +} + +static inline int netcp_align_psdata(struct netcp_packet *p_info, + unsigned int byte_align) +{ + int padding; + + switch (byte_align) { + case 0: + padding = -EINVAL; + break; + case 1: + case 2: + case 4: + padding = 0; + break; + case 8: + padding = (p_info->psdata_len << 2) % 8; + break; + case 16: + padding = (p_info->psdata_len << 2) % 16; + break; + default: + padding = (p_info->psdata_len << 2) % byte_align; + break; + } + return padding; +} + +struct netcp_module { + const char *name; + struct module *owner; + bool primary; + + /* probe/remove: called once per NETCP instance */ + int (*probe)(struct netcp_device *netcp_device, + struct device *device, struct device_node *node, + void **inst_priv); + int (*remove)(struct netcp_device *netcp_device, void *inst_priv); + + /* attach/release: called once per network interface */ + int (*attach)(void *inst_priv, struct net_device *ndev, + struct device_node *node, void **intf_priv); + int (*release)(void *intf_priv); + int (*open)(void *intf_priv, struct net_device *ndev); + int (*close)(void *intf_priv, struct net_device *ndev); + int (*add_addr)(void *intf_priv, struct netcp_addr *naddr); + int (*del_addr)(void *intf_priv, struct netcp_addr *naddr); + int (*add_vid)(void *intf_priv, int vid); + int (*del_vid)(void *intf_priv, int vid); + int (*ioctl)(void *intf_priv, struct ifreq *req, int cmd); + + /* used internally */ + struct list_head module_list; + struct list_head interface_list; +}; + +int netcp_register_module(struct netcp_module *module); +void netcp_unregister_module(struct netcp_module *module); +void *netcp_module_get_intf_data(struct netcp_module *module, + struct netcp_intf *intf); + +int netcp_txpipe_init(struct netcp_tx_pipe *tx_pipe, + struct netcp_device *netcp_device, + const char *dma_chan_name, unsigned int dma_queue_id); +int netcp_txpipe_open(struct netcp_tx_pipe *tx_pipe); +int netcp_txpipe_close(struct netcp_tx_pipe *tx_pipe); + +typedef int netcp_hook_rtn(int order, void *data, struct netcp_packet *packet); +int netcp_register_txhook(struct netcp_intf *netcp_priv, int order, + netcp_hook_rtn *hook_rtn, void *hook_data); +int netcp_unregister_txhook(struct netcp_intf *netcp_priv, int order, + netcp_hook_rtn *hook_rtn, void *hook_data); +int netcp_register_rxhook(struct netcp_intf *netcp_priv, int order, + netcp_hook_rtn *hook_rtn, void *hook_data); +int netcp_unregister_rxhook(struct netcp_intf *netcp_priv, int order, + netcp_hook_rtn *hook_rtn, void *hook_data); +void *netcp_device_find_module(struct netcp_device *netcp_device, + const char *name); + +/* SGMII functions */ +int netcp_sgmii_reset(void __iomem *sgmii_ofs, int port); +int netcp_sgmii_get_port_link(void __iomem *sgmii_ofs, int port); +int netcp_sgmii_config(void __iomem *sgmii_ofs, int port, u32 interface); + +/* XGBE SERDES init functions */ +int netcp_xgbe_serdes_init(void __iomem *serdes_regs, void __iomem *xgbe_regs); + +#endif /* __NETCP_H__ */ diff --git a/drivers/net/ethernet/ti/netcp_core.c b/drivers/net/ethernet/ti/netcp_core.c new file mode 100644 index 000000000000..ba3002ec710a --- /dev/null +++ b/drivers/net/ethernet/ti/netcp_core.c @@ -0,0 +1,2141 @@ +/* + * Keystone NetCP Core driver + * + * Copyright (C) 2014 Texas Instruments Incorporated + * Authors: Sandeep Nair + * Sandeep Paulraj + * Cyril Chemparathy + * Santosh Shilimkar + * Murali Karicheri + * Wingman Kwok + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netcp.h" + +#define NETCP_SOP_OFFSET (NET_IP_ALIGN + NET_SKB_PAD) +#define NETCP_NAPI_WEIGHT 64 +#define NETCP_TX_TIMEOUT (5 * HZ) +#define NETCP_MIN_PACKET_SIZE ETH_ZLEN +#define NETCP_MAX_MCAST_ADDR 16 + +#define NETCP_EFUSE_REG_INDEX 0 + +#define NETCP_MOD_PROBE_SKIPPED 1 +#define NETCP_MOD_PROBE_FAILED 2 + +#define NETCP_DEBUG (NETIF_MSG_HW | NETIF_MSG_WOL | \ + NETIF_MSG_DRV | NETIF_MSG_LINK | \ + NETIF_MSG_IFUP | NETIF_MSG_INTR | \ + NETIF_MSG_PROBE | NETIF_MSG_TIMER | \ + NETIF_MSG_IFDOWN | NETIF_MSG_RX_ERR | \ + NETIF_MSG_TX_ERR | NETIF_MSG_TX_DONE | \ + NETIF_MSG_PKTDATA | NETIF_MSG_TX_QUEUED | \ + NETIF_MSG_RX_STATUS) + +#define knav_queue_get_id(q) knav_queue_device_control(q, \ + KNAV_QUEUE_GET_ID, (unsigned long)NULL) + +#define knav_queue_enable_notify(q) knav_queue_device_control(q, \ + KNAV_QUEUE_ENABLE_NOTIFY, \ + (unsigned long)NULL) + +#define knav_queue_disable_notify(q) knav_queue_device_control(q, \ + KNAV_QUEUE_DISABLE_NOTIFY, \ + (unsigned long)NULL) + +#define knav_queue_get_count(q) knav_queue_device_control(q, \ + KNAV_QUEUE_GET_COUNT, (unsigned long)NULL) + +#define for_each_netcp_module(module) \ + list_for_each_entry(module, &netcp_modules, module_list) + +#define for_each_netcp_device_module(netcp_device, inst_modpriv) \ + list_for_each_entry(inst_modpriv, \ + &((netcp_device)->modpriv_head), inst_list) + +#define for_each_module(netcp, intf_modpriv) \ + list_for_each_entry(intf_modpriv, &netcp->module_head, intf_list) + +/* Module management structures */ +struct netcp_device { + struct list_head device_list; + struct list_head interface_head; + struct list_head modpriv_head; + struct device *device; +}; + +struct netcp_inst_modpriv { + struct netcp_device *netcp_device; + struct netcp_module *netcp_module; + struct list_head inst_list; + void *module_priv; +}; + +struct netcp_intf_modpriv { + struct netcp_intf *netcp_priv; + struct netcp_module *netcp_module; + struct list_head intf_list; + void *module_priv; +}; + +static LIST_HEAD(netcp_devices); +static LIST_HEAD(netcp_modules); +static DEFINE_MUTEX(netcp_modules_lock); + +static int netcp_debug_level = -1; +module_param(netcp_debug_level, int, 0); +MODULE_PARM_DESC(netcp_debug_level, "Netcp debug level (NETIF_MSG bits) (0=none,...,16=all)"); + +/* Helper functions - Get/Set */ +static void get_pkt_info(u32 *buff, u32 *buff_len, u32 *ndesc, + struct knav_dma_desc *desc) +{ + *buff_len = desc->buff_len; + *buff = desc->buff; + *ndesc = desc->next_desc; +} + +static void get_pad_info(u32 *pad0, u32 *pad1, struct knav_dma_desc *desc) +{ + *pad0 = desc->pad[0]; + *pad1 = desc->pad[1]; +} + +static void get_org_pkt_info(u32 *buff, u32 *buff_len, + struct knav_dma_desc *desc) +{ + *buff = desc->orig_buff; + *buff_len = desc->orig_len; +} + +static void get_words(u32 *words, int num_words, u32 *desc) +{ + int i; + + for (i = 0; i < num_words; i++) + words[i] = desc[i]; +} + +static void set_pkt_info(u32 buff, u32 buff_len, u32 ndesc, + struct knav_dma_desc *desc) +{ + desc->buff_len = buff_len; + desc->buff = buff; + desc->next_desc = ndesc; +} + +static void set_desc_info(u32 desc_info, u32 pkt_info, + struct knav_dma_desc *desc) +{ + desc->desc_info = desc_info; + desc->packet_info = pkt_info; +} + +static void set_pad_info(u32 pad0, u32 pad1, struct knav_dma_desc *desc) +{ + desc->pad[0] = pad0; + desc->pad[1] = pad1; +} + +static void set_org_pkt_info(u32 buff, u32 buff_len, + struct knav_dma_desc *desc) +{ + desc->orig_buff = buff; + desc->orig_len = buff_len; +} + +static void set_words(u32 *words, int num_words, u32 *desc) +{ + int i; + + for (i = 0; i < num_words; i++) + desc[i] = words[i]; +} + +/* Read the e-fuse value as 32 bit values to be endian independent */ +static int emac_arch_get_mac_addr(char *x, void __iomem *efuse_mac) +{ + unsigned int addr0, addr1; + + addr1 = readl(efuse_mac + 4); + addr0 = readl(efuse_mac); + + x[0] = (addr1 & 0x0000ff00) >> 8; + x[1] = addr1 & 0x000000ff; + x[2] = (addr0 & 0xff000000) >> 24; + x[3] = (addr0 & 0x00ff0000) >> 16; + x[4] = (addr0 & 0x0000ff00) >> 8; + x[5] = addr0 & 0x000000ff; + + return 0; +} + +static const char *netcp_node_name(struct device_node *node) +{ + const char *name; + + if (of_property_read_string(node, "label", &name) < 0) + name = node->name; + if (!name) + name = "unknown"; + return name; +} + +/* Module management routines */ +static int netcp_register_interface(struct netcp_intf *netcp) +{ + int ret; + + ret = register_netdev(netcp->ndev); + if (!ret) + netcp->netdev_registered = true; + return ret; +} + +static int netcp_module_probe(struct netcp_device *netcp_device, + struct netcp_module *module) +{ + struct device *dev = netcp_device->device; + struct device_node *devices, *interface, *node = dev->of_node; + struct device_node *child; + struct netcp_inst_modpriv *inst_modpriv; + struct netcp_intf *netcp_intf; + struct netcp_module *tmp; + bool primary_module_registered = false; + int ret; + + /* Find this module in the sub-tree for this device */ + devices = of_get_child_by_name(node, "netcp-devices"); + if (!devices) { + dev_err(dev, "could not find netcp-devices node\n"); + return NETCP_MOD_PROBE_SKIPPED; + } + + for_each_available_child_of_node(devices, child) { + const char *name = netcp_node_name(child); + + if (!strcasecmp(module->name, name)) + break; + } + + of_node_put(devices); + /* If module not used for this device, skip it */ + if (!child) { + dev_warn(dev, "module(%s) not used for device\n", module->name); + return NETCP_MOD_PROBE_SKIPPED; + } + + inst_modpriv = devm_kzalloc(dev, sizeof(*inst_modpriv), GFP_KERNEL); + if (!inst_modpriv) { + of_node_put(child); + return -ENOMEM; + } + + inst_modpriv->netcp_device = netcp_device; + inst_modpriv->netcp_module = module; + list_add_tail(&inst_modpriv->inst_list, &netcp_device->modpriv_head); + + ret = module->probe(netcp_device, dev, child, + &inst_modpriv->module_priv); + of_node_put(child); + if (ret) { + dev_err(dev, "Probe of module(%s) failed with %d\n", + module->name, ret); + list_del(&inst_modpriv->inst_list); + devm_kfree(dev, inst_modpriv); + return NETCP_MOD_PROBE_FAILED; + } + + /* Attach modules only if the primary module is probed */ + for_each_netcp_module(tmp) { + if (tmp->primary) + primary_module_registered = true; + } + + if (!primary_module_registered) + return 0; + + /* Attach module to interfaces */ + list_for_each_entry(netcp_intf, &netcp_device->interface_head, + interface_list) { + struct netcp_intf_modpriv *intf_modpriv; + + /* If interface not registered then register now */ + if (!netcp_intf->netdev_registered) + ret = netcp_register_interface(netcp_intf); + + if (ret) + return -ENODEV; + + intf_modpriv = devm_kzalloc(dev, sizeof(*intf_modpriv), + GFP_KERNEL); + if (!intf_modpriv) + return -ENOMEM; + + interface = of_parse_phandle(netcp_intf->node_interface, + module->name, 0); + + intf_modpriv->netcp_priv = netcp_intf; + intf_modpriv->netcp_module = module; + list_add_tail(&intf_modpriv->intf_list, + &netcp_intf->module_head); + + ret = module->attach(inst_modpriv->module_priv, + netcp_intf->ndev, interface, + &intf_modpriv->module_priv); + of_node_put(interface); + if (ret) { + dev_dbg(dev, "Attach of module %s declined with %d\n", + module->name, ret); + list_del(&intf_modpriv->intf_list); + devm_kfree(dev, intf_modpriv); + continue; + } + } + return 0; +} + +int netcp_register_module(struct netcp_module *module) +{ + struct netcp_device *netcp_device; + struct netcp_module *tmp; + int ret; + + if (!module->name) { + WARN(1, "error registering netcp module: no name\n"); + return -EINVAL; + } + + if (!module->probe) { + WARN(1, "error registering netcp module: no probe\n"); + return -EINVAL; + } + + mutex_lock(&netcp_modules_lock); + + for_each_netcp_module(tmp) { + if (!strcasecmp(tmp->name, module->name)) { + mutex_unlock(&netcp_modules_lock); + return -EEXIST; + } + } + list_add_tail(&module->module_list, &netcp_modules); + + list_for_each_entry(netcp_device, &netcp_devices, device_list) { + ret = netcp_module_probe(netcp_device, module); + if (ret < 0) + goto fail; + } + + mutex_unlock(&netcp_modules_lock); + return 0; + +fail: + mutex_unlock(&netcp_modules_lock); + netcp_unregister_module(module); + return ret; +} + +static void netcp_release_module(struct netcp_device *netcp_device, + struct netcp_module *module) +{ + struct netcp_inst_modpriv *inst_modpriv, *inst_tmp; + struct netcp_intf *netcp_intf, *netcp_tmp; + struct device *dev = netcp_device->device; + + /* Release the module from each interface */ + list_for_each_entry_safe(netcp_intf, netcp_tmp, + &netcp_device->interface_head, + interface_list) { + struct netcp_intf_modpriv *intf_modpriv, *intf_tmp; + + list_for_each_entry_safe(intf_modpriv, intf_tmp, + &netcp_intf->module_head, + intf_list) { + if (intf_modpriv->netcp_module == module) { + module->release(intf_modpriv->module_priv); + list_del(&intf_modpriv->intf_list); + devm_kfree(dev, intf_modpriv); + break; + } + } + } + + /* Remove the module from each instance */ + list_for_each_entry_safe(inst_modpriv, inst_tmp, + &netcp_device->modpriv_head, inst_list) { + if (inst_modpriv->netcp_module == module) { + module->remove(netcp_device, + inst_modpriv->module_priv); + list_del(&inst_modpriv->inst_list); + devm_kfree(dev, inst_modpriv); + break; + } + } +} + +void netcp_unregister_module(struct netcp_module *module) +{ + struct netcp_device *netcp_device; + struct netcp_module *module_tmp; + + mutex_lock(&netcp_modules_lock); + + list_for_each_entry(netcp_device, &netcp_devices, device_list) { + netcp_release_module(netcp_device, module); + } + + /* Remove the module from the module list */ + for_each_netcp_module(module_tmp) { + if (module == module_tmp) { + list_del(&module->module_list); + break; + } + } + + mutex_unlock(&netcp_modules_lock); +} + +void *netcp_module_get_intf_data(struct netcp_module *module, + struct netcp_intf *intf) +{ + struct netcp_intf_modpriv *intf_modpriv; + + list_for_each_entry(intf_modpriv, &intf->module_head, intf_list) + if (intf_modpriv->netcp_module == module) + return intf_modpriv->module_priv; + return NULL; +} + +/* Module TX and RX Hook management */ +struct netcp_hook_list { + struct list_head list; + netcp_hook_rtn *hook_rtn; + void *hook_data; + int order; +}; + +int netcp_register_txhook(struct netcp_intf *netcp_priv, int order, + netcp_hook_rtn *hook_rtn, void *hook_data) +{ + struct netcp_hook_list *entry; + struct netcp_hook_list *next; + unsigned long flags; + + entry = devm_kzalloc(netcp_priv->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->hook_rtn = hook_rtn; + entry->hook_data = hook_data; + entry->order = order; + + spin_lock_irqsave(&netcp_priv->lock, flags); + list_for_each_entry(next, &netcp_priv->txhook_list_head, list) { + if (next->order > order) + break; + } + __list_add(&entry->list, next->list.prev, &next->list); + spin_unlock_irqrestore(&netcp_priv->lock, flags); + + return 0; +} + +int netcp_unregister_txhook(struct netcp_intf *netcp_priv, int order, + netcp_hook_rtn *hook_rtn, void *hook_data) +{ + struct netcp_hook_list *next, *n; + unsigned long flags; + + spin_lock_irqsave(&netcp_priv->lock, flags); + list_for_each_entry_safe(next, n, &netcp_priv->txhook_list_head, list) { + if ((next->order == order) && + (next->hook_rtn == hook_rtn) && + (next->hook_data == hook_data)) { + list_del(&next->list); + spin_unlock_irqrestore(&netcp_priv->lock, flags); + devm_kfree(netcp_priv->dev, next); + return 0; + } + } + spin_unlock_irqrestore(&netcp_priv->lock, flags); + return -ENOENT; +} + +int netcp_register_rxhook(struct netcp_intf *netcp_priv, int order, + netcp_hook_rtn *hook_rtn, void *hook_data) +{ + struct netcp_hook_list *entry; + struct netcp_hook_list *next; + unsigned long flags; + + entry = devm_kzalloc(netcp_priv->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->hook_rtn = hook_rtn; + entry->hook_data = hook_data; + entry->order = order; + + spin_lock_irqsave(&netcp_priv->lock, flags); + list_for_each_entry(next, &netcp_priv->rxhook_list_head, list) { + if (next->order > order) + break; + } + __list_add(&entry->list, next->list.prev, &next->list); + spin_unlock_irqrestore(&netcp_priv->lock, flags); + + return 0; +} + +int netcp_unregister_rxhook(struct netcp_intf *netcp_priv, int order, + netcp_hook_rtn *hook_rtn, void *hook_data) +{ + struct netcp_hook_list *next, *n; + unsigned long flags; + + spin_lock_irqsave(&netcp_priv->lock, flags); + list_for_each_entry_safe(next, n, &netcp_priv->rxhook_list_head, list) { + if ((next->order == order) && + (next->hook_rtn == hook_rtn) && + (next->hook_data == hook_data)) { + list_del(&next->list); + spin_unlock_irqrestore(&netcp_priv->lock, flags); + devm_kfree(netcp_priv->dev, next); + return 0; + } + } + spin_unlock_irqrestore(&netcp_priv->lock, flags); + + return -ENOENT; +} + +static void netcp_frag_free(bool is_frag, void *ptr) +{ + if (is_frag) + put_page(virt_to_head_page(ptr)); + else + kfree(ptr); +} + +static void netcp_free_rx_desc_chain(struct netcp_intf *netcp, + struct knav_dma_desc *desc) +{ + struct knav_dma_desc *ndesc; + dma_addr_t dma_desc, dma_buf; + unsigned int buf_len, dma_sz = sizeof(*ndesc); + void *buf_ptr; + u32 tmp; + + get_words(&dma_desc, 1, &desc->next_desc); + + while (dma_desc) { + ndesc = knav_pool_desc_unmap(netcp->rx_pool, dma_desc, dma_sz); + if (unlikely(!ndesc)) { + dev_err(netcp->ndev_dev, "failed to unmap Rx desc\n"); + break; + } + get_pkt_info(&dma_buf, &tmp, &dma_desc, ndesc); + get_pad_info((u32 *)&buf_ptr, &tmp, ndesc); + dma_unmap_page(netcp->dev, dma_buf, PAGE_SIZE, DMA_FROM_DEVICE); + __free_page(buf_ptr); + knav_pool_desc_put(netcp->rx_pool, desc); + } + + get_pad_info((u32 *)&buf_ptr, &buf_len, desc); + if (buf_ptr) + netcp_frag_free(buf_len <= PAGE_SIZE, buf_ptr); + knav_pool_desc_put(netcp->rx_pool, desc); +} + +static void netcp_empty_rx_queue(struct netcp_intf *netcp) +{ + struct knav_dma_desc *desc; + unsigned int dma_sz; + dma_addr_t dma; + + for (; ;) { + dma = knav_queue_pop(netcp->rx_queue, &dma_sz); + if (!dma) + break; + + desc = knav_pool_desc_unmap(netcp->rx_pool, dma, dma_sz); + if (unlikely(!desc)) { + dev_err(netcp->ndev_dev, "%s: failed to unmap Rx desc\n", + __func__); + netcp->ndev->stats.rx_errors++; + continue; + } + netcp_free_rx_desc_chain(netcp, desc); + netcp->ndev->stats.rx_dropped++; + } +} + +static int netcp_process_one_rx_packet(struct netcp_intf *netcp) +{ + unsigned int dma_sz, buf_len, org_buf_len; + struct knav_dma_desc *desc, *ndesc; + unsigned int pkt_sz = 0, accum_sz; + struct netcp_hook_list *rx_hook; + dma_addr_t dma_desc, dma_buff; + struct netcp_packet p_info; + struct sk_buff *skb; + void *org_buf_ptr; + u32 tmp; + + dma_desc = knav_queue_pop(netcp->rx_queue, &dma_sz); + if (!dma_desc) + return -1; + + desc = knav_pool_desc_unmap(netcp->rx_pool, dma_desc, dma_sz); + if (unlikely(!desc)) { + dev_err(netcp->ndev_dev, "failed to unmap Rx desc\n"); + return 0; + } + + get_pkt_info(&dma_buff, &buf_len, &dma_desc, desc); + get_pad_info((u32 *)&org_buf_ptr, &org_buf_len, desc); + + if (unlikely(!org_buf_ptr)) { + dev_err(netcp->ndev_dev, "NULL bufptr in desc\n"); + goto free_desc; + } + + pkt_sz &= KNAV_DMA_DESC_PKT_LEN_MASK; + accum_sz = buf_len; + dma_unmap_single(netcp->dev, dma_buff, buf_len, DMA_FROM_DEVICE); + + /* Build a new sk_buff for the primary buffer */ + skb = build_skb(org_buf_ptr, org_buf_len); + if (unlikely(!skb)) { + dev_err(netcp->ndev_dev, "build_skb() failed\n"); + goto free_desc; + } + + /* update data, tail and len */ + skb_reserve(skb, NETCP_SOP_OFFSET); + __skb_put(skb, buf_len); + + /* Fill in the page fragment list */ + while (dma_desc) { + struct page *page; + + ndesc = knav_pool_desc_unmap(netcp->rx_pool, dma_desc, dma_sz); + if (unlikely(!ndesc)) { + dev_err(netcp->ndev_dev, "failed to unmap Rx desc\n"); + goto free_desc; + } + + get_pkt_info(&dma_buff, &buf_len, &dma_desc, ndesc); + get_pad_info((u32 *)&page, &tmp, ndesc); + + if (likely(dma_buff && buf_len && page)) { + dma_unmap_page(netcp->dev, dma_buff, PAGE_SIZE, + DMA_FROM_DEVICE); + } else { + dev_err(netcp->ndev_dev, "Bad Rx desc dma_buff(%p), len(%d), page(%p)\n", + (void *)dma_buff, buf_len, page); + goto free_desc; + } + + skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page, + offset_in_page(dma_buff), buf_len, PAGE_SIZE); + accum_sz += buf_len; + + /* Free the descriptor */ + knav_pool_desc_put(netcp->rx_pool, ndesc); + } + + /* Free the primary descriptor */ + knav_pool_desc_put(netcp->rx_pool, desc); + + /* check for packet len and warn */ + if (unlikely(pkt_sz != accum_sz)) + dev_dbg(netcp->ndev_dev, "mismatch in packet size(%d) & sum of fragments(%d)\n", + pkt_sz, accum_sz); + + /* Remove ethernet FCS from the packet */ + __pskb_trim(skb, skb->len - ETH_FCS_LEN); + + /* Call each of the RX hooks */ + p_info.skb = skb; + p_info.rxtstamp_complete = false; + list_for_each_entry(rx_hook, &netcp->rxhook_list_head, list) { + int ret; + + ret = rx_hook->hook_rtn(rx_hook->order, rx_hook->hook_data, + &p_info); + if (unlikely(ret)) { + dev_err(netcp->ndev_dev, "RX hook %d failed: %d\n", + rx_hook->order, ret); + netcp->ndev->stats.rx_errors++; + dev_kfree_skb(skb); + return 0; + } + } + + netcp->ndev->last_rx = jiffies; + netcp->ndev->stats.rx_packets++; + netcp->ndev->stats.rx_bytes += skb->len; + + /* push skb up the stack */ + skb->protocol = eth_type_trans(skb, netcp->ndev); + netif_receive_skb(skb); + return 0; + +free_desc: + netcp_free_rx_desc_chain(netcp, desc); + netcp->ndev->stats.rx_errors++; + return 0; +} + +static int netcp_process_rx_packets(struct netcp_intf *netcp, + unsigned int budget) +{ + int i; + + for (i = 0; (i < budget) && !netcp_process_one_rx_packet(netcp); i++) + ; + return i; +} + +/* Release descriptors and attached buffers from Rx FDQ */ +static void netcp_free_rx_buf(struct netcp_intf *netcp, int fdq) +{ + struct knav_dma_desc *desc; + unsigned int buf_len, dma_sz; + dma_addr_t dma; + void *buf_ptr; + u32 tmp; + + /* Allocate descriptor */ + while ((dma = knav_queue_pop(netcp->rx_fdq[fdq], &dma_sz))) { + desc = knav_pool_desc_unmap(netcp->rx_pool, dma, dma_sz); + if (unlikely(!desc)) { + dev_err(netcp->ndev_dev, "failed to unmap Rx desc\n"); + continue; + } + + get_org_pkt_info(&dma, &buf_len, desc); + get_pad_info((u32 *)&buf_ptr, &tmp, desc); + + if (unlikely(!dma)) { + dev_err(netcp->ndev_dev, "NULL orig_buff in desc\n"); + knav_pool_desc_put(netcp->rx_pool, desc); + continue; + } + + if (unlikely(!buf_ptr)) { + dev_err(netcp->ndev_dev, "NULL bufptr in desc\n"); + knav_pool_desc_put(netcp->rx_pool, desc); + continue; + } + + if (fdq == 0) { + dma_unmap_single(netcp->dev, dma, buf_len, + DMA_FROM_DEVICE); + netcp_frag_free((buf_len <= PAGE_SIZE), buf_ptr); + } else { + dma_unmap_page(netcp->dev, dma, buf_len, + DMA_FROM_DEVICE); + __free_page(buf_ptr); + } + + knav_pool_desc_put(netcp->rx_pool, desc); + } +} + +static void netcp_rxpool_free(struct netcp_intf *netcp) +{ + int i; + + for (i = 0; i < KNAV_DMA_FDQ_PER_CHAN && + !IS_ERR_OR_NULL(netcp->rx_fdq[i]); i++) + netcp_free_rx_buf(netcp, i); + + if (knav_pool_count(netcp->rx_pool) != netcp->rx_pool_size) + dev_err(netcp->ndev_dev, "Lost Rx (%d) descriptors\n", + netcp->rx_pool_size - knav_pool_count(netcp->rx_pool)); + + knav_pool_destroy(netcp->rx_pool); + netcp->rx_pool = NULL; +} + +static void netcp_allocate_rx_buf(struct netcp_intf *netcp, int fdq) +{ + struct knav_dma_desc *hwdesc; + unsigned int buf_len, dma_sz; + u32 desc_info, pkt_info; + struct page *page; + dma_addr_t dma; + void *bufptr; + u32 pad[2]; + + /* Allocate descriptor */ + hwdesc = knav_pool_desc_get(netcp->rx_pool); + if (IS_ERR_OR_NULL(hwdesc)) { + dev_dbg(netcp->ndev_dev, "out of rx pool desc\n"); + return; + } + + if (likely(fdq == 0)) { + unsigned int primary_buf_len; + /* Allocate a primary receive queue entry */ + buf_len = netcp->rx_buffer_sizes[0] + NETCP_SOP_OFFSET; + primary_buf_len = SKB_DATA_ALIGN(buf_len) + + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); + + if (primary_buf_len <= PAGE_SIZE) { + bufptr = netdev_alloc_frag(primary_buf_len); + pad[1] = primary_buf_len; + } else { + bufptr = kmalloc(primary_buf_len, GFP_ATOMIC | + GFP_DMA32 | __GFP_COLD); + pad[1] = 0; + } + + if (unlikely(!bufptr)) { + dev_warn_ratelimited(netcp->ndev_dev, "Primary RX buffer alloc failed\n"); + goto fail; + } + dma = dma_map_single(netcp->dev, bufptr, buf_len, + DMA_TO_DEVICE); + pad[0] = (u32)bufptr; + + } else { + /* Allocate a secondary receive queue entry */ + page = alloc_page(GFP_ATOMIC | GFP_DMA32 | __GFP_COLD); + if (unlikely(!page)) { + dev_warn_ratelimited(netcp->ndev_dev, "Secondary page alloc failed\n"); + goto fail; + } + buf_len = PAGE_SIZE; + dma = dma_map_page(netcp->dev, page, 0, buf_len, DMA_TO_DEVICE); + pad[0] = (u32)page; + pad[1] = 0; + } + + desc_info = KNAV_DMA_DESC_PS_INFO_IN_DESC; + desc_info |= buf_len & KNAV_DMA_DESC_PKT_LEN_MASK; + pkt_info = KNAV_DMA_DESC_HAS_EPIB; + pkt_info |= KNAV_DMA_NUM_PS_WORDS << KNAV_DMA_DESC_PSLEN_SHIFT; + pkt_info |= (netcp->rx_queue_id & KNAV_DMA_DESC_RETQ_MASK) << + KNAV_DMA_DESC_RETQ_SHIFT; + set_org_pkt_info(dma, buf_len, hwdesc); + set_pad_info(pad[0], pad[1], hwdesc); + set_desc_info(desc_info, pkt_info, hwdesc); + + /* Push to FDQs */ + knav_pool_desc_map(netcp->rx_pool, hwdesc, sizeof(*hwdesc), &dma, + &dma_sz); + knav_queue_push(netcp->rx_fdq[fdq], dma, sizeof(*hwdesc), 0); + return; + +fail: + knav_pool_desc_put(netcp->rx_pool, hwdesc); +} + +/* Refill Rx FDQ with descriptors & attached buffers */ +static void netcp_rxpool_refill(struct netcp_intf *netcp) +{ + u32 fdq_deficit[KNAV_DMA_FDQ_PER_CHAN] = {0}; + int i; + + /* Calculate the FDQ deficit and refill */ + for (i = 0; i < KNAV_DMA_FDQ_PER_CHAN && netcp->rx_fdq[i]; i++) { + fdq_deficit[i] = netcp->rx_queue_depths[i] - + knav_queue_get_count(netcp->rx_fdq[i]); + + while (fdq_deficit[i]--) + netcp_allocate_rx_buf(netcp, i); + } /* end for fdqs */ +} + +/* NAPI poll */ +static int netcp_rx_poll(struct napi_struct *napi, int budget) +{ + struct netcp_intf *netcp = container_of(napi, struct netcp_intf, + rx_napi); + unsigned int packets; + + packets = netcp_process_rx_packets(netcp, budget); + + if (packets < budget) { + napi_complete(&netcp->rx_napi); + knav_queue_enable_notify(netcp->rx_queue); + } + + netcp_rxpool_refill(netcp); + return packets; +} + +static void netcp_rx_notify(void *arg) +{ + struct netcp_intf *netcp = arg; + + knav_queue_disable_notify(netcp->rx_queue); + napi_schedule(&netcp->rx_napi); +} + +static void netcp_free_tx_desc_chain(struct netcp_intf *netcp, + struct knav_dma_desc *desc, + unsigned int desc_sz) +{ + struct knav_dma_desc *ndesc = desc; + dma_addr_t dma_desc, dma_buf; + unsigned int buf_len; + + while (ndesc) { + get_pkt_info(&dma_buf, &buf_len, &dma_desc, ndesc); + + if (dma_buf && buf_len) + dma_unmap_single(netcp->dev, dma_buf, buf_len, + DMA_TO_DEVICE); + else + dev_warn(netcp->ndev_dev, "bad Tx desc buf(%p), len(%d)\n", + (void *)dma_buf, buf_len); + + knav_pool_desc_put(netcp->tx_pool, ndesc); + ndesc = NULL; + if (dma_desc) { + ndesc = knav_pool_desc_unmap(netcp->tx_pool, dma_desc, + desc_sz); + if (!ndesc) + dev_err(netcp->ndev_dev, "failed to unmap Tx desc\n"); + } + } +} + +static int netcp_process_tx_compl_packets(struct netcp_intf *netcp, + unsigned int budget) +{ + struct knav_dma_desc *desc; + struct sk_buff *skb; + unsigned int dma_sz; + dma_addr_t dma; + int pkts = 0; + u32 tmp; + + while (budget--) { + dma = knav_queue_pop(netcp->tx_compl_q, &dma_sz); + if (!dma) + break; + desc = knav_pool_desc_unmap(netcp->tx_pool, dma, dma_sz); + if (unlikely(!desc)) { + dev_err(netcp->ndev_dev, "failed to unmap Tx desc\n"); + netcp->ndev->stats.tx_errors++; + continue; + } + + get_pad_info((u32 *)&skb, &tmp, desc); + netcp_free_tx_desc_chain(netcp, desc, dma_sz); + if (!skb) { + dev_err(netcp->ndev_dev, "No skb in Tx desc\n"); + netcp->ndev->stats.tx_errors++; + continue; + } + + if (netif_subqueue_stopped(netcp->ndev, skb) && + netif_running(netcp->ndev) && + (knav_pool_count(netcp->tx_pool) > + netcp->tx_resume_threshold)) { + u16 subqueue = skb_get_queue_mapping(skb); + + netif_wake_subqueue(netcp->ndev, subqueue); + } + + netcp->ndev->stats.tx_packets++; + netcp->ndev->stats.tx_bytes += skb->len; + dev_kfree_skb(skb); + pkts++; + } + return pkts; +} + +static int netcp_tx_poll(struct napi_struct *napi, int budget) +{ + int packets; + struct netcp_intf *netcp = container_of(napi, struct netcp_intf, + tx_napi); + + packets = netcp_process_tx_compl_packets(netcp, budget); + if (packets < budget) { + napi_complete(&netcp->tx_napi); + knav_queue_enable_notify(netcp->tx_compl_q); + } + + return packets; +} + +static void netcp_tx_notify(void *arg) +{ + struct netcp_intf *netcp = arg; + + knav_queue_disable_notify(netcp->tx_compl_q); + napi_schedule(&netcp->tx_napi); +} + +static struct knav_dma_desc* +netcp_tx_map_skb(struct sk_buff *skb, struct netcp_intf *netcp) +{ + struct knav_dma_desc *desc, *ndesc, *pdesc; + unsigned int pkt_len = skb_headlen(skb); + struct device *dev = netcp->dev; + dma_addr_t dma_addr; + unsigned int dma_sz; + int i; + + /* Map the linear buffer */ + dma_addr = dma_map_single(dev, skb->data, pkt_len, DMA_TO_DEVICE); + if (unlikely(!dma_addr)) { + dev_err(netcp->ndev_dev, "Failed to map skb buffer\n"); + return NULL; + } + + desc = knav_pool_desc_get(netcp->tx_pool); + if (unlikely(IS_ERR_OR_NULL(desc))) { + dev_err(netcp->ndev_dev, "out of TX desc\n"); + dma_unmap_single(dev, dma_addr, pkt_len, DMA_TO_DEVICE); + return NULL; + } + + set_pkt_info(dma_addr, pkt_len, 0, desc); + if (skb_is_nonlinear(skb)) { + prefetchw(skb_shinfo(skb)); + } else { + desc->next_desc = 0; + goto upd_pkt_len; + } + + pdesc = desc; + + /* Handle the case where skb is fragmented in pages */ + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + struct page *page = skb_frag_page(frag); + u32 page_offset = frag->page_offset; + u32 buf_len = skb_frag_size(frag); + dma_addr_t desc_dma; + u32 pkt_info; + + dma_addr = dma_map_page(dev, page, page_offset, buf_len, + DMA_TO_DEVICE); + if (unlikely(!dma_addr)) { + dev_err(netcp->ndev_dev, "Failed to map skb page\n"); + goto free_descs; + } + + ndesc = knav_pool_desc_get(netcp->tx_pool); + if (unlikely(IS_ERR_OR_NULL(ndesc))) { + dev_err(netcp->ndev_dev, "out of TX desc for frags\n"); + dma_unmap_page(dev, dma_addr, buf_len, DMA_TO_DEVICE); + goto free_descs; + } + + desc_dma = knav_pool_desc_virt_to_dma(netcp->tx_pool, + (void *)ndesc); + pkt_info = + (netcp->tx_compl_qid & KNAV_DMA_DESC_RETQ_MASK) << + KNAV_DMA_DESC_RETQ_SHIFT; + set_pkt_info(dma_addr, buf_len, 0, ndesc); + set_words(&desc_dma, 1, &pdesc->next_desc); + pkt_len += buf_len; + if (pdesc != desc) + knav_pool_desc_map(netcp->tx_pool, pdesc, + sizeof(*pdesc), &desc_dma, &dma_sz); + pdesc = ndesc; + } + if (pdesc != desc) + knav_pool_desc_map(netcp->tx_pool, pdesc, sizeof(*pdesc), + &dma_addr, &dma_sz); + + /* frag list based linkage is not supported for now. */ + if (skb_shinfo(skb)->frag_list) { + dev_err_ratelimited(netcp->ndev_dev, "NETIF_F_FRAGLIST not supported\n"); + goto free_descs; + } + +upd_pkt_len: + WARN_ON(pkt_len != skb->len); + + pkt_len &= KNAV_DMA_DESC_PKT_LEN_MASK; + set_words(&pkt_len, 1, &desc->desc_info); + return desc; + +free_descs: + netcp_free_tx_desc_chain(netcp, desc, sizeof(*desc)); + return NULL; +} + +static int netcp_tx_submit_skb(struct netcp_intf *netcp, + struct sk_buff *skb, + struct knav_dma_desc *desc) +{ + struct netcp_tx_pipe *tx_pipe = NULL; + struct netcp_hook_list *tx_hook; + struct netcp_packet p_info; + u32 packet_info = 0; + unsigned int dma_sz; + dma_addr_t dma; + int ret = 0; + + p_info.netcp = netcp; + p_info.skb = skb; + p_info.tx_pipe = NULL; + p_info.psdata_len = 0; + p_info.ts_context = NULL; + p_info.txtstamp_complete = NULL; + p_info.epib = desc->epib; + p_info.psdata = desc->psdata; + memset(p_info.epib, 0, KNAV_DMA_NUM_EPIB_WORDS * sizeof(u32)); + + /* Find out where to inject the packet for transmission */ + list_for_each_entry(tx_hook, &netcp->txhook_list_head, list) { + ret = tx_hook->hook_rtn(tx_hook->order, tx_hook->hook_data, + &p_info); + if (unlikely(ret != 0)) { + dev_err(netcp->ndev_dev, "TX hook %d rejected the packet with reason(%d)\n", + tx_hook->order, ret); + ret = (ret < 0) ? ret : NETDEV_TX_OK; + goto out; + } + } + + /* Make sure some TX hook claimed the packet */ + tx_pipe = p_info.tx_pipe; + if (!tx_pipe) { + dev_err(netcp->ndev_dev, "No TX hook claimed the packet!\n"); + ret = -ENXIO; + goto out; + } + + /* update descriptor */ + if (p_info.psdata_len) { + u32 *psdata = p_info.psdata; + + memmove(p_info.psdata, p_info.psdata + p_info.psdata_len, + p_info.psdata_len); + set_words(psdata, p_info.psdata_len, psdata); + packet_info |= + (p_info.psdata_len & KNAV_DMA_DESC_PSLEN_MASK) << + KNAV_DMA_DESC_PSLEN_SHIFT; + } + + packet_info |= KNAV_DMA_DESC_HAS_EPIB | + ((netcp->tx_compl_qid & KNAV_DMA_DESC_RETQ_MASK) << + KNAV_DMA_DESC_RETQ_SHIFT) | + ((tx_pipe->dma_psflags & KNAV_DMA_DESC_PSFLAG_MASK) << + KNAV_DMA_DESC_PSFLAG_SHIFT); + + set_words(&packet_info, 1, &desc->packet_info); + set_words((u32 *)&skb, 1, &desc->pad[0]); + + /* submit packet descriptor */ + ret = knav_pool_desc_map(netcp->tx_pool, desc, sizeof(*desc), &dma, + &dma_sz); + if (unlikely(ret)) { + dev_err(netcp->ndev_dev, "%s() failed to map desc\n", __func__); + ret = -ENOMEM; + goto out; + } + skb_tx_timestamp(skb); + knav_queue_push(tx_pipe->dma_queue, dma, dma_sz, 0); + +out: + return ret; +} + +/* Submit the packet */ +static int netcp_ndo_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct netcp_intf *netcp = netdev_priv(ndev); + int subqueue = skb_get_queue_mapping(skb); + struct knav_dma_desc *desc; + int desc_count, ret = 0; + + if (unlikely(skb->len <= 0)) { + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + + if (unlikely(skb->len < NETCP_MIN_PACKET_SIZE)) { + ret = skb_padto(skb, NETCP_MIN_PACKET_SIZE); + if (ret < 0) { + /* If we get here, the skb has already been dropped */ + dev_warn(netcp->ndev_dev, "padding failed (%d), packet dropped\n", + ret); + ndev->stats.tx_dropped++; + return ret; + } + skb->len = NETCP_MIN_PACKET_SIZE; + } + + desc = netcp_tx_map_skb(skb, netcp); + if (unlikely(!desc)) { + netif_stop_subqueue(ndev, subqueue); + ret = -ENOBUFS; + goto drop; + } + + ret = netcp_tx_submit_skb(netcp, skb, desc); + if (ret) + goto drop; + + ndev->trans_start = jiffies; + + /* Check Tx pool count & stop subqueue if needed */ + desc_count = knav_pool_count(netcp->tx_pool); + if (desc_count < netcp->tx_pause_threshold) { + dev_dbg(netcp->ndev_dev, "pausing tx, count(%d)\n", desc_count); + netif_stop_subqueue(ndev, subqueue); + } + return NETDEV_TX_OK; + +drop: + ndev->stats.tx_dropped++; + if (desc) + netcp_free_tx_desc_chain(netcp, desc, sizeof(*desc)); + dev_kfree_skb(skb); + return ret; +} + +int netcp_txpipe_close(struct netcp_tx_pipe *tx_pipe) +{ + if (tx_pipe->dma_channel) { + knav_dma_close_channel(tx_pipe->dma_channel); + tx_pipe->dma_channel = NULL; + } + return 0; +} + +int netcp_txpipe_open(struct netcp_tx_pipe *tx_pipe) +{ + struct device *dev = tx_pipe->netcp_device->device; + struct knav_dma_cfg config; + int ret = 0; + u8 name[16]; + + memset(&config, 0, sizeof(config)); + config.direction = DMA_MEM_TO_DEV; + config.u.tx.filt_einfo = false; + config.u.tx.filt_pswords = false; + config.u.tx.priority = DMA_PRIO_MED_L; + + tx_pipe->dma_channel = knav_dma_open_channel(dev, + tx_pipe->dma_chan_name, &config); + if (IS_ERR_OR_NULL(tx_pipe->dma_channel)) { + dev_err(dev, "failed opening tx chan(%s)\n", + tx_pipe->dma_chan_name); + goto err; + } + + snprintf(name, sizeof(name), "tx-pipe-%s", dev_name(dev)); + tx_pipe->dma_queue = knav_queue_open(name, tx_pipe->dma_queue_id, + KNAV_QUEUE_SHARED); + if (IS_ERR(tx_pipe->dma_queue)) { + dev_err(dev, "Could not open DMA queue for channel \"%s\": %d\n", + name, ret); + ret = PTR_ERR(tx_pipe->dma_queue); + goto err; + } + + dev_dbg(dev, "opened tx pipe %s\n", name); + return 0; + +err: + if (!IS_ERR_OR_NULL(tx_pipe->dma_channel)) + knav_dma_close_channel(tx_pipe->dma_channel); + tx_pipe->dma_channel = NULL; + return ret; +} + +int netcp_txpipe_init(struct netcp_tx_pipe *tx_pipe, + struct netcp_device *netcp_device, + const char *dma_chan_name, unsigned int dma_queue_id) +{ + memset(tx_pipe, 0, sizeof(*tx_pipe)); + tx_pipe->netcp_device = netcp_device; + tx_pipe->dma_chan_name = dma_chan_name; + tx_pipe->dma_queue_id = dma_queue_id; + return 0; +} + +static struct netcp_addr *netcp_addr_find(struct netcp_intf *netcp, + const u8 *addr, + enum netcp_addr_type type) +{ + struct netcp_addr *naddr; + + list_for_each_entry(naddr, &netcp->addr_list, node) { + if (naddr->type != type) + continue; + if (addr && memcmp(addr, naddr->addr, ETH_ALEN)) + continue; + return naddr; + } + + return NULL; +} + +static struct netcp_addr *netcp_addr_add(struct netcp_intf *netcp, + const u8 *addr, + enum netcp_addr_type type) +{ + struct netcp_addr *naddr; + + naddr = devm_kmalloc(netcp->dev, sizeof(*naddr), GFP_ATOMIC); + if (!naddr) + return NULL; + + naddr->type = type; + naddr->flags = 0; + naddr->netcp = netcp; + if (addr) + ether_addr_copy(naddr->addr, addr); + else + memset(naddr->addr, 0, ETH_ALEN); + list_add_tail(&naddr->node, &netcp->addr_list); + + return naddr; +} + +static void netcp_addr_del(struct netcp_intf *netcp, struct netcp_addr *naddr) +{ + list_del(&naddr->node); + devm_kfree(netcp->dev, naddr); +} + +static void netcp_addr_clear_mark(struct netcp_intf *netcp) +{ + struct netcp_addr *naddr; + + list_for_each_entry(naddr, &netcp->addr_list, node) + naddr->flags = 0; +} + +static void netcp_addr_add_mark(struct netcp_intf *netcp, const u8 *addr, + enum netcp_addr_type type) +{ + struct netcp_addr *naddr; + + naddr = netcp_addr_find(netcp, addr, type); + if (naddr) { + naddr->flags |= ADDR_VALID; + return; + } + + naddr = netcp_addr_add(netcp, addr, type); + if (!WARN_ON(!naddr)) + naddr->flags |= ADDR_NEW; +} + +static void netcp_addr_sweep_del(struct netcp_intf *netcp) +{ + struct netcp_addr *naddr, *tmp; + struct netcp_intf_modpriv *priv; + struct netcp_module *module; + int error; + + list_for_each_entry_safe(naddr, tmp, &netcp->addr_list, node) { + if (naddr->flags & (ADDR_VALID | ADDR_NEW)) + continue; + dev_dbg(netcp->ndev_dev, "deleting address %pM, type %x\n", + naddr->addr, naddr->type); + mutex_lock(&netcp_modules_lock); + for_each_module(netcp, priv) { + module = priv->netcp_module; + if (!module->del_addr) + continue; + error = module->del_addr(priv->module_priv, + naddr); + WARN_ON(error); + } + mutex_unlock(&netcp_modules_lock); + netcp_addr_del(netcp, naddr); + } +} + +static void netcp_addr_sweep_add(struct netcp_intf *netcp) +{ + struct netcp_addr *naddr, *tmp; + struct netcp_intf_modpriv *priv; + struct netcp_module *module; + int error; + + list_for_each_entry_safe(naddr, tmp, &netcp->addr_list, node) { + if (!(naddr->flags & ADDR_NEW)) + continue; + dev_dbg(netcp->ndev_dev, "adding address %pM, type %x\n", + naddr->addr, naddr->type); + mutex_lock(&netcp_modules_lock); + for_each_module(netcp, priv) { + module = priv->netcp_module; + if (!module->add_addr) + continue; + error = module->add_addr(priv->module_priv, naddr); + WARN_ON(error); + } + mutex_unlock(&netcp_modules_lock); + } +} + +static void netcp_set_rx_mode(struct net_device *ndev) +{ + struct netcp_intf *netcp = netdev_priv(ndev); + struct netdev_hw_addr *ndev_addr; + bool promisc; + + promisc = (ndev->flags & IFF_PROMISC || + ndev->flags & IFF_ALLMULTI || + netdev_mc_count(ndev) > NETCP_MAX_MCAST_ADDR); + + /* first clear all marks */ + netcp_addr_clear_mark(netcp); + + /* next add new entries, mark existing ones */ + netcp_addr_add_mark(netcp, ndev->broadcast, ADDR_BCAST); + for_each_dev_addr(ndev, ndev_addr) + netcp_addr_add_mark(netcp, ndev_addr->addr, ADDR_DEV); + netdev_for_each_uc_addr(ndev_addr, ndev) + netcp_addr_add_mark(netcp, ndev_addr->addr, ADDR_UCAST); + netdev_for_each_mc_addr(ndev_addr, ndev) + netcp_addr_add_mark(netcp, ndev_addr->addr, ADDR_MCAST); + + if (promisc) + netcp_addr_add_mark(netcp, NULL, ADDR_ANY); + + /* finally sweep and callout into modules */ + netcp_addr_sweep_del(netcp); + netcp_addr_sweep_add(netcp); +} + +static void netcp_free_navigator_resources(struct netcp_intf *netcp) +{ + int i; + + if (netcp->rx_channel) { + knav_dma_close_channel(netcp->rx_channel); + netcp->rx_channel = NULL; + } + + if (!IS_ERR_OR_NULL(netcp->rx_pool)) + netcp_rxpool_free(netcp); + + if (!IS_ERR_OR_NULL(netcp->rx_queue)) { + knav_queue_close(netcp->rx_queue); + netcp->rx_queue = NULL; + } + + for (i = 0; i < KNAV_DMA_FDQ_PER_CHAN && + !IS_ERR_OR_NULL(netcp->rx_fdq[i]) ; ++i) { + knav_queue_close(netcp->rx_fdq[i]); + netcp->rx_fdq[i] = NULL; + } + + if (!IS_ERR_OR_NULL(netcp->tx_compl_q)) { + knav_queue_close(netcp->tx_compl_q); + netcp->tx_compl_q = NULL; + } + + if (!IS_ERR_OR_NULL(netcp->tx_pool)) { + knav_pool_destroy(netcp->tx_pool); + netcp->tx_pool = NULL; + } +} + +static int netcp_setup_navigator_resources(struct net_device *ndev) +{ + struct netcp_intf *netcp = netdev_priv(ndev); + struct knav_queue_notify_config notify_cfg; + struct knav_dma_cfg config; + u32 last_fdq = 0; + u8 name[16]; + int ret; + int i; + + /* Create Rx/Tx descriptor pools */ + snprintf(name, sizeof(name), "rx-pool-%s", ndev->name); + netcp->rx_pool = knav_pool_create(name, netcp->rx_pool_size, + netcp->rx_pool_region_id); + if (IS_ERR_OR_NULL(netcp->rx_pool)) { + dev_err(netcp->ndev_dev, "Couldn't create rx pool\n"); + ret = PTR_ERR(netcp->rx_pool); + goto fail; + } + + snprintf(name, sizeof(name), "tx-pool-%s", ndev->name); + netcp->tx_pool = knav_pool_create(name, netcp->tx_pool_size, + netcp->tx_pool_region_id); + if (IS_ERR_OR_NULL(netcp->tx_pool)) { + dev_err(netcp->ndev_dev, "Couldn't create tx pool\n"); + ret = PTR_ERR(netcp->tx_pool); + goto fail; + } + + /* open Tx completion queue */ + snprintf(name, sizeof(name), "tx-compl-%s", ndev->name); + netcp->tx_compl_q = knav_queue_open(name, netcp->tx_compl_qid, 0); + if (IS_ERR_OR_NULL(netcp->tx_compl_q)) { + ret = PTR_ERR(netcp->tx_compl_q); + goto fail; + } + netcp->tx_compl_qid = knav_queue_get_id(netcp->tx_compl_q); + + /* Set notification for Tx completion */ + notify_cfg.fn = netcp_tx_notify; + notify_cfg.fn_arg = netcp; + ret = knav_queue_device_control(netcp->tx_compl_q, + KNAV_QUEUE_SET_NOTIFIER, + (unsigned long)¬ify_cfg); + if (ret) + goto fail; + + knav_queue_disable_notify(netcp->tx_compl_q); + + /* open Rx completion queue */ + snprintf(name, sizeof(name), "rx-compl-%s", ndev->name); + netcp->rx_queue = knav_queue_open(name, netcp->rx_queue_id, 0); + if (IS_ERR_OR_NULL(netcp->rx_queue)) { + ret = PTR_ERR(netcp->rx_queue); + goto fail; + } + netcp->rx_queue_id = knav_queue_get_id(netcp->rx_queue); + + /* Set notification for Rx completion */ + notify_cfg.fn = netcp_rx_notify; + notify_cfg.fn_arg = netcp; + ret = knav_queue_device_control(netcp->rx_queue, + KNAV_QUEUE_SET_NOTIFIER, + (unsigned long)¬ify_cfg); + if (ret) + goto fail; + + knav_queue_disable_notify(netcp->rx_queue); + + /* open Rx FDQs */ + for (i = 0; i < KNAV_DMA_FDQ_PER_CHAN && + netcp->rx_queue_depths[i] && netcp->rx_buffer_sizes[i]; ++i) { + snprintf(name, sizeof(name), "rx-fdq-%s-%d", ndev->name, i); + netcp->rx_fdq[i] = knav_queue_open(name, KNAV_QUEUE_GP, 0); + if (IS_ERR_OR_NULL(netcp->rx_fdq[i])) { + ret = PTR_ERR(netcp->rx_fdq[i]); + goto fail; + } + } + + memset(&config, 0, sizeof(config)); + config.direction = DMA_DEV_TO_MEM; + config.u.rx.einfo_present = true; + config.u.rx.psinfo_present = true; + config.u.rx.err_mode = DMA_DROP; + config.u.rx.desc_type = DMA_DESC_HOST; + config.u.rx.psinfo_at_sop = false; + config.u.rx.sop_offset = NETCP_SOP_OFFSET; + config.u.rx.dst_q = netcp->rx_queue_id; + config.u.rx.thresh = DMA_THRESH_NONE; + + for (i = 0; i < KNAV_DMA_FDQ_PER_CHAN; ++i) { + if (netcp->rx_fdq[i]) + last_fdq = knav_queue_get_id(netcp->rx_fdq[i]); + config.u.rx.fdq[i] = last_fdq; + } + + netcp->rx_channel = knav_dma_open_channel(netcp->netcp_device->device, + netcp->dma_chan_name, &config); + if (IS_ERR_OR_NULL(netcp->rx_channel)) { + dev_err(netcp->ndev_dev, "failed opening rx chan(%s\n", + netcp->dma_chan_name); + goto fail; + } + + dev_dbg(netcp->ndev_dev, "opened RX channel: %p\n", netcp->rx_channel); + return 0; + +fail: + netcp_free_navigator_resources(netcp); + return ret; +} + +/* Open the device */ +static int netcp_ndo_open(struct net_device *ndev) +{ + struct netcp_intf *netcp = netdev_priv(ndev); + struct netcp_intf_modpriv *intf_modpriv; + struct netcp_module *module; + int ret; + + netif_carrier_off(ndev); + ret = netcp_setup_navigator_resources(ndev); + if (ret) { + dev_err(netcp->ndev_dev, "Failed to setup navigator resources\n"); + goto fail; + } + + mutex_lock(&netcp_modules_lock); + for_each_module(netcp, intf_modpriv) { + module = intf_modpriv->netcp_module; + if (module->open) { + ret = module->open(intf_modpriv->module_priv, ndev); + if (ret != 0) { + dev_err(netcp->ndev_dev, "module open failed\n"); + goto fail_open; + } + } + } + mutex_unlock(&netcp_modules_lock); + + netcp_rxpool_refill(netcp); + napi_enable(&netcp->rx_napi); + napi_enable(&netcp->tx_napi); + knav_queue_enable_notify(netcp->tx_compl_q); + knav_queue_enable_notify(netcp->rx_queue); + netif_tx_wake_all_queues(ndev); + dev_dbg(netcp->ndev_dev, "netcp device %s opened\n", ndev->name); + return 0; + +fail_open: + for_each_module(netcp, intf_modpriv) { + module = intf_modpriv->netcp_module; + if (module->close) + module->close(intf_modpriv->module_priv, ndev); + } + mutex_unlock(&netcp_modules_lock); + +fail: + netcp_free_navigator_resources(netcp); + return ret; +} + +/* Close the device */ +static int netcp_ndo_stop(struct net_device *ndev) +{ + struct netcp_intf *netcp = netdev_priv(ndev); + struct netcp_intf_modpriv *intf_modpriv; + struct netcp_module *module; + int err = 0; + + netif_tx_stop_all_queues(ndev); + netif_carrier_off(ndev); + netcp_addr_clear_mark(netcp); + netcp_addr_sweep_del(netcp); + knav_queue_disable_notify(netcp->rx_queue); + knav_queue_disable_notify(netcp->tx_compl_q); + napi_disable(&netcp->rx_napi); + napi_disable(&netcp->tx_napi); + + mutex_lock(&netcp_modules_lock); + for_each_module(netcp, intf_modpriv) { + module = intf_modpriv->netcp_module; + if (module->close) { + err = module->close(intf_modpriv->module_priv, ndev); + if (err != 0) + dev_err(netcp->ndev_dev, "Close failed\n"); + } + } + mutex_unlock(&netcp_modules_lock); + + /* Recycle Rx descriptors from completion queue */ + netcp_empty_rx_queue(netcp); + + /* Recycle Tx descriptors from completion queue */ + netcp_process_tx_compl_packets(netcp, netcp->tx_pool_size); + + if (knav_pool_count(netcp->tx_pool) != netcp->tx_pool_size) + dev_err(netcp->ndev_dev, "Lost (%d) Tx descs\n", + netcp->tx_pool_size - knav_pool_count(netcp->tx_pool)); + + netcp_free_navigator_resources(netcp); + dev_dbg(netcp->ndev_dev, "netcp device %s stopped\n", ndev->name); + return 0; +} + +static int netcp_ndo_ioctl(struct net_device *ndev, + struct ifreq *req, int cmd) +{ + struct netcp_intf *netcp = netdev_priv(ndev); + struct netcp_intf_modpriv *intf_modpriv; + struct netcp_module *module; + int ret = -1, err = -EOPNOTSUPP; + + if (!netif_running(ndev)) + return -EINVAL; + + mutex_lock(&netcp_modules_lock); + for_each_module(netcp, intf_modpriv) { + module = intf_modpriv->netcp_module; + if (!module->ioctl) + continue; + + err = module->ioctl(intf_modpriv->module_priv, req, cmd); + if ((err < 0) && (err != -EOPNOTSUPP)) { + ret = err; + goto out; + } + if (err == 0) + ret = err; + } + +out: + mutex_unlock(&netcp_modules_lock); + return (ret == 0) ? 0 : err; +} + +static int netcp_ndo_change_mtu(struct net_device *ndev, int new_mtu) +{ + struct netcp_intf *netcp = netdev_priv(ndev); + + /* MTU < 68 is an error for IPv4 traffic */ + if ((new_mtu < 68) || + (new_mtu > (NETCP_MAX_FRAME_SIZE - ETH_HLEN - ETH_FCS_LEN))) { + dev_err(netcp->ndev_dev, "Invalid mtu size = %d\n", new_mtu); + return -EINVAL; + } + + ndev->mtu = new_mtu; + return 0; +} + +static void netcp_ndo_tx_timeout(struct net_device *ndev) +{ + struct netcp_intf *netcp = netdev_priv(ndev); + unsigned int descs = knav_pool_count(netcp->tx_pool); + + dev_err(netcp->ndev_dev, "transmit timed out tx descs(%d)\n", descs); + netcp_process_tx_compl_packets(netcp, netcp->tx_pool_size); + ndev->trans_start = jiffies; + netif_tx_wake_all_queues(ndev); +} + +static int netcp_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid) +{ + struct netcp_intf *netcp = netdev_priv(ndev); + struct netcp_intf_modpriv *intf_modpriv; + struct netcp_module *module; + int err = 0; + + dev_dbg(netcp->ndev_dev, "adding rx vlan id: %d\n", vid); + + mutex_lock(&netcp_modules_lock); + for_each_module(netcp, intf_modpriv) { + module = intf_modpriv->netcp_module; + if ((module->add_vid) && (vid != 0)) { + err = module->add_vid(intf_modpriv->module_priv, vid); + if (err != 0) { + dev_err(netcp->ndev_dev, "Could not add vlan id = %d\n", + vid); + break; + } + } + } + mutex_unlock(&netcp_modules_lock); + return err; +} + +static int netcp_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vid) +{ + struct netcp_intf *netcp = netdev_priv(ndev); + struct netcp_intf_modpriv *intf_modpriv; + struct netcp_module *module; + int err = 0; + + dev_dbg(netcp->ndev_dev, "removing rx vlan id: %d\n", vid); + + mutex_lock(&netcp_modules_lock); + for_each_module(netcp, intf_modpriv) { + module = intf_modpriv->netcp_module; + if (module->del_vid) { + err = module->del_vid(intf_modpriv->module_priv, vid); + if (err != 0) { + dev_err(netcp->ndev_dev, "Could not delete vlan id = %d\n", + vid); + break; + } + } + } + mutex_unlock(&netcp_modules_lock); + return err; +} + +static u16 netcp_select_queue(struct net_device *dev, struct sk_buff *skb, + void *accel_priv, + select_queue_fallback_t fallback) +{ + return 0; +} + +static int netcp_setup_tc(struct net_device *dev, u8 num_tc) +{ + int i; + + /* setup tc must be called under rtnl lock */ + ASSERT_RTNL(); + + /* Sanity-check the number of traffic classes requested */ + if ((dev->real_num_tx_queues <= 1) || + (dev->real_num_tx_queues < num_tc)) + return -EINVAL; + + /* Configure traffic class to queue mappings */ + if (num_tc) { + netdev_set_num_tc(dev, num_tc); + for (i = 0; i < num_tc; i++) + netdev_set_tc_queue(dev, i, 1, i); + } else { + netdev_reset_tc(dev); + } + + return 0; +} + +static const struct net_device_ops netcp_netdev_ops = { + .ndo_open = netcp_ndo_open, + .ndo_stop = netcp_ndo_stop, + .ndo_start_xmit = netcp_ndo_start_xmit, + .ndo_set_rx_mode = netcp_set_rx_mode, + .ndo_do_ioctl = netcp_ndo_ioctl, + .ndo_change_mtu = netcp_ndo_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_vlan_rx_add_vid = netcp_rx_add_vid, + .ndo_vlan_rx_kill_vid = netcp_rx_kill_vid, + .ndo_tx_timeout = netcp_ndo_tx_timeout, + .ndo_select_queue = netcp_select_queue, + .ndo_setup_tc = netcp_setup_tc, +}; + +static int netcp_create_interface(struct netcp_device *netcp_device, + struct device_node *node_interface) +{ + struct device *dev = netcp_device->device; + struct device_node *node = dev->of_node; + struct netcp_intf *netcp; + struct net_device *ndev; + resource_size_t size; + struct resource res; + void __iomem *efuse = NULL; + u32 efuse_mac = 0; + const void *mac_addr; + u8 efuse_mac_addr[6]; + u32 temp[2]; + int ret = 0; + + ndev = alloc_etherdev_mqs(sizeof(*netcp), 1, 1); + if (!ndev) { + dev_err(dev, "Error allocating netdev\n"); + return -ENOMEM; + } + + ndev->features |= NETIF_F_SG; + ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; + ndev->hw_features = ndev->features; + ndev->vlan_features |= NETIF_F_SG; + + netcp = netdev_priv(ndev); + spin_lock_init(&netcp->lock); + INIT_LIST_HEAD(&netcp->module_head); + INIT_LIST_HEAD(&netcp->txhook_list_head); + INIT_LIST_HEAD(&netcp->rxhook_list_head); + INIT_LIST_HEAD(&netcp->addr_list); + netcp->netcp_device = netcp_device; + netcp->dev = netcp_device->device; + netcp->ndev = ndev; + netcp->ndev_dev = &ndev->dev; + netcp->msg_enable = netif_msg_init(netcp_debug_level, NETCP_DEBUG); + netcp->tx_pause_threshold = MAX_SKB_FRAGS; + netcp->tx_resume_threshold = netcp->tx_pause_threshold; + netcp->node_interface = node_interface; + + ret = of_property_read_u32(node_interface, "efuse-mac", &efuse_mac); + if (efuse_mac) { + if (of_address_to_resource(node, NETCP_EFUSE_REG_INDEX, &res)) { + dev_err(dev, "could not find efuse-mac reg resource\n"); + ret = -ENODEV; + goto quit; + } + size = resource_size(&res); + + if (!devm_request_mem_region(dev, res.start, size, + dev_name(dev))) { + dev_err(dev, "could not reserve resource\n"); + ret = -ENOMEM; + goto quit; + } + + efuse = devm_ioremap_nocache(dev, res.start, size); + if (!efuse) { + dev_err(dev, "could not map resource\n"); + devm_release_mem_region(dev, res.start, size); + ret = -ENOMEM; + goto quit; + } + + emac_arch_get_mac_addr(efuse_mac_addr, efuse); + if (is_valid_ether_addr(efuse_mac_addr)) + ether_addr_copy(ndev->dev_addr, efuse_mac_addr); + else + random_ether_addr(ndev->dev_addr); + + devm_iounmap(dev, efuse); + devm_release_mem_region(dev, res.start, size); + } else { + mac_addr = of_get_mac_address(node_interface); + if (mac_addr) + ether_addr_copy(ndev->dev_addr, mac_addr); + else + random_ether_addr(ndev->dev_addr); + } + + ret = of_property_read_string(node_interface, "rx-channel", + &netcp->dma_chan_name); + if (ret < 0) { + dev_err(dev, "missing \"rx-channel\" parameter\n"); + ret = -ENODEV; + goto quit; + } + + ret = of_property_read_u32(node_interface, "rx-queue", + &netcp->rx_queue_id); + if (ret < 0) { + dev_warn(dev, "missing \"rx-queue\" parameter\n"); + netcp->rx_queue_id = KNAV_QUEUE_QPEND; + } + + ret = of_property_read_u32_array(node_interface, "rx-queue-depth", + netcp->rx_queue_depths, + KNAV_DMA_FDQ_PER_CHAN); + if (ret < 0) { + dev_err(dev, "missing \"rx-queue-depth\" parameter\n"); + netcp->rx_queue_depths[0] = 128; + } + + ret = of_property_read_u32_array(node_interface, "rx-buffer-size", + netcp->rx_buffer_sizes, + KNAV_DMA_FDQ_PER_CHAN); + if (ret) { + dev_err(dev, "missing \"rx-buffer-size\" parameter\n"); + netcp->rx_buffer_sizes[0] = 1536; + } + + ret = of_property_read_u32_array(node_interface, "rx-pool", temp, 2); + if (ret < 0) { + dev_err(dev, "missing \"rx-pool\" parameter\n"); + ret = -ENODEV; + goto quit; + } + netcp->rx_pool_size = temp[0]; + netcp->rx_pool_region_id = temp[1]; + + ret = of_property_read_u32_array(node_interface, "tx-pool", temp, 2); + if (ret < 0) { + dev_err(dev, "missing \"tx-pool\" parameter\n"); + ret = -ENODEV; + goto quit; + } + netcp->tx_pool_size = temp[0]; + netcp->tx_pool_region_id = temp[1]; + + if (netcp->tx_pool_size < MAX_SKB_FRAGS) { + dev_err(dev, "tx-pool size too small, must be atleast(%ld)\n", + MAX_SKB_FRAGS); + ret = -ENODEV; + goto quit; + } + + ret = of_property_read_u32(node_interface, "tx-completion-queue", + &netcp->tx_compl_qid); + if (ret < 0) { + dev_warn(dev, "missing \"tx-completion-queue\" parameter\n"); + netcp->tx_compl_qid = KNAV_QUEUE_QPEND; + } + + /* NAPI register */ + netif_napi_add(ndev, &netcp->rx_napi, netcp_rx_poll, NETCP_NAPI_WEIGHT); + netif_napi_add(ndev, &netcp->tx_napi, netcp_tx_poll, NETCP_NAPI_WEIGHT); + + /* Register the network device */ + ndev->dev_id = 0; + ndev->watchdog_timeo = NETCP_TX_TIMEOUT; + ndev->netdev_ops = &netcp_netdev_ops; + SET_NETDEV_DEV(ndev, dev); + + list_add_tail(&netcp->interface_list, &netcp_device->interface_head); + return 0; + +quit: + free_netdev(ndev); + return ret; +} + +static void netcp_delete_interface(struct netcp_device *netcp_device, + struct net_device *ndev) +{ + struct netcp_intf_modpriv *intf_modpriv, *tmp; + struct netcp_intf *netcp = netdev_priv(ndev); + struct netcp_module *module; + + dev_dbg(netcp_device->device, "Removing interface \"%s\"\n", + ndev->name); + + /* Notify each of the modules that the interface is going away */ + list_for_each_entry_safe(intf_modpriv, tmp, &netcp->module_head, + intf_list) { + module = intf_modpriv->netcp_module; + dev_dbg(netcp_device->device, "Releasing module \"%s\"\n", + module->name); + if (module->release) + module->release(intf_modpriv->module_priv); + list_del(&intf_modpriv->intf_list); + kfree(intf_modpriv); + } + WARN(!list_empty(&netcp->module_head), "%s interface module list is not empty!\n", + ndev->name); + + list_del(&netcp->interface_list); + + of_node_put(netcp->node_interface); + unregister_netdev(ndev); + netif_napi_del(&netcp->rx_napi); + free_netdev(ndev); +} + +static int netcp_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct netcp_intf *netcp_intf, *netcp_tmp; + struct device_node *child, *interfaces; + struct netcp_device *netcp_device; + struct device *dev = &pdev->dev; + struct netcp_module *module; + int ret; + + if (!node) { + dev_err(dev, "could not find device info\n"); + return -ENODEV; + } + + /* Allocate a new NETCP device instance */ + netcp_device = devm_kzalloc(dev, sizeof(*netcp_device), GFP_KERNEL); + if (!netcp_device) + return -ENOMEM; + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + dev_err(dev, "Failed to enable NETCP power-domain\n"); + pm_runtime_disable(&pdev->dev); + return ret; + } + + /* Initialize the NETCP device instance */ + INIT_LIST_HEAD(&netcp_device->interface_head); + INIT_LIST_HEAD(&netcp_device->modpriv_head); + netcp_device->device = dev; + platform_set_drvdata(pdev, netcp_device); + + /* create interfaces */ + interfaces = of_get_child_by_name(node, "netcp-interfaces"); + if (!interfaces) { + dev_err(dev, "could not find netcp-interfaces node\n"); + ret = -ENODEV; + goto probe_quit; + } + + for_each_available_child_of_node(interfaces, child) { + ret = netcp_create_interface(netcp_device, child); + if (ret) { + dev_err(dev, "could not create interface(%s)\n", + child->name); + goto probe_quit_interface; + } + } + + /* Add the device instance to the list */ + list_add_tail(&netcp_device->device_list, &netcp_devices); + + /* Probe & attach any modules already registered */ + mutex_lock(&netcp_modules_lock); + for_each_netcp_module(module) { + ret = netcp_module_probe(netcp_device, module); + if (ret < 0) + dev_err(dev, "module(%s) probe failed\n", module->name); + } + mutex_unlock(&netcp_modules_lock); + return 0; + +probe_quit_interface: + list_for_each_entry_safe(netcp_intf, netcp_tmp, + &netcp_device->interface_head, + interface_list) { + netcp_delete_interface(netcp_device, netcp_intf->ndev); + } + +probe_quit: + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + platform_set_drvdata(pdev, NULL); + return ret; +} + +static int netcp_remove(struct platform_device *pdev) +{ + struct netcp_device *netcp_device = platform_get_drvdata(pdev); + struct netcp_inst_modpriv *inst_modpriv, *tmp; + struct netcp_module *module; + + list_for_each_entry_safe(inst_modpriv, tmp, &netcp_device->modpriv_head, + inst_list) { + module = inst_modpriv->netcp_module; + dev_dbg(&pdev->dev, "Removing module \"%s\"\n", module->name); + module->remove(netcp_device, inst_modpriv->module_priv); + list_del(&inst_modpriv->inst_list); + kfree(inst_modpriv); + } + WARN(!list_empty(&netcp_device->interface_head), "%s interface list not empty!\n", + pdev->name); + + devm_kfree(&pdev->dev, netcp_device); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct of_device_id of_match[] = { + { .compatible = "ti,netcp-1.0", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_match); + +static struct platform_driver netcp_driver = { + .driver = { + .name = "netcp-1.0", + .owner = THIS_MODULE, + .of_match_table = of_match, + }, + .probe = netcp_probe, + .remove = netcp_remove, +}; +module_platform_driver(netcp_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TI NETCP driver for Keystone SOCs"); +MODULE_AUTHOR("Sandeep Nair Signed-off-by: Herbert Xu --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..aa5aaebf0397 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4402,6 +4402,7 @@ F: include/linux/hwmon*.h HARDWARE RANDOM NUMBER GENERATOR CORE M: Matt Mackall M: Herbert Xu +L: linux-crypto@vger.kernel.org S: Odd fixes F: Documentation/hw_random.txt F: drivers/char/hw_random/ -- cgit v1.2.3 From f8f847b51a0e1cb0c96e706d5da0ac507d96c605 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 20 Jan 2015 11:00:56 +0100 Subject: MAINTAINERS: Add entry for Maxim chargers on Samsung boards Add myself as supporter to help in reviewing patches for Maxim 14577 and 77693 MUIC charger drivers. These are used on Exynos-based boards (Trats 2, Gear 1 and Gear 2). Signed-off-by: Krzysztof Kozlowski Cc: Sebastian Reichel Cc: Dmitry Eremin-Solenikov Cc: David Woodhouse Acked-By: Sebastian Reichel Signed-off-by: Sebastian Reichel --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 64e13d5c1e65..55039221d7eb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6093,6 +6093,13 @@ F: Documentation/devicetree/bindings/i2c/max6697.txt F: drivers/hwmon/max6697.c F: include/linux/platform_data/max6697.h +MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS +M: Krzysztof Kozlowski +L: linux-pm@vger.kernel.org +S: Supported +F: drivers/power/max14577_charger.c +F: drivers/power/max77693_charger.c + MAXIRADIO FM RADIO RECEIVER DRIVER M: Hans Verkuil L: linux-media@vger.kernel.org -- cgit v1.2.3 From 8633fb30aab00b97f722bc1f832a614777c2a5c2 Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Mon, 19 Jan 2015 23:49:50 -0700 Subject: MAINTAINERS: add maintainer for OMAP hwmod data I wind up reviewing and committing most of the OMAP hwmod data patches, so, add myself to MAINTAINERS there so folks will cc me. Signed-off-by: Paul Walmsley Cc: Tony Lindgren Cc: Benoît Cousson --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..baefa168faba 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6887,6 +6887,12 @@ L: linux-omap@vger.kernel.org S: Maintained F: arch/arm/mach-omap2/omap_hwmod.* +OMAP HWMOD DATA +M: Paul Walmsley +L: linux-omap@vger.kernel.org +S: Maintained +F: arch/arm/mach-omap2/omap_hwmod*data* + OMAP HWMOD DATA FOR OMAP4-BASED DEVICES M: Benoît Cousson L: linux-omap@vger.kernel.org -- cgit v1.2.3 From 3a75ef0c788f092d912b2944e323e6447d557437 Mon Sep 17 00:00:00 2001 From: Yaowei Bai Date: Sat, 17 Jan 2015 15:31:07 +0800 Subject: MAINTAINERS / ACPI: add the necessary '/' according to entry rules Signed-off-by: Yaowei Bai Signed-off-by: Rafael J. Wysocki --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2fa385321245..98b6123fd681 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -270,12 +270,12 @@ F: drivers/acpi/ F: drivers/pnp/pnpacpi/ F: include/linux/acpi.h F: include/acpi/ -F: Documentation/acpi +F: Documentation/acpi/ F: Documentation/ABI/testing/sysfs-bus-acpi F: drivers/pci/*acpi* F: drivers/pci/*/*acpi* F: drivers/pci/*/*/*acpi* -F: tools/power/acpi +F: tools/power/acpi/ ACPI COMPONENT ARCHITECTURE (ACPICA) M: Robert Moore -- cgit v1.2.3 From bc48a51c2a29e231256c2f96daf80c2b7a45f390 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Sat, 10 Jan 2015 12:02:40 +0100 Subject: MAINTAINERS: remove ath5k mailing list The list is in the process of closing. Signed-off-by: Jiri Slaby Cc: Nick Kossifidis Cc: "Luis R. Rodriguez" Cc: linux-wireless@vger.kernel.org Cc: "Michael Renzmann" Signed-off-by: Kalle Valo --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 33098956d930..788d3a1fc723 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1657,7 +1657,6 @@ M: Jiri Slaby M: Nick Kossifidis M: "Luis R. Rodriguez" L: linux-wireless@vger.kernel.org -L: ath5k-devel@lists.ath5k.org W: http://wireless.kernel.org/en/users/Drivers/ath5k S: Maintained F: drivers/net/wireless/ath/ath5k/ -- cgit v1.2.3 From bfd33c4b4b1ac718d481efee10f3a16d88757577 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Thu, 15 Jan 2015 14:18:18 +0200 Subject: MAINTAINERS: email update Changed to my private email address as I left Samsung. Signed-off-by: Dmitry Kasatkin --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddf762511cb3..e9098d034b9d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4888,7 +4888,7 @@ F: drivers/ipack/ INTEGRITY MEASUREMENT ARCHITECTURE (IMA) M: Mimi Zohar -M: Dmitry Kasatkin +M: Dmitry Kasatkin L: linux-ima-devel@lists.sourceforge.net L: linux-ima-user@lists.sourceforge.net L: linux-security-module@vger.kernel.org -- cgit v1.2.3 From f5d3af9d21f9790ac078276e6c103871c12a3daa Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Wed, 21 Jan 2015 22:39:24 -0600 Subject: MAINTAINERS: Add co-maintainer for ARM/Qualcomm Support Added myself as a co-maintainer. Updated the files to include the Qualcomm SoC directory. Added linux-soc mailing list. Signed-off-by: Andy Gross Signed-off-by: Kumar Gala --- MAINTAINERS | 3 +++ 1 file changed, 3 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..22873c6e8602 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1290,10 +1290,13 @@ S: Maintained ARM/QUALCOMM SUPPORT M: Kumar Gala +M: Andy Gross M: David Brown L: linux-arm-msm@vger.kernel.org +L: linux-soc@vger.kernel.org S: Maintained F: arch/arm/mach-qcom/ +F: drivers/soc/qcom/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/galak/linux-qcom.git ARM/RADISYS ENP2611 MACHINE SUPPORT -- cgit v1.2.3 From 053e514f9172c10b55b1ef0706da57cecb6c85e3 Mon Sep 17 00:00:00 2001 From: Noralf Trønnes Date: Fri, 23 Jan 2015 19:29:07 +0100 Subject: MAINTAINERS: add entry for staging/fbtft/ Add MAINTAINERS entry for staging/fbtft/ FBTFT is a framework for writing framebuffer drivers for displays with LCD controllers having onchip RAM. Signed-off-by: Noralf Trønnes Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2fa385321245..0dbe23092343 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3872,6 +3872,12 @@ S: Supported F: Documentation/fault-injection/ F: lib/fault-inject.c +FBTFT Framebuffer drivers +M: Thomas Petazzoni +M: Noralf Trønnes +S: Maintained +F: drivers/staging/fbtft/ + FCOE SUBSYSTEM (libfc, libfcoe, fcoe) M: Robert Love L: fcoe-devel@open-fcoe.org -- cgit v1.2.3 From 13e4e9b84ba98dd448b3807467e8a1b1cf3bebf6 Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Mon, 19 Jan 2015 13:41:02 +0530 Subject: MAINTAINERS: update for SM7XX driver add myself and Teddy Wang as the Maintainer of the SM7XX FRAME BUFFER DRIVER. Signed-off-by: Sudip Mukherjee Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 0dbe23092343..9cff717f3d96 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9179,6 +9179,14 @@ L: linux-wireless@vger.kernel.org S: Maintained F: drivers/staging/rtl8723au/ +STAGING - SILICON MOTION SM7XX FRAME BUFFER DRIVER +M: Sudip Mukherjee +M: Teddy Wang +M: Sudip Mukherjee +L: linux-fbdev@vger.kernel.org +S: Maintained +F: drivers/staging/sm7xxfb/ + STAGING - SLICOSS M: Lior Dotan M: Christopher Harrer -- cgit v1.2.3 From b84894c7f088ed83d05292e35d3235ebf38a4465 Mon Sep 17 00:00:00 2001 From: Kevin Tsai Date: Thu, 15 Jan 2015 17:41:04 -0800 Subject: iio: Added Capella cm3232 ambient light sensor driver. CM3232 is an advanced ambient light sensor with I2C protocol interface. The I2C slave address is internally hardwired as 0x10 (7-bit). Writing to configure register is byte mode, but reading ALS register requests to use word mode for 16-bit resolution. Signed-off-by: Kevin Tsai Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/i2c/trivial-devices.txt | 1 + MAINTAINERS | 6 + drivers/iio/light/Kconfig | 11 + drivers/iio/light/Makefile | 1 + drivers/iio/light/cm3232.c | 403 +++++++++++++++++++++ 5 files changed, 422 insertions(+) create mode 100644 drivers/iio/light/cm3232.c (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt index 9f4e3824e71e..572a7c483aa7 100644 --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt @@ -34,6 +34,7 @@ atmel,24c512 i2c serial eeprom (24cxx) atmel,24c1024 i2c serial eeprom (24cxx) atmel,at97sc3204t i2c trusted platform module (TPM) capella,cm32181 CM32181: Ambient Light Sensor +capella,cm3232 CM3232: Ambient Light Sensor catalyst,24c32 i2c serial eeprom cirrus,cs42l51 Cirrus Logic CS42L51 audio codec dallas,ds1307 64 x 8, Serial, I2C Real-Time Clock diff --git a/MAINTAINERS b/MAINTAINERS index 2fa385321245..8d2d9a22449a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2379,6 +2379,12 @@ F: security/capability.c F: security/commoncap.c F: kernel/capability.c +CAPELLA MICROSYSTEMS LIGHT SENSOR DRIVER +M: Kevin Tsai +S: Maintained +F: drivers/iio/light/cm* +F: Documentation/devicetree/bindings/i2c/trivial-devices.txt + CC2520 IEEE-802.15.4 RADIO DRIVER M: Varka Bhadram L: linux-wpan@vger.kernel.org diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 5a3237b2aaa5..ae68c64bdad3 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -48,6 +48,17 @@ config CM32181 To compile this driver as a module, choose M here: the module will be called cm32181. +config CM3232 + depends on I2C + tristate "CM3232 ambient light sensor" + help + Say Y here if you use cm3232. + This option enables ambient light sensor using + Capella Microsystems cm3232 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm3232. + config CM36651 depends on I2C tristate "CM36651 driver" diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 74656c19a899..b12a5160d9e0 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_ADJD_S311) += adjd_s311.o obj-$(CONFIG_AL3320A) += al3320a.o obj-$(CONFIG_APDS9300) += apds9300.o obj-$(CONFIG_CM32181) += cm32181.o +obj-$(CONFIG_CM3232) += cm3232.o obj-$(CONFIG_CM36651) += cm36651.o obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o diff --git a/drivers/iio/light/cm3232.c b/drivers/iio/light/cm3232.c new file mode 100644 index 000000000000..90e3519a91de --- /dev/null +++ b/drivers/iio/light/cm3232.c @@ -0,0 +1,403 @@ +/* + * CM3232 Ambient Light Sensor + * + * Copyright (C) 2014-2015 Capella Microsystems Inc. + * Author: Kevin Tsai + * + * 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. + * + * IIO driver for CM3232 (7-bit I2C slave address 0x10). + */ + +#include +#include +#include +#include +#include + +/* Registers Address */ +#define CM3232_REG_ADDR_CMD 0x00 +#define CM3232_REG_ADDR_ALS 0x50 +#define CM3232_REG_ADDR_ID 0x53 + +#define CM3232_CMD_ALS_DISABLE BIT(0) + +#define CM3232_CMD_ALS_IT_SHIFT 2 +#define CM3232_CMD_ALS_IT_MASK (BIT(2) | BIT(3) | BIT(4)) +#define CM3232_CMD_ALS_IT_DEFAULT (0x01 << CM3232_CMD_ALS_IT_SHIFT) + +#define CM3232_CMD_ALS_RESET BIT(6) + +#define CM3232_CMD_DEFAULT CM3232_CMD_ALS_IT_DEFAULT + +#define CM3232_HW_ID 0x32 +#define CM3232_CALIBSCALE_DEFAULT 100000 +#define CM3232_CALIBSCALE_RESOLUTION 100000 +#define CM3232_MLUX_PER_LUX 1000 + +#define CM3232_MLUX_PER_BIT_DEFAULT 64 +#define CM3232_MLUX_PER_BIT_BASE_IT 100000 + +static const struct { + int val; + int val2; + u8 it; +} cm3232_als_it_scales[] = { + {0, 100000, 0}, /* 0.100000 */ + {0, 200000, 1}, /* 0.200000 */ + {0, 400000, 2}, /* 0.400000 */ + {0, 800000, 3}, /* 0.800000 */ + {1, 600000, 4}, /* 1.600000 */ + {3, 200000, 5}, /* 3.200000 */ +}; + +struct cm3232_als_info { + u8 regs_cmd_default; + u8 hw_id; + int calibscale; + int mlux_per_bit; + int mlux_per_bit_base_it; +}; + +static struct cm3232_als_info cm3232_als_info_default = { + .regs_cmd_default = CM3232_CMD_DEFAULT, + .hw_id = CM3232_HW_ID, + .calibscale = CM3232_CALIBSCALE_DEFAULT, + .mlux_per_bit = CM3232_MLUX_PER_BIT_DEFAULT, + .mlux_per_bit_base_it = CM3232_MLUX_PER_BIT_BASE_IT, +}; + +struct cm3232_chip { + struct i2c_client *client; + struct cm3232_als_info *als_info; + u8 regs_cmd; + u16 regs_als; +}; + +/** + * cm3232_reg_init() - Initialize CM3232 + * @chip: pointer of struct cm3232_chip. + * + * Check and initialize CM3232 ambient light sensor. + * + * Return: 0 for success; otherwise for error code. + */ +static int cm3232_reg_init(struct cm3232_chip *chip) +{ + struct i2c_client *client = chip->client; + s32 ret; + + chip->als_info = &cm3232_als_info_default; + + /* Identify device */ + ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ID); + if (ret < 0) { + dev_err(&chip->client->dev, "Error reading addr_id\n"); + return ret; + } + + if ((ret & 0xFF) != chip->als_info->hw_id) + return -ENODEV; + + /* Disable and reset device */ + chip->regs_cmd = CM3232_CMD_ALS_DISABLE | CM3232_CMD_ALS_RESET; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + if (ret < 0) { + dev_err(&chip->client->dev, "Error writing reg_cmd\n"); + return ret; + } + + /* Register default value */ + chip->regs_cmd = chip->als_info->regs_cmd_default; + + /* Configure register */ + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + if (ret < 0) + dev_err(&chip->client->dev, "Error writing reg_cmd\n"); + + return 0; +} + +/** + * cm3232_read_als_it() - Get sensor integration time + * @chip: pointer of struct cm3232_chip + * @val: pointer of int to load the integration (sec). + * @val2: pointer of int to load the integration time (microsecond). + * + * Report the current integration time. + * + * Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. + */ +static int cm3232_read_als_it(struct cm3232_chip *chip, int *val, int *val2) +{ + u16 als_it; + int i; + + als_it = chip->regs_cmd; + als_it &= CM3232_CMD_ALS_IT_MASK; + als_it >>= CM3232_CMD_ALS_IT_SHIFT; + for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { + if (als_it == cm3232_als_it_scales[i].it) { + *val = cm3232_als_it_scales[i].val; + *val2 = cm3232_als_it_scales[i].val2; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +/** + * cm3232_write_als_it() - Write sensor integration time + * @chip: pointer of struct cm3232_chip. + * @val: integration time in second. + * @val2: integration time in microsecond. + * + * Convert integration time to sensor value. + * + * Return: i2c_smbus_write_byte_data command return value. + */ +static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2) +{ + struct i2c_client *client = chip->client; + u16 als_it, cmd; + int i; + s32 ret; + + for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { + if (val == cm3232_als_it_scales[i].val && + val2 == cm3232_als_it_scales[i].val2) { + + als_it = cm3232_als_it_scales[i].it; + als_it <<= CM3232_CMD_ALS_IT_SHIFT; + + cmd = chip->regs_cmd & ~CM3232_CMD_ALS_IT_MASK; + cmd |= als_it; + ret = i2c_smbus_write_byte_data(client, + CM3232_REG_ADDR_CMD, + cmd); + if (ret < 0) + return ret; + chip->regs_cmd = cmd; + return 0; + } + } + return -EINVAL; +} + +/** + * cm3232_get_lux() - report current lux value + * @chip: pointer of struct cm3232_chip. + * + * Convert sensor data to lux. It depends on integration + * time and calibscale variable. + * + * Return: Zero or positive value is lux, otherwise error code. + */ +static int cm3232_get_lux(struct cm3232_chip *chip) +{ + struct i2c_client *client = chip->client; + struct cm3232_als_info *als_info = chip->als_info; + int ret; + int val, val2; + int als_it; + u64 lux; + + /* Calculate mlux per bit based on als_it */ + ret = cm3232_read_als_it(chip, &val, &val2); + if (ret < 0) + return -EINVAL; + als_it = val * 1000000 + val2; + lux = (__force u64)als_info->mlux_per_bit; + lux *= als_info->mlux_per_bit_base_it; + lux = div_u64(lux, als_it); + + ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ALS); + if (ret < 0) { + dev_err(&client->dev, "Error reading reg_addr_als\n"); + return ret; + } + + chip->regs_als = (u16)ret; + lux *= chip->regs_als; + lux *= als_info->calibscale; + lux = div_u64(lux, CM3232_CALIBSCALE_RESOLUTION); + lux = div_u64(lux, CM3232_MLUX_PER_LUX); + + if (lux > 0xFFFF) + lux = 0xFFFF; + + return (int)lux; +} + +static int cm3232_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm3232_chip *chip = iio_priv(indio_dev); + struct cm3232_als_info *als_info = chip->als_info; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = cm3232_get_lux(chip); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = als_info->calibscale; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + return cm3232_read_als_it(chip, val, val2); + } + + return -EINVAL; +} + +static int cm3232_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm3232_chip *chip = iio_priv(indio_dev); + struct cm3232_als_info *als_info = chip->als_info; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + als_info->calibscale = val; + return 0; + case IIO_CHAN_INFO_INT_TIME: + return cm3232_write_als_it(chip, val, val2); + } + + return -EINVAL; +} + +/** + * cm3232_get_it_available() - Get available ALS IT value + * @dev: pointer of struct device. + * @attr: pointer of struct device_attribute. + * @buf: pointer of return string buffer. + * + * Display the available integration time in second. + * + * Return: string length. + */ +static ssize_t cm3232_get_it_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len; + + for (i = 0, len = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ", + cm3232_als_it_scales[i].val, + cm3232_als_it_scales[i].val2); + return len + scnprintf(buf + len, PAGE_SIZE - len, "\n"); +} + +static const struct iio_chan_spec cm3232_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, + S_IRUGO, cm3232_get_it_available, NULL, 0); + +static struct attribute *cm3232_attributes[] = { + &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm3232_attribute_group = { + .attrs = cm3232_attributes +}; + +static const struct iio_info cm3232_info = { + .driver_module = THIS_MODULE, + .read_raw = &cm3232_read_raw, + .write_raw = &cm3232_write_raw, + .attrs = &cm3232_attribute_group, +}; + +static int cm3232_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cm3232_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + chip->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = cm3232_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3232_channels); + indio_dev->info = &cm3232_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm3232_reg_init(chip); + if (ret) { + dev_err(&client->dev, + "%s: register init failed\n", + __func__); + return ret; + } + + return iio_device_register(indio_dev); +} + +static int cm3232_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + CM3232_CMD_ALS_DISABLE); + + iio_device_unregister(indio_dev); + + return 0; +} + +static const struct i2c_device_id cm3232_id[] = { + {"cm3232", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cm3232_id); + +static const struct of_device_id cm3232_of_match[] = { + {.compatible = "capella,cm3232"}, + {} +}; + +static struct i2c_driver cm3232_driver = { + .driver = { + .name = "cm3232", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(cm3232_of_match), + }, + .id_table = cm3232_id, + .probe = cm3232_probe, + .remove = cm3232_remove, +}; + +module_i2c_driver(cm3232_driver); + +MODULE_AUTHOR("Kevin Tsai "); +MODULE_DESCRIPTION("CM3232 ambient light sensor driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 346be9bc802ddbaf7ce2ad35145d1ddfba376594 Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Wed, 28 Jan 2015 12:28:24 +0100 Subject: Add myself as UBI co-maintainer ...and set the state to "Supported" as UBI is part of my day job. Signed-off-by: Richard Weinberger Acked-by: Artem Bityutskiy --- MAINTAINERS | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2ebb056cbe0a..6417fedaf9fe 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9891,20 +9891,15 @@ F: drivers/scsi/ufs/ UNSORTED BLOCK IMAGES (UBI) M: Artem Bityutskiy +M: Richard Weinberger W: http://www.linux-mtd.infradead.org/ L: linux-mtd@lists.infradead.org T: git git://git.infradead.org/ubifs-2.6.git -S: Maintained +S: Supported F: drivers/mtd/ubi/ F: include/linux/mtd/ubi.h F: include/uapi/mtd/ubi-user.h -UNSORTED BLOCK IMAGES (UBI) Fastmap -M: Richard Weinberger -L: linux-mtd@lists.infradead.org -S: Maintained -F: drivers/mtd/ubi/fastmap.c - USB ACM DRIVER M: Oliver Neukum L: linux-usb@vger.kernel.org -- cgit v1.2.3 From 4ca5829ac8b1297715bf609443ade2c332f3fd0c Mon Sep 17 00:00:00 2001 From: Markus Pargmann Date: Wed, 28 Jan 2015 19:35:38 +0100 Subject: MAINTAINERS: Update NBD maintainer Paul stops maintining NBD and I will take his place from now on. Signed-off-by: Markus Pargmann Signed-off-by: Jens Axboe --- MAINTAINERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..bb5e510e977e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6537,9 +6537,10 @@ F: include/uapi/linux/netrom.h F: net/netrom/ NETWORK BLOCK DEVICE (NBD) -M: Paul Clements +M: Markus Pargmann S: Maintained L: nbd-general@lists.sourceforge.net +T: git git://git.pengutronix.de/git/mpa/linux-nbd.git F: Documentation/blockdev/nbd.txt F: drivers/block/nbd.c F: include/linux/nbd.h -- cgit v1.2.3 From 5833ac98651f7985037e52d4b41f7d4e02e32064 Mon Sep 17 00:00:00 2001 From: Barry Song Date: Mon, 12 Jan 2015 00:04:43 +0800 Subject: clocksource: marco: Rename marco to atlas7 marco project is replaced by atlas7 and we should obliterate its all traces. Signed-off-by: Barry Song Acked-by: Arnd Bergmann Acked-by: Daniel Lezcano Signed-off-by: Daniel Lezcano --- MAINTAINERS | 2 +- drivers/clocksource/Makefile | 2 +- drivers/clocksource/timer-atlas7.c | 306 +++++++++++++++++++++++++++++++++++++ drivers/clocksource/timer-marco.c | 306 ------------------------------------- 4 files changed, 308 insertions(+), 308 deletions(-) create mode 100644 drivers/clocksource/timer-atlas7.c delete mode 100644 drivers/clocksource/timer-marco.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2fa385321245..f6923cf22d3f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -958,7 +958,7 @@ S: Maintained F: arch/arm/mach-prima2/ F: drivers/clk/sirf/ F: drivers/clocksource/timer-prima2.c -F: drivers/clocksource/timer-marco.c +F: drivers/clocksource/timer-atlas7.c N: [^a-z]sirf ARM/EBSA110 MACHINE SUPPORT diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 94d90b24b56b..306bc4770be0 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -18,7 +18,7 @@ obj-$(CONFIG_ARMADA_370_XP_TIMER) += time-armada-370-xp.o obj-$(CONFIG_ORION_TIMER) += time-orion.o obj-$(CONFIG_ARCH_BCM2835) += bcm2835_timer.o obj-$(CONFIG_ARCH_CLPS711X) += clps711x-timer.o -obj-$(CONFIG_ARCH_MARCO) += timer-marco.o +obj-$(CONFIG_ARCH_ATLAS7) += timer-atlas7.o obj-$(CONFIG_ARCH_MOXART) += moxart_timer.o obj-$(CONFIG_ARCH_MXS) += mxs_timer.o obj-$(CONFIG_ARCH_PXA) += pxa_timer.o diff --git a/drivers/clocksource/timer-atlas7.c b/drivers/clocksource/timer-atlas7.c new file mode 100644 index 000000000000..60f9de3438b0 --- /dev/null +++ b/drivers/clocksource/timer-atlas7.c @@ -0,0 +1,306 @@ +/* + * System timer for CSR SiRFprimaII + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIRFSOC_TIMER_32COUNTER_0_CTRL 0x0000 +#define SIRFSOC_TIMER_32COUNTER_1_CTRL 0x0004 +#define SIRFSOC_TIMER_MATCH_0 0x0018 +#define SIRFSOC_TIMER_MATCH_1 0x001c +#define SIRFSOC_TIMER_COUNTER_0 0x0048 +#define SIRFSOC_TIMER_COUNTER_1 0x004c +#define SIRFSOC_TIMER_INTR_STATUS 0x0060 +#define SIRFSOC_TIMER_WATCHDOG_EN 0x0064 +#define SIRFSOC_TIMER_64COUNTER_CTRL 0x0068 +#define SIRFSOC_TIMER_64COUNTER_LO 0x006c +#define SIRFSOC_TIMER_64COUNTER_HI 0x0070 +#define SIRFSOC_TIMER_64COUNTER_LOAD_LO 0x0074 +#define SIRFSOC_TIMER_64COUNTER_LOAD_HI 0x0078 +#define SIRFSOC_TIMER_64COUNTER_RLATCHED_LO 0x007c +#define SIRFSOC_TIMER_64COUNTER_RLATCHED_HI 0x0080 + +#define SIRFSOC_TIMER_REG_CNT 6 + +static unsigned long atlas7_timer_rate; + +static const u32 sirfsoc_timer_reg_list[SIRFSOC_TIMER_REG_CNT] = { + SIRFSOC_TIMER_WATCHDOG_EN, + SIRFSOC_TIMER_32COUNTER_0_CTRL, + SIRFSOC_TIMER_32COUNTER_1_CTRL, + SIRFSOC_TIMER_64COUNTER_CTRL, + SIRFSOC_TIMER_64COUNTER_RLATCHED_LO, + SIRFSOC_TIMER_64COUNTER_RLATCHED_HI, +}; + +static u32 sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT]; + +static void __iomem *sirfsoc_timer_base; + +/* disable count and interrupt */ +static inline void sirfsoc_timer_count_disable(int idx) +{ + writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx) & ~0x7, + sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx); +} + +/* enable count and interrupt */ +static inline void sirfsoc_timer_count_enable(int idx) +{ + writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx) | 0x3, + sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx); +} + +/* timer interrupt handler */ +static irqreturn_t sirfsoc_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *ce = dev_id; + int cpu = smp_processor_id(); + + /* clear timer interrupt */ + writel_relaxed(BIT(cpu), sirfsoc_timer_base + SIRFSOC_TIMER_INTR_STATUS); + + if (ce->mode == CLOCK_EVT_MODE_ONESHOT) + sirfsoc_timer_count_disable(cpu); + + ce->event_handler(ce); + + return IRQ_HANDLED; +} + +/* read 64-bit timer counter */ +static cycle_t sirfsoc_timer_read(struct clocksource *cs) +{ + u64 cycles; + + writel_relaxed((readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL) | + BIT(0)) & ~BIT(1), sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); + + cycles = readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_RLATCHED_HI); + cycles = (cycles << 32) | readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_RLATCHED_LO); + + return cycles; +} + +static int sirfsoc_timer_set_next_event(unsigned long delta, + struct clock_event_device *ce) +{ + int cpu = smp_processor_id(); + + /* disable timer first, then modify the related registers */ + sirfsoc_timer_count_disable(cpu); + + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_0 + + 4 * cpu); + writel_relaxed(delta, sirfsoc_timer_base + SIRFSOC_TIMER_MATCH_0 + + 4 * cpu); + + /* enable the tick */ + sirfsoc_timer_count_enable(cpu); + + return 0; +} + +static void sirfsoc_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *ce) +{ + switch (mode) { + case CLOCK_EVT_MODE_ONESHOT: + /* enable in set_next_event */ + break; + default: + break; + } + + sirfsoc_timer_count_disable(smp_processor_id()); +} + +static void sirfsoc_clocksource_suspend(struct clocksource *cs) +{ + int i; + + for (i = 0; i < SIRFSOC_TIMER_REG_CNT; i++) + sirfsoc_timer_reg_val[i] = readl_relaxed(sirfsoc_timer_base + sirfsoc_timer_reg_list[i]); +} + +static void sirfsoc_clocksource_resume(struct clocksource *cs) +{ + int i; + + for (i = 0; i < SIRFSOC_TIMER_REG_CNT - 2; i++) + writel_relaxed(sirfsoc_timer_reg_val[i], sirfsoc_timer_base + sirfsoc_timer_reg_list[i]); + + writel_relaxed(sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT - 2], + sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_LO); + writel_relaxed(sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT - 1], + sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_HI); + + writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL) | + BIT(1) | BIT(0), sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); +} + +static struct clock_event_device __percpu *sirfsoc_clockevent; + +static struct clocksource sirfsoc_clocksource = { + .name = "sirfsoc_clocksource", + .rating = 200, + .mask = CLOCKSOURCE_MASK(64), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .read = sirfsoc_timer_read, + .suspend = sirfsoc_clocksource_suspend, + .resume = sirfsoc_clocksource_resume, +}; + +static struct irqaction sirfsoc_timer_irq = { + .name = "sirfsoc_timer0", + .flags = IRQF_TIMER | IRQF_NOBALANCING, + .handler = sirfsoc_timer_interrupt, +}; + +static struct irqaction sirfsoc_timer1_irq = { + .name = "sirfsoc_timer1", + .flags = IRQF_TIMER | IRQF_NOBALANCING, + .handler = sirfsoc_timer_interrupt, +}; + +static int sirfsoc_local_timer_setup(struct clock_event_device *ce) +{ + int cpu = smp_processor_id(); + struct irqaction *action; + + if (cpu == 0) + action = &sirfsoc_timer_irq; + else + action = &sirfsoc_timer1_irq; + + ce->irq = action->irq; + ce->name = "local_timer"; + ce->features = CLOCK_EVT_FEAT_ONESHOT; + ce->rating = 200; + ce->set_mode = sirfsoc_timer_set_mode; + ce->set_next_event = sirfsoc_timer_set_next_event; + clockevents_calc_mult_shift(ce, atlas7_timer_rate, 60); + ce->max_delta_ns = clockevent_delta2ns(-2, ce); + ce->min_delta_ns = clockevent_delta2ns(2, ce); + ce->cpumask = cpumask_of(cpu); + + action->dev_id = ce; + BUG_ON(setup_irq(ce->irq, action)); + irq_force_affinity(action->irq, cpumask_of(cpu)); + + clockevents_register_device(ce); + return 0; +} + +static void sirfsoc_local_timer_stop(struct clock_event_device *ce) +{ + int cpu = smp_processor_id(); + + sirfsoc_timer_count_disable(1); + + if (cpu == 0) + remove_irq(sirfsoc_timer_irq.irq, &sirfsoc_timer_irq); + else + remove_irq(sirfsoc_timer1_irq.irq, &sirfsoc_timer1_irq); +} + +static int sirfsoc_cpu_notify(struct notifier_block *self, + unsigned long action, void *hcpu) +{ + /* + * Grab cpu pointer in each case to avoid spurious + * preemptible warnings + */ + switch (action & ~CPU_TASKS_FROZEN) { + case CPU_STARTING: + sirfsoc_local_timer_setup(this_cpu_ptr(sirfsoc_clockevent)); + break; + case CPU_DYING: + sirfsoc_local_timer_stop(this_cpu_ptr(sirfsoc_clockevent)); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block sirfsoc_cpu_nb = { + .notifier_call = sirfsoc_cpu_notify, +}; + +static void __init sirfsoc_clockevent_init(void) +{ + sirfsoc_clockevent = alloc_percpu(struct clock_event_device); + BUG_ON(!sirfsoc_clockevent); + + BUG_ON(register_cpu_notifier(&sirfsoc_cpu_nb)); + + /* Immediately configure the timer on the boot CPU */ + sirfsoc_local_timer_setup(this_cpu_ptr(sirfsoc_clockevent)); +} + +/* initialize the kernel jiffy timer source */ +static void __init sirfsoc_atlas7_timer_init(struct device_node *np) +{ + struct clk *clk; + + clk = of_clk_get(np, 0); + BUG_ON(IS_ERR(clk)); + + BUG_ON(clk_prepare_enable(clk)); + + atlas7_timer_rate = clk_get_rate(clk); + + /* timer dividers: 0, not divided */ + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL); + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_1_CTRL); + + /* Initialize timer counters to 0 */ + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_LO); + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_HI); + writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL) | + BIT(1) | BIT(0), sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_0); + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_1); + + /* Clear all interrupts */ + writel_relaxed(0xFFFF, sirfsoc_timer_base + SIRFSOC_TIMER_INTR_STATUS); + + BUG_ON(clocksource_register_hz(&sirfsoc_clocksource, atlas7_timer_rate)); + + sirfsoc_clockevent_init(); +} + +static void __init sirfsoc_of_timer_init(struct device_node *np) +{ + sirfsoc_timer_base = of_iomap(np, 0); + if (!sirfsoc_timer_base) + panic("unable to map timer cpu registers\n"); + + sirfsoc_timer_irq.irq = irq_of_parse_and_map(np, 0); + if (!sirfsoc_timer_irq.irq) + panic("No irq passed for timer0 via DT\n"); + + sirfsoc_timer1_irq.irq = irq_of_parse_and_map(np, 1); + if (!sirfsoc_timer1_irq.irq) + panic("No irq passed for timer1 via DT\n"); + + sirfsoc_atlas7_timer_init(np); +} +CLOCKSOURCE_OF_DECLARE(sirfsoc_atlas7_timer, "sirf,atlas7-tick", sirfsoc_of_timer_init); diff --git a/drivers/clocksource/timer-marco.c b/drivers/clocksource/timer-marco.c deleted file mode 100644 index 3ddb81f7ee66..000000000000 --- a/drivers/clocksource/timer-marco.c +++ /dev/null @@ -1,306 +0,0 @@ -/* - * System timer for CSR SiRFprimaII - * - * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. - * - * Licensed under GPLv2 or later. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SIRFSOC_TIMER_32COUNTER_0_CTRL 0x0000 -#define SIRFSOC_TIMER_32COUNTER_1_CTRL 0x0004 -#define SIRFSOC_TIMER_MATCH_0 0x0018 -#define SIRFSOC_TIMER_MATCH_1 0x001c -#define SIRFSOC_TIMER_COUNTER_0 0x0048 -#define SIRFSOC_TIMER_COUNTER_1 0x004c -#define SIRFSOC_TIMER_INTR_STATUS 0x0060 -#define SIRFSOC_TIMER_WATCHDOG_EN 0x0064 -#define SIRFSOC_TIMER_64COUNTER_CTRL 0x0068 -#define SIRFSOC_TIMER_64COUNTER_LO 0x006c -#define SIRFSOC_TIMER_64COUNTER_HI 0x0070 -#define SIRFSOC_TIMER_64COUNTER_LOAD_LO 0x0074 -#define SIRFSOC_TIMER_64COUNTER_LOAD_HI 0x0078 -#define SIRFSOC_TIMER_64COUNTER_RLATCHED_LO 0x007c -#define SIRFSOC_TIMER_64COUNTER_RLATCHED_HI 0x0080 - -#define SIRFSOC_TIMER_REG_CNT 6 - -static unsigned long marco_timer_rate; - -static const u32 sirfsoc_timer_reg_list[SIRFSOC_TIMER_REG_CNT] = { - SIRFSOC_TIMER_WATCHDOG_EN, - SIRFSOC_TIMER_32COUNTER_0_CTRL, - SIRFSOC_TIMER_32COUNTER_1_CTRL, - SIRFSOC_TIMER_64COUNTER_CTRL, - SIRFSOC_TIMER_64COUNTER_RLATCHED_LO, - SIRFSOC_TIMER_64COUNTER_RLATCHED_HI, -}; - -static u32 sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT]; - -static void __iomem *sirfsoc_timer_base; - -/* disable count and interrupt */ -static inline void sirfsoc_timer_count_disable(int idx) -{ - writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx) & ~0x7, - sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx); -} - -/* enable count and interrupt */ -static inline void sirfsoc_timer_count_enable(int idx) -{ - writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx) | 0x3, - sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx); -} - -/* timer interrupt handler */ -static irqreturn_t sirfsoc_timer_interrupt(int irq, void *dev_id) -{ - struct clock_event_device *ce = dev_id; - int cpu = smp_processor_id(); - - /* clear timer interrupt */ - writel_relaxed(BIT(cpu), sirfsoc_timer_base + SIRFSOC_TIMER_INTR_STATUS); - - if (ce->mode == CLOCK_EVT_MODE_ONESHOT) - sirfsoc_timer_count_disable(cpu); - - ce->event_handler(ce); - - return IRQ_HANDLED; -} - -/* read 64-bit timer counter */ -static cycle_t sirfsoc_timer_read(struct clocksource *cs) -{ - u64 cycles; - - writel_relaxed((readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL) | - BIT(0)) & ~BIT(1), sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); - - cycles = readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_RLATCHED_HI); - cycles = (cycles << 32) | readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_RLATCHED_LO); - - return cycles; -} - -static int sirfsoc_timer_set_next_event(unsigned long delta, - struct clock_event_device *ce) -{ - int cpu = smp_processor_id(); - - /* disable timer first, then modify the related registers */ - sirfsoc_timer_count_disable(cpu); - - writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_0 + - 4 * cpu); - writel_relaxed(delta, sirfsoc_timer_base + SIRFSOC_TIMER_MATCH_0 + - 4 * cpu); - - /* enable the tick */ - sirfsoc_timer_count_enable(cpu); - - return 0; -} - -static void sirfsoc_timer_set_mode(enum clock_event_mode mode, - struct clock_event_device *ce) -{ - switch (mode) { - case CLOCK_EVT_MODE_ONESHOT: - /* enable in set_next_event */ - break; - default: - break; - } - - sirfsoc_timer_count_disable(smp_processor_id()); -} - -static void sirfsoc_clocksource_suspend(struct clocksource *cs) -{ - int i; - - for (i = 0; i < SIRFSOC_TIMER_REG_CNT; i++) - sirfsoc_timer_reg_val[i] = readl_relaxed(sirfsoc_timer_base + sirfsoc_timer_reg_list[i]); -} - -static void sirfsoc_clocksource_resume(struct clocksource *cs) -{ - int i; - - for (i = 0; i < SIRFSOC_TIMER_REG_CNT - 2; i++) - writel_relaxed(sirfsoc_timer_reg_val[i], sirfsoc_timer_base + sirfsoc_timer_reg_list[i]); - - writel_relaxed(sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT - 2], - sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_LO); - writel_relaxed(sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT - 1], - sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_HI); - - writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL) | - BIT(1) | BIT(0), sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); -} - -static struct clock_event_device __percpu *sirfsoc_clockevent; - -static struct clocksource sirfsoc_clocksource = { - .name = "sirfsoc_clocksource", - .rating = 200, - .mask = CLOCKSOURCE_MASK(64), - .flags = CLOCK_SOURCE_IS_CONTINUOUS, - .read = sirfsoc_timer_read, - .suspend = sirfsoc_clocksource_suspend, - .resume = sirfsoc_clocksource_resume, -}; - -static struct irqaction sirfsoc_timer_irq = { - .name = "sirfsoc_timer0", - .flags = IRQF_TIMER | IRQF_NOBALANCING, - .handler = sirfsoc_timer_interrupt, -}; - -static struct irqaction sirfsoc_timer1_irq = { - .name = "sirfsoc_timer1", - .flags = IRQF_TIMER | IRQF_NOBALANCING, - .handler = sirfsoc_timer_interrupt, -}; - -static int sirfsoc_local_timer_setup(struct clock_event_device *ce) -{ - int cpu = smp_processor_id(); - struct irqaction *action; - - if (cpu == 0) - action = &sirfsoc_timer_irq; - else - action = &sirfsoc_timer1_irq; - - ce->irq = action->irq; - ce->name = "local_timer"; - ce->features = CLOCK_EVT_FEAT_ONESHOT; - ce->rating = 200; - ce->set_mode = sirfsoc_timer_set_mode; - ce->set_next_event = sirfsoc_timer_set_next_event; - clockevents_calc_mult_shift(ce, marco_timer_rate, 60); - ce->max_delta_ns = clockevent_delta2ns(-2, ce); - ce->min_delta_ns = clockevent_delta2ns(2, ce); - ce->cpumask = cpumask_of(cpu); - - action->dev_id = ce; - BUG_ON(setup_irq(ce->irq, action)); - irq_force_affinity(action->irq, cpumask_of(cpu)); - - clockevents_register_device(ce); - return 0; -} - -static void sirfsoc_local_timer_stop(struct clock_event_device *ce) -{ - int cpu = smp_processor_id(); - - sirfsoc_timer_count_disable(1); - - if (cpu == 0) - remove_irq(sirfsoc_timer_irq.irq, &sirfsoc_timer_irq); - else - remove_irq(sirfsoc_timer1_irq.irq, &sirfsoc_timer1_irq); -} - -static int sirfsoc_cpu_notify(struct notifier_block *self, - unsigned long action, void *hcpu) -{ - /* - * Grab cpu pointer in each case to avoid spurious - * preemptible warnings - */ - switch (action & ~CPU_TASKS_FROZEN) { - case CPU_STARTING: - sirfsoc_local_timer_setup(this_cpu_ptr(sirfsoc_clockevent)); - break; - case CPU_DYING: - sirfsoc_local_timer_stop(this_cpu_ptr(sirfsoc_clockevent)); - break; - } - - return NOTIFY_OK; -} - -static struct notifier_block sirfsoc_cpu_nb = { - .notifier_call = sirfsoc_cpu_notify, -}; - -static void __init sirfsoc_clockevent_init(void) -{ - sirfsoc_clockevent = alloc_percpu(struct clock_event_device); - BUG_ON(!sirfsoc_clockevent); - - BUG_ON(register_cpu_notifier(&sirfsoc_cpu_nb)); - - /* Immediately configure the timer on the boot CPU */ - sirfsoc_local_timer_setup(this_cpu_ptr(sirfsoc_clockevent)); -} - -/* initialize the kernel jiffy timer source */ -static void __init sirfsoc_marco_timer_init(struct device_node *np) -{ - struct clk *clk; - - clk = of_clk_get(np, 0); - BUG_ON(IS_ERR(clk)); - - BUG_ON(clk_prepare_enable(clk)); - - marco_timer_rate = clk_get_rate(clk); - - /* timer dividers: 0, not divided */ - writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); - writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL); - writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_1_CTRL); - - /* Initialize timer counters to 0 */ - writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_LO); - writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_HI); - writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL) | - BIT(1) | BIT(0), sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); - writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_0); - writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_1); - - /* Clear all interrupts */ - writel_relaxed(0xFFFF, sirfsoc_timer_base + SIRFSOC_TIMER_INTR_STATUS); - - BUG_ON(clocksource_register_hz(&sirfsoc_clocksource, marco_timer_rate)); - - sirfsoc_clockevent_init(); -} - -static void __init sirfsoc_of_timer_init(struct device_node *np) -{ - sirfsoc_timer_base = of_iomap(np, 0); - if (!sirfsoc_timer_base) - panic("unable to map timer cpu registers\n"); - - sirfsoc_timer_irq.irq = irq_of_parse_and_map(np, 0); - if (!sirfsoc_timer_irq.irq) - panic("No irq passed for timer0 via DT\n"); - - sirfsoc_timer1_irq.irq = irq_of_parse_and_map(np, 1); - if (!sirfsoc_timer1_irq.irq) - panic("No irq passed for timer1 via DT\n"); - - sirfsoc_marco_timer_init(np); -} -CLOCKSOURCE_OF_DECLARE(sirfsoc_marco_timer, "sirf,marco-tick", sirfsoc_of_timer_init ); -- cgit v1.2.3 From b7e78170efd46db039f56f76f4aa672134004c41 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Wed, 28 Jan 2015 10:16:18 -0600 Subject: PCI: versatile: Add DT-based ARM Versatile PB PCIe host driver This converts the Versatile PCI host code to a platform driver using the commom DT parsing and setup. The driver uses only an empty ARM pci_sys_data struct and does not use pci_common_init_dev init function. The old host code will be removed in a subsequent commit when Versatile is completely converted to DT. I've tested this on QEMU with the sym53c8xx driver in both i/o and memory mapped modes. Signed-off-by: Rob Herring Signed-off-by: Bjorn Helgaas Acked-by: Linus Walleij CC: Russell King CC: Peter Maydell --- MAINTAINERS | 8 ++ drivers/pci/host/Kconfig | 4 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pci-versatile.c | 237 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 250 insertions(+) create mode 100644 drivers/pci/host/pci-versatile.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..1d38850a4b87 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7210,6 +7210,14 @@ F: include/linux/pci* F: arch/x86/pci/ F: arch/x86/kernel/quirks.c +PCI DRIVER FOR ARM VERSATILE PLATFORM +M: Rob Herring +L: linux-pci@vger.kernel.org +L: linux-arm-kernel@lists.infradead.org +S: Maintained +F: Documentation/devicetree/bindings/pci/versatile.txt +F: drivers/pci/host/pci-versatile.c + PCI DRIVER FOR APPLIEDMICRO XGENE M: Tanmay Inamdar L: linux-pci@vger.kernel.org diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index c4b6568e486d..7b892a9cc4fc 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -102,4 +102,8 @@ config PCI_LAYERSCAPE help Say Y here if you want PCIe controller support on Layerscape SoCs. +config PCI_VERSATILE + bool "ARM Versatile PB PCI controller" + depends on ARCH_VERSATILE + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 44c26998027f..e61d91c92bf1 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o obj-$(CONFIG_PCI_XGENE) += pci-xgene.o obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o +obj-$(CONFIG_PCI_VERSATILE) += pci-versatile.o diff --git a/drivers/pci/host/pci-versatile.c b/drivers/pci/host/pci-versatile.c new file mode 100644 index 000000000000..341529ca23e8 --- /dev/null +++ b/drivers/pci/host/pci-versatile.c @@ -0,0 +1,237 @@ +/* + * Copyright 2004 Koninklijke Philips Electronics NV + * + * Conversion to platform driver and DT: + * Copyright 2014 Linaro Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 14/04/2005 Initial version, colin.king@philips.com + */ +#include +#include +#include +#include +#include +#include +#include + +static void __iomem *versatile_pci_base; +static void __iomem *versatile_cfg_base[2]; + +#define PCI_IMAP(m) (versatile_pci_base + ((m) * 4)) +#define PCI_SMAP(m) (versatile_pci_base + 0x14 + ((m) * 4)) +#define PCI_SELFID (versatile_pci_base + 0xc) + +#define VP_PCI_DEVICE_ID 0x030010ee +#define VP_PCI_CLASS_ID 0x0b400000 + +static u32 pci_slot_ignore; + +static int __init versatile_pci_slot_ignore(char *str) +{ + int retval; + int slot; + + while ((retval = get_option(&str, &slot))) { + if ((slot < 0) || (slot > 31)) + pr_err("Illegal slot value: %d\n", slot); + else + pci_slot_ignore |= (1 << slot); + } + return 1; +} +__setup("pci_slot_ignore=", versatile_pci_slot_ignore); + + +static void __iomem *versatile_map_bus(struct pci_bus *bus, + unsigned int devfn, int offset) +{ + unsigned int busnr = bus->number; + + if (pci_slot_ignore & (1 << PCI_SLOT(devfn))) + return NULL; + + return versatile_cfg_base[1] + ((busnr << 16) | (devfn << 8) | offset); +} + +static struct pci_ops pci_versatile_ops = { + .map_bus = versatile_map_bus, + .read = pci_generic_config_read32, + .write = pci_generic_config_write, +}; + +static int versatile_pci_parse_request_of_pci_ranges(struct device *dev, + struct list_head *res) +{ + int err, mem = 1, res_valid = 0; + struct device_node *np = dev->of_node; + resource_size_t iobase; + struct pci_host_bridge_window *win; + + err = of_pci_get_host_bridge_resources(np, 0, 0xff, res, &iobase); + if (err) + return err; + + list_for_each_entry(win, res, list) { + struct resource *parent, *res = win->res; + + switch (resource_type(res)) { + case IORESOURCE_IO: + parent = &ioport_resource; + err = pci_remap_iospace(res, iobase); + if (err) { + dev_warn(dev, "error %d: failed to map resource %pR\n", + err, res); + continue; + } + break; + case IORESOURCE_MEM: + parent = &iomem_resource; + res_valid |= !(res->flags & IORESOURCE_PREFETCH); + + writel(res->start >> 28, PCI_IMAP(mem)); + writel(PHYS_OFFSET >> 28, PCI_SMAP(mem)); + mem++; + + break; + case IORESOURCE_BUS: + default: + continue; + } + + err = devm_request_resource(dev, parent, res); + if (err) + goto out_release_res; + } + + if (!res_valid) { + dev_err(dev, "non-prefetchable memory resource required\n"); + err = -EINVAL; + goto out_release_res; + } + + return 0; + +out_release_res: + pci_free_resource_list(res); + return err; +} + +/* Unused, temporary to satisfy ARM arch code */ +struct pci_sys_data sys; + +static int versatile_pci_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret, i, myslot = -1; + u32 val; + void __iomem *local_pci_cfg_base; + struct pci_bus *bus; + LIST_HEAD(pci_res); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + versatile_pci_base = devm_ioremap_resource(&pdev->dev, res); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -ENODEV; + versatile_cfg_base[0] = devm_ioremap_resource(&pdev->dev, res); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (!res) + return -ENODEV; + versatile_cfg_base[1] = devm_ioremap_resource(&pdev->dev, res); + + ret = versatile_pci_parse_request_of_pci_ranges(&pdev->dev, &pci_res); + if (ret) + return ret; + + /* + * We need to discover the PCI core first to configure itself + * before the main PCI probing is performed + */ + for (i = 0; i < 32; i++) { + if ((readl(versatile_cfg_base[0] + (i << 11) + PCI_VENDOR_ID) == VP_PCI_DEVICE_ID) && + (readl(versatile_cfg_base[0] + (i << 11) + PCI_CLASS_REVISION) == VP_PCI_CLASS_ID)) { + myslot = i; + break; + } + } + if (myslot == -1) { + dev_err(&pdev->dev, "Cannot find PCI core!\n"); + return -EIO; + } + /* + * Do not to map Versatile FPGA PCI device into memory space + */ + pci_slot_ignore |= (1 << myslot); + + dev_info(&pdev->dev, "PCI core found (slot %d)\n", myslot); + + writel(myslot, PCI_SELFID); + local_pci_cfg_base = versatile_cfg_base[1] + (myslot << 11); + + val = readl(local_pci_cfg_base + PCI_COMMAND); + val |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE; + writel(val, local_pci_cfg_base + PCI_COMMAND); + + /* + * Configure the PCI inbound memory windows to be 1:1 mapped to SDRAM + */ + writel(PHYS_OFFSET, local_pci_cfg_base + PCI_BASE_ADDRESS_0); + writel(PHYS_OFFSET, local_pci_cfg_base + PCI_BASE_ADDRESS_1); + writel(PHYS_OFFSET, local_pci_cfg_base + PCI_BASE_ADDRESS_2); + + /* + * For many years the kernel and QEMU were symbiotically buggy + * in that they both assumed the same broken IRQ mapping. + * QEMU therefore attempts to auto-detect old broken kernels + * so that they still work on newer QEMU as they did on old + * QEMU. Since we now use the correct (ie matching-hardware) + * IRQ mapping we write a definitely different value to a + * PCI_INTERRUPT_LINE register to tell QEMU that we expect + * real hardware behaviour and it need not be backwards + * compatible for us. This write is harmless on real hardware. + */ + writel(0, versatile_cfg_base[0] + PCI_INTERRUPT_LINE); + + pci_add_flags(PCI_ENABLE_PROC_DOMAINS); + pci_add_flags(PCI_REASSIGN_ALL_BUS | PCI_REASSIGN_ALL_RSRC); + + bus = pci_scan_root_bus(&pdev->dev, 0, &pci_versatile_ops, &sys, &pci_res); + if (!bus) + return -ENOMEM; + + pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); + pci_assign_unassigned_bus_resources(bus); + + return 0; +} + +static const struct of_device_id versatile_pci_of_match[] = { + { .compatible = "arm,versatile-pci", }, + { }, +}; +MODULE_DEVICE_TABLE(of, versatile_pci_of_match); + +static struct platform_driver versatile_pci_driver = { + .driver = { + .name = "versatile-pci", + .of_match_table = versatile_pci_of_match, + }, + .probe = versatile_pci_probe, +}; +module_platform_driver(versatile_pci_driver); + +MODULE_DESCRIPTION("Versatile PCI driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 3f7a3f6ecf3f8bb144617a190aef3e0dd258078d Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 23 Dec 2014 09:11:48 -0300 Subject: [media] tlg2300: remove deprecated staging driver This driver hasn't been tested in a long, long time. The company that made this chip has gone bust many years ago and hardware using this chip is next to impossible to find. This driver needs to be converted to newer media frameworks but due to the lack of hardware that's going to be impossible. Since cheap alternatives are easily available, there is little point in keeping this driver alive. This driver is already deprecated, so now remove it altogether. Signed-off-by: Hans Verkuil Cc: Huang Shijie Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 6 - drivers/staging/media/Kconfig | 2 - drivers/staging/media/Makefile | 1 - drivers/staging/media/tlg2300/Kconfig | 21 - drivers/staging/media/tlg2300/Makefile | 9 - drivers/staging/media/tlg2300/pd-alsa.c | 337 ------ drivers/staging/media/tlg2300/pd-common.h | 270 ----- drivers/staging/media/tlg2300/pd-dvb.c | 597 ----------- drivers/staging/media/tlg2300/pd-main.c | 553 ---------- drivers/staging/media/tlg2300/pd-radio.c | 336 ------ drivers/staging/media/tlg2300/pd-video.c | 1560 ---------------------------- drivers/staging/media/tlg2300/vendorcmds.h | 243 ----- 12 files changed, 3935 deletions(-) delete mode 100644 drivers/staging/media/tlg2300/Kconfig delete mode 100644 drivers/staging/media/tlg2300/Makefile delete mode 100644 drivers/staging/media/tlg2300/pd-alsa.c delete mode 100644 drivers/staging/media/tlg2300/pd-common.h delete mode 100644 drivers/staging/media/tlg2300/pd-dvb.c delete mode 100644 drivers/staging/media/tlg2300/pd-main.c delete mode 100644 drivers/staging/media/tlg2300/pd-radio.c delete mode 100644 drivers/staging/media/tlg2300/pd-video.c delete mode 100644 drivers/staging/media/tlg2300/vendorcmds.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 15bec7491fe9..f3ae573d1d4f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8393,12 +8393,6 @@ F: kernel/time/clocksource.c F: kernel/time/time*.c F: kernel/time/ntp.c -TLG2300 VIDEO4LINUX-2 DRIVER -M: Huang Shijie -M: Hans Verkuil -S: Odd Fixes -F: drivers/media/usb/tlg2300/ - SC1200 WDT DRIVER M: Zwane Mwaikambo S: Maintained diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index 2a054a99d433..61e4acb1e4ae 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -27,8 +27,6 @@ source "drivers/staging/media/davinci_vpfe/Kconfig" source "drivers/staging/media/dt3155v4l/Kconfig" -source "drivers/staging/media/tlg2300/Kconfig" - source "drivers/staging/media/mn88472/Kconfig" source "drivers/staging/media/mn88473/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 412b28408398..b7bda1057f54 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -7,6 +7,5 @@ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ obj-$(CONFIG_DVB_MN88472) += mn88472/ obj-$(CONFIG_DVB_MN88473) += mn88473/ obj-y += parport/ -obj-$(CONFIG_VIDEO_TLG2300) += tlg2300/ obj-y += vino/ diff --git a/drivers/staging/media/tlg2300/Kconfig b/drivers/staging/media/tlg2300/Kconfig deleted file mode 100644 index 77d8753f6ba4..000000000000 --- a/drivers/staging/media/tlg2300/Kconfig +++ /dev/null @@ -1,21 +0,0 @@ -config VIDEO_TLG2300 - tristate "Telegent TLG2300 USB video capture support (Deprecated)" - depends on VIDEO_DEV && I2C && SND && DVB_CORE - depends on MEDIA_USB_SUPPORT - select VIDEO_TUNER - select VIDEO_TVEEPROM - depends on RC_CORE - select VIDEOBUF_VMALLOC - select SND_PCM - select VIDEOBUF_DVB - - ---help--- - This is a video4linux driver for Telegent tlg2300 based TV cards. - The driver supports V4L2, DVB-T and radio. - - This driver is deprecated and will be removed soon. If you have - hardware for this and you want to work on this driver, then contact - the linux-media mailinglist. - - To compile this driver as a module, choose M here: the - module will be called poseidon diff --git a/drivers/staging/media/tlg2300/Makefile b/drivers/staging/media/tlg2300/Makefile deleted file mode 100644 index 137f8e38cdec..000000000000 --- a/drivers/staging/media/tlg2300/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -poseidon-objs := pd-video.o pd-alsa.o pd-dvb.o pd-radio.o pd-main.o - -obj-$(CONFIG_VIDEO_TLG2300) += poseidon.o - -ccflags-y += -Idrivers/media/i2c -ccflags-y += -Idrivers/media/tuners -ccflags-y += -Idrivers/media/dvb-core -ccflags-y += -Idrivers/media/dvb-frontends - diff --git a/drivers/staging/media/tlg2300/pd-alsa.c b/drivers/staging/media/tlg2300/pd-alsa.c deleted file mode 100644 index dd8fe100590f..000000000000 --- a/drivers/staging/media/tlg2300/pd-alsa.c +++ /dev/null @@ -1,337 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "pd-common.h" -#include "vendorcmds.h" - -static void complete_handler_audio(struct urb *urb); -#define AUDIO_EP (0x83) -#define AUDIO_BUF_SIZE (512) -#define PERIOD_SIZE (1024 * 8) -#define PERIOD_MIN (4) -#define PERIOD_MAX PERIOD_MIN - -static struct snd_pcm_hardware snd_pd_hw_capture = { - .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_MMAP_VALID, - - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .rates = SNDRV_PCM_RATE_48000, - - .rate_min = 48000, - .rate_max = 48000, - .channels_min = 2, - .channels_max = 2, - .buffer_bytes_max = PERIOD_SIZE * PERIOD_MIN, - .period_bytes_min = PERIOD_SIZE, - .period_bytes_max = PERIOD_SIZE, - .periods_min = PERIOD_MIN, - .periods_max = PERIOD_MAX, - /* - .buffer_bytes_max = 62720 * 8, - .period_bytes_min = 64, - .period_bytes_max = 12544, - .periods_min = 2, - .periods_max = 98 - */ -}; - -static int snd_pd_capture_open(struct snd_pcm_substream *substream) -{ - struct poseidon *p = snd_pcm_substream_chip(substream); - struct poseidon_audio *pa = &p->audio; - struct snd_pcm_runtime *runtime = substream->runtime; - - if (!p) - return -ENODEV; - pa->users++; - pa->card_close = 0; - pa->capture_pcm_substream = substream; - runtime->private_data = p; - - runtime->hw = snd_pd_hw_capture; - snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - usb_autopm_get_interface(p->interface); - kref_get(&p->kref); - return 0; -} - -static int snd_pd_pcm_close(struct snd_pcm_substream *substream) -{ - struct poseidon *p = snd_pcm_substream_chip(substream); - struct poseidon_audio *pa = &p->audio; - - pa->users--; - pa->card_close = 1; - usb_autopm_put_interface(p->interface); - kref_put(&p->kref, poseidon_delete); - return 0; -} - -static int snd_pd_hw_capture_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - unsigned int size; - - size = params_buffer_bytes(hw_params); - if (runtime->dma_area) { - if (runtime->dma_bytes > size) - return 0; - vfree(runtime->dma_area); - } - runtime->dma_area = vmalloc(size); - if (!runtime->dma_area) - return -ENOMEM; - else - runtime->dma_bytes = size; - return 0; -} - -static int audio_buf_free(struct poseidon *p) -{ - struct poseidon_audio *pa = &p->audio; - int i; - - for (i = 0; i < AUDIO_BUFS; i++) - if (pa->urb_array[i]) - usb_kill_urb(pa->urb_array[i]); - free_all_urb_generic(pa->urb_array, AUDIO_BUFS); - logpm(); - return 0; -} - -static int snd_pd_hw_capture_free(struct snd_pcm_substream *substream) -{ - struct poseidon *p = snd_pcm_substream_chip(substream); - - logpm(); - audio_buf_free(p); - return 0; -} - -static int snd_pd_prepare(struct snd_pcm_substream *substream) -{ - return 0; -} - -#define AUDIO_TRAILER_SIZE (16) -static inline void handle_audio_data(struct urb *urb, int *period_elapsed) -{ - struct poseidon_audio *pa = urb->context; - struct snd_pcm_runtime *runtime = pa->capture_pcm_substream->runtime; - - int stride = runtime->frame_bits >> 3; - int len = urb->actual_length / stride; - unsigned char *cp = urb->transfer_buffer; - unsigned int oldptr = pa->rcv_position; - - if (urb->actual_length == AUDIO_BUF_SIZE - 4) - len -= (AUDIO_TRAILER_SIZE / stride); - - /* do the copy */ - if (oldptr + len >= runtime->buffer_size) { - unsigned int cnt = runtime->buffer_size - oldptr; - - memcpy(runtime->dma_area + oldptr * stride, cp, cnt * stride); - memcpy(runtime->dma_area, (cp + cnt * stride), - (len * stride - cnt * stride)); - } else - memcpy(runtime->dma_area + oldptr * stride, cp, len * stride); - - /* update the statas */ - snd_pcm_stream_lock(pa->capture_pcm_substream); - pa->rcv_position += len; - if (pa->rcv_position >= runtime->buffer_size) - pa->rcv_position -= runtime->buffer_size; - - pa->copied_position += (len); - if (pa->copied_position >= runtime->period_size) { - pa->copied_position -= runtime->period_size; - *period_elapsed = 1; - } - snd_pcm_stream_unlock(pa->capture_pcm_substream); -} - -static void complete_handler_audio(struct urb *urb) -{ - struct poseidon_audio *pa = urb->context; - struct snd_pcm_substream *substream = pa->capture_pcm_substream; - int period_elapsed = 0; - int ret; - - if (1 == pa->card_close || pa->capture_stream != STREAM_ON) - return; - - if (urb->status != 0) { - /*if (urb->status == -ESHUTDOWN)*/ - return; - } - - if (substream) { - if (urb->actual_length) { - handle_audio_data(urb, &period_elapsed); - if (period_elapsed) - snd_pcm_period_elapsed(substream); - } - } - - ret = usb_submit_urb(urb, GFP_ATOMIC); - if (ret < 0) - log("audio urb failed (errcod = %i)", ret); - return; -} - -static int fire_audio_urb(struct poseidon *p) -{ - int i, ret = 0; - struct poseidon_audio *pa = &p->audio; - - alloc_bulk_urbs_generic(pa->urb_array, AUDIO_BUFS, - p->udev, AUDIO_EP, - AUDIO_BUF_SIZE, GFP_ATOMIC, - complete_handler_audio, pa); - - for (i = 0; i < AUDIO_BUFS; i++) { - ret = usb_submit_urb(pa->urb_array[i], GFP_KERNEL); - if (ret) - log("urb err : %d", ret); - } - log(); - return ret; -} - -static int snd_pd_capture_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct poseidon *p = snd_pcm_substream_chip(substream); - struct poseidon_audio *pa = &p->audio; - - if (debug_mode) - log("cmd %d, audio stat : %d\n", cmd, pa->capture_stream); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_RESUME: - case SNDRV_PCM_TRIGGER_START: - if (pa->capture_stream == STREAM_ON) - return 0; - - pa->rcv_position = pa->copied_position = 0; - pa->capture_stream = STREAM_ON; - - if (in_hibernation(p)) - return 0; - fire_audio_urb(p); - return 0; - - case SNDRV_PCM_TRIGGER_SUSPEND: - pa->capture_stream = STREAM_SUSPEND; - return 0; - case SNDRV_PCM_TRIGGER_STOP: - pa->capture_stream = STREAM_OFF; - return 0; - default: - return -EINVAL; - } -} - -static snd_pcm_uframes_t -snd_pd_capture_pointer(struct snd_pcm_substream *substream) -{ - struct poseidon *p = snd_pcm_substream_chip(substream); - struct poseidon_audio *pa = &p->audio; - return pa->rcv_position; -} - -static struct page *snd_pcm_pd_get_page(struct snd_pcm_substream *subs, - unsigned long offset) -{ - void *pageptr = subs->runtime->dma_area + offset; - return vmalloc_to_page(pageptr); -} - -static struct snd_pcm_ops pcm_capture_ops = { - .open = snd_pd_capture_open, - .close = snd_pd_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_pd_hw_capture_params, - .hw_free = snd_pd_hw_capture_free, - .prepare = snd_pd_prepare, - .trigger = snd_pd_capture_trigger, - .pointer = snd_pd_capture_pointer, - .page = snd_pcm_pd_get_page, -}; - -#ifdef CONFIG_PM -int pm_alsa_suspend(struct poseidon *p) -{ - logpm(p); - audio_buf_free(p); - return 0; -} - -int pm_alsa_resume(struct poseidon *p) -{ - logpm(p); - fire_audio_urb(p); - return 0; -} -#endif - -int poseidon_audio_init(struct poseidon *p) -{ - struct poseidon_audio *pa = &p->audio; - struct snd_card *card; - struct snd_pcm *pcm; - int ret; - - ret = snd_card_new(&p->interface->dev, -1, "Telegent", - THIS_MODULE, 0, &card); - if (ret != 0) - return ret; - - ret = snd_pcm_new(card, "poseidon audio", 0, 0, 1, &pcm); - if (ret < 0) { - snd_card_free(card); - return ret; - } - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); - pcm->info_flags = 0; - pcm->private_data = p; - strcpy(pcm->name, "poseidon audio capture"); - - strcpy(card->driver, "ALSA driver"); - strcpy(card->shortname, "poseidon Audio"); - strcpy(card->longname, "poseidon ALSA Audio"); - - if (snd_card_register(card)) { - snd_card_free(card); - return -ENOMEM; - } - pa->card = card; - return 0; -} - -int poseidon_audio_free(struct poseidon *p) -{ - struct poseidon_audio *pa = &p->audio; - - if (pa->card) - snd_card_free(pa->card); - return 0; -} diff --git a/drivers/staging/media/tlg2300/pd-common.h b/drivers/staging/media/tlg2300/pd-common.h deleted file mode 100644 index 04c5aacd836e..000000000000 --- a/drivers/staging/media/tlg2300/pd-common.h +++ /dev/null @@ -1,270 +0,0 @@ -#ifndef PD_COMMON_H -#define PD_COMMON_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dvb_frontend.h" -#include "dvbdev.h" -#include "dvb_demux.h" -#include "dmxdev.h" - -#define SBUF_NUM 8 -#define MAX_BUFFER_NUM 6 -#define PK_PER_URB 32 -#define ISO_PKT_SIZE 3072 - -#define POSEIDON_STATE_NONE (0x0000) -#define POSEIDON_STATE_ANALOG (0x0001) -#define POSEIDON_STATE_FM (0x0002) -#define POSEIDON_STATE_DVBT (0x0004) -#define POSEIDON_STATE_DISCONNECT (0x0080) - -#define PM_SUSPEND_DELAY 3 - -#define V4L_PAL_VBI_LINES 18 -#define V4L_NTSC_VBI_LINES 12 -#define V4L_PAL_VBI_FRAMESIZE (V4L_PAL_VBI_LINES * 1440 * 2) -#define V4L_NTSC_VBI_FRAMESIZE (V4L_NTSC_VBI_LINES * 1440 * 2) - -#define TUNER_FREQ_MIN (45000000U) -#define TUNER_FREQ_MAX (862000000U) - -struct vbi_data { - struct video_device v_dev; - struct video_data *video; - struct front_face *front; - - unsigned int copied; - unsigned int vbi_size; /* the whole size of two fields */ - int users; -}; - -/* - * This is the running context of the video, it is useful for - * resume() - */ -struct running_context { - u32 freq; /* VIDIOC_S_FREQUENCY */ - int audio_idx; /* VIDIOC_S_TUNER */ - v4l2_std_id tvnormid; /* VIDIOC_S_STD */ - int sig_index; /* VIDIOC_S_INPUT */ - struct v4l2_pix_format pix; /* VIDIOC_S_FMT */ -}; - -struct video_data { - /* v4l2 video device */ - struct video_device v_dev; - struct v4l2_ctrl_handler ctrl_handler; - - /* the working context */ - struct running_context context; - - /* for data copy */ - int field_count; - - char *dst; - int lines_copied; - int prev_left; - - int lines_per_field; - int lines_size; - - /* for communication */ - u8 endpoint_addr; - struct urb *urb_array[SBUF_NUM]; - struct vbi_data *vbi; - struct poseidon *pd; - struct front_face *front; - - int is_streaming; - int users; - - /* for bubble handler */ - struct work_struct bubble_work; -}; - -enum pcm_stream_state { - STREAM_OFF, - STREAM_ON, - STREAM_SUSPEND, -}; - -#define AUDIO_BUFS (3) -#define CAPTURE_STREAM_EN 1 -struct poseidon_audio { - struct urb *urb_array[AUDIO_BUFS]; - unsigned int copied_position; - struct snd_pcm_substream *capture_pcm_substream; - - unsigned int rcv_position; - struct snd_card *card; - int card_close; - - int users; - int pm_state; - enum pcm_stream_state capture_stream; -}; - -struct radio_data { - __u32 fm_freq; - unsigned int is_radio_streaming; - int pre_emphasis; - struct video_device fm_dev; - struct v4l2_ctrl_handler ctrl_handler; -}; - -#define DVB_SBUF_NUM 4 -#define DVB_URB_BUF_SIZE 0x2000 -struct pd_dvb_adapter { - struct dvb_adapter dvb_adap; - struct dvb_frontend dvb_fe; - struct dmxdev dmxdev; - struct dvb_demux demux; - - atomic_t users; - atomic_t active_feed; - - /* data transfer */ - s32 is_streaming; - struct urb *urb_array[DVB_SBUF_NUM]; - struct poseidon *pd_device; - u8 ep_addr; - u8 reserved[3]; - - /* data for power resume*/ - struct dtv_frontend_properties fe_param; - - /* for channel scanning */ - int prev_freq; - int bandwidth; - unsigned long last_jiffies; -}; - -struct front_face { - /* use this field to distinguish VIDEO and VBI */ - enum v4l2_buf_type type; - - /* for host */ - struct videobuf_queue q; - - /* the bridge for host and device */ - struct videobuf_buffer *curr_frame; - - /* for device */ - spinlock_t queue_lock; - struct list_head active; - struct poseidon *pd; -}; - -struct poseidon { - struct list_head device_list; - - struct mutex lock; - struct kref kref; - - /* for V4L2 */ - struct v4l2_device v4l2_dev; - - /* hardware info */ - struct usb_device *udev; - struct usb_interface *interface; - int cur_transfer_mode; - - struct video_data video_data; /* video */ - struct vbi_data vbi_data; /* vbi */ - struct poseidon_audio audio; /* audio (alsa) */ - struct radio_data radio_data; /* FM */ - struct pd_dvb_adapter dvb_data; /* DVB */ - - u32 state; - struct file *file_for_stream; /* the active stream*/ - -#ifdef CONFIG_PM - int (*pm_suspend)(struct poseidon *); - int (*pm_resume)(struct poseidon *); - pm_message_t msg; - - struct work_struct pm_work; - u8 portnum; -#endif -}; - -struct poseidon_format { - char *name; - int fourcc; /* video4linux 2 */ - int depth; /* bit/pixel */ - int flags; -}; - -struct poseidon_tvnorm { - v4l2_std_id v4l2_id; - char name[12]; - u32 tlg_tvnorm; -}; - -/* video */ -int pd_video_init(struct poseidon *); -void pd_video_exit(struct poseidon *); -int stop_all_video_stream(struct poseidon *); - -/* alsa audio */ -int poseidon_audio_init(struct poseidon *); -int poseidon_audio_free(struct poseidon *); -#ifdef CONFIG_PM -int pm_alsa_suspend(struct poseidon *); -int pm_alsa_resume(struct poseidon *); -#endif - -/* dvb */ -int pd_dvb_usb_device_init(struct poseidon *); -void pd_dvb_usb_device_exit(struct poseidon *); -void pd_dvb_usb_device_cleanup(struct poseidon *); -int pd_dvb_get_adapter_num(struct pd_dvb_adapter *); -void dvb_stop_streaming(struct pd_dvb_adapter *); - -/* FM */ -int poseidon_fm_init(struct poseidon *); -int poseidon_fm_exit(struct poseidon *); - -/* vendor command ops */ -int send_set_req(struct poseidon*, u8, s32, s32*); -int send_get_req(struct poseidon*, u8, s32, void*, s32*, s32); -s32 set_tuner_mode(struct poseidon*, unsigned char); - -/* bulk urb alloc/free */ -int alloc_bulk_urbs_generic(struct urb **urb_array, int num, - struct usb_device *udev, u8 ep_addr, - int buf_size, gfp_t gfp_flags, - usb_complete_t complete_fn, void *context); -void free_all_urb_generic(struct urb **urb_array, int num); - -/* misc */ -void poseidon_delete(struct kref *kref); -extern int debug_mode; - -#ifdef CONFIG_PM -#define in_hibernation(pd) (pd->msg.event == PM_EVENT_FREEZE) -#else -#define in_hibernation(pd) (0) -#endif -#define get_pm_count(p) (atomic_read(&(p)->interface->pm_usage_cnt)) - -#define log(a, ...) printk(KERN_DEBUG "\t[ %s : %.3d ] "a"\n", \ - __func__, __LINE__, ## __VA_ARGS__) - -/* for power management */ -#define logpm(pd) do {\ - if (debug_mode & 0x10)\ - log();\ - } while (0) - -#endif diff --git a/drivers/staging/media/tlg2300/pd-dvb.c b/drivers/staging/media/tlg2300/pd-dvb.c deleted file mode 100644 index ca4994a5190c..000000000000 --- a/drivers/staging/media/tlg2300/pd-dvb.c +++ /dev/null @@ -1,597 +0,0 @@ -#include "pd-common.h" -#include -#include -#include -#include -#include -#include - -#include "vendorcmds.h" -#include -#include - -static void dvb_urb_cleanup(struct pd_dvb_adapter *pd_dvb); - -static int dvb_bandwidth[][2] = { - { TLG_BW_8, 8000000 }, - { TLG_BW_7, 7000000 }, - { TLG_BW_6, 6000000 } -}; -static int dvb_bandwidth_length = ARRAY_SIZE(dvb_bandwidth); - -static s32 dvb_start_streaming(struct pd_dvb_adapter *pd_dvb); -static int poseidon_check_mode_dvbt(struct poseidon *pd) -{ - s32 ret = 0, cmd_status = 0; - - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(HZ/4); - - ret = usb_set_interface(pd->udev, 0, BULK_ALTERNATE_IFACE); - if (ret != 0) - return ret; - - ret = set_tuner_mode(pd, TLG_MODE_CAPS_DVB_T); - if (ret) - return ret; - - /* signal source */ - ret = send_set_req(pd, SGNL_SRC_SEL, TLG_SIG_SRC_ANTENNA, &cmd_status); - if (ret|cmd_status) - return ret; - - return 0; -} - -/* acquire : - * 1 == open - * 0 == release - */ -static int poseidon_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) -{ - struct poseidon *pd = fe->demodulator_priv; - struct pd_dvb_adapter *pd_dvb; - int ret = 0; - - if (!pd) - return -ENODEV; - - pd_dvb = container_of(fe, struct pd_dvb_adapter, dvb_fe); - if (acquire) { - mutex_lock(&pd->lock); - if (pd->state & POSEIDON_STATE_DISCONNECT) { - ret = -ENODEV; - goto open_out; - } - - if (pd->state && !(pd->state & POSEIDON_STATE_DVBT)) { - ret = -EBUSY; - goto open_out; - } - - usb_autopm_get_interface(pd->interface); - if (0 == pd->state) { - ret = poseidon_check_mode_dvbt(pd); - if (ret < 0) { - usb_autopm_put_interface(pd->interface); - goto open_out; - } - pd->state |= POSEIDON_STATE_DVBT; - pd_dvb->bandwidth = 0; - pd_dvb->prev_freq = 0; - } - atomic_inc(&pd_dvb->users); - kref_get(&pd->kref); -open_out: - mutex_unlock(&pd->lock); - } else { - dvb_stop_streaming(pd_dvb); - - if (atomic_dec_and_test(&pd_dvb->users)) { - mutex_lock(&pd->lock); - pd->state &= ~POSEIDON_STATE_DVBT; - mutex_unlock(&pd->lock); - } - kref_put(&pd->kref, poseidon_delete); - usb_autopm_put_interface(pd->interface); - } - return ret; -} - -#ifdef CONFIG_PM -static void poseidon_fe_release(struct dvb_frontend *fe) -{ - struct poseidon *pd = fe->demodulator_priv; - - pd->pm_suspend = NULL; - pd->pm_resume = NULL; -} -#else -#define poseidon_fe_release NULL -#endif - -static s32 poseidon_fe_sleep(struct dvb_frontend *fe) -{ - return 0; -} - -/* - * return true if we can satisfy the conditions, else return false. - */ -static bool check_scan_ok(__u32 freq, int bandwidth, - struct pd_dvb_adapter *adapter) -{ - if (bandwidth < 0) - return false; - - if (adapter->prev_freq == freq - && adapter->bandwidth == bandwidth) { - long nl = jiffies - adapter->last_jiffies; - unsigned int msec ; - - msec = jiffies_to_msecs(abs(nl)); - return msec > 15000 ? true : false; - } - return true; -} - -/* - * Check if the firmware delays too long for an invalid frequency. - */ -static int fw_delay_overflow(struct pd_dvb_adapter *adapter) -{ - long nl = jiffies - adapter->last_jiffies; - unsigned int msec ; - - msec = jiffies_to_msecs(abs(nl)); - return msec > 800 ? true : false; -} - -static int poseidon_set_fe(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *fep = &fe->dtv_property_cache; - s32 ret = 0, cmd_status = 0; - s32 i, bandwidth = -1; - struct poseidon *pd = fe->demodulator_priv; - struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; - - if (in_hibernation(pd)) - return -EBUSY; - - mutex_lock(&pd->lock); - for (i = 0; i < dvb_bandwidth_length; i++) - if (fep->bandwidth_hz == dvb_bandwidth[i][1]) - bandwidth = dvb_bandwidth[i][0]; - - if (check_scan_ok(fep->frequency, bandwidth, pd_dvb)) { - ret = send_set_req(pd, TUNE_FREQ_SELECT, - fep->frequency / 1000, &cmd_status); - if (ret | cmd_status) { - log("error line"); - goto front_out; - } - - ret = send_set_req(pd, DVBT_BANDW_SEL, - bandwidth, &cmd_status); - if (ret | cmd_status) { - log("error line"); - goto front_out; - } - - ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status); - if (ret | cmd_status) { - log("error line"); - goto front_out; - } - - /* save the context for future */ - memcpy(&pd_dvb->fe_param, fep, sizeof(*fep)); - pd_dvb->bandwidth = bandwidth; - pd_dvb->prev_freq = fep->frequency; - pd_dvb->last_jiffies = jiffies; - } -front_out: - mutex_unlock(&pd->lock); - return ret; -} - -#ifdef CONFIG_PM -static int pm_dvb_suspend(struct poseidon *pd) -{ - struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; - dvb_stop_streaming(pd_dvb); - dvb_urb_cleanup(pd_dvb); - msleep(500); - return 0; -} - -static int pm_dvb_resume(struct poseidon *pd) -{ - struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; - - poseidon_check_mode_dvbt(pd); - msleep(300); - poseidon_set_fe(&pd_dvb->dvb_fe); - - dvb_start_streaming(pd_dvb); - return 0; -} -#endif - -static s32 poseidon_fe_init(struct dvb_frontend *fe) -{ - struct poseidon *pd = fe->demodulator_priv; - struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; - -#ifdef CONFIG_PM - pd->pm_suspend = pm_dvb_suspend; - pd->pm_resume = pm_dvb_resume; -#endif - memset(&pd_dvb->fe_param, 0, - sizeof(struct dtv_frontend_properties)); - return 0; -} - -static int poseidon_get_fe(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *fep = &fe->dtv_property_cache; - struct poseidon *pd = fe->demodulator_priv; - struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; - - memcpy(fep, &pd_dvb->fe_param, sizeof(*fep)); - return 0; -} - -static int poseidon_fe_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *tune) -{ - tune->min_delay_ms = 1000; - return 0; -} - -static int poseidon_read_status(struct dvb_frontend *fe, fe_status_t *stat) -{ - struct poseidon *pd = fe->demodulator_priv; - s32 ret = -1, cmd_status; - struct tuner_dtv_sig_stat_s status = {}; - - if (in_hibernation(pd)) - return -EBUSY; - mutex_lock(&pd->lock); - - ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_DVB_T, - &status, &cmd_status, sizeof(status)); - if (ret | cmd_status) { - log("get tuner status error"); - goto out; - } - - if (debug_mode) - log("P : %d, L %d, LB :%d", status.sig_present, - status.sig_locked, status.sig_lock_busy); - - if (status.sig_lock_busy) { - goto out; - } else if (status.sig_present || status.sig_locked) { - *stat |= FE_HAS_LOCK | FE_HAS_SIGNAL | FE_HAS_CARRIER - | FE_HAS_SYNC | FE_HAS_VITERBI; - } else { - if (fw_delay_overflow(&pd->dvb_data)) - *stat |= FE_TIMEDOUT; - } -out: - mutex_unlock(&pd->lock); - return ret; -} - -static int poseidon_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct poseidon *pd = fe->demodulator_priv; - struct tuner_ber_rate_s tlg_ber = {}; - s32 ret = -1, cmd_status; - - mutex_lock(&pd->lock); - ret = send_get_req(pd, TUNER_BER_RATE, 0, - &tlg_ber, &cmd_status, sizeof(tlg_ber)); - if (ret | cmd_status) - goto out; - *ber = tlg_ber.ber_rate; -out: - mutex_unlock(&pd->lock); - return ret; -} - -static s32 poseidon_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - struct poseidon *pd = fe->demodulator_priv; - struct tuner_dtv_sig_stat_s status = {}; - s32 ret = 0, cmd_status; - - mutex_lock(&pd->lock); - ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_DVB_T, - &status, &cmd_status, sizeof(status)); - if (ret | cmd_status) - goto out; - if ((status.sig_present || status.sig_locked) && !status.sig_strength) - *strength = 0xFFFF; - else - *strength = status.sig_strength; -out: - mutex_unlock(&pd->lock); - return ret; -} - -static int poseidon_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - return 0; -} - -static int poseidon_read_unc_blocks(struct dvb_frontend *fe, u32 *unc) -{ - *unc = 0; - return 0; -} - -static struct dvb_frontend_ops poseidon_frontend_ops = { - .delsys = { SYS_DVBT }, - .info = { - .name = "Poseidon DVB-T", - .frequency_min = 174000000, - .frequency_max = 862000000, - .frequency_stepsize = 62500,/* FIXME */ - .caps = FE_CAN_INVERSION_AUTO | - FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | - FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | - FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_RECOVER | - FE_CAN_HIERARCHY_AUTO, - }, - - .release = poseidon_fe_release, - - .init = poseidon_fe_init, - .sleep = poseidon_fe_sleep, - - .set_frontend = poseidon_set_fe, - .get_frontend = poseidon_get_fe, - .get_tune_settings = poseidon_fe_get_tune_settings, - - .read_status = poseidon_read_status, - .read_ber = poseidon_read_ber, - .read_signal_strength = poseidon_read_signal_strength, - .read_snr = poseidon_read_snr, - .read_ucblocks = poseidon_read_unc_blocks, - - .ts_bus_ctrl = poseidon_ts_bus_ctrl, -}; - -static void dvb_urb_irq(struct urb *urb) -{ - struct pd_dvb_adapter *pd_dvb = urb->context; - int len = urb->transfer_buffer_length; - struct dvb_demux *demux = &pd_dvb->demux; - s32 ret; - - if (!pd_dvb->is_streaming || urb->status) { - if (urb->status == -EPROTO) - goto resend; - return; - } - - if (urb->actual_length == len) - dvb_dmx_swfilter(demux, urb->transfer_buffer, len); - else if (urb->actual_length == len - 4) { - int offset; - u8 *buf = urb->transfer_buffer; - - /* - * The packet size is 512, - * last packet contains 456 bytes tsp data - */ - for (offset = 456; offset < len; offset += 512) { - if (!strncmp(buf + offset, "DVHS", 4)) { - dvb_dmx_swfilter(demux, buf, offset); - if (len > offset + 52 + 4) { - /*16 bytes trailer + 36 bytes padding */ - buf += offset + 52; - len -= offset + 52 + 4; - dvb_dmx_swfilter(demux, buf, len); - } - break; - } - } - } - -resend: - ret = usb_submit_urb(urb, GFP_ATOMIC); - if (ret) - log(" usb_submit_urb failed: error %d", ret); -} - -static int dvb_urb_init(struct pd_dvb_adapter *pd_dvb) -{ - if (pd_dvb->urb_array[0]) - return 0; - - alloc_bulk_urbs_generic(pd_dvb->urb_array, DVB_SBUF_NUM, - pd_dvb->pd_device->udev, pd_dvb->ep_addr, - DVB_URB_BUF_SIZE, GFP_KERNEL, - dvb_urb_irq, pd_dvb); - return 0; -} - -static void dvb_urb_cleanup(struct pd_dvb_adapter *pd_dvb) -{ - free_all_urb_generic(pd_dvb->urb_array, DVB_SBUF_NUM); -} - -static s32 dvb_start_streaming(struct pd_dvb_adapter *pd_dvb) -{ - struct poseidon *pd = pd_dvb->pd_device; - int ret = 0; - - if (pd->state & POSEIDON_STATE_DISCONNECT) - return -ENODEV; - - mutex_lock(&pd->lock); - if (!pd_dvb->is_streaming) { - s32 i, cmd_status = 0; - /* - * Once upon a time, there was a difficult bug lying here. - * ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status); - */ - - ret = send_set_req(pd, PLAY_SERVICE, 1, &cmd_status); - if (ret | cmd_status) - goto out; - - ret = dvb_urb_init(pd_dvb); - if (ret < 0) - goto out; - - pd_dvb->is_streaming = 1; - for (i = 0; i < DVB_SBUF_NUM; i++) { - ret = usb_submit_urb(pd_dvb->urb_array[i], - GFP_KERNEL); - if (ret) { - log(" submit urb error %d", ret); - goto out; - } - } - } -out: - mutex_unlock(&pd->lock); - return ret; -} - -void dvb_stop_streaming(struct pd_dvb_adapter *pd_dvb) -{ - struct poseidon *pd = pd_dvb->pd_device; - - mutex_lock(&pd->lock); - if (pd_dvb->is_streaming) { - s32 i, ret, cmd_status = 0; - - pd_dvb->is_streaming = 0; - - for (i = 0; i < DVB_SBUF_NUM; i++) - if (pd_dvb->urb_array[i]) - usb_kill_urb(pd_dvb->urb_array[i]); - - ret = send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, - &cmd_status); - if (ret | cmd_status) - log("error"); - } - mutex_unlock(&pd->lock); -} - -static int pd_start_feed(struct dvb_demux_feed *feed) -{ - struct pd_dvb_adapter *pd_dvb = feed->demux->priv; - int ret = 0; - - if (!pd_dvb) - return -1; - if (atomic_inc_return(&pd_dvb->active_feed) == 1) - ret = dvb_start_streaming(pd_dvb); - return ret; -} - -static int pd_stop_feed(struct dvb_demux_feed *feed) -{ - struct pd_dvb_adapter *pd_dvb = feed->demux->priv; - - if (!pd_dvb) - return -1; - if (atomic_dec_and_test(&pd_dvb->active_feed)) - dvb_stop_streaming(pd_dvb); - return 0; -} - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); -int pd_dvb_usb_device_init(struct poseidon *pd) -{ - struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; - struct dvb_demux *dvbdemux; - int ret = 0; - - pd_dvb->ep_addr = 0x82; - atomic_set(&pd_dvb->users, 0); - atomic_set(&pd_dvb->active_feed, 0); - pd_dvb->pd_device = pd; - - ret = dvb_register_adapter(&pd_dvb->dvb_adap, - "Poseidon dvbt adapter", - THIS_MODULE, - NULL /* for hibernation correctly*/, - adapter_nr); - if (ret < 0) - goto error1; - - /* register frontend */ - pd_dvb->dvb_fe.demodulator_priv = pd; - memcpy(&pd_dvb->dvb_fe.ops, &poseidon_frontend_ops, - sizeof(struct dvb_frontend_ops)); - ret = dvb_register_frontend(&pd_dvb->dvb_adap, &pd_dvb->dvb_fe); - if (ret < 0) - goto error2; - - /* register demux device */ - dvbdemux = &pd_dvb->demux; - dvbdemux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING; - dvbdemux->priv = pd_dvb; - dvbdemux->feednum = dvbdemux->filternum = 64; - dvbdemux->start_feed = pd_start_feed; - dvbdemux->stop_feed = pd_stop_feed; - dvbdemux->write_to_decoder = NULL; - - ret = dvb_dmx_init(dvbdemux); - if (ret < 0) - goto error3; - - pd_dvb->dmxdev.filternum = pd_dvb->demux.filternum; - pd_dvb->dmxdev.demux = &pd_dvb->demux.dmx; - pd_dvb->dmxdev.capabilities = 0; - - ret = dvb_dmxdev_init(&pd_dvb->dmxdev, &pd_dvb->dvb_adap); - if (ret < 0) - goto error3; - return 0; - -error3: - dvb_unregister_frontend(&pd_dvb->dvb_fe); -error2: - dvb_unregister_adapter(&pd_dvb->dvb_adap); -error1: - return ret; -} - -void pd_dvb_usb_device_exit(struct poseidon *pd) -{ - struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; - - while (atomic_read(&pd_dvb->users) != 0 - || atomic_read(&pd_dvb->active_feed) != 0) { - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(HZ); - } - dvb_dmxdev_release(&pd_dvb->dmxdev); - dvb_unregister_frontend(&pd_dvb->dvb_fe); - dvb_unregister_adapter(&pd_dvb->dvb_adap); - pd_dvb_usb_device_cleanup(pd); -} - -void pd_dvb_usb_device_cleanup(struct poseidon *pd) -{ - struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; - - dvb_urb_cleanup(pd_dvb); -} - -int pd_dvb_get_adapter_num(struct pd_dvb_adapter *pd_dvb) -{ - return pd_dvb->dvb_adap.num; -} diff --git a/drivers/staging/media/tlg2300/pd-main.c b/drivers/staging/media/tlg2300/pd-main.c deleted file mode 100644 index b31f4791b8ff..000000000000 --- a/drivers/staging/media/tlg2300/pd-main.c +++ /dev/null @@ -1,553 +0,0 @@ -/* - * device driver for Telegent tlg2300 based TV cards - * - * Author : - * Kang Yong - * Zhang Xiaobing - * Huang Shijie or - * - * (c) 2009 Telegent Systems - * (c) 2010 Telegent Systems - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "vendorcmds.h" -#include "pd-common.h" - -#define VENDOR_ID 0x1B24 -#define PRODUCT_ID 0x4001 -static struct usb_device_id id_table[] = { - { USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID, PRODUCT_ID, 255, 1, 0) }, - { USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID, PRODUCT_ID, 255, 1, 1) }, - { }, -}; -MODULE_DEVICE_TABLE(usb, id_table); - -int debug_mode; -module_param(debug_mode, int, 0644); -MODULE_PARM_DESC(debug_mode, "0 = disable, 1 = enable, 2 = verbose"); - -#define TLG2300_FIRMWARE "tlg2300_firmware.bin" -static const char *firmware_name = TLG2300_FIRMWARE; -static LIST_HEAD(pd_device_list); - -/* - * send set request to USB firmware. - */ -s32 send_set_req(struct poseidon *pd, u8 cmdid, s32 param, s32 *cmd_status) -{ - s32 ret; - s8 data[32] = {}; - u16 lower_16, upper_16; - - if (pd->state & POSEIDON_STATE_DISCONNECT) - return -ENODEV; - - mdelay(30); - - if (param == 0) { - upper_16 = lower_16 = 0; - } else { - /* send 32 bit param as two 16 bit param,little endian */ - lower_16 = (unsigned short)(param & 0xffff); - upper_16 = (unsigned short)((param >> 16) & 0xffff); - } - ret = usb_control_msg(pd->udev, - usb_rcvctrlpipe(pd->udev, 0), - REQ_SET_CMD | cmdid, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - lower_16, - upper_16, - &data, - sizeof(*cmd_status), - USB_CTRL_GET_TIMEOUT); - - if (!ret) { - return -ENXIO; - } else { - /* 1st 4 bytes into cmd_status */ - memcpy((char *)cmd_status, &(data[0]), sizeof(*cmd_status)); - } - return 0; -} - -/* - * send get request to Poseidon firmware. - */ -s32 send_get_req(struct poseidon *pd, u8 cmdid, s32 param, - void *buf, s32 *cmd_status, s32 datalen) -{ - s32 ret; - s8 data[128] = {}; - u16 lower_16, upper_16; - - if (pd->state & POSEIDON_STATE_DISCONNECT) - return -ENODEV; - - mdelay(30); - if (param == 0) { - upper_16 = lower_16 = 0; - } else { - /*send 32 bit param as two 16 bit param, little endian */ - lower_16 = (unsigned short)(param & 0xffff); - upper_16 = (unsigned short)((param >> 16) & 0xffff); - } - ret = usb_control_msg(pd->udev, - usb_rcvctrlpipe(pd->udev, 0), - REQ_GET_CMD | cmdid, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - lower_16, - upper_16, - &data, - (datalen + sizeof(*cmd_status)), - USB_CTRL_GET_TIMEOUT); - - if (ret < 0) { - return -ENXIO; - } else { - /* 1st 4 bytes into cmd_status, remaining data into cmd_data */ - memcpy((char *)cmd_status, &data[0], sizeof(*cmd_status)); - memcpy((char *)buf, &data[sizeof(*cmd_status)], datalen); - } - return 0; -} - -static int pm_notifier_block(struct notifier_block *nb, - unsigned long event, void *dummy) -{ - struct poseidon *pd = NULL; - struct list_head *node, *next; - - switch (event) { - case PM_POST_HIBERNATION: - list_for_each_safe(node, next, &pd_device_list) { - struct usb_device *udev; - struct usb_interface *iface; - int rc = 0; - - pd = container_of(node, struct poseidon, device_list); - udev = pd->udev; - iface = pd->interface; - - /* It will cause the system to reload the firmware */ - rc = usb_lock_device_for_reset(udev, iface); - if (rc >= 0) { - usb_reset_device(udev); - usb_unlock_device(udev); - } - } - break; - default: - break; - } - log("event :%ld\n", event); - return 0; -} - -static struct notifier_block pm_notifer = { - .notifier_call = pm_notifier_block, -}; - -int set_tuner_mode(struct poseidon *pd, unsigned char mode) -{ - s32 ret, cmd_status; - - if (pd->state & POSEIDON_STATE_DISCONNECT) - return -ENODEV; - - ret = send_set_req(pd, TUNE_MODE_SELECT, mode, &cmd_status); - if (ret || cmd_status) - return -ENXIO; - return 0; -} - -void poseidon_delete(struct kref *kref) -{ - struct poseidon *pd = container_of(kref, struct poseidon, kref); - - if (!pd) - return; - list_del_init(&pd->device_list); - - pd_dvb_usb_device_cleanup(pd); - /* clean_audio_data(&pd->audio_data);*/ - - if (pd->udev) { - usb_put_dev(pd->udev); - pd->udev = NULL; - } - if (pd->interface) { - usb_put_intf(pd->interface); - pd->interface = NULL; - } - kfree(pd); - log(); -} - -static int firmware_download(struct usb_device *udev) -{ - int ret = 0, actual_length; - const struct firmware *fw = NULL; - void *fwbuf = NULL; - size_t fwlength = 0, offset; - size_t max_packet_size; - - ret = request_firmware(&fw, firmware_name, &udev->dev); - if (ret) { - log("download err : %d", ret); - return ret; - } - - fwlength = fw->size; - - fwbuf = kmemdup(fw->data, fwlength, GFP_KERNEL); - if (!fwbuf) { - ret = -ENOMEM; - goto out; - } - - max_packet_size = le16_to_cpu(udev->ep_out[0x1]->desc.wMaxPacketSize); - log("\t\t download size : %d", (int)max_packet_size); - - for (offset = 0; offset < fwlength; offset += max_packet_size) { - actual_length = 0; - ret = usb_bulk_msg(udev, - usb_sndbulkpipe(udev, 0x01), /* ep 1 */ - fwbuf + offset, - min(max_packet_size, fwlength - offset), - &actual_length, - HZ * 10); - if (ret) - break; - } - kfree(fwbuf); -out: - release_firmware(fw); - return ret; -} - -static inline struct poseidon *get_pd(struct usb_interface *intf) -{ - return usb_get_intfdata(intf); -} - -#ifdef CONFIG_PM -/* one-to-one map : poseidon{} <----> usb_device{}'s port */ -static inline void set_map_flags(struct poseidon *pd, struct usb_device *udev) -{ - pd->portnum = udev->portnum; -} - -static inline int get_autopm_ref(struct poseidon *pd) -{ - return pd->video_data.users + pd->vbi_data.users + pd->audio.users - + atomic_read(&pd->dvb_data.users) + - !list_empty(&pd->radio_data.fm_dev.fh_list); -} - -/* fixup something for poseidon */ -static inline struct poseidon *fixup(struct poseidon *pd) -{ - int count; - - /* old udev and interface have gone, so put back reference . */ - count = get_autopm_ref(pd); - log("count : %d, ref count : %d", count, get_pm_count(pd)); - while (count--) - usb_autopm_put_interface(pd->interface); - /*usb_autopm_set_interface(pd->interface); */ - - usb_put_dev(pd->udev); - usb_put_intf(pd->interface); - log("event : %d\n", pd->msg.event); - return pd; -} - -static struct poseidon *find_old_poseidon(struct usb_device *udev) -{ - struct poseidon *pd; - - list_for_each_entry(pd, &pd_device_list, device_list) { - if (pd->portnum == udev->portnum && in_hibernation(pd)) - return fixup(pd); - } - return NULL; -} - -/* Is the card working now ? */ -static inline int is_working(struct poseidon *pd) -{ - return get_pm_count(pd) > 0; -} - -static int poseidon_suspend(struct usb_interface *intf, pm_message_t msg) -{ - struct poseidon *pd = get_pd(intf); - - if (!pd) - return 0; - if (!is_working(pd)) { - if (get_pm_count(pd) <= 0 && !in_hibernation(pd)) { - pd->msg.event = PM_EVENT_AUTO_SUSPEND; - pd->pm_resume = NULL; /* a good guard */ - printk(KERN_DEBUG "TLG2300 auto suspend\n"); - } - return 0; - } - pd->msg = msg; /* save it here */ - logpm(pd); - return pd->pm_suspend ? pd->pm_suspend(pd) : 0; -} - -static int poseidon_resume(struct usb_interface *intf) -{ - struct poseidon *pd = get_pd(intf); - - if (!pd) - return 0; - printk(KERN_DEBUG "TLG2300 resume\n"); - - if (!is_working(pd)) { - if (PM_EVENT_AUTO_SUSPEND == pd->msg.event) - pd->msg = PMSG_ON; - return 0; - } - if (in_hibernation(pd)) { - logpm(pd); - return 0; - } - logpm(pd); - return pd->pm_resume ? pd->pm_resume(pd) : 0; -} - -static void hibernation_resume(struct work_struct *w) -{ - struct poseidon *pd = container_of(w, struct poseidon, pm_work); - int count; - - pd->msg.event = 0; /* clear it here */ - pd->state &= ~POSEIDON_STATE_DISCONNECT; - - /* set the new interface's reference */ - count = get_autopm_ref(pd); - while (count--) - usb_autopm_get_interface(pd->interface); - - /* resume the context */ - logpm(pd); - if (pd->pm_resume) - pd->pm_resume(pd); -} -#else /* CONFIG_PM is not enabled: */ -static inline struct poseidon *find_old_poseidon(struct usb_device *udev) -{ - return NULL; -} - -static inline void set_map_flags(struct poseidon *pd, struct usb_device *udev) -{ -} -#endif - -static int check_firmware(struct usb_device *udev) -{ - void *buf; - int ret; - struct cmd_firmware_vers_s *cmd_firm; - - buf = kzalloc(sizeof(*cmd_firm) + sizeof(u32), GFP_KERNEL); - if (!buf) - return -ENOMEM; - ret = usb_control_msg(udev, - usb_rcvctrlpipe(udev, 0), - REQ_GET_CMD | GET_FW_ID, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0, - 0, - buf, - sizeof(*cmd_firm) + sizeof(u32), - USB_CTRL_GET_TIMEOUT); - kfree(buf); - - if (ret < 0) - return firmware_download(udev); - return 0; -} - -static int poseidon_probe(struct usb_interface *interface, - const struct usb_device_id *id) -{ - struct usb_device *udev = interface_to_usbdev(interface); - struct poseidon *pd = NULL; - int ret = 0; - int new_one = 0; - - /* download firmware */ - ret = check_firmware(udev); - if (ret) - return ret; - - /* Do I recovery from the hibernate ? */ - pd = find_old_poseidon(udev); - if (!pd) { - pd = kzalloc(sizeof(*pd), GFP_KERNEL); - if (!pd) - return -ENOMEM; - kref_init(&pd->kref); - set_map_flags(pd, udev); - new_one = 1; - } - - pd->udev = usb_get_dev(udev); - pd->interface = usb_get_intf(interface); - usb_set_intfdata(interface, pd); - - if (new_one) { - logpm(pd); - mutex_init(&pd->lock); - - /* register v4l2 device */ - ret = v4l2_device_register(&interface->dev, &pd->v4l2_dev); - if (ret) - goto err_v4l2; - - /* register devices in directory /dev */ - ret = pd_video_init(pd); - if (ret) - goto err_video; - ret = poseidon_audio_init(pd); - if (ret) - goto err_audio; - ret = poseidon_fm_init(pd); - if (ret) - goto err_fm; - ret = pd_dvb_usb_device_init(pd); - if (ret) - goto err_dvb; - - INIT_LIST_HEAD(&pd->device_list); - list_add_tail(&pd->device_list, &pd_device_list); - } - - device_init_wakeup(&udev->dev, 1); -#ifdef CONFIG_PM - pm_runtime_set_autosuspend_delay(&pd->udev->dev, - 1000 * PM_SUSPEND_DELAY); - usb_enable_autosuspend(pd->udev); - - if (in_hibernation(pd)) { - INIT_WORK(&pd->pm_work, hibernation_resume); - schedule_work(&pd->pm_work); - } -#endif - return 0; -err_dvb: - poseidon_fm_exit(pd); -err_fm: - poseidon_audio_free(pd); -err_audio: - pd_video_exit(pd); -err_video: - v4l2_device_unregister(&pd->v4l2_dev); -err_v4l2: - usb_put_intf(pd->interface); - usb_put_dev(pd->udev); - kfree(pd); - return ret; -} - -static void poseidon_disconnect(struct usb_interface *interface) -{ - struct poseidon *pd = get_pd(interface); - - if (!pd) - return; - logpm(pd); - if (in_hibernation(pd)) - return; - - mutex_lock(&pd->lock); - pd->state |= POSEIDON_STATE_DISCONNECT; - mutex_unlock(&pd->lock); - - /* stop urb transferring */ - stop_all_video_stream(pd); - dvb_stop_streaming(&pd->dvb_data); - - /*unregister v4l2 device */ - v4l2_device_unregister(&pd->v4l2_dev); - - pd_dvb_usb_device_exit(pd); - poseidon_fm_exit(pd); - - poseidon_audio_free(pd); - pd_video_exit(pd); - - usb_set_intfdata(interface, NULL); - kref_put(&pd->kref, poseidon_delete); -} - -static struct usb_driver poseidon_driver = { - .name = "poseidon", - .probe = poseidon_probe, - .disconnect = poseidon_disconnect, - .id_table = id_table, -#ifdef CONFIG_PM - .suspend = poseidon_suspend, - .resume = poseidon_resume, -#endif - .supports_autosuspend = 1, -}; - -static int __init poseidon_init(void) -{ - int ret; - - ret = usb_register(&poseidon_driver); - if (ret) - return ret; - register_pm_notifier(&pm_notifer); - return ret; -} - -static void __exit poseidon_exit(void) -{ - log(); - unregister_pm_notifier(&pm_notifer); - usb_deregister(&poseidon_driver); -} - -module_init(poseidon_init); -module_exit(poseidon_exit); - -MODULE_AUTHOR("Telegent Systems"); -MODULE_DESCRIPTION("For tlg2300-based USB device"); -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.2"); -MODULE_FIRMWARE(TLG2300_FIRMWARE); diff --git a/drivers/staging/media/tlg2300/pd-radio.c b/drivers/staging/media/tlg2300/pd-radio.c deleted file mode 100644 index c0567b5ed363..000000000000 --- a/drivers/staging/media/tlg2300/pd-radio.c +++ /dev/null @@ -1,336 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pd-common.h" -#include "vendorcmds.h" - -static int set_frequency(struct poseidon *p, __u32 frequency); -static int poseidon_fm_close(struct file *filp); -static int poseidon_fm_open(struct file *filp); - -#define TUNER_FREQ_MIN_FM 76000000U -#define TUNER_FREQ_MAX_FM 108000000U - -#define MAX_PREEMPHASIS (V4L2_PREEMPHASIS_75_uS + 1) -static int preemphasis[MAX_PREEMPHASIS] = { - TLG_TUNE_ASTD_NONE, /* V4L2_PREEMPHASIS_DISABLED */ - TLG_TUNE_ASTD_FM_EUR, /* V4L2_PREEMPHASIS_50_uS */ - TLG_TUNE_ASTD_FM_US, /* V4L2_PREEMPHASIS_75_uS */ -}; - -static int poseidon_check_mode_radio(struct poseidon *p) -{ - int ret; - u32 status; - - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(HZ/2); - ret = usb_set_interface(p->udev, 0, BULK_ALTERNATE_IFACE); - if (ret < 0) - goto out; - - ret = set_tuner_mode(p, TLG_MODE_FM_RADIO); - if (ret != 0) - goto out; - - ret = send_set_req(p, SGNL_SRC_SEL, TLG_SIG_SRC_ANTENNA, &status); - ret = send_set_req(p, TUNER_AUD_ANA_STD, - p->radio_data.pre_emphasis, &status); - ret |= send_set_req(p, TUNER_AUD_MODE, - TLG_TUNE_TVAUDIO_MODE_STEREO, &status); - ret |= send_set_req(p, AUDIO_SAMPLE_RATE_SEL, - ATV_AUDIO_RATE_48K, &status); - ret |= send_set_req(p, TUNE_FREQ_SELECT, TUNER_FREQ_MIN_FM, &status); -out: - return ret; -} - -#ifdef CONFIG_PM -static int pm_fm_suspend(struct poseidon *p) -{ - logpm(p); - pm_alsa_suspend(p); - usb_set_interface(p->udev, 0, 0); - msleep(300); - return 0; -} - -static int pm_fm_resume(struct poseidon *p) -{ - logpm(p); - poseidon_check_mode_radio(p); - set_frequency(p, p->radio_data.fm_freq); - pm_alsa_resume(p); - return 0; -} -#endif - -static int poseidon_fm_open(struct file *filp) -{ - struct poseidon *p = video_drvdata(filp); - int ret = 0; - - mutex_lock(&p->lock); - if (p->state & POSEIDON_STATE_DISCONNECT) { - ret = -ENODEV; - goto out; - } - - if (p->state && !(p->state & POSEIDON_STATE_FM)) { - ret = -EBUSY; - goto out; - } - ret = v4l2_fh_open(filp); - if (ret) - goto out; - - usb_autopm_get_interface(p->interface); - if (0 == p->state) { - /* default pre-emphasis */ - if (p->radio_data.pre_emphasis == 0) - p->radio_data.pre_emphasis = TLG_TUNE_ASTD_FM_EUR; - - ret = poseidon_check_mode_radio(p); - if (ret < 0) { - usb_autopm_put_interface(p->interface); - goto out; - } - p->state |= POSEIDON_STATE_FM; - } - kref_get(&p->kref); -out: - mutex_unlock(&p->lock); - return ret; -} - -static int poseidon_fm_close(struct file *filp) -{ - struct poseidon *p = video_drvdata(filp); - struct radio_data *fm = &p->radio_data; - uint32_t status; - - mutex_lock(&p->lock); - if (v4l2_fh_is_singular_file(filp)) - p->state &= ~POSEIDON_STATE_FM; - - if (fm->is_radio_streaming && filp == p->file_for_stream) { - fm->is_radio_streaming = 0; - send_set_req(p, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, &status); - } - usb_autopm_put_interface(p->interface); - mutex_unlock(&p->lock); - - kref_put(&p->kref, poseidon_delete); - return v4l2_fh_release(filp); -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - struct poseidon *p = video_drvdata(file); - - strlcpy(v->driver, "tele-radio", sizeof(v->driver)); - strlcpy(v->card, "Telegent Poseidon", sizeof(v->card)); - usb_make_path(p->udev, v->bus_info, sizeof(v->bus_info)); - v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - /* Report all capabilities of the USB device */ - v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS | - V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE | - V4L2_CAP_AUDIO | V4L2_CAP_STREAMING | - V4L2_CAP_READWRITE; - return 0; -} - -static const struct v4l2_file_operations poseidon_fm_fops = { - .owner = THIS_MODULE, - .open = poseidon_fm_open, - .release = poseidon_fm_close, - .poll = v4l2_ctrl_poll, - .unlocked_ioctl = video_ioctl2, -}; - -static int tlg_fm_vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *vt) -{ - struct poseidon *p = video_drvdata(file); - struct tuner_fm_sig_stat_s fm_stat = {}; - int ret, status, count = 5; - - if (vt->index != 0) - return -EINVAL; - - vt->type = V4L2_TUNER_RADIO; - vt->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW; - vt->rangelow = TUNER_FREQ_MIN_FM * 2 / 125; - vt->rangehigh = TUNER_FREQ_MAX_FM * 2 / 125; - vt->rxsubchans = V4L2_TUNER_SUB_STEREO; - vt->audmode = V4L2_TUNER_MODE_STEREO; - vt->signal = 0; - vt->afc = 0; - strlcpy(vt->name, "Radio", sizeof(vt->name)); - - mutex_lock(&p->lock); - ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO, - &fm_stat, &status, sizeof(fm_stat)); - - while (fm_stat.sig_lock_busy && count-- && !ret) { - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(HZ); - - ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO, - &fm_stat, &status, sizeof(fm_stat)); - } - mutex_unlock(&p->lock); - - if (ret || status) { - vt->signal = 0; - } else if ((fm_stat.sig_present || fm_stat.sig_locked) - && fm_stat.sig_strength == 0) { - vt->signal = 0xffff; - } else - vt->signal = (fm_stat.sig_strength * 255 / 10) << 8; - - return 0; -} - -static int fm_get_freq(struct file *file, void *priv, - struct v4l2_frequency *argp) -{ - struct poseidon *p = video_drvdata(file); - - if (argp->tuner) - return -EINVAL; - argp->frequency = p->radio_data.fm_freq; - return 0; -} - -static int set_frequency(struct poseidon *p, __u32 frequency) -{ - __u32 freq ; - int ret, status; - - mutex_lock(&p->lock); - - ret = send_set_req(p, TUNER_AUD_ANA_STD, - p->radio_data.pre_emphasis, &status); - - freq = (frequency * 125) / 2; /* Hz */ - freq = clamp(freq, TUNER_FREQ_MIN_FM, TUNER_FREQ_MAX_FM); - - ret = send_set_req(p, TUNE_FREQ_SELECT, freq, &status); - if (ret < 0) - goto error ; - ret = send_set_req(p, TAKE_REQUEST, 0, &status); - - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(HZ/4); - if (!p->radio_data.is_radio_streaming) { - ret = send_set_req(p, TAKE_REQUEST, 0, &status); - ret = send_set_req(p, PLAY_SERVICE, - TLG_TUNE_PLAY_SVC_START, &status); - p->radio_data.is_radio_streaming = 1; - } - p->radio_data.fm_freq = freq * 2 / 125; -error: - mutex_unlock(&p->lock); - return ret; -} - -static int fm_set_freq(struct file *file, void *priv, - const struct v4l2_frequency *argp) -{ - struct poseidon *p = video_drvdata(file); - - if (argp->tuner) - return -EINVAL; - p->file_for_stream = file; -#ifdef CONFIG_PM - p->pm_suspend = pm_fm_suspend; - p->pm_resume = pm_fm_resume; -#endif - return set_frequency(p, argp->frequency); -} - -static int tlg_fm_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct poseidon *p = container_of(ctrl->handler, struct poseidon, - radio_data.ctrl_handler); - int pre_emphasis; - u32 status; - - switch (ctrl->id) { - case V4L2_CID_TUNE_PREEMPHASIS: - pre_emphasis = preemphasis[ctrl->val]; - send_set_req(p, TUNER_AUD_ANA_STD, pre_emphasis, &status); - p->radio_data.pre_emphasis = pre_emphasis; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *vt) -{ - return vt->index > 0 ? -EINVAL : 0; -} - -static const struct v4l2_ctrl_ops tlg_fm_ctrl_ops = { - .s_ctrl = tlg_fm_s_ctrl, -}; - -static const struct v4l2_ioctl_ops poseidon_fm_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_tuner = tlg_fm_vidioc_g_tuner, - .vidioc_g_frequency = fm_get_freq, - .vidioc_s_frequency = fm_set_freq, - .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, -}; - -static struct video_device poseidon_fm_template = { - .name = "Telegent-Radio", - .fops = &poseidon_fm_fops, - .minor = -1, - .release = video_device_release_empty, - .ioctl_ops = &poseidon_fm_ioctl_ops, -}; - -int poseidon_fm_init(struct poseidon *p) -{ - struct video_device *vfd = &p->radio_data.fm_dev; - struct v4l2_ctrl_handler *hdl = &p->radio_data.ctrl_handler; - - *vfd = poseidon_fm_template; - - set_frequency(p, TUNER_FREQ_MIN_FM); - v4l2_ctrl_handler_init(hdl, 1); - v4l2_ctrl_new_std_menu(hdl, &tlg_fm_ctrl_ops, V4L2_CID_TUNE_PREEMPHASIS, - V4L2_PREEMPHASIS_75_uS, 0, V4L2_PREEMPHASIS_50_uS); - if (hdl->error) { - v4l2_ctrl_handler_free(hdl); - return hdl->error; - } - vfd->v4l2_dev = &p->v4l2_dev; - vfd->ctrl_handler = hdl; - video_set_drvdata(vfd, p); - return video_register_device(vfd, VFL_TYPE_RADIO, -1); -} - -int poseidon_fm_exit(struct poseidon *p) -{ - video_unregister_device(&p->radio_data.fm_dev); - v4l2_ctrl_handler_free(&p->radio_data.ctrl_handler); - return 0; -} diff --git a/drivers/staging/media/tlg2300/pd-video.c b/drivers/staging/media/tlg2300/pd-video.c deleted file mode 100644 index c0c3c1c4517c..000000000000 --- a/drivers/staging/media/tlg2300/pd-video.c +++ /dev/null @@ -1,1560 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "pd-common.h" -#include "vendorcmds.h" - -#ifdef CONFIG_PM -static int pm_video_suspend(struct poseidon *pd); -static int pm_video_resume(struct poseidon *pd); -#endif -static void iso_bubble_handler(struct work_struct *w); - -static int usb_transfer_mode; -module_param(usb_transfer_mode, int, 0644); -MODULE_PARM_DESC(usb_transfer_mode, "0 = Bulk, 1 = Isochronous"); - -static const struct poseidon_format poseidon_formats[] = { - { "YUV 422", V4L2_PIX_FMT_YUYV, 16, 0}, - { "RGB565", V4L2_PIX_FMT_RGB565, 16, 0}, -}; - -static const struct poseidon_tvnorm poseidon_tvnorms[] = { - { V4L2_STD_PAL_D, "PAL-D", TLG_TUNE_VSTD_PAL_D }, - { V4L2_STD_PAL_B, "PAL-B", TLG_TUNE_VSTD_PAL_B }, - { V4L2_STD_PAL_G, "PAL-G", TLG_TUNE_VSTD_PAL_G }, - { V4L2_STD_PAL_H, "PAL-H", TLG_TUNE_VSTD_PAL_H }, - { V4L2_STD_PAL_I, "PAL-I", TLG_TUNE_VSTD_PAL_I }, - { V4L2_STD_PAL_M, "PAL-M", TLG_TUNE_VSTD_PAL_M }, - { V4L2_STD_PAL_N, "PAL-N", TLG_TUNE_VSTD_PAL_N_COMBO }, - { V4L2_STD_PAL_Nc, "PAL-Nc", TLG_TUNE_VSTD_PAL_N_COMBO }, - { V4L2_STD_NTSC_M, "NTSC-M", TLG_TUNE_VSTD_NTSC_M }, - { V4L2_STD_NTSC_M_JP, "NTSC-JP", TLG_TUNE_VSTD_NTSC_M_J }, - { V4L2_STD_SECAM_B, "SECAM-B", TLG_TUNE_VSTD_SECAM_B }, - { V4L2_STD_SECAM_D, "SECAM-D", TLG_TUNE_VSTD_SECAM_D }, - { V4L2_STD_SECAM_G, "SECAM-G", TLG_TUNE_VSTD_SECAM_G }, - { V4L2_STD_SECAM_H, "SECAM-H", TLG_TUNE_VSTD_SECAM_H }, - { V4L2_STD_SECAM_K, "SECAM-K", TLG_TUNE_VSTD_SECAM_K }, - { V4L2_STD_SECAM_K1, "SECAM-K1", TLG_TUNE_VSTD_SECAM_K1 }, - { V4L2_STD_SECAM_L, "SECAM-L", TLG_TUNE_VSTD_SECAM_L }, - { V4L2_STD_SECAM_LC, "SECAM-LC", TLG_TUNE_VSTD_SECAM_L1 }, -}; -static const unsigned int POSEIDON_TVNORMS = ARRAY_SIZE(poseidon_tvnorms); - -struct pd_audio_mode { - u32 tlg_audio_mode; - u32 v4l2_audio_sub; - u32 v4l2_audio_mode; -}; - -static const struct pd_audio_mode pd_audio_modes[] = { - { TLG_TUNE_TVAUDIO_MODE_MONO, V4L2_TUNER_SUB_MONO, - V4L2_TUNER_MODE_MONO }, - { TLG_TUNE_TVAUDIO_MODE_STEREO, V4L2_TUNER_SUB_STEREO, - V4L2_TUNER_MODE_STEREO }, - { TLG_TUNE_TVAUDIO_MODE_LANG_A, V4L2_TUNER_SUB_LANG1, - V4L2_TUNER_MODE_LANG1 }, - { TLG_TUNE_TVAUDIO_MODE_LANG_B, V4L2_TUNER_SUB_LANG2, - V4L2_TUNER_MODE_LANG2 }, - { TLG_TUNE_TVAUDIO_MODE_LANG_C, V4L2_TUNER_SUB_LANG1, - V4L2_TUNER_MODE_LANG1_LANG2 } -}; -static const unsigned int POSEIDON_AUDIOMODS = ARRAY_SIZE(pd_audio_modes); - -struct pd_input { - char *name; - uint32_t tlg_src; -}; - -static const struct pd_input pd_inputs[] = { - { "TV Antenna", TLG_SIG_SRC_ANTENNA }, - { "TV Cable", TLG_SIG_SRC_CABLE }, - { "TV SVideo", TLG_SIG_SRC_SVIDEO }, - { "TV Composite", TLG_SIG_SRC_COMPOSITE } -}; -static const unsigned int POSEIDON_INPUTS = ARRAY_SIZE(pd_inputs); - -struct video_std_to_audio_std { - v4l2_std_id video_std; - int audio_std; -}; - -static const struct video_std_to_audio_std video_to_audio_map[] = { - /* country : { 27, 32, 33, 34, 36, 44, 45, 46, 47, 48, 64, - 65, 86, 351, 352, 353, 354, 358, 372, 852, 972 } */ - { (V4L2_STD_PAL_I | V4L2_STD_PAL_B | V4L2_STD_PAL_D | - V4L2_STD_SECAM_L | V4L2_STD_SECAM_D), TLG_TUNE_ASTD_NICAM }, - - /* country : { 1, 52, 54, 55, 886 } */ - {V4L2_STD_NTSC_M | V4L2_STD_PAL_N | V4L2_STD_PAL_M, TLG_TUNE_ASTD_BTSC}, - - /* country : { 81 } */ - { V4L2_STD_NTSC_M_JP, TLG_TUNE_ASTD_EIAJ }, - - /* other country : TLG_TUNE_ASTD_A2 */ -}; -static const unsigned int map_size = ARRAY_SIZE(video_to_audio_map); - -static int get_audio_std(v4l2_std_id v4l2_std) -{ - int i = 0; - - for (; i < map_size; i++) { - if (v4l2_std & video_to_audio_map[i].video_std) - return video_to_audio_map[i].audio_std; - } - return TLG_TUNE_ASTD_A2; -} - -static int vidioc_querycap(struct file *file, void *fh, - struct v4l2_capability *cap) -{ - struct video_device *vdev = video_devdata(file); - struct poseidon *p = video_get_drvdata(vdev); - - strcpy(cap->driver, "tele-video"); - strcpy(cap->card, "Telegent Poseidon"); - usb_make_path(p->udev, cap->bus_info, sizeof(cap->bus_info)); - cap->device_caps = V4L2_CAP_TUNER | V4L2_CAP_AUDIO | - V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; - if (vdev->vfl_type == VFL_TYPE_VBI) - cap->device_caps |= V4L2_CAP_VBI_CAPTURE; - else - cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE; - cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS | - V4L2_CAP_RADIO | V4L2_CAP_VBI_CAPTURE | V4L2_CAP_VIDEO_CAPTURE; - return 0; -} - -/*====================================================================*/ -static void init_copy(struct video_data *video, bool index) -{ - struct front_face *front = video->front; - - video->field_count = index; - video->lines_copied = 0; - video->prev_left = 0 ; - video->dst = (char *)videobuf_to_vmalloc(front->curr_frame) - + index * video->lines_size; - video->vbi->copied = 0; /* set it here */ -} - -static bool get_frame(struct front_face *front, int *need_init) -{ - struct videobuf_buffer *vb = front->curr_frame; - - if (vb) - return true; - - spin_lock(&front->queue_lock); - if (!list_empty(&front->active)) { - vb = list_entry(front->active.next, - struct videobuf_buffer, queue); - if (need_init) - *need_init = 1; - front->curr_frame = vb; - list_del_init(&vb->queue); - } - spin_unlock(&front->queue_lock); - - return !!vb; -} - -/* check if the video's buffer is ready */ -static bool get_video_frame(struct front_face *front, struct video_data *video) -{ - int need_init = 0; - bool ret = true; - - ret = get_frame(front, &need_init); - if (ret && need_init) - init_copy(video, 0); - return ret; -} - -static void submit_frame(struct front_face *front) -{ - struct videobuf_buffer *vb = front->curr_frame; - - if (vb == NULL) - return; - - front->curr_frame = NULL; - vb->state = VIDEOBUF_DONE; - vb->field_count++; - v4l2_get_timestamp(&vb->ts); - - wake_up(&vb->done); -} - -/* - * A frame is composed of two fields. If we receive all the two fields, - * call the submit_frame() to submit the whole frame to applications. - */ -static void end_field(struct video_data *video) -{ - if (1 == video->field_count) - submit_frame(video->front); - else - init_copy(video, 1); -} - -static void copy_video_data(struct video_data *video, char *src, - unsigned int count) -{ -#define copy_data(len) \ - do { \ - if (++video->lines_copied > video->lines_per_field) \ - goto overflow; \ - memcpy(video->dst, src, len);\ - video->dst += len + video->lines_size; \ - src += len; \ - count -= len; \ - } while (0) - - while (count && count >= video->lines_size) { - if (video->prev_left) { - copy_data(video->prev_left); - video->prev_left = 0; - continue; - } - copy_data(video->lines_size); - } - if (count && count < video->lines_size) { - memcpy(video->dst, src, count); - - video->prev_left = video->lines_size - count; - video->dst += count; - } - return; - -overflow: - end_field(video); -} - -static void check_trailer(struct video_data *video, char *src, int count) -{ - struct vbi_data *vbi = video->vbi; - int offset; /* trailer's offset */ - char *buf; - - offset = (video->context.pix.sizeimage / 2 + vbi->vbi_size / 2) - - (vbi->copied + video->lines_size * video->lines_copied); - if (video->prev_left) - offset -= (video->lines_size - video->prev_left); - - if (offset > count || offset <= 0) - goto short_package; - - buf = src + offset; - - /* trailer : (VFHS) + U32 + U32 + field_num */ - if (!strncmp(buf, "VFHS", 4)) { - int field_num = *((u32 *)(buf + 12)); - - if ((field_num & 1) ^ video->field_count) { - init_copy(video, video->field_count); - return; - } - copy_video_data(video, src, offset); - } -short_package: - end_field(video); -} - -/* ========== Check this more carefully! =========== */ -static inline void copy_vbi_data(struct vbi_data *vbi, - char *src, unsigned int count) -{ - struct front_face *front = vbi->front; - - if (front && get_frame(front, NULL)) { - char *buf = videobuf_to_vmalloc(front->curr_frame); - - if (vbi->video->field_count) - buf += (vbi->vbi_size / 2); - memcpy(buf + vbi->copied, src, count); - } - vbi->copied += count; -} - -/* - * Copy the normal data (VBI or VIDEO) without the trailer. - * VBI is not interlaced, while VIDEO is interlaced. - */ -static inline void copy_vbi_video_data(struct video_data *video, - char *src, unsigned int count) -{ - struct vbi_data *vbi = video->vbi; - unsigned int vbi_delta = (vbi->vbi_size / 2) - vbi->copied; - - if (vbi_delta >= count) { - copy_vbi_data(vbi, src, count); - } else { - if (vbi_delta) { - copy_vbi_data(vbi, src, vbi_delta); - - /* we receive the two fields of the VBI*/ - if (vbi->front && video->field_count) - submit_frame(vbi->front); - } - copy_video_data(video, src + vbi_delta, count - vbi_delta); - } -} - -static void urb_complete_bulk(struct urb *urb) -{ - struct front_face *front = urb->context; - struct video_data *video = &front->pd->video_data; - char *src = (char *)urb->transfer_buffer; - int count = urb->actual_length; - int ret = 0; - - if (!video->is_streaming || urb->status) { - if (urb->status == -EPROTO) - goto resend_it; - return; - } - if (!get_video_frame(front, video)) - goto resend_it; - - if (count == urb->transfer_buffer_length) - copy_vbi_video_data(video, src, count); - else - check_trailer(video, src, count); - -resend_it: - ret = usb_submit_urb(urb, GFP_ATOMIC); - if (ret) - log(" submit failed: error %d", ret); -} - -/************************* for ISO *********************/ -#define GET_SUCCESS (0) -#define GET_TRAILER (1) -#define GET_TOO_MUCH_BUBBLE (2) -#define GET_NONE (3) -static int get_chunk(int start, struct urb *urb, - int *head, int *tail, int *bubble_err) -{ - struct usb_iso_packet_descriptor *pkt = NULL; - int ret = GET_SUCCESS; - - for (*head = *tail = -1; start < urb->number_of_packets; start++) { - pkt = &urb->iso_frame_desc[start]; - - /* handle the bubble of the Hub */ - if (-EOVERFLOW == pkt->status) { - if (++*bubble_err > urb->number_of_packets / 3) - return GET_TOO_MUCH_BUBBLE; - continue; - } - - /* This is the gap */ - if (pkt->status || pkt->actual_length <= 0 - || pkt->actual_length > ISO_PKT_SIZE) { - if (*head != -1) - break; - continue; - } - - /* a good isochronous packet */ - if (pkt->actual_length == ISO_PKT_SIZE) { - if (*head == -1) - *head = start; - *tail = start; - continue; - } - - /* trailer is here */ - if (pkt->actual_length < ISO_PKT_SIZE) { - if (*head == -1) { - *head = start; - *tail = start; - return GET_TRAILER; - } - break; - } - } - - if (*head == -1 && *tail == -1) - ret = GET_NONE; - return ret; -} - -/* - * |__|------|___|-----|_______| - * ^ ^ - * | | - * gap gap - */ -static void urb_complete_iso(struct urb *urb) -{ - struct front_face *front = urb->context; - struct video_data *video = &front->pd->video_data; - int bubble_err = 0, head = 0, tail = 0; - char *src = (char *)urb->transfer_buffer; - int ret = 0; - - if (!video->is_streaming) - return; - - do { - if (!get_video_frame(front, video)) - goto out; - - switch (get_chunk(head, urb, &head, &tail, &bubble_err)) { - case GET_SUCCESS: - copy_vbi_video_data(video, src + (head * ISO_PKT_SIZE), - (tail - head + 1) * ISO_PKT_SIZE); - break; - case GET_TRAILER: - check_trailer(video, src + (head * ISO_PKT_SIZE), - ISO_PKT_SIZE); - break; - case GET_NONE: - goto out; - case GET_TOO_MUCH_BUBBLE: - log("\t We got too much bubble"); - schedule_work(&video->bubble_work); - return; - } - } while (head = tail + 1, head < urb->number_of_packets); - -out: - ret = usb_submit_urb(urb, GFP_ATOMIC); - if (ret) - log("usb_submit_urb err : %d", ret); -} -/*============================= [ end ] =====================*/ - -static int prepare_iso_urb(struct video_data *video) -{ - struct usb_device *udev = video->pd->udev; - int i; - - if (video->urb_array[0]) - return 0; - - for (i = 0; i < SBUF_NUM; i++) { - struct urb *urb; - void *mem; - int j; - - urb = usb_alloc_urb(PK_PER_URB, GFP_KERNEL); - if (urb == NULL) - goto out; - - video->urb_array[i] = urb; - mem = usb_alloc_coherent(udev, - ISO_PKT_SIZE * PK_PER_URB, - GFP_KERNEL, - &urb->transfer_dma); - - urb->complete = urb_complete_iso; /* handler */ - urb->dev = udev; - urb->context = video->front; - urb->pipe = usb_rcvisocpipe(udev, - video->endpoint_addr); - urb->interval = 1; - urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; - urb->number_of_packets = PK_PER_URB; - urb->transfer_buffer = mem; - urb->transfer_buffer_length = PK_PER_URB * ISO_PKT_SIZE; - - for (j = 0; j < PK_PER_URB; j++) { - urb->iso_frame_desc[j].offset = ISO_PKT_SIZE * j; - urb->iso_frame_desc[j].length = ISO_PKT_SIZE; - } - } - return 0; -out: - for (; i > 0; i--) - ; - return -ENOMEM; -} - -/* return the succeeded number of the allocation */ -int alloc_bulk_urbs_generic(struct urb **urb_array, int num, - struct usb_device *udev, u8 ep_addr, - int buf_size, gfp_t gfp_flags, - usb_complete_t complete_fn, void *context) -{ - int i = 0; - - for (; i < num; i++) { - void *mem; - struct urb *urb = usb_alloc_urb(0, gfp_flags); - if (urb == NULL) - return i; - - mem = usb_alloc_coherent(udev, buf_size, gfp_flags, - &urb->transfer_dma); - if (mem == NULL) { - usb_free_urb(urb); - return i; - } - - usb_fill_bulk_urb(urb, udev, usb_rcvbulkpipe(udev, ep_addr), - mem, buf_size, complete_fn, context); - urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - urb_array[i] = urb; - } - return i; -} - -void free_all_urb_generic(struct urb **urb_array, int num) -{ - int i; - struct urb *urb; - - for (i = 0; i < num; i++) { - urb = urb_array[i]; - if (urb) { - usb_free_coherent(urb->dev, - urb->transfer_buffer_length, - urb->transfer_buffer, - urb->transfer_dma); - usb_free_urb(urb); - urb_array[i] = NULL; - } - } -} - -static int prepare_bulk_urb(struct video_data *video) -{ - if (video->urb_array[0]) - return 0; - - alloc_bulk_urbs_generic(video->urb_array, SBUF_NUM, - video->pd->udev, video->endpoint_addr, - 0x2000, GFP_KERNEL, - urb_complete_bulk, video->front); - return 0; -} - -/* free the URBs */ -static void free_all_urb(struct video_data *video) -{ - free_all_urb_generic(video->urb_array, SBUF_NUM); -} - -static void pd_buf_release(struct videobuf_queue *q, struct videobuf_buffer *vb) -{ - videobuf_vmalloc_free(vb); - vb->state = VIDEOBUF_NEEDS_INIT; -} - -static void pd_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) -{ - struct front_face *front = q->priv_data; - vb->state = VIDEOBUF_QUEUED; - list_add_tail(&vb->queue, &front->active); -} - -static int pd_buf_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, - enum v4l2_field field) -{ - struct front_face *front = q->priv_data; - int rc; - - switch (front->type) { - case V4L2_BUF_TYPE_VIDEO_CAPTURE: - if (VIDEOBUF_NEEDS_INIT == vb->state) { - struct v4l2_pix_format *pix; - - pix = &front->pd->video_data.context.pix; - vb->size = pix->sizeimage; /* real frame size */ - vb->width = pix->width; - vb->height = pix->height; - rc = videobuf_iolock(q, vb, NULL); - if (rc < 0) - return rc; - } - break; - case V4L2_BUF_TYPE_VBI_CAPTURE: - if (VIDEOBUF_NEEDS_INIT == vb->state) { - vb->size = front->pd->vbi_data.vbi_size; - rc = videobuf_iolock(q, vb, NULL); - if (rc < 0) - return rc; - } - break; - default: - return -EINVAL; - } - vb->field = field; - vb->state = VIDEOBUF_PREPARED; - return 0; -} - -static int fire_all_urb(struct video_data *video) -{ - int i, ret; - - video->is_streaming = 1; - - for (i = 0; i < SBUF_NUM; i++) { - ret = usb_submit_urb(video->urb_array[i], GFP_KERNEL); - if (ret) - log("(%d) failed: error %d", i, ret); - } - return ret; -} - -static int start_video_stream(struct poseidon *pd) -{ - struct video_data *video = &pd->video_data; - s32 cmd_status; - - send_set_req(pd, TAKE_REQUEST, 0, &cmd_status); - send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_START, &cmd_status); - - if (pd->cur_transfer_mode) { - prepare_iso_urb(video); - INIT_WORK(&video->bubble_work, iso_bubble_handler); - } else { - /* The bulk mode does not need a bubble handler */ - prepare_bulk_urb(video); - } - fire_all_urb(video); - return 0; -} - -static int pd_buf_setup(struct videobuf_queue *q, unsigned int *count, - unsigned int *size) -{ - struct front_face *front = q->priv_data; - struct poseidon *pd = front->pd; - - switch (front->type) { - default: - return -EINVAL; - case V4L2_BUF_TYPE_VIDEO_CAPTURE: { - struct video_data *video = &pd->video_data; - struct v4l2_pix_format *pix = &video->context.pix; - - *size = PAGE_ALIGN(pix->sizeimage);/* page aligned frame size */ - if (*count < 4) - *count = 4; - if (1) { - /* same in different altersetting */ - video->endpoint_addr = 0x82; - video->vbi = &pd->vbi_data; - video->vbi->video = video; - video->pd = pd; - video->lines_per_field = pix->height / 2; - video->lines_size = pix->width * 2; - video->front = front; - } - return start_video_stream(pd); - } - - case V4L2_BUF_TYPE_VBI_CAPTURE: { - struct vbi_data *vbi = &pd->vbi_data; - - *size = PAGE_ALIGN(vbi->vbi_size); - log("size : %d", *size); - if (*count == 0) - *count = 4; - } - break; - } - return 0; -} - -static struct videobuf_queue_ops pd_video_qops = { - .buf_setup = pd_buf_setup, - .buf_prepare = pd_buf_prepare, - .buf_queue = pd_buf_queue, - .buf_release = pd_buf_release, -}; - -static int vidioc_enum_fmt(struct file *file, void *fh, - struct v4l2_fmtdesc *f) -{ - if (ARRAY_SIZE(poseidon_formats) <= f->index) - return -EINVAL; - f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - f->flags = 0; - f->pixelformat = poseidon_formats[f->index].fourcc; - strcpy(f->description, poseidon_formats[f->index].name); - return 0; -} - -static int vidioc_g_fmt(struct file *file, void *fh, struct v4l2_format *f) -{ - struct front_face *front = fh; - struct poseidon *pd = front->pd; - - f->fmt.pix = pd->video_data.context.pix; - return 0; -} - -/* - * VLC calls VIDIOC_S_STD before VIDIOC_S_FMT, while - * Mplayer calls them in the reverse order. - */ -static int pd_vidioc_s_fmt(struct poseidon *pd, struct v4l2_pix_format *pix) -{ - struct video_data *video = &pd->video_data; - struct running_context *context = &video->context; - struct v4l2_pix_format *pix_def = &context->pix; - s32 ret = 0, cmd_status = 0, vid_resol; - - /* set the pixel format to firmware */ - if (pix->pixelformat == V4L2_PIX_FMT_RGB565) { - vid_resol = TLG_TUNER_VID_FORMAT_RGB_565; - } else { - pix->pixelformat = V4L2_PIX_FMT_YUYV; - vid_resol = TLG_TUNER_VID_FORMAT_YUV; - } - ret = send_set_req(pd, VIDEO_STREAM_FMT_SEL, - vid_resol, &cmd_status); - - /* set the resolution to firmware */ - vid_resol = TLG_TUNE_VID_RES_720; - switch (pix->width) { - case 704: - vid_resol = TLG_TUNE_VID_RES_704; - break; - default: - pix->width = 720; - case 720: - break; - } - ret |= send_set_req(pd, VIDEO_ROSOLU_SEL, - vid_resol, &cmd_status); - if (ret || cmd_status) - return -EBUSY; - - pix_def->pixelformat = pix->pixelformat; /* save it */ - pix->height = (context->tvnormid & V4L2_STD_525_60) ? 480 : 576; - - /* Compare with the default setting */ - if ((pix_def->width != pix->width) - || (pix_def->height != pix->height)) { - pix_def->width = pix->width; - pix_def->height = pix->height; - pix_def->bytesperline = pix->width * 2; - pix_def->sizeimage = pix->width * pix->height * 2; - } - *pix = *pix_def; - - return 0; -} - -static int vidioc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) -{ - struct front_face *front = fh; - struct poseidon *pd = front->pd; - - /* stop VBI here */ - if (V4L2_BUF_TYPE_VIDEO_CAPTURE != f->type) - return -EINVAL; - - mutex_lock(&pd->lock); - if (pd->file_for_stream == NULL) - pd->file_for_stream = file; - else if (file != pd->file_for_stream) { - mutex_unlock(&pd->lock); - return -EINVAL; - } - - pd_vidioc_s_fmt(pd, &f->fmt.pix); - mutex_unlock(&pd->lock); - return 0; -} - -static int vidioc_g_fmt_vbi(struct file *file, void *fh, - struct v4l2_format *v4l2_f) -{ - struct front_face *front = fh; - struct poseidon *pd = front->pd; - struct v4l2_vbi_format *vbi_fmt = &v4l2_f->fmt.vbi; - - vbi_fmt->samples_per_line = 720 * 2; - vbi_fmt->sampling_rate = 6750000 * 4; - vbi_fmt->sample_format = V4L2_PIX_FMT_GREY; - vbi_fmt->offset = 64 * 4; /*FIXME: why offset */ - if (pd->video_data.context.tvnormid & V4L2_STD_525_60) { - vbi_fmt->start[0] = 10; - vbi_fmt->start[1] = 264; - vbi_fmt->count[0] = V4L_NTSC_VBI_LINES; - vbi_fmt->count[1] = V4L_NTSC_VBI_LINES; - } else { - vbi_fmt->start[0] = 6; - vbi_fmt->start[1] = 314; - vbi_fmt->count[0] = V4L_PAL_VBI_LINES; - vbi_fmt->count[1] = V4L_PAL_VBI_LINES; - } - vbi_fmt->flags = V4L2_VBI_UNSYNC; - return 0; -} - -static int set_std(struct poseidon *pd, v4l2_std_id norm) -{ - struct video_data *video = &pd->video_data; - struct vbi_data *vbi = &pd->vbi_data; - struct running_context *context; - struct v4l2_pix_format *pix; - s32 i, ret = 0, cmd_status, param; - int height; - - for (i = 0; i < POSEIDON_TVNORMS; i++) { - if (norm & poseidon_tvnorms[i].v4l2_id) { - param = poseidon_tvnorms[i].tlg_tvnorm; - log("name : %s", poseidon_tvnorms[i].name); - goto found; - } - } - return -EINVAL; -found: - mutex_lock(&pd->lock); - ret = send_set_req(pd, VIDEO_STD_SEL, param, &cmd_status); - if (ret || cmd_status) - goto out; - - /* Set vbi size and check the height of the frame */ - context = &video->context; - context->tvnormid = poseidon_tvnorms[i].v4l2_id; - if (context->tvnormid & V4L2_STD_525_60) { - vbi->vbi_size = V4L_NTSC_VBI_FRAMESIZE; - height = 480; - } else { - vbi->vbi_size = V4L_PAL_VBI_FRAMESIZE; - height = 576; - } - - pix = &context->pix; - if (pix->height != height) { - pix->height = height; - pix->sizeimage = pix->width * pix->height * 2; - } - -out: - mutex_unlock(&pd->lock); - return ret; -} - -static int vidioc_s_std(struct file *file, void *fh, v4l2_std_id norm) -{ - struct front_face *front = fh; - - return set_std(front->pd, norm); -} - -static int vidioc_g_std(struct file *file, void *fh, v4l2_std_id *norm) -{ - struct front_face *front = fh; - - *norm = front->pd->video_data.context.tvnormid; - return 0; -} - -static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *in) -{ - if (in->index >= POSEIDON_INPUTS) - return -EINVAL; - strcpy(in->name, pd_inputs[in->index].name); - in->type = V4L2_INPUT_TYPE_TUNER; - - /* - * the audio input index mixed with this video input, - * Poseidon only have one audio/video, set to "0" - */ - in->audioset = 1; - in->tuner = 0; - in->std = V4L2_STD_ALL; - in->status = 0; - return 0; -} - -static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) -{ - struct front_face *front = fh; - struct poseidon *pd = front->pd; - struct running_context *context = &pd->video_data.context; - - *i = context->sig_index; - return 0; -} - -/* We can support several inputs */ -static int vidioc_s_input(struct file *file, void *fh, unsigned int i) -{ - struct front_face *front = fh; - struct poseidon *pd = front->pd; - s32 ret, cmd_status; - - if (i >= POSEIDON_INPUTS) - return -EINVAL; - ret = send_set_req(pd, SGNL_SRC_SEL, - pd_inputs[i].tlg_src, &cmd_status); - if (ret) - return ret; - - pd->video_data.context.sig_index = i; - return 0; -} - -static int tlg_s_ctrl(struct v4l2_ctrl *c) -{ - struct poseidon *pd = container_of(c->handler, struct poseidon, - video_data.ctrl_handler); - struct tuner_custom_parameter_s param = {0}; - s32 ret = 0, cmd_status, params; - - switch (c->id) { - case V4L2_CID_BRIGHTNESS: - param.param_id = CUST_PARM_ID_BRIGHTNESS_CTRL; - break; - case V4L2_CID_CONTRAST: - param.param_id = CUST_PARM_ID_CONTRAST_CTRL; - break; - case V4L2_CID_HUE: - param.param_id = CUST_PARM_ID_HUE_CTRL; - break; - case V4L2_CID_SATURATION: - param.param_id = CUST_PARM_ID_SATURATION_CTRL; - break; - } - param.param_value = c->val; - params = *(s32 *)¶m; /* temp code */ - - mutex_lock(&pd->lock); - ret = send_set_req(pd, TUNER_CUSTOM_PARAMETER, params, &cmd_status); - ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status); - mutex_unlock(&pd->lock); - - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(HZ/4); - return ret; -} - -/* Audio ioctls */ -static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a) -{ - if (0 != a->index) - return -EINVAL; - a->capability = V4L2_AUDCAP_STEREO; - strcpy(a->name, "USB audio in"); - /*Poseidon have no AVL function.*/ - a->mode = 0; - return 0; -} - -static int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a) -{ - a->index = 0; - a->capability = V4L2_AUDCAP_STEREO; - strcpy(a->name, "USB audio in"); - a->mode = 0; - return 0; -} - -static int vidioc_s_audio(struct file *file, void *fh, const struct v4l2_audio *a) -{ - return (0 == a->index) ? 0 : -EINVAL; -} - -/* Tuner ioctls */ -static int vidioc_g_tuner(struct file *file, void *fh, struct v4l2_tuner *tuner) -{ - struct front_face *front = fh; - struct poseidon *pd = front->pd; - struct tuner_atv_sig_stat_s atv_stat; - s32 count = 5, ret, cmd_status; - int index; - - if (0 != tuner->index) - return -EINVAL; - - mutex_lock(&pd->lock); - ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_ANALOG_TV, - &atv_stat, &cmd_status, sizeof(atv_stat)); - - while (atv_stat.sig_lock_busy && count-- && !ret) { - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(HZ); - - ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_ANALOG_TV, - &atv_stat, &cmd_status, sizeof(atv_stat)); - } - mutex_unlock(&pd->lock); - - if (debug_mode) - log("P:%d,S:%d", atv_stat.sig_present, atv_stat.sig_strength); - - if (ret || cmd_status) - tuner->signal = 0; - else if (atv_stat.sig_present && !atv_stat.sig_strength) - tuner->signal = 0xFFFF; - else - tuner->signal = (atv_stat.sig_strength * 255 / 10) << 8; - - strcpy(tuner->name, "Telegent Systems"); - tuner->type = V4L2_TUNER_ANALOG_TV; - tuner->rangelow = TUNER_FREQ_MIN / 62500; - tuner->rangehigh = TUNER_FREQ_MAX / 62500; - tuner->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | - V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; - index = pd->video_data.context.audio_idx; - tuner->rxsubchans = pd_audio_modes[index].v4l2_audio_sub; - tuner->audmode = pd_audio_modes[index].v4l2_audio_mode; - tuner->afc = 0; - return 0; -} - -static int pd_vidioc_s_tuner(struct poseidon *pd, int index) -{ - s32 ret = 0, cmd_status, param, audiomode; - - mutex_lock(&pd->lock); - param = pd_audio_modes[index].tlg_audio_mode; - ret = send_set_req(pd, TUNER_AUD_MODE, param, &cmd_status); - audiomode = get_audio_std(pd->video_data.context.tvnormid); - ret |= send_set_req(pd, TUNER_AUD_ANA_STD, audiomode, - &cmd_status); - if (!ret) - pd->video_data.context.audio_idx = index; - mutex_unlock(&pd->lock); - return ret; -} - -static int vidioc_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *a) -{ - struct front_face *front = fh; - struct poseidon *pd = front->pd; - int index; - - if (0 != a->index) - return -EINVAL; - for (index = 0; index < POSEIDON_AUDIOMODS; index++) - if (a->audmode == pd_audio_modes[index].v4l2_audio_mode) - return pd_vidioc_s_tuner(pd, index); - return -EINVAL; -} - -static int vidioc_g_frequency(struct file *file, void *fh, - struct v4l2_frequency *freq) -{ - struct front_face *front = fh; - struct poseidon *pd = front->pd; - struct running_context *context = &pd->video_data.context; - - if (0 != freq->tuner) - return -EINVAL; - freq->frequency = context->freq; - freq->type = V4L2_TUNER_ANALOG_TV; - return 0; -} - -static int set_frequency(struct poseidon *pd, u32 *frequency) -{ - s32 ret = 0, param, cmd_status; - struct running_context *context = &pd->video_data.context; - - *frequency = clamp(*frequency, - TUNER_FREQ_MIN / 62500, TUNER_FREQ_MAX / 62500); - param = (*frequency) * 62500 / 1000; - - mutex_lock(&pd->lock); - ret = send_set_req(pd, TUNE_FREQ_SELECT, param, &cmd_status); - ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status); - - msleep(250); /* wait for a while until the hardware is ready. */ - context->freq = *frequency; - mutex_unlock(&pd->lock); - return ret; -} - -static int vidioc_s_frequency(struct file *file, void *fh, - const struct v4l2_frequency *freq) -{ - struct front_face *front = fh; - struct poseidon *pd = front->pd; - u32 frequency = freq->frequency; - - if (freq->tuner) - return -EINVAL; -#ifdef CONFIG_PM - pd->pm_suspend = pm_video_suspend; - pd->pm_resume = pm_video_resume; -#endif - return set_frequency(pd, &frequency); -} - -static int vidioc_reqbufs(struct file *file, void *fh, - struct v4l2_requestbuffers *b) -{ - struct front_face *front = file->private_data; - return videobuf_reqbufs(&front->q, b); -} - -static int vidioc_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) -{ - struct front_face *front = file->private_data; - return videobuf_querybuf(&front->q, b); -} - -static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) -{ - struct front_face *front = file->private_data; - return videobuf_qbuf(&front->q, b); -} - -static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b) -{ - struct front_face *front = file->private_data; - return videobuf_dqbuf(&front->q, b, file->f_flags & O_NONBLOCK); -} - -/* Just stop the URBs, do not free the URBs */ -static int usb_transfer_stop(struct video_data *video) -{ - if (video->is_streaming) { - int i; - s32 cmd_status; - struct poseidon *pd = video->pd; - - video->is_streaming = 0; - for (i = 0; i < SBUF_NUM; ++i) { - if (video->urb_array[i]) - usb_kill_urb(video->urb_array[i]); - } - - send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, - &cmd_status); - } - return 0; -} - -int stop_all_video_stream(struct poseidon *pd) -{ - struct video_data *video = &pd->video_data; - struct vbi_data *vbi = &pd->vbi_data; - - mutex_lock(&pd->lock); - if (video->is_streaming) { - struct front_face *front = video->front; - - /* stop the URBs */ - usb_transfer_stop(video); - free_all_urb(video); - - /* stop the host side of VIDEO */ - videobuf_stop(&front->q); - videobuf_mmap_free(&front->q); - - /* stop the host side of VBI */ - front = vbi->front; - if (front) { - videobuf_stop(&front->q); - videobuf_mmap_free(&front->q); - } - } - mutex_unlock(&pd->lock); - return 0; -} - -/* - * The bubbles can seriously damage the video's quality, - * though it occurs in very rare situation. - */ -static void iso_bubble_handler(struct work_struct *w) -{ - struct video_data *video; - struct poseidon *pd; - - video = container_of(w, struct video_data, bubble_work); - pd = video->pd; - - mutex_lock(&pd->lock); - usb_transfer_stop(video); - msleep(500); - start_video_stream(pd); - mutex_unlock(&pd->lock); -} - - -static int vidioc_streamon(struct file *file, void *fh, - enum v4l2_buf_type type) -{ - struct front_face *front = fh; - - if (unlikely(type != front->type)) - return -EINVAL; - return videobuf_streamon(&front->q); -} - -static int vidioc_streamoff(struct file *file, void *fh, - enum v4l2_buf_type type) -{ - struct front_face *front = file->private_data; - - if (unlikely(type != front->type)) - return -EINVAL; - return videobuf_streamoff(&front->q); -} - -/* Set the firmware's default values : need altersetting */ -static int pd_video_checkmode(struct poseidon *pd) -{ - s32 ret = 0, cmd_status, audiomode; - - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(HZ/2); - - /* choose the altersetting */ - ret = usb_set_interface(pd->udev, 0, - (pd->cur_transfer_mode ? - ISO_3K_BULK_ALTERNATE_IFACE : - BULK_ALTERNATE_IFACE)); - if (ret < 0) - goto error; - - /* set default parameters for PAL-D , with the VBI enabled*/ - ret = set_tuner_mode(pd, TLG_MODE_ANALOG_TV); - ret |= send_set_req(pd, SGNL_SRC_SEL, - TLG_SIG_SRC_ANTENNA, &cmd_status); - ret |= send_set_req(pd, VIDEO_STD_SEL, - TLG_TUNE_VSTD_PAL_D, &cmd_status); - ret |= send_set_req(pd, VIDEO_STREAM_FMT_SEL, - TLG_TUNER_VID_FORMAT_YUV, &cmd_status); - ret |= send_set_req(pd, VIDEO_ROSOLU_SEL, - TLG_TUNE_VID_RES_720, &cmd_status); - ret |= send_set_req(pd, TUNE_FREQ_SELECT, TUNER_FREQ_MIN, &cmd_status); - ret |= send_set_req(pd, VBI_DATA_SEL, 1, &cmd_status);/* enable vbi */ - - /* set the audio */ - audiomode = get_audio_std(pd->video_data.context.tvnormid); - ret |= send_set_req(pd, TUNER_AUD_ANA_STD, audiomode, &cmd_status); - ret |= send_set_req(pd, TUNER_AUD_MODE, - TLG_TUNE_TVAUDIO_MODE_STEREO, &cmd_status); - ret |= send_set_req(pd, AUDIO_SAMPLE_RATE_SEL, - ATV_AUDIO_RATE_48K, &cmd_status); -error: - return ret; -} - -#ifdef CONFIG_PM -static int pm_video_suspend(struct poseidon *pd) -{ - /* stop audio */ - pm_alsa_suspend(pd); - - /* stop and free all the URBs */ - usb_transfer_stop(&pd->video_data); - free_all_urb(&pd->video_data); - - /* reset the interface */ - usb_set_interface(pd->udev, 0, 0); - msleep(300); - return 0; -} - -static int restore_v4l2_context(struct poseidon *pd, - struct running_context *context) -{ - struct front_face *front = pd->video_data.front; - - pd_video_checkmode(pd); - - set_std(pd, context->tvnormid); - vidioc_s_input(NULL, front, context->sig_index); - pd_vidioc_s_tuner(pd, context->audio_idx); - pd_vidioc_s_fmt(pd, &context->pix); - set_frequency(pd, &context->freq); - return 0; -} - -static int pm_video_resume(struct poseidon *pd) -{ - struct video_data *video = &pd->video_data; - - /* resume the video */ - /* [1] restore the origin V4L2 parameters */ - restore_v4l2_context(pd, &video->context); - - /* [2] initiate video copy variables */ - if (video->front->curr_frame) - init_copy(video, 0); - - /* [3] fire urbs */ - start_video_stream(pd); - - /* resume the audio */ - pm_alsa_resume(pd); - return 0; -} -#endif - -static void init_video_context(struct running_context *context) -{ - context->sig_index = 0; - context->audio_idx = 1; /* stereo */ - context->tvnormid = V4L2_STD_PAL_D; - context->pix = (struct v4l2_pix_format) { - .width = 720, - .height = 576, - .pixelformat = V4L2_PIX_FMT_YUYV, - .field = V4L2_FIELD_INTERLACED, - .bytesperline = 720 * 2, - .sizeimage = 720 * 576 * 2, - .colorspace = V4L2_COLORSPACE_SMPTE170M, - }; -} - -static int pd_video_open(struct file *file) -{ - struct video_device *vfd = video_devdata(file); - struct poseidon *pd = video_get_drvdata(vfd); - struct front_face *front = NULL; - int ret = -ENOMEM; - - mutex_lock(&pd->lock); - usb_autopm_get_interface(pd->interface); - - if (pd->state && !(pd->state & POSEIDON_STATE_ANALOG)) { - ret = -EBUSY; - goto out; - } - front = kzalloc(sizeof(struct front_face), GFP_KERNEL); - if (!front) - goto out; - if (vfd->vfl_type == VFL_TYPE_GRABBER) { - pd->cur_transfer_mode = usb_transfer_mode;/* bulk or iso */ - init_video_context(&pd->video_data.context); - - ret = pd_video_checkmode(pd); - if (ret < 0) { - kfree(front); - ret = -1; - goto out; - } - - front->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - pd->video_data.users++; - - videobuf_queue_vmalloc_init(&front->q, &pd_video_qops, - NULL, &front->queue_lock, - V4L2_BUF_TYPE_VIDEO_CAPTURE, - V4L2_FIELD_INTERLACED,/* video is interlacd */ - sizeof(struct videobuf_buffer),/*it's enough*/ - front, NULL); - } else { - front->type = V4L2_BUF_TYPE_VBI_CAPTURE; - pd->vbi_data.front = front; - pd->vbi_data.users++; - - videobuf_queue_vmalloc_init(&front->q, &pd_video_qops, - NULL, &front->queue_lock, - V4L2_BUF_TYPE_VBI_CAPTURE, - V4L2_FIELD_NONE, /* vbi is NONE mode */ - sizeof(struct videobuf_buffer), - front, NULL); - } - - pd->state |= POSEIDON_STATE_ANALOG; - front->pd = pd; - front->curr_frame = NULL; - INIT_LIST_HEAD(&front->active); - spin_lock_init(&front->queue_lock); - - file->private_data = front; - kref_get(&pd->kref); - - mutex_unlock(&pd->lock); - return 0; -out: - usb_autopm_put_interface(pd->interface); - mutex_unlock(&pd->lock); - return ret; -} - -static int pd_video_release(struct file *file) -{ - struct front_face *front = file->private_data; - struct poseidon *pd = front->pd; - s32 cmd_status = 0; - - mutex_lock(&pd->lock); - - if (front->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { - /* stop the device, and free the URBs */ - usb_transfer_stop(&pd->video_data); - free_all_urb(&pd->video_data); - - /* stop the firmware */ - send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, - &cmd_status); - - pd->file_for_stream = NULL; - pd->video_data.users--; - } else if (front->type == V4L2_BUF_TYPE_VBI_CAPTURE) { - pd->vbi_data.front = NULL; - pd->vbi_data.users--; - } - if (!pd->vbi_data.users && !pd->video_data.users) - pd->state &= ~POSEIDON_STATE_ANALOG; - videobuf_stop(&front->q); - videobuf_mmap_free(&front->q); - - usb_autopm_put_interface(pd->interface); - mutex_unlock(&pd->lock); - - kfree(front); - file->private_data = NULL; - kref_put(&pd->kref, poseidon_delete); - return 0; -} - -static int pd_video_mmap(struct file *file, struct vm_area_struct *vma) -{ - struct front_face *front = file->private_data; - return videobuf_mmap_mapper(&front->q, vma); -} - -static unsigned int pd_video_poll(struct file *file, poll_table *table) -{ - struct front_face *front = file->private_data; - return videobuf_poll_stream(file, &front->q, table); -} - -static ssize_t pd_video_read(struct file *file, char __user *buffer, - size_t count, loff_t *ppos) -{ - struct front_face *front = file->private_data; - return videobuf_read_stream(&front->q, buffer, count, ppos, - 0, file->f_flags & O_NONBLOCK); -} - -/* This struct works for both VIDEO and VBI */ -static const struct v4l2_file_operations pd_video_fops = { - .owner = THIS_MODULE, - .open = pd_video_open, - .release = pd_video_release, - .read = pd_video_read, - .poll = pd_video_poll, - .mmap = pd_video_mmap, - .ioctl = video_ioctl2, /* maybe changed in future */ -}; - -static const struct v4l2_ioctl_ops pd_video_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - - /* Video format */ - .vidioc_g_fmt_vid_cap = vidioc_g_fmt, - .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt, - .vidioc_s_fmt_vid_cap = vidioc_s_fmt, - .vidioc_g_fmt_vbi_cap = vidioc_g_fmt_vbi, /* VBI */ - - /* Input */ - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, - .vidioc_enum_input = vidioc_enum_input, - - /* Audio ioctls */ - .vidioc_enumaudio = vidioc_enumaudio, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - - /* Tuner ioctls */ - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_std = vidioc_g_std, - .vidioc_s_std = vidioc_s_std, - .vidioc_g_frequency = vidioc_g_frequency, - .vidioc_s_frequency = vidioc_s_frequency, - - /* Buffer handlers */ - .vidioc_reqbufs = vidioc_reqbufs, - .vidioc_querybuf = vidioc_querybuf, - .vidioc_qbuf = vidioc_qbuf, - .vidioc_dqbuf = vidioc_dqbuf, - - /* Stream on/off */ - .vidioc_streamon = vidioc_streamon, - .vidioc_streamoff = vidioc_streamoff, -}; - -static struct video_device pd_video_template = { - .name = "Telegent-Video", - .fops = &pd_video_fops, - .minor = -1, - .release = video_device_release_empty, - .tvnorms = V4L2_STD_ALL, - .ioctl_ops = &pd_video_ioctl_ops, -}; - -static const struct v4l2_ctrl_ops tlg_ctrl_ops = { - .s_ctrl = tlg_s_ctrl, -}; - -void pd_video_exit(struct poseidon *pd) -{ - struct video_data *video = &pd->video_data; - struct vbi_data *vbi = &pd->vbi_data; - - video_unregister_device(&video->v_dev); - video_unregister_device(&vbi->v_dev); - v4l2_ctrl_handler_free(&video->ctrl_handler); - log(); -} - -int pd_video_init(struct poseidon *pd) -{ - struct video_data *video = &pd->video_data; - struct vbi_data *vbi = &pd->vbi_data; - struct v4l2_ctrl_handler *hdl = &video->ctrl_handler; - u32 freq = TUNER_FREQ_MIN / 62500; - int ret = -ENOMEM; - - v4l2_ctrl_handler_init(hdl, 4); - v4l2_ctrl_new_std(hdl, &tlg_ctrl_ops, V4L2_CID_BRIGHTNESS, - 0, 10000, 1, 100); - v4l2_ctrl_new_std(hdl, &tlg_ctrl_ops, V4L2_CID_CONTRAST, - 0, 10000, 1, 100); - v4l2_ctrl_new_std(hdl, &tlg_ctrl_ops, V4L2_CID_HUE, - 0, 10000, 1, 100); - v4l2_ctrl_new_std(hdl, &tlg_ctrl_ops, V4L2_CID_SATURATION, - 0, 10000, 1, 100); - if (hdl->error) { - v4l2_ctrl_handler_free(hdl); - return hdl->error; - } - set_frequency(pd, &freq); - video->v_dev = pd_video_template; - video->v_dev.v4l2_dev = &pd->v4l2_dev; - video->v_dev.ctrl_handler = hdl; - video_set_drvdata(&video->v_dev, pd); - - ret = video_register_device(&video->v_dev, VFL_TYPE_GRABBER, -1); - if (ret != 0) - goto out; - - /* VBI uses the same template as video */ - vbi->v_dev = pd_video_template; - vbi->v_dev.v4l2_dev = &pd->v4l2_dev; - vbi->v_dev.ctrl_handler = hdl; - video_set_drvdata(&vbi->v_dev, pd); - ret = video_register_device(&vbi->v_dev, VFL_TYPE_VBI, -1); - if (ret != 0) - goto out; - log("register VIDEO/VBI devices"); - return 0; -out: - log("VIDEO/VBI devices register failed, : %d", ret); - pd_video_exit(pd); - return ret; -} diff --git a/drivers/staging/media/tlg2300/vendorcmds.h b/drivers/staging/media/tlg2300/vendorcmds.h deleted file mode 100644 index ba6f4ae3b2c2..000000000000 --- a/drivers/staging/media/tlg2300/vendorcmds.h +++ /dev/null @@ -1,243 +0,0 @@ -#ifndef VENDOR_CMD_H_ -#define VENDOR_CMD_H_ - -#define BULK_ALTERNATE_IFACE (2) -#define ISO_3K_BULK_ALTERNATE_IFACE (1) -#define REQ_SET_CMD (0X00) -#define REQ_GET_CMD (0X80) - -enum tlg__analog_audio_standard { - TLG_TUNE_ASTD_NONE = 0x00000000, - TLG_TUNE_ASTD_A2 = 0x00000001, - TLG_TUNE_ASTD_NICAM = 0x00000002, - TLG_TUNE_ASTD_EIAJ = 0x00000004, - TLG_TUNE_ASTD_BTSC = 0x00000008, - TLG_TUNE_ASTD_FM_US = 0x00000010, - TLG_TUNE_ASTD_FM_EUR = 0x00000020, - TLG_TUNE_ASTD_ALL = 0x0000003f -}; - -/* - * identifiers for Custom Parameter messages. - * @typedef cmd_custom_param_id_t - */ -enum cmd_custom_param_id { - CUST_PARM_ID_NONE = 0x00, - CUST_PARM_ID_BRIGHTNESS_CTRL = 0x01, - CUST_PARM_ID_CONTRAST_CTRL = 0x02, - CUST_PARM_ID_HUE_CTRL = 0x03, - CUST_PARM_ID_SATURATION_CTRL = 0x04, - CUST_PARM_ID_AUDIO_SNR_THRESHOLD = 0x10, - CUST_PARM_ID_AUDIO_AGC_THRESHOLD = 0x11, - CUST_PARM_ID_MAX -}; - -struct tuner_custom_parameter_s { - uint16_t param_id; /* Parameter identifier */ - uint16_t param_value; /* Parameter value */ -}; - -struct tuner_ber_rate_s { - uint32_t ber_rate; /* BER sample rate in seconds */ -}; - -struct tuner_atv_sig_stat_s { - uint32_t sig_present; - uint32_t sig_locked; - uint32_t sig_lock_busy; - uint32_t sig_strength; /* milliDb */ - uint32_t tv_audio_chan; /* mono/stereo/sap*/ - uint32_t mvision_stat; /* macrovision status */ -}; - -struct tuner_dtv_sig_stat_s { - uint32_t sig_present; /* Boolean*/ - uint32_t sig_locked; /* Boolean */ - uint32_t sig_lock_busy; /* Boolean (Can this time-out?) */ - uint32_t sig_strength; /* milliDb*/ -}; - -struct tuner_fm_sig_stat_s { - uint32_t sig_present; /* Boolean*/ - uint32_t sig_locked; /* Boolean */ - uint32_t sig_lock_busy; /* Boolean */ - uint32_t sig_stereo_mono;/* TBD*/ - uint32_t sig_strength; /* milliDb*/ -}; - -enum _tag_tlg_tune_srv_cmd { - TLG_TUNE_PLAY_SVC_START = 1, - TLG_TUNE_PLAY_SVC_STOP -}; - -enum _tag_tune_atv_audio_mode_caps { - TLG_TUNE_TVAUDIO_MODE_MONO = 0x00000001, - TLG_TUNE_TVAUDIO_MODE_STEREO = 0x00000002, - TLG_TUNE_TVAUDIO_MODE_LANG_A = 0x00000010,/* Primary language*/ - TLG_TUNE_TVAUDIO_MODE_LANG_B = 0x00000020,/* 2nd avail language*/ - TLG_TUNE_TVAUDIO_MODE_LANG_C = 0x00000040 -}; - - -enum _tag_tuner_atv_audio_rates { - ATV_AUDIO_RATE_NONE = 0x00,/* Audio not supported*/ - ATV_AUDIO_RATE_32K = 0x01,/* Audio rate = 32 KHz*/ - ATV_AUDIO_RATE_48K = 0x02, /* Audio rate = 48 KHz*/ - ATV_AUDIO_RATE_31_25K = 0x04 /* Audio rate = 31.25KHz */ -}; - -enum _tag_tune_atv_vid_res_caps { - TLG_TUNE_VID_RES_NONE = 0x00000000, - TLG_TUNE_VID_RES_720 = 0x00000001, - TLG_TUNE_VID_RES_704 = 0x00000002, - TLG_TUNE_VID_RES_360 = 0x00000004 -}; - -enum _tag_tuner_analog_video_format { - TLG_TUNER_VID_FORMAT_YUV = 0x00000001, - TLG_TUNER_VID_FORMAT_YCRCB = 0x00000002, - TLG_TUNER_VID_FORMAT_RGB_565 = 0x00000004, -}; - -enum tlg_ext_audio_support { - TLG_EXT_AUDIO_NONE = 0x00,/* No external audio input supported */ - TLG_EXT_AUDIO_LR = 0x01/* LR external audio inputs supported*/ -}; - -enum { - TLG_MODE_NONE = 0x00, /* No Mode specified*/ - TLG_MODE_ANALOG_TV = 0x01, /* Analog Television mode*/ - TLG_MODE_ANALOG_TV_UNCOMP = 0x01, /* Analog Television mode*/ - TLG_MODE_ANALOG_TV_COMP = 0x02, /* Analog TV mode (compressed)*/ - TLG_MODE_FM_RADIO = 0x04, /* FM Radio mode*/ - TLG_MODE_DVB_T = 0x08, /* Digital TV (DVB-T)*/ -}; - -enum tlg_signal_sources_t { - TLG_SIG_SRC_NONE = 0x00,/* Signal source not specified */ - TLG_SIG_SRC_ANTENNA = 0x01,/* Signal src is: Antenna */ - TLG_SIG_SRC_CABLE = 0x02,/* Signal src is: Coax Cable*/ - TLG_SIG_SRC_SVIDEO = 0x04,/* Signal src is: S_VIDEO */ - TLG_SIG_SRC_COMPOSITE = 0x08 /* Signal src is: Composite Video */ -}; - -enum tuner_analog_video_standard { - TLG_TUNE_VSTD_NONE = 0x00000000, - TLG_TUNE_VSTD_NTSC_M = 0x00000001, - TLG_TUNE_VSTD_NTSC_M_J = 0x00000002,/* Japan */ - TLG_TUNE_VSTD_PAL_B = 0x00000010, - TLG_TUNE_VSTD_PAL_D = 0x00000020, - TLG_TUNE_VSTD_PAL_G = 0x00000040, - TLG_TUNE_VSTD_PAL_H = 0x00000080, - TLG_TUNE_VSTD_PAL_I = 0x00000100, - TLG_TUNE_VSTD_PAL_M = 0x00000200, - TLG_TUNE_VSTD_PAL_N = 0x00000400, - TLG_TUNE_VSTD_SECAM_B = 0x00001000, - TLG_TUNE_VSTD_SECAM_D = 0x00002000, - TLG_TUNE_VSTD_SECAM_G = 0x00004000, - TLG_TUNE_VSTD_SECAM_H = 0x00008000, - TLG_TUNE_VSTD_SECAM_K = 0x00010000, - TLG_TUNE_VSTD_SECAM_K1 = 0x00020000, - TLG_TUNE_VSTD_SECAM_L = 0x00040000, - TLG_TUNE_VSTD_SECAM_L1 = 0x00080000, - TLG_TUNE_VSTD_PAL_N_COMBO = 0x00100000 -}; - -enum tlg_mode_caps { - TLG_MODE_CAPS_NONE = 0x00, /* No Mode specified */ - TLG_MODE_CAPS_ANALOG_TV_UNCOMP = 0x01, /* Analog TV mode */ - TLG_MODE_CAPS_ANALOG_TV_COMP = 0x02, /* Analog TV (compressed)*/ - TLG_MODE_CAPS_FM_RADIO = 0x04, /* FM Radio mode */ - TLG_MODE_CAPS_DVB_T = 0x08, /* Digital TV (DVB-T) */ -}; - -enum poseidon_vendor_cmds { - LAST_CMD_STAT = 0x00, - GET_CHIP_ID = 0x01, - GET_FW_ID = 0x02, - PRODUCT_CAPS = 0x03, - - TUNE_MODE_CAP_ATV = 0x10, - TUNE_MODE_CAP_ATVCOMP = 0X10, - TUNE_MODE_CAP_DVBT = 0x10, - TUNE_MODE_CAP_FM = 0x10, - TUNE_MODE_SELECT = 0x11, - TUNE_FREQ_SELECT = 0x12, - SGNL_SRC_SEL = 0x13, - - VIDEO_STD_SEL = 0x14, - VIDEO_STREAM_FMT_SEL = 0x15, - VIDEO_ROSOLU_AVAIL = 0x16, - VIDEO_ROSOLU_SEL = 0x17, - VIDEO_CONT_PROTECT = 0x20, - - VCR_TIMING_MODSEL = 0x21, - EXT_AUDIO_CAP = 0x22, - EXT_AUDIO_SEL = 0x23, - TEST_PATTERN_SEL = 0x24, - VBI_DATA_SEL = 0x25, - AUDIO_SAMPLE_RATE_CAP = 0x28, - AUDIO_SAMPLE_RATE_SEL = 0x29, - TUNER_AUD_MODE = 0x2a, - TUNER_AUD_MODE_AVAIL = 0x2b, - TUNER_AUD_ANA_STD = 0x2c, - TUNER_CUSTOM_PARAMETER = 0x2f, - - DVBT_TUNE_MODE_SEL = 0x30, - DVBT_BANDW_CAP = 0x31, - DVBT_BANDW_SEL = 0x32, - DVBT_GUARD_INTERV_CAP = 0x33, - DVBT_GUARD_INTERV_SEL = 0x34, - DVBT_MODULATION_CAP = 0x35, - DVBT_MODULATION_SEL = 0x36, - DVBT_INNER_FEC_RATE_CAP = 0x37, - DVBT_INNER_FEC_RATE_SEL = 0x38, - DVBT_TRANS_MODE_CAP = 0x39, - DVBT_TRANS_MODE_SEL = 0x3a, - DVBT_SEARCH_RANG = 0x3c, - - TUNER_SETUP_ANALOG = 0x40, - TUNER_SETUP_DIGITAL = 0x41, - TUNER_SETUP_FM_RADIO = 0x42, - TAKE_REQUEST = 0x43, /* Take effect of the command */ - PLAY_SERVICE = 0x44, /* Play start or Play stop */ - TUNER_STATUS = 0x45, - TUNE_PROP_DVBT = 0x46, - ERR_RATE_STATS = 0x47, - TUNER_BER_RATE = 0x48, - - SCAN_CAPS = 0x50, - SCAN_SETUP = 0x51, - SCAN_SERVICE = 0x52, - SCAN_STATS = 0x53, - - PID_SET = 0x58, - PID_UNSET = 0x59, - PID_LIST = 0x5a, - - IRD_CAP = 0x60, - IRD_MODE_SEL = 0x61, - IRD_SETUP = 0x62, - - PTM_MODE_CAP = 0x70, - PTM_MODE_SEL = 0x71, - PTM_SERVICE = 0x72, - TUNER_REG_SCRIPT = 0x73, - CMD_CHIP_RST = 0x74, -}; - -enum tlg_bw { - TLG_BW_5 = 5, - TLG_BW_6 = 6, - TLG_BW_7 = 7, - TLG_BW_8 = 8, - TLG_BW_12 = 12, - TLG_BW_15 = 15 -}; - -struct cmd_firmware_vers_s { - uint8_t fw_rev_major; - uint8_t fw_rev_minor; - uint16_t fw_patch; -}; -#endif /* VENDOR_CMD_H_ */ -- cgit v1.2.3 From 51d3d4eee565a707e4053fe447cd28b2d1f4ce79 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 23 Dec 2014 09:17:09 -0300 Subject: [media] bw/c-qcam, w9966, pms: remove deprecated staging drivers These drivers haven't been tested in a long, long time. The hardware is ancient and hopelessly obsolete. These drivers also need to be converted to newer media frameworks but due to the lack of hardware that's going to be impossible. In addition, cheaper and vastly better hardware is available today. These drivers are already deprecated, so now remove them altogether. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 16 - drivers/staging/media/Kconfig | 2 - drivers/staging/media/Makefile | 1 - drivers/staging/media/parport/Kconfig | 69 -- drivers/staging/media/parport/Makefile | 4 - drivers/staging/media/parport/bw-qcam.c | 1177 ------------------------------- drivers/staging/media/parport/c-qcam.c | 882 ----------------------- drivers/staging/media/parport/pms.c | 1156 ------------------------------ drivers/staging/media/parport/w9966.c | 980 ------------------------- 9 files changed, 4287 deletions(-) delete mode 100644 drivers/staging/media/parport/Kconfig delete mode 100644 drivers/staging/media/parport/Makefile delete mode 100644 drivers/staging/media/parport/bw-qcam.c delete mode 100644 drivers/staging/media/parport/c-qcam.c delete mode 100644 drivers/staging/media/parport/pms.c delete mode 100644 drivers/staging/media/parport/w9966.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index f3ae573d1d4f..3db56b8f90fc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6158,14 +6158,6 @@ F: include/uapi/linux/meye.h F: include/uapi/linux/ivtv* F: include/uapi/linux/uvcvideo.h -MEDIAVISION PRO MOVIE STUDIO DRIVER -M: Hans Verkuil -L: linux-media@vger.kernel.org -T: git git://linuxtv.org/media_tree.git -W: http://linuxtv.org -S: Odd Fixes -F: drivers/media/parport/pms* - MEGARAID SCSI/SAS DRIVERS M: Kashyap Desai M: Sumit Saxena @@ -7855,14 +7847,6 @@ T: git git://github.com/KrasnikovEugene/wcn36xx.git S: Supported F: drivers/net/wireless/ath/wcn36xx/ -QUICKCAM PARALLEL PORT WEBCAMS -M: Hans Verkuil -L: linux-media@vger.kernel.org -T: git git://linuxtv.org/media_tree.git -W: http://linuxtv.org -S: Odd Fixes -F: drivers/media/parport/*-qcam* - RADOS BLOCK DEVICE (RBD) M: Yehuda Sadeh M: Sage Weil diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index e633204b9685..96498b7fc20e 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -33,8 +33,6 @@ source "drivers/staging/media/mn88473/Kconfig" source "drivers/staging/media/omap4iss/Kconfig" -source "drivers/staging/media/parport/Kconfig" - # Keep LIRC at the end, as it has sub-menus source "drivers/staging/media/lirc/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 7edb567b2de6..a9006bcb4472 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -6,4 +6,3 @@ obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ obj-$(CONFIG_DVB_MN88472) += mn88472/ obj-$(CONFIG_DVB_MN88473) += mn88473/ -obj-y += parport/ diff --git a/drivers/staging/media/parport/Kconfig b/drivers/staging/media/parport/Kconfig deleted file mode 100644 index 15974efdba1d..000000000000 --- a/drivers/staging/media/parport/Kconfig +++ /dev/null @@ -1,69 +0,0 @@ -menuconfig MEDIA_PARPORT_SUPPORT - bool "ISA and parallel port devices" - depends on (ISA || PARPORT) && MEDIA_CAMERA_SUPPORT - help - Enables drivers for ISA and parallel port bus. If you - need media drivers using those legacy buses, say Y. - -if MEDIA_PARPORT_SUPPORT -config VIDEO_BWQCAM - tristate "Quickcam BW Video For Linux (Deprecated)" - depends on PARPORT && VIDEO_V4L2 - select VIDEOBUF2_VMALLOC - help - Say Y have if you the black and white version of the QuickCam - camera. See the next option for the color version. - - This driver is deprecated and will be removed soon. If you have - hardware for this and you want to work on this driver, then contact - the linux-media mailinglist. - - To compile this driver as a module, choose M here: the - module will be called bw-qcam. - -config VIDEO_CQCAM - tristate "QuickCam Colour Video For Linux (Deprecated)" - depends on PARPORT && VIDEO_V4L2 - help - This is the video4linux driver for the colour version of the - Connectix QuickCam. If you have one of these cameras, say Y here, - otherwise say N. This driver does not work with the original - monochrome QuickCam, QuickCam VC or QuickClip. It is also available - as a module (c-qcam). - Read for more information. - - This driver is deprecated and will be removed soon. If you have - hardware for this and you want to work on this driver, then contact - the linux-media mailinglist. - -config VIDEO_PMS - tristate "Mediavision Pro Movie Studio Video For Linux (Deprecated)" - depends on ISA && VIDEO_V4L2 - help - Say Y if you have the ISA Mediavision Pro Movie Studio - capture card. - - This driver is deprecated and will be removed soon. If you have - hardware for this and you want to work on this driver, then contact - the linux-media mailinglist. - - To compile this driver as a module, choose M here: the - module will be called pms. - -config VIDEO_W9966 - tristate "W9966CF Webcam (FlyCam Supra and others) Video For Linux (Deprecated)" - depends on PARPORT_1284 && PARPORT && VIDEO_V4L2 - help - Video4linux driver for Winbond's w9966 based Webcams. - Currently tested with the LifeView FlyCam Supra. - If you have one of these cameras, say Y here - otherwise say N. - This driver is also available as a module (w9966). - - Check out for more - information. - - This driver is deprecated and will be removed soon. If you have - hardware for this and you want to work on this driver, then contact - the linux-media mailinglist. -endif diff --git a/drivers/staging/media/parport/Makefile b/drivers/staging/media/parport/Makefile deleted file mode 100644 index 4eea06d7af5b..000000000000 --- a/drivers/staging/media/parport/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -obj-$(CONFIG_VIDEO_CQCAM) += c-qcam.o -obj-$(CONFIG_VIDEO_BWQCAM) += bw-qcam.o -obj-$(CONFIG_VIDEO_W9966) += w9966.o -obj-$(CONFIG_VIDEO_PMS) += pms.o diff --git a/drivers/staging/media/parport/bw-qcam.c b/drivers/staging/media/parport/bw-qcam.c deleted file mode 100644 index 67b9da1dc43f..000000000000 --- a/drivers/staging/media/parport/bw-qcam.c +++ /dev/null @@ -1,1177 +0,0 @@ -/* - * QuickCam Driver For Video4Linux. - * - * Video4Linux conversion work by Alan Cox. - * Parport compatibility by Phil Blundell. - * Busy loop avoidance by Mark Cooke. - * - * Module parameters: - * - * maxpoll=<1 - 5000> - * - * When polling the QuickCam for a response, busy-wait for a - * maximum of this many loops. The default of 250 gives little - * impact on interactive response. - * - * NOTE: If this parameter is set too high, the processor - * will busy wait until this loop times out, and then - * slowly poll for a further 5 seconds before failing - * the transaction. You have been warned. - * - * yieldlines=<1 - 250> - * - * When acquiring a frame from the camera, the data gathering - * loop will yield back to the scheduler after completing - * this many lines. The default of 4 provides a trade-off - * between increased frame acquisition time and impact on - * interactive response. - */ - -/* qcam-lib.c -- Library for programming with the Connectix QuickCam. - * See the included documentation for usage instructions and details - * of the protocol involved. */ - - -/* Version 0.5, August 4, 1996 */ -/* Version 0.7, August 27, 1996 */ -/* Version 0.9, November 17, 1996 */ - - -/****************************************************************** - -Copyright (C) 1996 by Scott Laird - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL SCOTT LAIRD BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -******************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* One from column A... */ -#define QC_NOTSET 0 -#define QC_UNIDIR 1 -#define QC_BIDIR 2 -#define QC_SERIAL 3 - -/* ... and one from column B */ -#define QC_ANY 0x00 -#define QC_FORCE_UNIDIR 0x10 -#define QC_FORCE_BIDIR 0x20 -#define QC_FORCE_SERIAL 0x30 -/* in the port_mode member */ - -#define QC_MODE_MASK 0x07 -#define QC_FORCE_MASK 0x70 - -#define MAX_HEIGHT 243 -#define MAX_WIDTH 336 - -/* Bit fields for status flags */ -#define QC_PARAM_CHANGE 0x01 /* Camera status change has occurred */ - -struct qcam { - struct v4l2_device v4l2_dev; - struct video_device vdev; - struct v4l2_ctrl_handler hdl; - struct vb2_queue vb_vidq; - struct pardevice *pdev; - struct parport *pport; - struct mutex lock; - struct mutex queue_lock; - int width, height; - int bpp; - int mode; - int contrast, brightness, whitebal; - int port_mode; - int transfer_scale; - int top, left; - int status; - unsigned int saved_bits; - unsigned long in_use; -}; - -static unsigned int maxpoll = 250; /* Maximum busy-loop count for qcam I/O */ -static unsigned int yieldlines = 4; /* Yield after this many during capture */ -static int video_nr = -1; -static unsigned int force_init; /* Whether to probe aggressively */ - -module_param(maxpoll, int, 0); -module_param(yieldlines, int, 0); -module_param(video_nr, int, 0); - -/* Set force_init=1 to avoid detection by polling status register and - * immediately attempt to initialize qcam */ -module_param(force_init, int, 0); - -#define MAX_CAMS 4 -static struct qcam *qcams[MAX_CAMS]; -static unsigned int num_cams; - -static inline int read_lpstatus(struct qcam *q) -{ - return parport_read_status(q->pport); -} - -static inline int read_lpdata(struct qcam *q) -{ - return parport_read_data(q->pport); -} - -static inline void write_lpdata(struct qcam *q, int d) -{ - parport_write_data(q->pport, d); -} - -static void write_lpcontrol(struct qcam *q, int d) -{ - if (d & 0x20) { - /* Set bidirectional mode to reverse (data in) */ - parport_data_reverse(q->pport); - } else { - /* Set bidirectional mode to forward (data out) */ - parport_data_forward(q->pport); - } - - /* Now issue the regular port command, but strip out the - * direction flag */ - d &= ~0x20; - parport_write_control(q->pport, d); -} - - -/* qc_waithand busy-waits for a handshake signal from the QuickCam. - * Almost all communication with the camera requires handshaking. */ - -static int qc_waithand(struct qcam *q, int val) -{ - int status; - int runs = 0; - - if (val) { - while (!((status = read_lpstatus(q)) & 8)) { - /* 1000 is enough spins on the I/O for all normal - cases, at that point we start to poll slowly - until the camera wakes up. However, we are - busy blocked until the camera responds, so - setting it lower is much better for interactive - response. */ - - if (runs++ > maxpoll) - msleep_interruptible(5); - if (runs > (maxpoll + 1000)) /* 5 seconds */ - return -1; - } - } else { - while (((status = read_lpstatus(q)) & 8)) { - /* 1000 is enough spins on the I/O for all normal - cases, at that point we start to poll slowly - until the camera wakes up. However, we are - busy blocked until the camera responds, so - setting it lower is much better for interactive - response. */ - - if (runs++ > maxpoll) - msleep_interruptible(5); - if (runs++ > (maxpoll + 1000)) /* 5 seconds */ - return -1; - } - } - - return status; -} - -/* Waithand2 is used when the qcam is in bidirectional mode, and the - * handshaking signal is CamRdy2 (bit 0 of data reg) instead of CamRdy1 - * (bit 3 of status register). It also returns the last value read, - * since this data is useful. */ - -static unsigned int qc_waithand2(struct qcam *q, int val) -{ - unsigned int status; - int runs = 0; - - do { - status = read_lpdata(q); - /* 1000 is enough spins on the I/O for all normal - cases, at that point we start to poll slowly - until the camera wakes up. However, we are - busy blocked until the camera responds, so - setting it lower is much better for interactive - response. */ - - if (runs++ > maxpoll) - msleep_interruptible(5); - if (runs++ > (maxpoll + 1000)) /* 5 seconds */ - return 0; - } while ((status & 1) != val); - - return status; -} - -/* qc_command is probably a bit of a misnomer -- it's used to send - * bytes *to* the camera. Generally, these bytes are either commands - * or arguments to commands, so the name fits, but it still bugs me a - * bit. See the documentation for a list of commands. */ - -static int qc_command(struct qcam *q, int command) -{ - int n1, n2; - int cmd; - - write_lpdata(q, command); - write_lpcontrol(q, 6); - - n1 = qc_waithand(q, 1); - - write_lpcontrol(q, 0xe); - n2 = qc_waithand(q, 0); - - cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4); - return cmd; -} - -static int qc_readparam(struct qcam *q) -{ - int n1, n2; - int cmd; - - write_lpcontrol(q, 6); - n1 = qc_waithand(q, 1); - - write_lpcontrol(q, 0xe); - n2 = qc_waithand(q, 0); - - cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4); - return cmd; -} - - -/* Try to detect a QuickCam. It appears to flash the upper 4 bits of - the status register at 5-10 Hz. This is only used in the autoprobe - code. Be aware that this isn't the way Connectix detects the - camera (they send a reset and try to handshake), but this should be - almost completely safe, while their method screws up my printer if - I plug it in before the camera. */ - -static int qc_detect(struct qcam *q) -{ - int reg, lastreg; - int count = 0; - int i; - - if (force_init) - return 1; - - lastreg = reg = read_lpstatus(q) & 0xf0; - - for (i = 0; i < 500; i++) { - reg = read_lpstatus(q) & 0xf0; - if (reg != lastreg) - count++; - lastreg = reg; - mdelay(2); - } - - -#if 0 - /* Force camera detection during testing. Sometimes the camera - won't be flashing these bits. Possibly unloading the module - in the middle of a grab? Or some timeout condition? - I've seen this parameter as low as 19 on my 450Mhz box - mpc */ - printk(KERN_DEBUG "Debugging: QCam detection counter <30-200 counts as detected>: %d\n", count); - return 1; -#endif - - /* Be (even more) liberal in what you accept... */ - - if (count > 20 && count < 400) { - return 1; /* found */ - } else { - printk(KERN_ERR "No Quickcam found on port %s\n", - q->pport->name); - printk(KERN_DEBUG "Quickcam detection counter: %u\n", count); - return 0; /* not found */ - } -} - -/* Decide which scan mode to use. There's no real requirement that - * the scanmode match the resolution in q->height and q-> width -- the - * camera takes the picture at the resolution specified in the - * "scanmode" and then returns the image at the resolution specified - * with the resolution commands. If the scan is bigger than the - * requested resolution, the upper-left hand corner of the scan is - * returned. If the scan is smaller, then the rest of the image - * returned contains garbage. */ - -static int qc_setscanmode(struct qcam *q) -{ - int old_mode = q->mode; - - switch (q->transfer_scale) { - case 1: - q->mode = 0; - break; - case 2: - q->mode = 4; - break; - case 4: - q->mode = 8; - break; - } - - switch (q->bpp) { - case 4: - break; - case 6: - q->mode += 2; - break; - } - - switch (q->port_mode & QC_MODE_MASK) { - case QC_BIDIR: - q->mode += 1; - break; - case QC_NOTSET: - case QC_UNIDIR: - break; - } - - if (q->mode != old_mode) - q->status |= QC_PARAM_CHANGE; - - return 0; -} - - -/* Reset the QuickCam. This uses the same sequence the Windows - * QuickPic program uses. Someone with a bi-directional port should - * check that bi-directional mode is detected right, and then - * implement bi-directional mode in qc_readbyte(). */ - -static void qc_reset(struct qcam *q) -{ - switch (q->port_mode & QC_FORCE_MASK) { - case QC_FORCE_UNIDIR: - q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_UNIDIR; - break; - - case QC_FORCE_BIDIR: - q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_BIDIR; - break; - - case QC_ANY: - write_lpcontrol(q, 0x20); - write_lpdata(q, 0x75); - - if (read_lpdata(q) != 0x75) - q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_BIDIR; - else - q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_UNIDIR; - break; - } - - write_lpcontrol(q, 0xb); - udelay(250); - write_lpcontrol(q, 0xe); - qc_setscanmode(q); /* in case port_mode changed */ -} - - - -/* Reset the QuickCam and program for brightness, contrast, - * white-balance, and resolution. */ - -static void qc_set(struct qcam *q) -{ - int val; - int val2; - - /* Set the brightness. Yes, this is repetitive, but it works. - * Shorter versions seem to fail subtly. Feel free to try :-). */ - /* I think the problem was in qc_command, not here -- bls */ - - qc_command(q, 0xb); - qc_command(q, q->brightness); - - val = q->height / q->transfer_scale; - qc_command(q, 0x11); - qc_command(q, val); - if ((q->port_mode & QC_MODE_MASK) == QC_UNIDIR && q->bpp == 6) { - /* The normal "transfers per line" calculation doesn't seem to work - as expected here (and yet it works fine in qc_scan). No idea - why this case is the odd man out. Fortunately, Laird's original - working version gives me a good way to guess at working values. - -- bls */ - val = q->width; - val2 = q->transfer_scale * 4; - } else { - val = q->width * q->bpp; - val2 = (((q->port_mode & QC_MODE_MASK) == QC_BIDIR) ? 24 : 8) * - q->transfer_scale; - } - val = DIV_ROUND_UP(val, val2); - qc_command(q, 0x13); - qc_command(q, val); - - /* Setting top and left -- bls */ - qc_command(q, 0xd); - qc_command(q, q->top); - qc_command(q, 0xf); - qc_command(q, q->left / 2); - - qc_command(q, 0x19); - qc_command(q, q->contrast); - qc_command(q, 0x1f); - qc_command(q, q->whitebal); - - /* Clear flag that we must update the grabbing parameters on the camera - before we grab the next frame */ - q->status &= (~QC_PARAM_CHANGE); -} - -/* Qc_readbytes reads some bytes from the QC and puts them in - the supplied buffer. It returns the number of bytes read, - or -1 on error. */ - -static inline int qc_readbytes(struct qcam *q, char buffer[]) -{ - int ret = 1; - unsigned int hi, lo; - unsigned int hi2, lo2; - static int state; - - if (buffer == NULL) { - state = 0; - return 0; - } - - switch (q->port_mode & QC_MODE_MASK) { - case QC_BIDIR: /* Bi-directional Port */ - write_lpcontrol(q, 0x26); - lo = (qc_waithand2(q, 1) >> 1); - hi = (read_lpstatus(q) >> 3) & 0x1f; - write_lpcontrol(q, 0x2e); - lo2 = (qc_waithand2(q, 0) >> 1); - hi2 = (read_lpstatus(q) >> 3) & 0x1f; - switch (q->bpp) { - case 4: - buffer[0] = lo & 0xf; - buffer[1] = ((lo & 0x70) >> 4) | ((hi & 1) << 3); - buffer[2] = (hi & 0x1e) >> 1; - buffer[3] = lo2 & 0xf; - buffer[4] = ((lo2 & 0x70) >> 4) | ((hi2 & 1) << 3); - buffer[5] = (hi2 & 0x1e) >> 1; - ret = 6; - break; - case 6: - buffer[0] = lo & 0x3f; - buffer[1] = ((lo & 0x40) >> 6) | (hi << 1); - buffer[2] = lo2 & 0x3f; - buffer[3] = ((lo2 & 0x40) >> 6) | (hi2 << 1); - ret = 4; - break; - } - break; - - case QC_UNIDIR: /* Unidirectional Port */ - write_lpcontrol(q, 6); - lo = (qc_waithand(q, 1) & 0xf0) >> 4; - write_lpcontrol(q, 0xe); - hi = (qc_waithand(q, 0) & 0xf0) >> 4; - - switch (q->bpp) { - case 4: - buffer[0] = lo; - buffer[1] = hi; - ret = 2; - break; - case 6: - switch (state) { - case 0: - buffer[0] = (lo << 2) | ((hi & 0xc) >> 2); - q->saved_bits = (hi & 3) << 4; - state = 1; - ret = 1; - break; - case 1: - buffer[0] = lo | q->saved_bits; - q->saved_bits = hi << 2; - state = 2; - ret = 1; - break; - case 2: - buffer[0] = ((lo & 0xc) >> 2) | q->saved_bits; - buffer[1] = ((lo & 3) << 4) | hi; - state = 0; - ret = 2; - break; - } - break; - } - break; - } - return ret; -} - -/* requests a scan from the camera. It sends the correct instructions - * to the camera and then reads back the correct number of bytes. In - * previous versions of this routine the return structure contained - * the raw output from the camera, and there was a 'qc_convertscan' - * function that converted that to a useful format. In version 0.3 I - * rolled qc_convertscan into qc_scan and now I only return the - * converted scan. The format is just an one-dimensional array of - * characters, one for each pixel, with 0=black up to n=white, where - * n=2^(bit depth)-1. Ask me for more details if you don't understand - * this. */ - -static long qc_capture(struct qcam *q, u8 *buf, unsigned long len) -{ - int i, j, k, yield; - int bytes; - int linestotrans, transperline; - int divisor; - int pixels_per_line; - int pixels_read = 0; - int got = 0; - char buffer[6]; - int shift = 8 - q->bpp; - char invert; - - if (q->mode == -1) - return -ENXIO; - - qc_command(q, 0x7); - qc_command(q, q->mode); - - if ((q->port_mode & QC_MODE_MASK) == QC_BIDIR) { - write_lpcontrol(q, 0x2e); /* turn port around */ - write_lpcontrol(q, 0x26); - qc_waithand(q, 1); - write_lpcontrol(q, 0x2e); - qc_waithand(q, 0); - } - - /* strange -- should be 15:63 below, but 4bpp is odd */ - invert = (q->bpp == 4) ? 16 : 63; - - linestotrans = q->height / q->transfer_scale; - pixels_per_line = q->width / q->transfer_scale; - transperline = q->width * q->bpp; - divisor = (((q->port_mode & QC_MODE_MASK) == QC_BIDIR) ? 24 : 8) * - q->transfer_scale; - transperline = DIV_ROUND_UP(transperline, divisor); - - for (i = 0, yield = yieldlines; i < linestotrans; i++) { - for (pixels_read = j = 0; j < transperline; j++) { - bytes = qc_readbytes(q, buffer); - for (k = 0; k < bytes && (pixels_read + k) < pixels_per_line; k++) { - int o; - if (buffer[k] == 0 && invert == 16) { - /* 4bpp is odd (again) -- inverter is 16, not 15, but output - must be 0-15 -- bls */ - buffer[k] = 16; - } - o = i * pixels_per_line + pixels_read + k; - if (o < len) { - u8 ch = invert - buffer[k]; - got++; - buf[o] = ch << shift; - } - } - pixels_read += bytes; - } - qc_readbytes(q, NULL); /* reset state machine */ - - /* Grabbing an entire frame from the quickcam is a lengthy - process. We don't (usually) want to busy-block the - processor for the entire frame. yieldlines is a module - parameter. If we yield every line, the minimum frame - time will be 240 / 200 = 1.2 seconds. The compile-time - default is to yield every 4 lines. */ - if (i >= yield) { - msleep_interruptible(5); - yield = i + yieldlines; - } - } - - if ((q->port_mode & QC_MODE_MASK) == QC_BIDIR) { - write_lpcontrol(q, 2); - write_lpcontrol(q, 6); - udelay(3); - write_lpcontrol(q, 0xe); - } - if (got < len) - return got; - return len; -} - -/* ------------------------------------------------------------------ - Videobuf operations - ------------------------------------------------------------------*/ -static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, - unsigned int *nbuffers, unsigned int *nplanes, - unsigned int sizes[], void *alloc_ctxs[]) -{ - struct qcam *dev = vb2_get_drv_priv(vq); - - if (0 == *nbuffers) - *nbuffers = 3; - *nplanes = 1; - mutex_lock(&dev->lock); - if (fmt) - sizes[0] = fmt->fmt.pix.width * fmt->fmt.pix.height; - else - sizes[0] = (dev->width / dev->transfer_scale) * - (dev->height / dev->transfer_scale); - mutex_unlock(&dev->lock); - return 0; -} - -static void buffer_queue(struct vb2_buffer *vb) -{ - vb2_buffer_done(vb, VB2_BUF_STATE_DONE); -} - -static void buffer_finish(struct vb2_buffer *vb) -{ - struct qcam *qcam = vb2_get_drv_priv(vb->vb2_queue); - void *vbuf = vb2_plane_vaddr(vb, 0); - int size = vb->vb2_queue->plane_sizes[0]; - int len; - - if (!vb2_is_streaming(vb->vb2_queue)) - return; - - mutex_lock(&qcam->lock); - parport_claim_or_block(qcam->pdev); - - qc_reset(qcam); - - /* Update the camera parameters if we need to */ - if (qcam->status & QC_PARAM_CHANGE) - qc_set(qcam); - - len = qc_capture(qcam, vbuf, size); - - parport_release(qcam->pdev); - mutex_unlock(&qcam->lock); - v4l2_get_timestamp(&vb->v4l2_buf.timestamp); - if (len != size) - vb->state = VB2_BUF_STATE_ERROR; - vb2_set_plane_payload(vb, 0, len); -} - -static struct vb2_ops qcam_video_qops = { - .queue_setup = queue_setup, - .buf_queue = buffer_queue, - .buf_finish = buffer_finish, - .wait_prepare = vb2_ops_wait_prepare, - .wait_finish = vb2_ops_wait_finish, -}; - -/* - * Video4linux interfacing - */ - -static int qcam_querycap(struct file *file, void *priv, - struct v4l2_capability *vcap) -{ - struct qcam *qcam = video_drvdata(file); - - strlcpy(vcap->driver, qcam->v4l2_dev.name, sizeof(vcap->driver)); - strlcpy(vcap->card, "Connectix B&W Quickcam", sizeof(vcap->card)); - strlcpy(vcap->bus_info, qcam->pport->name, sizeof(vcap->bus_info)); - vcap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | - V4L2_CAP_STREAMING; - vcap->capabilities = vcap->device_caps | V4L2_CAP_DEVICE_CAPS; - return 0; -} - -static int qcam_enum_input(struct file *file, void *fh, struct v4l2_input *vin) -{ - if (vin->index > 0) - return -EINVAL; - strlcpy(vin->name, "Camera", sizeof(vin->name)); - vin->type = V4L2_INPUT_TYPE_CAMERA; - vin->audioset = 0; - vin->tuner = 0; - vin->std = 0; - vin->status = 0; - return 0; -} - -static int qcam_g_input(struct file *file, void *fh, unsigned int *inp) -{ - *inp = 0; - return 0; -} - -static int qcam_s_input(struct file *file, void *fh, unsigned int inp) -{ - return (inp > 0) ? -EINVAL : 0; -} - -static int qcam_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct qcam *qcam = video_drvdata(file); - struct v4l2_pix_format *pix = &fmt->fmt.pix; - - pix->width = qcam->width / qcam->transfer_scale; - pix->height = qcam->height / qcam->transfer_scale; - pix->pixelformat = (qcam->bpp == 4) ? V4L2_PIX_FMT_Y4 : V4L2_PIX_FMT_Y6; - pix->field = V4L2_FIELD_NONE; - pix->bytesperline = pix->width; - pix->sizeimage = pix->width * pix->height; - /* Just a guess */ - pix->colorspace = V4L2_COLORSPACE_SRGB; - return 0; -} - -static int qcam_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct v4l2_pix_format *pix = &fmt->fmt.pix; - - if (pix->height <= 60 || pix->width <= 80) { - pix->height = 60; - pix->width = 80; - } else if (pix->height <= 120 || pix->width <= 160) { - pix->height = 120; - pix->width = 160; - } else { - pix->height = 240; - pix->width = 320; - } - if (pix->pixelformat != V4L2_PIX_FMT_Y4 && - pix->pixelformat != V4L2_PIX_FMT_Y6) - pix->pixelformat = V4L2_PIX_FMT_Y4; - pix->field = V4L2_FIELD_NONE; - pix->bytesperline = pix->width; - pix->sizeimage = pix->width * pix->height; - /* Just a guess */ - pix->colorspace = V4L2_COLORSPACE_SRGB; - return 0; -} - -static int qcam_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct qcam *qcam = video_drvdata(file); - struct v4l2_pix_format *pix = &fmt->fmt.pix; - int ret = qcam_try_fmt_vid_cap(file, fh, fmt); - - if (ret) - return ret; - if (vb2_is_busy(&qcam->vb_vidq)) - return -EBUSY; - qcam->width = 320; - qcam->height = 240; - if (pix->height == 60) - qcam->transfer_scale = 4; - else if (pix->height == 120) - qcam->transfer_scale = 2; - else - qcam->transfer_scale = 1; - if (pix->pixelformat == V4L2_PIX_FMT_Y6) - qcam->bpp = 6; - else - qcam->bpp = 4; - - qc_setscanmode(qcam); - /* We must update the camera before we grab. We could - just have changed the grab size */ - qcam->status |= QC_PARAM_CHANGE; - return 0; -} - -static int qcam_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt) -{ - static struct v4l2_fmtdesc formats[] = { - { 0, 0, 0, - "4-Bit Monochrome", V4L2_PIX_FMT_Y4, - { 0, 0, 0, 0 } - }, - { 1, 0, 0, - "6-Bit Monochrome", V4L2_PIX_FMT_Y6, - { 0, 0, 0, 0 } - }, - }; - enum v4l2_buf_type type = fmt->type; - - if (fmt->index > 1) - return -EINVAL; - - *fmt = formats[fmt->index]; - fmt->type = type; - return 0; -} - -static int qcam_enum_framesizes(struct file *file, void *fh, - struct v4l2_frmsizeenum *fsize) -{ - static const struct v4l2_frmsize_discrete sizes[] = { - { 80, 60 }, - { 160, 120 }, - { 320, 240 }, - }; - - if (fsize->index > 2) - return -EINVAL; - if (fsize->pixel_format != V4L2_PIX_FMT_Y4 && - fsize->pixel_format != V4L2_PIX_FMT_Y6) - return -EINVAL; - fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; - fsize->discrete = sizes[fsize->index]; - return 0; -} - -static int qcam_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct qcam *qcam = - container_of(ctrl->handler, struct qcam, hdl); - int ret = 0; - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - qcam->brightness = ctrl->val; - break; - case V4L2_CID_CONTRAST: - qcam->contrast = ctrl->val; - break; - case V4L2_CID_GAMMA: - qcam->whitebal = ctrl->val; - break; - default: - ret = -EINVAL; - break; - } - if (ret == 0) - qcam->status |= QC_PARAM_CHANGE; - return ret; -} - -static const struct v4l2_file_operations qcam_fops = { - .owner = THIS_MODULE, - .open = v4l2_fh_open, - .release = vb2_fop_release, - .poll = vb2_fop_poll, - .unlocked_ioctl = video_ioctl2, - .read = vb2_fop_read, - .mmap = vb2_fop_mmap, -}; - -static const struct v4l2_ioctl_ops qcam_ioctl_ops = { - .vidioc_querycap = qcam_querycap, - .vidioc_g_input = qcam_g_input, - .vidioc_s_input = qcam_s_input, - .vidioc_enum_input = qcam_enum_input, - .vidioc_enum_fmt_vid_cap = qcam_enum_fmt_vid_cap, - .vidioc_enum_framesizes = qcam_enum_framesizes, - .vidioc_g_fmt_vid_cap = qcam_g_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = qcam_s_fmt_vid_cap, - .vidioc_try_fmt_vid_cap = qcam_try_fmt_vid_cap, - .vidioc_reqbufs = vb2_ioctl_reqbufs, - .vidioc_create_bufs = vb2_ioctl_create_bufs, - .vidioc_prepare_buf = vb2_ioctl_prepare_buf, - .vidioc_querybuf = vb2_ioctl_querybuf, - .vidioc_qbuf = vb2_ioctl_qbuf, - .vidioc_dqbuf = vb2_ioctl_dqbuf, - .vidioc_streamon = vb2_ioctl_streamon, - .vidioc_streamoff = vb2_ioctl_streamoff, - .vidioc_log_status = v4l2_ctrl_log_status, - .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, -}; - -static const struct v4l2_ctrl_ops qcam_ctrl_ops = { - .s_ctrl = qcam_s_ctrl, -}; - -/* Initialize the QuickCam driver control structure. This is where - * defaults are set for people who don't have a config file.*/ - -static struct qcam *qcam_init(struct parport *port) -{ - struct qcam *qcam; - struct v4l2_device *v4l2_dev; - struct vb2_queue *q; - int err; - - qcam = kzalloc(sizeof(struct qcam), GFP_KERNEL); - if (qcam == NULL) - return NULL; - - v4l2_dev = &qcam->v4l2_dev; - snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "bw-qcam%u", num_cams); - - if (v4l2_device_register(port->dev, v4l2_dev) < 0) { - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - kfree(qcam); - return NULL; - } - - v4l2_ctrl_handler_init(&qcam->hdl, 3); - v4l2_ctrl_new_std(&qcam->hdl, &qcam_ctrl_ops, - V4L2_CID_BRIGHTNESS, 0, 255, 1, 180); - v4l2_ctrl_new_std(&qcam->hdl, &qcam_ctrl_ops, - V4L2_CID_CONTRAST, 0, 255, 1, 192); - v4l2_ctrl_new_std(&qcam->hdl, &qcam_ctrl_ops, - V4L2_CID_GAMMA, 0, 255, 1, 105); - if (qcam->hdl.error) { - v4l2_err(v4l2_dev, "couldn't register controls\n"); - goto exit; - } - - mutex_init(&qcam->lock); - mutex_init(&qcam->queue_lock); - - /* initialize queue */ - q = &qcam->vb_vidq; - q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; - q->drv_priv = qcam; - q->ops = &qcam_video_qops; - q->mem_ops = &vb2_vmalloc_memops; - q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - err = vb2_queue_init(q); - if (err < 0) { - v4l2_err(v4l2_dev, "couldn't init vb2_queue for %s.\n", port->name); - goto exit; - } - qcam->vdev.queue = q; - qcam->vdev.queue->lock = &qcam->queue_lock; - - qcam->pport = port; - qcam->pdev = parport_register_device(port, v4l2_dev->name, NULL, NULL, - NULL, 0, NULL); - if (qcam->pdev == NULL) { - v4l2_err(v4l2_dev, "couldn't register for %s.\n", port->name); - goto exit; - } - - strlcpy(qcam->vdev.name, "Connectix QuickCam", sizeof(qcam->vdev.name)); - qcam->vdev.v4l2_dev = v4l2_dev; - qcam->vdev.ctrl_handler = &qcam->hdl; - qcam->vdev.fops = &qcam_fops; - qcam->vdev.lock = &qcam->lock; - qcam->vdev.ioctl_ops = &qcam_ioctl_ops; - qcam->vdev.release = video_device_release_empty; - video_set_drvdata(&qcam->vdev, qcam); - - qcam->port_mode = (QC_ANY | QC_NOTSET); - qcam->width = 320; - qcam->height = 240; - qcam->bpp = 4; - qcam->transfer_scale = 2; - qcam->contrast = 192; - qcam->brightness = 180; - qcam->whitebal = 105; - qcam->top = 1; - qcam->left = 14; - qcam->mode = -1; - qcam->status = QC_PARAM_CHANGE; - return qcam; - -exit: - v4l2_ctrl_handler_free(&qcam->hdl); - kfree(qcam); - return NULL; -} - -static int qc_calibrate(struct qcam *q) -{ - /* - * Bugfix by Hanno Mueller hmueller@kabel.de, Mai 21 96 - * The white balance is an individual value for each - * quickcam. - */ - - int value; - int count = 0; - - qc_command(q, 27); /* AutoAdjustOffset */ - qc_command(q, 0); /* Dummy Parameter, ignored by the camera */ - - /* GetOffset (33) will read 255 until autocalibration */ - /* is finished. After that, a value of 1-254 will be */ - /* returned. */ - - do { - qc_command(q, 33); - value = qc_readparam(q); - mdelay(1); - schedule(); - count++; - } while (value == 0xff && count < 2048); - - q->whitebal = value; - return value; -} - -static int init_bwqcam(struct parport *port) -{ - struct qcam *qcam; - - if (num_cams == MAX_CAMS) { - printk(KERN_ERR "Too many Quickcams (max %d)\n", MAX_CAMS); - return -ENOSPC; - } - - qcam = qcam_init(port); - if (qcam == NULL) - return -ENODEV; - - parport_claim_or_block(qcam->pdev); - - qc_reset(qcam); - - if (qc_detect(qcam) == 0) { - parport_release(qcam->pdev); - parport_unregister_device(qcam->pdev); - kfree(qcam); - return -ENODEV; - } - qc_calibrate(qcam); - v4l2_ctrl_handler_setup(&qcam->hdl); - - parport_release(qcam->pdev); - - v4l2_info(&qcam->v4l2_dev, "Connectix Quickcam on %s\n", qcam->pport->name); - - if (video_register_device(&qcam->vdev, VFL_TYPE_GRABBER, video_nr) < 0) { - parport_unregister_device(qcam->pdev); - kfree(qcam); - return -ENODEV; - } - - qcams[num_cams++] = qcam; - - return 0; -} - -static void close_bwqcam(struct qcam *qcam) -{ - video_unregister_device(&qcam->vdev); - v4l2_ctrl_handler_free(&qcam->hdl); - parport_unregister_device(qcam->pdev); - kfree(qcam); -} - -/* The parport parameter controls which parports will be scanned. - * Scanning all parports causes some printers to print a garbage page. - * -- March 14, 1999 Billy Donahue */ -#ifdef MODULE -static char *parport[MAX_CAMS] = { NULL, }; -module_param_array(parport, charp, NULL, 0); -#endif - -static int accept_bwqcam(struct parport *port) -{ -#ifdef MODULE - int n; - - if (parport[0] && strncmp(parport[0], "auto", 4) != 0) { - /* user gave parport parameters */ - for (n = 0; n < MAX_CAMS && parport[n]; n++) { - char *ep; - unsigned long r; - r = simple_strtoul(parport[n], &ep, 0); - if (ep == parport[n]) { - printk(KERN_ERR - "bw-qcam: bad port specifier \"%s\"\n", - parport[n]); - continue; - } - if (r == port->number) - return 1; - } - return 0; - } -#endif - return 1; -} - -static void bwqcam_attach(struct parport *port) -{ - if (accept_bwqcam(port)) - init_bwqcam(port); -} - -static void bwqcam_detach(struct parport *port) -{ - int i; - for (i = 0; i < num_cams; i++) { - struct qcam *qcam = qcams[i]; - if (qcam && qcam->pdev->port == port) { - qcams[i] = NULL; - close_bwqcam(qcam); - } - } -} - -static struct parport_driver bwqcam_driver = { - .name = "bw-qcam", - .attach = bwqcam_attach, - .detach = bwqcam_detach, -}; - -static void __exit exit_bw_qcams(void) -{ - parport_unregister_driver(&bwqcam_driver); -} - -static int __init init_bw_qcams(void) -{ -#ifdef MODULE - /* Do some sanity checks on the module parameters. */ - if (maxpoll > 5000) { - printk(KERN_INFO "Connectix Quickcam max-poll was above 5000. Using 5000.\n"); - maxpoll = 5000; - } - - if (yieldlines < 1) { - printk(KERN_INFO "Connectix Quickcam yieldlines was less than 1. Using 1.\n"); - yieldlines = 1; - } -#endif - return parport_register_driver(&bwqcam_driver); -} - -module_init(init_bw_qcams); -module_exit(exit_bw_qcams); - -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); diff --git a/drivers/staging/media/parport/c-qcam.c b/drivers/staging/media/parport/c-qcam.c deleted file mode 100644 index b9010bd3ed3e..000000000000 --- a/drivers/staging/media/parport/c-qcam.c +++ /dev/null @@ -1,882 +0,0 @@ -/* - * Video4Linux Colour QuickCam driver - * Copyright 1997-2000 Philip Blundell - * - * Module parameters: - * - * parport=auto -- probe all parports (default) - * parport=0 -- parport0 becomes qcam1 - * parport=2,0,1 -- parports 2,0,1 are tried in that order - * - * probe=0 -- do no probing, assume camera is present - * probe=1 -- use IEEE-1284 autoprobe data only (default) - * probe=2 -- probe aggressively for cameras - * - * force_rgb=1 -- force data format to RGB (default is BGR) - * - * The parport parameter controls which parports will be scanned. - * Scanning all parports causes some printers to print a garbage page. - * -- March 14, 1999 Billy Donahue - * - * Fixed data format to BGR, added force_rgb parameter. Added missing - * parport_unregister_driver() on module removal. - * -- May 28, 2000 Claudio Matsuoka - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct qcam { - struct v4l2_device v4l2_dev; - struct video_device vdev; - struct v4l2_ctrl_handler hdl; - struct pardevice *pdev; - struct parport *pport; - int width, height; - int ccd_width, ccd_height; - int mode; - int contrast, brightness, whitebal; - int top, left; - unsigned int bidirectional; - struct mutex lock; -}; - -/* cameras maximum */ -#define MAX_CAMS 4 - -/* The three possible QuickCam modes */ -#define QC_MILLIONS 0x18 -#define QC_BILLIONS 0x10 -#define QC_THOUSANDS 0x08 /* with VIDEC compression (not supported) */ - -/* The three possible decimations */ -#define QC_DECIMATION_1 0 -#define QC_DECIMATION_2 2 -#define QC_DECIMATION_4 4 - -#define BANNER "Colour QuickCam for Video4Linux v0.06" - -static int parport[MAX_CAMS] = { [1 ... MAX_CAMS-1] = -1 }; -static int probe = 2; -static bool force_rgb; -static int video_nr = -1; - -/* FIXME: parport=auto would never have worked, surely? --RR */ -MODULE_PARM_DESC(parport, "parport= for port detection method\n" - "probe=<0|1|2> for camera detection method\n" - "force_rgb=<0|1> for RGB data format (default BGR)"); -module_param_array(parport, int, NULL, 0); -module_param(probe, int, 0); -module_param(force_rgb, bool, 0); -module_param(video_nr, int, 0); - -static struct qcam *qcams[MAX_CAMS]; -static unsigned int num_cams; - -static inline void qcam_set_ack(struct qcam *qcam, unsigned int i) -{ - /* note: the QC specs refer to the PCAck pin by voltage, not - software level. PC ports have builtin inverters. */ - parport_frob_control(qcam->pport, 8, i ? 8 : 0); -} - -static inline unsigned int qcam_ready1(struct qcam *qcam) -{ - return (parport_read_status(qcam->pport) & 0x8) ? 1 : 0; -} - -static inline unsigned int qcam_ready2(struct qcam *qcam) -{ - return (parport_read_data(qcam->pport) & 0x1) ? 1 : 0; -} - -static unsigned int qcam_await_ready1(struct qcam *qcam, int value) -{ - struct v4l2_device *v4l2_dev = &qcam->v4l2_dev; - unsigned long oldjiffies = jiffies; - unsigned int i; - - for (oldjiffies = jiffies; - time_before(jiffies, oldjiffies + msecs_to_jiffies(40));) - if (qcam_ready1(qcam) == value) - return 0; - - /* If the camera didn't respond within 1/25 second, poll slowly - for a while. */ - for (i = 0; i < 50; i++) { - if (qcam_ready1(qcam) == value) - return 0; - msleep_interruptible(100); - } - - /* Probably somebody pulled the plug out. Not much we can do. */ - v4l2_err(v4l2_dev, "ready1 timeout (%d) %x %x\n", value, - parport_read_status(qcam->pport), - parport_read_control(qcam->pport)); - return 1; -} - -static unsigned int qcam_await_ready2(struct qcam *qcam, int value) -{ - struct v4l2_device *v4l2_dev = &qcam->v4l2_dev; - unsigned long oldjiffies = jiffies; - unsigned int i; - - for (oldjiffies = jiffies; - time_before(jiffies, oldjiffies + msecs_to_jiffies(40));) - if (qcam_ready2(qcam) == value) - return 0; - - /* If the camera didn't respond within 1/25 second, poll slowly - for a while. */ - for (i = 0; i < 50; i++) { - if (qcam_ready2(qcam) == value) - return 0; - msleep_interruptible(100); - } - - /* Probably somebody pulled the plug out. Not much we can do. */ - v4l2_err(v4l2_dev, "ready2 timeout (%d) %x %x %x\n", value, - parport_read_status(qcam->pport), - parport_read_control(qcam->pport), - parport_read_data(qcam->pport)); - return 1; -} - -static int qcam_read_data(struct qcam *qcam) -{ - unsigned int idata; - - qcam_set_ack(qcam, 0); - if (qcam_await_ready1(qcam, 1)) - return -1; - idata = parport_read_status(qcam->pport) & 0xf0; - qcam_set_ack(qcam, 1); - if (qcam_await_ready1(qcam, 0)) - return -1; - idata |= parport_read_status(qcam->pport) >> 4; - return idata; -} - -static int qcam_write_data(struct qcam *qcam, unsigned int data) -{ - struct v4l2_device *v4l2_dev = &qcam->v4l2_dev; - unsigned int idata; - - parport_write_data(qcam->pport, data); - idata = qcam_read_data(qcam); - if (data != idata) { - v4l2_warn(v4l2_dev, "sent %x but received %x\n", data, - idata); - return 1; - } - return 0; -} - -static inline int qcam_set(struct qcam *qcam, unsigned int cmd, unsigned int data) -{ - if (qcam_write_data(qcam, cmd)) - return -1; - if (qcam_write_data(qcam, data)) - return -1; - return 0; -} - -static inline int qcam_get(struct qcam *qcam, unsigned int cmd) -{ - if (qcam_write_data(qcam, cmd)) - return -1; - return qcam_read_data(qcam); -} - -static int qc_detect(struct qcam *qcam) -{ - unsigned int stat, ostat, i, count = 0; - - /* The probe routine below is not very reliable. The IEEE-1284 - probe takes precedence. */ - /* XXX Currently parport provides no way to distinguish between - "the IEEE probe was not done" and "the probe was done, but - no device was found". Fix this one day. */ - if (qcam->pport->probe_info[0].class == PARPORT_CLASS_MEDIA - && qcam->pport->probe_info[0].model - && !strcmp(qcam->pdev->port->probe_info[0].model, - "Color QuickCam 2.0")) { - printk(KERN_DEBUG "QuickCam: Found by IEEE1284 probe.\n"); - return 1; - } - - if (probe < 2) - return 0; - - parport_write_control(qcam->pport, 0xc); - - /* look for a heartbeat */ - ostat = stat = parport_read_status(qcam->pport); - for (i = 0; i < 250; i++) { - mdelay(1); - stat = parport_read_status(qcam->pport); - if (ostat != stat) { - if (++count >= 3) - return 1; - ostat = stat; - } - } - - /* Reset the camera and try again */ - parport_write_control(qcam->pport, 0xc); - parport_write_control(qcam->pport, 0x8); - mdelay(1); - parport_write_control(qcam->pport, 0xc); - mdelay(1); - count = 0; - - ostat = stat = parport_read_status(qcam->pport); - for (i = 0; i < 250; i++) { - mdelay(1); - stat = parport_read_status(qcam->pport); - if (ostat != stat) { - if (++count >= 3) - return 1; - ostat = stat; - } - } - - /* no (or flatline) camera, give up */ - return 0; -} - -static void qc_reset(struct qcam *qcam) -{ - parport_write_control(qcam->pport, 0xc); - parport_write_control(qcam->pport, 0x8); - mdelay(1); - parport_write_control(qcam->pport, 0xc); - mdelay(1); -} - -/* Reset the QuickCam and program for brightness, contrast, - * white-balance, and resolution. */ - -static void qc_setup(struct qcam *qcam) -{ - qc_reset(qcam); - - /* Set the brightness. */ - qcam_set(qcam, 11, qcam->brightness); - - /* Set the height and width. These refer to the actual - CCD area *before* applying the selected decimation. */ - qcam_set(qcam, 17, qcam->ccd_height); - qcam_set(qcam, 19, qcam->ccd_width / 2); - - /* Set top and left. */ - qcam_set(qcam, 0xd, qcam->top); - qcam_set(qcam, 0xf, qcam->left); - - /* Set contrast and white balance. */ - qcam_set(qcam, 0x19, qcam->contrast); - qcam_set(qcam, 0x1f, qcam->whitebal); - - /* Set the speed. */ - qcam_set(qcam, 45, 2); -} - -/* Read some bytes from the camera and put them in the buffer. - nbytes should be a multiple of 3, because bidirectional mode gives - us three bytes at a time. */ - -static unsigned int qcam_read_bytes(struct qcam *qcam, unsigned char *buf, unsigned int nbytes) -{ - unsigned int bytes = 0; - - qcam_set_ack(qcam, 0); - if (qcam->bidirectional) { - /* It's a bidirectional port */ - while (bytes < nbytes) { - unsigned int lo1, hi1, lo2, hi2; - unsigned char r, g, b; - - if (qcam_await_ready2(qcam, 1)) - return bytes; - lo1 = parport_read_data(qcam->pport) >> 1; - hi1 = ((parport_read_status(qcam->pport) >> 3) & 0x1f) ^ 0x10; - qcam_set_ack(qcam, 1); - if (qcam_await_ready2(qcam, 0)) - return bytes; - lo2 = parport_read_data(qcam->pport) >> 1; - hi2 = ((parport_read_status(qcam->pport) >> 3) & 0x1f) ^ 0x10; - qcam_set_ack(qcam, 0); - r = lo1 | ((hi1 & 1) << 7); - g = ((hi1 & 0x1e) << 3) | ((hi2 & 0x1e) >> 1); - b = lo2 | ((hi2 & 1) << 7); - if (force_rgb) { - buf[bytes++] = r; - buf[bytes++] = g; - buf[bytes++] = b; - } else { - buf[bytes++] = b; - buf[bytes++] = g; - buf[bytes++] = r; - } - } - } else { - /* It's a unidirectional port */ - int i = 0, n = bytes; - unsigned char rgb[3]; - - while (bytes < nbytes) { - unsigned int hi, lo; - - if (qcam_await_ready1(qcam, 1)) - return bytes; - hi = (parport_read_status(qcam->pport) & 0xf0); - qcam_set_ack(qcam, 1); - if (qcam_await_ready1(qcam, 0)) - return bytes; - lo = (parport_read_status(qcam->pport) & 0xf0); - qcam_set_ack(qcam, 0); - /* flip some bits */ - rgb[(i = bytes++ % 3)] = (hi | (lo >> 4)) ^ 0x88; - if (i >= 2) { -get_fragment: - if (force_rgb) { - buf[n++] = rgb[0]; - buf[n++] = rgb[1]; - buf[n++] = rgb[2]; - } else { - buf[n++] = rgb[2]; - buf[n++] = rgb[1]; - buf[n++] = rgb[0]; - } - } - } - if (i) { - i = 0; - goto get_fragment; - } - } - return bytes; -} - -#define BUFSZ 150 - -static long qc_capture(struct qcam *qcam, char __user *buf, unsigned long len) -{ - struct v4l2_device *v4l2_dev = &qcam->v4l2_dev; - unsigned lines, pixelsperline; - unsigned int is_bi_dir = qcam->bidirectional; - size_t wantlen, outptr = 0; - char tmpbuf[BUFSZ]; - - if (!access_ok(VERIFY_WRITE, buf, len)) - return -EFAULT; - - /* Wait for camera to become ready */ - for (;;) { - int i = qcam_get(qcam, 41); - - if (i == -1) { - qc_setup(qcam); - return -EIO; - } - if ((i & 0x80) == 0) - break; - schedule(); - } - - if (qcam_set(qcam, 7, (qcam->mode | (is_bi_dir ? 1 : 0)) + 1)) - return -EIO; - - lines = qcam->height; - pixelsperline = qcam->width; - - if (is_bi_dir) { - /* Turn the port around */ - parport_data_reverse(qcam->pport); - mdelay(3); - qcam_set_ack(qcam, 0); - if (qcam_await_ready1(qcam, 1)) { - qc_setup(qcam); - return -EIO; - } - qcam_set_ack(qcam, 1); - if (qcam_await_ready1(qcam, 0)) { - qc_setup(qcam); - return -EIO; - } - } - - wantlen = lines * pixelsperline * 24 / 8; - - while (wantlen) { - size_t t, s; - - s = (wantlen > BUFSZ) ? BUFSZ : wantlen; - t = qcam_read_bytes(qcam, tmpbuf, s); - if (outptr < len) { - size_t sz = len - outptr; - - if (sz > t) - sz = t; - if (__copy_to_user(buf + outptr, tmpbuf, sz)) - break; - outptr += sz; - } - wantlen -= t; - if (t < s) - break; - cond_resched(); - } - - len = outptr; - - if (wantlen) { - v4l2_err(v4l2_dev, "short read.\n"); - if (is_bi_dir) - parport_data_forward(qcam->pport); - qc_setup(qcam); - return len; - } - - if (is_bi_dir) { - int l; - - do { - l = qcam_read_bytes(qcam, tmpbuf, 3); - cond_resched(); - } while (l && (tmpbuf[0] == 0x7e || tmpbuf[1] == 0x7e || tmpbuf[2] == 0x7e)); - if (force_rgb) { - if (tmpbuf[0] != 0xe || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xf) - v4l2_err(v4l2_dev, "bad EOF\n"); - } else { - if (tmpbuf[0] != 0xf || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xe) - v4l2_err(v4l2_dev, "bad EOF\n"); - } - qcam_set_ack(qcam, 0); - if (qcam_await_ready1(qcam, 1)) { - v4l2_err(v4l2_dev, "no ack after EOF\n"); - parport_data_forward(qcam->pport); - qc_setup(qcam); - return len; - } - parport_data_forward(qcam->pport); - mdelay(3); - qcam_set_ack(qcam, 1); - if (qcam_await_ready1(qcam, 0)) { - v4l2_err(v4l2_dev, "no ack to port turnaround\n"); - qc_setup(qcam); - return len; - } - } else { - int l; - - do { - l = qcam_read_bytes(qcam, tmpbuf, 1); - cond_resched(); - } while (l && tmpbuf[0] == 0x7e); - l = qcam_read_bytes(qcam, tmpbuf + 1, 2); - if (force_rgb) { - if (tmpbuf[0] != 0xe || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xf) - v4l2_err(v4l2_dev, "bad EOF\n"); - } else { - if (tmpbuf[0] != 0xf || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xe) - v4l2_err(v4l2_dev, "bad EOF\n"); - } - } - - qcam_write_data(qcam, 0); - return len; -} - -/* - * Video4linux interfacing - */ - -static int qcam_querycap(struct file *file, void *priv, - struct v4l2_capability *vcap) -{ - struct qcam *qcam = video_drvdata(file); - - strlcpy(vcap->driver, qcam->v4l2_dev.name, sizeof(vcap->driver)); - strlcpy(vcap->card, "Color Quickcam", sizeof(vcap->card)); - strlcpy(vcap->bus_info, "parport", sizeof(vcap->bus_info)); - vcap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE; - vcap->capabilities = vcap->device_caps | V4L2_CAP_DEVICE_CAPS; - return 0; -} - -static int qcam_enum_input(struct file *file, void *fh, struct v4l2_input *vin) -{ - if (vin->index > 0) - return -EINVAL; - strlcpy(vin->name, "Camera", sizeof(vin->name)); - vin->type = V4L2_INPUT_TYPE_CAMERA; - vin->audioset = 0; - vin->tuner = 0; - vin->std = 0; - vin->status = 0; - return 0; -} - -static int qcam_g_input(struct file *file, void *fh, unsigned int *inp) -{ - *inp = 0; - return 0; -} - -static int qcam_s_input(struct file *file, void *fh, unsigned int inp) -{ - return (inp > 0) ? -EINVAL : 0; -} - -static int qcam_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct qcam *qcam = video_drvdata(file); - struct v4l2_pix_format *pix = &fmt->fmt.pix; - - pix->width = qcam->width; - pix->height = qcam->height; - pix->pixelformat = V4L2_PIX_FMT_RGB24; - pix->field = V4L2_FIELD_NONE; - pix->bytesperline = 3 * qcam->width; - pix->sizeimage = 3 * qcam->width * qcam->height; - /* Just a guess */ - pix->colorspace = V4L2_COLORSPACE_SRGB; - return 0; -} - -static int qcam_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct v4l2_pix_format *pix = &fmt->fmt.pix; - - if (pix->height < 60 || pix->width < 80) { - pix->height = 60; - pix->width = 80; - } else if (pix->height < 120 || pix->width < 160) { - pix->height = 120; - pix->width = 160; - } else { - pix->height = 240; - pix->width = 320; - } - pix->pixelformat = V4L2_PIX_FMT_RGB24; - pix->field = V4L2_FIELD_NONE; - pix->bytesperline = 3 * pix->width; - pix->sizeimage = 3 * pix->width * pix->height; - /* Just a guess */ - pix->colorspace = V4L2_COLORSPACE_SRGB; - return 0; -} - -static int qcam_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct qcam *qcam = video_drvdata(file); - struct v4l2_pix_format *pix = &fmt->fmt.pix; - int ret = qcam_try_fmt_vid_cap(file, fh, fmt); - - if (ret) - return ret; - switch (pix->height) { - case 60: - qcam->mode = QC_DECIMATION_4; - break; - case 120: - qcam->mode = QC_DECIMATION_2; - break; - default: - qcam->mode = QC_DECIMATION_1; - break; - } - - mutex_lock(&qcam->lock); - qcam->mode |= QC_MILLIONS; - qcam->height = pix->height; - qcam->width = pix->width; - parport_claim_or_block(qcam->pdev); - qc_setup(qcam); - parport_release(qcam->pdev); - mutex_unlock(&qcam->lock); - return 0; -} - -static int qcam_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt) -{ - static struct v4l2_fmtdesc formats[] = { - { 0, 0, 0, - "RGB 8:8:8", V4L2_PIX_FMT_RGB24, - { 0, 0, 0, 0 } - }, - }; - enum v4l2_buf_type type = fmt->type; - - if (fmt->index > 0) - return -EINVAL; - - *fmt = formats[fmt->index]; - fmt->type = type; - return 0; -} - -static ssize_t qcam_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos) -{ - struct qcam *qcam = video_drvdata(file); - int len; - - mutex_lock(&qcam->lock); - parport_claim_or_block(qcam->pdev); - /* Probably should have a semaphore against multiple users */ - len = qc_capture(qcam, buf, count); - parport_release(qcam->pdev); - mutex_unlock(&qcam->lock); - return len; -} - -static int qcam_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct qcam *qcam = - container_of(ctrl->handler, struct qcam, hdl); - int ret = 0; - - mutex_lock(&qcam->lock); - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - qcam->brightness = ctrl->val; - break; - case V4L2_CID_CONTRAST: - qcam->contrast = ctrl->val; - break; - case V4L2_CID_GAMMA: - qcam->whitebal = ctrl->val; - break; - default: - ret = -EINVAL; - break; - } - if (ret == 0) { - parport_claim_or_block(qcam->pdev); - qc_setup(qcam); - parport_release(qcam->pdev); - } - mutex_unlock(&qcam->lock); - return ret; -} - -static const struct v4l2_file_operations qcam_fops = { - .owner = THIS_MODULE, - .open = v4l2_fh_open, - .release = v4l2_fh_release, - .poll = v4l2_ctrl_poll, - .unlocked_ioctl = video_ioctl2, - .read = qcam_read, -}; - -static const struct v4l2_ioctl_ops qcam_ioctl_ops = { - .vidioc_querycap = qcam_querycap, - .vidioc_g_input = qcam_g_input, - .vidioc_s_input = qcam_s_input, - .vidioc_enum_input = qcam_enum_input, - .vidioc_enum_fmt_vid_cap = qcam_enum_fmt_vid_cap, - .vidioc_g_fmt_vid_cap = qcam_g_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = qcam_s_fmt_vid_cap, - .vidioc_try_fmt_vid_cap = qcam_try_fmt_vid_cap, - .vidioc_log_status = v4l2_ctrl_log_status, - .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, -}; - -static const struct v4l2_ctrl_ops qcam_ctrl_ops = { - .s_ctrl = qcam_s_ctrl, -}; - -/* Initialize the QuickCam driver control structure. */ - -static struct qcam *qcam_init(struct parport *port) -{ - struct qcam *qcam; - struct v4l2_device *v4l2_dev; - - qcam = kzalloc(sizeof(*qcam), GFP_KERNEL); - if (qcam == NULL) - return NULL; - - v4l2_dev = &qcam->v4l2_dev; - strlcpy(v4l2_dev->name, "c-qcam", sizeof(v4l2_dev->name)); - - if (v4l2_device_register(NULL, v4l2_dev) < 0) { - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - kfree(qcam); - return NULL; - } - - v4l2_ctrl_handler_init(&qcam->hdl, 3); - v4l2_ctrl_new_std(&qcam->hdl, &qcam_ctrl_ops, - V4L2_CID_BRIGHTNESS, 0, 255, 1, 240); - v4l2_ctrl_new_std(&qcam->hdl, &qcam_ctrl_ops, - V4L2_CID_CONTRAST, 0, 255, 1, 192); - v4l2_ctrl_new_std(&qcam->hdl, &qcam_ctrl_ops, - V4L2_CID_GAMMA, 0, 255, 1, 128); - if (qcam->hdl.error) { - v4l2_err(v4l2_dev, "couldn't register controls\n"); - v4l2_ctrl_handler_free(&qcam->hdl); - kfree(qcam); - return NULL; - } - - qcam->pport = port; - qcam->pdev = parport_register_device(port, "c-qcam", NULL, NULL, - NULL, 0, NULL); - - qcam->bidirectional = (qcam->pport->modes & PARPORT_MODE_TRISTATE) ? 1 : 0; - - if (qcam->pdev == NULL) { - v4l2_err(v4l2_dev, "couldn't register for %s.\n", port->name); - v4l2_ctrl_handler_free(&qcam->hdl); - kfree(qcam); - return NULL; - } - - strlcpy(qcam->vdev.name, "Colour QuickCam", sizeof(qcam->vdev.name)); - qcam->vdev.v4l2_dev = v4l2_dev; - qcam->vdev.fops = &qcam_fops; - qcam->vdev.ioctl_ops = &qcam_ioctl_ops; - qcam->vdev.release = video_device_release_empty; - qcam->vdev.ctrl_handler = &qcam->hdl; - video_set_drvdata(&qcam->vdev, qcam); - - mutex_init(&qcam->lock); - qcam->width = qcam->ccd_width = 320; - qcam->height = qcam->ccd_height = 240; - qcam->mode = QC_MILLIONS | QC_DECIMATION_1; - qcam->contrast = 192; - qcam->brightness = 240; - qcam->whitebal = 128; - qcam->top = 1; - qcam->left = 14; - return qcam; -} - -static int init_cqcam(struct parport *port) -{ - struct qcam *qcam; - struct v4l2_device *v4l2_dev; - - if (parport[0] != -1) { - /* The user gave specific instructions */ - int i, found = 0; - - for (i = 0; i < MAX_CAMS && parport[i] != -1; i++) { - if (parport[0] == port->number) - found = 1; - } - if (!found) - return -ENODEV; - } - - if (num_cams == MAX_CAMS) - return -ENOSPC; - - qcam = qcam_init(port); - if (qcam == NULL) - return -ENODEV; - - v4l2_dev = &qcam->v4l2_dev; - - parport_claim_or_block(qcam->pdev); - - qc_reset(qcam); - - if (probe && qc_detect(qcam) == 0) { - parport_release(qcam->pdev); - parport_unregister_device(qcam->pdev); - kfree(qcam); - return -ENODEV; - } - - qc_setup(qcam); - - parport_release(qcam->pdev); - - if (video_register_device(&qcam->vdev, VFL_TYPE_GRABBER, video_nr) < 0) { - v4l2_err(v4l2_dev, "Unable to register Colour QuickCam on %s\n", - qcam->pport->name); - parport_unregister_device(qcam->pdev); - kfree(qcam); - return -ENODEV; - } - - v4l2_info(v4l2_dev, "%s: Colour QuickCam found on %s\n", - video_device_node_name(&qcam->vdev), qcam->pport->name); - - qcams[num_cams++] = qcam; - - return 0; -} - -static void close_cqcam(struct qcam *qcam) -{ - video_unregister_device(&qcam->vdev); - v4l2_ctrl_handler_free(&qcam->hdl); - parport_unregister_device(qcam->pdev); - kfree(qcam); -} - -static void cq_attach(struct parport *port) -{ - init_cqcam(port); -} - -static void cq_detach(struct parport *port) -{ - /* Write this some day. */ -} - -static struct parport_driver cqcam_driver = { - .name = "cqcam", - .attach = cq_attach, - .detach = cq_detach, -}; - -static int __init cqcam_init(void) -{ - printk(KERN_INFO BANNER "\n"); - - return parport_register_driver(&cqcam_driver); -} - -static void __exit cqcam_cleanup(void) -{ - unsigned int i; - - for (i = 0; i < num_cams; i++) - close_cqcam(qcams[i]); - - parport_unregister_driver(&cqcam_driver); -} - -MODULE_AUTHOR("Philip Blundell "); -MODULE_DESCRIPTION(BANNER); -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.4"); - -module_init(cqcam_init); -module_exit(cqcam_cleanup); diff --git a/drivers/staging/media/parport/pms.c b/drivers/staging/media/parport/pms.c deleted file mode 100644 index e6b497528cea..000000000000 --- a/drivers/staging/media/parport/pms.c +++ /dev/null @@ -1,1156 +0,0 @@ -/* - * Media Vision Pro Movie Studio - * or - * "all you need is an I2C bus some RAM and a prayer" - * - * This draws heavily on code - * - * (c) Wolfgang Koehler, wolf@first.gmd.de, Dec. 1994 - * Kiefernring 15 - * 14478 Potsdam, Germany - * - * Most of this code is directly derived from his userspace driver. - * His driver works so send any reports to alan@lxorguk.ukuu.org.uk - * unless the userspace driver also doesn't work for you... - * - * Changes: - * 25-11-2009 Hans Verkuil - * - converted to version 2 of the V4L API. - * 08/07/2003 Daniele Bellucci - * - pms_capture: report back -EFAULT - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.5"); - -#define MOTOROLA 1 -#define PHILIPS2 2 /* SAA7191 */ -#define PHILIPS1 3 -#define MVVMEMORYWIDTH 0x40 /* 512 bytes */ - -struct i2c_info { - u8 slave; - u8 sub; - u8 data; - u8 hits; -}; - -struct pms { - struct v4l2_device v4l2_dev; - struct video_device vdev; - struct v4l2_ctrl_handler hdl; - int height; - int width; - int depth; - int input; - struct mutex lock; - int i2c_count; - struct i2c_info i2cinfo[64]; - - int decoder; - int standard; /* 0 - auto 1 - ntsc 2 - pal 3 - secam */ - v4l2_std_id std; - int io; - int data; - void __iomem *mem; -}; - -/* - * I/O ports and Shared Memory - */ - -static int io_port = 0x250; -module_param(io_port, int, 0); - -static int mem_base = 0xc8000; -module_param(mem_base, int, 0); - -static int video_nr = -1; -module_param(video_nr, int, 0); - - -static inline void mvv_write(struct pms *dev, u8 index, u8 value) -{ - outw(index | (value << 8), dev->io); -} - -static inline u8 mvv_read(struct pms *dev, u8 index) -{ - outb(index, dev->io); - return inb(dev->data); -} - -static int pms_i2c_stat(struct pms *dev, u8 slave) -{ - int counter = 0; - int i; - - outb(0x28, dev->io); - - while ((inb(dev->data) & 0x01) == 0) - if (counter++ == 256) - break; - - while ((inb(dev->data) & 0x01) != 0) - if (counter++ == 256) - break; - - outb(slave, dev->io); - - counter = 0; - while ((inb(dev->data) & 0x01) == 0) - if (counter++ == 256) - break; - - while ((inb(dev->data) & 0x01) != 0) - if (counter++ == 256) - break; - - for (i = 0; i < 12; i++) { - char st = inb(dev->data); - - if ((st & 2) != 0) - return -1; - if ((st & 1) == 0) - break; - } - outb(0x29, dev->io); - return inb(dev->data); -} - -static int pms_i2c_write(struct pms *dev, u16 slave, u16 sub, u16 data) -{ - int skip = 0; - int count; - int i; - - for (i = 0; i < dev->i2c_count; i++) { - if ((dev->i2cinfo[i].slave == slave) && - (dev->i2cinfo[i].sub == sub)) { - if (dev->i2cinfo[i].data == data) - skip = 1; - dev->i2cinfo[i].data = data; - i = dev->i2c_count + 1; - } - } - - if (i == dev->i2c_count && dev->i2c_count < 64) { - dev->i2cinfo[dev->i2c_count].slave = slave; - dev->i2cinfo[dev->i2c_count].sub = sub; - dev->i2cinfo[dev->i2c_count].data = data; - dev->i2c_count++; - } - - if (skip) - return 0; - - mvv_write(dev, 0x29, sub); - mvv_write(dev, 0x2A, data); - mvv_write(dev, 0x28, slave); - - outb(0x28, dev->io); - - count = 0; - while ((inb(dev->data) & 1) == 0) - if (count > 255) - break; - while ((inb(dev->data) & 1) != 0) - if (count > 255) - break; - - count = inb(dev->data); - - if (count & 2) - return -1; - return count; -} - -static int pms_i2c_read(struct pms *dev, int slave, int sub) -{ - int i; - - for (i = 0; i < dev->i2c_count; i++) { - if (dev->i2cinfo[i].slave == slave && dev->i2cinfo[i].sub == sub) - return dev->i2cinfo[i].data; - } - return 0; -} - - -static void pms_i2c_andor(struct pms *dev, int slave, int sub, int and, int or) -{ - u8 tmp; - - tmp = pms_i2c_read(dev, slave, sub); - tmp = (tmp & and) | or; - pms_i2c_write(dev, slave, sub, tmp); -} - -/* - * Control functions - */ - - -static void pms_videosource(struct pms *dev, short source) -{ - switch (dev->decoder) { - case MOTOROLA: - break; - case PHILIPS2: - pms_i2c_andor(dev, 0x8a, 0x06, 0x7f, source ? 0x80 : 0); - break; - case PHILIPS1: - break; - } - mvv_write(dev, 0x2E, 0x31); - /* Was: mvv_write(dev, 0x2E, source ? 0x31 : 0x30); - But could not make this work correctly. Only Composite input - worked for me. */ -} - -static void pms_hue(struct pms *dev, short hue) -{ - switch (dev->decoder) { - case MOTOROLA: - pms_i2c_write(dev, 0x8a, 0x00, hue); - break; - case PHILIPS2: - pms_i2c_write(dev, 0x8a, 0x07, hue); - break; - case PHILIPS1: - pms_i2c_write(dev, 0x42, 0x07, hue); - break; - } -} - -static void pms_saturation(struct pms *dev, short sat) -{ - switch (dev->decoder) { - case MOTOROLA: - pms_i2c_write(dev, 0x8a, 0x00, sat); - break; - case PHILIPS1: - pms_i2c_write(dev, 0x42, 0x12, sat); - break; - } -} - - -static void pms_contrast(struct pms *dev, short contrast) -{ - switch (dev->decoder) { - case MOTOROLA: - pms_i2c_write(dev, 0x8a, 0x00, contrast); - break; - case PHILIPS1: - pms_i2c_write(dev, 0x42, 0x13, contrast); - break; - } -} - -static void pms_brightness(struct pms *dev, short brightness) -{ - switch (dev->decoder) { - case MOTOROLA: - pms_i2c_write(dev, 0x8a, 0x00, brightness); - pms_i2c_write(dev, 0x8a, 0x00, brightness); - pms_i2c_write(dev, 0x8a, 0x00, brightness); - break; - case PHILIPS1: - pms_i2c_write(dev, 0x42, 0x19, brightness); - break; - } -} - - -static void pms_format(struct pms *dev, short format) -{ - int target; - - dev->standard = format; - - if (dev->decoder == PHILIPS1) - target = 0x42; - else if (dev->decoder == PHILIPS2) - target = 0x8a; - else - return; - - switch (format) { - case 0: /* Auto */ - pms_i2c_andor(dev, target, 0x0d, 0xfe, 0x00); - pms_i2c_andor(dev, target, 0x0f, 0x3f, 0x80); - break; - case 1: /* NTSC */ - pms_i2c_andor(dev, target, 0x0d, 0xfe, 0x00); - pms_i2c_andor(dev, target, 0x0f, 0x3f, 0x40); - break; - case 2: /* PAL */ - pms_i2c_andor(dev, target, 0x0d, 0xfe, 0x00); - pms_i2c_andor(dev, target, 0x0f, 0x3f, 0x00); - break; - case 3: /* SECAM */ - pms_i2c_andor(dev, target, 0x0d, 0xfe, 0x01); - pms_i2c_andor(dev, target, 0x0f, 0x3f, 0x00); - break; - } -} - -#ifdef FOR_FUTURE_EXPANSION - -/* - * These features of the PMS card are not currently exposes. They - * could become a private v4l ioctl for PMSCONFIG or somesuch if - * people need it. We also don't yet use the PMS interrupt. - */ - -static void pms_hstart(struct pms *dev, short start) -{ - switch (dev->decoder) { - case PHILIPS1: - pms_i2c_write(dev, 0x8a, 0x05, start); - pms_i2c_write(dev, 0x8a, 0x18, start); - break; - case PHILIPS2: - pms_i2c_write(dev, 0x42, 0x05, start); - pms_i2c_write(dev, 0x42, 0x18, start); - break; - } -} - -/* - * Bandpass filters - */ - -static void pms_bandpass(struct pms *dev, short pass) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0x8a, 0x06, 0xcf, (pass & 0x03) << 4); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x06, 0xcf, (pass & 0x03) << 4); -} - -static void pms_antisnow(struct pms *dev, short snow) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0x8a, 0x06, 0xf3, (snow & 0x03) << 2); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x06, 0xf3, (snow & 0x03) << 2); -} - -static void pms_sharpness(struct pms *dev, short sharp) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0x8a, 0x06, 0xfc, sharp & 0x03); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x06, 0xfc, sharp & 0x03); -} - -static void pms_chromaagc(struct pms *dev, short agc) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0x8a, 0x0c, 0x9f, (agc & 0x03) << 5); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x0c, 0x9f, (agc & 0x03) << 5); -} - -static void pms_vertnoise(struct pms *dev, short noise) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0x8a, 0x10, 0xfc, noise & 3); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x10, 0xfc, noise & 3); -} - -static void pms_forcecolour(struct pms *dev, short colour) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0x8a, 0x0c, 0x7f, (colour & 1) << 7); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x0c, 0x7, (colour & 1) << 7); -} - -static void pms_antigamma(struct pms *dev, short gamma) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0xb8, 0x00, 0x7f, (gamma & 1) << 7); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x20, 0x7, (gamma & 1) << 7); -} - -static void pms_prefilter(struct pms *dev, short filter) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0x8a, 0x06, 0xbf, (filter & 1) << 6); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x06, 0xbf, (filter & 1) << 6); -} - -static void pms_hfilter(struct pms *dev, short filter) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0xb8, 0x04, 0x1f, (filter & 7) << 5); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x24, 0x1f, (filter & 7) << 5); -} - -static void pms_vfilter(struct pms *dev, short filter) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0xb8, 0x08, 0x9f, (filter & 3) << 5); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x28, 0x9f, (filter & 3) << 5); -} - -static void pms_killcolour(struct pms *dev, short colour) -{ - if (dev->decoder == PHILIPS2) { - pms_i2c_andor(dev, 0x8a, 0x08, 0x07, (colour & 0x1f) << 3); - pms_i2c_andor(dev, 0x8a, 0x09, 0x07, (colour & 0x1f) << 3); - } else if (dev->decoder == PHILIPS1) { - pms_i2c_andor(dev, 0x42, 0x08, 0x07, (colour & 0x1f) << 3); - pms_i2c_andor(dev, 0x42, 0x09, 0x07, (colour & 0x1f) << 3); - } -} - -static void pms_chromagain(struct pms *dev, short chroma) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_write(dev, 0x8a, 0x11, chroma); - else if (dev->decoder == PHILIPS1) - pms_i2c_write(dev, 0x42, 0x11, chroma); -} - - -static void pms_spacialcompl(struct pms *dev, short data) -{ - mvv_write(dev, 0x3b, data); -} - -static void pms_spacialcomph(struct pms *dev, short data) -{ - mvv_write(dev, 0x3a, data); -} - -static void pms_vstart(struct pms *dev, short start) -{ - mvv_write(dev, 0x16, start); - mvv_write(dev, 0x17, (start >> 8) & 0x01); -} - -#endif - -static void pms_secamcross(struct pms *dev, short cross) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0x8a, 0x0f, 0xdf, (cross & 1) << 5); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x0f, 0xdf, (cross & 1) << 5); -} - - -static void pms_swsense(struct pms *dev, short sense) -{ - if (dev->decoder == PHILIPS2) { - pms_i2c_write(dev, 0x8a, 0x0a, sense); - pms_i2c_write(dev, 0x8a, 0x0b, sense); - } else if (dev->decoder == PHILIPS1) { - pms_i2c_write(dev, 0x42, 0x0a, sense); - pms_i2c_write(dev, 0x42, 0x0b, sense); - } -} - - -static void pms_framerate(struct pms *dev, short frr) -{ - int fps = (dev->std & V4L2_STD_525_60) ? 30 : 25; - - if (frr == 0) - return; - fps = fps/frr; - mvv_write(dev, 0x14, 0x80 | fps); - mvv_write(dev, 0x15, 1); -} - -static void pms_vert(struct pms *dev, u8 deciden, u8 decinum) -{ - mvv_write(dev, 0x1c, deciden); /* Denominator */ - mvv_write(dev, 0x1d, decinum); /* Numerator */ -} - -/* - * Turn 16bit ratios into best small ratio the chipset can grok - */ - -static void pms_vertdeci(struct pms *dev, unsigned short decinum, unsigned short deciden) -{ - /* Knock it down by / 5 once */ - if (decinum % 5 == 0) { - deciden /= 5; - decinum /= 5; - } - /* - * 3's - */ - while (decinum % 3 == 0 && deciden % 3 == 0) { - deciden /= 3; - decinum /= 3; - } - /* - * 2's - */ - while (decinum % 2 == 0 && deciden % 2 == 0) { - decinum /= 2; - deciden /= 2; - } - /* - * Fudgyify - */ - while (deciden > 32) { - deciden /= 2; - decinum = (decinum + 1) / 2; - } - if (deciden == 32) - deciden--; - pms_vert(dev, deciden, decinum); -} - -static void pms_horzdeci(struct pms *dev, short decinum, short deciden) -{ - if (decinum <= 512) { - if (decinum % 5 == 0) { - decinum /= 5; - deciden /= 5; - } - } else { - decinum = 512; - deciden = 640; /* 768 would be ideal */ - } - - while (((decinum | deciden) & 1) == 0) { - decinum >>= 1; - deciden >>= 1; - } - while (deciden > 32) { - deciden >>= 1; - decinum = (decinum + 1) >> 1; - } - if (deciden == 32) - deciden--; - - mvv_write(dev, 0x24, 0x80 | deciden); - mvv_write(dev, 0x25, decinum); -} - -static void pms_resolution(struct pms *dev, short width, short height) -{ - int fg_height; - - fg_height = height; - if (fg_height > 280) - fg_height = 280; - - mvv_write(dev, 0x18, fg_height); - mvv_write(dev, 0x19, fg_height >> 8); - - if (dev->std & V4L2_STD_525_60) { - mvv_write(dev, 0x1a, 0xfc); - mvv_write(dev, 0x1b, 0x00); - if (height > fg_height) - pms_vertdeci(dev, 240, 240); - else - pms_vertdeci(dev, fg_height, 240); - } else { - mvv_write(dev, 0x1a, 0x1a); - mvv_write(dev, 0x1b, 0x01); - if (fg_height > 256) - pms_vertdeci(dev, 270, 270); - else - pms_vertdeci(dev, fg_height, 270); - } - mvv_write(dev, 0x12, 0); - mvv_write(dev, 0x13, MVVMEMORYWIDTH); - mvv_write(dev, 0x42, 0x00); - mvv_write(dev, 0x43, 0x00); - mvv_write(dev, 0x44, MVVMEMORYWIDTH); - - mvv_write(dev, 0x22, width + 8); - mvv_write(dev, 0x23, (width + 8) >> 8); - - if (dev->std & V4L2_STD_525_60) - pms_horzdeci(dev, width, 640); - else - pms_horzdeci(dev, width + 8, 768); - - mvv_write(dev, 0x30, mvv_read(dev, 0x30) & 0xfe); - mvv_write(dev, 0x08, mvv_read(dev, 0x08) | 0x01); - mvv_write(dev, 0x01, mvv_read(dev, 0x01) & 0xfd); - mvv_write(dev, 0x32, 0x00); - mvv_write(dev, 0x33, MVVMEMORYWIDTH); -} - - -/* - * Set Input - */ - -static void pms_vcrinput(struct pms *dev, short input) -{ - if (dev->decoder == PHILIPS2) - pms_i2c_andor(dev, 0x8a, 0x0d, 0x7f, (input & 1) << 7); - else if (dev->decoder == PHILIPS1) - pms_i2c_andor(dev, 0x42, 0x0d, 0x7f, (input & 1) << 7); -} - - -static int pms_capture(struct pms *dev, char __user *buf, int rgb555, int count) -{ - int y; - int dw = 2 * dev->width; - char *tmp; /* using a temp buffer is faster than direct */ - int cnt = 0; - int len = 0; - unsigned char r8 = 0x5; /* value for reg8 */ - - tmp = kmalloc(dw + 32, GFP_KERNEL); - if (!tmp) - return 0; - - if (rgb555) - r8 |= 0x20; /* else use untranslated rgb = 565 */ - mvv_write(dev, 0x08, r8); /* capture rgb555/565, init DRAM, PC enable */ - -/* printf("%d %d %d %d %d %x %x\n",width,height,voff,nom,den,mvv_buf); */ - - for (y = 0; y < dev->height; y++) { - writeb(0, dev->mem); /* synchronisiert neue Zeile */ - - /* - * This is in truth a fifo, be very careful as if you - * forgot this odd things will occur 8) - */ - - memcpy_fromio(tmp, dev->mem, dw + 32); /* discard 16 word */ - cnt -= dev->height; - while (cnt <= 0) { - /* - * Don't copy too far - */ - int dt = dw; - if (dt + len > count) - dt = count - len; - cnt += dev->height; - if (copy_to_user(buf, tmp + 32, dt)) - return len ? len : -EFAULT; - buf += dt; - len += dt; - } - } - kfree(tmp); - return len; -} - - -/* - * Video4linux interfacing - */ - -static int pms_querycap(struct file *file, void *priv, - struct v4l2_capability *vcap) -{ - struct pms *dev = video_drvdata(file); - - strlcpy(vcap->driver, dev->v4l2_dev.name, sizeof(vcap->driver)); - strlcpy(vcap->card, "Mediavision PMS", sizeof(vcap->card)); - snprintf(vcap->bus_info, sizeof(vcap->bus_info), - "ISA:%s", dev->v4l2_dev.name); - vcap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE; - vcap->capabilities = vcap->device_caps | V4L2_CAP_DEVICE_CAPS; - return 0; -} - -static int pms_enum_input(struct file *file, void *fh, struct v4l2_input *vin) -{ - static const char *inputs[4] = { - "Composite", - "S-Video", - "Composite (VCR)", - "S-Video (VCR)" - }; - - if (vin->index > 3) - return -EINVAL; - strlcpy(vin->name, inputs[vin->index], sizeof(vin->name)); - vin->type = V4L2_INPUT_TYPE_CAMERA; - vin->audioset = 0; - vin->tuner = 0; - vin->std = V4L2_STD_ALL; - vin->status = 0; - return 0; -} - -static int pms_g_input(struct file *file, void *fh, unsigned int *inp) -{ - struct pms *dev = video_drvdata(file); - - *inp = dev->input; - return 0; -} - -static int pms_s_input(struct file *file, void *fh, unsigned int inp) -{ - struct pms *dev = video_drvdata(file); - - if (inp > 3) - return -EINVAL; - - dev->input = inp; - pms_videosource(dev, inp & 1); - pms_vcrinput(dev, inp >> 1); - return 0; -} - -static int pms_g_std(struct file *file, void *fh, v4l2_std_id *std) -{ - struct pms *dev = video_drvdata(file); - - *std = dev->std; - return 0; -} - -static int pms_s_std(struct file *file, void *fh, v4l2_std_id std) -{ - struct pms *dev = video_drvdata(file); - int ret = 0; - - dev->std = std; - if (dev->std & V4L2_STD_NTSC) { - pms_framerate(dev, 30); - pms_secamcross(dev, 0); - pms_format(dev, 1); - } else if (dev->std & V4L2_STD_PAL) { - pms_framerate(dev, 25); - pms_secamcross(dev, 0); - pms_format(dev, 2); - } else if (dev->std & V4L2_STD_SECAM) { - pms_framerate(dev, 25); - pms_secamcross(dev, 1); - pms_format(dev, 2); - } else { - ret = -EINVAL; - } - /* - switch (v->mode) { - case VIDEO_MODE_AUTO: - pms_framerate(dev, 25); - pms_secamcross(dev, 0); - pms_format(dev, 0); - break; - }*/ - return ret; -} - -static int pms_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct pms *dev = container_of(ctrl->handler, struct pms, hdl); - int ret = 0; - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - pms_brightness(dev, ctrl->val); - break; - case V4L2_CID_CONTRAST: - pms_contrast(dev, ctrl->val); - break; - case V4L2_CID_SATURATION: - pms_saturation(dev, ctrl->val); - break; - case V4L2_CID_HUE: - pms_hue(dev, ctrl->val); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int pms_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct pms *dev = video_drvdata(file); - struct v4l2_pix_format *pix = &fmt->fmt.pix; - - pix->width = dev->width; - pix->height = dev->height; - pix->pixelformat = dev->width == 15 ? - V4L2_PIX_FMT_RGB555 : V4L2_PIX_FMT_RGB565; - pix->field = V4L2_FIELD_NONE; - pix->bytesperline = 2 * dev->width; - pix->sizeimage = 2 * dev->width * dev->height; - /* Just a guess */ - pix->colorspace = V4L2_COLORSPACE_SRGB; - return 0; -} - -static int pms_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct v4l2_pix_format *pix = &fmt->fmt.pix; - - if (pix->height < 16 || pix->height > 480) - return -EINVAL; - if (pix->width < 16 || pix->width > 640) - return -EINVAL; - if (pix->pixelformat != V4L2_PIX_FMT_RGB555 && - pix->pixelformat != V4L2_PIX_FMT_RGB565) - return -EINVAL; - pix->field = V4L2_FIELD_NONE; - pix->bytesperline = 2 * pix->width; - pix->sizeimage = 2 * pix->width * pix->height; - /* Just a guess */ - pix->colorspace = V4L2_COLORSPACE_SRGB; - return 0; -} - -static int pms_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct pms *dev = video_drvdata(file); - struct v4l2_pix_format *pix = &fmt->fmt.pix; - int ret = pms_try_fmt_vid_cap(file, fh, fmt); - - if (ret) - return ret; - dev->width = pix->width; - dev->height = pix->height; - dev->depth = (pix->pixelformat == V4L2_PIX_FMT_RGB555) ? 15 : 16; - pms_resolution(dev, dev->width, dev->height); - /* Ok we figured out what to use from our wide choice */ - return 0; -} - -static int pms_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt) -{ - static struct v4l2_fmtdesc formats[] = { - { 0, 0, 0, - "RGB 5:5:5", V4L2_PIX_FMT_RGB555, - { 0, 0, 0, 0 } - }, - { 1, 0, 0, - "RGB 5:6:5", V4L2_PIX_FMT_RGB565, - { 0, 0, 0, 0 } - }, - }; - enum v4l2_buf_type type = fmt->type; - - if (fmt->index > 1) - return -EINVAL; - - *fmt = formats[fmt->index]; - fmt->type = type; - return 0; -} - -static ssize_t pms_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos) -{ - struct pms *dev = video_drvdata(file); - int len; - - len = pms_capture(dev, buf, (dev->depth == 15), count); - return len; -} - -static unsigned int pms_poll(struct file *file, struct poll_table_struct *wait) -{ - struct v4l2_fh *fh = file->private_data; - unsigned int res = POLLIN | POLLRDNORM; - - if (v4l2_event_pending(fh)) - res |= POLLPRI; - poll_wait(file, &fh->wait, wait); - return res; -} - -static const struct v4l2_file_operations pms_fops = { - .owner = THIS_MODULE, - .open = v4l2_fh_open, - .release = v4l2_fh_release, - .poll = pms_poll, - .unlocked_ioctl = video_ioctl2, - .read = pms_read, -}; - -static const struct v4l2_ioctl_ops pms_ioctl_ops = { - .vidioc_querycap = pms_querycap, - .vidioc_g_input = pms_g_input, - .vidioc_s_input = pms_s_input, - .vidioc_enum_input = pms_enum_input, - .vidioc_g_std = pms_g_std, - .vidioc_s_std = pms_s_std, - .vidioc_enum_fmt_vid_cap = pms_enum_fmt_vid_cap, - .vidioc_g_fmt_vid_cap = pms_g_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = pms_s_fmt_vid_cap, - .vidioc_try_fmt_vid_cap = pms_try_fmt_vid_cap, - .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, -}; - -/* - * Probe for and initialise the Mediavision PMS - */ - -static int init_mediavision(struct pms *dev) -{ - int idec, decst; - int i; - static const unsigned char i2c_defs[] = { - 0x4c, 0x30, 0x00, 0xe8, - 0xb6, 0xe2, 0x00, 0x00, - 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x78, 0x98, - 0x00, 0x00, 0x00, 0x00, - 0x34, 0x0a, 0xf4, 0xce, - 0xe4 - }; - - dev->mem = ioremap(mem_base, 0x800); - if (!dev->mem) - return -ENOMEM; - - if (!request_region(0x9a01, 1, "Mediavision PMS config")) { - printk(KERN_WARNING "mediavision: unable to detect: 0x9a01 in use.\n"); - iounmap(dev->mem); - return -EBUSY; - } - if (!request_region(dev->io, 3, "Mediavision PMS")) { - printk(KERN_WARNING "mediavision: I/O port %d in use.\n", dev->io); - release_region(0x9a01, 1); - iounmap(dev->mem); - return -EBUSY; - } - outb(0xb8, 0x9a01); /* Unlock */ - outb(dev->io >> 4, 0x9a01); /* Set IO port */ - - - decst = pms_i2c_stat(dev, 0x43); - - if (decst != -1) - idec = 2; - else if (pms_i2c_stat(dev, 0xb9) != -1) - idec = 3; - else if (pms_i2c_stat(dev, 0x8b) != -1) - idec = 1; - else - idec = 0; - - printk(KERN_INFO "PMS type is %d\n", idec); - if (idec == 0) { - release_region(dev->io, 3); - release_region(0x9a01, 1); - iounmap(dev->mem); - return -ENODEV; - } - - /* - * Ok we have a PMS of some sort - */ - - mvv_write(dev, 0x04, mem_base >> 12); /* Set the memory area */ - - /* Ok now load the defaults */ - - for (i = 0; i < 0x19; i++) { - if (i2c_defs[i] == 0xff) - pms_i2c_andor(dev, 0x8a, i, 0x07, 0x00); - else - pms_i2c_write(dev, 0x8a, i, i2c_defs[i]); - } - - pms_i2c_write(dev, 0xb8, 0x00, 0x12); - pms_i2c_write(dev, 0xb8, 0x04, 0x00); - pms_i2c_write(dev, 0xb8, 0x07, 0x00); - pms_i2c_write(dev, 0xb8, 0x08, 0x00); - pms_i2c_write(dev, 0xb8, 0x09, 0xff); - pms_i2c_write(dev, 0xb8, 0x0a, 0x00); - pms_i2c_write(dev, 0xb8, 0x0b, 0x10); - pms_i2c_write(dev, 0xb8, 0x10, 0x03); - - mvv_write(dev, 0x01, 0x00); - mvv_write(dev, 0x05, 0xa0); - mvv_write(dev, 0x08, 0x25); - mvv_write(dev, 0x09, 0x00); - mvv_write(dev, 0x0a, 0x20 | MVVMEMORYWIDTH); - - mvv_write(dev, 0x10, 0x02); - mvv_write(dev, 0x1e, 0x0c); - mvv_write(dev, 0x1f, 0x03); - mvv_write(dev, 0x26, 0x06); - - mvv_write(dev, 0x2b, 0x00); - mvv_write(dev, 0x2c, 0x20); - mvv_write(dev, 0x2d, 0x00); - mvv_write(dev, 0x2f, 0x70); - mvv_write(dev, 0x32, 0x00); - mvv_write(dev, 0x33, MVVMEMORYWIDTH); - mvv_write(dev, 0x34, 0x00); - mvv_write(dev, 0x35, 0x00); - mvv_write(dev, 0x3a, 0x80); - mvv_write(dev, 0x3b, 0x10); - mvv_write(dev, 0x20, 0x00); - mvv_write(dev, 0x21, 0x00); - mvv_write(dev, 0x30, 0x22); - return 0; -} - -/* - * Initialization and module stuff - */ - -#ifndef MODULE -static int enable; -module_param(enable, int, 0); -#endif - -static const struct v4l2_ctrl_ops pms_ctrl_ops = { - .s_ctrl = pms_s_ctrl, -}; - -static int pms_probe(struct device *pdev, unsigned int card) -{ - struct pms *dev; - struct v4l2_device *v4l2_dev; - struct v4l2_ctrl_handler *hdl; - int res; - -#ifndef MODULE - if (!enable) { - pr_err("PMS: not enabled, use pms.enable=1 to probe\n"); - return -ENODEV; - } -#endif - - dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (dev == NULL) - return -ENOMEM; - - dev->decoder = PHILIPS2; - dev->io = io_port; - dev->data = io_port + 1; - v4l2_dev = &dev->v4l2_dev; - hdl = &dev->hdl; - - res = v4l2_device_register(pdev, v4l2_dev); - if (res < 0) { - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - goto free_dev; - } - v4l2_info(v4l2_dev, "Mediavision Pro Movie Studio driver 0.05\n"); - - res = init_mediavision(dev); - if (res) { - v4l2_err(v4l2_dev, "Board not found.\n"); - goto free_io; - } - - v4l2_ctrl_handler_init(hdl, 4); - v4l2_ctrl_new_std(hdl, &pms_ctrl_ops, - V4L2_CID_BRIGHTNESS, 0, 255, 1, 139); - v4l2_ctrl_new_std(hdl, &pms_ctrl_ops, - V4L2_CID_CONTRAST, 0, 255, 1, 70); - v4l2_ctrl_new_std(hdl, &pms_ctrl_ops, - V4L2_CID_SATURATION, 0, 255, 1, 64); - v4l2_ctrl_new_std(hdl, &pms_ctrl_ops, - V4L2_CID_HUE, 0, 255, 1, 0); - if (hdl->error) { - res = hdl->error; - goto free_hdl; - } - - mutex_init(&dev->lock); - strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); - dev->vdev.v4l2_dev = v4l2_dev; - dev->vdev.ctrl_handler = hdl; - dev->vdev.fops = &pms_fops; - dev->vdev.ioctl_ops = &pms_ioctl_ops; - dev->vdev.release = video_device_release_empty; - dev->vdev.lock = &dev->lock; - dev->vdev.tvnorms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM; - video_set_drvdata(&dev->vdev, dev); - dev->std = V4L2_STD_NTSC_M; - dev->height = 240; - dev->width = 320; - dev->depth = 16; - pms_swsense(dev, 75); - pms_resolution(dev, 320, 240); - pms_videosource(dev, 0); - pms_vcrinput(dev, 0); - v4l2_ctrl_handler_setup(hdl); - res = video_register_device(&dev->vdev, VFL_TYPE_GRABBER, video_nr); - if (res >= 0) - return 0; - -free_hdl: - v4l2_ctrl_handler_free(hdl); - v4l2_device_unregister(&dev->v4l2_dev); -free_io: - release_region(dev->io, 3); - release_region(0x9a01, 1); - iounmap(dev->mem); -free_dev: - kfree(dev); - return res; -} - -static int pms_remove(struct device *pdev, unsigned int card) -{ - struct pms *dev = dev_get_drvdata(pdev); - - video_unregister_device(&dev->vdev); - v4l2_ctrl_handler_free(&dev->hdl); - release_region(dev->io, 3); - release_region(0x9a01, 1); - iounmap(dev->mem); - return 0; -} - -static struct isa_driver pms_driver = { - .probe = pms_probe, - .remove = pms_remove, - .driver = { - .name = "pms", - }, -}; - -static int __init pms_init(void) -{ - return isa_register_driver(&pms_driver, 1); -} - -static void __exit pms_exit(void) -{ - isa_unregister_driver(&pms_driver); -} - -module_init(pms_init); -module_exit(pms_exit); diff --git a/drivers/staging/media/parport/w9966.c b/drivers/staging/media/parport/w9966.c deleted file mode 100644 index f7502f3a6a3c..000000000000 --- a/drivers/staging/media/parport/w9966.c +++ /dev/null @@ -1,980 +0,0 @@ -/* - Winbond w9966cf Webcam parport driver. - - Version 0.33 - - Copyright (C) 2001 Jakob Kemi - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ -/* - Supported devices: - *Lifeview FlyCam Supra (using the Philips saa7111a chip) - - Does any other model using the w9966 interface chip exist ? - - Todo: - - *Add a working EPP mode, since DMA ECP read isn't implemented - in the parport drivers. (That's why it's so sloow) - - *Add support for other ccd-control chips than the saa7111 - please send me feedback on what kind of chips you have. - - *Add proper probing. I don't know what's wrong with the IEEE1284 - parport drivers but (IEEE1284_MODE_NIBBLE|IEEE1284_DEVICE_ID) - and nibble read seems to be broken for some peripherals. - - *Add probing for onboard SRAM, port directions etc. (if possible) - - *Add support for the hardware compressed modes (maybe using v4l2) - - *Fix better support for the capture window (no skewed images, v4l - interface to capt. window) - - *Probably some bugs that I don't know of - - Please support me by sending feedback! - - Changes: - - Alan Cox: Removed RGB mode for kernel merge, added THIS_MODULE - and owner support for newer module locks -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/*#define DEBUG*/ /* Undef me for production */ - -#ifdef DEBUG -#define DPRINTF(x, a...) printk(KERN_DEBUG "W9966: %s(): "x, __func__ , ##a) -#else -#define DPRINTF(x...) -#endif - -/* - * Defines, simple typedefs etc. - */ - -#define W9966_DRIVERNAME "W9966CF Webcam" -#define W9966_MAXCAMS 4 /* Maximum number of cameras */ -#define W9966_RBUFFER 2048 /* Read buffer (must be an even number) */ -#define W9966_SRAMSIZE 131072 /* 128kb */ -#define W9966_SRAMID 0x02 /* check w9966cf.pdf */ - -/* Empirically determined window limits */ -#define W9966_WND_MIN_X 16 -#define W9966_WND_MIN_Y 14 -#define W9966_WND_MAX_X 705 -#define W9966_WND_MAX_Y 253 -#define W9966_WND_MAX_W (W9966_WND_MAX_X - W9966_WND_MIN_X) -#define W9966_WND_MAX_H (W9966_WND_MAX_Y - W9966_WND_MIN_Y) - -/* Keep track of our current state */ -#define W9966_STATE_PDEV 0x01 -#define W9966_STATE_CLAIMED 0x02 -#define W9966_STATE_VDEV 0x04 - -#define W9966_I2C_W_ID 0x48 -#define W9966_I2C_R_ID 0x49 -#define W9966_I2C_R_DATA 0x08 -#define W9966_I2C_R_CLOCK 0x04 -#define W9966_I2C_W_DATA 0x02 -#define W9966_I2C_W_CLOCK 0x01 - -struct w9966 { - struct v4l2_device v4l2_dev; - struct v4l2_ctrl_handler hdl; - unsigned char dev_state; - unsigned char i2c_state; - unsigned short ppmode; - struct parport *pport; - struct pardevice *pdev; - struct video_device vdev; - unsigned short width; - unsigned short height; - unsigned char brightness; - signed char contrast; - signed char color; - signed char hue; - struct mutex lock; -}; - -/* - * Module specific properties - */ - -MODULE_AUTHOR("Jakob Kemi "); -MODULE_DESCRIPTION("Winbond w9966cf WebCam driver (0.32)"); -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.33.1"); - -#ifdef MODULE -static char *pardev[] = {[0 ... W9966_MAXCAMS] = ""}; -#else -static char *pardev[] = {[0 ... W9966_MAXCAMS] = "aggressive"}; -#endif -module_param_array(pardev, charp, NULL, 0); -MODULE_PARM_DESC(pardev, "pardev: where to search for\n" - "\teach camera. 'aggressive' means brute-force search.\n" - "\tEg: >pardev=parport3,aggressive,parport2,parport1< would assign\n" - "\tcam 1 to parport3 and search every parport for cam 2 etc..."); - -static int parmode; -module_param(parmode, int, 0); -MODULE_PARM_DESC(parmode, "parmode: transfer mode (0=auto, 1=ecp, 2=epp"); - -static int video_nr = -1; -module_param(video_nr, int, 0); - -static struct w9966 w9966_cams[W9966_MAXCAMS]; - -/* - * Private function defines - */ - - -/* Set camera phase flags, so we know what to uninit when terminating */ -static inline void w9966_set_state(struct w9966 *cam, int mask, int val) -{ - cam->dev_state = (cam->dev_state & ~mask) ^ val; -} - -/* Get camera phase flags */ -static inline int w9966_get_state(struct w9966 *cam, int mask, int val) -{ - return ((cam->dev_state & mask) == val); -} - -/* Claim parport for ourself */ -static void w9966_pdev_claim(struct w9966 *cam) -{ - if (w9966_get_state(cam, W9966_STATE_CLAIMED, W9966_STATE_CLAIMED)) - return; - parport_claim_or_block(cam->pdev); - w9966_set_state(cam, W9966_STATE_CLAIMED, W9966_STATE_CLAIMED); -} - -/* Release parport for others to use */ -static void w9966_pdev_release(struct w9966 *cam) -{ - if (w9966_get_state(cam, W9966_STATE_CLAIMED, 0)) - return; - parport_release(cam->pdev); - w9966_set_state(cam, W9966_STATE_CLAIMED, 0); -} - -/* Read register from W9966 interface-chip - Expects a claimed pdev - -1 on error, else register data (byte) */ -static int w9966_read_reg(struct w9966 *cam, int reg) -{ - /* ECP, read, regtransfer, REG, REG, REG, REG, REG */ - const unsigned char addr = 0x80 | (reg & 0x1f); - unsigned char val; - - if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_ADDR) != 0) - return -1; - if (parport_write(cam->pport, &addr, 1) != 1) - return -1; - if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_DATA) != 0) - return -1; - if (parport_read(cam->pport, &val, 1) != 1) - return -1; - - return val; -} - -/* Write register to W9966 interface-chip - Expects a claimed pdev - -1 on error */ -static int w9966_write_reg(struct w9966 *cam, int reg, int data) -{ - /* ECP, write, regtransfer, REG, REG, REG, REG, REG */ - const unsigned char addr = 0xc0 | (reg & 0x1f); - const unsigned char val = data; - - if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_ADDR) != 0) - return -1; - if (parport_write(cam->pport, &addr, 1) != 1) - return -1; - if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_DATA) != 0) - return -1; - if (parport_write(cam->pport, &val, 1) != 1) - return -1; - - return 0; -} - -/* - * Ugly and primitive i2c protocol functions - */ - -/* Sets the data line on the i2c bus. - Expects a claimed pdev. */ -static void w9966_i2c_setsda(struct w9966 *cam, int state) -{ - if (state) - cam->i2c_state |= W9966_I2C_W_DATA; - else - cam->i2c_state &= ~W9966_I2C_W_DATA; - - w9966_write_reg(cam, 0x18, cam->i2c_state); - udelay(5); -} - -/* Get peripheral clock line - Expects a claimed pdev. */ -static int w9966_i2c_getscl(struct w9966 *cam) -{ - const unsigned char state = w9966_read_reg(cam, 0x18); - return ((state & W9966_I2C_R_CLOCK) > 0); -} - -/* Sets the clock line on the i2c bus. - Expects a claimed pdev. -1 on error */ -static int w9966_i2c_setscl(struct w9966 *cam, int state) -{ - unsigned long timeout; - - if (state) - cam->i2c_state |= W9966_I2C_W_CLOCK; - else - cam->i2c_state &= ~W9966_I2C_W_CLOCK; - - w9966_write_reg(cam, 0x18, cam->i2c_state); - udelay(5); - - /* we go to high, we also expect the peripheral to ack. */ - if (state) { - timeout = jiffies + 100; - while (!w9966_i2c_getscl(cam)) { - if (time_after(jiffies, timeout)) - return -1; - } - } - return 0; -} - -#if 0 -/* Get peripheral data line - Expects a claimed pdev. */ -static int w9966_i2c_getsda(struct w9966 *cam) -{ - const unsigned char state = w9966_read_reg(cam, 0x18); - return ((state & W9966_I2C_R_DATA) > 0); -} -#endif - -/* Write a byte with ack to the i2c bus. - Expects a claimed pdev. -1 on error */ -static int w9966_i2c_wbyte(struct w9966 *cam, int data) -{ - int i; - - for (i = 7; i >= 0; i--) { - w9966_i2c_setsda(cam, (data >> i) & 0x01); - - if (w9966_i2c_setscl(cam, 1) == -1) - return -1; - w9966_i2c_setscl(cam, 0); - } - - w9966_i2c_setsda(cam, 1); - - if (w9966_i2c_setscl(cam, 1) == -1) - return -1; - w9966_i2c_setscl(cam, 0); - - return 0; -} - -/* Read a data byte with ack from the i2c-bus - Expects a claimed pdev. -1 on error */ -#if 0 -static int w9966_i2c_rbyte(struct w9966 *cam) -{ - unsigned char data = 0x00; - int i; - - w9966_i2c_setsda(cam, 1); - - for (i = 0; i < 8; i++) { - if (w9966_i2c_setscl(cam, 1) == -1) - return -1; - data = data << 1; - if (w9966_i2c_getsda(cam)) - data |= 0x01; - - w9966_i2c_setscl(cam, 0); - } - return data; -} -#endif - -/* Read a register from the i2c device. - Expects claimed pdev. -1 on error */ -#if 0 -static int w9966_read_reg_i2c(struct w9966 *cam, int reg) -{ - int data; - - w9966_i2c_setsda(cam, 0); - w9966_i2c_setscl(cam, 0); - - if (w9966_i2c_wbyte(cam, W9966_I2C_W_ID) == -1 || - w9966_i2c_wbyte(cam, reg) == -1) - return -1; - - w9966_i2c_setsda(cam, 1); - if (w9966_i2c_setscl(cam, 1) == -1) - return -1; - w9966_i2c_setsda(cam, 0); - w9966_i2c_setscl(cam, 0); - - if (w9966_i2c_wbyte(cam, W9966_I2C_R_ID) == -1) - return -1; - data = w9966_i2c_rbyte(cam); - if (data == -1) - return -1; - - w9966_i2c_setsda(cam, 0); - - if (w9966_i2c_setscl(cam, 1) == -1) - return -1; - w9966_i2c_setsda(cam, 1); - - return data; -} -#endif - -/* Write a register to the i2c device. - Expects claimed pdev. -1 on error */ -static int w9966_write_reg_i2c(struct w9966 *cam, int reg, int data) -{ - w9966_i2c_setsda(cam, 0); - w9966_i2c_setscl(cam, 0); - - if (w9966_i2c_wbyte(cam, W9966_I2C_W_ID) == -1 || - w9966_i2c_wbyte(cam, reg) == -1 || - w9966_i2c_wbyte(cam, data) == -1) - return -1; - - w9966_i2c_setsda(cam, 0); - if (w9966_i2c_setscl(cam, 1) == -1) - return -1; - - w9966_i2c_setsda(cam, 1); - - return 0; -} - -/* Find a good length for capture window (used both for W and H) - A bit ugly but pretty functional. The capture length - have to match the downscale */ -static int w9966_findlen(int near, int size, int maxlen) -{ - int bestlen = size; - int besterr = abs(near - bestlen); - int len; - - for (len = size + 1; len < maxlen; len++) { - int err; - if (((64 * size) % len) != 0) - continue; - - err = abs(near - len); - - /* Only continue as long as we keep getting better values */ - if (err > besterr) - break; - - besterr = err; - bestlen = len; - } - - return bestlen; -} - -/* Modify capture window (if necessary) - and calculate downscaling - Return -1 on error */ -static int w9966_calcscale(int size, int min, int max, int *beg, int *end, unsigned char *factor) -{ - int maxlen = max - min; - int len = *end - *beg + 1; - int newlen = w9966_findlen(len, size, maxlen); - int err = newlen - len; - - /* Check for bad format */ - if (newlen > maxlen || newlen < size) - return -1; - - /* Set factor (6 bit fixed) */ - *factor = (64 * size) / newlen; - if (*factor == 64) - *factor = 0x00; /* downscale is disabled */ - else - *factor |= 0x80; /* set downscale-enable bit */ - - /* Modify old beginning and end */ - *beg -= err / 2; - *end += err - (err / 2); - - /* Move window if outside borders */ - if (*beg < min) { - *end += min - *beg; - *beg += min - *beg; - } - if (*end > max) { - *beg -= *end - max; - *end -= *end - max; - } - - return 0; -} - -/* Setup the cameras capture window etc. - Expects a claimed pdev - return -1 on error */ -static int w9966_setup(struct w9966 *cam, int x1, int y1, int x2, int y2, int w, int h) -{ - unsigned int i; - unsigned int enh_s, enh_e; - unsigned char scale_x, scale_y; - unsigned char regs[0x1c]; - unsigned char saa7111_regs[] = { - 0x21, 0x00, 0xd8, 0x23, 0x00, 0x80, 0x80, 0x00, - 0x88, 0x10, 0x80, 0x40, 0x40, 0x00, 0x01, 0x00, - 0x48, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x71, 0xe7, 0x00, 0x00, 0xc0 - }; - - - if (w * h * 2 > W9966_SRAMSIZE) { - DPRINTF("capture window exceeds SRAM size!.\n"); - w = 200; h = 160; /* Pick default values */ - } - - w &= ~0x1; - if (w < 2) - w = 2; - if (h < 1) - h = 1; - if (w > W9966_WND_MAX_W) - w = W9966_WND_MAX_W; - if (h > W9966_WND_MAX_H) - h = W9966_WND_MAX_H; - - cam->width = w; - cam->height = h; - - enh_s = 0; - enh_e = w * h * 2; - - /* Modify capture window if necessary and calculate downscaling */ - if (w9966_calcscale(w, W9966_WND_MIN_X, W9966_WND_MAX_X, &x1, &x2, &scale_x) != 0 || - w9966_calcscale(h, W9966_WND_MIN_Y, W9966_WND_MAX_Y, &y1, &y2, &scale_y) != 0) - return -1; - - DPRINTF("%dx%d, x: %d<->%d, y: %d<->%d, sx: %d/64, sy: %d/64.\n", - w, h, x1, x2, y1, y2, scale_x & ~0x80, scale_y & ~0x80); - - /* Setup registers */ - regs[0x00] = 0x00; /* Set normal operation */ - regs[0x01] = 0x18; /* Capture mode */ - regs[0x02] = scale_y; /* V-scaling */ - regs[0x03] = scale_x; /* H-scaling */ - - /* Capture window */ - regs[0x04] = (x1 & 0x0ff); /* X-start (8 low bits) */ - regs[0x05] = (x1 & 0x300)>>8; /* X-start (2 high bits) */ - regs[0x06] = (y1 & 0x0ff); /* Y-start (8 low bits) */ - regs[0x07] = (y1 & 0x300)>>8; /* Y-start (2 high bits) */ - regs[0x08] = (x2 & 0x0ff); /* X-end (8 low bits) */ - regs[0x09] = (x2 & 0x300)>>8; /* X-end (2 high bits) */ - regs[0x0a] = (y2 & 0x0ff); /* Y-end (8 low bits) */ - - regs[0x0c] = W9966_SRAMID; /* SRAM-banks (1x 128kb) */ - - /* Enhancement layer */ - regs[0x0d] = (enh_s & 0x000ff); /* Enh. start (0-7) */ - regs[0x0e] = (enh_s & 0x0ff00) >> 8; /* Enh. start (8-15) */ - regs[0x0f] = (enh_s & 0x70000) >> 16; /* Enh. start (16-17/18??) */ - regs[0x10] = (enh_e & 0x000ff); /* Enh. end (0-7) */ - regs[0x11] = (enh_e & 0x0ff00) >> 8; /* Enh. end (8-15) */ - regs[0x12] = (enh_e & 0x70000) >> 16; /* Enh. end (16-17/18??) */ - - /* Misc */ - regs[0x13] = 0x40; /* VEE control (raw 4:2:2) */ - regs[0x17] = 0x00; /* ??? */ - regs[0x18] = cam->i2c_state = 0x00; /* Serial bus */ - regs[0x19] = 0xff; /* I/O port direction control */ - regs[0x1a] = 0xff; /* I/O port data register */ - regs[0x1b] = 0x10; /* ??? */ - - /* SAA7111 chip settings */ - saa7111_regs[0x0a] = cam->brightness; - saa7111_regs[0x0b] = cam->contrast; - saa7111_regs[0x0c] = cam->color; - saa7111_regs[0x0d] = cam->hue; - - /* Reset (ECP-fifo & serial-bus) */ - if (w9966_write_reg(cam, 0x00, 0x03) == -1) - return -1; - - /* Write regs to w9966cf chip */ - for (i = 0; i < 0x1c; i++) - if (w9966_write_reg(cam, i, regs[i]) == -1) - return -1; - - /* Write regs to saa7111 chip */ - for (i = 0; i < 0x20; i++) - if (w9966_write_reg_i2c(cam, i, saa7111_regs[i]) == -1) - return -1; - - return 0; -} - -/* - * Video4linux interfacing - */ - -static int cam_querycap(struct file *file, void *priv, - struct v4l2_capability *vcap) -{ - struct w9966 *cam = video_drvdata(file); - - strlcpy(vcap->driver, cam->v4l2_dev.name, sizeof(vcap->driver)); - strlcpy(vcap->card, W9966_DRIVERNAME, sizeof(vcap->card)); - strlcpy(vcap->bus_info, "parport", sizeof(vcap->bus_info)); - vcap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE; - vcap->capabilities = vcap->device_caps | V4L2_CAP_DEVICE_CAPS; - return 0; -} - -static int cam_enum_input(struct file *file, void *fh, struct v4l2_input *vin) -{ - if (vin->index > 0) - return -EINVAL; - strlcpy(vin->name, "Camera", sizeof(vin->name)); - vin->type = V4L2_INPUT_TYPE_CAMERA; - vin->audioset = 0; - vin->tuner = 0; - vin->std = 0; - vin->status = 0; - return 0; -} - -static int cam_g_input(struct file *file, void *fh, unsigned int *inp) -{ - *inp = 0; - return 0; -} - -static int cam_s_input(struct file *file, void *fh, unsigned int inp) -{ - return (inp > 0) ? -EINVAL : 0; -} - -static int cam_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct w9966 *cam = - container_of(ctrl->handler, struct w9966, hdl); - int ret = 0; - - mutex_lock(&cam->lock); - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - cam->brightness = ctrl->val; - break; - case V4L2_CID_CONTRAST: - cam->contrast = ctrl->val; - break; - case V4L2_CID_SATURATION: - cam->color = ctrl->val; - break; - case V4L2_CID_HUE: - cam->hue = ctrl->val; - break; - default: - ret = -EINVAL; - break; - } - - if (ret == 0) { - w9966_pdev_claim(cam); - - if (w9966_write_reg_i2c(cam, 0x0a, cam->brightness) == -1 || - w9966_write_reg_i2c(cam, 0x0b, cam->contrast) == -1 || - w9966_write_reg_i2c(cam, 0x0c, cam->color) == -1 || - w9966_write_reg_i2c(cam, 0x0d, cam->hue) == -1) { - ret = -EIO; - } - - w9966_pdev_release(cam); - } - mutex_unlock(&cam->lock); - return ret; -} - -static int cam_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct w9966 *cam = video_drvdata(file); - struct v4l2_pix_format *pix = &fmt->fmt.pix; - - pix->width = cam->width; - pix->height = cam->height; - pix->pixelformat = V4L2_PIX_FMT_YUYV; - pix->field = V4L2_FIELD_NONE; - pix->bytesperline = 2 * cam->width; - pix->sizeimage = 2 * cam->width * cam->height; - /* Just a guess */ - pix->colorspace = V4L2_COLORSPACE_SMPTE170M; - return 0; -} - -static int cam_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct v4l2_pix_format *pix = &fmt->fmt.pix; - - if (pix->width < 2) - pix->width = 2; - if (pix->height < 1) - pix->height = 1; - if (pix->width > W9966_WND_MAX_W) - pix->width = W9966_WND_MAX_W; - if (pix->height > W9966_WND_MAX_H) - pix->height = W9966_WND_MAX_H; - pix->pixelformat = V4L2_PIX_FMT_YUYV; - pix->field = V4L2_FIELD_NONE; - pix->bytesperline = 2 * pix->width; - pix->sizeimage = 2 * pix->width * pix->height; - /* Just a guess */ - pix->colorspace = V4L2_COLORSPACE_SMPTE170M; - return 0; -} - -static int cam_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) -{ - struct w9966 *cam = video_drvdata(file); - struct v4l2_pix_format *pix = &fmt->fmt.pix; - int ret = cam_try_fmt_vid_cap(file, fh, fmt); - - if (ret) - return ret; - - mutex_lock(&cam->lock); - /* Update camera regs */ - w9966_pdev_claim(cam); - ret = w9966_setup(cam, 0, 0, 1023, 1023, pix->width, pix->height); - w9966_pdev_release(cam); - mutex_unlock(&cam->lock); - return ret; -} - -static int cam_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt) -{ - static struct v4l2_fmtdesc formats[] = { - { 0, 0, 0, - "YUV 4:2:2", V4L2_PIX_FMT_YUYV, - { 0, 0, 0, 0 } - }, - }; - enum v4l2_buf_type type = fmt->type; - - if (fmt->index > 0) - return -EINVAL; - - *fmt = formats[fmt->index]; - fmt->type = type; - return 0; -} - -/* Capture data */ -static ssize_t w9966_v4l_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos) -{ - struct w9966 *cam = video_drvdata(file); - unsigned char addr = 0xa0; /* ECP, read, CCD-transfer, 00000 */ - unsigned char __user *dest = (unsigned char __user *)buf; - unsigned long dleft = count; - unsigned char *tbuf; - - /* Why would anyone want more than this?? */ - if (count > cam->width * cam->height * 2) - return -EINVAL; - - mutex_lock(&cam->lock); - w9966_pdev_claim(cam); - w9966_write_reg(cam, 0x00, 0x02); /* Reset ECP-FIFO buffer */ - w9966_write_reg(cam, 0x00, 0x00); /* Return to normal operation */ - w9966_write_reg(cam, 0x01, 0x98); /* Enable capture */ - - /* write special capture-addr and negotiate into data transfer */ - if ((parport_negotiate(cam->pport, cam->ppmode|IEEE1284_ADDR) != 0) || - (parport_write(cam->pport, &addr, 1) != 1) || - (parport_negotiate(cam->pport, cam->ppmode|IEEE1284_DATA) != 0)) { - w9966_pdev_release(cam); - mutex_unlock(&cam->lock); - return -EFAULT; - } - - tbuf = kmalloc(W9966_RBUFFER, GFP_KERNEL); - if (tbuf == NULL) { - count = -ENOMEM; - goto out; - } - - while (dleft > 0) { - unsigned long tsize = (dleft > W9966_RBUFFER) ? W9966_RBUFFER : dleft; - - if (parport_read(cam->pport, tbuf, tsize) < tsize) { - count = -EFAULT; - goto out; - } - if (copy_to_user(dest, tbuf, tsize) != 0) { - count = -EFAULT; - goto out; - } - dest += tsize; - dleft -= tsize; - } - - w9966_write_reg(cam, 0x01, 0x18); /* Disable capture */ - -out: - kfree(tbuf); - w9966_pdev_release(cam); - mutex_unlock(&cam->lock); - - return count; -} - -static const struct v4l2_file_operations w9966_fops = { - .owner = THIS_MODULE, - .open = v4l2_fh_open, - .release = v4l2_fh_release, - .poll = v4l2_ctrl_poll, - .unlocked_ioctl = video_ioctl2, - .read = w9966_v4l_read, -}; - -static const struct v4l2_ioctl_ops w9966_ioctl_ops = { - .vidioc_querycap = cam_querycap, - .vidioc_g_input = cam_g_input, - .vidioc_s_input = cam_s_input, - .vidioc_enum_input = cam_enum_input, - .vidioc_enum_fmt_vid_cap = cam_enum_fmt_vid_cap, - .vidioc_g_fmt_vid_cap = cam_g_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = cam_s_fmt_vid_cap, - .vidioc_try_fmt_vid_cap = cam_try_fmt_vid_cap, - .vidioc_log_status = v4l2_ctrl_log_status, - .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, -}; - -static const struct v4l2_ctrl_ops cam_ctrl_ops = { - .s_ctrl = cam_s_ctrl, -}; - - -/* Initialize camera device. Setup all internal flags, set a - default video mode, setup ccd-chip, register v4l device etc.. - Also used for 'probing' of hardware. - -1 on error */ -static int w9966_init(struct w9966 *cam, struct parport *port) -{ - struct v4l2_device *v4l2_dev = &cam->v4l2_dev; - - if (cam->dev_state != 0) - return -1; - - strlcpy(v4l2_dev->name, "w9966", sizeof(v4l2_dev->name)); - - if (v4l2_device_register(NULL, v4l2_dev) < 0) { - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return -1; - } - - v4l2_ctrl_handler_init(&cam->hdl, 4); - v4l2_ctrl_new_std(&cam->hdl, &cam_ctrl_ops, - V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); - v4l2_ctrl_new_std(&cam->hdl, &cam_ctrl_ops, - V4L2_CID_CONTRAST, -64, 64, 1, 64); - v4l2_ctrl_new_std(&cam->hdl, &cam_ctrl_ops, - V4L2_CID_SATURATION, -64, 64, 1, 64); - v4l2_ctrl_new_std(&cam->hdl, &cam_ctrl_ops, - V4L2_CID_HUE, -128, 127, 1, 0); - if (cam->hdl.error) { - v4l2_err(v4l2_dev, "couldn't register controls\n"); - return -1; - } - cam->pport = port; - cam->brightness = 128; - cam->contrast = 64; - cam->color = 64; - cam->hue = 0; - - /* Select requested transfer mode */ - switch (parmode) { - default: /* Auto-detect (priority: hw-ecp, hw-epp, sw-ecp) */ - case 0: - if (port->modes & PARPORT_MODE_ECP) - cam->ppmode = IEEE1284_MODE_ECP; - else if (port->modes & PARPORT_MODE_EPP) - cam->ppmode = IEEE1284_MODE_EPP; - else - cam->ppmode = IEEE1284_MODE_ECP; - break; - case 1: /* hw- or sw-ecp */ - cam->ppmode = IEEE1284_MODE_ECP; - break; - case 2: /* hw- or sw-epp */ - cam->ppmode = IEEE1284_MODE_EPP; - break; - } - - /* Tell the parport driver that we exists */ - cam->pdev = parport_register_device(port, "w9966", NULL, NULL, NULL, 0, NULL); - if (cam->pdev == NULL) { - DPRINTF("parport_register_device() failed\n"); - return -1; - } - w9966_set_state(cam, W9966_STATE_PDEV, W9966_STATE_PDEV); - - w9966_pdev_claim(cam); - - /* Setup a default capture mode */ - if (w9966_setup(cam, 0, 0, 1023, 1023, 200, 160) != 0) { - DPRINTF("w9966_setup() failed.\n"); - return -1; - } - - w9966_pdev_release(cam); - - /* Fill in the video_device struct and register us to v4l */ - strlcpy(cam->vdev.name, W9966_DRIVERNAME, sizeof(cam->vdev.name)); - cam->vdev.v4l2_dev = v4l2_dev; - cam->vdev.fops = &w9966_fops; - cam->vdev.ioctl_ops = &w9966_ioctl_ops; - cam->vdev.release = video_device_release_empty; - cam->vdev.ctrl_handler = &cam->hdl; - video_set_drvdata(&cam->vdev, cam); - - mutex_init(&cam->lock); - - if (video_register_device(&cam->vdev, VFL_TYPE_GRABBER, video_nr) < 0) - return -1; - - w9966_set_state(cam, W9966_STATE_VDEV, W9966_STATE_VDEV); - - /* All ok */ - v4l2_info(v4l2_dev, "Found and initialized a webcam on %s.\n", - cam->pport->name); - return 0; -} - - -/* Terminate everything gracefully */ -static void w9966_term(struct w9966 *cam) -{ - /* Unregister from v4l */ - if (w9966_get_state(cam, W9966_STATE_VDEV, W9966_STATE_VDEV)) { - video_unregister_device(&cam->vdev); - w9966_set_state(cam, W9966_STATE_VDEV, 0); - } - - v4l2_ctrl_handler_free(&cam->hdl); - - /* Terminate from IEEE1284 mode and release pdev block */ - if (w9966_get_state(cam, W9966_STATE_PDEV, W9966_STATE_PDEV)) { - w9966_pdev_claim(cam); - parport_negotiate(cam->pport, IEEE1284_MODE_COMPAT); - w9966_pdev_release(cam); - } - - /* Unregister from parport */ - if (w9966_get_state(cam, W9966_STATE_PDEV, W9966_STATE_PDEV)) { - parport_unregister_device(cam->pdev); - w9966_set_state(cam, W9966_STATE_PDEV, 0); - } - memset(cam, 0, sizeof(*cam)); -} - - -/* Called once for every parport on init */ -static void w9966_attach(struct parport *port) -{ - int i; - - for (i = 0; i < W9966_MAXCAMS; i++) { - if (w9966_cams[i].dev_state != 0) /* Cam is already assigned */ - continue; - if (strcmp(pardev[i], "aggressive") == 0 || strcmp(pardev[i], port->name) == 0) { - if (w9966_init(&w9966_cams[i], port) != 0) - w9966_term(&w9966_cams[i]); - break; /* return */ - } - } -} - -/* Called once for every parport on termination */ -static void w9966_detach(struct parport *port) -{ - int i; - - for (i = 0; i < W9966_MAXCAMS; i++) - if (w9966_cams[i].dev_state != 0 && w9966_cams[i].pport == port) - w9966_term(&w9966_cams[i]); -} - - -static struct parport_driver w9966_ppd = { - .name = W9966_DRIVERNAME, - .attach = w9966_attach, - .detach = w9966_detach, -}; - -/* Module entry point */ -static int __init w9966_mod_init(void) -{ - int i; - - for (i = 0; i < W9966_MAXCAMS; i++) - w9966_cams[i].dev_state = 0; - - return parport_register_driver(&w9966_ppd); -} - -/* Module cleanup */ -static void __exit w9966_mod_term(void) -{ - parport_unregister_driver(&w9966_ppd); -} - -module_init(w9966_mod_init); -module_exit(w9966_mod_term); -- cgit v1.2.3 From eb6725d03027a9375a25d3089b48be1b8c845972 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Thu, 29 Jan 2015 15:21:56 -0800 Subject: MAINTAINERS: fix git repositories for Broadcom SoCs Fix the git repositories URLs for Broadcom SoCs, git.github.com/broadcom/ is not valid, but github.com/broadcom is, fix that where relevant. Signed-off-by: Florian Fainelli --- MAINTAINERS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index b70f687ec2f4..cbff8262926b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2124,7 +2124,7 @@ F: arch/arm/boot/dts/bcm470* BROADCOM BCM63XX ARM ARCHITECTURE M: Florian Fainelli L: linux-arm-kernel@lists.infradead.org -T: git git://git.github.com/broadcom/arm-bcm63xx.git +T: git git://github.com/broadcom/arm-bcm63xx.git S: Maintained F: arch/arm/mach-bcm/bcm63xx.c F: arch/arm/include/debug/bcm63xx.S @@ -2141,7 +2141,7 @@ M: Brian Norris M: Gregory Fong M: Florian Fainelli L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) -T: git git://git.github.com/broadcom/stblinux.git +T: git git://github.com/broadcom/stblinux.git S: Maintained F: arch/arm/mach-bcm/*brcmstb* F: arch/arm/boot/dts/bcm7*.dts* @@ -2151,7 +2151,7 @@ BROADCOM BMIPS MIPS ARCHITECTURE M: Kevin Cernekee M: Florian Fainelli L: linux-mips@linux-mips.org -T: git git://git.github.com/broadcom/stblinux.git +T: git git://github.com/broadcom/stblinux.git S: Maintained F: arch/mips/bmips/* F: arch/mips/include/asm/mach-bmips/* @@ -2194,7 +2194,7 @@ M: Ray Jui M: Scott Branden L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) L: bcm-kernel-feedback-list@broadcom.com -T: git git://git.github.com/broadcom/cygnus-linux.git +T: git git://github.com/broadcom/cygnus-linux.git S: Maintained N: iproc N: cygnus -- cgit v1.2.3 From 1f141f6b2955f2b5556ca9522c033c3e51a95bca Mon Sep 17 00:00:00 2001 From: Ismael Luceno Date: Tue, 20 Jan 2015 11:43:50 -0300 Subject: [media] MAINTAINERS: Update solo6x10 entry Re-add Ismael Luceno as co-maintainer (with personal email address). Signed-off-by: Ismael Luceno Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3db56b8f90fc..c4124f105c84 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8900,6 +8900,7 @@ SOFTLOGIC 6x10 MPEG CODEC M: Bluecherry Maintainers M: Andrey Utkin M: Andrey Utkin +M: Ismael Luceno L: linux-media@vger.kernel.org S: Supported F: drivers/media/pci/solo6x10/ -- cgit v1.2.3 From 614b438441fc56e384ab9ec9a836e1cb5616ea12 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 23 Jan 2015 12:52:34 -0300 Subject: [media] Add MAINTAINERS entry for the adv7180 Add myself as the maintainer for the adv7180 video subdev driver. Signed-off-by: Lars-Peter Clausen Acked-by: Federico Vaga Acked-by: Hans Verkuil Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index c4124f105c84..becb274751fa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -659,6 +659,13 @@ L: linux-media@vger.kernel.org S: Maintained F: drivers/media/i2c/ad9389b* +ANALOG DEVICES INC ADV7180 DRIVER +M: Lars-Peter Clausen +L: linux-media@vger.kernel.org +W: http://ez.analog.com/community/linux-device-drivers +S: Supported +F: drivers/media/i2c/adv7180.c + ANALOG DEVICES INC ADV7511 DRIVER M: Hans Verkuil L: linux-media@vger.kernel.org -- cgit v1.2.3 From 1fb200d6d42c67678bfa98a7d122dfa61f8cbb8d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Jan 2015 18:23:06 +0200 Subject: dmaengine: dw: update MAINTAINERS file This is a follow up to the previously done changes in the layout of the driver files. We now have an additional file include/linux/dma/dw.h which is missed in the MAINTAINERS data base. Fixes: 3d588f83e4d6 (dmaengine: dw: split dma-dw.h to platform and private parts) Signed-off-by: Andy Shevchenko Signed-off-by: Vinod Koul --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..59c346315110 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8324,6 +8324,7 @@ SYNOPSYS DESIGNWARE DMAC DRIVER M: Viresh Kumar M: Andy Shevchenko S: Maintained +F: include/linux/dma/dw.h F: include/linux/platform_data/dma-dw.h F: drivers/dma/dw/ -- cgit v1.2.3 From 0e3b137fbf0f4ab901de58fcac7edb12922daa08 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Tue, 3 Feb 2015 17:13:58 -0500 Subject: NFS: Add Anna Schumaker as co-maintainer for the NFS client Anna has essentially been performing the duties of co-maintainer for the past several years. In recognition of those efforts, I'd like to add her to the maintainers file. Cc: Anna Schumaker Signed-off-by: Trond Myklebust --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2fa385321245..b30d937c3ec8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6682,6 +6682,7 @@ F: Documentation/devicetree/bindings/net/nfc/ NFS, SUNRPC, AND LOCKD CLIENTS M: Trond Myklebust +M: Anna Schumaker L: linux-nfs@vger.kernel.org W: http://client.linux-nfs.org T: git git://git.linux-nfs.org/projects/trondmy/linux-nfs.git -- cgit v1.2.3 From 4d8e2cef25c3c8c8fcb9a4b7b5cf5bba771a43a7 Mon Sep 17 00:00:00 2001 From: Han Xu Date: Fri, 16 Jan 2015 03:29:17 +0800 Subject: MAINTAINERS: add maintainer entry for FREESCALE QUAD SPI driver Add a maintainer entry for FREESCALE QUAD SPI driver and add myself as a maintainer. Signed-off-by: Han Xu Acked-by: Huang Shijie Signed-off-by: Brian Norris --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddb9ac8d32b3..cabe127fba76 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4028,6 +4028,12 @@ S: Maintained F: include/linux/platform_data/video-imxfb.h F: drivers/video/fbdev/imxfb.c +FREESCALE QUAD SPI DRIVER +M: Han Xu +L: linux-mtd@lists.infradead.org +S: Maintained +F: drivers/mtd/spi-nor/fsl-quadspi.c + FREESCALE SOC FS_ENET DRIVER M: Pantelis Antoniou M: Vitaly Bordug -- cgit v1.2.3 From f62092f6d77dfd9214ae753a24b76ba4ecd801d7 Mon Sep 17 00:00:00 2001 From: Ley Foon Tan Date: Wed, 4 Feb 2015 16:32:18 +0800 Subject: mailbox: Add Altera mailbox driver The Altera mailbox allows for interprocessor communication. It supports only one channel and work as either sender or receiver. Signed-off-by: Ley Foon Tan --- .../devicetree/bindings/mailbox/altera-mailbox.txt | 49 +++ MAINTAINERS | 6 + drivers/mailbox/Kconfig | 6 + drivers/mailbox/Makefile | 2 + drivers/mailbox/mailbox-altera.c | 388 +++++++++++++++++++++ 5 files changed, 451 insertions(+) create mode 100644 Documentation/devicetree/bindings/mailbox/altera-mailbox.txt create mode 100644 drivers/mailbox/mailbox-altera.c (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/mailbox/altera-mailbox.txt b/Documentation/devicetree/bindings/mailbox/altera-mailbox.txt new file mode 100644 index 000000000000..c2619797ce0c --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/altera-mailbox.txt @@ -0,0 +1,49 @@ +Altera Mailbox Driver +===================== + +Required properties: +- compatible : "altr,mailbox-1.0". +- reg : physical base address of the mailbox and length of + memory mapped region. +- #mbox-cells: Common mailbox binding property to identify the number + of cells required for the mailbox specifier. Should be 1. + +Optional properties: +- interrupt-parent : interrupt source phandle. +- interrupts : interrupt number. The interrupt specifier format + depends on the interrupt controller parent. + +Example: + mbox_tx: mailbox@0x100 { + compatible = "altr,mailbox-1.0"; + reg = <0x100 0x8>; + interrupt-parent = < &gic_0 >; + interrupts = <5>; + #mbox-cells = <1>; + }; + + mbox_rx: mailbox@0x200 { + compatible = "altr,mailbox-1.0"; + reg = <0x200 0x8>; + interrupt-parent = < &gic_0 >; + interrupts = <6>; + #mbox-cells = <1>; + }; + +Mailbox client +=============== +"mboxes" and the optional "mbox-names" (please see +Documentation/devicetree/bindings/mailbox/mailbox.txt for details). Each value +of the mboxes property should contain a phandle to the mailbox controller +device node and second argument is the channel index. It must be 0 (hardware +support only one channel).The equivalent "mbox-names" property value can be +used to give a name to the communication channel to be used by the client user. + +Example: + mclient0: mclient0@0x400 { + compatible = "client-1.0"; + reg = <0x400 0x10>; + mbox-names = "mbox-tx", "mbox-rx"; + mboxes = <&mbox_tx 0>, + <&mbox_rx 0>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index aaa039dee999..f2f0faf20695 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -563,6 +563,12 @@ S: Odd Fixes L: linux-alpha@vger.kernel.org F: arch/alpha/ +ALTERA MAILBOX DRIVER +M: Ley Foon Tan +L: nios2-dev@lists.rocketboards.org (moderated for non-subscribers) +S: Maintained +F: drivers/mailbox/mailbox-altera.c + ALTERA TRIPLE SPEED ETHERNET DRIVER M: Vince Bridgers L: netdev@vger.kernel.org diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index c04fed9eb15d..84325f267acf 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -45,4 +45,10 @@ config PCC states). Select this driver if your platform implements the PCC clients mentioned above. +config ALTERA_MBOX + tristate "Altera Mailbox" + help + An implementation of the Altera Mailbox soft core. It is used + to send message between processors. Say Y here if you want to use the + Altera mailbox support. endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index dd412c22208b..2e79231154cf 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_PL320_MBOX) += pl320-ipc.o obj-$(CONFIG_OMAP2PLUS_MBOX) += omap-mailbox.o obj-$(CONFIG_PCC) += pcc.o + +obj-$(CONFIG_ALTERA_MBOX) += mailbox-altera.o diff --git a/drivers/mailbox/mailbox-altera.c b/drivers/mailbox/mailbox-altera.c new file mode 100644 index 000000000000..a266265677d3 --- /dev/null +++ b/drivers/mailbox/mailbox-altera.c @@ -0,0 +1,388 @@ +/* + * Copyright Altera Corporation (C) 2013-2014. All rights reserved + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "altera-mailbox" + +#define MAILBOX_CMD_REG 0x00 +#define MAILBOX_PTR_REG 0x04 +#define MAILBOX_STS_REG 0x08 +#define MAILBOX_INTMASK_REG 0x0C + +#define INT_PENDING_MSK 0x1 +#define INT_SPACE_MSK 0x2 + +#define STS_PENDING_MSK 0x1 +#define STS_FULL_MSK 0x2 +#define STS_FULL_OFT 0x1 + +#define MBOX_PENDING(status) (((status) & STS_PENDING_MSK)) +#define MBOX_FULL(status) (((status) & STS_FULL_MSK) >> STS_FULL_OFT) + +enum altera_mbox_msg { + MBOX_CMD = 0, + MBOX_PTR, +}; + +#define MBOX_POLLING_MS 5 /* polling interval 5ms */ + +struct altera_mbox { + bool is_sender; /* 1-sender, 0-receiver */ + bool intr_mode; + int irq; + void __iomem *mbox_base; + struct device *dev; + struct mbox_controller controller; + + /* If the controller supports only RX polling mode */ + struct timer_list rxpoll_timer; +}; + +static struct altera_mbox *mbox_chan_to_altera_mbox(struct mbox_chan *chan) +{ + if (!chan || !chan->con_priv) + return NULL; + + return (struct altera_mbox *)chan->con_priv; +} + +static inline int altera_mbox_full(struct altera_mbox *mbox) +{ + u32 status; + + status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG); + return MBOX_FULL(status); +} + +static inline int altera_mbox_pending(struct altera_mbox *mbox) +{ + u32 status; + + status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG); + return MBOX_PENDING(status); +} + +static void altera_mbox_rx_intmask(struct altera_mbox *mbox, bool enable) +{ + u32 mask; + + mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG); + if (enable) + mask |= INT_PENDING_MSK; + else + mask &= ~INT_PENDING_MSK; + writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); +} + +static void altera_mbox_tx_intmask(struct altera_mbox *mbox, bool enable) +{ + u32 mask; + + mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG); + if (enable) + mask |= INT_SPACE_MSK; + else + mask &= ~INT_SPACE_MSK; + writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); +} + +static bool altera_mbox_is_sender(struct altera_mbox *mbox) +{ + u32 reg; + /* Write a magic number to PTR register and read back this register. + * This register is read-write if it is a sender. + */ + #define MBOX_MAGIC 0xA5A5AA55 + writel_relaxed(MBOX_MAGIC, mbox->mbox_base + MAILBOX_PTR_REG); + reg = readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG); + if (reg == MBOX_MAGIC) { + /* Clear to 0 */ + writel_relaxed(0, mbox->mbox_base + MAILBOX_PTR_REG); + return true; + } + return false; +} + +static void altera_mbox_rx_data(struct mbox_chan *chan) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + u32 data[2]; + + if (altera_mbox_pending(mbox)) { + data[MBOX_PTR] = + readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG); + data[MBOX_CMD] = + readl_relaxed(mbox->mbox_base + MAILBOX_CMD_REG); + mbox_chan_received_data(chan, (void *)data); + } +} + +static void altera_mbox_poll_rx(unsigned long data) +{ + struct mbox_chan *chan = (struct mbox_chan *)data; + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + altera_mbox_rx_data(chan); + + mod_timer(&mbox->rxpoll_timer, + jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); +} + +static irqreturn_t altera_mbox_tx_interrupt(int irq, void *p) +{ + struct mbox_chan *chan = (struct mbox_chan *)p; + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + altera_mbox_tx_intmask(mbox, false); + mbox_chan_txdone(chan, 0); + + return IRQ_HANDLED; +} + +static irqreturn_t altera_mbox_rx_interrupt(int irq, void *p) +{ + struct mbox_chan *chan = (struct mbox_chan *)p; + + altera_mbox_rx_data(chan); + return IRQ_HANDLED; +} + +static int altera_mbox_startup_sender(struct mbox_chan *chan) +{ + int ret; + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + if (mbox->intr_mode) { + ret = request_irq(mbox->irq, altera_mbox_tx_interrupt, 0, + DRIVER_NAME, chan); + if (unlikely(ret)) { + dev_err(mbox->dev, + "failed to register mailbox interrupt:%d\n", + ret); + return ret; + } + } + + return 0; +} + +static int altera_mbox_startup_receiver(struct mbox_chan *chan) +{ + int ret; + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + if (mbox->intr_mode) { + ret = request_irq(mbox->irq, altera_mbox_rx_interrupt, 0, + DRIVER_NAME, chan); + if (unlikely(ret)) { + mbox->intr_mode = false; + goto polling; /* use polling if failed */ + } + + altera_mbox_rx_intmask(mbox, true); + return 0; + } + +polling: + /* Setup polling timer */ + setup_timer(&mbox->rxpoll_timer, altera_mbox_poll_rx, + (unsigned long)chan); + mod_timer(&mbox->rxpoll_timer, + jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); + + return 0; +} + +static int altera_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + u32 *udata = (u32 *)data; + + if (!mbox || !data) + return -EINVAL; + if (!mbox->is_sender) { + dev_warn(mbox->dev, + "failed to send. This is receiver mailbox.\n"); + return -EINVAL; + } + + if (altera_mbox_full(mbox)) + return -EBUSY; + + /* Enable interrupt before send */ + if (mbox->intr_mode) + altera_mbox_tx_intmask(mbox, true); + + /* Pointer register must write before command register */ + writel_relaxed(udata[MBOX_PTR], mbox->mbox_base + MAILBOX_PTR_REG); + writel_relaxed(udata[MBOX_CMD], mbox->mbox_base + MAILBOX_CMD_REG); + + return 0; +} + +static bool altera_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + /* Return false if mailbox is full */ + return altera_mbox_full(mbox) ? false : true; +} + +static bool altera_mbox_peek_data(struct mbox_chan *chan) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + return altera_mbox_pending(mbox) ? true : false; +} + +static int altera_mbox_startup(struct mbox_chan *chan) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + int ret = 0; + + if (!mbox) + return -EINVAL; + + if (mbox->is_sender) + ret = altera_mbox_startup_sender(chan); + else + ret = altera_mbox_startup_receiver(chan); + + return ret; +} + +static void altera_mbox_shutdown(struct mbox_chan *chan) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + if (mbox->intr_mode) { + /* Unmask all interrupt masks */ + writel_relaxed(~0, mbox->mbox_base + MAILBOX_INTMASK_REG); + free_irq(mbox->irq, chan); + } else if (!mbox->is_sender) { + del_timer_sync(&mbox->rxpoll_timer); + } +} + +static struct mbox_chan_ops altera_mbox_ops = { + .send_data = altera_mbox_send_data, + .startup = altera_mbox_startup, + .shutdown = altera_mbox_shutdown, + .last_tx_done = altera_mbox_last_tx_done, + .peek_data = altera_mbox_peek_data, +}; + +static int altera_mbox_probe(struct platform_device *pdev) +{ + struct altera_mbox *mbox; + struct resource *regs; + struct mbox_chan *chans; + int ret; + + mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), + GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + /* Allocated one channel */ + chans = devm_kzalloc(&pdev->dev, sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + mbox->mbox_base = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(mbox->mbox_base)) + return PTR_ERR(mbox->mbox_base); + + /* Check is it a sender or receiver? */ + mbox->is_sender = altera_mbox_is_sender(mbox); + + mbox->irq = platform_get_irq(pdev, 0); + if (mbox->irq >= 0) + mbox->intr_mode = true; + + mbox->dev = &pdev->dev; + + /* Hardware supports only one channel. */ + chans[0].con_priv = mbox; + mbox->controller.dev = mbox->dev; + mbox->controller.num_chans = 1; + mbox->controller.chans = chans; + mbox->controller.ops = &altera_mbox_ops; + + if (mbox->is_sender) { + if (mbox->intr_mode) { + mbox->controller.txdone_irq = true; + } else { + mbox->controller.txdone_poll = true; + mbox->controller.txpoll_period = MBOX_POLLING_MS; + } + } + + ret = mbox_controller_register(&mbox->controller); + if (ret) { + dev_err(&pdev->dev, "Register mailbox failed\n"); + goto err; + } + + platform_set_drvdata(pdev, mbox); +err: + return ret; +} + +static int altera_mbox_remove(struct platform_device *pdev) +{ + struct altera_mbox *mbox = platform_get_drvdata(pdev); + + if (!mbox) + return -EINVAL; + + mbox_controller_unregister(&mbox->controller); + + return 0; +} + +static const struct of_device_id altera_mbox_match[] = { + { .compatible = "altr,mailbox-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_mbox_match); + +static struct platform_driver altera_mbox_driver = { + .probe = altera_mbox_probe, + .remove = altera_mbox_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = altera_mbox_match, + }, +}; + +module_platform_driver(altera_mbox_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera mailbox specific functions"); +MODULE_AUTHOR("Ley Foon Tan "); +MODULE_ALIAS("platform:altera-mailbox"); -- cgit v1.2.3 From 3bd746cfee36d923ee7ba9b230391215828c69c7 Mon Sep 17 00:00:00 2001 From: Robert Love Date: Wed, 28 May 2014 14:19:01 -0700 Subject: fcoe: Transition maintainership to Vasu Acked-by: Vasu Dev Signed-off-by: Robert Love Cc: Christoph Hellwig Signed-off-by: James Bottomley --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index f6dee56ac3df..46f344e9f629 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3872,7 +3872,7 @@ F: Documentation/fault-injection/ F: lib/fault-inject.c FCOE SUBSYSTEM (libfc, libfcoe, fcoe) -M: Robert Love +M: Vasu Dev L: fcoe-devel@open-fcoe.org W: www.Open-FCoE.org S: Supported -- cgit v1.2.3 From 7a85951692eb133f20ae93b48494322837b9d1f5 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Fri, 6 Feb 2015 17:46:07 +0100 Subject: EDAC: Add repo URLs to MAINTAINERS ... so that people can base new work ontop. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Borislav Petkov --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 375e2488d7ca..bd82e470fc72 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3508,6 +3508,8 @@ M: Borislav Petkov M: Mauro Carvalho Chehab L: linux-edac@vger.kernel.org W: bluesmoke.sourceforge.net +T: git://git.kernel.org/pub/scm/linux/kernel/git/bp/bp.git#for-next +T: git://git.kernel.org/pub/linux/kernel/git/mchehab/linux-edac.git#linux_next S: Supported F: Documentation/edac.txt F: drivers/edac/ -- cgit v1.2.3 From bcadb699b05b828fb43c54abb75607db7e8c3e29 Mon Sep 17 00:00:00 2001 From: Roger Pau Monne Date: Fri, 23 Jan 2015 17:14:29 +0100 Subject: xen-blkback,xen-blkfront: add myself as maintainer I've done quite a lot of work in blkfront/blkback, and I usually end up looking at the patches, so add myself as maintainer together with Konrad. Signed-off-by: Roger Pau Monné Acked-by: Konrad Rzeszutek Wilk --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index bb5e510e977e..6660beb96e61 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10574,6 +10574,7 @@ F: drivers/pci/*xen* XEN BLOCK SUBSYSTEM M: Konrad Rzeszutek Wilk +M: Roger Pau Monné L: xen-devel@lists.xenproject.org (moderated for non-subscribers) S: Supported F: drivers/block/xen-blkback/* -- cgit v1.2.3 From a2e199915725e666772dd077dbffbef154e58096 Mon Sep 17 00:00:00 2001 From: Luis R. Rodriguez Date: Fri, 13 Feb 2015 17:13:40 +1030 Subject: virtual: Documentation: simplify and generalize paravirt_ops.txt The general documentation we have for pv_ops is currenty present on the IA64 docs, but since this documentation covers IA64 xen enablement and IA64 Xen support got ripped out a while ago through commit d52eefb47 present since v3.14-rc1 lets just simplify, generalize and move the pv_ops documentation to a shared place. Cc: Isaku Yamahata Cc: Jeremy Fitzhardinge Cc: Chris Wright Cc: Alok Kataria Cc: Rusty Russell Cc: virtualization@lists.linux-foundation.org Cc: Tony Luck Cc: Fenghua Yu Cc: Boris Ostrovsky Cc: xen-devel@lists.xenproject.org Cc: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Luis R. Rodriguez Signed-off-by: Rusty Russell --- Documentation/ia64/paravirt_ops.txt | 137 --------------------------------- Documentation/virtual/00-INDEX | 3 + Documentation/virtual/paravirt_ops.txt | 32 ++++++++ MAINTAINERS | 2 +- 4 files changed, 36 insertions(+), 138 deletions(-) delete mode 100644 Documentation/ia64/paravirt_ops.txt create mode 100644 Documentation/virtual/paravirt_ops.txt (limited to 'MAINTAINERS') diff --git a/Documentation/ia64/paravirt_ops.txt b/Documentation/ia64/paravirt_ops.txt deleted file mode 100644 index 39ded02ec33f..000000000000 --- a/Documentation/ia64/paravirt_ops.txt +++ /dev/null @@ -1,137 +0,0 @@ -Paravirt_ops on IA64 -==================== - 21 May 2008, Isaku Yamahata - - -Introduction ------------- -The aim of this documentation is to help with maintainability and/or to -encourage people to use paravirt_ops/IA64. - -paravirt_ops (pv_ops in short) is a way for virtualization support of -Linux kernel on x86. Several ways for virtualization support were -proposed, paravirt_ops is the winner. -On the other hand, now there are also several IA64 virtualization -technologies like kvm/IA64, xen/IA64 and many other academic IA64 -hypervisors so that it is good to add generic virtualization -infrastructure on Linux/IA64. - - -What is paravirt_ops? ---------------------- -It has been developed on x86 as virtualization support via API, not ABI. -It allows each hypervisor to override operations which are important for -hypervisors at API level. And it allows a single kernel binary to run on -all supported execution environments including native machine. -Essentially paravirt_ops is a set of function pointers which represent -operations corresponding to low level sensitive instructions and high -level functionalities in various area. But one significant difference -from usual function pointer table is that it allows optimization with -binary patch. It is because some of these operations are very -performance sensitive and indirect call overhead is not negligible. -With binary patch, indirect C function call can be transformed into -direct C function call or in-place execution to eliminate the overhead. - -Thus, operations of paravirt_ops are classified into three categories. -- simple indirect call - These operations correspond to high level functionality so that the - overhead of indirect call isn't very important. - -- indirect call which allows optimization with binary patch - Usually these operations correspond to low level instructions. They - are called frequently and performance critical. So the overhead is - very important. - -- a set of macros for hand written assembly code - Hand written assembly codes (.S files) also need paravirtualization - because they include sensitive instructions or some of code paths in - them are very performance critical. - - -The relation to the IA64 machine vector ---------------------------------------- -Linux/IA64 has the IA64 machine vector functionality which allows the -kernel to switch implementations (e.g. initialization, ipi, dma api...) -depending on executing platform. -We can replace some implementations very easily defining a new machine -vector. Thus another approach for virtualization support would be -enhancing the machine vector functionality. -But paravirt_ops approach was taken because -- virtualization support needs wider support than machine vector does. - e.g. low level instruction paravirtualization. It must be - initialized very early before platform detection. - -- virtualization support needs more functionality like binary patch. - Probably the calling overhead might not be very large compared to the - emulation overhead of virtualization. However in the native case, the - overhead should be eliminated completely. - A single kernel binary should run on each environment including native, - and the overhead of paravirt_ops on native environment should be as - small as possible. - -- for full virtualization technology, e.g. KVM/IA64 or - Xen/IA64 HVM domain, the result would be - (the emulated platform machine vector. probably dig) + (pv_ops). - This means that the virtualization support layer should be under - the machine vector layer. - -Possibly it might be better to move some function pointers from -paravirt_ops to machine vector. In fact, Xen domU case utilizes both -pv_ops and machine vector. - - -IA64 paravirt_ops ------------------ -In this section, the concrete paravirt_ops will be discussed. -Because of the architecture difference between ia64 and x86, the -resulting set of functions is very different from x86 pv_ops. - -- C function pointer tables -They are not very performance critical so that simple C indirect -function call is acceptable. The following structures are defined at -this moment. For details see linux/include/asm-ia64/paravirt.h - - struct pv_info - This structure describes the execution environment. - - struct pv_init_ops - This structure describes the various initialization hooks. - - struct pv_iosapic_ops - This structure describes hooks to iosapic operations. - - struct pv_irq_ops - This structure describes hooks to irq related operations - - struct pv_time_op - This structure describes hooks to steal time accounting. - -- a set of indirect calls which need optimization -Currently this class of functions correspond to a subset of IA64 -intrinsics. At this moment the optimization with binary patch isn't -implemented yet. -struct pv_cpu_op is defined. For details see -linux/include/asm-ia64/paravirt_privop.h -Mostly they correspond to ia64 intrinsics 1-to-1. -Caveat: Now they are defined as C indirect function pointers, but in -order to support binary patch optimization, they will be changed -using GCC extended inline assembly code. - -- a set of macros for hand written assembly code (.S files) -For maintenance purpose, the taken approach for .S files is single -source code and compile multiple times with different macros definitions. -Each pv_ops instance must define those macros to compile. -The important thing here is that sensitive, but non-privileged -instructions must be paravirtualized and that some privileged -instructions also need paravirtualization for reasonable performance. -Developers who modify .S files must be aware of that. At this moment -an easy checker is implemented to detect paravirtualization breakage. -But it doesn't cover all the cases. - -Sometimes this set of macros is called pv_cpu_asm_op. But there is no -corresponding structure in the source code. -Those macros mostly 1:1 correspond to a subset of privileged -instructions. See linux/include/asm-ia64/native/inst.h. -And some functions written in assembly also need to be overrided so -that each pv_ops instance have to define some macros. Again see -linux/include/asm-ia64/native/inst.h. - - -Those structures must be initialized very early before start_kernel. -Probably initialized in head.S using multi entry point or some other trick. -For native case implementation see linux/arch/ia64/kernel/paravirt.c. diff --git a/Documentation/virtual/00-INDEX b/Documentation/virtual/00-INDEX index e952d30bbf0f..af0d23968ee7 100644 --- a/Documentation/virtual/00-INDEX +++ b/Documentation/virtual/00-INDEX @@ -2,6 +2,9 @@ Virtualization support in the Linux kernel. 00-INDEX - this file. + +paravirt_ops.txt + - Describes the Linux kernel pv_ops to support different hypervisors kvm/ - Kernel Virtual Machine. See also http://linux-kvm.org uml/ diff --git a/Documentation/virtual/paravirt_ops.txt b/Documentation/virtual/paravirt_ops.txt new file mode 100644 index 000000000000..d4881c00e339 --- /dev/null +++ b/Documentation/virtual/paravirt_ops.txt @@ -0,0 +1,32 @@ +Paravirt_ops +============ + +Linux provides support for different hypervisor virtualization technologies. +Historically different binary kernels would be required in order to support +different hypervisors, this restriction was removed with pv_ops. +Linux pv_ops is a virtualization API which enables support for different +hypervisors. It allows each hypervisor to override critical operations and +allows a single kernel binary to run on all supported execution environments +including native machine -- without any hypervisors. + +pv_ops provides a set of function pointers which represent operations +corresponding to low level critical instructions and high level +functionalities in various areas. pv-ops allows for optimizations at run +time by enabling binary patching of the low-ops critical operations +at boot time. + +pv_ops operations are classified into three categories: + +- simple indirect call + These operations correspond to high level functionality where it is + known that the overhead of indirect call isn't very important. + +- indirect call which allows optimization with binary patch + Usually these operations correspond to low level critical instructions. They + are called frequently and are performance critical. The overhead is + very important. + +- a set of macros for hand written assembly code + Hand written assembly codes (.S files) also need paravirtualization + because they include sensitive instructions or some of code paths in + them are very performance critical. diff --git a/MAINTAINERS b/MAINTAINERS index 93409ade65a5..9af1c6e5ee65 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7171,7 +7171,7 @@ M: Alok Kataria M: Rusty Russell L: virtualization@lists.linux-foundation.org S: Supported -F: Documentation/ia64/paravirt_ops.txt +F: Documentation/virtual/paravirt_ops.txt F: arch/*/kernel/paravirt* F: arch/*/include/asm/paravirt.h -- cgit v1.2.3 From 740e1433f50e6d0f1c4705c1a963645072f5a74c Mon Sep 17 00:00:00 2001 From: Chris Metcalf Date: Fri, 13 Feb 2015 13:16:49 -0500 Subject: tile: change MAINTAINERS website from tilera.com to ezchip.com Signed-off-by: Chris Metcalf --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3589d67437f8..be19dcb34547 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9618,7 +9618,7 @@ F: net/tipc/ TILE ARCHITECTURE M: Chris Metcalf -W: http://www.tilera.com/scm/ +W: http://www.ezchip.com/scm/ S: Supported F: arch/tile/ F: drivers/char/tile-srom.c -- cgit v1.2.3 From c6a95dbee79321d10f030546cc57a2268f4dd2b7 Mon Sep 17 00:00:00 2001 From: Gregory CLEMENT Date: Fri, 13 Feb 2015 14:41:14 -0800 Subject: MAINTAINERS: add the RTC driver for the Armada38x Put it in the mvebu entry. Signed-off-by: Gregory CLEMENT Cc: Alessandro Zummo Cc: Jason Cooper Cc: Andrew Lunn Cc: Sebastian Hesselbarth Cc: Arnaud Ebalard Cc: Thomas Petazzoni Cc: Ezequiel Garcia Cc: Maxime Ripard Cc: Boris BREZILLON Cc: Lior Amsalem Cc: Tawfik Bayouk Cc: Nadav Haklai Cc: Mark Rutland Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index debe74cde67b..cd8383e26ac8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1173,6 +1173,7 @@ M: Sebastian Hesselbarth L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: arch/arm/mach-mvebu/ +F: drivers/rtc/armada38x-rtc ARM/Marvell Berlin SoC support M: Sebastian Hesselbarth -- cgit v1.2.3 From 08c283cc039efc2cf7d64d7e69d8d01de825fc6c Mon Sep 17 00:00:00 2001 From: Ley Foon Tan Date: Mon, 16 Feb 2015 11:05:31 +0800 Subject: MAINTAINERS: update arch/nios2 git tree Signed-off-by: Ley Foon Tan --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index d66a97dd3a12..63e6c3094a86 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6780,7 +6780,7 @@ F: drivers/scsi/nsp32* NIOS2 ARCHITECTURE M: Ley Foon Tan L: nios2-dev@lists.rocketboards.org (moderated for non-subscribers) -T: git git://git.rocketboards.org/linux-socfpga.git +T: git git://git.rocketboards.org/linux-socfpga-next.git S: Maintained F: arch/nios2/ -- cgit v1.2.3 From 3453bddbebebb6d6b89659b777f04959f60175a2 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Feb 2015 15:27:56 +0100 Subject: MAINTAINERS: Update SRP initiator entry We have been asked to use our company e-mail address for open source contributions. Hence this change from a personal e-mail address into a company e-mail address. Signed-off-by: Bart Van Assche Cc: Bart Van Assche Signed-off-by: Roland Dreier --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index d66a97dd3a12..d2357a1da410 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8450,7 +8450,7 @@ S: Maintained F: drivers/scsi/sr* SCSI RDMA PROTOCOL (SRP) INITIATOR -M: Bart Van Assche +M: Bart Van Assche L: linux-rdma@vger.kernel.org S: Supported W: http://www.openfabrics.org -- cgit v1.2.3 From d475c6346a38aef3058eba96867bfa726a3cc940 Mon Sep 17 00:00:00 2001 From: Matthew Wilcox Date: Mon, 16 Feb 2015 15:58:56 -0800 Subject: dax,ext2: replace XIP read and write with DAX I/O Use the generic AIO infrastructure instead of custom read and write methods. In addition to giving us support for AIO, this adds the missing locking between read() and truncate(). Signed-off-by: Matthew Wilcox Reviewed-by: Ross Zwisler Reviewed-by: Jan Kara Cc: Andreas Dilger Cc: Boaz Harrosh Cc: Christoph Hellwig Cc: Dave Chinner Cc: Jens Axboe Cc: Kirill A. Shutemov Cc: Mathieu Desnoyers Cc: Randy Dunlap Cc: Theodore Ts'o Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 6 ++ fs/Makefile | 1 + fs/dax.c | 186 ++++++++++++++++++++++++++++++++++++++++++ fs/ext2/file.c | 6 +- fs/ext2/inode.c | 8 +- include/linux/fs.h | 12 ++- mm/filemap.c | 6 +- mm/filemap_xip.c | 234 ----------------------------------------------------- 8 files changed, 214 insertions(+), 245 deletions(-) create mode 100644 fs/dax.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 348f5c16ef50..8670c224c833 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3151,6 +3151,12 @@ L: linux-i2c@vger.kernel.org S: Maintained F: drivers/i2c/busses/i2c-diolan-u2c.c +DIRECT ACCESS (DAX) +M: Matthew Wilcox +L: linux-fsdevel@vger.kernel.org +S: Supported +F: fs/dax.c + DIRECTORY NOTIFICATION (DNOTIFY) M: Eric Paris S: Maintained diff --git a/fs/Makefile b/fs/Makefile index bedff48e8fdc..0534444e257c 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_SIGNALFD) += signalfd.o obj-$(CONFIG_TIMERFD) += timerfd.o obj-$(CONFIG_EVENTFD) += eventfd.o obj-$(CONFIG_AIO) += aio.o +obj-$(CONFIG_FS_XIP) += dax.o obj-$(CONFIG_FILE_LOCKING) += locks.o obj-$(CONFIG_COMPAT) += compat.o compat_ioctl.o obj-$(CONFIG_BINFMT_AOUT) += binfmt_aout.o diff --git a/fs/dax.c b/fs/dax.c new file mode 100644 index 000000000000..1a2bdbfa3ea9 --- /dev/null +++ b/fs/dax.c @@ -0,0 +1,186 @@ +/* + * fs/dax.c - Direct Access filesystem code + * Copyright (c) 2013-2014 Intel Corporation + * Author: Matthew Wilcox + * Author: Ross Zwisler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +static long dax_get_addr(struct buffer_head *bh, void **addr, unsigned blkbits) +{ + unsigned long pfn; + sector_t sector = bh->b_blocknr << (blkbits - 9); + return bdev_direct_access(bh->b_bdev, sector, addr, &pfn, bh->b_size); +} + +static void dax_new_buf(void *addr, unsigned size, unsigned first, loff_t pos, + loff_t end) +{ + loff_t final = end - pos + first; /* The final byte of the buffer */ + + if (first > 0) + memset(addr, 0, first); + if (final < size) + memset(addr + final, 0, size - final); +} + +static bool buffer_written(struct buffer_head *bh) +{ + return buffer_mapped(bh) && !buffer_unwritten(bh); +} + +/* + * When ext4 encounters a hole, it returns without modifying the buffer_head + * which means that we can't trust b_size. To cope with this, we set b_state + * to 0 before calling get_block and, if any bit is set, we know we can trust + * b_size. Unfortunate, really, since ext4 knows precisely how long a hole is + * and would save us time calling get_block repeatedly. + */ +static bool buffer_size_valid(struct buffer_head *bh) +{ + return bh->b_state != 0; +} + +static ssize_t dax_io(int rw, struct inode *inode, struct iov_iter *iter, + loff_t start, loff_t end, get_block_t get_block, + struct buffer_head *bh) +{ + ssize_t retval = 0; + loff_t pos = start; + loff_t max = start; + loff_t bh_max = start; + void *addr; + bool hole = false; + + if (rw != WRITE) + end = min(end, i_size_read(inode)); + + while (pos < end) { + unsigned len; + if (pos == max) { + unsigned blkbits = inode->i_blkbits; + sector_t block = pos >> blkbits; + unsigned first = pos - (block << blkbits); + long size; + + if (pos == bh_max) { + bh->b_size = PAGE_ALIGN(end - pos); + bh->b_state = 0; + retval = get_block(inode, block, bh, + rw == WRITE); + if (retval) + break; + if (!buffer_size_valid(bh)) + bh->b_size = 1 << blkbits; + bh_max = pos - first + bh->b_size; + } else { + unsigned done = bh->b_size - + (bh_max - (pos - first)); + bh->b_blocknr += done >> blkbits; + bh->b_size -= done; + } + + hole = (rw != WRITE) && !buffer_written(bh); + if (hole) { + addr = NULL; + size = bh->b_size - first; + } else { + retval = dax_get_addr(bh, &addr, blkbits); + if (retval < 0) + break; + if (buffer_unwritten(bh) || buffer_new(bh)) + dax_new_buf(addr, retval, first, pos, + end); + addr += first; + size = retval - first; + } + max = min(pos + size, end); + } + + if (rw == WRITE) + len = copy_from_iter(addr, max - pos, iter); + else if (!hole) + len = copy_to_iter(addr, max - pos, iter); + else + len = iov_iter_zero(max - pos, iter); + + if (!len) + break; + + pos += len; + addr += len; + } + + return (pos == start) ? retval : pos - start; +} + +/** + * dax_do_io - Perform I/O to a DAX file + * @rw: READ to read or WRITE to write + * @iocb: The control block for this I/O + * @inode: The file which the I/O is directed at + * @iter: The addresses to do I/O from or to + * @pos: The file offset where the I/O starts + * @get_block: The filesystem method used to translate file offsets to blocks + * @end_io: A filesystem callback for I/O completion + * @flags: See below + * + * This function uses the same locking scheme as do_blockdev_direct_IO: + * If @flags has DIO_LOCKING set, we assume that the i_mutex is held by the + * caller for writes. For reads, we take and release the i_mutex ourselves. + * If DIO_LOCKING is not set, the filesystem takes care of its own locking. + * As with do_blockdev_direct_IO(), we increment i_dio_count while the I/O + * is in progress. + */ +ssize_t dax_do_io(int rw, struct kiocb *iocb, struct inode *inode, + struct iov_iter *iter, loff_t pos, + get_block_t get_block, dio_iodone_t end_io, int flags) +{ + struct buffer_head bh; + ssize_t retval = -EINVAL; + loff_t end = pos + iov_iter_count(iter); + + memset(&bh, 0, sizeof(bh)); + + if ((flags & DIO_LOCKING) && (rw == READ)) { + struct address_space *mapping = inode->i_mapping; + mutex_lock(&inode->i_mutex); + retval = filemap_write_and_wait_range(mapping, pos, end - 1); + if (retval) { + mutex_unlock(&inode->i_mutex); + goto out; + } + } + + /* Protects against truncate */ + atomic_inc(&inode->i_dio_count); + + retval = dax_io(rw, inode, iter, pos, end, get_block, &bh); + + if ((flags & DIO_LOCKING) && (rw == READ)) + mutex_unlock(&inode->i_mutex); + + if ((retval > 0) && end_io) + end_io(iocb, pos, retval, bh.b_private); + + inode_dio_done(inode); + out: + return retval; +} +EXPORT_SYMBOL_GPL(dax_do_io); diff --git a/fs/ext2/file.c b/fs/ext2/file.c index 7c87b22a7228..a247123fd798 100644 --- a/fs/ext2/file.c +++ b/fs/ext2/file.c @@ -81,8 +81,10 @@ const struct file_operations ext2_file_operations = { #ifdef CONFIG_EXT2_FS_XIP const struct file_operations ext2_xip_file_operations = { .llseek = generic_file_llseek, - .read = xip_file_read, - .write = xip_file_write, + .read = new_sync_read, + .write = new_sync_write, + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, .unlocked_ioctl = ext2_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = ext2_compat_ioctl, diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index 0cb04486577d..3ccd5fd47d66 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -859,7 +859,12 @@ ext2_direct_IO(int rw, struct kiocb *iocb, struct iov_iter *iter, size_t count = iov_iter_count(iter); ssize_t ret; - ret = blockdev_direct_IO(rw, iocb, inode, iter, offset, ext2_get_block); + if (IS_DAX(inode)) + ret = dax_do_io(rw, iocb, inode, iter, offset, ext2_get_block, + NULL, DIO_LOCKING); + else + ret = blockdev_direct_IO(rw, iocb, inode, iter, offset, + ext2_get_block); if (ret < 0 && (rw & WRITE)) ext2_write_failed(mapping, offset + count); return ret; @@ -888,6 +893,7 @@ const struct address_space_operations ext2_aops = { const struct address_space_operations ext2_aops_xip = { .bmap = ext2_bmap, .get_xip_mem = ext2_get_xip_mem, + .direct_IO = ext2_direct_IO, }; const struct address_space_operations ext2_nobh_aops = { diff --git a/include/linux/fs.h b/include/linux/fs.h index fb373bb5cf03..241c3c030fb5 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2587,12 +2587,11 @@ extern loff_t fixed_size_llseek(struct file *file, loff_t offset, extern int generic_file_open(struct inode * inode, struct file * filp); extern int nonseekable_open(struct inode * inode, struct file * filp); +ssize_t dax_do_io(int rw, struct kiocb *, struct inode *, struct iov_iter *, + loff_t, get_block_t, dio_iodone_t, int flags); + #ifdef CONFIG_FS_XIP -extern ssize_t xip_file_read(struct file *filp, char __user *buf, size_t len, - loff_t *ppos); extern int xip_file_mmap(struct file * file, struct vm_area_struct * vma); -extern ssize_t xip_file_write(struct file *filp, const char __user *buf, - size_t len, loff_t *ppos); extern int xip_truncate_page(struct address_space *mapping, loff_t from); #else static inline int xip_truncate_page(struct address_space *mapping, loff_t from) @@ -2756,6 +2755,11 @@ extern int generic_show_options(struct seq_file *m, struct dentry *root); extern void save_mount_options(struct super_block *sb, char *options); extern void replace_mount_options(struct super_block *sb, char *options); +static inline bool io_is_direct(struct file *filp) +{ + return (filp->f_flags & O_DIRECT) || IS_DAX(file_inode(filp)); +} + static inline ino_t parent_ino(struct dentry *dentry) { ino_t res; diff --git a/mm/filemap.c b/mm/filemap.c index 1578c224285e..ad7242043bdb 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -1695,8 +1695,7 @@ generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter) loff_t *ppos = &iocb->ki_pos; loff_t pos = *ppos; - /* coalesce the iovecs and go direct-to-BIO for O_DIRECT */ - if (file->f_flags & O_DIRECT) { + if (io_is_direct(file)) { struct address_space *mapping = file->f_mapping; struct inode *inode = mapping->host; size_t count = iov_iter_count(iter); @@ -2584,8 +2583,7 @@ ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from) if (err) goto out; - /* coalesce the iovecs and go direct-to-BIO for O_DIRECT */ - if (unlikely(file->f_flags & O_DIRECT)) { + if (io_is_direct(file)) { loff_t endbyte; written = generic_file_direct_write(iocb, from, pos); diff --git a/mm/filemap_xip.c b/mm/filemap_xip.c index 59e1c5585748..9c869f402c07 100644 --- a/mm/filemap_xip.c +++ b/mm/filemap_xip.c @@ -42,119 +42,6 @@ static struct page *xip_sparse_page(void) return __xip_sparse_page; } -/* - * This is a file read routine for execute in place files, and uses - * the mapping->a_ops->get_xip_mem() function for the actual low-level - * stuff. - * - * Note the struct file* is not used at all. It may be NULL. - */ -static ssize_t -do_xip_mapping_read(struct address_space *mapping, - struct file_ra_state *_ra, - struct file *filp, - char __user *buf, - size_t len, - loff_t *ppos) -{ - struct inode *inode = mapping->host; - pgoff_t index, end_index; - unsigned long offset; - loff_t isize, pos; - size_t copied = 0, error = 0; - - BUG_ON(!mapping->a_ops->get_xip_mem); - - pos = *ppos; - index = pos >> PAGE_CACHE_SHIFT; - offset = pos & ~PAGE_CACHE_MASK; - - isize = i_size_read(inode); - if (!isize) - goto out; - - end_index = (isize - 1) >> PAGE_CACHE_SHIFT; - do { - unsigned long nr, left; - void *xip_mem; - unsigned long xip_pfn; - int zero = 0; - - /* nr is the maximum number of bytes to copy from this page */ - nr = PAGE_CACHE_SIZE; - if (index >= end_index) { - if (index > end_index) - goto out; - nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1; - if (nr <= offset) { - goto out; - } - } - nr = nr - offset; - if (nr > len - copied) - nr = len - copied; - - error = mapping->a_ops->get_xip_mem(mapping, index, 0, - &xip_mem, &xip_pfn); - if (unlikely(error)) { - if (error == -ENODATA) { - /* sparse */ - zero = 1; - } else - goto out; - } - - /* If users can be writing to this page using arbitrary - * virtual addresses, take care about potential aliasing - * before reading the page on the kernel side. - */ - if (mapping_writably_mapped(mapping)) - /* address based flush */ ; - - /* - * Ok, we have the mem, so now we can copy it to user space... - * - * The actor routine returns how many bytes were actually used.. - * NOTE! This may not be the same as how much of a user buffer - * we filled up (we may be padding etc), so we can only update - * "pos" here (the actor routine has to update the user buffer - * pointers and the remaining count). - */ - if (!zero) - left = __copy_to_user(buf+copied, xip_mem+offset, nr); - else - left = __clear_user(buf + copied, nr); - - if (left) { - error = -EFAULT; - goto out; - } - - copied += (nr - left); - offset += (nr - left); - index += offset >> PAGE_CACHE_SHIFT; - offset &= ~PAGE_CACHE_MASK; - } while (copied < len); - -out: - *ppos = pos + copied; - if (filp) - file_accessed(filp); - - return (copied ? copied : error); -} - -ssize_t -xip_file_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos) -{ - if (!access_ok(VERIFY_WRITE, buf, len)) - return -EFAULT; - - return do_xip_mapping_read(filp->f_mapping, &filp->f_ra, filp, - buf, len, ppos); -} -EXPORT_SYMBOL_GPL(xip_file_read); - /* * __xip_unmap is invoked from xip_unmap and xip_write * @@ -341,127 +228,6 @@ int xip_file_mmap(struct file * file, struct vm_area_struct * vma) } EXPORT_SYMBOL_GPL(xip_file_mmap); -static ssize_t -__xip_file_write(struct file *filp, const char __user *buf, - size_t count, loff_t pos, loff_t *ppos) -{ - struct address_space * mapping = filp->f_mapping; - const struct address_space_operations *a_ops = mapping->a_ops; - struct inode *inode = mapping->host; - long status = 0; - size_t bytes; - ssize_t written = 0; - - BUG_ON(!mapping->a_ops->get_xip_mem); - - do { - unsigned long index; - unsigned long offset; - size_t copied; - void *xip_mem; - unsigned long xip_pfn; - - offset = (pos & (PAGE_CACHE_SIZE -1)); /* Within page */ - index = pos >> PAGE_CACHE_SHIFT; - bytes = PAGE_CACHE_SIZE - offset; - if (bytes > count) - bytes = count; - - status = a_ops->get_xip_mem(mapping, index, 0, - &xip_mem, &xip_pfn); - if (status == -ENODATA) { - /* we allocate a new page unmap it */ - mutex_lock(&xip_sparse_mutex); - status = a_ops->get_xip_mem(mapping, index, 1, - &xip_mem, &xip_pfn); - mutex_unlock(&xip_sparse_mutex); - if (!status) - /* unmap page at pgoff from all other vmas */ - __xip_unmap(mapping, index); - } - - if (status) - break; - - copied = bytes - - __copy_from_user_nocache(xip_mem + offset, buf, bytes); - - if (likely(copied > 0)) { - status = copied; - - if (status >= 0) { - written += status; - count -= status; - pos += status; - buf += status; - } - } - if (unlikely(copied != bytes)) - if (status >= 0) - status = -EFAULT; - if (status < 0) - break; - } while (count); - *ppos = pos; - /* - * No need to use i_size_read() here, the i_size - * cannot change under us because we hold i_mutex. - */ - if (pos > inode->i_size) { - i_size_write(inode, pos); - mark_inode_dirty(inode); - } - - return written ? written : status; -} - -ssize_t -xip_file_write(struct file *filp, const char __user *buf, size_t len, - loff_t *ppos) -{ - struct address_space *mapping = filp->f_mapping; - struct inode *inode = mapping->host; - size_t count; - loff_t pos; - ssize_t ret; - - mutex_lock(&inode->i_mutex); - - if (!access_ok(VERIFY_READ, buf, len)) { - ret=-EFAULT; - goto out_up; - } - - pos = *ppos; - count = len; - - /* We can write back this queue in page reclaim */ - current->backing_dev_info = inode_to_bdi(inode); - - ret = generic_write_checks(filp, &pos, &count, S_ISBLK(inode->i_mode)); - if (ret) - goto out_backing; - if (count == 0) - goto out_backing; - - ret = file_remove_suid(filp); - if (ret) - goto out_backing; - - ret = file_update_time(filp); - if (ret) - goto out_backing; - - ret = __xip_file_write (filp, buf, count, pos, ppos); - - out_backing: - current->backing_dev_info = NULL; - out_up: - mutex_unlock(&inode->i_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(xip_file_write); - /* * truncate a page used for execute in place * functionality is analog to block_truncate_page but does use get_xip_mem -- cgit v1.2.3 From 6f9e2456c9f8904346958b6d9602a755372865b0 Mon Sep 17 00:00:00 2001 From: Akash Shende Date: Mon, 16 Feb 2015 15:59:48 -0800 Subject: MAINTAINERS: fix spelling mistake & remove trailing WS Signed-off-by: Akash Shende Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 8670c224c833..0ca23aa48ae7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -34,7 +34,7 @@ trivial patch so apply some common sense. generalized kernel feature ready for next time. PLEASE check your patch with the automated style checker - (scripts/checkpatch.pl) to catch trival style violations. + (scripts/checkpatch.pl) to catch trivial style violations. See Documentation/CodingStyle for guidance here. PLEASE CC: the maintainers and mailing lists that are generated -- cgit v1.2.3 From befeb596a7224119b60499eb20c7545f7a73f104 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 16 Feb 2015 16:00:23 -0800 Subject: MAINTAINERS: add entry for Maxim PMICs on Samsung boards Add myself and Chanwoo Choi as supporters to help in reviewing patches for Maxim 77686 PMIC and Maxim 14577/77693 MUIC drivers: - mfd (all of them), - extcon (extcon-max14577.c, extcon-max77693.c), - regulator (all of them), - clock (clk-max77686.c), - RTC (rtc-max77686.c). Lately I am the author of contributors to them. These drivers are used on Exynos-based boards (Trats 2, Gear 1 and Gear 2). Signed-off-by: Krzysztof Kozlowski Cc: MyungJoo Ham Cc: Chanwoo Choi Cc: Samuel Ortiz Cc: Lee Jones Cc: Liam Girdwood Cc: Mark Brown Cc: Mike Turquette Cc: Stephen Boyd Cc: Alessandro Zummo Acked-by: Mark Brown Acked-by: Lee Jones Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 0ca23aa48ae7..6f2c849e6da3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6216,6 +6216,26 @@ S: Supported F: drivers/power/max14577_charger.c F: drivers/power/max77693_charger.c +MAXIM PMIC AND MUIC DRIVERS FOR EXYNOS BASED BOARDS +M: Chanwoo Choi +M: Krzysztof Kozlowski +L: linux-kernel@vger.kernel.org +S: Supported +F: drivers/*/max14577.c +F: drivers/*/max77686.c +F: drivers/*/max77693.c +F: drivers/extcon/extcon-max14577.c +F: drivers/extcon/extcon-max77693.c +F: drivers/rtc/rtc-max77686.c +F: drivers/clk/clk-max77686.c +F: Documentation/devicetree/bindings/mfd/max14577.txt +F: Documentation/devicetree/bindings/mfd/max77686.txt +F: Documentation/devicetree/bindings/mfd/max77693.txt +F: Documentation/devicetree/bindings/clock/maxim,max77686.txt +F: include/linux/mfd/max14577*.h +F: include/linux/mfd/max77686*.h +F: include/linux/mfd/max77693*.h + MAXIRADIO FM RADIO RECEIVER DRIVER M: Hans Verkuil L: linux-media@vger.kernel.org -- cgit v1.2.3 From aaaf5fbf56f16c81a653713cc333b18ad6e25ea9 Mon Sep 17 00:00:00 2001 From: Joshua Kinard Date: Mon, 16 Feb 2015 16:00:26 -0800 Subject: rtc: add driver for DS1685 family of real time clocks This adds a driver for the Dallas/Maxim DS1685-family of RTC chips. It supports the DS1685/DS1687, DS1688/DS1691, DS1689/DS1693, DS17285/DS17287, DS17485/DS17487, and DS17885/DS17887 RTC chips. These chips are commonly found in SGI O2 and SGI Octane systems. It was originally derived from a driver patch submitted by Matthias Fuchs many years ago for use in EPPC-405-UC modules, which also used these RTCs. In addition to the time-keeping functions, this RTC also handles the shutdown mechanism of the O2 and Octane and acts as a partial NVRAM for the boot PROMS in these systems. Verified on both an SGI O2 and an SGI Octane. Signed-off-by: Joshua Kinard Cc: Ralf Baechle Cc: Alessandro Zummo Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 6 + drivers/rtc/Kconfig | 90 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-ds1685.c | 2252 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/rtc/ds1685.h | 375 ++++++++ 5 files changed, 2724 insertions(+) create mode 100644 drivers/rtc/rtc-ds1685.c create mode 100644 include/linux/rtc/ds1685.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 6f2c849e6da3..d5ff4c3cdbac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2963,6 +2963,12 @@ S: Supported F: drivers/input/touchscreen/cyttsp* F: include/linux/input/cyttsp.h +DALLAS/MAXIM DS1685-FAMILY REAL TIME CLOCK +M: Joshua Kinard +S: Maintained +F: drivers/rtc/rtc-ds1685.c +F: include/linux/rtc/ds1685.h + DAMA SLAVE for AX.25 M: Joerg Reuter W: http://yaina.de/jreuter/ diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 3bc9ddbe5cf7..0cf2e1d9cb17 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -801,6 +801,96 @@ config RTC_DRV_DS1553 This driver can also be built as a module. If so, the module will be called rtc-ds1553. +config RTC_DRV_DS1685_FAMILY + tristate "Dallas/Maxim DS1685 Family" + help + If you say yes here you get support for the Dallas/Maxim DS1685 + family of real time chips. This family includes the DS1685/DS1687, + DS1689/DS1693, DS17285/DS17287, DS17485/DS17487, and + DS17885/DS17887 chips. + + This driver can also be built as a module. If so, the module + will be called rtc-ds1685. + +choice + prompt "Subtype" + depends on RTC_DRV_DS1685_FAMILY + default RTC_DRV_DS1685 + +config RTC_DRV_DS1685 + bool "DS1685/DS1687" + help + This enables support for the Dallas/Maxim DS1685/DS1687 real time + clock chip. + + This chip is commonly found in SGI O2 (IP32) and SGI Octane (IP30) + systems, as well as EPPC-405-UC modules by electronic system design + GmbH. + +config RTC_DRV_DS1689 + bool "DS1689/DS1693" + help + This enables support for the Dallas/Maxim DS1689/DS1693 real time + clock chip. + + This is an older RTC chip, supplanted by the DS1685/DS1687 above, + which supports a few minor features such as Vcc, Vbat, and Power + Cycle counters, plus a customer-specific, 8-byte ROM/Serial number. + + It also works for the even older DS1688/DS1691 RTC chips, which are + virtually the same and carry the same model number. Both chips + have 114 bytes of user NVRAM. + +config RTC_DRV_DS17285 + bool "DS17285/DS17287" + help + This enables support for the Dallas/Maxim DS17285/DS17287 real time + clock chip. + + This chip features 2kb of extended NV-SRAM. It may possibly be + found in some SGI O2 systems (rare). + +config RTC_DRV_DS17485 + bool "DS17485/DS17487" + help + This enables support for the Dallas/Maxim DS17485/DS17487 real time + clock chip. + + This chip features 4kb of extended NV-SRAM. + +config RTC_DRV_DS17885 + bool "DS17885/DS17887" + help + This enables support for the Dallas/Maxim DS17885/DS17887 real time + clock chip. + + This chip features 8kb of extended NV-SRAM. + +endchoice + +config RTC_DS1685_PROC_REGS + bool "Display register values in /proc" + depends on RTC_DRV_DS1685_FAMILY && PROC_FS + help + Enable this to display a readout of all of the RTC registers in + /proc/drivers/rtc. Keep in mind that this can potentially lead + to lost interrupts, as reading Control Register C will clear + all pending IRQ flags. + + Unless you are debugging this driver, choose N. + +config RTC_DS1685_SYSFS_REGS + bool "SysFS access to RTC register bits" + depends on RTC_DRV_DS1685_FAMILY && SYSFS + help + Enable this to provide access to the RTC control register bits + in /sys. Some of the bits are read-write, others are read-only. + + Keep in mind that reading Control C's bits automatically clears + all pending IRQ flags - this can cause lost interrupts. + + If you know that you need access to these bits, choose Y, Else N. + config RTC_DRV_DS1742 tristate "Maxim/Dallas DS1742/1743" depends on HAS_IOMEM diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 99ded8b75e95..69c87062b098 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_RTC_DRV_DS1390) += rtc-ds1390.o obj-$(CONFIG_RTC_DRV_DS1511) += rtc-ds1511.o obj-$(CONFIG_RTC_DRV_DS1553) += rtc-ds1553.o obj-$(CONFIG_RTC_DRV_DS1672) += rtc-ds1672.o +obj-$(CONFIG_RTC_DRV_DS1685_FAMILY) += rtc-ds1685.o obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o obj-$(CONFIG_RTC_DRV_DS2404) += rtc-ds2404.o obj-$(CONFIG_RTC_DRV_DS3232) += rtc-ds3232.o diff --git a/drivers/rtc/rtc-ds1685.c b/drivers/rtc/rtc-ds1685.c new file mode 100644 index 000000000000..8c3bfcb115b7 --- /dev/null +++ b/drivers/rtc/rtc-ds1685.c @@ -0,0 +1,2252 @@ +/* + * An rtc driver for the Dallas/Maxim DS1685/DS1687 and related real-time + * chips. + * + * Copyright (C) 2011-2014 Joshua Kinard . + * Copyright (C) 2009 Matthias Fuchs . + * + * References: + * DS1685/DS1687 3V/5V Real-Time Clocks, 19-5215, Rev 4/10. + * DS17x85/DS17x87 3V/5V Real-Time Clocks, 19-5222, Rev 4/10. + * DS1689/DS1693 3V/5V Serialized Real-Time Clocks, Rev 112105. + * Application Note 90, Using the Multiplex Bus RTC Extended Features. + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#ifdef CONFIG_PROC_FS +#include +#endif + +#define DRV_VERSION "0.42.0" + + +/* ----------------------------------------------------------------------- */ +/* Standard read/write functions if platform does not provide overrides */ + +/** + * ds1685_read - read a value from an rtc register. + * @rtc: pointer to the ds1685 rtc structure. + * @reg: the register address to read. + */ +static u8 +ds1685_read(struct ds1685_priv *rtc, int reg) +{ + return readb((u8 __iomem *)rtc->regs + + (reg * rtc->regstep)); +} + +/** + * ds1685_write - write a value to an rtc register. + * @rtc: pointer to the ds1685 rtc structure. + * @reg: the register address to write. + * @value: value to write to the register. + */ +static void +ds1685_write(struct ds1685_priv *rtc, int reg, u8 value) +{ + writeb(value, ((u8 __iomem *)rtc->regs + + (reg * rtc->regstep))); +} +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* Inlined functions */ + +/** + * ds1685_rtc_bcd2bin - bcd2bin wrapper in case platform doesn't support BCD. + * @rtc: pointer to the ds1685 rtc structure. + * @val: u8 time value to consider converting. + * @bcd_mask: u8 mask value if BCD mode is used. + * @bin_mask: u8 mask value if BIN mode is used. + * + * Returns the value, converted to BIN if originally in BCD and bcd_mode TRUE. + */ +static inline u8 +ds1685_rtc_bcd2bin(struct ds1685_priv *rtc, u8 val, u8 bcd_mask, u8 bin_mask) +{ + if (rtc->bcd_mode) + return (bcd2bin(val) & bcd_mask); + + return (val & bin_mask); +} + +/** + * ds1685_rtc_bin2bcd - bin2bcd wrapper in case platform doesn't support BCD. + * @rtc: pointer to the ds1685 rtc structure. + * @val: u8 time value to consider converting. + * @bin_mask: u8 mask value if BIN mode is used. + * @bcd_mask: u8 mask value if BCD mode is used. + * + * Returns the value, converted to BCD if originally in BIN and bcd_mode TRUE. + */ +static inline u8 +ds1685_rtc_bin2bcd(struct ds1685_priv *rtc, u8 val, u8 bin_mask, u8 bcd_mask) +{ + if (rtc->bcd_mode) + return (bin2bcd(val) & bcd_mask); + + return (val & bin_mask); +} + +/** + * ds1685_rtc_switch_to_bank0 - switch the rtc to bank 0. + * @rtc: pointer to the ds1685 rtc structure. + */ +static inline void +ds1685_rtc_switch_to_bank0(struct ds1685_priv *rtc) +{ + rtc->write(rtc, RTC_CTRL_A, + (rtc->read(rtc, RTC_CTRL_A) & ~(RTC_CTRL_A_DV0))); +} + +/** + * ds1685_rtc_switch_to_bank1 - switch the rtc to bank 1. + * @rtc: pointer to the ds1685 rtc structure. + */ +static inline void +ds1685_rtc_switch_to_bank1(struct ds1685_priv *rtc) +{ + rtc->write(rtc, RTC_CTRL_A, + (rtc->read(rtc, RTC_CTRL_A) | RTC_CTRL_A_DV0)); +} + +/** + * ds1685_rtc_begin_data_access - prepare the rtc for data access. + * @rtc: pointer to the ds1685 rtc structure. + * + * This takes several steps to prepare the rtc for access to get/set time + * and alarm values from the rtc registers: + * - Sets the SET bit in Control Register B. + * - Reads Ext Control Register 4A and checks the INCR bit. + * - If INCR is active, a short delay is added before Ext Control Register 4A + * is read again in a loop until INCR is inactive. + * - Switches the rtc to bank 1. This allows access to all relevant + * data for normal rtc operation, as bank 0 contains only the nvram. + */ +static inline void +ds1685_rtc_begin_data_access(struct ds1685_priv *rtc) +{ + /* Set the SET bit in Ctrl B */ + rtc->write(rtc, RTC_CTRL_B, + (rtc->read(rtc, RTC_CTRL_B) | RTC_CTRL_B_SET)); + + /* Read Ext Ctrl 4A and check the INCR bit to avoid a lockout. */ + while (rtc->read(rtc, RTC_EXT_CTRL_4A) & RTC_CTRL_4A_INCR) + cpu_relax(); + + /* Switch to Bank 1 */ + ds1685_rtc_switch_to_bank1(rtc); +} + +/** + * ds1685_rtc_end_data_access - end data access on the rtc. + * @rtc: pointer to the ds1685 rtc structure. + * + * This ends what was started by ds1685_rtc_begin_data_access: + * - Switches the rtc back to bank 0. + * - Clears the SET bit in Control Register B. + */ +static inline void +ds1685_rtc_end_data_access(struct ds1685_priv *rtc) +{ + /* Switch back to Bank 0 */ + ds1685_rtc_switch_to_bank1(rtc); + + /* Clear the SET bit in Ctrl B */ + rtc->write(rtc, RTC_CTRL_B, + (rtc->read(rtc, RTC_CTRL_B) & ~(RTC_CTRL_B_SET))); +} + +/** + * ds1685_rtc_begin_ctrl_access - prepare the rtc for ctrl access. + * @rtc: pointer to the ds1685 rtc structure. + * @flags: irq flags variable for spin_lock_irqsave. + * + * This takes several steps to prepare the rtc for access to read just the + * control registers: + * - Sets a spinlock on the rtc IRQ. + * - Switches the rtc to bank 1. This allows access to the two extended + * control registers. + * + * Only use this where you are certain another lock will not be held. + */ +static inline void +ds1685_rtc_begin_ctrl_access(struct ds1685_priv *rtc, unsigned long flags) +{ + spin_lock_irqsave(&rtc->lock, flags); + ds1685_rtc_switch_to_bank1(rtc); +} + +/** + * ds1685_rtc_end_ctrl_access - end ctrl access on the rtc. + * @rtc: pointer to the ds1685 rtc structure. + * @flags: irq flags variable for spin_unlock_irqrestore. + * + * This ends what was started by ds1685_rtc_begin_ctrl_access: + * - Switches the rtc back to bank 0. + * - Unsets the spinlock on the rtc IRQ. + */ +static inline void +ds1685_rtc_end_ctrl_access(struct ds1685_priv *rtc, unsigned long flags) +{ + ds1685_rtc_switch_to_bank0(rtc); + spin_unlock_irqrestore(&rtc->lock, flags); +} + +/** + * ds1685_rtc_get_ssn - retrieve the silicon serial number. + * @rtc: pointer to the ds1685 rtc structure. + * @ssn: u8 array to hold the bits of the silicon serial number. + * + * This number starts at 0x40, and is 8-bytes long, ending at 0x47. The + * first byte is the model number, the next six bytes are the serial number + * digits, and the final byte is a CRC check byte. Together, they form the + * silicon serial number. + * + * These values are stored in bank1, so ds1685_rtc_switch_to_bank1 must be + * called first before calling this function, else data will be read out of + * the bank0 NVRAM. Be sure to call ds1685_rtc_switch_to_bank0 when done. + */ +static inline void +ds1685_rtc_get_ssn(struct ds1685_priv *rtc, u8 *ssn) +{ + ssn[0] = rtc->read(rtc, RTC_BANK1_SSN_MODEL); + ssn[1] = rtc->read(rtc, RTC_BANK1_SSN_BYTE_1); + ssn[2] = rtc->read(rtc, RTC_BANK1_SSN_BYTE_2); + ssn[3] = rtc->read(rtc, RTC_BANK1_SSN_BYTE_3); + ssn[4] = rtc->read(rtc, RTC_BANK1_SSN_BYTE_4); + ssn[5] = rtc->read(rtc, RTC_BANK1_SSN_BYTE_5); + ssn[6] = rtc->read(rtc, RTC_BANK1_SSN_BYTE_6); + ssn[7] = rtc->read(rtc, RTC_BANK1_SSN_CRC); +} +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* Read/Set Time & Alarm functions */ + +/** + * ds1685_rtc_read_time - reads the time registers. + * @dev: pointer to device structure. + * @tm: pointer to rtc_time structure. + */ +static int +ds1685_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + u8 ctrlb, century; + u8 seconds, minutes, hours, wday, mday, month, years; + + /* Fetch the time info from the RTC registers. */ + ds1685_rtc_begin_data_access(rtc); + seconds = rtc->read(rtc, RTC_SECS); + minutes = rtc->read(rtc, RTC_MINS); + hours = rtc->read(rtc, RTC_HRS); + wday = rtc->read(rtc, RTC_WDAY); + mday = rtc->read(rtc, RTC_MDAY); + month = rtc->read(rtc, RTC_MONTH); + years = rtc->read(rtc, RTC_YEAR); + century = rtc->read(rtc, RTC_CENTURY); + ctrlb = rtc->read(rtc, RTC_CTRL_B); + ds1685_rtc_end_data_access(rtc); + + /* bcd2bin if needed, perform fixups, and store to rtc_time. */ + years = ds1685_rtc_bcd2bin(rtc, years, RTC_YEAR_BCD_MASK, + RTC_YEAR_BIN_MASK); + century = ds1685_rtc_bcd2bin(rtc, century, RTC_CENTURY_MASK, + RTC_CENTURY_MASK); + tm->tm_sec = ds1685_rtc_bcd2bin(rtc, seconds, RTC_SECS_BCD_MASK, + RTC_SECS_BIN_MASK); + tm->tm_min = ds1685_rtc_bcd2bin(rtc, minutes, RTC_MINS_BCD_MASK, + RTC_MINS_BIN_MASK); + tm->tm_hour = ds1685_rtc_bcd2bin(rtc, hours, RTC_HRS_24_BCD_MASK, + RTC_HRS_24_BIN_MASK); + tm->tm_wday = (ds1685_rtc_bcd2bin(rtc, wday, RTC_WDAY_MASK, + RTC_WDAY_MASK) - 1); + tm->tm_mday = ds1685_rtc_bcd2bin(rtc, mday, RTC_MDAY_BCD_MASK, + RTC_MDAY_BIN_MASK); + tm->tm_mon = (ds1685_rtc_bcd2bin(rtc, month, RTC_MONTH_BCD_MASK, + RTC_MONTH_BIN_MASK) - 1); + tm->tm_year = ((years + (century * 100)) - 1900); + tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year); + tm->tm_isdst = 0; /* RTC has hardcoded timezone, so don't use. */ + + return rtc_valid_tm(tm); +} + +/** + * ds1685_rtc_set_time - sets the time registers. + * @dev: pointer to device structure. + * @tm: pointer to rtc_time structure. + */ +static int +ds1685_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + u8 ctrlb, seconds, minutes, hours, wday, mday, month, years, century; + + /* Fetch the time info from rtc_time. */ + seconds = ds1685_rtc_bin2bcd(rtc, tm->tm_sec, RTC_SECS_BIN_MASK, + RTC_SECS_BCD_MASK); + minutes = ds1685_rtc_bin2bcd(rtc, tm->tm_min, RTC_MINS_BIN_MASK, + RTC_MINS_BCD_MASK); + hours = ds1685_rtc_bin2bcd(rtc, tm->tm_hour, RTC_HRS_24_BIN_MASK, + RTC_HRS_24_BCD_MASK); + wday = ds1685_rtc_bin2bcd(rtc, (tm->tm_wday + 1), RTC_WDAY_MASK, + RTC_WDAY_MASK); + mday = ds1685_rtc_bin2bcd(rtc, tm->tm_mday, RTC_MDAY_BIN_MASK, + RTC_MDAY_BCD_MASK); + month = ds1685_rtc_bin2bcd(rtc, (tm->tm_mon + 1), RTC_MONTH_BIN_MASK, + RTC_MONTH_BCD_MASK); + years = ds1685_rtc_bin2bcd(rtc, (tm->tm_year % 100), + RTC_YEAR_BIN_MASK, RTC_YEAR_BCD_MASK); + century = ds1685_rtc_bin2bcd(rtc, ((tm->tm_year + 1900) / 100), + RTC_CENTURY_MASK, RTC_CENTURY_MASK); + + /* + * Perform Sanity Checks: + * - Months: !> 12, Month Day != 0. + * - Month Day !> Max days in current month. + * - Hours !>= 24, Mins !>= 60, Secs !>= 60, & Weekday !> 7. + */ + if ((tm->tm_mon > 11) || (mday == 0)) + return -EDOM; + + if (tm->tm_mday > rtc_month_days(tm->tm_mon, tm->tm_year)) + return -EDOM; + + if ((tm->tm_hour >= 24) || (tm->tm_min >= 60) || + (tm->tm_sec >= 60) || (wday > 7)) + return -EDOM; + + /* + * Set the data mode to use and store the time values in the + * RTC registers. + */ + ds1685_rtc_begin_data_access(rtc); + ctrlb = rtc->read(rtc, RTC_CTRL_B); + if (rtc->bcd_mode) + ctrlb &= ~(RTC_CTRL_B_DM); + else + ctrlb |= RTC_CTRL_B_DM; + rtc->write(rtc, RTC_CTRL_B, ctrlb); + rtc->write(rtc, RTC_SECS, seconds); + rtc->write(rtc, RTC_MINS, minutes); + rtc->write(rtc, RTC_HRS, hours); + rtc->write(rtc, RTC_WDAY, wday); + rtc->write(rtc, RTC_MDAY, mday); + rtc->write(rtc, RTC_MONTH, month); + rtc->write(rtc, RTC_YEAR, years); + rtc->write(rtc, RTC_CENTURY, century); + ds1685_rtc_end_data_access(rtc); + + return 0; +} + +/** + * ds1685_rtc_read_alarm - reads the alarm registers. + * @dev: pointer to device structure. + * @alrm: pointer to rtc_wkalrm structure. + * + * There are three primary alarm registers: seconds, minutes, and hours. + * A fourth alarm register for the month date is also available in bank1 for + * kickstart/wakeup features. The DS1685/DS1687 manual states that a + * "don't care" value ranging from 0xc0 to 0xff may be written into one or + * more of the three alarm bytes to act as a wildcard value. The fourth + * byte doesn't support a "don't care" value. + */ +static int +ds1685_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + u8 seconds, minutes, hours, mday, ctrlb, ctrlc; + + /* Fetch the alarm info from the RTC alarm registers. */ + ds1685_rtc_begin_data_access(rtc); + seconds = rtc->read(rtc, RTC_SECS_ALARM); + minutes = rtc->read(rtc, RTC_MINS_ALARM); + hours = rtc->read(rtc, RTC_HRS_ALARM); + mday = rtc->read(rtc, RTC_MDAY_ALARM); + ctrlb = rtc->read(rtc, RTC_CTRL_B); + ctrlc = rtc->read(rtc, RTC_CTRL_C); + ds1685_rtc_end_data_access(rtc); + + /* Check month date. */ + if (!(mday >= 1) && (mday <= 31)) + return -EDOM; + + /* + * Check the three alarm bytes. + * + * The Linux RTC system doesn't support the "don't care" capability + * of this RTC chip. We check for it anyways in case support is + * added in the future. + */ + if (unlikely((seconds >= 0xc0) && (seconds <= 0xff))) + alrm->time.tm_sec = -1; + else + alrm->time.tm_sec = ds1685_rtc_bcd2bin(rtc, seconds, + RTC_SECS_BCD_MASK, + RTC_SECS_BIN_MASK); + + if (unlikely((minutes >= 0xc0) && (minutes <= 0xff))) + alrm->time.tm_min = -1; + else + alrm->time.tm_min = ds1685_rtc_bcd2bin(rtc, minutes, + RTC_MINS_BCD_MASK, + RTC_MINS_BIN_MASK); + + if (unlikely((hours >= 0xc0) && (hours <= 0xff))) + alrm->time.tm_hour = -1; + else + alrm->time.tm_hour = ds1685_rtc_bcd2bin(rtc, hours, + RTC_HRS_24_BCD_MASK, + RTC_HRS_24_BIN_MASK); + + /* Write the data to rtc_wkalrm. */ + alrm->time.tm_mday = ds1685_rtc_bcd2bin(rtc, mday, RTC_MDAY_BCD_MASK, + RTC_MDAY_BIN_MASK); + alrm->time.tm_mon = -1; + alrm->time.tm_year = -1; + alrm->time.tm_wday = -1; + alrm->time.tm_yday = -1; + alrm->time.tm_isdst = -1; + alrm->enabled = !!(ctrlb & RTC_CTRL_B_AIE); + alrm->pending = !!(ctrlc & RTC_CTRL_C_AF); + + return 0; +} + +/** + * ds1685_rtc_set_alarm - sets the alarm in registers. + * @dev: pointer to device structure. + * @alrm: pointer to rtc_wkalrm structure. + */ +static int +ds1685_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + u8 ctrlb, seconds, minutes, hours, mday; + + /* Fetch the alarm info and convert to BCD. */ + seconds = ds1685_rtc_bin2bcd(rtc, alrm->time.tm_sec, + RTC_SECS_BIN_MASK, + RTC_SECS_BCD_MASK); + minutes = ds1685_rtc_bin2bcd(rtc, alrm->time.tm_min, + RTC_MINS_BIN_MASK, + RTC_MINS_BCD_MASK); + hours = ds1685_rtc_bin2bcd(rtc, alrm->time.tm_hour, + RTC_HRS_24_BIN_MASK, + RTC_HRS_24_BCD_MASK); + mday = ds1685_rtc_bin2bcd(rtc, alrm->time.tm_mday, + RTC_MDAY_BIN_MASK, + RTC_MDAY_BCD_MASK); + + /* Check the month date for validity. */ + if (!(mday >= 1) && (mday <= 31)) + return -EDOM; + + /* + * Check the three alarm bytes. + * + * The Linux RTC system doesn't support the "don't care" capability + * of this RTC chip because rtc_valid_tm tries to validate every + * field, and we only support four fields. We put the support + * here anyways for the future. + */ + if (unlikely((seconds >= 0xc0) && (seconds <= 0xff))) + seconds = 0xff; + + if (unlikely((minutes >= 0xc0) && (minutes <= 0xff))) + minutes = 0xff; + + if (unlikely((hours >= 0xc0) && (hours <= 0xff))) + hours = 0xff; + + alrm->time.tm_mon = -1; + alrm->time.tm_year = -1; + alrm->time.tm_wday = -1; + alrm->time.tm_yday = -1; + alrm->time.tm_isdst = -1; + + /* Disable the alarm interrupt first. */ + ds1685_rtc_begin_data_access(rtc); + ctrlb = rtc->read(rtc, RTC_CTRL_B); + rtc->write(rtc, RTC_CTRL_B, (ctrlb & ~(RTC_CTRL_B_AIE))); + + /* Read ctrlc to clear RTC_CTRL_C_AF. */ + rtc->read(rtc, RTC_CTRL_C); + + /* + * Set the data mode to use and store the time values in the + * RTC registers. + */ + ctrlb = rtc->read(rtc, RTC_CTRL_B); + if (rtc->bcd_mode) + ctrlb &= ~(RTC_CTRL_B_DM); + else + ctrlb |= RTC_CTRL_B_DM; + rtc->write(rtc, RTC_CTRL_B, ctrlb); + rtc->write(rtc, RTC_SECS_ALARM, seconds); + rtc->write(rtc, RTC_MINS_ALARM, minutes); + rtc->write(rtc, RTC_HRS_ALARM, hours); + rtc->write(rtc, RTC_MDAY_ALARM, mday); + + /* Re-enable the alarm if needed. */ + if (alrm->enabled) { + ctrlb = rtc->read(rtc, RTC_CTRL_B); + ctrlb |= RTC_CTRL_B_AIE; + rtc->write(rtc, RTC_CTRL_B, ctrlb); + } + + /* Done! */ + ds1685_rtc_end_data_access(rtc); + + return 0; +} +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* /dev/rtcX Interface functions */ + +#ifdef CONFIG_RTC_INTF_DEV +/** + * ds1685_rtc_alarm_irq_enable - replaces ioctl() RTC_AIE on/off. + * @dev: pointer to device structure. + * @enabled: flag indicating whether to enable or disable. + */ +static int +ds1685_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct ds1685_priv *rtc = dev_get_drvdata(dev); + unsigned long flags = 0; + + /* Enable/disable the Alarm IRQ-Enable flag. */ + spin_lock_irqsave(&rtc->lock, flags); + + /* Flip the requisite interrupt-enable bit. */ + if (enabled) + rtc->write(rtc, RTC_CTRL_B, (rtc->read(rtc, RTC_CTRL_B) | + RTC_CTRL_B_AIE)); + else + rtc->write(rtc, RTC_CTRL_B, (rtc->read(rtc, RTC_CTRL_B) & + ~(RTC_CTRL_B_AIE))); + + /* Read Control C to clear all the flag bits. */ + rtc->read(rtc, RTC_CTRL_C); + spin_unlock_irqrestore(&rtc->lock, flags); + + return 0; +} +#endif +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* IRQ handler & workqueue. */ + +/** + * ds1685_rtc_irq_handler - IRQ handler. + * @irq: IRQ number. + * @dev_id: platform device pointer. + */ +static irqreturn_t +ds1685_rtc_irq_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + u8 ctrlb, ctrlc; + unsigned long events = 0; + u8 num_irqs = 0; + + /* Abort early if the device isn't ready yet (i.e., DEBUG_SHIRQ). */ + if (unlikely(!rtc)) + return IRQ_HANDLED; + + /* Ctrlb holds the interrupt-enable bits and ctrlc the flag bits. */ + spin_lock(&rtc->lock); + ctrlb = rtc->read(rtc, RTC_CTRL_B); + ctrlc = rtc->read(rtc, RTC_CTRL_C); + + /* Is the IRQF bit set? */ + if (likely(ctrlc & RTC_CTRL_C_IRQF)) { + /* + * We need to determine if it was one of the standard + * events: PF, AF, or UF. If so, we handle them and + * update the RTC core. + */ + if (likely(ctrlc & RTC_CTRL_B_PAU_MASK)) { + events = RTC_IRQF; + + /* Check for a periodic interrupt. */ + if ((ctrlb & RTC_CTRL_B_PIE) && + (ctrlc & RTC_CTRL_C_PF)) { + events |= RTC_PF; + num_irqs++; + } + + /* Check for an alarm interrupt. */ + if ((ctrlb & RTC_CTRL_B_AIE) && + (ctrlc & RTC_CTRL_C_AF)) { + events |= RTC_AF; + num_irqs++; + } + + /* Check for an update interrupt. */ + if ((ctrlb & RTC_CTRL_B_UIE) && + (ctrlc & RTC_CTRL_C_UF)) { + events |= RTC_UF; + num_irqs++; + } + + rtc_update_irq(rtc->dev, num_irqs, events); + } else { + /* + * One of the "extended" interrupts was received that + * is not recognized by the RTC core. These need to + * be handled in task context as they can call other + * functions and the time spent in irq context needs + * to be minimized. Schedule them into a workqueue + * and inform the RTC core that the IRQs were handled. + */ + spin_unlock(&rtc->lock); + schedule_work(&rtc->work); + rtc_update_irq(rtc->dev, 0, 0); + return IRQ_HANDLED; + } + } + spin_unlock(&rtc->lock); + + return events ? IRQ_HANDLED : IRQ_NONE; +} + +/** + * ds1685_rtc_work_queue - work queue handler. + * @work: work_struct containing data to work on in task context. + */ +static void +ds1685_rtc_work_queue(struct work_struct *work) +{ + struct ds1685_priv *rtc = container_of(work, + struct ds1685_priv, work); + struct platform_device *pdev = to_platform_device(&rtc->dev->dev); + struct mutex *rtc_mutex = &rtc->dev->ops_lock; + u8 ctrl4a, ctrl4b; + + mutex_lock(rtc_mutex); + + ds1685_rtc_switch_to_bank1(rtc); + ctrl4a = rtc->read(rtc, RTC_EXT_CTRL_4A); + ctrl4b = rtc->read(rtc, RTC_EXT_CTRL_4B); + + /* + * Check for a kickstart interrupt. With Vcc applied, this + * typically means that the power button was pressed, so we + * begin the shutdown sequence. + */ + if ((ctrl4b & RTC_CTRL_4B_KSE) && (ctrl4a & RTC_CTRL_4A_KF)) { + /* Briefly disable kickstarts to debounce button presses. */ + rtc->write(rtc, RTC_EXT_CTRL_4B, + (rtc->read(rtc, RTC_EXT_CTRL_4B) & + ~(RTC_CTRL_4B_KSE))); + + /* Clear the kickstart flag. */ + rtc->write(rtc, RTC_EXT_CTRL_4A, + (ctrl4a & ~(RTC_CTRL_4A_KF))); + + + /* + * Sleep 500ms before re-enabling kickstarts. This allows + * adequate time to avoid reading signal jitter as additional + * button presses. + */ + msleep(500); + rtc->write(rtc, RTC_EXT_CTRL_4B, + (rtc->read(rtc, RTC_EXT_CTRL_4B) | + RTC_CTRL_4B_KSE)); + + /* Call the platform pre-poweroff function. Else, shutdown. */ + if (rtc->prepare_poweroff != NULL) + rtc->prepare_poweroff(); + else + ds1685_rtc_poweroff(pdev); + } + + /* + * Check for a wake-up interrupt. With Vcc applied, this is + * essentially a second alarm interrupt, except it takes into + * account the 'date' register in bank1 in addition to the + * standard three alarm registers. + */ + if ((ctrl4b & RTC_CTRL_4B_WIE) && (ctrl4a & RTC_CTRL_4A_WF)) { + rtc->write(rtc, RTC_EXT_CTRL_4A, + (ctrl4a & ~(RTC_CTRL_4A_WF))); + + /* Call the platform wake_alarm function if defined. */ + if (rtc->wake_alarm != NULL) + rtc->wake_alarm(); + else + dev_warn(&pdev->dev, + "Wake Alarm IRQ just occurred!\n"); + } + + /* + * Check for a ram-clear interrupt. This happens if RIE=1 and RF=0 + * when RCE=1 in 4B. This clears all NVRAM bytes in bank0 by setting + * each byte to a logic 1. This has no effect on any extended + * NV-SRAM that might be present, nor on the time/calendar/alarm + * registers. After a ram-clear is completed, there is a minimum + * recovery time of ~150ms in which all reads/writes are locked out. + * NOTE: A ram-clear can still occur if RCE=1 and RIE=0. We cannot + * catch this scenario. + */ + if ((ctrl4b & RTC_CTRL_4B_RIE) && (ctrl4a & RTC_CTRL_4A_RF)) { + rtc->write(rtc, RTC_EXT_CTRL_4A, + (ctrl4a & ~(RTC_CTRL_4A_RF))); + msleep(150); + + /* Call the platform post_ram_clear function if defined. */ + if (rtc->post_ram_clear != NULL) + rtc->post_ram_clear(); + else + dev_warn(&pdev->dev, + "RAM-Clear IRQ just occurred!\n"); + } + ds1685_rtc_switch_to_bank0(rtc); + + mutex_unlock(rtc_mutex); +} +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* ProcFS interface */ + +#ifdef CONFIG_PROC_FS +#define NUM_REGS 6 /* Num of control registers. */ +#define NUM_BITS 8 /* Num bits per register. */ +#define NUM_SPACES 4 /* Num spaces between each bit. */ + +/* + * Periodic Interrupt Rates. + */ +static const char *ds1685_rtc_pirq_rate[16] = { + "none", "3.90625ms", "7.8125ms", "0.122070ms", "0.244141ms", + "0.488281ms", "0.9765625ms", "1.953125ms", "3.90625ms", "7.8125ms", + "15.625ms", "31.25ms", "62.5ms", "125ms", "250ms", "500ms" +}; + +/* + * Square-Wave Output Frequencies. + */ +static const char *ds1685_rtc_sqw_freq[16] = { + "none", "256Hz", "128Hz", "8192Hz", "4096Hz", "2048Hz", "1024Hz", + "512Hz", "256Hz", "128Hz", "64Hz", "32Hz", "16Hz", "8Hz", "4Hz", "2Hz" +}; + +#ifdef CONFIG_RTC_DS1685_PROC_REGS +/** + * ds1685_rtc_print_regs - helper function to print register values. + * @hex: hex byte to convert into binary bits. + * @dest: destination char array. + * + * This is basically a hex->binary function, just with extra spacing between + * the digits. It only works on 1-byte values (8 bits). + */ +static char* +ds1685_rtc_print_regs(u8 hex, char *dest) +{ + u32 i, j; + char *tmp = dest; + + for (i = 0; i < NUM_BITS; i++) { + *tmp++ = ((hex & 0x80) != 0 ? '1' : '0'); + for (j = 0; j < NUM_SPACES; j++) + *tmp++ = ' '; + hex <<= 1; + } + *tmp++ = '\0'; + + return dest; +} +#endif + +/** + * ds1685_rtc_proc - procfs access function. + * @dev: pointer to device structure. + * @seq: pointer to seq_file structure. + */ +static int +ds1685_rtc_proc(struct device *dev, struct seq_file *seq) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + u8 ctrla, ctrlb, ctrlc, ctrld, ctrl4a, ctrl4b, ssn[8]; + char *model = '\0'; +#ifdef CONFIG_RTC_DS1685_PROC_REGS + char bits[NUM_REGS][(NUM_BITS * NUM_SPACES) + NUM_BITS + 1]; +#endif + + /* Read all the relevant data from the control registers. */ + ds1685_rtc_switch_to_bank1(rtc); + ds1685_rtc_get_ssn(rtc, ssn); + ctrla = rtc->read(rtc, RTC_CTRL_A); + ctrlb = rtc->read(rtc, RTC_CTRL_B); + ctrlc = rtc->read(rtc, RTC_CTRL_C); + ctrld = rtc->read(rtc, RTC_CTRL_D); + ctrl4a = rtc->read(rtc, RTC_EXT_CTRL_4A); + ctrl4b = rtc->read(rtc, RTC_EXT_CTRL_4B); + ds1685_rtc_switch_to_bank0(rtc); + + /* Determine the RTC model. */ + switch (ssn[0]) { + case RTC_MODEL_DS1685: + model = "DS1685/DS1687\0"; + break; + case RTC_MODEL_DS1689: + model = "DS1689/DS1693\0"; + break; + case RTC_MODEL_DS17285: + model = "DS17285/DS17287\0"; + break; + case RTC_MODEL_DS17485: + model = "DS17485/DS17487\0"; + break; + case RTC_MODEL_DS17885: + model = "DS17885/DS17887\0"; + break; + default: + model = "Unknown\0"; + break; + } + + /* Print out the information. */ + seq_printf(seq, + "Model\t\t: %s\n" + "Oscillator\t: %s\n" + "12/24hr\t\t: %s\n" + "DST\t\t: %s\n" + "Data mode\t: %s\n" + "Battery\t\t: %s\n" + "Aux batt\t: %s\n" + "Update IRQ\t: %s\n" + "Periodic IRQ\t: %s\n" + "Periodic Rate\t: %s\n" + "SQW Freq\t: %s\n" +#ifdef CONFIG_RTC_DS1685_PROC_REGS + "Serial #\t: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n" + "Register Status\t:\n" + " Ctrl A\t: UIP DV2 DV1 DV0 RS3 RS2 RS1 RS0\n" + "\t\t: %s\n" + " Ctrl B\t: SET PIE AIE UIE SQWE DM 2412 DSE\n" + "\t\t: %s\n" + " Ctrl C\t: IRQF PF AF UF --- --- --- ---\n" + "\t\t: %s\n" + " Ctrl D\t: VRT --- --- --- --- --- --- ---\n" + "\t\t: %s\n" +#if !defined(CONFIG_RTC_DRV_DS1685) && !defined(CONFIG_RTC_DRV_DS1689) + " Ctrl 4A\t: VRT2 INCR BME --- PAB RF WF KF\n" +#else + " Ctrl 4A\t: VRT2 INCR --- --- PAB RF WF KF\n" +#endif + "\t\t: %s\n" + " Ctrl 4B\t: ABE E32k CS RCE PRS RIE WIE KSE\n" + "\t\t: %s\n", +#else + "Serial #\t: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", +#endif + model, + ((ctrla & RTC_CTRL_A_DV1) ? "enabled" : "disabled"), + ((ctrlb & RTC_CTRL_B_2412) ? "24-hour" : "12-hour"), + ((ctrlb & RTC_CTRL_B_DSE) ? "enabled" : "disabled"), + ((ctrlb & RTC_CTRL_B_DM) ? "binary" : "BCD"), + ((ctrld & RTC_CTRL_D_VRT) ? "ok" : "exhausted or n/a"), + ((ctrl4a & RTC_CTRL_4A_VRT2) ? "ok" : "exhausted or n/a"), + ((ctrlb & RTC_CTRL_B_UIE) ? "yes" : "no"), + ((ctrlb & RTC_CTRL_B_PIE) ? "yes" : "no"), + (!(ctrl4b & RTC_CTRL_4B_E32K) ? + ds1685_rtc_pirq_rate[(ctrla & RTC_CTRL_A_RS_MASK)] : "none"), + (!((ctrl4b & RTC_CTRL_4B_E32K)) ? + ds1685_rtc_sqw_freq[(ctrla & RTC_CTRL_A_RS_MASK)] : "32768Hz"), +#ifdef CONFIG_RTC_DS1685_PROC_REGS + ssn[0], ssn[1], ssn[2], ssn[3], ssn[4], ssn[5], ssn[6], ssn[7], + ds1685_rtc_print_regs(ctrla, bits[0]), + ds1685_rtc_print_regs(ctrlb, bits[1]), + ds1685_rtc_print_regs(ctrlc, bits[2]), + ds1685_rtc_print_regs(ctrld, bits[3]), + ds1685_rtc_print_regs(ctrl4a, bits[4]), + ds1685_rtc_print_regs(ctrl4b, bits[5])); +#else + ssn[0], ssn[1], ssn[2], ssn[3], ssn[4], ssn[5], ssn[6], ssn[7]); +#endif + return 0; +} +#else +#define ds1685_rtc_proc NULL +#endif /* CONFIG_PROC_FS */ +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* RTC Class operations */ + +static const struct rtc_class_ops +ds1685_rtc_ops = { + .proc = ds1685_rtc_proc, + .read_time = ds1685_rtc_read_time, + .set_time = ds1685_rtc_set_time, + .read_alarm = ds1685_rtc_read_alarm, + .set_alarm = ds1685_rtc_set_alarm, + .alarm_irq_enable = ds1685_rtc_alarm_irq_enable, +}; +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* SysFS interface */ + +#ifdef CONFIG_SYSFS +/** + * ds1685_rtc_sysfs_nvram_read - reads rtc nvram via sysfs. + * @file: pointer to file structure. + * @kobj: pointer to kobject structure. + * @bin_attr: pointer to bin_attribute structure. + * @buf: pointer to char array to hold the output. + * @pos: current file position pointer. + * @size: size of the data to read. + */ +static ssize_t +ds1685_rtc_sysfs_nvram_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t size) +{ + struct platform_device *pdev = + to_platform_device(container_of(kobj, struct device, kobj)); + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + ssize_t count; + unsigned long flags = 0; + + spin_lock_irqsave(&rtc->lock, flags); + ds1685_rtc_switch_to_bank0(rtc); + + /* Read NVRAM in time and bank0 registers. */ + for (count = 0; size > 0 && pos < NVRAM_TOTAL_SZ_BANK0; + count++, size--) { + if (count < NVRAM_SZ_TIME) + *buf++ = rtc->read(rtc, (NVRAM_TIME_BASE + pos++)); + else + *buf++ = rtc->read(rtc, (NVRAM_BANK0_BASE + pos++)); + } + +#ifndef CONFIG_RTC_DRV_DS1689 + if (size > 0) { + ds1685_rtc_switch_to_bank1(rtc); + +#ifndef CONFIG_RTC_DRV_DS1685 + /* Enable burst-mode on DS17x85/DS17x87 */ + rtc->write(rtc, RTC_EXT_CTRL_4A, + (rtc->read(rtc, RTC_EXT_CTRL_4A) | + RTC_CTRL_4A_BME)); + + /* We need one write to RTC_BANK1_RAM_ADDR_LSB to start + * reading with burst-mode */ + rtc->write(rtc, RTC_BANK1_RAM_ADDR_LSB, + (pos - NVRAM_TOTAL_SZ_BANK0)); +#endif + + /* Read NVRAM in bank1 registers. */ + for (count = 0; size > 0 && pos < NVRAM_TOTAL_SZ; + count++, size--) { +#ifdef CONFIG_RTC_DRV_DS1685 + /* DS1685/DS1687 has to write to RTC_BANK1_RAM_ADDR + * before each read. */ + rtc->write(rtc, RTC_BANK1_RAM_ADDR, + (pos - NVRAM_TOTAL_SZ_BANK0)); +#endif + *buf++ = rtc->read(rtc, RTC_BANK1_RAM_DATA_PORT); + pos++; + } + +#ifndef CONFIG_RTC_DRV_DS1685 + /* Disable burst-mode on DS17x85/DS17x87 */ + rtc->write(rtc, RTC_EXT_CTRL_4A, + (rtc->read(rtc, RTC_EXT_CTRL_4A) & + ~(RTC_CTRL_4A_BME))); +#endif + ds1685_rtc_switch_to_bank0(rtc); + } +#endif /* !CONFIG_RTC_DRV_DS1689 */ + spin_unlock_irqrestore(&rtc->lock, flags); + + /* + * XXX: Bug? this appears to cause the function to get executed + * several times in succession. But it's the only way to actually get + * data written out to a file. + */ + return count; +} + +/** + * ds1685_rtc_sysfs_nvram_write - writes rtc nvram via sysfs. + * @file: pointer to file structure. + * @kobj: pointer to kobject structure. + * @bin_attr: pointer to bin_attribute structure. + * @buf: pointer to char array to hold the input. + * @pos: current file position pointer. + * @size: size of the data to write. + */ +static ssize_t +ds1685_rtc_sysfs_nvram_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t size) +{ + struct platform_device *pdev = + to_platform_device(container_of(kobj, struct device, kobj)); + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + ssize_t count; + unsigned long flags = 0; + + spin_lock_irqsave(&rtc->lock, flags); + ds1685_rtc_switch_to_bank0(rtc); + + /* Write NVRAM in time and bank0 registers. */ + for (count = 0; size > 0 && pos < NVRAM_TOTAL_SZ_BANK0; + count++, size--) + if (count < NVRAM_SZ_TIME) + rtc->write(rtc, (NVRAM_TIME_BASE + pos++), + *buf++); + else + rtc->write(rtc, (NVRAM_BANK0_BASE), *buf++); + +#ifndef CONFIG_RTC_DRV_DS1689 + if (size > 0) { + ds1685_rtc_switch_to_bank1(rtc); + +#ifndef CONFIG_RTC_DRV_DS1685 + /* Enable burst-mode on DS17x85/DS17x87 */ + rtc->write(rtc, RTC_EXT_CTRL_4A, + (rtc->read(rtc, RTC_EXT_CTRL_4A) | + RTC_CTRL_4A_BME)); + + /* We need one write to RTC_BANK1_RAM_ADDR_LSB to start + * writing with burst-mode */ + rtc->write(rtc, RTC_BANK1_RAM_ADDR_LSB, + (pos - NVRAM_TOTAL_SZ_BANK0)); +#endif + + /* Write NVRAM in bank1 registers. */ + for (count = 0; size > 0 && pos < NVRAM_TOTAL_SZ; + count++, size--) { +#ifdef CONFIG_RTC_DRV_DS1685 + /* DS1685/DS1687 has to write to RTC_BANK1_RAM_ADDR + * before each read. */ + rtc->write(rtc, RTC_BANK1_RAM_ADDR, + (pos - NVRAM_TOTAL_SZ_BANK0)); +#endif + rtc->write(rtc, RTC_BANK1_RAM_DATA_PORT, *buf++); + pos++; + } + +#ifndef CONFIG_RTC_DRV_DS1685 + /* Disable burst-mode on DS17x85/DS17x87 */ + rtc->write(rtc, RTC_EXT_CTRL_4A, + (rtc->read(rtc, RTC_EXT_CTRL_4A) & + ~(RTC_CTRL_4A_BME))); +#endif + ds1685_rtc_switch_to_bank0(rtc); + } +#endif /* !CONFIG_RTC_DRV_DS1689 */ + spin_unlock_irqrestore(&rtc->lock, flags); + + return count; +} + +/** + * struct ds1685_rtc_sysfs_nvram_attr - sysfs attributes for rtc nvram. + * @attr: nvram attributes. + * @read: nvram read function. + * @write: nvram write function. + * @size: nvram total size (bank0 + extended). + */ +static struct bin_attribute +ds1685_rtc_sysfs_nvram_attr = { + .attr = { + .name = "nvram", + .mode = S_IRUGO | S_IWUSR, + }, + .read = ds1685_rtc_sysfs_nvram_read, + .write = ds1685_rtc_sysfs_nvram_write, + .size = NVRAM_TOTAL_SZ +}; + +/** + * ds1685_rtc_sysfs_battery_show - sysfs file for main battery status. + * @dev: pointer to device structure. + * @attr: pointer to device_attribute structure. + * @buf: pointer to char array to hold the output. + */ +static ssize_t +ds1685_rtc_sysfs_battery_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + u8 ctrld; + + ctrld = rtc->read(rtc, RTC_CTRL_D); + + return snprintf(buf, 13, "%s\n", + (ctrld & RTC_CTRL_D_VRT) ? "ok" : "not ok or N/A"); +} +static DEVICE_ATTR(battery, S_IRUGO, ds1685_rtc_sysfs_battery_show, NULL); + +/** + * ds1685_rtc_sysfs_auxbatt_show - sysfs file for aux battery status. + * @dev: pointer to device structure. + * @attr: pointer to device_attribute structure. + * @buf: pointer to char array to hold the output. + */ +static ssize_t +ds1685_rtc_sysfs_auxbatt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + u8 ctrl4a; + + ds1685_rtc_switch_to_bank1(rtc); + ctrl4a = rtc->read(rtc, RTC_EXT_CTRL_4A); + ds1685_rtc_switch_to_bank0(rtc); + + return snprintf(buf, 13, "%s\n", + (ctrl4a & RTC_CTRL_4A_VRT2) ? "ok" : "not ok or N/A"); +} +static DEVICE_ATTR(auxbatt, S_IRUGO, ds1685_rtc_sysfs_auxbatt_show, NULL); + +/** + * ds1685_rtc_sysfs_serial_show - sysfs file for silicon serial number. + * @dev: pointer to device structure. + * @attr: pointer to device_attribute structure. + * @buf: pointer to char array to hold the output. + */ +static ssize_t +ds1685_rtc_sysfs_serial_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + u8 ssn[8]; + + ds1685_rtc_switch_to_bank1(rtc); + ds1685_rtc_get_ssn(rtc, ssn); + ds1685_rtc_switch_to_bank0(rtc); + + return snprintf(buf, 24, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + ssn[0], ssn[1], ssn[2], ssn[3], ssn[4], ssn[5], + ssn[6], ssn[7]); + + return 0; +} +static DEVICE_ATTR(serial, S_IRUGO, ds1685_rtc_sysfs_serial_show, NULL); + +/** + * struct ds1685_rtc_sysfs_misc_attrs - list for misc RTC features. + */ +static struct attribute* +ds1685_rtc_sysfs_misc_attrs[] = { + &dev_attr_battery.attr, + &dev_attr_auxbatt.attr, + &dev_attr_serial.attr, + NULL, +}; + +/** + * struct ds1685_rtc_sysfs_misc_grp - attr group for misc RTC features. + */ +static const struct attribute_group +ds1685_rtc_sysfs_misc_grp = { + .name = "misc", + .attrs = ds1685_rtc_sysfs_misc_attrs, +}; + +#ifdef CONFIG_RTC_DS1685_SYSFS_REGS +/** + * struct ds1685_rtc_ctrl_regs. + * @name: char pointer for the bit name. + * @reg: control register the bit is in. + * @bit: the bit's offset in the register. + */ +struct ds1685_rtc_ctrl_regs { + const char *name; + const u8 reg; + const u8 bit; +}; + +/* + * Ctrl register bit lookup table. + */ +static const struct ds1685_rtc_ctrl_regs +ds1685_ctrl_regs_table[] = { + { "uip", RTC_CTRL_A, RTC_CTRL_A_UIP }, + { "dv2", RTC_CTRL_A, RTC_CTRL_A_DV2 }, + { "dv1", RTC_CTRL_A, RTC_CTRL_A_DV1 }, + { "dv0", RTC_CTRL_A, RTC_CTRL_A_DV0 }, + { "rs3", RTC_CTRL_A, RTC_CTRL_A_RS3 }, + { "rs2", RTC_CTRL_A, RTC_CTRL_A_RS2 }, + { "rs1", RTC_CTRL_A, RTC_CTRL_A_RS1 }, + { "rs0", RTC_CTRL_A, RTC_CTRL_A_RS0 }, + { "set", RTC_CTRL_B, RTC_CTRL_B_SET }, + { "pie", RTC_CTRL_B, RTC_CTRL_B_PIE }, + { "aie", RTC_CTRL_B, RTC_CTRL_B_AIE }, + { "uie", RTC_CTRL_B, RTC_CTRL_B_UIE }, + { "sqwe", RTC_CTRL_B, RTC_CTRL_B_SQWE }, + { "dm", RTC_CTRL_B, RTC_CTRL_B_DM }, + { "2412", RTC_CTRL_B, RTC_CTRL_B_2412 }, + { "dse", RTC_CTRL_B, RTC_CTRL_B_DSE }, + { "irqf", RTC_CTRL_C, RTC_CTRL_C_IRQF }, + { "pf", RTC_CTRL_C, RTC_CTRL_C_PF }, + { "af", RTC_CTRL_C, RTC_CTRL_C_AF }, + { "uf", RTC_CTRL_C, RTC_CTRL_C_UF }, + { "vrt", RTC_CTRL_D, RTC_CTRL_D_VRT }, + { "vrt2", RTC_EXT_CTRL_4A, RTC_CTRL_4A_VRT2 }, + { "incr", RTC_EXT_CTRL_4A, RTC_CTRL_4A_INCR }, + { "pab", RTC_EXT_CTRL_4A, RTC_CTRL_4A_PAB }, + { "rf", RTC_EXT_CTRL_4A, RTC_CTRL_4A_RF }, + { "wf", RTC_EXT_CTRL_4A, RTC_CTRL_4A_WF }, + { "kf", RTC_EXT_CTRL_4A, RTC_CTRL_4A_KF }, +#if !defined(CONFIG_RTC_DRV_DS1685) && !defined(CONFIG_RTC_DRV_DS1689) + { "bme", RTC_EXT_CTRL_4A, RTC_CTRL_4A_BME }, +#endif + { "abe", RTC_EXT_CTRL_4B, RTC_CTRL_4B_ABE }, + { "e32k", RTC_EXT_CTRL_4B, RTC_CTRL_4B_E32K }, + { "cs", RTC_EXT_CTRL_4B, RTC_CTRL_4B_CS }, + { "rce", RTC_EXT_CTRL_4B, RTC_CTRL_4B_RCE }, + { "prs", RTC_EXT_CTRL_4B, RTC_CTRL_4B_PRS }, + { "rie", RTC_EXT_CTRL_4B, RTC_CTRL_4B_RIE }, + { "wie", RTC_EXT_CTRL_4B, RTC_CTRL_4B_WIE }, + { "kse", RTC_EXT_CTRL_4B, RTC_CTRL_4B_KSE }, + { NULL, 0, 0 }, +}; + +/** + * ds1685_rtc_sysfs_ctrl_regs_lookup - ctrl register bit lookup function. + * @name: ctrl register bit to look up in ds1685_ctrl_regs_table. + */ +static const struct ds1685_rtc_ctrl_regs* +ds1685_rtc_sysfs_ctrl_regs_lookup(const char *name) +{ + const struct ds1685_rtc_ctrl_regs *p = ds1685_ctrl_regs_table; + + for (; p->name != NULL; ++p) + if (strcmp(p->name, name) == 0) + return p; + + return NULL; +} + +/** + * ds1685_rtc_sysfs_ctrl_regs_show - reads a ctrl register bit via sysfs. + * @dev: pointer to device structure. + * @attr: pointer to device_attribute structure. + * @buf: pointer to char array to hold the output. + */ +static ssize_t +ds1685_rtc_sysfs_ctrl_regs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 tmp; + struct ds1685_priv *rtc = dev_get_drvdata(dev); + const struct ds1685_rtc_ctrl_regs *reg_info = + ds1685_rtc_sysfs_ctrl_regs_lookup(attr->attr.name); + + /* Make sure we actually matched something. */ + if (!reg_info) + return -EINVAL; + + /* No spinlock during a read -- mutex is already held. */ + ds1685_rtc_switch_to_bank1(rtc); + tmp = rtc->read(rtc, reg_info->reg) & reg_info->bit; + ds1685_rtc_switch_to_bank0(rtc); + + return snprintf(buf, 2, "%d\n", (tmp ? 1 : 0)); +} + +/** + * ds1685_rtc_sysfs_ctrl_regs_store - writes a ctrl register bit via sysfs. + * @dev: pointer to device structure. + * @attr: pointer to device_attribute structure. + * @buf: pointer to char array to hold the output. + * @count: number of bytes written. + */ +static ssize_t +ds1685_rtc_sysfs_ctrl_regs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ds1685_priv *rtc = dev_get_drvdata(dev); + u8 reg = 0, bit = 0, tmp; + unsigned long flags = 0; + long int val = 0; + const struct ds1685_rtc_ctrl_regs *reg_info = + ds1685_rtc_sysfs_ctrl_regs_lookup(attr->attr.name); + + /* We only accept numbers. */ + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + /* bits are binary, 0 or 1 only. */ + if ((val != 0) && (val != 1)) + return -ERANGE; + + /* Make sure we actually matched something. */ + if (!reg_info) + return -EINVAL; + + reg = reg_info->reg; + bit = reg_info->bit; + + /* Safe to spinlock during a write. */ + ds1685_rtc_begin_ctrl_access(rtc, flags); + tmp = rtc->read(rtc, reg); + rtc->write(rtc, reg, (val ? (tmp | bit) : (tmp & ~(bit)))); + ds1685_rtc_end_ctrl_access(rtc, flags); + + return count; +} + +/** + * DS1685_RTC_SYSFS_CTRL_REG_RO - device_attribute for read-only register bit. + * @bit: bit to read. + */ +#define DS1685_RTC_SYSFS_CTRL_REG_RO(bit) \ + static DEVICE_ATTR(bit, S_IRUGO, \ + ds1685_rtc_sysfs_ctrl_regs_show, NULL) + +/** + * DS1685_RTC_SYSFS_CTRL_REG_RW - device_attribute for read-write register bit. + * @bit: bit to read or write. + */ +#define DS1685_RTC_SYSFS_CTRL_REG_RW(bit) \ + static DEVICE_ATTR(bit, S_IRUGO | S_IWUSR, \ + ds1685_rtc_sysfs_ctrl_regs_show, \ + ds1685_rtc_sysfs_ctrl_regs_store) + +/* + * Control Register A bits. + */ +DS1685_RTC_SYSFS_CTRL_REG_RO(uip); +DS1685_RTC_SYSFS_CTRL_REG_RW(dv2); +DS1685_RTC_SYSFS_CTRL_REG_RW(dv1); +DS1685_RTC_SYSFS_CTRL_REG_RO(dv0); +DS1685_RTC_SYSFS_CTRL_REG_RW(rs3); +DS1685_RTC_SYSFS_CTRL_REG_RW(rs2); +DS1685_RTC_SYSFS_CTRL_REG_RW(rs1); +DS1685_RTC_SYSFS_CTRL_REG_RW(rs0); + +static struct attribute* +ds1685_rtc_sysfs_ctrla_attrs[] = { + &dev_attr_uip.attr, + &dev_attr_dv2.attr, + &dev_attr_dv1.attr, + &dev_attr_dv0.attr, + &dev_attr_rs3.attr, + &dev_attr_rs2.attr, + &dev_attr_rs1.attr, + &dev_attr_rs0.attr, + NULL, +}; + +static const struct attribute_group +ds1685_rtc_sysfs_ctrla_grp = { + .name = "ctrla", + .attrs = ds1685_rtc_sysfs_ctrla_attrs, +}; + + +/* + * Control Register B bits. + */ +DS1685_RTC_SYSFS_CTRL_REG_RO(set); +DS1685_RTC_SYSFS_CTRL_REG_RW(pie); +DS1685_RTC_SYSFS_CTRL_REG_RW(aie); +DS1685_RTC_SYSFS_CTRL_REG_RW(uie); +DS1685_RTC_SYSFS_CTRL_REG_RW(sqwe); +DS1685_RTC_SYSFS_CTRL_REG_RO(dm); +DS1685_RTC_SYSFS_CTRL_REG_RO(2412); +DS1685_RTC_SYSFS_CTRL_REG_RO(dse); + +static struct attribute* +ds1685_rtc_sysfs_ctrlb_attrs[] = { + &dev_attr_set.attr, + &dev_attr_pie.attr, + &dev_attr_aie.attr, + &dev_attr_uie.attr, + &dev_attr_sqwe.attr, + &dev_attr_dm.attr, + &dev_attr_2412.attr, + &dev_attr_dse.attr, + NULL, +}; + +static const struct attribute_group +ds1685_rtc_sysfs_ctrlb_grp = { + .name = "ctrlb", + .attrs = ds1685_rtc_sysfs_ctrlb_attrs, +}; + +/* + * Control Register C bits. + * + * Reading Control C clears these bits! Reading them individually can + * possibly cause an interrupt to be missed. Use the /proc interface + * to see all the bits in this register simultaneously. + */ +DS1685_RTC_SYSFS_CTRL_REG_RO(irqf); +DS1685_RTC_SYSFS_CTRL_REG_RO(pf); +DS1685_RTC_SYSFS_CTRL_REG_RO(af); +DS1685_RTC_SYSFS_CTRL_REG_RO(uf); + +static struct attribute* +ds1685_rtc_sysfs_ctrlc_attrs[] = { + &dev_attr_irqf.attr, + &dev_attr_pf.attr, + &dev_attr_af.attr, + &dev_attr_uf.attr, + NULL, +}; + +static const struct attribute_group +ds1685_rtc_sysfs_ctrlc_grp = { + .name = "ctrlc", + .attrs = ds1685_rtc_sysfs_ctrlc_attrs, +}; + +/* + * Control Register D bits. + */ +DS1685_RTC_SYSFS_CTRL_REG_RO(vrt); + +static struct attribute* +ds1685_rtc_sysfs_ctrld_attrs[] = { + &dev_attr_vrt.attr, + NULL, +}; + +static const struct attribute_group +ds1685_rtc_sysfs_ctrld_grp = { + .name = "ctrld", + .attrs = ds1685_rtc_sysfs_ctrld_attrs, +}; + +/* + * Control Register 4A bits. + */ +DS1685_RTC_SYSFS_CTRL_REG_RO(vrt2); +DS1685_RTC_SYSFS_CTRL_REG_RO(incr); +DS1685_RTC_SYSFS_CTRL_REG_RW(pab); +DS1685_RTC_SYSFS_CTRL_REG_RW(rf); +DS1685_RTC_SYSFS_CTRL_REG_RW(wf); +DS1685_RTC_SYSFS_CTRL_REG_RW(kf); +#if !defined(CONFIG_RTC_DRV_DS1685) && !defined(CONFIG_RTC_DRV_DS1689) +DS1685_RTC_SYSFS_CTRL_REG_RO(bme); +#endif + +static struct attribute* +ds1685_rtc_sysfs_ctrl4a_attrs[] = { + &dev_attr_vrt2.attr, + &dev_attr_incr.attr, + &dev_attr_pab.attr, + &dev_attr_rf.attr, + &dev_attr_wf.attr, + &dev_attr_kf.attr, +#if !defined(CONFIG_RTC_DRV_DS1685) && !defined(CONFIG_RTC_DRV_DS1689) + &dev_attr_bme.attr, +#endif + NULL, +}; + +static const struct attribute_group +ds1685_rtc_sysfs_ctrl4a_grp = { + .name = "ctrl4a", + .attrs = ds1685_rtc_sysfs_ctrl4a_attrs, +}; + +/* + * Control Register 4B bits. + */ +DS1685_RTC_SYSFS_CTRL_REG_RW(abe); +DS1685_RTC_SYSFS_CTRL_REG_RW(e32k); +DS1685_RTC_SYSFS_CTRL_REG_RO(cs); +DS1685_RTC_SYSFS_CTRL_REG_RW(rce); +DS1685_RTC_SYSFS_CTRL_REG_RW(prs); +DS1685_RTC_SYSFS_CTRL_REG_RW(rie); +DS1685_RTC_SYSFS_CTRL_REG_RW(wie); +DS1685_RTC_SYSFS_CTRL_REG_RW(kse); + +static struct attribute* +ds1685_rtc_sysfs_ctrl4b_attrs[] = { + &dev_attr_abe.attr, + &dev_attr_e32k.attr, + &dev_attr_cs.attr, + &dev_attr_rce.attr, + &dev_attr_prs.attr, + &dev_attr_rie.attr, + &dev_attr_wie.attr, + &dev_attr_kse.attr, + NULL, +}; + +static const struct attribute_group +ds1685_rtc_sysfs_ctrl4b_grp = { + .name = "ctrl4b", + .attrs = ds1685_rtc_sysfs_ctrl4b_attrs, +}; + + +/** + * struct ds1685_rtc_ctrl_regs. + * @name: char pointer for the bit name. + * @reg: control register the bit is in. + * @bit: the bit's offset in the register. + */ +struct ds1685_rtc_time_regs { + const char *name; + const u8 reg; + const u8 mask; + const u8 min; + const u8 max; +}; + +/* + * Time/Date register lookup tables. + */ +static const struct ds1685_rtc_time_regs +ds1685_time_regs_bcd_table[] = { + { "seconds", RTC_SECS, RTC_SECS_BCD_MASK, 0, 59 }, + { "minutes", RTC_MINS, RTC_MINS_BCD_MASK, 0, 59 }, + { "hours", RTC_HRS, RTC_HRS_24_BCD_MASK, 0, 23 }, + { "wday", RTC_WDAY, RTC_WDAY_MASK, 1, 7 }, + { "mday", RTC_MDAY, RTC_MDAY_BCD_MASK, 1, 31 }, + { "month", RTC_MONTH, RTC_MONTH_BCD_MASK, 1, 12 }, + { "year", RTC_YEAR, RTC_YEAR_BCD_MASK, 0, 99 }, + { "century", RTC_CENTURY, RTC_CENTURY_MASK, 0, 99 }, + { "alarm_seconds", RTC_SECS_ALARM, RTC_SECS_BCD_MASK, 0, 59 }, + { "alarm_minutes", RTC_MINS_ALARM, RTC_MINS_BCD_MASK, 0, 59 }, + { "alarm_hours", RTC_HRS_ALARM, RTC_HRS_24_BCD_MASK, 0, 23 }, + { "alarm_mday", RTC_MDAY_ALARM, RTC_MDAY_ALARM_MASK, 1, 31 }, + { NULL, 0, 0, 0, 0 }, +}; + +static const struct ds1685_rtc_time_regs +ds1685_time_regs_bin_table[] = { + { "seconds", RTC_SECS, RTC_SECS_BIN_MASK, 0x00, 0x3b }, + { "minutes", RTC_MINS, RTC_MINS_BIN_MASK, 0x00, 0x3b }, + { "hours", RTC_HRS, RTC_HRS_24_BIN_MASK, 0x00, 0x17 }, + { "wday", RTC_WDAY, RTC_WDAY_MASK, 0x01, 0x07 }, + { "mday", RTC_MDAY, RTC_MDAY_BIN_MASK, 0x01, 0x1f }, + { "month", RTC_MONTH, RTC_MONTH_BIN_MASK, 0x01, 0x0c }, + { "year", RTC_YEAR, RTC_YEAR_BIN_MASK, 0x00, 0x63 }, + { "century", RTC_CENTURY, RTC_CENTURY_MASK, 0x00, 0x63 }, + { "alarm_seconds", RTC_SECS_ALARM, RTC_SECS_BIN_MASK, 0x00, 0x3b }, + { "alarm_minutes", RTC_MINS_ALARM, RTC_MINS_BIN_MASK, 0x00, 0x3b }, + { "alarm_hours", RTC_HRS_ALARM, RTC_HRS_24_BIN_MASK, 0x00, 0x17 }, + { "alarm_mday", RTC_MDAY_ALARM, RTC_MDAY_ALARM_MASK, 0x01, 0x1f }, + { NULL, 0, 0, 0x00, 0x00 }, +}; + +/** + * ds1685_rtc_sysfs_time_regs_bcd_lookup - time/date reg bit lookup function. + * @name: register bit to look up in ds1685_time_regs_bcd_table. + */ +static const struct ds1685_rtc_time_regs* +ds1685_rtc_sysfs_time_regs_lookup(const char *name, bool bcd_mode) +{ + const struct ds1685_rtc_time_regs *p; + + if (bcd_mode) + p = ds1685_time_regs_bcd_table; + else + p = ds1685_time_regs_bin_table; + + for (; p->name != NULL; ++p) + if (strcmp(p->name, name) == 0) + return p; + + return NULL; +} + +/** + * ds1685_rtc_sysfs_time_regs_show - reads a time/date register via sysfs. + * @dev: pointer to device structure. + * @attr: pointer to device_attribute structure. + * @buf: pointer to char array to hold the output. + */ +static ssize_t +ds1685_rtc_sysfs_time_regs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 tmp; + struct ds1685_priv *rtc = dev_get_drvdata(dev); + const struct ds1685_rtc_time_regs *bcd_reg_info = + ds1685_rtc_sysfs_time_regs_lookup(attr->attr.name, true); + const struct ds1685_rtc_time_regs *bin_reg_info = + ds1685_rtc_sysfs_time_regs_lookup(attr->attr.name, false); + + /* Make sure we actually matched something. */ + if (!bcd_reg_info && !bin_reg_info) + return -EINVAL; + + /* bcd_reg_info->reg == bin_reg_info->reg. */ + ds1685_rtc_begin_data_access(rtc); + tmp = rtc->read(rtc, bcd_reg_info->reg); + ds1685_rtc_end_data_access(rtc); + + tmp = ds1685_rtc_bcd2bin(rtc, tmp, bcd_reg_info->mask, + bin_reg_info->mask); + + return snprintf(buf, 4, "%d\n", tmp); +} + +/** + * ds1685_rtc_sysfs_time_regs_store - writes a time/date register via sysfs. + * @dev: pointer to device structure. + * @attr: pointer to device_attribute structure. + * @buf: pointer to char array to hold the output. + * @count: number of bytes written. + */ +static ssize_t +ds1685_rtc_sysfs_time_regs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + long int val = 0; + struct ds1685_priv *rtc = dev_get_drvdata(dev); + const struct ds1685_rtc_time_regs *bcd_reg_info = + ds1685_rtc_sysfs_time_regs_lookup(attr->attr.name, true); + const struct ds1685_rtc_time_regs *bin_reg_info = + ds1685_rtc_sysfs_time_regs_lookup(attr->attr.name, false); + + /* We only accept numbers. */ + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + /* Make sure we actually matched something. */ + if (!bcd_reg_info && !bin_reg_info) + return -EINVAL; + + /* Check for a valid range. */ + if (rtc->bcd_mode) { + if ((val < bcd_reg_info->min) || (val > bcd_reg_info->max)) + return -ERANGE; + } else { + if ((val < bin_reg_info->min) || (val > bin_reg_info->max)) + return -ERANGE; + } + + val = ds1685_rtc_bin2bcd(rtc, val, bin_reg_info->mask, + bcd_reg_info->mask); + + /* bcd_reg_info->reg == bin_reg_info->reg. */ + ds1685_rtc_begin_data_access(rtc); + rtc->write(rtc, bcd_reg_info->reg, val); + ds1685_rtc_end_data_access(rtc); + + return count; +} + +/** + * DS1685_RTC_SYSFS_REG_RW - device_attribute for a read-write time register. + * @reg: time/date register to read or write. + */ +#define DS1685_RTC_SYSFS_TIME_REG_RW(reg) \ + static DEVICE_ATTR(reg, S_IRUGO | S_IWUSR, \ + ds1685_rtc_sysfs_time_regs_show, \ + ds1685_rtc_sysfs_time_regs_store) + +/* + * Time/Date Register bits. + */ +DS1685_RTC_SYSFS_TIME_REG_RW(seconds); +DS1685_RTC_SYSFS_TIME_REG_RW(minutes); +DS1685_RTC_SYSFS_TIME_REG_RW(hours); +DS1685_RTC_SYSFS_TIME_REG_RW(wday); +DS1685_RTC_SYSFS_TIME_REG_RW(mday); +DS1685_RTC_SYSFS_TIME_REG_RW(month); +DS1685_RTC_SYSFS_TIME_REG_RW(year); +DS1685_RTC_SYSFS_TIME_REG_RW(century); +DS1685_RTC_SYSFS_TIME_REG_RW(alarm_seconds); +DS1685_RTC_SYSFS_TIME_REG_RW(alarm_minutes); +DS1685_RTC_SYSFS_TIME_REG_RW(alarm_hours); +DS1685_RTC_SYSFS_TIME_REG_RW(alarm_mday); + +static struct attribute* +ds1685_rtc_sysfs_time_attrs[] = { + &dev_attr_seconds.attr, + &dev_attr_minutes.attr, + &dev_attr_hours.attr, + &dev_attr_wday.attr, + &dev_attr_mday.attr, + &dev_attr_month.attr, + &dev_attr_year.attr, + &dev_attr_century.attr, + NULL, +}; + +static const struct attribute_group +ds1685_rtc_sysfs_time_grp = { + .name = "datetime", + .attrs = ds1685_rtc_sysfs_time_attrs, +}; + +static struct attribute* +ds1685_rtc_sysfs_alarm_attrs[] = { + &dev_attr_alarm_seconds.attr, + &dev_attr_alarm_minutes.attr, + &dev_attr_alarm_hours.attr, + &dev_attr_alarm_mday.attr, + NULL, +}; + +static const struct attribute_group +ds1685_rtc_sysfs_alarm_grp = { + .name = "alarm", + .attrs = ds1685_rtc_sysfs_alarm_attrs, +}; +#endif /* CONFIG_RTC_DS1685_SYSFS_REGS */ + + +/** + * ds1685_rtc_sysfs_register - register sysfs files. + * @dev: pointer to device structure. + */ +static int +ds1685_rtc_sysfs_register(struct device *dev) +{ + int ret = 0; + + sysfs_bin_attr_init(&ds1685_rtc_sysfs_nvram_attr); + ret = sysfs_create_bin_file(&dev->kobj, &ds1685_rtc_sysfs_nvram_attr); + if (ret) + return ret; + + ret = sysfs_create_group(&dev->kobj, &ds1685_rtc_sysfs_misc_grp); + if (ret) + return ret; + +#ifdef CONFIG_RTC_DS1685_SYSFS_REGS + ret = sysfs_create_group(&dev->kobj, &ds1685_rtc_sysfs_ctrla_grp); + if (ret) + return ret; + + ret = sysfs_create_group(&dev->kobj, &ds1685_rtc_sysfs_ctrlb_grp); + if (ret) + return ret; + + ret = sysfs_create_group(&dev->kobj, &ds1685_rtc_sysfs_ctrlc_grp); + if (ret) + return ret; + + ret = sysfs_create_group(&dev->kobj, &ds1685_rtc_sysfs_ctrld_grp); + if (ret) + return ret; + + ret = sysfs_create_group(&dev->kobj, &ds1685_rtc_sysfs_ctrl4a_grp); + if (ret) + return ret; + + ret = sysfs_create_group(&dev->kobj, &ds1685_rtc_sysfs_ctrl4b_grp); + if (ret) + return ret; + + ret = sysfs_create_group(&dev->kobj, &ds1685_rtc_sysfs_time_grp); + if (ret) + return ret; + + ret = sysfs_create_group(&dev->kobj, &ds1685_rtc_sysfs_alarm_grp); + if (ret) + return ret; +#endif + return 0; +} + +/** + * ds1685_rtc_sysfs_unregister - unregister sysfs files. + * @dev: pointer to device structure. + */ +static int +ds1685_rtc_sysfs_unregister(struct device *dev) +{ + sysfs_remove_bin_file(&dev->kobj, &ds1685_rtc_sysfs_nvram_attr); + sysfs_remove_group(&dev->kobj, &ds1685_rtc_sysfs_misc_grp); + +#ifdef CONFIG_RTC_DS1685_SYSFS_REGS + sysfs_remove_group(&dev->kobj, &ds1685_rtc_sysfs_ctrla_grp); + sysfs_remove_group(&dev->kobj, &ds1685_rtc_sysfs_ctrlb_grp); + sysfs_remove_group(&dev->kobj, &ds1685_rtc_sysfs_ctrlc_grp); + sysfs_remove_group(&dev->kobj, &ds1685_rtc_sysfs_ctrld_grp); + sysfs_remove_group(&dev->kobj, &ds1685_rtc_sysfs_ctrl4a_grp); + sysfs_remove_group(&dev->kobj, &ds1685_rtc_sysfs_ctrl4b_grp); + sysfs_remove_group(&dev->kobj, &ds1685_rtc_sysfs_time_grp); + sysfs_remove_group(&dev->kobj, &ds1685_rtc_sysfs_alarm_grp); +#endif + + return 0; +} +#endif /* CONFIG_SYSFS */ + + + +/* ----------------------------------------------------------------------- */ +/* Driver Probe/Removal */ + +/** + * ds1685_rtc_probe - initializes rtc driver. + * @pdev: pointer to platform_device structure. + */ +static int +ds1685_rtc_probe(struct platform_device *pdev) +{ + struct rtc_device *rtc_dev; + struct resource *res; + struct ds1685_priv *rtc; + struct ds1685_rtc_platform_data *pdata; + u8 ctrla, ctrlb, hours; + unsigned char am_pm; + int ret = 0; + + /* Get the platform data. */ + pdata = (struct ds1685_rtc_platform_data *) pdev->dev.platform_data; + if (!pdata) + return -ENODEV; + + /* Allocate memory for the rtc device. */ + rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + /* + * Allocate/setup any IORESOURCE_MEM resources, if required. Not all + * platforms put the RTC in an easy-access place. Like the SGI Octane, + * which attaches the RTC to a "ByteBus", hooked to a SuperIO chip + * that sits behind the IOC3 PCI metadevice. + */ + if (pdata->alloc_io_resources) { + /* Get the platform resources. */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + rtc->size = resource_size(res); + + /* Request a memory region. */ + /* XXX: mmio-only for now. */ + if (!devm_request_mem_region(&pdev->dev, res->start, rtc->size, + pdev->name)) + return -EBUSY; + + /* + * Set the base address for the rtc, and ioremap its + * registers. + */ + rtc->baseaddr = res->start; + rtc->regs = devm_ioremap(&pdev->dev, res->start, rtc->size); + if (!rtc->regs) + return -ENOMEM; + } + rtc->alloc_io_resources = pdata->alloc_io_resources; + + /* Get the register step size. */ + if (pdata->regstep > 0) + rtc->regstep = pdata->regstep; + else + rtc->regstep = 1; + + /* Platform read function, else default if mmio setup */ + if (pdata->plat_read) + rtc->read = pdata->plat_read; + else + if (pdata->alloc_io_resources) + rtc->read = ds1685_read; + else + return -ENXIO; + + /* Platform write function, else default if mmio setup */ + if (pdata->plat_write) + rtc->write = pdata->plat_write; + else + if (pdata->alloc_io_resources) + rtc->write = ds1685_write; + else + return -ENXIO; + + /* Platform pre-shutdown function, if defined. */ + if (pdata->plat_prepare_poweroff) + rtc->prepare_poweroff = pdata->plat_prepare_poweroff; + + /* Platform wake_alarm function, if defined. */ + if (pdata->plat_wake_alarm) + rtc->wake_alarm = pdata->plat_wake_alarm; + + /* Platform post_ram_clear function, if defined. */ + if (pdata->plat_post_ram_clear) + rtc->post_ram_clear = pdata->plat_post_ram_clear; + + /* Init the spinlock, workqueue, & set the driver data. */ + spin_lock_init(&rtc->lock); + INIT_WORK(&rtc->work, ds1685_rtc_work_queue); + platform_set_drvdata(pdev, rtc); + + /* Turn the oscillator on if is not already on (DV1 = 1). */ + ctrla = rtc->read(rtc, RTC_CTRL_A); + if (!(ctrla & RTC_CTRL_A_DV1)) + ctrla |= RTC_CTRL_A_DV1; + + /* Enable the countdown chain (DV2 = 0) */ + ctrla &= ~(RTC_CTRL_A_DV2); + + /* Clear RS3-RS0 in Control A. */ + ctrla &= ~(RTC_CTRL_A_RS_MASK); + + /* + * All done with Control A. Switch to Bank 1 for the remainder of + * the RTC setup so we have access to the extended functions. + */ + ctrla |= RTC_CTRL_A_DV0; + rtc->write(rtc, RTC_CTRL_A, ctrla); + + /* Default to 32768kHz output. */ + rtc->write(rtc, RTC_EXT_CTRL_4B, + (rtc->read(rtc, RTC_EXT_CTRL_4B) | RTC_CTRL_4B_E32K)); + + /* Set the SET bit in Control B so we can do some housekeeping. */ + rtc->write(rtc, RTC_CTRL_B, + (rtc->read(rtc, RTC_CTRL_B) | RTC_CTRL_B_SET)); + + /* Read Ext Ctrl 4A and check the INCR bit to avoid a lockout. */ + while (rtc->read(rtc, RTC_EXT_CTRL_4A) & RTC_CTRL_4A_INCR) + cpu_relax(); + + /* + * If the platform supports BCD mode, then set DM=0 in Control B. + * Otherwise, set DM=1 for BIN mode. + */ + ctrlb = rtc->read(rtc, RTC_CTRL_B); + if (pdata->bcd_mode) + ctrlb &= ~(RTC_CTRL_B_DM); + else + ctrlb |= RTC_CTRL_B_DM; + rtc->bcd_mode = pdata->bcd_mode; + + /* + * Disable Daylight Savings Time (DSE = 0). + * The RTC has hardcoded timezone information that is rendered + * obselete. We'll let the OS deal with DST settings instead. + */ + if (ctrlb & RTC_CTRL_B_DSE) + ctrlb &= ~(RTC_CTRL_B_DSE); + + /* Force 24-hour mode (2412 = 1). */ + if (!(ctrlb & RTC_CTRL_B_2412)) { + /* Reinitialize the time hours. */ + hours = rtc->read(rtc, RTC_HRS); + am_pm = hours & RTC_HRS_AMPM_MASK; + hours = ds1685_rtc_bcd2bin(rtc, hours, RTC_HRS_12_BCD_MASK, + RTC_HRS_12_BIN_MASK); + hours = ((hours == 12) ? 0 : ((am_pm) ? hours + 12 : hours)); + + /* Enable 24-hour mode. */ + ctrlb |= RTC_CTRL_B_2412; + + /* Write back to Control B, including DM & DSE bits. */ + rtc->write(rtc, RTC_CTRL_B, ctrlb); + + /* Write the time hours back. */ + rtc->write(rtc, RTC_HRS, + ds1685_rtc_bin2bcd(rtc, hours, + RTC_HRS_24_BIN_MASK, + RTC_HRS_24_BCD_MASK)); + + /* Reinitialize the alarm hours. */ + hours = rtc->read(rtc, RTC_HRS_ALARM); + am_pm = hours & RTC_HRS_AMPM_MASK; + hours = ds1685_rtc_bcd2bin(rtc, hours, RTC_HRS_12_BCD_MASK, + RTC_HRS_12_BIN_MASK); + hours = ((hours == 12) ? 0 : ((am_pm) ? hours + 12 : hours)); + + /* Write the alarm hours back. */ + rtc->write(rtc, RTC_HRS_ALARM, + ds1685_rtc_bin2bcd(rtc, hours, + RTC_HRS_24_BIN_MASK, + RTC_HRS_24_BCD_MASK)); + } else { + /* 24-hour mode is already set, so write Control B back. */ + rtc->write(rtc, RTC_CTRL_B, ctrlb); + } + + /* Unset the SET bit in Control B so the RTC can update. */ + rtc->write(rtc, RTC_CTRL_B, + (rtc->read(rtc, RTC_CTRL_B) & ~(RTC_CTRL_B_SET))); + + /* Check the main battery. */ + if (!(rtc->read(rtc, RTC_CTRL_D) & RTC_CTRL_D_VRT)) + dev_warn(&pdev->dev, + "Main battery is exhausted! RTC may be invalid!\n"); + + /* Check the auxillary battery. It is optional. */ + if (!(rtc->read(rtc, RTC_EXT_CTRL_4A) & RTC_CTRL_4A_VRT2)) + dev_warn(&pdev->dev, + "Aux battery is exhausted or not available.\n"); + + /* Read Ctrl B and clear PIE/AIE/UIE. */ + rtc->write(rtc, RTC_CTRL_B, + (rtc->read(rtc, RTC_CTRL_B) & ~(RTC_CTRL_B_PAU_MASK))); + + /* Reading Ctrl C auto-clears PF/AF/UF. */ + rtc->read(rtc, RTC_CTRL_C); + + /* Read Ctrl 4B and clear RIE/WIE/KSE. */ + rtc->write(rtc, RTC_EXT_CTRL_4B, + (rtc->read(rtc, RTC_EXT_CTRL_4B) & ~(RTC_CTRL_4B_RWK_MASK))); + + /* Clear RF/WF/KF in Ctrl 4A. */ + rtc->write(rtc, RTC_EXT_CTRL_4A, + (rtc->read(rtc, RTC_EXT_CTRL_4A) & ~(RTC_CTRL_4A_RWK_MASK))); + + /* + * Re-enable KSE to handle power button events. We do not enable + * WIE or RIE by default. + */ + rtc->write(rtc, RTC_EXT_CTRL_4B, + (rtc->read(rtc, RTC_EXT_CTRL_4B) | RTC_CTRL_4B_KSE)); + + /* + * Fetch the IRQ and setup the interrupt handler. + * + * Not all platforms have the IRQF pin tied to something. If not, the + * RTC will still set the *IE / *F flags and raise IRQF in ctrlc, but + * there won't be an automatic way of notifying the kernel about it, + * unless ctrlc is explicitly polled. + */ + if (!pdata->no_irq) { + ret = platform_get_irq(pdev, 0); + if (ret > 0) { + rtc->irq_num = ret; + + /* Request an IRQ. */ + ret = devm_request_irq(&pdev->dev, rtc->irq_num, + ds1685_rtc_irq_handler, + IRQF_SHARED, pdev->name, pdev); + + /* Check to see if something came back. */ + if (unlikely(ret)) { + dev_warn(&pdev->dev, + "RTC interrupt not available\n"); + rtc->irq_num = 0; + } + } else + return ret; + } + rtc->no_irq = pdata->no_irq; + + /* Setup complete. */ + ds1685_rtc_switch_to_bank0(rtc); + + /* Register the device as an RTC. */ + rtc_dev = rtc_device_register(pdev->name, &pdev->dev, + &ds1685_rtc_ops, THIS_MODULE); + + /* Success? */ + if (IS_ERR(rtc_dev)) + return PTR_ERR(rtc_dev); + + /* Maximum periodic rate is 8192Hz (0.122070ms). */ + rtc_dev->max_user_freq = RTC_MAX_USER_FREQ; + + /* See if the platform doesn't support UIE. */ + if (pdata->uie_unsupported) + rtc_dev->uie_unsupported = 1; + rtc->uie_unsupported = pdata->uie_unsupported; + + rtc->dev = rtc_dev; + +#ifdef CONFIG_SYSFS + ret = ds1685_rtc_sysfs_register(&pdev->dev); + if (ret) + rtc_device_unregister(rtc->dev); +#endif + + /* Done! */ + return ret; +} + +/** + * ds1685_rtc_remove - removes rtc driver. + * @pdev: pointer to platform_device structure. + */ +static int +ds1685_rtc_remove(struct platform_device *pdev) +{ + struct ds1685_priv *rtc = platform_get_drvdata(pdev); + +#ifdef CONFIG_SYSFS + ds1685_rtc_sysfs_unregister(&pdev->dev); +#endif + + rtc_device_unregister(rtc->dev); + + /* Read Ctrl B and clear PIE/AIE/UIE. */ + rtc->write(rtc, RTC_CTRL_B, + (rtc->read(rtc, RTC_CTRL_B) & + ~(RTC_CTRL_B_PAU_MASK))); + + /* Reading Ctrl C auto-clears PF/AF/UF. */ + rtc->read(rtc, RTC_CTRL_C); + + /* Read Ctrl 4B and clear RIE/WIE/KSE. */ + rtc->write(rtc, RTC_EXT_CTRL_4B, + (rtc->read(rtc, RTC_EXT_CTRL_4B) & + ~(RTC_CTRL_4B_RWK_MASK))); + + /* Manually clear RF/WF/KF in Ctrl 4A. */ + rtc->write(rtc, RTC_EXT_CTRL_4A, + (rtc->read(rtc, RTC_EXT_CTRL_4A) & + ~(RTC_CTRL_4A_RWK_MASK))); + + cancel_work_sync(&rtc->work); + + return 0; +} + +/** + * ds1685_rtc_driver - rtc driver properties. + */ +static struct platform_driver ds1685_rtc_driver = { + .driver = { + .name = "rtc-ds1685", + .owner = THIS_MODULE, + }, + .probe = ds1685_rtc_probe, + .remove = ds1685_rtc_remove, +}; + +/** + * ds1685_rtc_init - rtc module init. + */ +static int __init +ds1685_rtc_init(void) +{ + return platform_driver_register(&ds1685_rtc_driver); +} + +/** + * ds1685_rtc_exit - rtc module exit. + */ +static void __exit +ds1685_rtc_exit(void) +{ + platform_driver_unregister(&ds1685_rtc_driver); +} + +module_init(ds1685_rtc_init); +module_exit(ds1685_rtc_exit); +/* ----------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------------------- */ +/* Poweroff function */ + +/** + * ds1685_rtc_poweroff - uses the RTC chip to power the system off. + * @pdev: pointer to platform_device structure. + */ +extern void __noreturn +ds1685_rtc_poweroff(struct platform_device *pdev) +{ + u8 ctrla, ctrl4a, ctrl4b; + struct ds1685_priv *rtc; + + /* Check for valid RTC data, else, spin forever. */ + if (unlikely(!pdev)) { + pr_emerg("rtc-ds1685: platform device data not available, spinning forever ...\n"); + unreachable(); + } else { + /* Get the rtc data. */ + rtc = platform_get_drvdata(pdev); + + /* + * Disable our IRQ. We're powering down, so we're not + * going to worry about cleaning up. Most of that should + * have been taken care of by the shutdown scripts and this + * is the final function call. + */ + if (!rtc->no_irq) + disable_irq_nosync(rtc->irq_num); + + /* Oscillator must be on and the countdown chain enabled. */ + ctrla = rtc->read(rtc, RTC_CTRL_A); + ctrla |= RTC_CTRL_A_DV1; + ctrla &= ~(RTC_CTRL_A_DV2); + rtc->write(rtc, RTC_CTRL_A, ctrla); + + /* + * Read Control 4A and check the status of the auxillary + * battery. This must be present and working (VRT2 = 1) + * for wakeup and kickstart functionality to be useful. + */ + ds1685_rtc_switch_to_bank1(rtc); + ctrl4a = rtc->read(rtc, RTC_EXT_CTRL_4A); + if (ctrl4a & RTC_CTRL_4A_VRT2) { + /* Clear all of the interrupt flags on Control 4A. */ + ctrl4a &= ~(RTC_CTRL_4A_RWK_MASK); + rtc->write(rtc, RTC_EXT_CTRL_4A, ctrl4a); + + /* + * The auxillary battery is present and working. + * Enable extended functions (ABE=1), enable + * wake-up (WIE=1), and enable kickstart (KSE=1) + * in Control 4B. + */ + ctrl4b = rtc->read(rtc, RTC_EXT_CTRL_4B); + ctrl4b |= (RTC_CTRL_4B_ABE | RTC_CTRL_4B_WIE | + RTC_CTRL_4B_KSE); + rtc->write(rtc, RTC_EXT_CTRL_4B, ctrl4b); + } + + /* Set PAB to 1 in Control 4A to power the system down. */ + dev_warn(&pdev->dev, "Powerdown.\n"); + msleep(20); + rtc->write(rtc, RTC_EXT_CTRL_4A, + (ctrl4a | RTC_CTRL_4A_PAB)); + + /* Spin ... we do not switch back to bank0. */ + unreachable(); + } +} +EXPORT_SYMBOL(ds1685_rtc_poweroff); +/* ----------------------------------------------------------------------- */ + + +MODULE_AUTHOR("Joshua Kinard "); +MODULE_AUTHOR("Matthias Fuchs "); +MODULE_DESCRIPTION("Dallas/Maxim DS1685/DS1687-series RTC driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); +MODULE_ALIAS("platform:rtc-ds1685"); diff --git a/include/linux/rtc/ds1685.h b/include/linux/rtc/ds1685.h new file mode 100644 index 000000000000..e6337a56d741 --- /dev/null +++ b/include/linux/rtc/ds1685.h @@ -0,0 +1,375 @@ +/* + * Definitions for the registers, addresses, and platform data of the + * DS1685/DS1687-series RTC chips. + * + * This Driver also works for the DS17X85/DS17X87 RTC chips. Functionally + * similar to the DS1685/DS1687, they support a few extra features which + * include larger, battery-backed NV-SRAM, burst-mode access, and an RTC + * write counter. + * + * Copyright (C) 2011-2014 Joshua Kinard . + * Copyright (C) 2009 Matthias Fuchs . + * + * References: + * DS1685/DS1687 3V/5V Real-Time Clocks, 19-5215, Rev 4/10. + * DS17x85/DS17x87 3V/5V Real-Time Clocks, 19-5222, Rev 4/10. + * DS1689/DS1693 3V/5V Serialized Real-Time Clocks, Rev 112105. + * Application Note 90, Using the Multiplex Bus RTC Extended Features. + * + * 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 _LINUX_RTC_DS1685_H_ +#define _LINUX_RTC_DS1685_H_ + +#include +#include +#include + +/** + * struct ds1685_priv - DS1685 private data structure. + * @dev: pointer to the rtc_device structure. + * @regs: iomapped base address pointer of the RTC registers. + * @regstep: padding/step size between registers (optional). + * @baseaddr: base address of the RTC device. + * @size: resource size. + * @lock: private lock variable for spin locking/unlocking. + * @work: private workqueue. + * @irq: IRQ number assigned to the RTC device. + * @prepare_poweroff: pointer to platform pre-poweroff function. + * @wake_alarm: pointer to platform wake alarm function. + * @post_ram_clear: pointer to platform post ram-clear function. + */ +struct ds1685_priv { + struct rtc_device *dev; + void __iomem *regs; + u32 regstep; + resource_size_t baseaddr; + size_t size; + spinlock_t lock; + struct work_struct work; + int irq_num; + bool bcd_mode; + bool no_irq; + bool uie_unsupported; + bool alloc_io_resources; + u8 (*read)(struct ds1685_priv *, int); + void (*write)(struct ds1685_priv *, int, u8); + void (*prepare_poweroff)(void); + void (*wake_alarm)(void); + void (*post_ram_clear)(void); +}; + + +/** + * struct ds1685_rtc_platform_data - platform data structure. + * @plat_prepare_poweroff: platform-specific pre-poweroff function. + * @plat_wake_alarm: platform-specific wake alarm function. + * @plat_post_ram_clear: platform-specific post ram-clear function. + * + * If your platform needs to use a custom padding/step size between + * registers, or uses one or more of the extended interrupts and needs special + * handling, then include this header file in your platform definition and + * set regstep and the plat_* pointers as appropriate. + */ +struct ds1685_rtc_platform_data { + const u32 regstep; + const bool bcd_mode; + const bool no_irq; + const bool uie_unsupported; + const bool alloc_io_resources; + u8 (*plat_read)(struct ds1685_priv *, int); + void (*plat_write)(struct ds1685_priv *, int, u8); + void (*plat_prepare_poweroff)(void); + void (*plat_wake_alarm)(void); + void (*plat_post_ram_clear)(void); +}; + + +/* + * Time Registers. + */ +#define RTC_SECS 0x00 /* Seconds 00-59 */ +#define RTC_SECS_ALARM 0x01 /* Alarm Seconds 00-59 */ +#define RTC_MINS 0x02 /* Minutes 00-59 */ +#define RTC_MINS_ALARM 0x03 /* Alarm Minutes 00-59 */ +#define RTC_HRS 0x04 /* Hours 01-12 AM/PM || 00-23 */ +#define RTC_HRS_ALARM 0x05 /* Alarm Hours 01-12 AM/PM || 00-23 */ +#define RTC_WDAY 0x06 /* Day of Week 01-07 */ +#define RTC_MDAY 0x07 /* Day of Month 01-31 */ +#define RTC_MONTH 0x08 /* Month 01-12 */ +#define RTC_YEAR 0x09 /* Year 00-99 */ +#define RTC_CENTURY 0x48 /* Century 00-99 */ +#define RTC_MDAY_ALARM 0x49 /* Alarm Day of Month 01-31 */ + + +/* + * Bit masks for the Time registers in BCD Mode (DM = 0). + */ +#define RTC_SECS_BCD_MASK 0x7f /* - x x x x x x x */ +#define RTC_MINS_BCD_MASK 0x7f /* - x x x x x x x */ +#define RTC_HRS_12_BCD_MASK 0x1f /* - - - x x x x x */ +#define RTC_HRS_24_BCD_MASK 0x3f /* - - x x x x x x */ +#define RTC_MDAY_BCD_MASK 0x3f /* - - x x x x x x */ +#define RTC_MONTH_BCD_MASK 0x1f /* - - - x x x x x */ +#define RTC_YEAR_BCD_MASK 0xff /* x x x x x x x x */ + +/* + * Bit masks for the Time registers in BIN Mode (DM = 1). + */ +#define RTC_SECS_BIN_MASK 0x3f /* - - x x x x x x */ +#define RTC_MINS_BIN_MASK 0x3f /* - - x x x x x x */ +#define RTC_HRS_12_BIN_MASK 0x0f /* - - - - x x x x */ +#define RTC_HRS_24_BIN_MASK 0x1f /* - - - x x x x x */ +#define RTC_MDAY_BIN_MASK 0x1f /* - - - x x x x x */ +#define RTC_MONTH_BIN_MASK 0x0f /* - - - - x x x x */ +#define RTC_YEAR_BIN_MASK 0x7f /* - x x x x x x x */ + +/* + * Bit masks common for the Time registers in BCD or BIN Mode. + */ +#define RTC_WDAY_MASK 0x07 /* - - - - - x x x */ +#define RTC_CENTURY_MASK 0xff /* x x x x x x x x */ +#define RTC_MDAY_ALARM_MASK 0xff /* x x x x x x x x */ +#define RTC_HRS_AMPM_MASK BIT(7) /* Mask for the AM/PM bit */ + + + +/* + * Control Registers. + */ +#define RTC_CTRL_A 0x0a /* Control Register A */ +#define RTC_CTRL_B 0x0b /* Control Register B */ +#define RTC_CTRL_C 0x0c /* Control Register C */ +#define RTC_CTRL_D 0x0d /* Control Register D */ +#define RTC_EXT_CTRL_4A 0x4a /* Extended Control Register 4A */ +#define RTC_EXT_CTRL_4B 0x4b /* Extended Control Register 4B */ + + +/* + * Bit names in Control Register A. + */ +#define RTC_CTRL_A_UIP BIT(7) /* Update In Progress */ +#define RTC_CTRL_A_DV2 BIT(6) /* Countdown Chain */ +#define RTC_CTRL_A_DV1 BIT(5) /* Oscillator Enable */ +#define RTC_CTRL_A_DV0 BIT(4) /* Bank Select */ +#define RTC_CTRL_A_RS2 BIT(2) /* Rate-Selection Bit 2 */ +#define RTC_CTRL_A_RS3 BIT(3) /* Rate-Selection Bit 3 */ +#define RTC_CTRL_A_RS1 BIT(1) /* Rate-Selection Bit 1 */ +#define RTC_CTRL_A_RS0 BIT(0) /* Rate-Selection Bit 0 */ +#define RTC_CTRL_A_RS_MASK 0x0f /* RS3 + RS2 + RS1 + RS0 */ + +/* + * Bit names in Control Register B. + */ +#define RTC_CTRL_B_SET BIT(7) /* SET Bit */ +#define RTC_CTRL_B_PIE BIT(6) /* Periodic-Interrupt Enable */ +#define RTC_CTRL_B_AIE BIT(5) /* Alarm-Interrupt Enable */ +#define RTC_CTRL_B_UIE BIT(4) /* Update-Ended Interrupt-Enable */ +#define RTC_CTRL_B_SQWE BIT(3) /* Square-Wave Enable */ +#define RTC_CTRL_B_DM BIT(2) /* Data Mode */ +#define RTC_CTRL_B_2412 BIT(1) /* 12-Hr/24-Hr Mode */ +#define RTC_CTRL_B_DSE BIT(0) /* Daylight Savings Enable */ +#define RTC_CTRL_B_PAU_MASK 0x70 /* PIE + AIE + UIE */ + + +/* + * Bit names in Control Register C. + * + * BIT(0), BIT(1), BIT(2), & BIT(3) are unused, always return 0, and cannot + * be written to. + */ +#define RTC_CTRL_C_IRQF BIT(7) /* Interrupt-Request Flag */ +#define RTC_CTRL_C_PF BIT(6) /* Periodic-Interrupt Flag */ +#define RTC_CTRL_C_AF BIT(5) /* Alarm-Interrupt Flag */ +#define RTC_CTRL_C_UF BIT(4) /* Update-Ended Interrupt Flag */ +#define RTC_CTRL_C_PAU_MASK 0x70 /* PF + AF + UF */ + + +/* + * Bit names in Control Register D. + * + * BIT(0) through BIT(6) are unused, always return 0, and cannot + * be written to. + */ +#define RTC_CTRL_D_VRT BIT(7) /* Valid RAM and Time */ + + +/* + * Bit names in Extended Control Register 4A. + * + * On the DS1685/DS1687/DS1689/DS1693, BIT(4) and BIT(5) are reserved for + * future use. They can be read from and written to, but have no effect + * on the RTC's operation. + * + * On the DS17x85/DS17x87, BIT(5) is Burst-Mode Enable (BME), and allows + * access to the extended NV-SRAM by automatically incrementing the address + * register when they are read from or written to. + */ +#define RTC_CTRL_4A_VRT2 BIT(7) /* Auxillary Battery Status */ +#define RTC_CTRL_4A_INCR BIT(6) /* Increment-in-Progress Status */ +#define RTC_CTRL_4A_PAB BIT(3) /* Power-Active Bar Control */ +#define RTC_CTRL_4A_RF BIT(2) /* RAM-Clear Flag */ +#define RTC_CTRL_4A_WF BIT(1) /* Wake-Up Alarm Flag */ +#define RTC_CTRL_4A_KF BIT(0) /* Kickstart Flag */ +#if !defined(CONFIG_RTC_DRV_DS1685) && !defined(CONFIG_RTC_DRV_DS1689) +#define RTC_CTRL_4A_BME BIT(5) /* Burst-Mode Enable */ +#endif +#define RTC_CTRL_4A_RWK_MASK 0x07 /* RF + WF + KF */ + + +/* + * Bit names in Extended Control Register 4B. + */ +#define RTC_CTRL_4B_ABE BIT(7) /* Auxillary Battery Enable */ +#define RTC_CTRL_4B_E32K BIT(6) /* Enable 32.768Hz on SQW Pin */ +#define RTC_CTRL_4B_CS BIT(5) /* Crystal Select */ +#define RTC_CTRL_4B_RCE BIT(4) /* RAM Clear-Enable */ +#define RTC_CTRL_4B_PRS BIT(3) /* PAB Reset-Select */ +#define RTC_CTRL_4B_RIE BIT(2) /* RAM Clear-Interrupt Enable */ +#define RTC_CTRL_4B_WIE BIT(1) /* Wake-Up Alarm-Interrupt Enable */ +#define RTC_CTRL_4B_KSE BIT(0) /* Kickstart Interrupt-Enable */ +#define RTC_CTRL_4B_RWK_MASK 0x07 /* RIE + WIE + KSE */ + + +/* + * Misc register names in Bank 1. + * + * The DV0 bit in Control Register A must be set to 1 for these registers + * to become available, including Extended Control Registers 4A & 4B. + */ +#define RTC_BANK1_SSN_MODEL 0x40 /* Model Number */ +#define RTC_BANK1_SSN_BYTE_1 0x41 /* 1st Byte of Serial Number */ +#define RTC_BANK1_SSN_BYTE_2 0x42 /* 2nd Byte of Serial Number */ +#define RTC_BANK1_SSN_BYTE_3 0x43 /* 3rd Byte of Serial Number */ +#define RTC_BANK1_SSN_BYTE_4 0x44 /* 4th Byte of Serial Number */ +#define RTC_BANK1_SSN_BYTE_5 0x45 /* 5th Byte of Serial Number */ +#define RTC_BANK1_SSN_BYTE_6 0x46 /* 6th Byte of Serial Number */ +#define RTC_BANK1_SSN_CRC 0x47 /* Serial CRC Byte */ +#define RTC_BANK1_RAM_DATA_PORT 0x53 /* Extended RAM Data Port */ + + +/* + * Model-specific registers in Bank 1. + * + * The addresses below differ depending on the model of the RTC chip + * selected in the kernel configuration. Not all of these features are + * supported in the main driver at present. + * + * DS1685/DS1687 - Extended NV-SRAM address (LSB only). + * DS1689/DS1693 - Vcc, Vbat, Pwr Cycle Counters & Customer-specific S/N. + * DS17x85/DS17x87 - Extended NV-SRAM addresses (MSB & LSB) & Write counter. + */ +#if defined(CONFIG_RTC_DRV_DS1685) +#define RTC_BANK1_RAM_ADDR 0x50 /* NV-SRAM Addr */ +#elif defined(CONFIG_RTC_DRV_DS1689) +#define RTC_BANK1_VCC_CTR_LSB 0x54 /* Vcc Counter Addr (LSB) */ +#define RTC_BANK1_VCC_CTR_MSB 0x57 /* Vcc Counter Addr (MSB) */ +#define RTC_BANK1_VBAT_CTR_LSB 0x58 /* Vbat Counter Addr (LSB) */ +#define RTC_BANK1_VBAT_CTR_MSB 0x5b /* Vbat Counter Addr (MSB) */ +#define RTC_BANK1_PWR_CTR_LSB 0x5c /* Pwr Cycle Counter Addr (LSB) */ +#define RTC_BANK1_PWR_CTR_MSB 0x5d /* Pwr Cycle Counter Addr (MSB) */ +#define RTC_BANK1_UNIQ_SN 0x60 /* Customer-specific S/N */ +#else /* DS17x85/DS17x87 */ +#define RTC_BANK1_RAM_ADDR_LSB 0x50 /* NV-SRAM Addr (LSB) */ +#define RTC_BANK1_RAM_ADDR_MSB 0x51 /* NV-SRAM Addr (MSB) */ +#define RTC_BANK1_WRITE_CTR 0x5e /* RTC Write Counter */ +#endif + + +/* + * Model numbers. + * + * The DS1688/DS1691 and DS1689/DS1693 chips share the same model number + * and the manual doesn't indicate any major differences. As such, they + * are regarded as the same chip in this driver. + */ +#define RTC_MODEL_DS1685 0x71 /* DS1685/DS1687 */ +#define RTC_MODEL_DS17285 0x72 /* DS17285/DS17287 */ +#define RTC_MODEL_DS1689 0x73 /* DS1688/DS1691/DS1689/DS1693 */ +#define RTC_MODEL_DS17485 0x74 /* DS17485/DS17487 */ +#define RTC_MODEL_DS17885 0x78 /* DS17885/DS17887 */ + + +/* + * Periodic Interrupt Rates / Square-Wave Output Frequency + * + * Periodic rates are selected by setting the RS3-RS0 bits in Control + * Register A and enabled via either the E32K bit in Extended Control + * Register 4B or the SQWE bit in Control Register B. + * + * E32K overrides the settings of RS3-RS0 and outputs a frequency of 32768Hz + * on the SQW pin of the RTC chip. While there are 16 possible selections, + * the 1-of-16 decoder is only able to divide the base 32768Hz signal into 13 + * smaller frequencies. The values 0x01 and 0x02 are not used and are + * synonymous with 0x08 and 0x09, respectively. + * + * When E32K is set to a logic 1, periodic interrupts are disabled and reading + * /dev/rtc will return -EINVAL. This also applies if the periodic interrupt + * frequency is set to 0Hz. + * + * Not currently used by the rtc-ds1685 driver because the RTC core removed + * support for hardware-generated periodic-interrupts in favour of + * hrtimer-generated interrupts. But these defines are kept around for use + * in userland, as documentation to the hardware, and possible future use if + * hardware-generated periodic interrupts are ever added back. + */ + /* E32K RS3 RS2 RS1 RS0 */ +#define RTC_SQW_8192HZ 0x03 /* 0 0 0 1 1 */ +#define RTC_SQW_4096HZ 0x04 /* 0 0 1 0 0 */ +#define RTC_SQW_2048HZ 0x05 /* 0 0 1 0 1 */ +#define RTC_SQW_1024HZ 0x06 /* 0 0 1 1 0 */ +#define RTC_SQW_512HZ 0x07 /* 0 0 1 1 1 */ +#define RTC_SQW_256HZ 0x08 /* 0 1 0 0 0 */ +#define RTC_SQW_128HZ 0x09 /* 0 1 0 0 1 */ +#define RTC_SQW_64HZ 0x0a /* 0 1 0 1 0 */ +#define RTC_SQW_32HZ 0x0b /* 0 1 0 1 1 */ +#define RTC_SQW_16HZ 0x0c /* 0 1 1 0 0 */ +#define RTC_SQW_8HZ 0x0d /* 0 1 1 0 1 */ +#define RTC_SQW_4HZ 0x0e /* 0 1 1 1 0 */ +#define RTC_SQW_2HZ 0x0f /* 0 1 1 1 1 */ +#define RTC_SQW_0HZ 0x00 /* 0 0 0 0 0 */ +#define RTC_SQW_32768HZ 32768 /* 1 - - - - */ +#define RTC_MAX_USER_FREQ 8192 + + +/* + * NVRAM data & addresses: + * - 50 bytes of NVRAM are available just past the clock registers. + * - 64 additional bytes are available in Bank0. + * + * Extended, battery-backed NV-SRAM: + * - DS1685/DS1687 - 128 bytes. + * - DS1689/DS1693 - 0 bytes. + * - DS17285/DS17287 - 2048 bytes. + * - DS17485/DS17487 - 4096 bytes. + * - DS17885/DS17887 - 8192 bytes. + */ +#define NVRAM_TIME_BASE 0x0e /* NVRAM Addr in Time regs */ +#define NVRAM_BANK0_BASE 0x40 /* NVRAM Addr in Bank0 regs */ +#define NVRAM_SZ_TIME 50 +#define NVRAM_SZ_BANK0 64 +#if defined(CONFIG_RTC_DRV_DS1685) +# define NVRAM_SZ_EXTND 128 +#elif defined(CONFIG_RTC_DRV_DS1689) +# define NVRAM_SZ_EXTND 0 +#elif defined(CONFIG_RTC_DRV_DS17285) +# define NVRAM_SZ_EXTND 2048 +#elif defined(CONFIG_RTC_DRV_DS17485) +# define NVRAM_SZ_EXTND 4096 +#elif defined(CONFIG_RTC_DRV_DS17885) +# define NVRAM_SZ_EXTND 8192 +#endif +#define NVRAM_TOTAL_SZ_BANK0 (NVRAM_SZ_TIME + NVRAM_SZ_BANK0) +#define NVRAM_TOTAL_SZ (NVRAM_TOTAL_SZ_BANK0 + NVRAM_SZ_EXTND) + + +/* + * Function Prototypes. + */ +extern void __noreturn +ds1685_rtc_poweroff(struct platform_device *pdev); + +#endif /* _LINUX_RTC_DS1685_H_ */ -- cgit v1.2.3 From 158daf167377dfc49ce6d70f70fd7c6fab2df987 Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Tue, 17 Feb 2015 13:47:49 -0800 Subject: scripts/gdb: define maintainer I'm proposing myself for keeping an eye on these scripts and integrating contributions. Signed-off-by: Jan Kiszka Cc: Thomas Gleixner Cc: Jason Wessel Cc: Andi Kleen Cc: Ben Widawsky Cc: Borislav Petkov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 85024e23309f..a6c9b599fd8f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4232,6 +4232,11 @@ W: http://www.icp-vortex.com/ S: Supported F: drivers/scsi/gdt* +GDB KERNEL DEBUGGING HELPER SCRIPTS +M: Jan Kiszka +S: Supported +F: scripts/gdb/ + GEMTEK FM RADIO RECEIVER DRIVER M: Hans Verkuil L: linux-media@vger.kernel.org -- cgit v1.2.3 From 0f5417cea6cfeafd5cdec4223df63ca79918fdea Mon Sep 17 00:00:00 2001 From: Sage Weil Date: Thu, 19 Feb 2015 10:10:40 -0800 Subject: MAINTAINERS: update Ceph and RBD maintainers - add Ilya, drop Yehuda as an RBD maintainer - add Zheng as a Ceph maintainer - update Yehuda and Sage's emails Signed-off-by: Sage Weil --- MAINTAINERS | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index d66a97dd3a12..9d64a6f3d6d9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2405,7 +2405,8 @@ F: arch/powerpc/oprofile/*cell* F: arch/powerpc/platforms/cell/ CEPH DISTRIBUTED FILE SYSTEM CLIENT -M: Sage Weil +M: Yan, Zheng +M: Sage Weil L: ceph-devel@vger.kernel.org W: http://ceph.com/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/sage/ceph-client.git @@ -7884,8 +7885,8 @@ S: Odd Fixes F: drivers/media/parport/*-qcam* RADOS BLOCK DEVICE (RBD) -M: Yehuda Sadeh -M: Sage Weil +M: Ilya Dryomov +M: Sage Weil M: Alex Elder M: ceph-devel@vger.kernel.org W: http://ceph.com/ -- cgit v1.2.3 From 31639b94cadc03727d0ae1f048e9688dd508883f Mon Sep 17 00:00:00 2001 From: Andy Gospodarek Date: Wed, 25 Feb 2015 17:00:16 -0500 Subject: MAINTAINERS: update my email address I have been signing off on patches with this address so I'll change it. Signed-off-by: Andy Gospodarek Signed-off-by: David S. Miller --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 4f4915cbeab9..5d1606e9f558 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2065,7 +2065,7 @@ F: include/net/bluetooth/ BONDING DRIVER M: Jay Vosburgh M: Veaceslav Falico -M: Andy Gospodarek +M: Andy Gospodarek L: netdev@vger.kernel.org W: http://sourceforge.net/projects/bonding/ S: Supported -- cgit v1.2.3 From 01945fa248ff6d34f5fdb8106118910b77b76f91 Mon Sep 17 00:00:00 2001 From: Mark Fasheh Date: Fri, 27 Feb 2015 15:51:40 -0800 Subject: ocfs2: update web page + git tree in documentation We (the Ocfs2 project) recently moved the location of our ocfs2-tools git tree and project web page. The pertinent discussion can be seen here: https://oss.oracle.com/pipermail/ocfs2-devel/2015-February/010579.html The following patch updates the Ocfs2 documentation in MAINTAINERS, ocfs2.txt, and dlmfs.txt. I added our new official web page, changed the location of our tools git tree and removed the link to Joel's ancient kernel git tree - Andrew has handled our patches for a while now. Signed-off-by: Mark Fasheh Cc: Joel Becker Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/filesystems/dlmfs.txt | 4 ++-- Documentation/filesystems/ocfs2.txt | 4 ++-- MAINTAINERS | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) (limited to 'MAINTAINERS') diff --git a/Documentation/filesystems/dlmfs.txt b/Documentation/filesystems/dlmfs.txt index 1b528b2ad809..fcf4d509d118 100644 --- a/Documentation/filesystems/dlmfs.txt +++ b/Documentation/filesystems/dlmfs.txt @@ -5,8 +5,8 @@ system. dlmfs is built with OCFS2 as it requires most of its infrastructure. -Project web page: http://oss.oracle.com/projects/ocfs2 -Tools web page: http://oss.oracle.com/projects/ocfs2-tools +Project web page: http://ocfs2.wiki.kernel.org +Tools web page: https://github.com/markfasheh/ocfs2-tools OCFS2 mailing lists: http://oss.oracle.com/projects/ocfs2/mailman/ All code copyright 2005 Oracle except when otherwise noted. diff --git a/Documentation/filesystems/ocfs2.txt b/Documentation/filesystems/ocfs2.txt index 28f8c08201e2..4c49e5410595 100644 --- a/Documentation/filesystems/ocfs2.txt +++ b/Documentation/filesystems/ocfs2.txt @@ -8,8 +8,8 @@ also make it attractive for non-clustered use. You'll want to install the ocfs2-tools package in order to at least get "mount.ocfs2" and "ocfs2_hb_ctl". -Project web page: http://oss.oracle.com/projects/ocfs2 -Tools web page: http://oss.oracle.com/projects/ocfs2-tools +Project web page: http://ocfs2.wiki.kernel.org +Tools git tree: https://github.com/markfasheh/ocfs2-tools OCFS2 mailing lists: http://oss.oracle.com/projects/ocfs2/mailman/ All code copyright 2005 Oracle except when otherwise noted. diff --git a/MAINTAINERS b/MAINTAINERS index ddc5a8cf9a8a..eaf999638a65 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7213,8 +7213,7 @@ ORACLE CLUSTER FILESYSTEM 2 (OCFS2) M: Mark Fasheh M: Joel Becker L: ocfs2-devel@oss.oracle.com (moderated for non-subscribers) -W: http://oss.oracle.com/projects/ocfs2/ -T: git git://git.kernel.org/pub/scm/linux/kernel/git/jlbec/ocfs2.git +W: http://ocfs2.wiki.kernel.org S: Supported F: Documentation/filesystems/ocfs2.txt F: Documentation/filesystems/dlmfs.txt -- cgit v1.2.3 From 93c537affd5d2a7b2fcea4a1d608b011841d3c04 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Tue, 24 Feb 2015 16:31:28 +0100 Subject: MAINTAINERS: Add entry for SAMSUNG THERMAL DRIVER This patch adds entry for SAMSUNG THERMAL DRIVER in the MAINTAINERS file. It has been agreed, that pull request are going to be sent to Eduardo Valentin. Signed-off-by: Lukasz Majewski Acked-by: Eduardo Valentin --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ddc5a8cf9a8a..be837a0d2280 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8481,6 +8481,14 @@ S: Supported L: netdev@vger.kernel.org F: drivers/net/ethernet/samsung/sxgbe/ +SAMSUNG THERMAL DRIVER +M: Lukasz Majewski +L: linux-pm@vger.kernel.org +L: linux-samsung-soc@vger.kernel.org +S: Supported +T: https://github.com/lmajewski/linux-samsung-thermal.git +F: drivers/thermal/samsung/ + SAMSUNG USB2 PHY DRIVER M: Kamil Debski L: linux-kernel@vger.kernel.org -- cgit v1.2.3 From 84b0d715d805a2af5b12a51ce85f66cec87111d0 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Fri, 6 Mar 2015 08:58:33 +0100 Subject: MAINTAINERS: linux-can moved to github As gitorious will shut down at the end of May 2015, the linux-can website moved to github. This patch reflects this change. Signed-off-by: Marc Kleine-Budde --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 42f686f2e4b2..ce4380d7ab1a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2370,7 +2370,7 @@ F: arch/x86/include/asm/tce.h CAN NETWORK LAYER M: Oliver Hartkopp L: linux-can@vger.kernel.org -W: http://gitorious.org/linux-can +W: https://github.com/linux-can T: git git://git.kernel.org/pub/scm/linux/kernel/git/mkl/linux-can.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/mkl/linux-can-next.git S: Maintained @@ -2386,7 +2386,7 @@ CAN NETWORK DRIVERS M: Wolfgang Grandegger M: Marc Kleine-Budde L: linux-can@vger.kernel.org -W: http://gitorious.org/linux-can +W: https://github.com/linux-can T: git git://git.kernel.org/pub/scm/linux/kernel/git/mkl/linux-can.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/mkl/linux-can-next.git S: Maintained -- cgit v1.2.3 From f7214cf29ca6c977ad2c428f2b832e9c66f2ee1b Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Fri, 6 Mar 2015 09:00:38 +0100 Subject: MAINTAINERS: add Marc Kleine-Budde as co maintainer for CAN networking layer This patch adds Marc Kleine-Budde as a co maintainer for the CAN networking layer. Acked-by: Oliver Hartkopp Signed-off-by: Marc Kleine-Budde --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ce4380d7ab1a..ba57e5d3ed5c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2369,6 +2369,7 @@ F: arch/x86/include/asm/tce.h CAN NETWORK LAYER M: Oliver Hartkopp +M: Marc Kleine-Budde L: linux-can@vger.kernel.org W: https://github.com/linux-can T: git git://git.kernel.org/pub/scm/linux/kernel/git/mkl/linux-can.git -- cgit v1.2.3 From 735783d7d0b01609e5645ddb79611dcd8e65f346 Mon Sep 17 00:00:00 2001 From: Matt Porter Date: Tue, 17 Feb 2015 12:17:57 -0500 Subject: MAINTAINERS: Remove self as ARM mach-bcm co-maintainer Removing myself as a co-maintainer. Signed-off-by: Matt Porter Signed-off-by: Arnd Bergmann --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index eaf999638a65..a6c9f6c2091b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2107,7 +2107,6 @@ F: drivers/net/ethernet/broadcom/bnx2x/ BROADCOM BCM281XX/BCM11XXX/BCM216XX ARM ARCHITECTURE M: Christian Daudt -M: Matt Porter M: Florian Fainelli L: bcm-kernel-feedback-list@broadcom.com T: git git://github.com/broadcom/mach-bcm -- cgit v1.2.3 From 142109d21c7155b56e185e13f15f58410609a866 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 2 Mar 2015 00:09:02 +0100 Subject: MAINTAINERS: add Freescale Vybrid SoC Add Freescale Vybrid family as a own entry, along with an entry for the so far orphan Vybrid device tree files. Also add myself as a designated reviewer. Acked-by: Shawn Guo Signed-off-by: Stefan Agner Signed-off-by: Arnd Bergmann --- MAINTAINERS | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index a6c9f6c2091b..dd6b2383161c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1030,6 +1030,16 @@ F: arch/arm/mach-mxs/ F: arch/arm/boot/dts/imx* F: arch/arm/configs/imx*_defconfig +ARM/FREESCALE VYBRID ARM ARCHITECTURE +M: Shawn Guo +M: Sascha Hauer +R: Stefan Agner +L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/shawnguo/linux.git +F: arch/arm/mach-imx/*vf610* +F: arch/arm/boot/dts/vf* + ARM/GLOMATION GESBC9312SX MACHINE SUPPORT M: Lennert Buytenhek L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) -- cgit v1.2.3 From 366c1bd191c49380d81b15b96cf7d4e3528a82a2 Mon Sep 17 00:00:00 2001 From: chas williams - CONTRACTOR Date: Wed, 11 Mar 2015 16:18:01 -0400 Subject: MAINTAINERS: Update my email address Changed to my private email address. Signed-off-by: Chas Williams -- CONTRACTOR Signed-off-by: David S. Miller --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 69cc89f7a9c9..80dddbbee5fc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1730,7 +1730,7 @@ S: Maintained F: drivers/net/ethernet/atheros/ ATM -M: Chas Williams +M: Chas Williams <3chas3@gmail.com> L: linux-atm-general@lists.sourceforge.net (moderated for non-subscribers) L: netdev@vger.kernel.org W: http://linux-atm.sourceforge.net -- cgit v1.2.3 From bfda4031621b048ca634abc5f6bce1aa490ac4e5 Mon Sep 17 00:00:00 2001 From: Gregory CLEMENT Date: Fri, 13 Mar 2015 14:41:45 +0100 Subject: MAINTAINERS: Add myself as co-maintainer to the legacy support of the mvebu SoCs I will also take care of the legacy support(not fully converted to DT) of the mvebu SoCs. Signed-off-by: Gregory CLEMENT Acked-by: Andrew Lunn Acked-by: Jason Cooper Signed-off-by: Arnd Bergmann --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index dd6b2383161c..d6910765e99a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1198,6 +1198,7 @@ ARM/Marvell Dove/MV78xx0/Orion SOC support M: Jason Cooper M: Andrew Lunn M: Sebastian Hesselbarth +M: Gregory Clement L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: arch/arm/mach-dove/ -- cgit v1.2.3 From 963ffa3e97d4011cbc545d4236179f227e199f3f Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Sun, 15 Feb 2015 12:22:59 +0800 Subject: MAINTAINERS: add entry for USB OTG FSM Add MAINTAINER entry for USB OTG Finite State Machine Cc: Felipe Balbi Signed-off-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 6239a305dff0..43391d668bf5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10196,6 +10196,13 @@ S: Maintained F: Documentation/usb/ohci.txt F: drivers/usb/host/ohci* +USB OTG FSM (Finite State Machine) +M: Peter Chen +T: git git://github.com/hzpeterchen/linux-usb.git +L: linux-usb@vger.kernel.org +S: Maintained +F: drivers/usb/common/usb-otg-fsm.c + USB OVER IP DRIVER M: Valentina Manea M: Shuah Khan -- cgit v1.2.3